[
  {
    "path": ".cnb/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: QQ 群\n    url: https://docs.hmcl.net/groups.html\n    about: Hello Minecraft! Launcher 的官方 QQ 交流群。\n  - name: Discord 服务器\n    url: https://discord.gg/jVvC7HfM6U\n    about: Hello Minecraft! Launcher 的官方 Discord 服务器。\n"
  },
  {
    "path": ".editorconfig",
    "content": "[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = false\nmax_line_length = 120\ntab_width = 4\nij_continuation_indent_size = 8\nij_formatter_off_tag = @formatter:off\nij_formatter_on_tag = @formatter:on\nij_formatter_tags_enabled = true\nij_smart_tabs = false\nij_visual_guides = none\nij_wrap_on_typing = false\n\n[*.css]\nij_css_align_closing_brace_with_properties = false\nij_css_blank_lines_around_nested_selector = 1\nij_css_blank_lines_between_blocks = 1\nij_css_brace_placement = end_of_line\nij_css_enforce_quotes_on_format = false\nij_css_hex_color_long_format = false\nij_css_hex_color_lower_case = false\nij_css_hex_color_short_format = false\nij_css_hex_color_upper_case = false\nij_css_keep_blank_lines_in_code = 2\nij_css_keep_indents_on_empty_lines = false\nij_css_keep_single_line_blocks = false\nij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow\nij_css_space_after_colon = true\nij_css_space_before_opening_brace = true\nij_css_use_double_quotes = true\nij_css_value_alignment = do_not_align\n\n[*.feature]\nindent_size = 2\nij_gherkin_keep_indents_on_empty_lines = false\n\n[*.gsp]\nij_gsp_keep_indents_on_empty_lines = false\n\n[*.haml]\nindent_size = 2\nij_haml_keep_indents_on_empty_lines = false\n\n[*.java]\nij_java_align_consecutive_assignments = false\nij_java_align_consecutive_variable_declarations = false\nij_java_align_group_field_declarations = false\nij_java_align_multiline_annotation_parameters = false\nij_java_align_multiline_array_initializer_expression = false\nij_java_align_multiline_assignment = false\nij_java_align_multiline_binary_operation = false\nij_java_align_multiline_chained_methods = false\nij_java_align_multiline_extends_list = false\nij_java_align_multiline_for = true\nij_java_align_multiline_method_parentheses = false\nij_java_align_multiline_parameters = true\nij_java_align_multiline_parameters_in_calls = false\nij_java_align_multiline_parenthesized_expression = false\nij_java_align_multiline_records = true\nij_java_align_multiline_resources = true\nij_java_align_multiline_ternary_operation = false\nij_java_align_multiline_text_blocks = false\nij_java_align_multiline_throws_list = false\nij_java_align_subsequent_simple_methods = false\nij_java_align_throws_keyword = false\nij_java_annotation_parameter_wrap = off\nij_java_array_initializer_new_line_after_left_brace = false\nij_java_array_initializer_right_brace_on_new_line = false\nij_java_array_initializer_wrap = off\nij_java_assert_statement_colon_on_next_line = false\nij_java_assert_statement_wrap = off\nij_java_assignment_wrap = off\nij_java_binary_operation_sign_on_next_line = false\nij_java_binary_operation_wrap = off\nij_java_blank_lines_after_anonymous_class_header = 0\nij_java_blank_lines_after_class_header = 0\nij_java_blank_lines_after_imports = 1\nij_java_blank_lines_after_package = 1\nij_java_blank_lines_around_class = 1\nij_java_blank_lines_around_field = 0\nij_java_blank_lines_around_field_in_interface = 0\nij_java_blank_lines_around_initializer = 1\nij_java_blank_lines_around_method = 1\nij_java_blank_lines_around_method_in_interface = 1\nij_java_blank_lines_before_class_end = 0\nij_java_blank_lines_before_imports = 1\nij_java_blank_lines_before_method_body = 0\nij_java_blank_lines_before_package = 0\nij_java_block_brace_style = end_of_line\nij_java_block_comment_at_first_column = true\nij_java_builder_methods = none\nij_java_call_parameters_new_line_after_left_paren = false\nij_java_call_parameters_right_paren_on_new_line = false\nij_java_call_parameters_wrap = off\nij_java_case_statement_on_separate_line = true\nij_java_catch_on_new_line = false\nij_java_class_annotation_wrap = split_into_lines\nij_java_class_brace_style = end_of_line\nij_java_class_count_to_use_import_on_demand = 5\nij_java_class_names_in_javadoc = 1\nij_java_do_not_indent_top_level_class_members = false\nij_java_do_not_wrap_after_single_annotation = false\nij_java_do_while_brace_force = never\nij_java_doc_add_blank_line_after_description = true\nij_java_doc_add_blank_line_after_param_comments = false\nij_java_doc_add_blank_line_after_return = false\nij_java_doc_add_p_tag_on_empty_lines = true\nij_java_doc_align_exception_comments = true\nij_java_doc_align_param_comments = true\nij_java_doc_do_not_wrap_if_one_line = false\nij_java_doc_enable_formatting = true\nij_java_doc_enable_leading_asterisks = true\nij_java_doc_indent_on_continuation = false\nij_java_doc_keep_empty_lines = true\nij_java_doc_keep_empty_parameter_tag = true\nij_java_doc_keep_empty_return_tag = true\nij_java_doc_keep_empty_throws_tag = true\nij_java_doc_keep_invalid_tags = true\nij_java_doc_param_description_on_new_line = false\nij_java_doc_preserve_line_breaks = false\nij_java_doc_use_throws_not_exception_tag = true\nij_java_else_on_new_line = false\nij_java_entity_dd_suffix = EJB\nij_java_entity_eb_suffix = Bean\nij_java_entity_hi_suffix = Home\nij_java_entity_lhi_prefix = Local\nij_java_entity_lhi_suffix = Home\nij_java_entity_li_prefix = Local\nij_java_entity_pk_class = java.lang.String\nij_java_entity_vo_suffix = VO\nij_java_enum_constants_wrap = off\nij_java_extends_keyword_wrap = off\nij_java_extends_list_wrap = off\nij_java_field_annotation_wrap = split_into_lines\nij_java_finally_on_new_line = false\nij_java_for_brace_force = never\nij_java_for_statement_new_line_after_left_paren = false\nij_java_for_statement_right_paren_on_new_line = false\nij_java_for_statement_wrap = off\nij_java_generate_final_locals = false\nij_java_generate_final_parameters = false\nij_java_if_brace_force = never\nij_java_imports_layout = *,|,javax.**,java.**,|,$*\nij_java_indent_case_from_switch = true\nij_java_insert_inner_class_imports = false\nij_java_insert_override_annotation = true\nij_java_keep_blank_lines_before_right_brace = 2\nij_java_keep_blank_lines_between_package_declaration_and_header = 2\nij_java_keep_blank_lines_in_code = 2\nij_java_keep_blank_lines_in_declarations = 2\nij_java_keep_builder_methods_indents = false\nij_java_keep_control_statement_in_one_line = true\nij_java_keep_first_column_comment = true\nij_java_keep_indents_on_empty_lines = false\nij_java_keep_line_breaks = true\nij_java_keep_multiple_expressions_in_one_line = false\nij_java_keep_simple_blocks_in_one_line = false\nij_java_keep_simple_classes_in_one_line = false\nij_java_keep_simple_lambdas_in_one_line = false\nij_java_keep_simple_methods_in_one_line = false\nij_java_label_indent_absolute = false\nij_java_label_indent_size = 0\nij_java_lambda_brace_style = end_of_line\nij_java_layout_static_imports_separately = true\nij_java_line_comment_add_space = false\nij_java_line_comment_at_first_column = true\nij_java_message_dd_suffix = EJB\nij_java_message_eb_suffix = Bean\nij_java_method_annotation_wrap = split_into_lines\nij_java_method_brace_style = end_of_line\nij_java_method_call_chain_wrap = off\nij_java_method_parameters_new_line_after_left_paren = false\nij_java_method_parameters_right_paren_on_new_line = false\nij_java_method_parameters_wrap = off\nij_java_modifier_list_wrap = false\nij_java_names_count_to_use_import_on_demand = 3\nij_java_new_line_after_lparen_in_record_header = false\nij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.*\nij_java_parameter_annotation_wrap = off\nij_java_parentheses_expression_new_line_after_left_paren = false\nij_java_parentheses_expression_right_paren_on_new_line = false\nij_java_place_assignment_sign_on_next_line = false\nij_java_prefer_longer_names = true\nij_java_prefer_parameters_wrap = false\nij_java_record_components_wrap = normal\nij_java_repeat_synchronized = true\nij_java_replace_instanceof_and_cast = false\nij_java_replace_null_check = true\nij_java_replace_sum_lambda_with_method_ref = true\nij_java_resource_list_new_line_after_left_paren = false\nij_java_resource_list_right_paren_on_new_line = false\nij_java_resource_list_wrap = off\nij_java_rparen_on_new_line_in_record_header = false\nij_java_session_dd_suffix = EJB\nij_java_session_eb_suffix = Bean\nij_java_session_hi_suffix = Home\nij_java_session_lhi_prefix = Local\nij_java_session_lhi_suffix = Home\nij_java_session_li_prefix = Local\nij_java_session_si_suffix = Service\nij_java_space_after_closing_angle_bracket_in_type_argument = false\nij_java_space_after_colon = true\nij_java_space_after_comma = true\nij_java_space_after_comma_in_type_arguments = true\nij_java_space_after_for_semicolon = true\nij_java_space_after_quest = true\nij_java_space_after_type_cast = true\nij_java_space_before_annotation_array_initializer_left_brace = false\nij_java_space_before_annotation_parameter_list = false\nij_java_space_before_array_initializer_left_brace = false\nij_java_space_before_catch_keyword = true\nij_java_space_before_catch_left_brace = true\nij_java_space_before_catch_parentheses = true\nij_java_space_before_class_left_brace = true\nij_java_space_before_colon = true\nij_java_space_before_colon_in_foreach = true\nij_java_space_before_comma = false\nij_java_space_before_do_left_brace = true\nij_java_space_before_else_keyword = true\nij_java_space_before_else_left_brace = true\nij_java_space_before_finally_keyword = true\nij_java_space_before_finally_left_brace = true\nij_java_space_before_for_left_brace = true\nij_java_space_before_for_parentheses = true\nij_java_space_before_for_semicolon = false\nij_java_space_before_if_left_brace = true\nij_java_space_before_if_parentheses = true\nij_java_space_before_method_call_parentheses = false\nij_java_space_before_method_left_brace = true\nij_java_space_before_method_parentheses = false\nij_java_space_before_opening_angle_bracket_in_type_parameter = false\nij_java_space_before_quest = true\nij_java_space_before_switch_left_brace = true\nij_java_space_before_switch_parentheses = true\nij_java_space_before_synchronized_left_brace = true\nij_java_space_before_synchronized_parentheses = true\nij_java_space_before_try_left_brace = true\nij_java_space_before_try_parentheses = true\nij_java_space_before_type_parameter_list = false\nij_java_space_before_while_keyword = true\nij_java_space_before_while_left_brace = true\nij_java_space_before_while_parentheses = true\nij_java_space_inside_one_line_enum_braces = false\nij_java_space_within_empty_array_initializer_braces = false\nij_java_space_within_empty_method_call_parentheses = false\nij_java_space_within_empty_method_parentheses = false\nij_java_spaces_around_additive_operators = true\nij_java_spaces_around_assignment_operators = true\nij_java_spaces_around_bitwise_operators = true\nij_java_spaces_around_equality_operators = true\nij_java_spaces_around_lambda_arrow = true\nij_java_spaces_around_logical_operators = true\nij_java_spaces_around_method_ref_dbl_colon = false\nij_java_spaces_around_multiplicative_operators = true\nij_java_spaces_around_relational_operators = true\nij_java_spaces_around_shift_operators = true\nij_java_spaces_around_type_bounds_in_type_parameters = true\nij_java_spaces_around_unary_operator = false\nij_java_spaces_within_angle_brackets = false\nij_java_spaces_within_annotation_parentheses = false\nij_java_spaces_within_array_initializer_braces = false\nij_java_spaces_within_braces = false\nij_java_spaces_within_brackets = false\nij_java_spaces_within_cast_parentheses = false\nij_java_spaces_within_catch_parentheses = false\nij_java_spaces_within_for_parentheses = false\nij_java_spaces_within_if_parentheses = false\nij_java_spaces_within_method_call_parentheses = false\nij_java_spaces_within_method_parentheses = false\nij_java_spaces_within_parentheses = false\nij_java_spaces_within_record_header = false\nij_java_spaces_within_switch_parentheses = false\nij_java_spaces_within_synchronized_parentheses = false\nij_java_spaces_within_try_parentheses = false\nij_java_spaces_within_while_parentheses = false\nij_java_special_else_if_treatment = true\nij_java_subclass_name_suffix = Impl\nij_java_ternary_operation_signs_on_next_line = false\nij_java_ternary_operation_wrap = off\nij_java_test_name_suffix = Test\nij_java_throws_keyword_wrap = off\nij_java_throws_list_wrap = off\nij_java_use_external_annotations = false\nij_java_use_fq_class_names = false\nij_java_use_relative_indents = false\nij_java_use_single_class_imports = true\nij_java_variable_annotation_wrap = off\nij_java_visibility = public\nij_java_while_brace_force = never\nij_java_while_on_new_line = false\nij_java_wrap_comments = false\nij_java_wrap_first_method_in_call_chain = false\nij_java_wrap_long_lines = false\n\n[*.less]\nindent_size = 2\nij_less_align_closing_brace_with_properties = false\nij_less_blank_lines_around_nested_selector = 1\nij_less_blank_lines_between_blocks = 1\nij_less_brace_placement = 0\nij_less_enforce_quotes_on_format = false\nij_less_hex_color_long_format = false\nij_less_hex_color_lower_case = false\nij_less_hex_color_short_format = false\nij_less_hex_color_upper_case = false\nij_less_keep_blank_lines_in_code = 2\nij_less_keep_indents_on_empty_lines = false\nij_less_keep_single_line_blocks = false\nij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow\nij_less_space_after_colon = true\nij_less_space_before_opening_brace = true\nij_less_use_double_quotes = true\nij_less_value_alignment = 0\n\n[*.proto]\nindent_size = 2\ntab_width = 2\nij_continuation_indent_size = 4\nij_protobuf_keep_blank_lines_in_code = 2\nij_protobuf_keep_indents_on_empty_lines = false\nij_protobuf_keep_line_breaks = true\nij_protobuf_space_after_comma = true\nij_protobuf_space_before_comma = false\nij_protobuf_spaces_around_assignment_operators = true\nij_protobuf_spaces_within_braces = false\nij_protobuf_spaces_within_brackets = false\n\n[*.sass]\nindent_size = 2\nij_sass_align_closing_brace_with_properties = false\nij_sass_blank_lines_around_nested_selector = 1\nij_sass_blank_lines_between_blocks = 1\nij_sass_brace_placement = 0\nij_sass_enforce_quotes_on_format = false\nij_sass_hex_color_long_format = false\nij_sass_hex_color_lower_case = false\nij_sass_hex_color_short_format = false\nij_sass_hex_color_upper_case = false\nij_sass_keep_blank_lines_in_code = 2\nij_sass_keep_indents_on_empty_lines = false\nij_sass_keep_single_line_blocks = false\nij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow\nij_sass_space_after_colon = true\nij_sass_space_before_opening_brace = true\nij_sass_use_double_quotes = true\nij_sass_value_alignment = 0\n\n[*.scss]\nindent_size = 2\nij_scss_align_closing_brace_with_properties = false\nij_scss_blank_lines_around_nested_selector = 1\nij_scss_blank_lines_between_blocks = 1\nij_scss_brace_placement = 0\nij_scss_enforce_quotes_on_format = false\nij_scss_hex_color_long_format = false\nij_scss_hex_color_lower_case = false\nij_scss_hex_color_short_format = false\nij_scss_hex_color_upper_case = false\nij_scss_keep_blank_lines_in_code = 2\nij_scss_keep_indents_on_empty_lines = false\nij_scss_keep_single_line_blocks = false\nij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow\nij_scss_space_after_colon = true\nij_scss_space_before_opening_brace = true\nij_scss_use_double_quotes = true\nij_scss_value_alignment = 0\n\n[*.styl]\nindent_size = 2\nij_stylus_align_closing_brace_with_properties = false\nij_stylus_blank_lines_around_nested_selector = 1\nij_stylus_blank_lines_between_blocks = 1\nij_stylus_brace_placement = 0\nij_stylus_enforce_quotes_on_format = false\nij_stylus_hex_color_long_format = false\nij_stylus_hex_color_lower_case = false\nij_stylus_hex_color_short_format = false\nij_stylus_hex_color_upper_case = false\nij_stylus_keep_blank_lines_in_code = 2\nij_stylus_keep_indents_on_empty_lines = false\nij_stylus_keep_single_line_blocks = false\nij_stylus_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow\nij_stylus_space_after_colon = true\nij_stylus_space_before_opening_brace = true\nij_stylus_use_double_quotes = true\nij_stylus_value_alignment = 0\n\n[.editorconfig]\nij_editorconfig_align_group_field_declarations = false\nij_editorconfig_space_after_colon = false\nij_editorconfig_space_after_comma = true\nij_editorconfig_space_before_colon = false\nij_editorconfig_space_before_comma = false\nij_editorconfig_spaces_around_assignment_operators = true\n\n[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.wsdd,*.wsdl,*.xjb,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]\nij_xml_align_attributes = true\nij_xml_align_text = false\nij_xml_attribute_wrap = normal\nij_xml_block_comment_at_first_column = true\nij_xml_keep_blank_lines = 2\nij_xml_keep_indents_on_empty_lines = false\nij_xml_keep_line_breaks = true\nij_xml_keep_line_breaks_in_text = true\nij_xml_keep_whitespaces = false\nij_xml_keep_whitespaces_around_cdata = preserve\nij_xml_keep_whitespaces_inside_cdata = false\nij_xml_line_comment_at_first_column = true\nij_xml_space_after_tag_name = false\nij_xml_space_around_equals_in_attribute = false\nij_xml_space_inside_empty_tag = false\nij_xml_text_wrap = normal\nij_xml_use_custom_settings = false\n\n[{*.ats,*.ts}]\nij_continuation_indent_size = 4\nij_typescript_align_imports = false\nij_typescript_align_multiline_array_initializer_expression = false\nij_typescript_align_multiline_binary_operation = false\nij_typescript_align_multiline_chained_methods = false\nij_typescript_align_multiline_extends_list = false\nij_typescript_align_multiline_for = true\nij_typescript_align_multiline_parameters = true\nij_typescript_align_multiline_parameters_in_calls = false\nij_typescript_align_multiline_ternary_operation = false\nij_typescript_align_object_properties = 0\nij_typescript_align_union_types = false\nij_typescript_align_var_statements = 0\nij_typescript_array_initializer_new_line_after_left_brace = false\nij_typescript_array_initializer_right_brace_on_new_line = false\nij_typescript_array_initializer_wrap = off\nij_typescript_assignment_wrap = off\nij_typescript_binary_operation_sign_on_next_line = false\nij_typescript_binary_operation_wrap = off\nij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/**\nij_typescript_blank_lines_after_imports = 1\nij_typescript_blank_lines_around_class = 1\nij_typescript_blank_lines_around_field = 0\nij_typescript_blank_lines_around_field_in_interface = 0\nij_typescript_blank_lines_around_function = 1\nij_typescript_blank_lines_around_method = 1\nij_typescript_blank_lines_around_method_in_interface = 1\nij_typescript_block_brace_style = end_of_line\nij_typescript_call_parameters_new_line_after_left_paren = false\nij_typescript_call_parameters_right_paren_on_new_line = false\nij_typescript_call_parameters_wrap = off\nij_typescript_catch_on_new_line = false\nij_typescript_chained_call_dot_on_new_line = true\nij_typescript_class_brace_style = end_of_line\nij_typescript_comma_on_new_line = false\nij_typescript_do_while_brace_force = never\nij_typescript_else_on_new_line = false\nij_typescript_enforce_trailing_comma = keep\nij_typescript_extends_keyword_wrap = off\nij_typescript_extends_list_wrap = off\nij_typescript_field_prefix = _\nij_typescript_file_name_style = relaxed\nij_typescript_finally_on_new_line = false\nij_typescript_for_brace_force = never\nij_typescript_for_statement_new_line_after_left_paren = false\nij_typescript_for_statement_right_paren_on_new_line = false\nij_typescript_for_statement_wrap = off\nij_typescript_force_quote_style = false\nij_typescript_force_semicolon_style = false\nij_typescript_function_expression_brace_style = end_of_line\nij_typescript_if_brace_force = never\nij_typescript_import_merge_members = global\nij_typescript_import_prefer_absolute_path = global\nij_typescript_import_sort_members = true\nij_typescript_import_sort_module_name = false\nij_typescript_import_use_node_resolution = true\nij_typescript_imports_wrap = on_every_item\nij_typescript_indent_case_from_switch = true\nij_typescript_indent_chained_calls = true\nij_typescript_indent_package_children = 0\nij_typescript_jsdoc_include_types = false\nij_typescript_jsx_attribute_value = braces\nij_typescript_keep_blank_lines_in_code = 2\nij_typescript_keep_first_column_comment = true\nij_typescript_keep_indents_on_empty_lines = false\nij_typescript_keep_line_breaks = true\nij_typescript_keep_simple_blocks_in_one_line = false\nij_typescript_keep_simple_methods_in_one_line = false\nij_typescript_line_comment_add_space = true\nij_typescript_line_comment_at_first_column = false\nij_typescript_method_brace_style = end_of_line\nij_typescript_method_call_chain_wrap = off\nij_typescript_method_parameters_new_line_after_left_paren = false\nij_typescript_method_parameters_right_paren_on_new_line = false\nij_typescript_method_parameters_wrap = off\nij_typescript_object_literal_wrap = on_every_item\nij_typescript_parentheses_expression_new_line_after_left_paren = false\nij_typescript_parentheses_expression_right_paren_on_new_line = false\nij_typescript_place_assignment_sign_on_next_line = false\nij_typescript_prefer_as_type_cast = false\nij_typescript_prefer_explicit_types_function_expression_returns = false\nij_typescript_prefer_explicit_types_function_returns = false\nij_typescript_prefer_explicit_types_vars_fields = false\nij_typescript_prefer_parameters_wrap = false\nij_typescript_reformat_c_style_comments = false\nij_typescript_space_after_colon = true\nij_typescript_space_after_comma = true\nij_typescript_space_after_dots_in_rest_parameter = false\nij_typescript_space_after_generator_mult = true\nij_typescript_space_after_property_colon = true\nij_typescript_space_after_quest = true\nij_typescript_space_after_type_colon = true\nij_typescript_space_after_unary_not = false\nij_typescript_space_before_async_arrow_lparen = true\nij_typescript_space_before_catch_keyword = true\nij_typescript_space_before_catch_left_brace = true\nij_typescript_space_before_catch_parentheses = true\nij_typescript_space_before_class_lbrace = true\nij_typescript_space_before_class_left_brace = true\nij_typescript_space_before_colon = true\nij_typescript_space_before_comma = false\nij_typescript_space_before_do_left_brace = true\nij_typescript_space_before_else_keyword = true\nij_typescript_space_before_else_left_brace = true\nij_typescript_space_before_finally_keyword = true\nij_typescript_space_before_finally_left_brace = true\nij_typescript_space_before_for_left_brace = true\nij_typescript_space_before_for_parentheses = true\nij_typescript_space_before_for_semicolon = false\nij_typescript_space_before_function_left_parenth = true\nij_typescript_space_before_generator_mult = false\nij_typescript_space_before_if_left_brace = true\nij_typescript_space_before_if_parentheses = true\nij_typescript_space_before_method_call_parentheses = false\nij_typescript_space_before_method_left_brace = true\nij_typescript_space_before_method_parentheses = false\nij_typescript_space_before_property_colon = false\nij_typescript_space_before_quest = true\nij_typescript_space_before_switch_left_brace = true\nij_typescript_space_before_switch_parentheses = true\nij_typescript_space_before_try_left_brace = true\nij_typescript_space_before_type_colon = false\nij_typescript_space_before_unary_not = false\nij_typescript_space_before_while_keyword = true\nij_typescript_space_before_while_left_brace = true\nij_typescript_space_before_while_parentheses = true\nij_typescript_spaces_around_additive_operators = true\nij_typescript_spaces_around_arrow_function_operator = true\nij_typescript_spaces_around_assignment_operators = true\nij_typescript_spaces_around_bitwise_operators = true\nij_typescript_spaces_around_equality_operators = true\nij_typescript_spaces_around_logical_operators = true\nij_typescript_spaces_around_multiplicative_operators = true\nij_typescript_spaces_around_relational_operators = true\nij_typescript_spaces_around_shift_operators = true\nij_typescript_spaces_around_unary_operator = false\nij_typescript_spaces_within_array_initializer_brackets = false\nij_typescript_spaces_within_brackets = false\nij_typescript_spaces_within_catch_parentheses = false\nij_typescript_spaces_within_for_parentheses = false\nij_typescript_spaces_within_if_parentheses = false\nij_typescript_spaces_within_imports = false\nij_typescript_spaces_within_interpolation_expressions = false\nij_typescript_spaces_within_method_call_parentheses = false\nij_typescript_spaces_within_method_parentheses = false\nij_typescript_spaces_within_object_literal_braces = false\nij_typescript_spaces_within_object_type_braces = true\nij_typescript_spaces_within_parentheses = false\nij_typescript_spaces_within_switch_parentheses = false\nij_typescript_spaces_within_type_assertion = false\nij_typescript_spaces_within_union_types = true\nij_typescript_spaces_within_while_parentheses = false\nij_typescript_special_else_if_treatment = true\nij_typescript_ternary_operation_signs_on_next_line = false\nij_typescript_ternary_operation_wrap = off\nij_typescript_union_types_wrap = on_every_item\nij_typescript_use_chained_calls_group_indents = false\nij_typescript_use_double_quotes = true\nij_typescript_use_explicit_js_extension = global\nij_typescript_use_path_mapping = always\nij_typescript_use_public_modifier = false\nij_typescript_use_semicolon_after_statement = true\nij_typescript_var_declaration_wrap = normal\nij_typescript_while_brace_force = never\nij_typescript_while_on_new_line = false\nij_typescript_wrap_comments = false\n\n[{*.bash,*.sh,*.zsh}]\nindent_size = 2\ntab_width = 2\nij_shell_binary_ops_start_line = false\nij_shell_keep_column_alignment_padding = false\nij_shell_minify_program = false\nij_shell_redirect_followed_by_space = false\nij_shell_switch_cases_indented = false\nij_shell_use_unix_line_separator = true\n\n[{*.cjs,*.js}]\nij_continuation_indent_size = 4\nij_javascript_align_imports = false\nij_javascript_align_multiline_array_initializer_expression = false\nij_javascript_align_multiline_binary_operation = false\nij_javascript_align_multiline_chained_methods = false\nij_javascript_align_multiline_extends_list = false\nij_javascript_align_multiline_for = true\nij_javascript_align_multiline_parameters = true\nij_javascript_align_multiline_parameters_in_calls = false\nij_javascript_align_multiline_ternary_operation = false\nij_javascript_align_object_properties = 0\nij_javascript_align_union_types = false\nij_javascript_align_var_statements = 0\nij_javascript_array_initializer_new_line_after_left_brace = false\nij_javascript_array_initializer_right_brace_on_new_line = false\nij_javascript_array_initializer_wrap = off\nij_javascript_assignment_wrap = off\nij_javascript_binary_operation_sign_on_next_line = false\nij_javascript_binary_operation_wrap = off\nij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/**\nij_javascript_blank_lines_after_imports = 1\nij_javascript_blank_lines_around_class = 1\nij_javascript_blank_lines_around_field = 0\nij_javascript_blank_lines_around_function = 1\nij_javascript_blank_lines_around_method = 1\nij_javascript_block_brace_style = end_of_line\nij_javascript_call_parameters_new_line_after_left_paren = false\nij_javascript_call_parameters_right_paren_on_new_line = false\nij_javascript_call_parameters_wrap = off\nij_javascript_catch_on_new_line = false\nij_javascript_chained_call_dot_on_new_line = true\nij_javascript_class_brace_style = end_of_line\nij_javascript_comma_on_new_line = false\nij_javascript_do_while_brace_force = never\nij_javascript_else_on_new_line = false\nij_javascript_enforce_trailing_comma = keep\nij_javascript_extends_keyword_wrap = off\nij_javascript_extends_list_wrap = off\nij_javascript_field_prefix = _\nij_javascript_file_name_style = relaxed\nij_javascript_finally_on_new_line = false\nij_javascript_for_brace_force = never\nij_javascript_for_statement_new_line_after_left_paren = false\nij_javascript_for_statement_right_paren_on_new_line = false\nij_javascript_for_statement_wrap = off\nij_javascript_force_quote_style = false\nij_javascript_force_semicolon_style = false\nij_javascript_function_expression_brace_style = end_of_line\nij_javascript_if_brace_force = never\nij_javascript_import_merge_members = global\nij_javascript_import_prefer_absolute_path = global\nij_javascript_import_sort_members = true\nij_javascript_import_sort_module_name = false\nij_javascript_import_use_node_resolution = true\nij_javascript_imports_wrap = on_every_item\nij_javascript_indent_case_from_switch = true\nij_javascript_indent_chained_calls = true\nij_javascript_indent_package_children = 0\nij_javascript_jsx_attribute_value = braces\nij_javascript_keep_blank_lines_in_code = 2\nij_javascript_keep_first_column_comment = true\nij_javascript_keep_indents_on_empty_lines = false\nij_javascript_keep_line_breaks = true\nij_javascript_keep_simple_blocks_in_one_line = false\nij_javascript_keep_simple_methods_in_one_line = false\nij_javascript_line_comment_add_space = true\nij_javascript_line_comment_at_first_column = false\nij_javascript_method_brace_style = end_of_line\nij_javascript_method_call_chain_wrap = off\nij_javascript_method_parameters_new_line_after_left_paren = false\nij_javascript_method_parameters_right_paren_on_new_line = false\nij_javascript_method_parameters_wrap = off\nij_javascript_object_literal_wrap = on_every_item\nij_javascript_parentheses_expression_new_line_after_left_paren = false\nij_javascript_parentheses_expression_right_paren_on_new_line = false\nij_javascript_place_assignment_sign_on_next_line = false\nij_javascript_prefer_as_type_cast = false\nij_javascript_prefer_explicit_types_function_expression_returns = false\nij_javascript_prefer_explicit_types_function_returns = false\nij_javascript_prefer_explicit_types_vars_fields = false\nij_javascript_prefer_parameters_wrap = false\nij_javascript_reformat_c_style_comments = false\nij_javascript_space_after_colon = true\nij_javascript_space_after_comma = true\nij_javascript_space_after_dots_in_rest_parameter = false\nij_javascript_space_after_generator_mult = true\nij_javascript_space_after_property_colon = true\nij_javascript_space_after_quest = true\nij_javascript_space_after_type_colon = true\nij_javascript_space_after_unary_not = false\nij_javascript_space_before_async_arrow_lparen = true\nij_javascript_space_before_catch_keyword = true\nij_javascript_space_before_catch_left_brace = true\nij_javascript_space_before_catch_parentheses = true\nij_javascript_space_before_class_lbrace = true\nij_javascript_space_before_class_left_brace = true\nij_javascript_space_before_colon = true\nij_javascript_space_before_comma = false\nij_javascript_space_before_do_left_brace = true\nij_javascript_space_before_else_keyword = true\nij_javascript_space_before_else_left_brace = true\nij_javascript_space_before_finally_keyword = true\nij_javascript_space_before_finally_left_brace = true\nij_javascript_space_before_for_left_brace = true\nij_javascript_space_before_for_parentheses = true\nij_javascript_space_before_for_semicolon = false\nij_javascript_space_before_function_left_parenth = true\nij_javascript_space_before_generator_mult = false\nij_javascript_space_before_if_left_brace = true\nij_javascript_space_before_if_parentheses = true\nij_javascript_space_before_method_call_parentheses = false\nij_javascript_space_before_method_left_brace = true\nij_javascript_space_before_method_parentheses = false\nij_javascript_space_before_property_colon = false\nij_javascript_space_before_quest = true\nij_javascript_space_before_switch_left_brace = true\nij_javascript_space_before_switch_parentheses = true\nij_javascript_space_before_try_left_brace = true\nij_javascript_space_before_type_colon = false\nij_javascript_space_before_unary_not = false\nij_javascript_space_before_while_keyword = true\nij_javascript_space_before_while_left_brace = true\nij_javascript_space_before_while_parentheses = true\nij_javascript_spaces_around_additive_operators = true\nij_javascript_spaces_around_arrow_function_operator = true\nij_javascript_spaces_around_assignment_operators = true\nij_javascript_spaces_around_bitwise_operators = true\nij_javascript_spaces_around_equality_operators = true\nij_javascript_spaces_around_logical_operators = true\nij_javascript_spaces_around_multiplicative_operators = true\nij_javascript_spaces_around_relational_operators = true\nij_javascript_spaces_around_shift_operators = true\nij_javascript_spaces_around_unary_operator = false\nij_javascript_spaces_within_array_initializer_brackets = false\nij_javascript_spaces_within_brackets = false\nij_javascript_spaces_within_catch_parentheses = false\nij_javascript_spaces_within_for_parentheses = false\nij_javascript_spaces_within_if_parentheses = false\nij_javascript_spaces_within_imports = false\nij_javascript_spaces_within_interpolation_expressions = false\nij_javascript_spaces_within_method_call_parentheses = false\nij_javascript_spaces_within_method_parentheses = false\nij_javascript_spaces_within_object_literal_braces = false\nij_javascript_spaces_within_object_type_braces = true\nij_javascript_spaces_within_parentheses = false\nij_javascript_spaces_within_switch_parentheses = false\nij_javascript_spaces_within_type_assertion = false\nij_javascript_spaces_within_union_types = true\nij_javascript_spaces_within_while_parentheses = false\nij_javascript_special_else_if_treatment = true\nij_javascript_ternary_operation_signs_on_next_line = false\nij_javascript_ternary_operation_wrap = off\nij_javascript_union_types_wrap = on_every_item\nij_javascript_use_chained_calls_group_indents = false\nij_javascript_use_double_quotes = true\nij_javascript_use_explicit_js_extension = global\nij_javascript_use_path_mapping = always\nij_javascript_use_public_modifier = false\nij_javascript_use_semicolon_after_statement = true\nij_javascript_var_declaration_wrap = normal\nij_javascript_while_brace_force = never\nij_javascript_while_on_new_line = false\nij_javascript_wrap_comments = false\n\n[{*.ft,*.vm,*.vsl}]\nij_vtl_keep_indents_on_empty_lines = false\n\n[{*.gant,*.gradle,*.groovy,*.gson,*.gy}]\nij_groovy_align_group_field_declarations = false\nij_groovy_align_multiline_array_initializer_expression = false\nij_groovy_align_multiline_assignment = false\nij_groovy_align_multiline_binary_operation = false\nij_groovy_align_multiline_chained_methods = false\nij_groovy_align_multiline_extends_list = false\nij_groovy_align_multiline_for = true\nij_groovy_align_multiline_list_or_map = true\nij_groovy_align_multiline_method_parentheses = false\nij_groovy_align_multiline_parameters = true\nij_groovy_align_multiline_parameters_in_calls = false\nij_groovy_align_multiline_resources = true\nij_groovy_align_multiline_ternary_operation = false\nij_groovy_align_multiline_throws_list = false\nij_groovy_align_named_args_in_map = true\nij_groovy_align_throws_keyword = false\nij_groovy_array_initializer_new_line_after_left_brace = false\nij_groovy_array_initializer_right_brace_on_new_line = false\nij_groovy_array_initializer_wrap = off\nij_groovy_assert_statement_wrap = off\nij_groovy_assignment_wrap = off\nij_groovy_binary_operation_wrap = off\nij_groovy_blank_lines_after_class_header = 0\nij_groovy_blank_lines_after_imports = 1\nij_groovy_blank_lines_after_package = 1\nij_groovy_blank_lines_around_class = 1\nij_groovy_blank_lines_around_field = 0\nij_groovy_blank_lines_around_field_in_interface = 0\nij_groovy_blank_lines_around_method = 1\nij_groovy_blank_lines_around_method_in_interface = 1\nij_groovy_blank_lines_before_imports = 1\nij_groovy_blank_lines_before_method_body = 0\nij_groovy_blank_lines_before_package = 0\nij_groovy_block_brace_style = end_of_line\nij_groovy_block_comment_at_first_column = true\nij_groovy_call_parameters_new_line_after_left_paren = false\nij_groovy_call_parameters_right_paren_on_new_line = false\nij_groovy_call_parameters_wrap = off\nij_groovy_catch_on_new_line = false\nij_groovy_class_annotation_wrap = split_into_lines\nij_groovy_class_brace_style = end_of_line\nij_groovy_class_count_to_use_import_on_demand = 5\nij_groovy_do_while_brace_force = never\nij_groovy_else_on_new_line = false\nij_groovy_enum_constants_wrap = off\nij_groovy_extends_keyword_wrap = off\nij_groovy_extends_list_wrap = off\nij_groovy_field_annotation_wrap = split_into_lines\nij_groovy_finally_on_new_line = false\nij_groovy_for_brace_force = never\nij_groovy_for_statement_new_line_after_left_paren = false\nij_groovy_for_statement_right_paren_on_new_line = false\nij_groovy_for_statement_wrap = off\nij_groovy_if_brace_force = never\nij_groovy_import_annotation_wrap = 2\nij_groovy_imports_layout = *,|,javax.**,java.**,|,$*\nij_groovy_indent_case_from_switch = true\nij_groovy_indent_label_blocks = true\nij_groovy_insert_inner_class_imports = false\nij_groovy_keep_blank_lines_before_right_brace = 2\nij_groovy_keep_blank_lines_in_code = 2\nij_groovy_keep_blank_lines_in_declarations = 2\nij_groovy_keep_control_statement_in_one_line = true\nij_groovy_keep_first_column_comment = true\nij_groovy_keep_indents_on_empty_lines = false\nij_groovy_keep_line_breaks = true\nij_groovy_keep_multiple_expressions_in_one_line = false\nij_groovy_keep_simple_blocks_in_one_line = false\nij_groovy_keep_simple_classes_in_one_line = true\nij_groovy_keep_simple_lambdas_in_one_line = true\nij_groovy_keep_simple_methods_in_one_line = true\nij_groovy_label_indent_absolute = false\nij_groovy_label_indent_size = 0\nij_groovy_lambda_brace_style = end_of_line\nij_groovy_layout_static_imports_separately = true\nij_groovy_line_comment_add_space = false\nij_groovy_line_comment_at_first_column = true\nij_groovy_method_annotation_wrap = split_into_lines\nij_groovy_method_brace_style = end_of_line\nij_groovy_method_call_chain_wrap = off\nij_groovy_method_parameters_new_line_after_left_paren = false\nij_groovy_method_parameters_right_paren_on_new_line = false\nij_groovy_method_parameters_wrap = off\nij_groovy_modifier_list_wrap = false\nij_groovy_names_count_to_use_import_on_demand = 3\nij_groovy_parameter_annotation_wrap = off\nij_groovy_parentheses_expression_new_line_after_left_paren = false\nij_groovy_parentheses_expression_right_paren_on_new_line = false\nij_groovy_prefer_parameters_wrap = false\nij_groovy_resource_list_new_line_after_left_paren = false\nij_groovy_resource_list_right_paren_on_new_line = false\nij_groovy_resource_list_wrap = off\nij_groovy_space_after_assert_separator = true\nij_groovy_space_after_colon = true\nij_groovy_space_after_comma = true\nij_groovy_space_after_comma_in_type_arguments = true\nij_groovy_space_after_for_semicolon = true\nij_groovy_space_after_quest = true\nij_groovy_space_after_type_cast = true\nij_groovy_space_before_annotation_parameter_list = false\nij_groovy_space_before_array_initializer_left_brace = false\nij_groovy_space_before_assert_separator = false\nij_groovy_space_before_catch_keyword = true\nij_groovy_space_before_catch_left_brace = true\nij_groovy_space_before_catch_parentheses = true\nij_groovy_space_before_class_left_brace = true\nij_groovy_space_before_closure_left_brace = true\nij_groovy_space_before_colon = true\nij_groovy_space_before_comma = false\nij_groovy_space_before_do_left_brace = true\nij_groovy_space_before_else_keyword = true\nij_groovy_space_before_else_left_brace = true\nij_groovy_space_before_finally_keyword = true\nij_groovy_space_before_finally_left_brace = true\nij_groovy_space_before_for_left_brace = true\nij_groovy_space_before_for_parentheses = true\nij_groovy_space_before_for_semicolon = false\nij_groovy_space_before_if_left_brace = true\nij_groovy_space_before_if_parentheses = true\nij_groovy_space_before_method_call_parentheses = false\nij_groovy_space_before_method_left_brace = true\nij_groovy_space_before_method_parentheses = false\nij_groovy_space_before_quest = true\nij_groovy_space_before_switch_left_brace = true\nij_groovy_space_before_switch_parentheses = true\nij_groovy_space_before_synchronized_left_brace = true\nij_groovy_space_before_synchronized_parentheses = true\nij_groovy_space_before_try_left_brace = true\nij_groovy_space_before_try_parentheses = true\nij_groovy_space_before_while_keyword = true\nij_groovy_space_before_while_left_brace = true\nij_groovy_space_before_while_parentheses = true\nij_groovy_space_in_named_argument = true\nij_groovy_space_in_named_argument_before_colon = false\nij_groovy_space_within_empty_array_initializer_braces = false\nij_groovy_space_within_empty_method_call_parentheses = false\nij_groovy_spaces_around_additive_operators = true\nij_groovy_spaces_around_assignment_operators = true\nij_groovy_spaces_around_bitwise_operators = true\nij_groovy_spaces_around_equality_operators = true\nij_groovy_spaces_around_lambda_arrow = true\nij_groovy_spaces_around_logical_operators = true\nij_groovy_spaces_around_multiplicative_operators = true\nij_groovy_spaces_around_regex_operators = true\nij_groovy_spaces_around_relational_operators = true\nij_groovy_spaces_around_shift_operators = true\nij_groovy_spaces_within_annotation_parentheses = false\nij_groovy_spaces_within_array_initializer_braces = false\nij_groovy_spaces_within_braces = true\nij_groovy_spaces_within_brackets = false\nij_groovy_spaces_within_cast_parentheses = false\nij_groovy_spaces_within_catch_parentheses = false\nij_groovy_spaces_within_for_parentheses = false\nij_groovy_spaces_within_gstring_injection_braces = false\nij_groovy_spaces_within_if_parentheses = false\nij_groovy_spaces_within_list_or_map = false\nij_groovy_spaces_within_method_call_parentheses = false\nij_groovy_spaces_within_method_parentheses = false\nij_groovy_spaces_within_parentheses = false\nij_groovy_spaces_within_switch_parentheses = false\nij_groovy_spaces_within_synchronized_parentheses = false\nij_groovy_spaces_within_try_parentheses = false\nij_groovy_spaces_within_tuple_expression = false\nij_groovy_spaces_within_while_parentheses = false\nij_groovy_special_else_if_treatment = true\nij_groovy_ternary_operation_wrap = off\nij_groovy_throws_keyword_wrap = off\nij_groovy_throws_list_wrap = off\nij_groovy_use_flying_geese_braces = false\nij_groovy_use_fq_class_names = false\nij_groovy_use_fq_class_names_in_javadoc = true\nij_groovy_use_relative_indents = false\nij_groovy_use_single_class_imports = true\nij_groovy_variable_annotation_wrap = off\nij_groovy_while_brace_force = never\nij_groovy_while_on_new_line = false\nij_groovy_wrap_chain_calls_after_dot = false\nij_groovy_wrap_long_lines = false\n\n[{*.gradle.kts,*.kt,*.kts,*.main.kts,*.space.kts}]\nij_kotlin_align_in_columns_case_branch = false\nij_kotlin_align_multiline_binary_operation = false\nij_kotlin_align_multiline_extends_list = false\nij_kotlin_align_multiline_method_parentheses = false\nij_kotlin_align_multiline_parameters = true\nij_kotlin_align_multiline_parameters_in_calls = false\nij_kotlin_allow_trailing_comma = false\nij_kotlin_allow_trailing_comma_on_call_site = false\nij_kotlin_assignment_wrap = normal\nij_kotlin_blank_lines_after_class_header = 0\nij_kotlin_blank_lines_around_block_when_branches = 0\nij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1\nij_kotlin_block_comment_at_first_column = true\nij_kotlin_call_parameters_new_line_after_left_paren = true\nij_kotlin_call_parameters_right_paren_on_new_line = true\nij_kotlin_call_parameters_wrap = on_every_item\nij_kotlin_catch_on_new_line = false\nij_kotlin_class_annotation_wrap = split_into_lines\nij_kotlin_code_style_defaults = KOTLIN_OFFICIAL\nij_kotlin_continuation_indent_for_chained_calls = false\nij_kotlin_continuation_indent_for_expression_bodies = false\nij_kotlin_continuation_indent_in_argument_lists = false\nij_kotlin_continuation_indent_in_elvis = false\nij_kotlin_continuation_indent_in_if_conditions = false\nij_kotlin_continuation_indent_in_parameter_lists = false\nij_kotlin_continuation_indent_in_supertype_lists = false\nij_kotlin_else_on_new_line = false\nij_kotlin_enum_constants_wrap = off\nij_kotlin_extends_list_wrap = normal\nij_kotlin_field_annotation_wrap = split_into_lines\nij_kotlin_finally_on_new_line = false\nij_kotlin_if_rparen_on_new_line = true\nij_kotlin_import_nested_classes = false\nij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^\nij_kotlin_insert_whitespaces_in_simple_one_line_method = true\nij_kotlin_keep_blank_lines_before_right_brace = 2\nij_kotlin_keep_blank_lines_in_code = 2\nij_kotlin_keep_blank_lines_in_declarations = 2\nij_kotlin_keep_first_column_comment = true\nij_kotlin_keep_indents_on_empty_lines = false\nij_kotlin_keep_line_breaks = true\nij_kotlin_lbrace_on_next_line = false\nij_kotlin_line_comment_add_space = false\nij_kotlin_line_comment_at_first_column = true\nij_kotlin_method_annotation_wrap = split_into_lines\nij_kotlin_method_call_chain_wrap = normal\nij_kotlin_method_parameters_new_line_after_left_paren = true\nij_kotlin_method_parameters_right_paren_on_new_line = true\nij_kotlin_method_parameters_wrap = on_every_item\nij_kotlin_name_count_to_use_star_import = 5\nij_kotlin_name_count_to_use_star_import_for_members = 3\nij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.**\nij_kotlin_parameter_annotation_wrap = off\nij_kotlin_space_after_comma = true\nij_kotlin_space_after_extend_colon = true\nij_kotlin_space_after_type_colon = true\nij_kotlin_space_before_catch_parentheses = true\nij_kotlin_space_before_comma = false\nij_kotlin_space_before_extend_colon = true\nij_kotlin_space_before_for_parentheses = true\nij_kotlin_space_before_if_parentheses = true\nij_kotlin_space_before_lambda_arrow = true\nij_kotlin_space_before_type_colon = false\nij_kotlin_space_before_when_parentheses = true\nij_kotlin_space_before_while_parentheses = true\nij_kotlin_spaces_around_additive_operators = true\nij_kotlin_spaces_around_assignment_operators = true\nij_kotlin_spaces_around_equality_operators = true\nij_kotlin_spaces_around_function_type_arrow = true\nij_kotlin_spaces_around_logical_operators = true\nij_kotlin_spaces_around_multiplicative_operators = true\nij_kotlin_spaces_around_range = false\nij_kotlin_spaces_around_relational_operators = true\nij_kotlin_spaces_around_unary_operator = false\nij_kotlin_spaces_around_when_arrow = true\nij_kotlin_variable_annotation_wrap = off\nij_kotlin_while_on_new_line = false\nij_kotlin_wrap_elvis_expressions = 1\nij_kotlin_wrap_expression_body_functions = 1\nij_kotlin_wrap_first_method_in_call_chain = false\n\n[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}]\nindent_size = 2\nij_json_keep_blank_lines_in_code = 0\nij_json_keep_indents_on_empty_lines = false\nij_json_keep_line_breaks = true\nij_json_space_after_colon = true\nij_json_space_after_comma = true\nij_json_space_before_colon = true\nij_json_space_before_comma = false\nij_json_spaces_within_braces = false\nij_json_spaces_within_brackets = false\nij_json_wrap_long_lines = false\n\n[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}]\nij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3\nij_html_align_attributes = true\nij_html_align_text = false\nij_html_attribute_wrap = normal\nij_html_block_comment_at_first_column = true\nij_html_do_not_align_children_of_min_lines = 0\nij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p\nij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot\nij_html_enforce_quotes = false\nij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var\nij_html_keep_blank_lines = 2\nij_html_keep_indents_on_empty_lines = false\nij_html_keep_line_breaks = true\nij_html_keep_line_breaks_in_text = true\nij_html_keep_whitespaces = false\nij_html_keep_whitespaces_inside = span,pre,textarea\nij_html_line_comment_at_first_column = true\nij_html_new_line_after_last_attribute = never\nij_html_new_line_before_first_attribute = never\nij_html_quote_style = double\nij_html_remove_new_line_before_tags = br\nij_html_space_after_tag_name = false\nij_html_space_around_equality_in_attribute = false\nij_html_space_inside_empty_tag = false\nij_html_text_wrap = normal\n\n[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}]\nij_jsp_jsp_prefer_comma_separated_import_list = false\nij_jsp_keep_indents_on_empty_lines = false\n\n[{*.jspx,*.tagx}]\nij_jspx_keep_indents_on_empty_lines = false\n\n[{*.markdown,*.md}]\nmax_line_length = 200\nij_markdown_force_one_space_after_blockquote_symbol = true\nij_markdown_force_one_space_after_header_symbol = true\nij_markdown_force_one_space_after_list_bullet = true\nij_markdown_force_one_space_between_words = true\nij_markdown_keep_indents_on_empty_lines = false\nij_markdown_max_lines_around_block_elements = 1\nij_markdown_max_lines_around_header = 1\nij_markdown_max_lines_between_paragraphs = 1\nij_markdown_min_lines_around_block_elements = 1\nij_markdown_min_lines_around_header = 1\nij_markdown_min_lines_between_paragraphs = 1\n\n[{*.pb,*.textproto}]\nindent_size = 2\ntab_width = 2\nij_continuation_indent_size = 4\nij_prototext_keep_blank_lines_in_code = 2\nij_prototext_keep_indents_on_empty_lines = false\nij_prototext_keep_line_breaks = true\nij_prototext_space_after_colon = true\nij_prototext_space_after_comma = true\nij_prototext_space_before_colon = false\nij_prototext_space_before_comma = false\nij_prototext_spaces_within_braces = true\nij_prototext_spaces_within_brackets = false\n\n[{*.properties,spring.handlers,spring.schemas}]\nij_properties_align_group_field_declarations = false\nij_properties_keep_blank_lines = false\nij_properties_key_value_delimiter = equals\nij_properties_spaces_around_key_value_delimiter = false\n\n[{*.yaml,*.yml}]\nindent_size = 2\nij_yaml_align_values_properties = do_not_align\nij_yaml_autoinsert_sequence_marker = true\nij_yaml_block_mapping_on_new_line = false\nij_yaml_indent_sequence_value = true\nij_yaml_keep_indents_on_empty_lines = false\nij_yaml_keep_line_breaks = true\nij_yaml_sequence_on_new_line = false\nij_yaml_space_before_colon = false\nij_yaml_spaces_within_braces = true\nij_yaml_spaces_within_brackets = true\n"
  },
  {
    "path": ".gitee/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: QQ 群\n    url: https://docs.hmcl.net/groups.html\n    about: Hello Minecraft! Launcher 的官方 QQ 交流群。\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "content": "name: Bug 反馈 | Bug Report\ndescription: \n  反馈一个 HMCL 错误。| File a bug report for HMCL.\ntitle: \"[Bug] \"\nlabels: bug\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        提交前请确认：\n\n        * 该问题确实是 **HMCL 的错误**，而**不是 Minecraft 非正常退出**。如果你的 Minecraft 非正常退出，请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。\n        * 你的启动器版本是**最新的预览版本**。你可以从 [GitHub Actions](https://github.com/HMCL-dev/HMCL/actions/workflows/gradle.yml?query=branch%3Amain+event%3Apush) 或 [nightly.link](https://nightly.link/HMCL-dev/HMCL/workflows/gradle/main) 下载最新预览版本。\n\n        如果你的问题并不属于上述两类，你可以选取另一种 Issue 类型，或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。\n\n        Before submitting, please confirm:\n\n        * The issue is indeed **a bug of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.\n        * Your launcher is the **latest nightly build**. You can download the latest nightly build from [GitHub Actions](https://github.com/HMCL-dev/HMCL/actions/workflows/gradle.yml?query=branch%3Amain+event%3Apush) or [nightly.link](https://nightly.link/HMCL-dev/HMCL/workflows/gradle/main).\n\n        If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.\n  - type: textarea\n    id: bug-report\n    attributes:\n      label: 问题描述 | Bug Description\n      description: |\n        请尽可能地详细描述你所遇到的问题，并描述如何重新触发这个问题。\n        Please describe the bug you met in as much detail as possible. Additionally, describe the steps to reproduce this bug.\n      placeholder: |\n        1. 点击 HMCL 上的某个按钮 | Click a button named ...\n        2. 向下翻页 | Scroll down\n        3. ...\n    validations:\n      required: true\n  - type: textarea\n    id: hmcl-crash-report-or-logs\n    attributes:\n      label: 启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File\n      description: |\n        如果你的启动器崩溃了，请将崩溃报告填入 (或将文件拖入) 下方。\n        如果你的启动器没有崩溃，请在遇到问题后**不要退出启动器**，在启动器的 “设置 → 通用 → 调试” 一栏中点击 “导出启动器日志”，并将导出的日志拖到下方的输入栏中。\n        **请注意：启动器崩溃报告或日志文件是诊断问题的重要依据，请务必上传！**\n        \n        If your launcher crashes, please fill in (or drag the file in) the following input field with the crash report.\n        If your launcher does not crash, please DO NOT EXIT your launcher, click \"Export Launcher Logs\" in the \"Settings → General → Debug\" of the launcher, and drag the exported log to the following input field.\n        **ATTENTION: The crash report or log file is the key to resolving the bug. Please upload them!**\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: QQ 群 | QQ Group\n    url: https://docs.hmcl.net/groups.html\n    about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ group of Hello Minecraft! Launcher.\n  - name: Discord 服务器 | Discord Server\n    url: https://discord.gg/jVvC7HfM6U\n    about: Hello Minecraft! Launcher 的官方 Discord 服务器。| The official Discord server of Hello Minecraft! Launcher.\n  - name: 其他反馈 | Others\n    url: https://github.com/HMCL-dev/HMCL/discussions/new/choose\n    about: 通过 Discussions 反馈其他问题。| Report other problems in Discussions.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "content": "name: 新功能 | Feature Request\ndescription: 为 HMCL 提出新功能。| Suggest a new feature or enhancement for HMCL.\ntitle: \"[Feature] \"\nlabels: enhancement\nbody:\n- type: markdown\n  attributes:\n    value: |\n      请确认 Issues 列表无重复的项目。\n      Please make sure that no duplicate issues have already been submitted.\n- type: textarea\n  id: summary\n  attributes:\n    label: 概述 | Summary\n    description: |\n      请介绍你想加入的新功能。\n      Please describe the new feature.\n  validations:\n    required: true\n- type: textarea\n  id: reason\n  attributes:\n    label: 原因 | Reason\n    description: |\n      请描述该功能带来的好处及原因。\n      Please describe why you want to add the feature or enhancement to HMCL.\n  validations:\n    required: true\n- type: textarea\n  id: description\n  attributes:\n    label: 详情 | Description\n    description: |\n      在这里可以补充描述该功能的具体实现方式或建议。（可选）\n      Describe implementation details or suggestions here. (Optional)\n"
  },
  {
    "path": ".github/workflows/check-codes.yml",
    "content": "name: Check Codes\n\non:\n  push:\n    paths:\n      - '**.java'\n      - '**.properties'\n  pull_request:\n    paths:\n      - '**.java'\n      - '**.properties'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up JDK 17\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: '17'\n          java-package: 'jdk+fx'\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n        with:\n          cache-cleanup: never\n      - name: Check Codes\n        run: ./gradlew checkstyle checkTranslations --no-daemon --parallel --stacktrace\n"
  },
  {
    "path": ".github/workflows/gradle.yml",
    "content": "name: Java CI\n\non:\n  push:\n  pull_request:\n    paths-ignore:\n      - '**.md'\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up JDK 17\n      uses: actions/setup-java@v5\n      with:\n        distribution: 'zulu'\n        java-version: '17'\n        java-package: 'jdk+fx'\n    - name: Setup Gradle\n      uses: gradle/actions/setup-gradle@v5\n      with:\n        cache-cleanup: never\n    - name: Build with Gradle\n      run: ./gradlew build --no-daemon --parallel\n      env:\n        MICROSOFT_AUTH_ID: ${{ secrets.MICROSOFT_AUTH_ID }}\n        CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }}\n    - name: Get short SHA\n      run: echo \"SHORT_SHA=${GITHUB_SHA::7}\" >> $GITHUB_ENV\n    - name: Upload JAR\n      uses: actions/upload-artifact@v7\n      with:\n        name: HMCL-${{ env.SHORT_SHA }}-jar\n        path: HMCL/build/libs/HMCL-*.jar\n        archive: false\n    - name: Upload EXE\n      uses: actions/upload-artifact@v7\n      with:\n        name: HMCL-${{ env.SHORT_SHA }}-exe\n        path: HMCL/build/libs/HMCL-*.exe\n        archive: false\n    - name: Upload SH\n      uses: actions/upload-artifact@v7\n      with:\n        name: HMCL-${{ env.SHORT_SHA }}-sh\n        path: HMCL/build/libs/HMCL-*.sh\n        archive: false\n"
  },
  {
    "path": ".github/workflows/mirror.yml",
    "content": "name: Mirror Repository\n\non:\n  workflow_dispatch:\n  push:\n\nconcurrency:\n  group: mirror-repository\n  cancel-in-progress: true\n\njobs:\n  mirror:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: Gitee\n            repo: gitee.com/huanghongxun/HMCL\n            user: 'hmcl-sync'\n            token: 'GITEE_SYNC_TOKEN'\n          - name: CNB\n            repo: cnb.cool/HMCL-dev/HMCL\n            user: 'cnb'\n            token: 'CNB_SYNC_TOKEN'\n    name: Mirror to ${{ matrix.name }}\n    if: ${{ github.repository == 'HMCL-dev/HMCL' }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Mirror GitHub to ${{ matrix.name }}\n        run: |\n          git clone --mirror \"https://github.com/${{ github.repository }}.git\" -- repo\n          cd repo\n          git push -f --prune \"https://${{ matrix.user }}:${{ secrets[matrix.token] }}@${{ matrix.repo }}.git\" \"refs/heads/*:refs/heads/*\" \"refs/tags/*:refs/tags/*\"\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Create Release\n\non:\n  workflow_dispatch:\n#  schedule:\n#    - cron: '30 * * * *'\n\npermissions:\n  contents: write\n\njobs:\n  check-update:\n    if: ${{ github.repository_owner == 'HMCL-dev' }}\n    strategy:\n      fail-fast: false\n      max-parallel: 1\n      matrix:\n        include:\n          - channel: dev\n            task: checkUpdateDev\n          - channel: stable\n            task: checkUpdateStable\n    runs-on: ubuntu-latest\n    name: check-update-${{ matrix.channel }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          fetch-tags: true\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: '25'\n      - name: Fetch last version\n        run: ./gradlew ${{ matrix.task }} --no-daemon --info --stacktrace\n      - name: Check for existing tags\n        run: if [ -z \"$(git tag -l \"$HMCL_TAG_NAME\")\" ]; then echo \"continue=true\" >> $GITHUB_ENV; fi\n      - name: Download artifacts\n        if: ${{ env.continue == 'true' }}\n        run: |\n          wget \"$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.exe\"\n          wget \"$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.exe.sha256\"\n          wget \"$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.jar\"\n          wget \"$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.jar.sha256\"\n          wget \"$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh\"\n          wget \"$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh.sha256\"\n      - name: Verify artifacts\n        if: ${{ env.continue == 'true' }}\n        run: |\n          export JAR_SHA256=$(cat HMCL-$HMCL_VERSION.jar.sha256 | tr -d '\\n')\n          export EXE_SHA256=$(cat HMCL-$HMCL_VERSION.exe.sha256 | tr -d '\\n')\n          export SH_SHA256=$(cat HMCL-$HMCL_VERSION.sh.sha256 | tr -d '\\n')\n          \n          echo \"$JAR_SHA256  HMCL-$HMCL_VERSION.jar\" | sha256sum -c\n          echo \"$EXE_SHA256  HMCL-$HMCL_VERSION.exe\" | sha256sum -c\n          echo \"$SH_SHA256  HMCL-$HMCL_VERSION.sh\" | sha256sum -c\n      - name: Generate release note\n        if: ${{ env.continue == 'true' }}\n        run: |\n          # GitHub Release Note\n          echo \"&nbsp;**[Changelog](https://docs.hmcl.net/changelog/${{ matrix.channel }}.html#HMCL-$HMCL_VERSION)** (Chinese)\" >> RELEASE_NOTE\n          echo \"\" >> RELEASE_NOTE\n          echo \"| File | SHA-256 Checksum |\"  >> RELEASE_NOTE\n          echo \"| ---  | --- |\" >> RELEASE_NOTE\n          echo \"| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \\`$(cat HMCL-$HMCL_VERSION.exe.sha256)\\` |\" >> RELEASE_NOTE\n          echo \"| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \\`$(cat HMCL-$HMCL_VERSION.jar.sha256)\\` |\" >> RELEASE_NOTE\n          echo \"| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh)   | \\`$(cat HMCL-$HMCL_VERSION.sh.sha256)\\` |\"  >> RELEASE_NOTE\n          \n          # CNB Release Note\n          echo \"[更新日志](https://docs.hmcl.net/changelog/${{ matrix.channel }}.html#HMCL-$HMCL_VERSION)\" >> CNB_RELEASE_NOTE\n          echo \"\" >> CNB_RELEASE_NOTE\n          echo \"| 文件 | SHA-256 校验码 |\"  >> CNB_RELEASE_NOTE\n          echo \"| :---  | --- |\" >> CNB_RELEASE_NOTE\n          echo \"| [HMCL-$HMCL_VERSION.exe]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \\`$(cat HMCL-$HMCL_VERSION.exe.sha256)\\` |\" >> CNB_RELEASE_NOTE\n          echo \"| [HMCL-$HMCL_VERSION.jar]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \\`$(cat HMCL-$HMCL_VERSION.jar.sha256)\\` |\" >> CNB_RELEASE_NOTE\n          echo \"| [HMCL-$HMCL_VERSION.sh]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh)   | \\`$(cat HMCL-$HMCL_VERSION.sh.sha256)\\` |\"  >> CNB_RELEASE_NOTE\n        env:\n          GH_DOWNLOAD_BASE_URL: https://github.com/${{ github.repository }}/releases/download\n          CNB_DOWNLOAD_BASE_URL: https://cnb.cool/HMCL-dev/HMCL/-/releases/download\n      - name: Create GitHub release\n        if: ${{ env.continue == 'true' }}\n        run: |\n          gh release create \"${{ env.HMCL_TAG_NAME }}\" \\\n            \"HMCL-${{ env.HMCL_VERSION }}.exe\" \\\n            \"HMCL-${{ env.HMCL_VERSION }}.jar\" \\\n            \"HMCL-${{ env.HMCL_VERSION }}.sh\" \\\n            --target \"${{ env.HMCL_COMMIT_SHA }}\" \\\n            --title \"${{ env.HMCL_TAG_NAME }}\" \\\n            --notes-file RELEASE_NOTE \\\n            --prerelease\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Install git-cnb\n        if: ${{ env.continue == 'true' }}\n        run: go install \"cnb.cool/looc/git-cnb@$GIT_CNB_VERSION\"\n        env:\n          GIT_CNB_VERSION: '1.1.2'\n      - name: Create CNB release\n        if: ${{ env.continue == 'true' }}\n        run: |\n          echo \"Uploading tags to CNB\"\n          git fetch --tags\n          git push \"https://cnb:${{ secrets.CNB_SYNC_TOKEN }}@cnb.cool/$CNB_REPO.git\" \"$HMCL_TAG_NAME\"\n          \n          echo \"Creating CNB release\"\n          ~/go/bin/git-cnb release create \\\n             --repo \"$CNB_REPO\" \\\n             --tag \"$HMCL_TAG_NAME\" \\\n             --name \"HMCL $HMCL_VERSION\" \\\n             --body \"$(cat CNB_RELEASE_NOTE)\" \\\n             --prerelease true\n\n           echo \"Uploading HMCL-$HMCL_VERSION.jar\"\n           ~/go/bin/git-cnb release asset-upload --repo=\"$CNB_REPO\" --tag-name \"$HMCL_TAG_NAME\" --file-name \"HMCL-$HMCL_VERSION.jar\"\n\n           echo \"Uploading HMCL-$HMCL_VERSION.exe\"\n           ~/go/bin/git-cnb release asset-upload --repo=\"$CNB_REPO\" --tag-name \"$HMCL_TAG_NAME\" --file-name \"HMCL-$HMCL_VERSION.exe\"\n\n           echo \"Uploading HMCL-$HMCL_VERSION.sh\"\n           ~/go/bin/git-cnb release asset-upload --repo=\"$CNB_REPO\" --tag-name \"$HMCL_TAG_NAME\" --file-name \"HMCL-$HMCL_VERSION.sh\"\n        env:\n          CNB_TOKEN: ${{ secrets.CNB_SYNC_TOKEN }}\n          CNB_REPO: HMCL-dev/HMCL\n"
  },
  {
    "path": ".gitignore",
    "content": "*.class\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n*.hprof\n\n.gradle\n\n*.lck\n*.1\n*.2\n*.log\n.mine*\n/externalgames\nNVIDIA\nminecraft-exported-crash-info*\nhmcl-exported-logs-*\nterracotta-log-*\n/.java/\n/.local/\n/.cache/\n\n# IANA Language Subtag Registry\nlanguage-subtag-registry\n\n# gradle build\n/build/\n/HMCL/build/\n/HMCLCore/build/\n/HMCLBoot/build/\n/minecraft/libraries/HMCLTransformerDiscoveryService/build/\n/minecraft/libraries/HMCLMultiMCBootstrap/build/\n/buildSrc/build/\n\n# idea\n.idea\n/out/\n/HMCL/out/\n/HMCLCore/out/\n/minecraft/libraries/HMCLTransformerDiscoveryService/out/\n/minecraft/libraries/HMCLMultiMCBootstrap/out/\n\n# eclipse\n/bin/\n/HMCL/bin/\n/HMCLCore/bin/\n/minecraft/libraries/HMCLTransformerDiscoveryService/bin/\n/minecraft/libraries/HMCLMultiMCBootstrap/bin/\n.classpath\n.project\n.settings\n\n# netbeans\n.nb-gradle\n\n*.exe\n\n# macos\n.DS_Store\n\n# vscode\n.vscode/\n\n# test\n/hmcl.json\n/.hmcl.json\n/.hmcl/"
  },
  {
    "path": "HMCL/.gitignore",
    "content": "/data.csv\n/data.json\n/mod.json\n/modpack.json"
  },
  {
    "path": "HMCL/build.gradle.kts",
    "content": "import org.jackhuang.hmcl.gradle.TerracottaConfigUpgradeTask\nimport org.jackhuang.hmcl.gradle.ci.GitHubActionUtils\nimport org.jackhuang.hmcl.gradle.ci.JenkinsUtils\nimport org.jackhuang.hmcl.gradle.l10n.CheckTranslations\nimport org.jackhuang.hmcl.gradle.l10n.CreateLanguageList\nimport org.jackhuang.hmcl.gradle.l10n.CreateLocaleNamesResourceBundle\nimport org.jackhuang.hmcl.gradle.l10n.UpsideDownTranslate\nimport org.jackhuang.hmcl.gradle.mod.ParseModDataTask\nimport org.jackhuang.hmcl.gradle.utils.PropertiesUtils\nimport java.net.URI\nimport java.nio.file.FileSystems\nimport java.nio.file.Files\nimport java.security.KeyFactory\nimport java.security.MessageDigest\nimport java.security.Signature\nimport java.security.spec.PKCS8EncodedKeySpec\nimport java.util.zip.ZipFile\n\nplugins {\n    alias(libs.plugins.shadow)\n}\n\nval projectConfig = PropertiesUtils.load(rootProject.file(\"config/project.properties\").toPath())\n\nval isOfficial = JenkinsUtils.IS_ON_CI || GitHubActionUtils.IS_ON_OFFICIAL_REPO\n\nval versionType = System.getenv(\"VERSION_TYPE\") ?: if (isOfficial) \"nightly\" else \"unofficial\"\nval versionRoot = System.getenv(\"VERSION_ROOT\") ?: projectConfig.getProperty(\"versionRoot\") ?: \"3\"\n\nval microsoftAuthId = System.getenv(\"MICROSOFT_AUTH_ID\") ?: \"\"\nval curseForgeApiKey = System.getenv(\"CURSEFORGE_API_KEY\") ?: \"\"\n\nval launcherExe = System.getenv(\"HMCL_LAUNCHER_EXE\") ?: \"\"\n\nval buildNumber = System.getenv(\"BUILD_NUMBER\")?.toInt()\nif (buildNumber != null) {\n    version = if (JenkinsUtils.IS_ON_CI && versionType == \"dev\") {\n        \"$versionRoot.0.$buildNumber\"\n    } else {\n        \"$versionRoot.$buildNumber\"\n    }\n} else {\n    val shortCommit = System.getenv(\"GITHUB_SHA\")?.lowercase()?.substring(0, 7)\n    version = if (shortCommit.isNullOrBlank()) {\n        \"$versionRoot.SNAPSHOT\"\n    } else if (isOfficial) {\n        \"$versionRoot.dev-$shortCommit\"\n    } else {\n        \"$versionRoot.unofficial-$shortCommit\"\n    }\n}\n\nval embedResources by configurations.registering\n\ndependencies {\n    implementation(project(\":HMCLCore\"))\n    implementation(project(\":HMCLBoot\"))\n    implementation(\"libs:JFoenix\")\n    implementation(libs.twelvemonkeys.imageio.webp)\n    implementation(libs.java.info)\n    implementation(libs.monet.fx)\n    implementation(libs.nayuki.qrcodegen)\n\n    if (launcherExe.isBlank()) {\n        implementation(libs.hmclauncher)\n    }\n\n    embedResources(libs.authlib.injector)\n}\n\nfun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes)\n\nfun createChecksum(file: File) {\n    val algorithms = linkedMapOf(\n        \"SHA-1\" to \"sha1\",\n        \"SHA-256\" to \"sha256\",\n        \"SHA-512\" to \"sha512\"\n    )\n\n    algorithms.forEach { (algorithm, ext) ->\n        File(file.parentFile, \"${file.name}.$ext\").writeText(\n            digest(algorithm, file.readBytes()).joinToString(separator = \"\", postfix = \"\\n\") { \"%02x\".format(it) }\n        )\n    }\n}\n\nfun attachSignature(jar: File) {\n    val keyLocation = System.getenv(\"HMCL_SIGNATURE_KEY\")\n    if (keyLocation == null) {\n        logger.warn(\"Missing signature key\")\n        return\n    }\n\n    val privatekey = KeyFactory.getInstance(\"RSA\").generatePrivate(PKCS8EncodedKeySpec(File(keyLocation).readBytes()))\n    val signer = Signature.getInstance(\"SHA512withRSA\")\n    signer.initSign(privatekey)\n    ZipFile(jar).use { zip ->\n        zip.stream()\n            .sorted(Comparator.comparing { it.name })\n            .filter { it.name != \"META-INF/hmcl_signature\" }\n            .forEach {\n                signer.update(digest(\"SHA-512\", it.name.toByteArray()))\n                signer.update(digest(\"SHA-512\", zip.getInputStream(it).readBytes()))\n            }\n    }\n    val signature = signer.sign()\n    FileSystems.newFileSystem(URI.create(\"jar:\" + jar.toURI()), emptyMap<String, Any>()).use { zipfs ->\n        Files.newOutputStream(zipfs.getPath(\"META-INF/hmcl_signature\")).use { it.write(signature) }\n    }\n}\n\ntasks.withType<JavaCompile> {\n    sourceCompatibility = \"17\"\n    targetCompatibility = \"17\"\n}\n\ntasks.checkstyleMain {\n    // Third-party code is not checked\n    exclude(\"**/org/jackhuang/hmcl/ui/image/apng/**\")\n}\n\nval addOpens = listOf(\n    \"java.base/java.lang\",\n    \"java.base/java.lang.reflect\",\n    \"java.base/jdk.internal.loader\",\n    \"javafx.base/com.sun.javafx.binding\",\n    \"javafx.base/com.sun.javafx.event\",\n    \"javafx.base/com.sun.javafx.runtime\",\n    \"javafx.base/javafx.beans.property\",\n    \"javafx.graphics/javafx.css\",\n    \"javafx.graphics/javafx.stage\",\n    \"javafx.graphics/javafx.scene\",\n    \"javafx.graphics/com.sun.glass.ui\",\n    \"javafx.graphics/com.sun.javafx.stage\",\n    \"javafx.graphics/com.sun.javafx.util\",\n    \"javafx.graphics/com.sun.prism\",\n    \"javafx.controls/com.sun.javafx.scene.control\",\n    \"javafx.controls/com.sun.javafx.scene.control.behavior\",\n    \"javafx.graphics/com.sun.javafx.tk.quantum\",\n    \"javafx.controls/javafx.scene.control.skin\",\n    \"jdk.attach/sun.tools.attach\",\n)\n\ntasks.compileJava {\n    options.compilerArgs.addAll(addOpens.map { \"--add-exports=$it=ALL-UNNAMED\" })\n}\n\nval hmclProperties = buildList {\n    add(\"hmcl.version\" to project.version.toString())\n    add(\"hmcl.add-opens\" to addOpens.joinToString(\" \"))\n    System.getenv(\"GITHUB_SHA\")?.let {\n        add(\"hmcl.version.hash\" to it)\n    }\n    add(\"hmcl.version.type\" to versionType)\n    add(\"hmcl.microsoft.auth.id\" to microsoftAuthId)\n    add(\"hmcl.curseforge.apikey\" to curseForgeApiKey)\n    add(\"hmcl.authlib-injector.version\" to libs.authlib.injector.get().version!!)\n}\n\nval hmclPropertiesFile = layout.buildDirectory.file(\"hmcl.properties\")\nval createPropertiesFile by tasks.registering {\n    outputs.file(hmclPropertiesFile)\n    hmclProperties.forEach { (k, v) -> inputs.property(k, v) }\n\n    doLast {\n        val targetFile = hmclPropertiesFile.get().asFile\n        targetFile.parentFile.mkdir()\n        targetFile.bufferedWriter().use {\n            for ((k, v) in hmclProperties) {\n                it.write(\"$k=$v\\n\")\n            }\n        }\n    }\n}\n\ntasks.jar {\n    enabled = false\n    dependsOn(tasks[\"shadowJar\"])\n}\n\nval jarPath = tasks.jar.get().archiveFile.get().asFile\n\ntasks.shadowJar {\n    dependsOn(createPropertiesFile)\n\n    archiveClassifier.set(null as String?)\n\n    exclude(\"**/package-info.class\")\n    exclude(\"META-INF/maven/**\")\n\n    exclude(\"META-INF/services/javax.imageio.spi.ImageReaderSpi\")\n    exclude(\"META-INF/services/javax.imageio.spi.ImageInputStreamSpi\")\n\n    listOf(\n        \"aix-*\", \"sunos-*\", \"openbsd-*\", \"dragonflybsd-*\", \"freebsd-*\", \"linux-*\", \"darwin-*\",\n        \"*-ppc\", \"*-ppc64le\", \"*-s390x\", \"*-armel\",\n    ).forEach { exclude(\"com/sun/jna/$it/**\") }\n\n    minimize {\n        exclude(dependency(\"com.google.code.gson:.*:.*\"))\n        exclude(dependency(\"net.java.dev.jna:jna:.*\"))\n        exclude(dependency(\"libs:JFoenix:.*\"))\n        exclude(project(\":HMCLBoot\"))\n    }\n\n    manifest.attributes(\n        \"Created-By\" to \"Copyright(c) 2013-2025 huangyuhui.\",\n        \"Implementation-Version\" to project.version.toString(),\n        \"Main-Class\" to \"org.jackhuang.hmcl.Main\",\n        \"Multi-Release\" to \"true\",\n        \"Add-Opens\" to addOpens.joinToString(\" \"),\n        \"Enable-Native-Access\" to \"ALL-UNNAMED\",\n        \"Enable-Final-Field-Mutation\" to \"ALL-UNNAMED\",\n    )\n\n    if (launcherExe.isNotBlank()) {\n        into(\"assets\") {\n            from(file(launcherExe))\n        }\n    }\n\n    doLast {\n        attachSignature(jarPath)\n        createChecksum(jarPath)\n    }\n}\n\ntasks.processResources {\n    dependsOn(createPropertiesFile)\n    dependsOn(upsideDownTranslate)\n    dependsOn(createLocaleNamesResourceBundle)\n    dependsOn(createLanguageList)\n\n    into(\"assets/\") {\n        from(hmclPropertiesFile)\n        from(embedResources)\n    }\n\n    into(\"assets/lang\") {\n        from(createLanguageList.map { it.outputFile })\n        from(upsideDownTranslate.map { it.outputFile })\n        from(createLocaleNamesResourceBundle.map { it.outputDirectory })\n    }\n\n    inputs.property(\"terracotta_version\", libs.versions.terracotta)\n    doLast {\n        upgradeTerracottaConfig.get().checkValid()\n    }\n}\n\nval makeExecutables by tasks.registering {\n    val extensions = listOf(\"exe\", \"sh\")\n\n    dependsOn(tasks.jar)\n\n    inputs.file(jarPath)\n    outputs.files(extensions.map { File(jarPath.parentFile, jarPath.nameWithoutExtension + '.' + it) })\n\n    doLast {\n        val jarContent = jarPath.readBytes()\n\n        ZipFile(jarPath).use { zipFile ->\n            for (extension in extensions) {\n                val output = File(jarPath.parentFile, jarPath.nameWithoutExtension + '.' + extension)\n                val entry = zipFile.getEntry(\"assets/HMCLauncher.$extension\")\n                    ?: throw GradleException(\"HMCLauncher.$extension not found\")\n\n                output.outputStream().use { outputStream ->\n                    zipFile.getInputStream(entry).use { it.copyTo(outputStream) }\n                    outputStream.write(jarContent)\n                }\n\n                createChecksum(output)\n            }\n        }\n    }\n}\n\ntasks.build {\n    dependsOn(makeExecutables)\n}\n\nfun parseToolOptions(options: String?): MutableList<String> {\n    if (options == null)\n        return mutableListOf()\n\n    val builder = StringBuilder()\n    val result = mutableListOf<String>()\n\n    var offset = 0\n\n    loop@ while (offset < options.length) {\n        val ch = options[offset]\n        if (Character.isWhitespace(ch)) {\n            if (builder.isNotEmpty()) {\n                result += builder.toString()\n                builder.clear()\n            }\n\n            while (offset < options.length && Character.isWhitespace(options[offset])) {\n                offset++\n            }\n\n            continue@loop\n        }\n\n        if (ch == '\\'' || ch == '\"') {\n            offset++\n\n            while (offset < options.length) {\n                val ch2 = options[offset++]\n                if (ch2 != ch) {\n                    builder.append(ch2)\n                } else {\n                    continue@loop\n                }\n            }\n\n            throw GradleException(\"Unmatched quote in $options\")\n        }\n\n        builder.append(ch)\n        offset++\n    }\n\n    if (builder.isNotEmpty()) {\n        result += builder.toString()\n    }\n\n    return result\n}\n\n// For IntelliJ IDEA\ntasks.withType<JavaExec> {\n    if (name != \"run\") {\n        jvmArgs(addOpens.map { \"--add-opens=$it=ALL-UNNAMED\" })\n//        if (javaVersion >= JavaVersion.VERSION_24) {\n//            jvmArgs(\"--enable-native-access=ALL-UNNAMED\")\n//        }\n    }\n}\n\ntasks.register<JavaExec>(\"run\") {\n    dependsOn(tasks.jar)\n\n    group = \"application\"\n\n    classpath = files(jarPath)\n    workingDir = rootProject.rootDir\n\n    val vmOptions = parseToolOptions(System.getenv(\"HMCL_JAVA_OPTS\") ?: \"-Xmx1g\")\n    if (vmOptions.none { it.startsWith(\"-Dhmcl.offline.auth.restricted=\") })\n        vmOptions += \"-Dhmcl.offline.auth.restricted=false\"\n\n    jvmArgs(vmOptions)\n\n    val hmclJavaHome = System.getenv(\"HMCL_JAVA_HOME\")\n    if (hmclJavaHome != null) {\n        this.executable(\n            file(hmclJavaHome).resolve(\"bin\")\n                .resolve(if (System.getProperty(\"os.name\").lowercase().startsWith(\"windows\")) \"java.exe\" else \"java\")\n        )\n    }\n\n    doFirst {\n        logger.quiet(\"HMCL_JAVA_OPTS: {}\", vmOptions)\n        logger.quiet(\"HMCL_JAVA_HOME: {}\", hmclJavaHome ?: System.getProperty(\"java.home\"))\n    }\n}\n\n// terracotta\n\nval upgradeTerracottaConfig = tasks.register<TerracottaConfigUpgradeTask>(\"upgradeTerracottaConfig\") {\n    val destination = layout.projectDirectory.file(\"src/main/resources/assets/terracotta.json\")\n    val source = layout.projectDirectory.file(\"terracotta-template.json\");\n\n    classifiers.set(listOf(\n        \"windows-x86_64\", \"windows-arm64\",\n        \"macos-x86_64\", \"macos-arm64\",\n        \"linux-x86_64\", \"linux-arm64\", \"linux-loongarch64\", \"linux-riscv64\",\n        \"freebsd-x86_64\"\n    ))\n\n    version.set(libs.versions.terracotta)\n    downloadURL.set($$\"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz\")\n\n    templateFile.set(source)\n    outputFile.set(destination)\n}\n\n// Check Translations\n\ntasks.register<CheckTranslations>(\"checkTranslations\") {\n    val dir = layout.projectDirectory.dir(\"src/main/resources/assets/lang\")\n\n    englishFile.set(dir.file(\"I18N.properties\"))\n    simplifiedChineseFile.set(dir.file(\"I18N_zh_CN.properties\"))\n    traditionalChineseFile.set(dir.file(\"I18N_zh.properties\"))\n    classicalChineseFile.set(dir.file(\"I18N_lzh.properties\"))\n}\n\n// l10n\n\nval generatedDir = layout.buildDirectory.dir(\"generated\")\n\nval upsideDownTranslate by tasks.registering(UpsideDownTranslate::class) {\n    inputFile.set(layout.projectDirectory.file(\"src/main/resources/assets/lang/I18N.properties\"))\n    outputFile.set(generatedDir.map { it.file(\"generated/i18n/I18N_en_Qabs.properties\") })\n}\n\nval createLanguageList by tasks.registering(CreateLanguageList::class) {\n    resourceBundleDir.set(layout.projectDirectory.dir(\"src/main/resources/assets/lang\"))\n    resourceBundleBaseName.set(\"I18N\")\n    additionalLanguages.set(listOf(\"en-Qabs\"))\n    outputFile.set(generatedDir.map { it.file(\"languages.json\") })\n}\n\nval createLocaleNamesResourceBundle by tasks.registering(CreateLocaleNamesResourceBundle::class) {\n    dependsOn(createLanguageList)\n\n    languagesFile.set(createLanguageList.flatMap { it.outputFile })\n    outputDirectory.set(generatedDir.map { it.dir(\"generated/LocaleNames\") })\n}\n\n// mcmod data\n\ntasks.register<ParseModDataTask>(\"parseModData\") {\n    inputFile.set(layout.projectDirectory.file(\"mod.json\"))\n    outputFile.set(layout.projectDirectory.file(\"src/main/resources/assets/mod_data.txt\"))\n}\n\ntasks.register<ParseModDataTask>(\"parseModPackData\") {\n    inputFile.set(layout.projectDirectory.file(\"modpack.json\"))\n    outputFile.set(layout.projectDirectory.file(\"src/main/resources/assets/modpack_data.txt\"))\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXButton.java",
    "content": "//\n// Source code recreated from a .class file by IntelliJ IDEA\n// (powered by Fernflower decompiler)\n//\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.converters.ButtonTypeConverter;\nimport com.jfoenix.skins.JFXButtonSkin;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.css.CssMetaData;\nimport javafx.css.SimpleStyleableObjectProperty;\nimport javafx.css.Styleable;\nimport javafx.css.StyleableObjectProperty;\nimport javafx.css.StyleableProperty;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Labeled;\nimport javafx.scene.control.Skin;\nimport javafx.scene.paint.Paint;\n\npublic class JFXButton extends Button {\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-button\";\n\n    private List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n    public JFXButton() {\n        this.initialize();\n    }\n\n    public JFXButton(String text) {\n        super(text);\n        this.initialize();\n    }\n\n    public JFXButton(String text, Node graphic) {\n        super(text, graphic);\n        this.initialize();\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    protected Skin<?> createDefaultSkin() {\n        return new JFXButtonSkin(this);\n    }\n\n    private final ObjectProperty<Paint> ripplerFill = new SimpleObjectProperty<>(this, \"ripplerFill\", null);\n\n    public final ObjectProperty<Paint> ripplerFillProperty() {\n        return this.ripplerFill;\n    }\n\n    public final Paint getRipplerFill() {\n        return this.ripplerFillProperty().get();\n    }\n\n    public final void setRipplerFill(Paint ripplerFill) {\n        this.ripplerFillProperty().set(ripplerFill);\n    }\n\n    private final StyleableObjectProperty<ButtonType> buttonType = new SimpleStyleableObjectProperty<>(\n            JFXButton.StyleableProperties.BUTTON_TYPE, this, \"buttonType\", JFXButton.ButtonType.FLAT);\n\n    public ButtonType getButtonType() {\n        return this.buttonType == null ? JFXButton.ButtonType.FLAT : this.buttonType.get();\n    }\n\n    public StyleableObjectProperty<ButtonType> buttonTypeProperty() {\n        return this.buttonType;\n    }\n\n    public void setButtonType(ButtonType type) {\n        this.buttonType.set(type);\n    }\n\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        if (this.STYLEABLES == null) {\n            List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());\n            styleables.addAll(getClassCssMetaData());\n            styleables.addAll(Labeled.getClassCssMetaData());\n            this.STYLEABLES = List.copyOf(styleables);\n        }\n\n        return this.STYLEABLES;\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return JFXButton.StyleableProperties.CHILD_STYLEABLES;\n    }\n\n    protected void layoutChildren() {\n        super.layoutChildren();\n        this.setNeedsLayout(false);\n    }\n\n    public enum ButtonType {\n        FLAT,\n        RAISED;\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<JFXButton, ButtonType> BUTTON_TYPE;\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            BUTTON_TYPE = new CssMetaData<>(\"-jfx-button-type\", ButtonTypeConverter.getInstance(), JFXButton.ButtonType.FLAT) {\n                public boolean isSettable(JFXButton control) {\n                    return control.buttonType == null || !control.buttonType.isBound();\n                }\n\n                public StyleableProperty<ButtonType> getStyleableProperty(JFXButton control) {\n                    return control.buttonTypeProperty();\n                }\n            };\n            List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());\n            Collections.addAll(styleables, BUTTON_TYPE);\n            CHILD_STYLEABLES = List.copyOf(styleables);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXCheckBox.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXCheckBoxSkin;\nimport javafx.css.*;\nimport javafx.css.converter.PaintConverter;\nimport javafx.scene.control.CheckBox;\nimport javafx.scene.control.Skin;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/// JFXCheckBox is the material design implementation of a checkbox.\n/// it shows ripple effect and a custom selection animation.\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class JFXCheckBox extends CheckBox {\n\n    public JFXCheckBox(String text) {\n        super(text);\n        initialize();\n    }\n\n    public JFXCheckBox() {\n        initialize();\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXCheckBoxSkin(this);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Stylesheet Handling                                                     *\n     *                                                                         *\n     **************************************************************************/\n\n    /// Initialize the style class to 'jfx-check-box'.\n    ///\n    /// This is the selector class from which CSS can be used to style\n    /// this control.\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-check-box\";\n\n    /// checkbox color property when selected\n    private StyleableObjectProperty<Paint> checkedColor;\n\n    private static final Color DEFAULT_CHECKED_COLOR = Color.valueOf(\"#0F9D58\");\n\n    public StyleableObjectProperty<Paint> checkedColorProperty() {\n        if (checkedColor == null) {\n            checkedColor = new SimpleStyleableObjectProperty<>(StyleableProperties.CHECKED_COLOR,\n                    JFXCheckBox.this,\n                    \"checkedColor\",\n                    DEFAULT_CHECKED_COLOR);\n        }\n        return this.checkedColor;\n    }\n\n    public Paint getCheckedColor() {\n        return checkedColor == null ? DEFAULT_CHECKED_COLOR : checkedColor.get();\n    }\n\n    public void setCheckedColor(Paint color) {\n        this.checkedColor.set(color);\n    }\n\n    /**\n     * checkbox color property when not selected\n     */\n    private StyleableObjectProperty<Paint> unCheckedColor;\n\n    private static final Color DEFAULT_UNCHECKED_COLOR = Color.valueOf(\"#5A5A5A\");\n\n    public StyleableObjectProperty<Paint> unCheckedColorProperty() {\n        if (unCheckedColor == null) {\n            unCheckedColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNCHECKED_COLOR,\n                    JFXCheckBox.this,\n                    \"unCheckedColor\",\n                    DEFAULT_UNCHECKED_COLOR);\n        }\n        return this.unCheckedColor;\n    }\n\n    public Paint getUnCheckedColor() {\n        return unCheckedColor == null ? DEFAULT_UNCHECKED_COLOR : unCheckedColor.get();\n    }\n\n    public void setUnCheckedColor(Paint color) {\n        this.unCheckedColor.set(color);\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<JFXCheckBox, Paint> CHECKED_COLOR =\n                new CssMetaData<>(\"-jfx-checked-color\",\n                        PaintConverter.getInstance(), DEFAULT_CHECKED_COLOR) {\n                    @Override\n                    public boolean isSettable(JFXCheckBox control) {\n                        return control.checkedColor == null || !control.checkedColor.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Paint> getStyleableProperty(JFXCheckBox control) {\n                        return control.checkedColorProperty();\n                    }\n                };\n        private static final CssMetaData<JFXCheckBox, Paint> UNCHECKED_COLOR =\n                new CssMetaData<>(\"-jfx-unchecked-color\",\n                        PaintConverter.getInstance(), DEFAULT_UNCHECKED_COLOR) {\n                    @Override\n                    public boolean isSettable(JFXCheckBox control) {\n                        return control.unCheckedColor == null || !control.unCheckedColor.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Paint> getStyleableProperty(JFXCheckBox control) {\n                        return control.unCheckedColorProperty();\n                    }\n                };\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables =\n                    new ArrayList<>(CheckBox.getClassCssMetaData());\n            Collections.addAll(styleables,\n                    CHECKED_COLOR,\n                    UNCHECKED_COLOR\n            );\n            CHILD_STYLEABLES = List.copyOf(styleables);\n        }\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXClippedPane.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.utils.JFXNodeUtils;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.layout.*;\nimport javafx.scene.paint.Color;\n\n/**\n * JFXClippedPane is a StackPane that clips its content if exceeding the pane bounds.\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2018-06-02\n */\npublic class JFXClippedPane extends StackPane {\n\n    private final Region clip = new Region();\n\n    public JFXClippedPane() {\n        super();\n        init();\n    }\n\n    public JFXClippedPane(Node... children) {\n        super(children);\n        init();\n    }\n\n    private void init() {\n        setClip(clip);\n        clip.setBackground(new Background(new BackgroundFill(Color.BLACK, new CornerRadii(2), Insets.EMPTY)));\n        backgroundProperty().addListener(observable -> JFXNodeUtils.updateBackground(getBackground(), clip));\n    }\n\n    @Override\n    protected void layoutChildren() {\n        super.layoutChildren();\n        clip.resizeRelocate(0, 0, getWidth(), getHeight());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXColorPicker.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXColorPickerSkin;\nimport javafx.css.CssMetaData;\nimport javafx.css.SimpleStyleableBooleanProperty;\nimport javafx.css.Styleable;\nimport javafx.css.StyleableBooleanProperty;\nimport javafx.css.converter.BooleanConverter;\nimport javafx.scene.control.ColorPicker;\nimport javafx.scene.control.Skin;\nimport javafx.scene.paint.Color;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * JFXColorPicker is the metrial design implementation of color picker.\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2016-03-09\n */\npublic class JFXColorPicker extends ColorPicker {\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXColorPicker() {\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXColorPicker(Color color) {\n        super(color);\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXColorPickerSkin(this);\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    /**\n     * Initialize the style class to 'jfx-color-picker'.\n     * <p>\n     * This is the selector class from which CSS can be used to style\n     * this control.\n     */\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-color-picker\";\n\n    private double[] preDefinedColors = null;\n\n    public double[] getPreDefinedColors() {\n        return preDefinedColors;\n    }\n\n    public void setPreDefinedColors(double[] preDefinedColors) {\n        this.preDefinedColors = preDefinedColors;\n    }\n\n    /**\n     * disable animation on button action\n     */\n    private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION,\n        JFXColorPicker.this,\n        \"disableAnimation\",\n        false);\n\n    public final StyleableBooleanProperty disableAnimationProperty() {\n        return this.disableAnimation;\n    }\n\n    public final Boolean isDisableAnimation() {\n        return disableAnimation != null && this.disableAnimationProperty().get();\n    }\n\n    public final void setDisableAnimation(final Boolean disabled) {\n        this.disableAnimationProperty().set(disabled);\n    }\n\n    private static final class StyleableProperties {\n\n        private static final CssMetaData<JFXColorPicker, Boolean> DISABLE_ANIMATION =\n            new CssMetaData<JFXColorPicker, Boolean>(\"-jfx-disable-animation\",\n                BooleanConverter.getInstance(), false) {\n                @Override\n                public boolean isSettable(JFXColorPicker control) {\n                    return control.disableAnimation == null || !control.disableAnimation.isBound();\n                }\n\n                @Override\n                public StyleableBooleanProperty getStyleableProperty(JFXColorPicker control) {\n                    return control.disableAnimationProperty();\n                }\n            };\n\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables =\n                new ArrayList<>(ColorPicker.getClassCssMetaData());\n            Collections.addAll(styleables, DISABLE_ANIMATION);\n            CHILD_STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXComboBox.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.converters.base.NodeConverter;\nimport com.jfoenix.skins.JFXComboBoxListViewSkin;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.ObservableList;\nimport javafx.css.*;\nimport javafx.css.converter.BooleanConverter;\nimport javafx.css.converter.PaintConverter;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundFill;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\nimport javafx.util.StringConverter;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.useJFXContextMenu;\n\n/**\n * JFXComboBox is the material design implementation of a combobox.\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2016-03-09\n */\npublic class JFXComboBox<T> extends ComboBox<T> {\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXComboBox() {\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXComboBox(ObservableList<T> items) {\n        super(items);\n        initialize();\n    }\n\n    private void initialize() {\n        getStyleClass().add(DEFAULT_STYLE_CLASS);\n        this.setCellFactory(listView -> new JFXListCell<T>() {\n            @Override\n            public void updateItem(T item, boolean empty) {\n                super.updateItem(item, empty);\n                updateDisplayText(this, item, empty);\n            }\n        });\n\n        // had to refactor the code out of the skin class to allow\n        // customization of the button cell\n        this.setButtonCell(new ListCell<T>() {\n            {\n                // fixed clearing the combo box value is causing\n                // java prompt text to be shown because the button cell is not updated\n                JFXComboBox.this.valueProperty().addListener(observable -> {\n                    if (JFXComboBox.this.getValue() == null) {\n                        updateItem(null, true);\n                    }\n                });\n            }\n\n            @Override\n            protected void updateItem(T item, boolean empty) {\n                updateDisplayText(this, item, empty);\n                this.setVisible(item != null || !empty);\n            }\n\n        });\n\n        useJFXContextMenu(editorProperty().get());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXComboBoxListViewSkin<T>(this);\n    }\n\n    /**\n     * Initialize the style class to 'jfx-combo-box'.\n     * <p>\n     * This is the selector class from which CSS can be used to style\n     * this control.\n     */\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-combo-box\";\n\n    /***************************************************************************\n     *                                                                         *\n     * Node Converter Property                                                 *\n     *                                                                         *\n     **************************************************************************/\n    /**\n     * Converts the user-typed input (when the ComboBox is\n     * {@link #editableProperty() editable}) to an object of type T, such that\n     * the input may be retrieved via the  {@link #valueProperty() value} property.\n     */\n    public ObjectProperty<NodeConverter<T>> nodeConverterProperty() {\n        return nodeConverter;\n    }\n\n    private ObjectProperty<NodeConverter<T>> nodeConverter = new SimpleObjectProperty<>(this, \"nodeConverter\",\n            JFXComboBox.<T>defaultNodeConverter());\n\n    public final void setNodeConverter(NodeConverter<T> value) {\n        nodeConverterProperty().set(value);\n    }\n\n    public final NodeConverter<T> getNodeConverter() {\n        return nodeConverterProperty().get();\n    }\n\n    private static <T> NodeConverter<T> defaultNodeConverter() {\n        return new NodeConverter<T>() {\n            @Override\n            public Node toNode(T object) {\n                if (object == null) {\n                    return null;\n                }\n                StackPane selectedValueContainer = new StackPane();\n                selectedValueContainer.getStyleClass().add(\"combo-box-selected-value-container\");\n                selectedValueContainer.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));\n                Label selectedValueLabel = object instanceof Label ? new Label(((Label) object).getText()) : new Label(\n                        object.toString());\n                selectedValueLabel.setTextFill(Color.BLACK);\n                selectedValueContainer.getChildren().add(selectedValueLabel);\n                StackPane.setAlignment(selectedValueLabel, Pos.CENTER_LEFT);\n                StackPane.setMargin(selectedValueLabel, new Insets(0, 0, 0, 5));\n                return selectedValueContainer;\n            }\n\n            @SuppressWarnings(\"unchecked\")\n            @Override\n            public T fromNode(Node node) {\n                return (T) node;\n            }\n\n            @Override\n            public String toString(T object) {\n                if (object == null) {\n                    return null;\n                }\n                if (object instanceof Label) {\n                    return ((Label) object).getText();\n                }\n                return object.toString();\n            }\n        };\n    }\n\n    private boolean updateDisplayText(ListCell<T> cell, T item, boolean empty) {\n        if (empty) {\n            // create empty cell\n            if (cell == null) {\n                return true;\n            }\n            cell.setGraphic(null);\n            cell.setText(null);\n            return true;\n        } else if (item instanceof Node) {\n            Node currentNode = cell.getGraphic();\n            Node newNode = (Node) item;\n            //  create a node from the selected node of the listview\n            //  using JFXComboBox {@link #nodeConverterProperty() NodeConverter})\n            NodeConverter<T> nc = this.getNodeConverter();\n            Node node = nc == null ? null : nc.toNode(item);\n            if (currentNode == null || !currentNode.equals(newNode)) {\n                cell.setText(null);\n                cell.setGraphic(node == null ? newNode : node);\n            }\n            return node == null;\n        } else {\n            // run item through StringConverter if it isn't null\n            StringConverter<T> c = this.getConverter();\n            String s = item == null ? this.getPromptText() : (c == null ? item.toString() : c.toString(item));\n            cell.setText(s);\n            cell.setGraphic(null);\n            return s == null || s.isEmpty();\n        }\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * styleable Properties                                                    *\n     *                                                                         *\n     **************************************************************************/\n\n    /**\n     * set true to show a float the prompt text when focusing the field\n     */\n    private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT,\n            JFXComboBox.this,\n            \"lableFloat\",\n            false);\n\n    public final StyleableBooleanProperty labelFloatProperty() {\n        return this.labelFloat;\n    }\n\n    public final boolean isLabelFloat() {\n        return this.labelFloatProperty().get();\n    }\n\n    public final void setLabelFloat(final boolean labelFloat) {\n        this.labelFloatProperty().set(labelFloat);\n    }\n\n    /**\n     * default color used when the field is unfocused\n     */\n    private StyleableObjectProperty<Paint> unFocusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNFOCUS_COLOR,\n            JFXComboBox.this,\n            \"unFocusColor\",\n            Color.rgb(77,\n                    77,\n                    77));\n\n    public Paint getUnFocusColor() {\n        return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> unFocusColorProperty() {\n        return this.unFocusColor;\n    }\n\n    public void setUnFocusColor(Paint color) {\n        this.unFocusColor.set(color);\n    }\n\n    /**\n     * default color used when the field is focused\n     */\n    private StyleableObjectProperty<Paint> focusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.FOCUS_COLOR,\n            JFXComboBox.this,\n            \"focusColor\",\n            Color.valueOf(\"#4059A9\"));\n\n    public Paint getFocusColor() {\n        return focusColor == null ? Color.valueOf(\"#4059A9\") : focusColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> focusColorProperty() {\n        return this.focusColor;\n    }\n\n    public void setFocusColor(Paint color) {\n        this.focusColor.set(color);\n    }\n\n    private final static class StyleableProperties {\n        private static final CssMetaData<JFXComboBox<?>, Paint> UNFOCUS_COLOR = new CssMetaData<JFXComboBox<?>, Paint>(\n                \"-jfx-unfocus-color\",\n                PaintConverter.getInstance(),\n                Color.valueOf(\"#A6A6A6\")) {\n            @Override\n            public boolean isSettable(JFXComboBox<?> control) {\n                return control.unFocusColor == null || !control.unFocusColor.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Paint> getStyleableProperty(JFXComboBox<?> control) {\n                return control.unFocusColorProperty();\n            }\n        };\n        private static final CssMetaData<JFXComboBox<?>, Paint> FOCUS_COLOR = new CssMetaData<JFXComboBox<?>, Paint>(\n                \"-jfx-focus-color\",\n                PaintConverter.getInstance(),\n                Color.valueOf(\"#3f51b5\")) {\n            @Override\n            public boolean isSettable(JFXComboBox<?> control) {\n                return control.focusColor == null || !control.focusColor.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Paint> getStyleableProperty(JFXComboBox<?> control) {\n                return control.focusColorProperty();\n            }\n        };\n        private static final CssMetaData<JFXComboBox<?>, Boolean> LABEL_FLOAT = new CssMetaData<JFXComboBox<?>, Boolean>(\n                \"-jfx-label-float\",\n                BooleanConverter.getInstance(),\n                false) {\n            @Override\n            public boolean isSettable(JFXComboBox<?> control) {\n                return control.labelFloat == null || !control.labelFloat.isBound();\n            }\n\n            @Override\n            public StyleableBooleanProperty getStyleableProperty(JFXComboBox<?> control) {\n                return control.labelFloatProperty();\n            }\n        };\n\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(\n                    Control.getClassCssMetaData());\n            Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT);\n            CHILD_STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n    }\n\n    // inherit the styleable properties from parent\n    private List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        if (STYLEABLES == null) {\n            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(\n                    Control.getClassCssMetaData());\n            styleables.addAll(getClassCssMetaData());\n            styleables.addAll(Control.getClassCssMetaData());\n            STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n        return STYLEABLES;\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXDialog.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.controls.events.JFXDialogEvent;\nimport com.jfoenix.converters.DialogTransitionConverter;\nimport com.jfoenix.effects.JFXDepthManager;\nimport com.jfoenix.transitions.CachedTransition;\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.animation.Transition;\nimport javafx.beans.DefaultProperty;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.ObjectPropertyBase;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.css.CssMetaData;\nimport javafx.css.SimpleStyleableObjectProperty;\nimport javafx.css.Styleable;\nimport javafx.css.StyleableObjectProperty;\nimport javafx.css.StyleableProperty;\nimport javafx.event.Event;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Pos;\nimport javafx.scene.CacheHint;\nimport javafx.scene.Node;\nimport javafx.scene.SnapshotParameters;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.image.WritableImage;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundFill;\nimport javafx.scene.layout.CornerRadii;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.animation.Motion;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/// Note: for JFXDialog to work properly, the root node **MUST**\n/// be of type [StackPane]\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\n@DefaultProperty(value = \"content\")\npublic class JFXDialog extends StackPane {\n\n    private static final double INITIAL_SCALE = 0.8;\n\n    // public static enum JFXDialogLayout{PLAIN, HEADING, ACTIONS, BACKDROP};\n    public enum DialogTransition {\n        CENTER, NONE\n    }\n\n    private StackPane contentHolder;\n\n    private double offsetX = 0;\n    private double offsetY = 0;\n\n    private StackPane dialogContainer;\n    private Region content;\n    private Transition animation;\n\n    private final EventHandler<? super MouseEvent> closeHandler = e -> close();\n\n    /// creates empty JFXDialog control with CENTER animation type\n    public JFXDialog() {\n        this(null, null, DialogTransition.CENTER);\n    }\n\n    /// creates empty JFXDialog control with a specified animation type\n    public JFXDialog(DialogTransition transition) {\n        this(null, null, transition);\n    }\n\n    /// creates JFXDialog control with a specified animation type, the animation type\n    /// can be one of the following:\n    ///\n    ///   - CENTER\n    ///   - TOP\n    ///   - RIGHT\n    ///   - BOTTOM\n    ///   - LEFT\n    ///\n    /// @param dialogContainer is the parent of the dialog, it\n    /// @param content         the content of dialog\n    /// @param transitionType  the animation type\n    public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType) {\n        initialize();\n        setContent(content);\n        setDialogContainer(dialogContainer);\n        this.transitionType.set(transitionType);\n        // init change listeners\n        initChangeListeners();\n    }\n\n    /// creates JFXDialog control with a specified animation type that\n    /// is closed when clicking on the overlay, the animation type\n    /// can be one of the following:\n    ///\n    ///   - CENTER\n    ///   - TOP\n    ///   - RIGHT\n    ///   - BOTTOM\n    ///   - LEFT\n    public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType, boolean overlayClose) {\n        setOverlayClose(overlayClose);\n        initialize();\n        setContent(content);\n        setDialogContainer(dialogContainer);\n        this.transitionType.set(transitionType);\n        // init change listeners\n        initChangeListeners();\n    }\n\n    private void initChangeListeners() {\n        overlayCloseProperty().addListener((o, oldVal, newVal) -> {\n            if (newVal) {\n                this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);\n            } else {\n                this.removeEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);\n            }\n        });\n    }\n\n    private void initialize() {\n        this.setVisible(false);\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n        this.transitionType.addListener((o, oldVal, newVal) -> {\n            animation = getShowAnimation(transitionType.get());\n        });\n\n        contentHolder = new StackPane();\n        contentHolder.setBackground(new Background(new BackgroundFill(Color.WHITE, new CornerRadii(2), null)));\n        JFXDepthManager.setDepth(contentHolder, 4);\n        contentHolder.setPickOnBounds(false);\n        // ensure stackpane is never resized beyond it's preferred size\n        contentHolder.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);\n        this.getChildren().add(contentHolder);\n        this.getStyleClass().add(\"jfx-dialog-overlay-pane\");\n        StackPane.setAlignment(contentHolder, Pos.CENTER);\n        this.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), null, null)));\n        // close the dialog if clicked on the overlay pane\n        if (overlayClose.get()) {\n            this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);\n        }\n        // prevent propagating the events to overlay pane\n        contentHolder.addEventHandler(MouseEvent.ANY, Event::consume);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Setters / Getters                                                       *\n     *                                                                         *\n     **************************************************************************/\n\n    /// @return the dialog container\n    public StackPane getDialogContainer() {\n        return dialogContainer;\n    }\n\n    /// set the dialog container\n    /// Note: the dialog container must be StackPane, its the container for the dialog to be shown in.\n    public void setDialogContainer(StackPane dialogContainer) {\n        if (dialogContainer != null) {\n            this.dialogContainer = dialogContainer;\n            // FIXME: need to be improved to consider only the parent boundary\n            offsetX = dialogContainer.getBoundsInLocal().getWidth();\n            offsetY = dialogContainer.getBoundsInLocal().getHeight();\n            animation = getShowAnimation(transitionType.get());\n        }\n    }\n\n    /// @return dialog content node\n    public Region getContent() {\n        return content;\n    }\n\n    /// set the content of the dialog\n    public void setContent(Region content) {\n        if (content != null) {\n            this.content = content;\n            this.content.setPickOnBounds(false);\n            contentHolder.getChildren().setAll(content);\n        }\n    }\n\n    /// indicates whether the dialog will close when clicking on the overlay or not\n    private final BooleanProperty overlayClose = new SimpleBooleanProperty(true);\n\n    public final BooleanProperty overlayCloseProperty() {\n        return this.overlayClose;\n    }\n\n    public final boolean isOverlayClose() {\n        return this.overlayCloseProperty().get();\n    }\n\n    public final void setOverlayClose(final boolean overlayClose) {\n        this.overlayCloseProperty().set(overlayClose);\n    }\n\n    /// if sets to true, the content of dialog container will be cached and replaced with an image\n    /// when displaying the dialog (better performance).\n    /// this is recommended if the content behind the dialog will not change during the showing\n    /// period\n    private final BooleanProperty cacheContainer = new SimpleBooleanProperty(false);\n\n    public boolean isCacheContainer() {\n        return cacheContainer.get();\n    }\n\n    public BooleanProperty cacheContainerProperty() {\n        return cacheContainer;\n    }\n\n    public void setCacheContainer(boolean cacheContainer) {\n        this.cacheContainer.set(cacheContainer);\n    }\n\n    /// it will show the dialog in the specified container\n    public void show(StackPane dialogContainer) {\n        this.setDialogContainer(dialogContainer);\n        showDialog();\n    }\n\n    private ArrayList<Node> tempContent;\n\n    /**\n     * show the dialog inside its parent container\n     */\n    public void show() {\n        this.setDialogContainer(dialogContainer);\n        showDialog();\n    }\n\n    private void showDialog() {\n        if (dialogContainer == null) {\n            throw new RuntimeException(\"ERROR: JFXDialog container is not set!\");\n        }\n        if (isCacheContainer()) {\n            tempContent = new ArrayList<>(dialogContainer.getChildren());\n\n            SnapshotParameters snapShotparams = new SnapshotParameters();\n            snapShotparams.setFill(Color.TRANSPARENT);\n            WritableImage temp = dialogContainer.snapshot(snapShotparams,\n                    new WritableImage((int) dialogContainer.getWidth(),\n                            (int) dialogContainer.getHeight()));\n            ImageView tempImage = new ImageView(temp);\n            tempImage.setCache(true);\n            tempImage.setCacheHint(CacheHint.SPEED);\n            dialogContainer.getChildren().setAll(tempImage, this);\n        } else {\n            //prevent error if opening an already opened dialog\n            dialogContainer.getChildren().remove(this);\n            tempContent = null;\n            dialogContainer.getChildren().add(this);\n        }\n\n        if (animation != null) {\n            animation.play();\n        } else {\n            setVisible(true);\n            setOpacity(1);\n            Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.OPENED));\n        }\n    }\n\n    /**\n     * close the dialog\n     */\n    public void close() {\n        if (animation != null) {\n            animation.setRate(-2);\n            animation.play();\n            animation.setOnFinished(e -> {\n                closeDialog();\n            });\n        } else {\n            setOpacity(0);\n            setVisible(false);\n            closeDialog();\n        }\n    }\n\n    private void closeDialog() {\n        resetProperties();\n        Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.CLOSED));\n        if (tempContent == null) {\n            dialogContainer.getChildren().remove(this);\n        } else {\n            dialogContainer.getChildren().setAll(tempContent);\n        }\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Transitions                                                             *\n     *                                                                         *\n     **************************************************************************/\n\n    private Transition getShowAnimation(DialogTransition transitionType) {\n        Transition animation = null;\n        if (contentHolder != null) {\n            animation = switch (transitionType) {\n                case CENTER -> {\n                    contentHolder.setScaleX(INITIAL_SCALE);\n                    contentHolder.setScaleY(INITIAL_SCALE);\n                    yield new CenterTransition();\n                }\n                case NONE -> {\n                    contentHolder.setScaleX(1);\n                    contentHolder.setScaleY(1);\n                    contentHolder.setTranslateX(0);\n                    contentHolder.setTranslateY(0);\n                    yield null;\n                }\n            };\n        }\n        if (animation != null) {\n            animation.setOnFinished(finish ->\n                    Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.OPENED)));\n        }\n        return animation;\n    }\n\n    private void resetProperties() {\n        this.setVisible(false);\n        contentHolder.setTranslateX(0);\n        contentHolder.setTranslateY(0);\n        contentHolder.setScaleX(1);\n        contentHolder.setScaleY(1);\n    }\n\n    private final class CenterTransition extends CachedTransition {\n        private static final Interpolator INTERPOLATOR = Motion.EMPHASIZED_DECELERATE;\n\n        CenterTransition() {\n            super(contentHolder, new Timeline(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(contentHolder.scaleXProperty(), INITIAL_SCALE, INTERPOLATOR),\n                            new KeyValue(contentHolder.scaleYProperty(), INITIAL_SCALE, INTERPOLATOR),\n                            new KeyValue(JFXDialog.this.visibleProperty(), false, Motion.LINEAR)\n                    ),\n                    new KeyFrame(Duration.millis(10),\n                            new KeyValue(JFXDialog.this.visibleProperty(), true, Motion.LINEAR),\n                            new KeyValue(JFXDialog.this.opacityProperty(), 0, INTERPOLATOR)\n                    ),\n                    new KeyFrame(Motion.EXTRA_LONG4,\n                            new KeyValue(contentHolder.scaleXProperty(), 1, INTERPOLATOR),\n                            new KeyValue(contentHolder.scaleYProperty(), 1, INTERPOLATOR),\n                            new KeyValue(JFXDialog.this.visibleProperty(), true, Motion.LINEAR),\n                            new KeyValue(JFXDialog.this.opacityProperty(), 1, INTERPOLATOR)\n                    ))\n            );\n            // reduce the number to increase the shifting , increase number to reduce shifting\n            setCycleDuration(Duration.seconds(0.4));\n            setDelay(Duration.ZERO);\n        }\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Stylesheet Handling                                                     *\n     *                                                                         *\n     **************************************************************************/\n\n    /// Initialize the style class to 'jfx-dialog'.\n    ///\n    /// This is the selector class from which CSS can be used to style\n    /// this control.\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-dialog\";\n\n    /// dialog transition type property, it can be one of the following:\n    ///\n    ///   - CENTER\n    ///   - TOP\n    ///   - RIGHT\n    ///   - BOTTOM\n    ///   - LEFT\n    ///   - NONE\n    private final StyleableObjectProperty<DialogTransition> transitionType = new SimpleStyleableObjectProperty<>(\n            StyleableProperties.DIALOG_TRANSITION,\n            JFXDialog.this,\n            \"dialogTransition\",\n            DialogTransition.CENTER);\n\n    public DialogTransition getTransitionType() {\n        return transitionType == null ? DialogTransition.CENTER : transitionType.get();\n    }\n\n    public StyleableObjectProperty<DialogTransition> transitionTypeProperty() {\n        return this.transitionType;\n    }\n\n    public void setTransitionType(DialogTransition transition) {\n        this.transitionType.set(transition);\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<JFXDialog, DialogTransition> DIALOG_TRANSITION =\n                new CssMetaData<JFXDialog, DialogTransition>(\"-jfx-dialog-transition\",\n                        DialogTransitionConverter.getInstance(),\n                        DialogTransition.CENTER) {\n                    @Override\n                    public boolean isSettable(JFXDialog control) {\n                        return control.transitionType == null || !control.transitionType.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<DialogTransition> getStyleableProperty(JFXDialog control) {\n                        return control.transitionTypeProperty();\n                    }\n                };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables =\n                    new ArrayList<>(StackPane.getClassCssMetaData());\n            Collections.addAll(styleables,\n                    DIALOG_TRANSITION\n            );\n            CHILD_STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n\n\n    /***************************************************************************\n     *                                                                         *\n     * Custom Events                                                           *\n     *                                                                         *\n     **************************************************************************/\n\n    private final ObjectProperty<EventHandler<? super JFXDialogEvent>> onDialogClosedProperty = new ObjectPropertyBase<EventHandler<? super JFXDialogEvent>>() {\n        @Override\n        protected void invalidated() {\n            setEventHandler(JFXDialogEvent.CLOSED, get());\n        }\n\n        @Override\n        public Object getBean() {\n            return JFXDialog.this;\n        }\n\n        @Override\n        public String getName() {\n            return \"onClosed\";\n        }\n    };\n\n    /**\n     * Defines a function to be called when the dialog is closed.\n     * Note: it will be triggered after the close animation is finished.\n     */\n    public ObjectProperty<EventHandler<? super JFXDialogEvent>> onDialogClosedProperty() {\n        return onDialogClosedProperty;\n    }\n\n    public void setOnDialogClosed(EventHandler<? super JFXDialogEvent> handler) {\n        onDialogClosedProperty().set(handler);\n    }\n\n    public EventHandler<? super JFXDialogEvent> getOnDialogClosed() {\n        return onDialogClosedProperty().get();\n    }\n\n    private final ObjectProperty<EventHandler<? super JFXDialogEvent>> onDialogOpenedProperty = new ObjectPropertyBase<>() {\n        @Override\n        protected void invalidated() {\n            setEventHandler(JFXDialogEvent.OPENED, get());\n        }\n\n        @Override\n        public Object getBean() {\n            return JFXDialog.this;\n        }\n\n        @Override\n        public String getName() {\n            return \"onOpened\";\n        }\n    };\n\n    /**\n     * Defines a function to be called when the dialog is opened.\n     * Note: it will be triggered after the show animation is finished.\n     */\n    public ObjectProperty<EventHandler<? super JFXDialogEvent>> onDialogOpenedProperty() {\n        return onDialogOpenedProperty;\n    }\n\n    public void setOnDialogOpened(EventHandler<? super JFXDialogEvent> handler) {\n        onDialogOpenedProperty().set(handler);\n    }\n\n    public EventHandler<? super JFXDialogEvent> getOnDialogOpened() {\n        return onDialogOpenedProperty().get();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXDialogLayout.java",
    "content": "//\n// Source code recreated from a .class file by IntelliJ IDEA\n// (powered by Fernflower decompiler)\n//\n\npackage com.jfoenix.controls;\n\nimport java.util.List;\n\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Orientation;\nimport javafx.scene.Node;\nimport javafx.scene.layout.FlowPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\n\npublic class JFXDialogLayout extends StackPane {\n    private final StackPane heading = new StackPane();\n    private final StackPane body = new StackPane();\n    private final FlowPane actions = new FlowPane() {\n        protected double computeMinWidth(double height) {\n            if (this.getContentBias() == Orientation.HORIZONTAL) {\n                double maxPref = 0.0;\n                for (Node child : this.getChildren()) {\n                    if (child.isManaged()) {\n                        maxPref = Math.max(maxPref, child.minWidth(-1.0));\n                    }\n                }\n\n                Insets insets = this.getInsets();\n                return insets.getLeft() + this.snapSizeX(maxPref) + insets.getRight();\n            } else {\n                return this.computePrefWidth(height);\n            }\n        }\n\n        protected double computeMinHeight(double width) {\n            if (this.getContentBias() == Orientation.VERTICAL) {\n                double maxPref = 0.0;\n\n                for (Node child : this.getChildren()) {\n                    if (child.isManaged()) {\n                        maxPref = Math.max(maxPref, child.minHeight(-1.0));\n                    }\n                }\n\n                Insets insets = this.getInsets();\n                return insets.getTop() + this.snapSizeY(maxPref) + insets.getBottom();\n            } else {\n                return this.computePrefHeight(width);\n            }\n        }\n    };\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-dialog-layout\";\n\n    public JFXDialogLayout() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n\n        VBox layout = new VBox();\n\n        this.heading.getStyleClass().add(\"jfx-layout-heading\");\n        this.heading.getStyleClass().add(\"title\");\n\n        this.body.getStyleClass().add(\"jfx-layout-body\");\n        this.body.prefHeightProperty().bind(this.prefHeightProperty());\n        this.body.prefWidthProperty().bind(this.prefWidthProperty());\n\n        this.actions.getStyleClass().add(\"jfx-layout-actions\");\n\n        layout.getChildren().setAll(this.heading, this.body, this.actions);\n\n        this.getChildren().add(layout);\n    }\n\n    public ObservableList<Node> getHeading() {\n        return this.heading.getChildren();\n    }\n\n    public void setHeading(Node... titleContent) {\n        this.heading.getChildren().setAll(titleContent);\n    }\n\n    public ObservableList<Node> getBody() {\n        return this.body.getChildren();\n    }\n\n    public void setBody(Node... body) {\n        this.body.getChildren().setAll(body);\n    }\n\n    public ObservableList<Node> getActions() {\n        return this.actions.getChildren();\n    }\n\n    public void setActions(Node... actions) {\n        this.actions.getChildren().setAll(actions);\n    }\n\n    public void setActions(List<? extends Node> actions) {\n        this.actions.getChildren().setAll(actions);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXListCell.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.utils.JFXNodeUtils;\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.application.Platform;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.layout.Region;\nimport javafx.scene.shape.Rectangle;\nimport javafx.scene.shape.Shape;\nimport javafx.util.Duration;\n\nimport java.util.Set;\n\n/// material design implementation of ListCell\n///\n/// By default, JFXListCell will try to create a graphic node for the cell,\n/// to override it you need to set graphic to null in [#updateItem(Object, boolean)] method.\n///\n/// NOTE: passive nodes (Labels and Shapes) will be set to mouse transparent in order to\n/// show the ripple effect upon clicking , to change this behavior you can override the\n/// method {[#makeChildrenTransparent()]\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class JFXListCell<T> extends ListCell<T> {\n\n    protected JFXRippler cellRippler = new JFXRippler(this) {\n        @Override\n        protected Node getMask() {\n            Region clip = new Region();\n            JFXNodeUtils.updateBackground(JFXListCell.this.getBackground(), clip);\n            double width = control.getLayoutBounds().getWidth();\n            double height = control.getLayoutBounds().getHeight();\n            clip.resize(width, height);\n            return clip;\n        }\n\n        @Override\n        protected void positionControl(Node control) {\n            // do nothing\n        }\n    };\n\n    protected Node cellContent;\n    private Rectangle clip;\n\n    private Timeline gapAnimation;\n    private boolean playExpandAnimation = false;\n    private boolean selectionChanged = false;\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXListCell() {\n        initialize();\n        initListeners();\n    }\n\n    /**\n     * init listeners to update the vertical gap / selection animation\n     */\n    private void initListeners() {\n        listViewProperty().addListener((listObj, oldList, newList) -> {\n            if (newList instanceof JFXListView<?> listView) {\n                listView.currentVerticalGapProperty().addListener((o, oldVal, newVal) -> {\n                    cellRippler.rippler.setClip(null);\n                    if (newVal.doubleValue() != 0) {\n                        playExpandAnimation = true;\n                        getListView().requestLayout();\n                    } else {\n                        // fake expand state\n                        double gap = clip.getY() * 2;\n                        gapAnimation = new Timeline(\n                                new KeyFrame(Duration.millis(240),\n                                        new KeyValue(this.translateYProperty(),\n                                                -gap / 2 - (gap * (getIndex())),\n                                                Interpolator.EASE_BOTH)\n                                ));\n                        gapAnimation.play();\n                        gapAnimation.setOnFinished((finish) -> {\n                            requestLayout();\n                            Platform.runLater(() -> getListView().requestLayout());\n                        });\n                    }\n                });\n\n                selectedProperty().addListener((o, oldVal, newVal) -> {\n                    if (newVal) {\n                        selectionChanged = true;\n                    }\n                });\n            }\n        });\n    }\n\n    @Override\n    protected void layoutChildren() {\n        super.layoutChildren();\n        cellRippler.resizeRelocate(0, 0, getWidth(), getHeight());\n        double gap = getGap();\n\n        if (clip == null) {\n            clip = new Rectangle(0, gap / 2, getWidth(), getHeight() - gap);\n            setClip(clip);\n        } else {\n            if (gap != 0) {\n                if (playExpandAnimation || selectionChanged) {\n                    // fake list collapse state\n                    if (playExpandAnimation) {\n                        this.setTranslateY(-gap / 2 + (-gap * (getIndex())));\n                        clip.setY(gap / 2);\n                        clip.setHeight(getHeight() - gap);\n                        gapAnimation = new Timeline(new KeyFrame(Duration.millis(240),\n                                new KeyValue(this.translateYProperty(),\n                                        0,\n                                        Interpolator.EASE_BOTH)));\n                        playExpandAnimation = false;\n                    } else if (selectionChanged) {\n                        clip.setY(0);\n                        clip.setHeight(getHeight());\n                        gapAnimation = new Timeline(\n                                new KeyFrame(Duration.millis(240),\n                                        new KeyValue(clip.yProperty(), gap / 2, Interpolator.EASE_BOTH),\n                                        new KeyValue(clip.heightProperty(), getHeight() - gap, Interpolator.EASE_BOTH)\n                                ));\n                    }\n                    playExpandAnimation = false;\n                    selectionChanged = false;\n                    gapAnimation.play();\n                } else {\n                    if (gapAnimation != null) {\n                        gapAnimation.stop();\n                    }\n                    this.setTranslateY(0);\n                    clip.setY(gap / 2);\n                    clip.setHeight(getHeight() - gap);\n                }\n            } else {\n                this.setTranslateY(0);\n                clip.setY(0);\n                clip.setHeight(getHeight());\n            }\n            clip.setX(0);\n            clip.setWidth(getWidth());\n        }\n        if (!getChildren().contains(cellRippler)) {\n            makeChildrenTransparent();\n            getChildren().add(0, cellRippler);\n            cellRippler.rippler.clear();\n        }\n    }\n\n    /**\n     * this method is used to set some nodes in cell content as mouse transparent nodes\n     * so clicking on them will trigger the ripple effect.\n     */\n    protected void makeChildrenTransparent() {\n        for (Node child : getChildren()) {\n            if (child instanceof Label) {\n                Set<Node> texts = child.lookupAll(\"Text\");\n                for (Node text : texts) {\n                    text.setMouseTransparent(true);\n                }\n            } else if (child instanceof Shape) {\n                child.setMouseTransparent(true);\n            }\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected void updateItem(T item, boolean empty) {\n        super.updateItem(item, empty);\n        if (empty) {\n            setText(null);\n            setGraphic(null);\n            // remove empty (Trailing cells)\n            setMouseTransparent(true);\n            setStyle(\"-fx-background-color:TRANSPARENT;\");\n        } else {\n            setMouseTransparent(false);\n            setStyle(null);\n            if (item instanceof Node newNode) {\n                setText(null);\n                Node currentNode = getGraphic();\n                if (currentNode == null || !currentNode.equals(newNode)) {\n                    cellContent = newNode;\n                    cellRippler.rippler.cacheRippleClip(false);\n                    // build the Cell node\n                    // RIPPLER ITEM : in case if the list item has its own rippler bind the list rippler and item rippler properties\n                    if (newNode instanceof JFXRippler newRippler) {\n                        // build cell container from exisiting rippler\n                        cellRippler.ripplerFillProperty().bind(newRippler.ripplerFillProperty());\n                        cellRippler.maskTypeProperty().bind(newRippler.maskTypeProperty());\n                        cellRippler.positionProperty().bind(newRippler.positionProperty());\n                        cellContent = newRippler.getControl();\n                    }\n                    ((Region) cellContent).setMaxHeight(cellContent.prefHeight(-1));\n                    setGraphic(cellContent);\n                }\n            } else {\n                setText(item == null ? \"null\" : item.toString());\n                setGraphic(null);\n            }\n            // show cell tooltip if it's toggled in JFXListView\n            if (getListView() instanceof JFXListView<?> listView && listView.isShowTooltip()) {\n                if (item instanceof Label label) {\n                    setTooltip(new Tooltip(label.getText()));\n                } else if (getText() != null) {\n                    setTooltip(new Tooltip(getText()));\n                }\n            }\n        }\n    }\n\n    // Stylesheet Handling                                                     *\n\n    /**\n     * Initialize the style class to 'jfx-list-cell'.\n     * <p>\n     * This is the selector class from which CSS can be used to style\n     * this control.\n     */\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-list-cell\";\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n        this.setPadding(new Insets(8, 12, 8, 12));\n    }\n\n    @Override\n    protected double computePrefHeight(double width) {\n        double gap = getGap();\n        return super.computePrefHeight(width) + gap;\n    }\n\n    private double getGap() {\n        return (getListView() instanceof JFXListView<?> listView)\n                ? (listView.isExpanded() ? listView.currentVerticalGapProperty().get() : 0)\n                : 0;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXListView.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXListViewSkin;\nimport javafx.beans.property.*;\nimport javafx.css.*;\nimport javafx.css.converter.BooleanConverter;\nimport javafx.css.converter.SizeConverter;\nimport javafx.scene.control.ListView;\nimport javafx.scene.control.Skin;\nimport javafx.scene.input.MouseEvent;\n\nimport java.util.*;\n\n/// Material design implementation of List View\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class JFXListView<T> extends ListView<T> {\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXListView() {\n        this.setCellFactory(listView -> new JFXListCell<>());\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXListViewSkin<>(this);\n    }\n\n    private IntegerProperty depth;\n\n    public IntegerProperty depthProperty() {\n        if (depth == null) {\n            depth = new SimpleIntegerProperty(this, \"depth\", 0);\n        }\n        return depth;\n    }\n\n    public int getDepth() {\n        return depth != null ? depth.get() : 0;\n    }\n\n    public void setDepth(int depth) {\n        depthProperty().set(depth);\n    }\n\n    private DoubleProperty currentVerticalGap;\n\n    DoubleProperty currentVerticalGapProperty() {\n        if (currentVerticalGap == null) {\n            currentVerticalGap = new SimpleDoubleProperty(this, \"currentVerticalGap\");\n        }\n        return currentVerticalGap;\n    }\n\n    private void updateVerticalGap() {\n        if (isExpanded()) {\n            currentVerticalGapProperty().set(verticalGap.get());\n        } else {\n            currentVerticalGapProperty().set(0);\n        }\n    }\n\n    /*\n     * this only works if the items were labels / strings\n     */\n    private BooleanProperty showTooltip;\n\n    public final BooleanProperty showTooltipProperty() {\n        if (showTooltip == null) {\n            showTooltip = new SimpleBooleanProperty(this, \"showTooltip\", false);\n        }\n        return this.showTooltip;\n    }\n\n    public final boolean isShowTooltip() {\n        return showTooltip != null && showTooltip.get();\n    }\n\n    public final void setShowTooltip(final boolean showTooltip) {\n        this.showTooltipProperty().set(showTooltip);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Stylesheet Handling                                                     *\n     *                                                                         *\n     **************************************************************************/\n\n    /// Initialize the style class to 'jfx-list-view'.\n    ///\n    /// This is the selector class from which CSS can be used to style\n    /// this control.\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-list-view\";\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    /**\n     * propagate mouse events to the parent node ( e.g. to allow dragging while clicking on the list)\n     */\n    public void propagateMouseEventsToParent() {\n        this.addEventHandler(MouseEvent.ANY, e -> {\n            e.consume();\n            this.getParent().fireEvent(e);\n        });\n    }\n\n    private StyleableDoubleProperty verticalGap;\n\n    public StyleableDoubleProperty verticalGapProperty() {\n        if (this.verticalGap == null) {\n            this.verticalGap = new StyleableDoubleProperty(0.0) {\n                @Override\n                public Object getBean() {\n                    return JFXListView.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"verticalGap\";\n                }\n\n                @Override\n                public CssMetaData<? extends Styleable, Number> getCssMetaData() {\n                    return StyleableProperties.VERTICAL_GAP;\n                }\n\n                @Override\n                protected void invalidated() {\n                    updateVerticalGap();\n                }\n            };\n        }\n        return this.verticalGap;\n    }\n\n    public Double getVerticalGap() {\n        return verticalGap == null ? 0.0 : verticalGap.get();\n    }\n\n    public void setVerticalGap(Double gap) {\n        verticalGapProperty().set(gap);\n    }\n\n    private StyleableBooleanProperty expanded;\n\n    public StyleableBooleanProperty expandedProperty() {\n        if (expanded == null) {\n            expanded = new StyleableBooleanProperty(false) {\n                @Override\n                public Object getBean() {\n                    return JFXListView.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"expanded\";\n                }\n\n                @Override\n                public CssMetaData<? extends Styleable, Boolean> getCssMetaData() {\n                    return StyleableProperties.EXPANDED;\n                }\n\n                @Override\n                protected void invalidated() {\n                    updateVerticalGap();\n                }\n            };\n        }\n\n        return this.expanded;\n    }\n\n    public Boolean isExpanded() {\n        return expanded != null && expanded.get();\n    }\n\n    public void setExpanded(Boolean expanded) {\n        expandedProperty().set(expanded);\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<JFXListView<?>, Number> VERTICAL_GAP =\n                new CssMetaData<>(\"-jfx-vertical-gap\",\n                        SizeConverter.getInstance(), 0) {\n                    @Override\n                    public boolean isSettable(JFXListView<?> control) {\n                        return control.verticalGap == null || !control.verticalGap.isBound();\n                    }\n\n                    @Override\n                    public StyleableDoubleProperty getStyleableProperty(JFXListView<?> control) {\n                        return control.verticalGapProperty();\n                    }\n                };\n        private static final CssMetaData<JFXListView<?>, Boolean> EXPANDED =\n                new CssMetaData<>(\"-jfx-expanded\",\n                        BooleanConverter.getInstance(), false) {\n                    @Override\n                    public boolean isSettable(JFXListView<?> control) {\n                        // it's only settable if the List is not shown yet\n                        return control.getHeight() == 0 && (control.expanded == null || !control.expanded.isBound());\n                    }\n\n                    @Override\n                    public StyleableBooleanProperty getStyleableProperty(JFXListView<?> control) {\n                        return control.expandedProperty();\n                    }\n                };\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables =\n                    new ArrayList<>(ListView.getClassCssMetaData());\n            Collections.addAll(styleables, VERTICAL_GAP, EXPANDED);\n            CHILD_STYLEABLES = List.copyOf(styleables);\n        }\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXPasswordField.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXPasswordFieldSkin;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.*;\nimport javafx.css.converter.BooleanConverter;\nimport javafx.css.converter.PaintConverter;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.PasswordField;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.TextField;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Locale;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.useJFXContextMenu;\n\n/**\n * JFXPasswordField is the material design implementation of a password Field.\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2016-03-09\n */\npublic class JFXPasswordField extends PasswordField {\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXPasswordField() {\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXPasswordFieldSkin(this);\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n        if (\"dalvik\".equals(System.getProperty(\"java.vm.name\").toLowerCase(Locale.ROOT))) {\n            this.setStyle(\"-fx-skin: \\\"com.jfoenix.android.skins.JFXPasswordFieldSkinAndroid\\\";\");\n        }\n\n\n        useJFXContextMenu(this);\n    }\n\n    /**\n     * Initialize the style class to 'jfx-password-field'.\n     * <p>\n     * This is the selector class from which CSS can be used to style\n     * this control.\n     */\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-password-field\";\n\n    /***************************************************************************\n     *                                                                         *\n     * Properties                                                              *\n     *                                                                         *\n     **************************************************************************/\n\n    /**\n     * holds the current active validator on the password field in case of validation error\n     */\n    private ReadOnlyObjectWrapper<ValidatorBase> activeValidator = new ReadOnlyObjectWrapper<>();\n\n    public ValidatorBase getActiveValidator() {\n        return activeValidator == null ? null : activeValidator.get();\n    }\n\n    public ReadOnlyObjectProperty<ValidatorBase> activeValidatorProperty() {\n        return this.activeValidator.getReadOnlyProperty();\n    }\n\n    /**\n     * list of validators that will validate the password value upon calling\n     * {{@link #validate()}\n     */\n    private ObservableList<ValidatorBase> validators = FXCollections.observableArrayList();\n\n    public ObservableList<ValidatorBase> getValidators() {\n        return validators;\n    }\n\n    public void setValidators(ValidatorBase... validators) {\n        this.validators.addAll(validators);\n    }\n\n    /**\n     * validates the password value using the list of validators provided by the user\n     * {{@link #setValidators(ValidatorBase...)}\n     *\n     * @return true if the value is valid else false\n     */\n    public boolean validate() {\n        for (ValidatorBase validator : validators) {\n            if (validator.getSrcControl() == null) {\n                validator.setSrcControl(this);\n            }\n            validator.validate();\n            if (validator.getHasErrors()) {\n                activeValidator.set(validator);\n                return false;\n            }\n        }\n        activeValidator.set(null);\n        return true;\n    }\n\n    public void resetValidation() {\n        pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false);\n        activeValidator.set(null);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * styleable Properties                                                    *\n     *                                                                         *\n     **************************************************************************/\n\n    /**\n     * set true to show a float the prompt text when focusing the field\n     */\n    private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXPasswordField.this, \"lableFloat\", false);\n\n    public final StyleableBooleanProperty labelFloatProperty() {\n        return this.labelFloat;\n    }\n\n    public final boolean isLabelFloat() {\n        return this.labelFloatProperty().get();\n    }\n\n    public final void setLabelFloat(final boolean labelFloat) {\n        this.labelFloatProperty().set(labelFloat);\n    }\n\n    /**\n     * default color used when the field is unfocused\n     */\n    private StyleableObjectProperty<Paint> unFocusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNFOCUS_COLOR, JFXPasswordField.this, \"unFocusColor\", Color.rgb(77, 77, 77));\n\n    public Paint getUnFocusColor() {\n        return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> unFocusColorProperty() {\n        return this.unFocusColor;\n    }\n\n    public void setUnFocusColor(Paint color) {\n        this.unFocusColor.set(color);\n    }\n\n    /**\n     * default color used when the field is focused\n     */\n    private StyleableObjectProperty<Paint> focusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.FOCUS_COLOR, JFXPasswordField.this, \"focusColor\", Color.valueOf(\"#4059A9\"));\n\n    public Paint getFocusColor() {\n        return focusColor == null ? Color.valueOf(\"#4059A9\") : focusColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> focusColorProperty() {\n        return this.focusColor;\n    }\n\n    public void setFocusColor(Paint color) {\n        this.focusColor.set(color);\n    }\n\n    /**\n     * disable animation on validation\n     */\n    private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXPasswordField.this, \"disableAnimation\", false);\n\n    public final StyleableBooleanProperty disableAnimationProperty() {\n        return this.disableAnimation;\n    }\n\n    public final Boolean isDisableAnimation() {\n        return disableAnimation != null && this.disableAnimationProperty().get();\n    }\n\n    public final void setDisableAnimation(final Boolean disabled) {\n        this.disableAnimationProperty().set(disabled);\n    }\n\n    private final static class StyleableProperties {\n        private static final CssMetaData<JFXPasswordField, Paint> UNFOCUS_COLOR = new CssMetaData<JFXPasswordField, Paint>(\"-jfx-unfocus-color\", PaintConverter.getInstance(), Color.valueOf(\"#A6A6A6\")) {\n            @Override\n            public boolean isSettable(JFXPasswordField control) {\n                return control.unFocusColor == null || !control.unFocusColor.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Paint> getStyleableProperty(JFXPasswordField control) {\n                return control.unFocusColorProperty();\n            }\n        };\n        private static final CssMetaData<JFXPasswordField, Paint> FOCUS_COLOR = new CssMetaData<JFXPasswordField, Paint>(\"-jfx-focus-color\", PaintConverter.getInstance(), Color.valueOf(\"#3f51b5\")) {\n            @Override\n            public boolean isSettable(JFXPasswordField control) {\n                return control.focusColor == null || !control.focusColor.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Paint> getStyleableProperty(JFXPasswordField control) {\n                return control.focusColorProperty();\n            }\n        };\n\n        private static final CssMetaData<JFXPasswordField, Boolean> LABEL_FLOAT = new CssMetaData<JFXPasswordField, Boolean>(\"-jfx-label-float\", BooleanConverter.getInstance(), false) {\n            @Override\n            public boolean isSettable(JFXPasswordField control) {\n                return control.labelFloat == null || !control.labelFloat.isBound();\n            }\n\n            @Override\n            public StyleableBooleanProperty getStyleableProperty(JFXPasswordField control) {\n                return control.labelFloatProperty();\n            }\n        };\n\n        private static final CssMetaData<JFXPasswordField, Boolean> DISABLE_ANIMATION = new CssMetaData<JFXPasswordField, Boolean>(\"-fx-disable-animation\", BooleanConverter.getInstance(), false) {\n            @Override\n            public boolean isSettable(JFXPasswordField control) {\n                return control.disableAnimation == null || !control.disableAnimation.isBound();\n            }\n\n            @Override\n            public StyleableBooleanProperty getStyleableProperty(JFXPasswordField control) {\n                return control.disableAnimationProperty();\n            }\n        };\n\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());\n            Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION);\n            CHILD_STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n    }\n\n    // inherit the styleable properties from parent\n    private List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        if (STYLEABLES == null) {\n            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());\n            styleables.addAll(getClassCssMetaData());\n            styleables.addAll(TextField.getClassCssMetaData());\n            STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n        return STYLEABLES;\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXPopupSkin;\nimport javafx.application.Platform;\nimport javafx.beans.DefaultProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.geometry.NodeOrientation;\nimport javafx.geometry.Point2D;\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.scene.control.PopupControl;\nimport javafx.scene.control.Skin;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.layout.Region;\nimport javafx.stage.Window;\n\n/// JFXPopup is the material design implementation of a popup.\n///\n/// @author Shadi Shaheen\n/// @version 2.0\n/// @since 2017-03-01\n@DefaultProperty(value = \"popupContent\")\npublic class JFXPopup extends PopupControl {\n\n    public enum PopupHPosition {\n        RIGHT,\n        LEFT;\n\n        public PopupHPosition getOpposite() {\n            return (this == RIGHT) ? LEFT : RIGHT;\n        }\n    }\n\n    public enum PopupVPosition {\n        TOP, BOTTOM\n    }\n\n    /// Creates empty popup.\n    public JFXPopup() {\n        this(null);\n    }\n\n    /// creates popup with a specified container and content\n    ///\n    /// @param content the node that will be shown in the popup\n    public JFXPopup(Region content) {\n        setPopupContent(content);\n        initialize();\n    }\n\n    private void initialize() {\n        this.setAutoFix(false);\n        this.setAutoHide(true);\n        this.setHideOnEscape(true);\n        this.setConsumeAutoHidingEvents(false);\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXPopupSkin(this);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Setters / Getters                                                       *\n     *                                                                         *\n     **************************************************************************/\n\n    private final ObjectProperty<Region> popupContent = new SimpleObjectProperty<>(new Pane());\n\n    public final ObjectProperty<Region> popupContentProperty() {\n        return this.popupContent;\n    }\n\n    public final Region getPopupContent() {\n        return this.popupContentProperty().get();\n    }\n\n    public final void setPopupContent(final Region popupContent) {\n        this.popupContentProperty().set(popupContent);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Public API                                                              *\n     *                                                                         *\n     **************************************************************************/\n\n    /// show the popup using the default position\n    public void show(Node node) {\n        this.show(node, PopupVPosition.TOP, PopupHPosition.LEFT, 0, 0);\n    }\n\n    /// show the popup according to the specified position\n    ///\n    /// @param vAlign can be TOP/BOTTOM\n    /// @param hAlign can be LEFT/RIGHT\n    public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign) {\n        this.show(node, vAlign, hAlign, 0, 0);\n    }\n\n    /// show the popup according to the specified position with a certain offset\n    ///\n    /// @param vAlign      can be TOP/BOTTOM\n    /// @param hAlign      can be LEFT/RIGHT\n    /// @param initOffsetX on the x-axis\n    /// @param initOffsetY on the y-axis\n    public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign, double initOffsetX, double initOffsetY) {\n        show(node, vAlign, hAlign, initOffsetX, initOffsetY, false);\n    }\n\n    public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign, double initOffsetX, double initOffsetY, boolean attachToNode) {\n        if (!isShowing()) {\n            Scene scene = node.getScene();\n            if (scene == null || scene.getWindow() == null) {\n                throw new IllegalStateException(\"Can not show popup. The node must be attached to a scene/window.\");\n            }\n            Window parent = scene.getWindow();\n            final Point2D origin = node.localToScene(0, 0);\n\n            boolean isRTL = node.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT;\n\n            double anchorX = parent.getX() + scene.getX() + origin.getX() + (hAlign == PopupHPosition.RIGHT ? ((Region) node).getWidth() : 0);\n            double anchorY = parent.getY() + origin.getY() + scene.getY() + (vAlign == PopupVPosition.BOTTOM ? ((Region) node).getHeight() : 0);\n\n            if (attachToNode)\n                this.show(node, anchorX, anchorY);\n            else\n                this.show(parent, anchorX, anchorY);\n\n            ((JFXPopupSkin) getSkin()).reset(vAlign, isRTL ? hAlign.getOpposite() : hAlign, isRTL ? -initOffsetX : initOffsetX, initOffsetY);\n            Platform.runLater(() -> ((JFXPopupSkin) getSkin()).animate());\n        }\n    }\n\n    public void show(Window window, double x, double y, PopupVPosition vAlign, PopupHPosition hAlign, double initOffsetX, double initOffsetY) {\n        if (!isShowing()) {\n            if (window == null) {\n                throw new IllegalStateException(\"Can not show popup. The node must be attached to a scene/window.\");\n            }\n            Window parent = window;\n            final double anchorX = parent.getX() + x + initOffsetX;\n            final double anchorY = parent.getY() + y + initOffsetY;\n            this.show(parent, anchorX, anchorY);\n            ((JFXPopupSkin) getSkin()).reset(vAlign, hAlign, initOffsetX, initOffsetY);\n            Platform.runLater(() -> ((JFXPopupSkin) getSkin()).animate());\n        }\n    }\n\n    @Override\n    public void hide() {\n        super.hide();\n        ((JFXPopupSkin) getSkin()).init();\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Stylesheet Handling                                                     *\n     *                                                                         *\n     **************************************************************************/\n\n    /// Initialize the style class to 'jfx-popup'.\n    ///\n    /// This is the selector class from which CSS can be used to style\n    /// this control.\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-popup\";\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXProgressBar.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXProgressBarSkin;\nimport javafx.scene.control.ProgressBar;\nimport javafx.scene.control.Skin;\n\n/// JFXProgressBar is the material design implementation of a progress bar.\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class JFXProgressBar extends ProgressBar {\n    /// Initialize the style class to 'jfx-progress-bar'.\n    ///\n    /// This is the selector class from which CSS can be used to style\n    /// this control.\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-progress-bar\";\n\n    public JFXProgressBar() {\n        initialize();\n    }\n\n    public JFXProgressBar(double progress) {\n        super(progress);\n        initialize();\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXProgressBarSkin(this);\n    }\n\n    private boolean smoothProgress = true;\n\n    public boolean isSmoothProgress() {\n        return smoothProgress;\n    }\n\n    public void setSmoothProgress(boolean smoothProgress) {\n        this.smoothProgress = smoothProgress;\n    }\n\n    private void initialize() {\n        setPrefWidth(200);\n        getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXRadioButton.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXRadioButtonSkin;\nimport javafx.css.*;\nimport javafx.css.converter.ColorConverter;\nimport javafx.scene.control.RadioButton;\nimport javafx.scene.control.Skin;\nimport javafx.scene.paint.Color;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/// JFXRadioButton is the material design implementation of a radio button.\n///\n/// @author Bashir Elias & Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class JFXRadioButton extends RadioButton {\n\n    public JFXRadioButton(String text) {\n        super(text);\n        initialize();\n    }\n\n    public JFXRadioButton() {\n        initialize();\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXRadioButtonSkin(this);\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    /// Initialize the style class to 'jfx-radio-button'.\n    ///\n    /// This is the selector class from which CSS can be used to style\n    /// this control.\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-radio-button\";\n\n    /// default color used when the radio button is selected\n    private StyleableObjectProperty<Color> selectedColor;\n\n    private static final Color DEFAULT_SELECTED_COLOR = Color.valueOf(\"#0F9D58\");\n\n    public final StyleableObjectProperty<Color> selectedColorProperty() {\n        if (selectedColor == null) {\n            selectedColor = new SimpleStyleableObjectProperty<>(StyleableProperties.SELECTED_COLOR,\n                    JFXRadioButton.this,\n                    \"selectedColor\",\n                    DEFAULT_SELECTED_COLOR);\n        }\n        return this.selectedColor;\n    }\n\n    public final Color getSelectedColor() {\n        return selectedColor == null ? DEFAULT_SELECTED_COLOR : this.selectedColorProperty().get();\n    }\n\n    public final void setSelectedColor(final Color selectedColor) {\n        this.selectedColorProperty().set(selectedColor);\n    }\n\n    /// default color used when the radio button is not selected\n    private StyleableObjectProperty<Color> unSelectedColor;\n\n    private static final Color DEFAULT_UNSELECTED_COLOR = Color.valueOf(\"#5A5A5A\");\n\n    public final StyleableObjectProperty<Color> unSelectedColorProperty() {\n        if (unSelectedColor == null) {\n            unSelectedColor = new SimpleStyleableObjectProperty<>(\n                    StyleableProperties.UNSELECTED_COLOR,\n                    JFXRadioButton.this,\n                    \"unSelectedColor\",\n                    DEFAULT_UNSELECTED_COLOR);\n        }\n        return this.unSelectedColor;\n    }\n\n    public final Color getUnSelectedColor() {\n        return unSelectedColor == null ? DEFAULT_UNSELECTED_COLOR : this.unSelectedColorProperty().get();\n    }\n\n    public final void setUnSelectedColor(final Color unSelectedColor) {\n        this.unSelectedColorProperty().set(unSelectedColor);\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<JFXRadioButton, Color> SELECTED_COLOR =\n                new CssMetaData<JFXRadioButton, Color>(\"-jfx-selected-color\",\n                        ColorConverter.getInstance(), DEFAULT_SELECTED_COLOR) {\n                    @Override\n                    public boolean isSettable(JFXRadioButton control) {\n                        return control.selectedColor == null || !control.selectedColor.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Color> getStyleableProperty(JFXRadioButton control) {\n                        return control.selectedColorProperty();\n                    }\n                };\n        private static final CssMetaData<JFXRadioButton, Color> UNSELECTED_COLOR =\n                new CssMetaData<JFXRadioButton, Color>(\"-jfx-unselected-color\",\n                        ColorConverter.getInstance(), DEFAULT_UNSELECTED_COLOR) {\n                    @Override\n                    public boolean isSettable(JFXRadioButton control) {\n                        return control.unSelectedColor == null || !control.unSelectedColor.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Color> getStyleableProperty(JFXRadioButton control) {\n                        return control.unSelectedColorProperty();\n                    }\n                };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables =\n                    new ArrayList<>(RadioButton.getClassCssMetaData());\n            Collections.addAll(styleables,\n                    SELECTED_COLOR,\n                    UNSELECTED_COLOR\n            );\n            CHILD_STYLEABLES = List.copyOf(styleables);\n        }\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXRippler.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.converters.RipplerMaskTypeConverter;\nimport com.jfoenix.utils.JFXNodeUtils;\nimport javafx.animation.*;\nimport javafx.beans.DefaultProperty;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.css.*;\nimport javafx.css.converter.BooleanConverter;\nimport javafx.css.converter.PaintConverter;\nimport javafx.css.converter.SizeConverter;\nimport javafx.geometry.Bounds;\nimport javafx.scene.CacheHint;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\nimport javafx.scene.shape.Circle;\nimport javafx.scene.shape.Rectangle;\nimport javafx.scene.shape.Shape;\nimport javafx.util.Duration;\n\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * JFXRippler is the material design implementation of a ripple effect.\n * the ripple effect can be applied to any node in the scene. JFXRippler is\n * a {@link StackPane} container that holds a specified node (control node) and a ripple generator.\n * <p>\n * UPDATE NOTES:\n * - fireEventProgrammatically(Event) method has been removed as the ripple controller is\n * the control itself, so you can trigger manual ripple by firing mouse event on the control\n * instead of JFXRippler\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2016-03-09\n */\n@DefaultProperty(value = \"control\")\npublic class JFXRippler extends StackPane {\n    public enum RipplerPos {\n        FRONT, BACK\n    }\n\n    public enum RipplerMask {\n        CIRCLE, RECT, FIT\n    }\n\n    protected RippleGenerator rippler;\n    protected Pane ripplerPane;\n    protected Node control;\n\n    protected static final double RIPPLE_MAX_RADIUS = 300;\n    private static final Interpolator RIPPLE_INTERPOLATOR = Interpolator.SPLINE(0.0825,\n            0.3025,\n            0.0875,\n            0.9975); //0.1, 0.54, 0.28, 0.95);\n\n    private boolean forceOverlay = false;\n\n    /// creates empty rippler node\n    public JFXRippler() {\n        this(null, RipplerMask.RECT, RipplerPos.FRONT);\n    }\n\n    /// creates a rippler for the specified control\n    public JFXRippler(Node control) {\n        this(control, RipplerMask.RECT, RipplerPos.FRONT);\n    }\n\n    /// creates a rippler for the specified control\n    ///\n    /// @param pos can be either FRONT/BACK (position the ripple effect infront of or behind the control)\n    public JFXRippler(Node control, RipplerPos pos) {\n        this(control, RipplerMask.RECT, pos);\n    }\n\n    /// creates a rippler for the specified control and apply the specified mask to it\n    ///\n    /// @param mask can be either rectangle/cricle\n    public JFXRippler(Node control, RipplerMask mask) {\n        this(control, mask, RipplerPos.FRONT);\n    }\n\n    /// creates a rippler for the specified control, mask and position.\n    ///\n    /// @param mask can be either rectangle/cricle\n    /// @param pos  can be either FRONT/BACK (position the ripple effect infront of or behind the control)\n    public JFXRippler(Node control, RipplerMask mask, RipplerPos pos) {\n        initialize();\n\n        setMaskType(mask);\n        setPosition(pos);\n        createRippleUI();\n        setControl(control);\n\n        // listen to control position changed\n        position.addListener(observable -> updateControlPosition());\n\n        setPickOnBounds(false);\n        setCache(true);\n        setCacheHint(CacheHint.SPEED);\n        setCacheShape(true);\n    }\n\n    protected final void createRippleUI() {\n        // create rippler panels\n        rippler = new RippleGenerator();\n        ripplerPane = new StackPane();\n        ripplerPane.setMouseTransparent(true);\n        ripplerPane.getChildren().add(rippler);\n        getChildren().add(ripplerPane);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Setters / Getters                                                       *\n     *                                                                         *\n     **************************************************************************/\n\n    public void setControl(Node control) {\n        if (control != null) {\n            this.control = control;\n            // position control\n            positionControl(control);\n            // add control listeners to generate / release ripples\n            initControlListeners();\n        }\n    }\n\n    // Override this method to create JFXRippler for a control outside the ripple\n    protected void positionControl(Node control) {\n        if (this.position.get() == RipplerPos.BACK) {\n            getChildren().add(control);\n        } else {\n            getChildren().add(0, control);\n        }\n    }\n\n    protected void updateControlPosition() {\n        if (this.position.get() == RipplerPos.BACK) {\n            ripplerPane.toBack();\n        } else {\n            ripplerPane.toFront();\n        }\n    }\n\n    public Node getControl() {\n        return control;\n    }\n\n    // methods that can be changed by extending the rippler class\n\n    /// generate the clipping mask\n    ///\n    /// @return the mask node\n    protected Node getMask() {\n        double borderWidth = ripplerPane.getBorder() != null ? ripplerPane.getBorder().getInsets().getTop() : 0;\n        Bounds bounds = control.getBoundsInParent();\n        double width = control.getLayoutBounds().getWidth();\n        double height = control.getLayoutBounds().getHeight();\n        double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX());\n        double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY());\n        double diffMaxX = Math.abs(control.getBoundsInLocal().getMaxX() - control.getLayoutBounds().getMaxX());\n        double diffMaxY = Math.abs(control.getBoundsInLocal().getMaxY() - control.getLayoutBounds().getMaxY());\n        Node mask;\n        switch (getMaskType()) {\n            case RECT:\n                mask = new Rectangle(bounds.getMinX() + diffMinX - snappedLeftInset(),\n                        bounds.getMinY() + diffMinY - snappedTopInset(),\n                        width - 2 * borderWidth,\n                        height - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane\n                break;\n            case CIRCLE:\n                double radius = Math.min((width / 2) - 2 * borderWidth, (height / 2) - 2 * borderWidth);\n                mask = new Circle((bounds.getMinX() + diffMinX + bounds.getMaxX() - diffMaxX) / 2 - snappedLeftInset(),\n                        (bounds.getMinY() + diffMinY + bounds.getMaxY() - diffMaxY) / 2 - snappedTopInset(),\n                        radius,\n                        Color.BLUE);\n                break;\n            case FIT:\n                mask = new Region();\n                if (control instanceof Shape) {\n                    ((Region) mask).setShape((Shape) control);\n                } else if (control instanceof Region) {\n                    ((Region) mask).setShape(((Region) control).getShape());\n                    JFXNodeUtils.updateBackground(((Region) control).getBackground(), (Region) mask);\n                }\n                mask.resize(width, height);\n                mask.relocate(bounds.getMinX() + diffMinX, bounds.getMinY() + diffMinY);\n                break;\n            default:\n                mask = new Rectangle(bounds.getMinX() + diffMinX - snappedLeftInset(),\n                        bounds.getMinY() + diffMinY - snappedTopInset(),\n                        width - 2 * borderWidth,\n                        height - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane\n                break;\n        }\n        return mask;\n    }\n\n    /**\n     * compute the ripple radius\n     *\n     * @return the ripple radius size\n     */\n    protected double computeRippleRadius() {\n        double width2 = control.getLayoutBounds().getWidth() * control.getLayoutBounds().getWidth();\n        double height2 = control.getLayoutBounds().getHeight() * control.getLayoutBounds().getHeight();\n        return Math.min(Math.sqrt(width2 + height2), RIPPLE_MAX_RADIUS) * 1.1 + 5;\n    }\n\n    protected void setOverLayBounds(Rectangle overlay) {\n        overlay.setWidth(control.getLayoutBounds().getWidth());\n        overlay.setHeight(control.getLayoutBounds().getHeight());\n    }\n\n    /**\n     * init mouse listeners on the control\n     */\n    protected void initControlListeners() {\n        // if the control got resized the overlay rect must be rest\n        control.layoutBoundsProperty().addListener(observable -> resetRippler());\n        if (getChildren().contains(control)) {\n            control.boundsInParentProperty().addListener(observable -> resetRippler());\n        }\n        control.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {\n            if (event.getButton() == MouseButton.PRIMARY)\n                createRipple(event.getX(), event.getY());\n        });\n        // create fade out transition for the ripple\n        control.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {\n            if (event.getButton() == MouseButton.PRIMARY)\n                releaseRipple();\n        });\n    }\n\n    /**\n     * creates Ripple effect\n     */\n    protected void createRipple(double x, double y) {\n        if (!isRipplerDisabled()) {\n            rippler.setGeneratorCenterX(x);\n            rippler.setGeneratorCenterY(y);\n            rippler.createRipple();\n        }\n    }\n\n    protected void releaseRipple() {\n        rippler.releaseRipple();\n    }\n\n    /**\n     * creates Ripple effect in the center of the control\n     *\n     * @return a runnable to release the ripple when needed\n     */\n    public Runnable createManualRipple() {\n        if (!isRipplerDisabled()) {\n            rippler.setGeneratorCenterX(control.getLayoutBounds().getWidth() / 2);\n            rippler.setGeneratorCenterY(control.getLayoutBounds().getHeight() / 2);\n            rippler.createRipple();\n            return () -> {\n                // create fade out transition for the ripple\n                releaseRipple();\n            };\n        }\n        return () -> {\n        };\n    }\n\n    /// show/hide the ripple overlay\n    ///\n    /// @param forceOverlay used to hold the overlay after ripple action\n    public void setOverlayVisible(boolean visible, boolean forceOverlay) {\n        this.forceOverlay = forceOverlay;\n        setOverlayVisible(visible);\n    }\n\n    /// show/hide the ripple overlay\n    /// NOTE: setting overlay visibility to false will reset forceOverlay to false\n    public void setOverlayVisible(boolean visible) {\n        if (visible) {\n            showOverlay();\n        } else {\n            forceOverlay = false;\n            hideOverlay();\n        }\n    }\n\n    /**\n     * this method will be set to private in future versions of JFoenix,\n     * user the method {@link #setOverlayVisible(boolean)}\n     */\n    public void showOverlay() {\n        if (rippler.overlayRect != null) {\n            rippler.overlayRect.outAnimation.stop();\n        }\n        rippler.createOverlay();\n        rippler.overlayRect.inAnimation.play();\n    }\n\n    public void hideOverlay() {\n        if (!forceOverlay) {\n            if (rippler.overlayRect != null) {\n                rippler.overlayRect.inAnimation.stop();\n            }\n            if (rippler.overlayRect != null) {\n                rippler.overlayRect.outAnimation.play();\n            }\n        } else {\n            System.err.println(\"Ripple Overlay is forced!\");\n        }\n    }\n\n    /**\n     * Generates ripples on the screen every 0.3 seconds or whenever\n     * the createRipple method is called. Ripples grow and fade out\n     * over 0.6 seconds\n     */\n    protected final class RippleGenerator extends Group {\n\n        private double generatorCenterX = 0;\n        private double generatorCenterY = 0;\n        private OverLayRipple overlayRect;\n        private final AtomicBoolean generating = new AtomicBoolean(false);\n        private boolean cacheRipplerClip = false;\n        private boolean resetClip = false;\n        private final Queue<Ripple> ripplesQueue = new LinkedList<>();\n\n        RippleGenerator() {\n            // improve in performance, by preventing\n            // redrawing the parent when the ripple effect is triggered\n            this.setManaged(false);\n            this.setCache(true);\n            this.setCacheHint(CacheHint.SPEED);\n        }\n\n        void createRipple() {\n            if (!generating.getAndSet(true)) {\n                // create overlay once then change its color later\n                createOverlay();\n                if (this.getClip() == null || (getChildren().size() == 1 && !cacheRipplerClip) || resetClip) {\n                    this.setClip(getMask());\n                }\n                this.resetClip = false;\n\n                // create the ripple effect\n                final Ripple ripple = new Ripple(generatorCenterX, generatorCenterY);\n                getChildren().add(ripple);\n                ripplesQueue.add(ripple);\n\n                // animate the ripple\n                overlayRect.outAnimation.stop();\n                overlayRect.inAnimation.play();\n                ripple.inAnimation.play();\n            }\n        }\n\n        private void releaseRipple() {\n            Ripple ripple = ripplesQueue.poll();\n            if (ripple != null) {\n                ripple.inAnimation.stop();\n                ripple.outAnimation = new Timeline(\n                        new KeyFrame(Duration.millis(Math.min(800, (0.9 * 500) / ripple.getScaleX()))\n                                , ripple.outKeyValues));\n                ripple.outAnimation.setOnFinished((event) -> getChildren().remove(ripple));\n                ripple.outAnimation.play();\n                if (generating.getAndSet(false)) {\n                    if (overlayRect != null) {\n                        overlayRect.inAnimation.stop();\n                        if (!forceOverlay) {\n                            overlayRect.outAnimation.play();\n                        }\n                    }\n                }\n            }\n        }\n\n        void cacheRippleClip(boolean cached) {\n            cacheRipplerClip = cached;\n        }\n\n        void createOverlay() {\n            if (overlayRect == null) {\n                overlayRect = new OverLayRipple();\n                overlayRect.setClip(getMask());\n                getChildren().add(0, overlayRect);\n                overlayRect.fillProperty().bind(Bindings.createObjectBinding(() -> {\n                    if (getRipplerFill() instanceof Color fill) {\n                        return new Color(fill.getRed(),\n                                fill.getGreen(),\n                                fill.getBlue(),\n                                0.2);\n                    } else {\n                        return Color.TRANSPARENT;\n                    }\n                }, ripplerFillProperty()));\n            }\n        }\n\n        void setGeneratorCenterX(double generatorCenterX) {\n            this.generatorCenterX = generatorCenterX;\n        }\n\n        void setGeneratorCenterY(double generatorCenterY) {\n            this.generatorCenterY = generatorCenterY;\n        }\n\n        private final class OverLayRipple extends Rectangle {\n            // Overlay ripple animations\n            Animation inAnimation = new Timeline(new KeyFrame(Duration.millis(300),\n                    new KeyValue(opacityProperty(), 1, Interpolator.EASE_IN)));\n\n            Animation outAnimation = new Timeline(new KeyFrame(Duration.millis(300),\n                    new KeyValue(opacityProperty(), 0, Interpolator.EASE_OUT)));\n\n            OverLayRipple() {\n                super();\n                setOverLayBounds(this);\n                this.getStyleClass().add(\"jfx-rippler-overlay\");\n                // update initial position\n                if (JFXRippler.this.getChildrenUnmodifiable().contains(control)) {\n                    double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX());\n                    double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY());\n                    Bounds bounds = control.getBoundsInParent();\n                    this.setX(bounds.getMinX() + diffMinX - snappedLeftInset());\n                    this.setY(bounds.getMinY() + diffMinY - snappedTopInset());\n                }\n                // set initial attributes\n                setOpacity(0);\n                setCache(true);\n                setCacheHint(CacheHint.SPEED);\n                setCacheShape(true);\n                setManaged(false);\n            }\n        }\n\n        private final class Ripple extends Circle {\n\n            KeyValue[] outKeyValues;\n            Animation outAnimation = null;\n            Animation inAnimation = null;\n\n            private Ripple(double centerX, double centerY) {\n                super(centerX,\n                        centerY,\n                        getRipplerRadius() == Region.USE_COMPUTED_SIZE ?\n                                computeRippleRadius() : getRipplerRadius(), null);\n                setCache(true);\n                setCacheHint(CacheHint.SPEED);\n                setCacheShape(true);\n                setManaged(false);\n                setSmooth(true);\n\n                KeyValue[] inKeyValues = new KeyValue[isRipplerRecenter() ? 4 : 2];\n                outKeyValues = new KeyValue[isRipplerRecenter() ? 5 : 3];\n\n                inKeyValues[0] = new KeyValue(scaleXProperty(), 0.9, RIPPLE_INTERPOLATOR);\n                inKeyValues[1] = new KeyValue(scaleYProperty(), 0.9, RIPPLE_INTERPOLATOR);\n\n                outKeyValues[0] = new KeyValue(this.scaleXProperty(), 1, RIPPLE_INTERPOLATOR);\n                outKeyValues[1] = new KeyValue(this.scaleYProperty(), 1, RIPPLE_INTERPOLATOR);\n                outKeyValues[2] = new KeyValue(this.opacityProperty(), 0, RIPPLE_INTERPOLATOR);\n\n                if (isRipplerRecenter()) {\n                    double dx = (control.getLayoutBounds().getWidth() / 2 - centerX) / 1.55;\n                    double dy = (control.getLayoutBounds().getHeight() / 2 - centerY) / 1.55;\n                    inKeyValues[2] = outKeyValues[3] = new KeyValue(translateXProperty(),\n                            Math.signum(dx) * Math.min(Math.abs(dx),\n                                    this.getRadius() / 2),\n                            RIPPLE_INTERPOLATOR);\n                    inKeyValues[3] = outKeyValues[4] = new KeyValue(translateYProperty(),\n                            Math.signum(dy) * Math.min(Math.abs(dy),\n                                    this.getRadius() / 2),\n                            RIPPLE_INTERPOLATOR);\n                }\n                inAnimation = new Timeline(new KeyFrame(Duration.ZERO,\n                        new KeyValue(scaleXProperty(),\n                                0,\n                                RIPPLE_INTERPOLATOR),\n                        new KeyValue(scaleYProperty(),\n                                0,\n                                RIPPLE_INTERPOLATOR),\n                        new KeyValue(translateXProperty(),\n                                0,\n                                RIPPLE_INTERPOLATOR),\n                        new KeyValue(translateYProperty(),\n                                0,\n                                RIPPLE_INTERPOLATOR),\n                        new KeyValue(opacityProperty(),\n                                1,\n                                RIPPLE_INTERPOLATOR)\n                ), new KeyFrame(Duration.millis(900), inKeyValues));\n\n                setScaleX(0);\n                setScaleY(0);\n                if (getRipplerFill() instanceof Color fill) {\n                    Color circleColor = new Color(fill.getRed(),\n                            fill.getGreen(),\n                            fill.getBlue(),\n                            0.3);\n                    setStroke(circleColor);\n                    setFill(circleColor);\n                } else {\n                    setStroke(getRipplerFill());\n                    setFill(getRipplerFill());\n                }\n            }\n        }\n\n        public void clear() {\n            getChildren().clear();\n            rippler.overlayRect = null;\n            generating.set(false);\n        }\n    }\n\n    private void resetOverLay() {\n        if (rippler.overlayRect != null) {\n            rippler.overlayRect.inAnimation.stop();\n            final RippleGenerator.OverLayRipple oldOverlay = rippler.overlayRect;\n            rippler.overlayRect.outAnimation.setOnFinished((finish) -> rippler.getChildren().remove(oldOverlay));\n            rippler.overlayRect.outAnimation.play();\n            rippler.overlayRect = null;\n        }\n    }\n\n    private void resetClip() {\n        this.rippler.resetClip = true;\n    }\n\n    protected void resetRippler() {\n        resetOverLay();\n        resetClip();\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Stylesheet Handling                                                     *\n     *                                                                         *\n     **************************************************************************/\n\n    /**\n     * Initialize the style class to 'jfx-rippler'.\n     * <p>\n     * This is the selector class from which CSS can be used to style\n     * this control.\n     */\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-rippler\";\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    /**\n     * the ripple recenter property, by default it's false.\n     * if true the ripple effect will show gravitational pull to the center of its control\n     */\n    private StyleableBooleanProperty ripplerRecenter;\n\n    public boolean isRipplerRecenter() {\n        return ripplerRecenter != null && ripplerRecenter.get();\n    }\n\n    public StyleableBooleanProperty ripplerRecenterProperty() {\n        if (this.ripplerRecenter == null) {\n            this.ripplerRecenter = new SimpleStyleableBooleanProperty(\n                    StyleableProperties.RIPPLER_RECENTER,\n                    JFXRippler.this,\n                    \"ripplerRecenter\",\n                    false);\n        }\n        return this.ripplerRecenter;\n    }\n\n    public void setRipplerRecenter(boolean recenter) {\n        ripplerRecenterProperty().set(recenter);\n    }\n\n    /**\n     * the ripple radius size, by default it will be automatically computed.\n     */\n    private StyleableDoubleProperty ripplerRadius;\n\n    public double getRipplerRadius() {\n        return ripplerRadius == null ? Region.USE_COMPUTED_SIZE : ripplerRadius.get();\n    }\n\n    public StyleableDoubleProperty ripplerRadiusProperty() {\n        if (this.ripplerRadius == null) {\n            this.ripplerRadius = new SimpleStyleableDoubleProperty(\n                    StyleableProperties.RIPPLER_RADIUS,\n                    JFXRippler.this,\n                    \"ripplerRadius\",\n                    Region.USE_COMPUTED_SIZE);\n        }\n        return this.ripplerRadius;\n    }\n\n    public void setRipplerRadius(double radius) {\n        ripplerRadiusProperty().set(radius);\n    }\n\n    private static final Color DEFAULT_RIPPLER_FILL = Color.rgb(0, 200, 255);\n\n    /**\n     * the default color of the ripple effect\n     */\n    private StyleableObjectProperty<Paint> ripplerFill;\n\n    public Paint getRipplerFill() {\n        return ripplerFill == null ? DEFAULT_RIPPLER_FILL : ripplerFill.get();\n    }\n\n    public StyleableObjectProperty<Paint> ripplerFillProperty() {\n        if (this.ripplerFill == null) {\n            this.ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,\n                    JFXRippler.this,\n                    \"ripplerFill\",\n                    DEFAULT_RIPPLER_FILL);\n        }\n        return this.ripplerFill;\n    }\n\n    public void setRipplerFill(Paint color) {\n        ripplerFillProperty().set(color);\n    }\n\n    /// mask property used for clipping the rippler.\n    /// can be either CIRCLE/RECT\n    private StyleableObjectProperty<RipplerMask> maskType;\n\n    public RipplerMask getMaskType() {\n        return maskType == null ? RipplerMask.RECT : maskType.get();\n    }\n\n    public StyleableObjectProperty<RipplerMask> maskTypeProperty() {\n        if (this.maskType == null) {\n            this.maskType = new SimpleStyleableObjectProperty<>(\n                    StyleableProperties.MASK_TYPE,\n                    JFXRippler.this,\n                    \"maskType\",\n                    RipplerMask.RECT);\n        }\n        return this.maskType;\n    }\n\n    public void setMaskType(RipplerMask type) {\n        if (this.maskType != null || type != RipplerMask.RECT)\n            maskTypeProperty().set(type);\n    }\n\n    /**\n     * the ripple disable, by default it's false.\n     * if true the ripple effect will be hidden\n     */\n    private StyleableBooleanProperty ripplerDisabled;\n\n    public boolean isRipplerDisabled() {\n        return ripplerDisabled != null && ripplerDisabled.get();\n    }\n\n    public StyleableBooleanProperty ripplerDisabledProperty() {\n        if (this.ripplerDisabled == null) {\n            this.ripplerDisabled = new SimpleStyleableBooleanProperty(\n                    StyleableProperties.RIPPLER_DISABLED,\n                    JFXRippler.this,\n                    \"ripplerDisabled\",\n                    false);\n        }\n        return this.ripplerDisabled;\n    }\n\n    public void setRipplerDisabled(boolean disabled) {\n        ripplerDisabledProperty().set(disabled);\n    }\n\n    /**\n     * indicates whether the ripple effect is infront of or behind the node\n     */\n    protected ObjectProperty<RipplerPos> position = new SimpleObjectProperty<>();\n\n    public void setPosition(RipplerPos pos) {\n        this.position.set(pos);\n    }\n\n    public RipplerPos getPosition() {\n        return position == null ? RipplerPos.FRONT : position.get();\n    }\n\n    public ObjectProperty<RipplerPos> positionProperty() {\n        return this.position;\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<JFXRippler, Boolean> RIPPLER_RECENTER =\n                new CssMetaData<>(\"-jfx-rippler-recenter\",\n                        BooleanConverter.getInstance(), false) {\n                    @Override\n                    public boolean isSettable(JFXRippler control) {\n                        return control.ripplerRecenter == null || !control.ripplerRecenter.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Boolean> getStyleableProperty(JFXRippler control) {\n                        return control.ripplerRecenterProperty();\n                    }\n                };\n        private static final CssMetaData<JFXRippler, Boolean> RIPPLER_DISABLED =\n                new CssMetaData<>(\"-jfx-rippler-disabled\",\n                        BooleanConverter.getInstance(), false) {\n                    @Override\n                    public boolean isSettable(JFXRippler control) {\n                        return control.ripplerDisabled == null || !control.ripplerDisabled.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Boolean> getStyleableProperty(JFXRippler control) {\n                        return control.ripplerDisabledProperty();\n                    }\n                };\n        private static final CssMetaData<JFXRippler, Paint> RIPPLER_FILL =\n                new CssMetaData<>(\"-jfx-rippler-fill\",\n                        PaintConverter.getInstance(), DEFAULT_RIPPLER_FILL) {\n                    @Override\n                    public boolean isSettable(JFXRippler control) {\n                        return control.ripplerFill == null || !control.ripplerFill.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Paint> getStyleableProperty(JFXRippler control) {\n                        return control.ripplerFillProperty();\n                    }\n                };\n        private static final CssMetaData<JFXRippler, Number> RIPPLER_RADIUS =\n                new CssMetaData<>(\"-jfx-rippler-radius\",\n                        SizeConverter.getInstance(), Region.USE_COMPUTED_SIZE) {\n                    @Override\n                    public boolean isSettable(JFXRippler control) {\n                        return control.ripplerRadius == null || !control.ripplerRadius.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Number> getStyleableProperty(JFXRippler control) {\n                        return control.ripplerRadiusProperty();\n                    }\n                };\n        private static final CssMetaData<JFXRippler, RipplerMask> MASK_TYPE =\n                new CssMetaData<>(\"-jfx-mask-type\",\n                        RipplerMaskTypeConverter.getInstance(), RipplerMask.RECT) {\n                    @Override\n                    public boolean isSettable(JFXRippler control) {\n                        return control.maskType == null || !control.maskType.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<RipplerMask> getStyleableProperty(JFXRippler control) {\n                        return control.maskTypeProperty();\n                    }\n                };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables =\n                    new ArrayList<>(StackPane.getClassCssMetaData());\n            Collections.addAll(styleables,\n                    RIPPLER_RECENTER,\n                    RIPPLER_RADIUS,\n                    RIPPLER_FILL,\n                    MASK_TYPE,\n                    RIPPLER_DISABLED\n            );\n            STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.STYLEABLES;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXSlider.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.converters.IndicatorPositionConverter;\nimport com.jfoenix.skins.JFXSliderSkin;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.css.*;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.Slider;\nimport javafx.util.Callback;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/// JFXSlider is the material design implementation of a slider.\n///\n/// @author Bashir Elias & Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class JFXSlider extends Slider {\n\n    public JFXSlider() {\n        super(0, 100, 50);\n        initialize();\n    }\n\n    public JFXSlider(double min, double max, double value) {\n        super(min, max, value);\n        initialize();\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXSliderSkin(this);\n    }\n\n    private void initialize() {\n        getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    public enum IndicatorPosition {\n        LEFT, RIGHT\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Properties                                                              *\n     *                                                                         *\n     **************************************************************************/\n\n    /// String binding factory for the slider value.\n    /// Sets a custom string for the value text (by default, it shows the value rounded to the nearest whole number).\n    ///\n    ///\n    /// For example, to have the value displayed as a percentage (assuming the slider has a range of (0, 100)):\n    /// ```java\n    /// JFXSlider mySlider = ...\n    /// mySlider.setValueFactory(slider ->\n    ///        Bindings.createStringBinding(\n    ///            () -> ((int) slider.getValue()) + \"%\",\n    ///            slider.valueProperty()\n    ///        )\n    /// );\n    /// ```\n    ///\n    /// NOTE: might be replaced later with a call back to create the animated thumb node\n    ///\n    /// @param callback a callback to create the string value binding\n    private ObjectProperty<Callback<JFXSlider, StringBinding>> valueFactory;\n\n    public final ObjectProperty<Callback<JFXSlider, StringBinding>> valueFactoryProperty() {\n        if (valueFactory == null) {\n            valueFactory = new SimpleObjectProperty<>(this, \"valueFactory\");\n        }\n        return valueFactory;\n    }\n\n    /// @return the current slider value factory\n    public final Callback<JFXSlider, StringBinding> getValueFactory() {\n        return valueFactory == null ? null : valueFactory.get();\n    }\n\n    /// sets custom string binding for the slider text value\n    ///\n    /// @param valueFactory a callback to create the string value binding\n    public final void setValueFactory(final Callback<JFXSlider, StringBinding> valueFactory) {\n        this.valueFactoryProperty().set(valueFactory);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Stylesheet Handling                                                     *\n     *                                                                         *\n     **************************************************************************/\n\n    /// Initialize the style class to 'jfx-slider'.\n    ///\n    /// This is the selector class from which CSS can be used to style\n    /// this control.\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-slider\";\n\n    /// indicates the position of the slider indicator, can be\n    /// either LEFT or RIGHT\n    private StyleableObjectProperty<IndicatorPosition> indicatorPosition;\n\n    public StyleableObjectProperty<IndicatorPosition> indicatorPositionProperty() {\n        if (indicatorPosition == null) {\n            indicatorPosition = new SimpleStyleableObjectProperty<>(\n                    StyleableProperties.INDICATOR_POSITION,\n                    JFXSlider.this,\n                    \"indicatorPosition\",\n                    IndicatorPosition.LEFT);\n        }\n        return this.indicatorPosition;\n    }\n\n    public IndicatorPosition getIndicatorPosition() {\n        return indicatorPosition == null ? IndicatorPosition.LEFT : indicatorPosition.get();\n    }\n\n    public void setIndicatorPosition(IndicatorPosition pos) {\n        indicatorPositionProperty().set(pos);\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<JFXSlider, IndicatorPosition> INDICATOR_POSITION = new CssMetaData<>(\n                \"-jfx-indicator-position\",\n                IndicatorPositionConverter.getInstance(),\n                IndicatorPosition.LEFT) {\n            @Override\n            public boolean isSettable(JFXSlider control) {\n                return control.indicatorPosition == null || !control.indicatorPosition.isBound();\n            }\n\n            @Override\n            public StyleableProperty<IndicatorPosition> getStyleableProperty(JFXSlider control) {\n                return control.indicatorPositionProperty();\n            }\n        };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(\n                    Slider.getClassCssMetaData());\n            Collections.addAll(styleables, INDICATOR_POSITION);\n            CHILD_STYLEABLES = List.copyOf(styleables);\n        }\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXSnackbar.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.PauseTransition;\nimport javafx.animation.Timeline;\nimport javafx.application.Platform;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.WeakChangeListener;\nimport javafx.css.PseudoClass;\nimport javafx.event.Event;\nimport javafx.event.EventType;\nimport javafx.geometry.Bounds;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.layout.StackPane;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.animation.Motion;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/// \"Snackbars provide brief messages about app processes at the bottom of the screen\"\n/// (<a href=\"https://material.io/design/components/snackbars.html#\">Material Design Guidelines</a>).\n///\n/// To show a snackbar you need to\n/// <ol>\n///   - Have a [Pane] (snackbarContainer) to show the snackbar on top of. Register it in\n///     [the JFXSnackbar constructor][#JFXSnackbar(Pane)] or using the [#registerSnackbarContainer(Pane)] method.\n///   - Have or create a [JFXSnackbar].  - Having one snackbar where you pass all your\n///     [SnackbarEvents][JFXSnackbar.SnackbarEvent] will ensure that the [enqueuemethod][JFXSnackbar#enqueue(SnackbarEvent)] works as intended.\n///\n///   - Have something to show in the snackbar. A [JFXSnackbarLayout] is nice and pretty,\n///     but any arbitrary [Node] will do.\n///   - Create a [SnackbarEvent][JFXSnackbar.SnackbarEvent] specifying the contents and the\n///     duration.\n/// </ol>\n///\n/// Finally, with all those things prepared, show your snackbar using\n/// [snackbar.enqueue(snackbarEvent);][JFXSnackbar#enqueue(SnackbarEvent)].\n///\n/// It's most convenient to create functions to do most of this (creating the layout and event) with the default\n/// settings; that way all you need to do to show a snackbar is specify the message or just the message and the duration.\n///\n/// @see <a href=\"https://material.io/design/components/snackbars.html#\"> The Material Design Snackbar</a>\npublic class JFXSnackbar extends Group {\n\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-snackbar\";\n\n    private Pane snackbarContainer;\n    private final ChangeListener<? super Number> sizeListener = (o, oldVal, newVal) -> refreshPopup();\n    private final WeakChangeListener<? super Number> weakSizeListener = new WeakChangeListener<>(sizeListener);\n\n    private final AtomicBoolean processingQueue = new AtomicBoolean(false);\n    private final ConcurrentLinkedQueue<SnackbarEvent> eventQueue = new ConcurrentLinkedQueue<>();\n    private final ConcurrentHashMap.KeySetView<Object, Boolean> eventsSet = ConcurrentHashMap.newKeySet();\n\n    private final Pane content;\n    private PseudoClass activePseudoClass = null;\n    private PauseTransition pauseTransition;\n\n    /// This constructor assumes that you will eventually call the [#registerSnackbarContainer(Pane)] method before\n    /// calling the [#enqueue(SnackbarEvent)] method. Otherwise, how will the snackbar know where to show itself?\n    ///\n    ///\n    /// \"Snackbars provide brief messages about app processes at the bottom of the screen\"\n    /// (<a href=\"https://material.io/design/components/snackbars.html#\">Material Design Guidelines</a>).\n    ///\n    /// To show a snackbar you need to\n    ///\n    /// - Have a [Pane] (snackbarContainer) to show the snackbar on top of. Register it in\n    ///   [the JFXSnackbar constructor][#JFXSnackbar(Pane)] or using the [#registerSnackbarContainer(Pane)] method.\n    /// - Have or create a [JFXSnackbar].  - Having one snackbar where you pass all your\n    ///   [SnackbarEvents][JFXSnackbar.SnackbarEvent] will ensure that the [enqueuemethod][JFXSnackbar#enqueue(SnackbarEvent)] works as intended.\n    /// - Have something to show in the snackbar. A [JFXSnackbarLayout] is nice and pretty,\n    ///   but any arbitrary [Node] will do.\n    /// - Create a [SnackbarEvent][JFXSnackbar.SnackbarEvent] specifying the contents and the\n    ///   duration.\n    ///\n    /// Finally, with all those things prepared, show your snackbar using\n    /// [snackbar.enqueue(snackbarEvent);][JFXSnackbar#enqueue(SnackbarEvent)].\n    ///\n    public JFXSnackbar() {\n        this(null);\n    }\n\n    /// \"Snackbars provide brief messages about app processes at the bottom of the screen\"\n    /// (<a href=\"https://material.io/design/components/snackbars.html#\">Material Design Guidelines</a>).\n    ///\n    /// To show a snackbar you need to\n    ///\n    /// - Have a [Pane] (snackbarContainer) to show the snackbar on top of. Register it in\n    ///   [the JFXSnackbar constructor][#JFXSnackbar(Pane)] or using the [#registerSnackbarContainer(Pane)] method.\n    /// - Have or create a [JFXSnackbar].  - Having one snackbar where you pass all your\n    ///   [SnackbarEvents][JFXSnackbar.SnackbarEvent] will ensure that the [enqueuemethod][JFXSnackbar#enqueue(SnackbarEvent)] works as intended.\n    /// - Have something to show in the snackbar. A [JFXSnackbarLayout] is nice and pretty,\n    ///   but any arbitrary [Node] will do.\n    /// - Create a [SnackbarEvent][JFXSnackbar.SnackbarEvent] specifying the contents and the\n    ///   duration.\n    ///\n    /// Finally, with all those things prepared, show your snackbar using\n    /// [snackbar.enqueue(snackbarEvent);][JFXSnackbar#enqueue(SnackbarEvent)].\n    ///\n    /// @param snackbarContainer where the snackbar will appear. Using a single snackbar instead of many, will ensure that\n    ///                                                                                                                              the [#enqueue(SnackbarEvent)] method works correctly.\n    public JFXSnackbar(Pane snackbarContainer) {\n        initialize();\n        content = new StackPane();\n        content.getStyleClass().add(\"jfx-snackbar-content\");\n        //wrap the content in a group so that the content is managed inside its own container\n        //but the group is not managed in the snackbarContainer so it does not affect any layout calculations\n        getChildren().add(content);\n        setManaged(false);\n        setVisible(false);\n\n        // register the container before resizing it\n        registerSnackbarContainer(snackbarContainer);\n\n        // resize the popup if its layout has been changed\n        layoutBoundsProperty().addListener((o, oldVal, newVal) -> refreshPopup());\n\n        addEventHandler(SnackbarEvent.SNACKBAR, this::enqueue);\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    // Setters / Getters\n\n    public Pane getPopupContainer() {\n        return snackbarContainer;\n    }\n\n    public void setPrefWidth(double width) {\n        content.setPrefWidth(width);\n    }\n\n    public double getPrefWidth() {\n        return content.getPrefWidth();\n    }\n\n    // Public API\n\n    public void registerSnackbarContainer(Pane snackbarContainer) {\n        if (snackbarContainer != null) {\n            if (this.snackbarContainer != null) {\n                //since listeners are added the container should be properly registered/unregistered\n                throw new IllegalArgumentException(\"Snackbar Container already set\");\n            }\n            this.snackbarContainer = snackbarContainer;\n            this.snackbarContainer.getChildren().add(this);\n            this.snackbarContainer.heightProperty().addListener(weakSizeListener);\n            this.snackbarContainer.widthProperty().addListener(weakSizeListener);\n        }\n    }\n\n    public void unregisterSnackbarContainer(Pane snackbarContainer) {\n        if (snackbarContainer != null) {\n            if (this.snackbarContainer == null) {\n                throw new IllegalArgumentException(\"Snackbar Container not set\");\n            }\n            this.snackbarContainer.getChildren().remove(this);\n            this.snackbarContainer.heightProperty().removeListener(weakSizeListener);\n            this.snackbarContainer.widthProperty().removeListener(weakSizeListener);\n            this.snackbarContainer = null;\n        }\n    }\n\n    private void show(SnackbarEvent event) {\n        content.getChildren().setAll(event.getContent());\n        openAnimation = getTimeline(event.getTimeout());\n        if (event.getPseudoClass() != null) {\n            activePseudoClass = event.getPseudoClass();\n            content.pseudoClassStateChanged(activePseudoClass, true);\n        }\n        openAnimation.play();\n    }\n\n    private Timeline openAnimation = null;\n\n    private Timeline getTimeline(Duration timeout) {\n        Timeline animation;\n        animation = new Timeline(\n                new KeyFrame(\n                        Duration.ZERO,\n                        e -> {\n                            this.toBack();\n                            this.setVisible(false);\n                        },\n                        new KeyValue(this.translateYProperty(), this.getLayoutBounds().getHeight(), Motion.EASE),\n                        new KeyValue(this.opacityProperty(), 0, Motion.EASE)\n                ),\n                new KeyFrame(\n                        Duration.millis(10),\n                        e -> {\n                            this.toFront();\n                            this.setVisible(true);\n                        }\n                ),\n                new KeyFrame(Duration.millis(300),\n                        new KeyValue(this.opacityProperty(), 1, Motion.EASE),\n                        new KeyValue(this.translateYProperty(), 0, Motion.EASE)\n                )\n        );\n        animation.setCycleCount(1);\n        pauseTransition = Duration.INDEFINITE.equals(timeout) ? null : new PauseTransition(timeout);\n        if (pauseTransition != null) {\n            animation.setOnFinished(finish -> {\n                pauseTransition.setOnFinished(done -> {\n                    pauseTransition = null;\n                    eventsSet.remove(currentEvent);\n                    currentEvent = eventQueue.peek();\n                    close();\n                });\n                pauseTransition.play();\n            });\n        }\n        return animation;\n    }\n\n    public void close() {\n        if (openAnimation != null) {\n            openAnimation.stop();\n        }\n        if (this.isVisible()) {\n            Timeline closeAnimation = new Timeline(\n                    new KeyFrame(\n                            Duration.ZERO,\n                            e -> this.toFront(),\n                            new KeyValue(this.opacityProperty(), 1, Motion.EASE),\n                            new KeyValue(this.translateYProperty(), 0, Motion.EASE)\n                    ),\n                    new KeyFrame(\n                            Duration.millis(290),\n                            e -> this.setVisible(true)\n                    ),\n                    new KeyFrame(Duration.millis(300),\n                            e -> {\n                                this.toBack();\n                                this.setVisible(false);\n                            },\n                            new KeyValue(this.translateYProperty(), this.getLayoutBounds().getHeight(), Motion.EASE),\n                            new KeyValue(this.opacityProperty(), 0, Motion.EASE)\n                    )\n            );\n            closeAnimation.setCycleCount(1);\n            closeAnimation.setOnFinished(e -> {\n                resetPseudoClass();\n                processSnackbar();\n            });\n            closeAnimation.play();\n        }\n    }\n\n    private SnackbarEvent currentEvent = null;\n\n    public SnackbarEvent getCurrentEvent() {\n        return currentEvent;\n    }\n\n    /**\n     * Shows {@link SnackbarEvent SnackbarEvents} one by one. The next event will be shown after the current event's duration.\n     *\n     * @param event the {@link SnackbarEvent event} to put in the queue.\n     */\n    public void enqueue(SnackbarEvent event) {\n        synchronized (this) {\n            if (!eventsSet.contains(event)) {\n                eventsSet.add(event);\n                eventQueue.offer(event);\n            } else if (currentEvent == event && pauseTransition != null) {\n                pauseTransition.playFromStart();\n            }\n        }\n        if (processingQueue.compareAndSet(false, true)) {\n            Platform.runLater(() -> {\n                currentEvent = eventQueue.poll();\n                if (currentEvent != null) {\n                    show(currentEvent);\n                }\n            });\n        }\n    }\n\n    private void resetPseudoClass() {\n        if (activePseudoClass != null) {\n            content.pseudoClassStateChanged(activePseudoClass, false);\n            activePseudoClass = null;\n        }\n    }\n\n    private void processSnackbar() {\n        currentEvent = eventQueue.poll();\n        if (currentEvent != null) {\n            eventsSet.remove(currentEvent);\n            show(currentEvent);\n        } else {\n            //The enqueue method and this listener should be executed sequentially on the FX Thread so there\n            //should not be a race condition\n            processingQueue.getAndSet(false);\n        }\n    }\n\n    private void refreshPopup() {\n        if (snackbarContainer == null) {\n            return;\n        }\n        Bounds contentBound = this.getLayoutBounds();\n        double offsetX = Math.ceil(snackbarContainer.getWidth() / 2) - Math.ceil(contentBound.getWidth() / 2);\n        double offsetY = snackbarContainer.getHeight() - contentBound.getHeight();\n        this.setLayoutX(offsetX);\n        this.setLayoutY(offsetY);\n\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    // Event API\n    ///////////////////////////////////////////////////////////////////////////\n\n    /// Specifies _what_ and _how long_ to show a [JFXSnackbar].\n    ///\n    /// The _what_ can be any arbitrary [Node]; the [JFXSnackbarLayout] is a great choice.\n    ///\n    /// The _how long_ is specified in the form of a [javafx.util.Duration][javafx.util.Duration], not to be\n    /// confused with the [java.time.Duration].\n    public static class SnackbarEvent extends Event {\n\n        public static final EventType<SnackbarEvent> SNACKBAR = new EventType<>(Event.ANY, \"SNACKBAR\");\n\n        /// The amount of time the snackbar will show for, if not otherwise specified.\n        ///\n        /// It's 1.5 seconds.\n        public static Duration DEFAULT_DURATION = Duration.seconds(1.5);\n\n        private final Node content;\n        private final PseudoClass pseudoClass;\n        private final Duration timeout;\n\n        /// Creates a [SnackbarEvent] with the [default duration][#DEFAULT_DURATION] and no pseudoClass.\n        ///\n        /// @param content what you want shown in the snackbar; a [JFXSnackbarLayout] is a great choice.\n        public SnackbarEvent(Node content) {\n            this(content, DEFAULT_DURATION, null);\n        }\n\n        /// Creates a [SnackbarEvent] with the [default duration][#DEFAULT_DURATION]; you specify the contents and\n        /// pseudoClass.\n        ///\n        /// @param content what you want shown in the snackbar; a [JFXSnackbarLayout] is a great choice.\n        public SnackbarEvent(Node content, PseudoClass pseudoClass) {\n            this(content, DEFAULT_DURATION, pseudoClass);\n        }\n\n        /// Creates a SnackbarEvent with no pseudoClass; you specify the contents and duration.\n        /// pseudoClass.\n        ///\n        /// @param content what you want shown in the snackbar; a [JFXSnackbarLayout] is a great choice.\n        /// @param timeout the amount of time you want the snackbar to show for.\n        public SnackbarEvent(Node content, Duration timeout) {\n            this(content, timeout, null);\n        }\n\n        /// Creates a SnackbarEvent; you specify the contents, duration and pseudoClass.\n        ///\n        /// If you don't need so much customization, try one of the other constructors.\n        ///\n        /// @param content what you want shown in the snackbar; a [JFXSnackbarLayout] is a great choice.\n        /// @param timeout the amount of time you want the snackbar to show for.\n        public SnackbarEvent(Node content, Duration timeout, PseudoClass pseudoClass) {\n            super(SNACKBAR);\n            this.content = content;\n            this.pseudoClass = pseudoClass;\n            this.timeout = timeout;\n        }\n\n        public Node getContent() {\n            return content;\n        }\n\n        public PseudoClass getPseudoClass() {\n            return pseudoClass;\n        }\n\n        public Duration getTimeout() {\n            return timeout;\n        }\n\n        @Override\n        @SuppressWarnings(\"unchecked\")\n        public EventType<? extends SnackbarEvent> getEventType() {\n            return (EventType<? extends SnackbarEvent>) super.getEventType();\n        }\n\n        public boolean isPersistent() {\n            return Duration.INDEFINITE.equals(getTimeout());\n        }\n    }\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXSnackbarLayout.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Insets;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.StackPane;\n\n/// JFXSnackbarLayout default layout for snackbar content\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2018-11-16\npublic class JFXSnackbarLayout extends BorderPane {\n\n    private final Label toast;\n    private JFXButton action;\n    private final StackPane actionContainer;\n\n    public JFXSnackbarLayout(String message) {\n        this(message, null, null);\n    }\n\n    public JFXSnackbarLayout(String message, String actionText, EventHandler<ActionEvent> actionHandler) {\n        initialize();\n\n        toast = new Label();\n        toast.setMinWidth(Control.USE_PREF_SIZE);\n        toast.getStyleClass().add(\"jfx-snackbar-toast\");\n        toast.setWrapText(true);\n        toast.setText(message);\n        StackPane toastContainer = new StackPane(toast);\n        toastContainer.setPadding(new Insets(20));\n        actionContainer = new StackPane();\n        actionContainer.setPadding(new Insets(0, 10, 0, 0));\n\n        toast.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {\n            if (getPrefWidth() == -1) {\n                return getPrefWidth();\n            }\n            double actionWidth = actionContainer.isVisible() ? actionContainer.getWidth() : 0.0;\n            return prefWidthProperty().get() - actionWidth;\n        }, prefWidthProperty(), actionContainer.widthProperty(), actionContainer.visibleProperty()));\n\n        setLeft(toastContainer);\n        setRight(actionContainer);\n\n        if (actionText != null) {\n            action = new JFXButton();\n            action.setText(actionText);\n            action.setOnAction(actionHandler);\n            action.setMinWidth(Control.USE_PREF_SIZE);\n            action.setButtonType(JFXButton.ButtonType.FLAT);\n            action.getStyleClass().add(\"jfx-snackbar-action\");\n            // actions will be added upon showing the snackbar if needed\n            actionContainer.getChildren().add(action);\n\n            if (!actionText.isEmpty()) {\n                action.setVisible(true);\n                actionContainer.setVisible(true);\n                actionContainer.setManaged(true);\n                // to force updating the layout bounds\n                action.setText(\"\");\n                action.setText(actionText);\n                action.setOnAction(actionHandler);\n            } else {\n                actionContainer.setVisible(false);\n                actionContainer.setManaged(false);\n                action.setVisible(false);\n            }\n        }\n    }\n\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-snackbar-layout\";\n\n    public String getToast() {\n        return toast.getText();\n    }\n\n    public void setToast(String toast) {\n        this.toast.setText(toast);\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXSpinner.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXSpinnerSkin;\nimport javafx.css.CssMetaData;\nimport javafx.css.SimpleStyleableDoubleProperty;\nimport javafx.css.Styleable;\nimport javafx.css.StyleableDoubleProperty;\nimport javafx.css.converter.SizeConverter;\nimport javafx.scene.control.ProgressIndicator;\nimport javafx.scene.control.Skin;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\n\n/// JFXSpinner is the material design implementation of a loading spinner.\n///\n/// @author Bashir Elias & Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class JFXSpinner extends ProgressIndicator {\n\n    public static final double INDETERMINATE_PROGRESS = ProgressIndicator.INDETERMINATE_PROGRESS;\n\n    public JFXSpinner() {\n        this(INDETERMINATE_PROGRESS);\n    }\n\n    public JFXSpinner(double progress) {\n        super(progress);\n        init();\n    }\n\n    private void init() {\n        getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXSpinnerSkin(this);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Stylesheet Handling                                                     *\n     *                                                                         *\n     **************************************************************************/\n\n    /// Initialize the style class to 'jfx-spinner'.\n    ///\n    /// This is the selector class from which CSS can be used to style\n    /// this control.\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-spinner\";\n\n    private static final double DEFAULT_RADIUS = 16.0;\n\n    /**\n     * specifies the radius of the spinner node, by default it's set to `16.0`\n     */\n    private StyleableDoubleProperty radius;\n\n    public final StyleableDoubleProperty radiusProperty() {\n        if (this.radius == null) {\n            this.radius = new SimpleStyleableDoubleProperty(StyleableProperties.RADIUS,\n                    JFXSpinner.this,\n                    \"radius\",\n                    DEFAULT_RADIUS);\n        }\n        return this.radius;\n    }\n\n    public final double getRadius() {\n        return radius != null ? radius.get() : DEFAULT_RADIUS;\n    }\n\n    public final void setRadius(final double radius) {\n        this.radiusProperty().set(radius);\n    }\n\n    public double getStartingAngle() {\n        return 360 - ThreadLocalRandom.current().nextDouble() * 720;\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<JFXSpinner, Number> RADIUS =\n                new CssMetaData<>(\"-jfx-radius\",\n                        SizeConverter.getInstance(), DEFAULT_RADIUS) {\n                    @Override\n                    public boolean isSettable(JFXSpinner control) {\n                        return control.radius == null || !control.radius.isBound();\n                    }\n\n                    @Override\n                    public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) {\n                        return control.radiusProperty();\n                    }\n                };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables =\n                    new ArrayList<>(ProgressIndicator.getClassCssMetaData());\n            Collections.addAll(styleables, RADIUS);\n            CHILD_STYLEABLES = List.copyOf(styleables);\n        }\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXTextArea.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXTextAreaSkin;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.*;\nimport javafx.css.converter.BooleanConverter;\nimport javafx.css.converter.PaintConverter;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.TextArea;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.useJFXContextMenu;\n\n/**\n * JFXTextArea is the material design implementation of a text area.\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2016-03-09\n */\npublic class JFXTextArea extends TextArea {\n    /**\n     * Initialize the style class to 'jfx-text-field'.\n     * <p>\n     * This is the selector class from which CSS can be used to style\n     * this control.\n     */\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-text-area\";\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXTextArea() {\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXTextArea(String text) {\n        super(text);\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXTextAreaSkin(this);\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n        if (\"dalvik\".equalsIgnoreCase(System.getProperty(\"java.vm.name\"))) {\n            this.setStyle(\"-fx-skin: \\\"com.jfoenix.android.skins.JFXTextAreaSkinAndroid\\\";\");\n        }\n\n        useJFXContextMenu(this);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Properties                                                              *\n     *                                                                         *\n     **************************************************************************/\n\n    /**\n     * holds the current active validator on the text area in case of validation error\n     */\n    private ReadOnlyObjectWrapper<ValidatorBase> activeValidator = new ReadOnlyObjectWrapper<>();\n\n    public ValidatorBase getActiveValidator() {\n        return activeValidator == null ? null : activeValidator.get();\n    }\n\n    public ReadOnlyObjectProperty<ValidatorBase> activeValidatorProperty() {\n        return this.activeValidator.getReadOnlyProperty();\n    }\n\n    /**\n     * list of validators that will validate the text value upon calling\n     * {{@link #validate()}\n     */\n    private ObservableList<ValidatorBase> validators = FXCollections.observableArrayList();\n\n    public ObservableList<ValidatorBase> getValidators() {\n        return validators;\n    }\n\n    public void setValidators(ValidatorBase... validators) {\n        this.validators.addAll(validators);\n    }\n\n    /**\n     * validates the text value using the list of validators provided by the user\n     * {{@link #setValidators(ValidatorBase...)}\n     *\n     * @return true if the value is valid else false\n     */\n    public boolean validate() {\n        for (ValidatorBase validator : validators) {\n            if (validator.getSrcControl() == null) {\n                validator.setSrcControl(this);\n            }\n            validator.validate();\n            if (validator.getHasErrors()) {\n                activeValidator.set(validator);\n                return false;\n            }\n        }\n        activeValidator.set(null);\n        return true;\n    }\n\n    public void resetValidation() {\n        pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false);\n        activeValidator.set(null);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * styleable Properties                                                    *\n     *                                                                         *\n     **************************************************************************/\n\n    /**\n     * set true to show a float the prompt text when focusing the field\n     */\n    private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXTextArea.this, \"lableFloat\", false);\n\n    public final StyleableBooleanProperty labelFloatProperty() {\n        return this.labelFloat;\n    }\n\n    public final boolean isLabelFloat() {\n        return this.labelFloatProperty().get();\n    }\n\n    public final void setLabelFloat(final boolean labelFloat) {\n        this.labelFloatProperty().set(labelFloat);\n    }\n\n    /**\n     * default color used when the text area is unfocused\n     */\n    private StyleableObjectProperty<Paint> unFocusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNFOCUS_COLOR, JFXTextArea.this, \"unFocusColor\", Color.rgb(77, 77, 77));\n\n    public Paint getUnFocusColor() {\n        return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> unFocusColorProperty() {\n        return this.unFocusColor;\n    }\n\n    public void setUnFocusColor(Paint color) {\n        this.unFocusColor.set(color);\n    }\n\n    /**\n     * default color used when the text area is focused\n     */\n    private StyleableObjectProperty<Paint> focusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.FOCUS_COLOR, JFXTextArea.this, \"focusColor\", Color.valueOf(\"#4059A9\"));\n\n    public Paint getFocusColor() {\n        return focusColor == null ? Color.valueOf(\"#4059A9\") : focusColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> focusColorProperty() {\n        return this.focusColor;\n    }\n\n    public void setFocusColor(Paint color) {\n        this.focusColor.set(color);\n    }\n\n    /**\n     * disable animation on validation\n     */\n    private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXTextArea.this, \"disableAnimation\", false);\n\n    public final StyleableBooleanProperty disableAnimationProperty() {\n        return this.disableAnimation;\n    }\n\n    public final Boolean isDisableAnimation() {\n        return disableAnimation != null && this.disableAnimationProperty().get();\n    }\n\n    public final void setDisableAnimation(final Boolean disabled) {\n        this.disableAnimationProperty().set(disabled);\n    }\n\n    private final static class StyleableProperties {\n        private static final CssMetaData<JFXTextArea, Paint> UNFOCUS_COLOR = new CssMetaData<JFXTextArea, Paint>(\"-jfx-unfocus-color\", PaintConverter.getInstance(), Color.rgb(77, 77, 77)) {\n            @Override\n            public boolean isSettable(JFXTextArea control) {\n                return control.unFocusColor == null || !control.unFocusColor.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Paint> getStyleableProperty(JFXTextArea control) {\n                return control.unFocusColorProperty();\n            }\n        };\n        private static final CssMetaData<JFXTextArea, Paint> FOCUS_COLOR = new CssMetaData<JFXTextArea, Paint>(\"-jfx-focus-color\", PaintConverter.getInstance(), Color.valueOf(\"#4059A9\")) {\n            @Override\n            public boolean isSettable(JFXTextArea control) {\n                return control.focusColor == null || !control.focusColor.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Paint> getStyleableProperty(JFXTextArea control) {\n                return control.focusColorProperty();\n            }\n        };\n        private static final CssMetaData<JFXTextArea, Boolean> LABEL_FLOAT = new CssMetaData<JFXTextArea, Boolean>(\"-jfx-label-float\", BooleanConverter.getInstance(), false) {\n            @Override\n            public boolean isSettable(JFXTextArea control) {\n                return control.labelFloat == null || !control.labelFloat.isBound();\n            }\n\n            @Override\n            public StyleableBooleanProperty getStyleableProperty(JFXTextArea control) {\n                return control.labelFloatProperty();\n            }\n        };\n\n        private static final CssMetaData<JFXTextArea, Boolean> DISABLE_ANIMATION = new CssMetaData<JFXTextArea, Boolean>(\"-jfx-disable-animation\", BooleanConverter.getInstance(), false) {\n            @Override\n            public boolean isSettable(JFXTextArea control) {\n                return control.disableAnimation == null || !control.disableAnimation.isBound();\n            }\n\n            @Override\n            public StyleableBooleanProperty getStyleableProperty(JFXTextArea control) {\n                return control.disableAnimationProperty();\n            }\n        };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());\n            Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION);\n            CHILD_STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n    }\n\n    // inherit the styleable properties from parent\n    private List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        if (STYLEABLES == null) {\n            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());\n            styleables.addAll(getClassCssMetaData());\n            styleables.addAll(TextArea.getClassCssMetaData());\n            STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n        return STYLEABLES;\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXTextField.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXTextFieldSkin;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.*;\nimport javafx.css.converter.BooleanConverter;\nimport javafx.css.converter.PaintConverter;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.TextField;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.useJFXContextMenu;\n\n/**\n * JFXTextField is the material design implementation of a text Field.\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2016-03-09\n */\npublic class JFXTextField extends TextField {\n    /**\n     * Initialize the style class to 'jfx-text-field'.\n     * <p>\n     * This is the selector class from which CSS can be used to style\n     * this control.\n     */\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-text-field\";\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXTextField() {\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXTextField(String text) {\n        super(text);\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXTextFieldSkin(this);\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n        if (\"dalvik\".equalsIgnoreCase(System.getProperty(\"java.vm.name\"))) {\n            this.setStyle(\"-fx-skin: \\\"com.jfoenix.android.skins.JFXTextFieldSkinAndroid\\\";\");\n        }\n\n        useJFXContextMenu(this);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Properties                                                              *\n     *                                                                         *\n     **************************************************************************/\n\n    /**\n     * holds the current active validator on the text field in case of validation error\n     */\n    private ReadOnlyObjectWrapper<ValidatorBase> activeValidator = new ReadOnlyObjectWrapper<>();\n\n    public ValidatorBase getActiveValidator() {\n        return activeValidator == null ? null : activeValidator.get();\n    }\n\n    public ReadOnlyObjectProperty<ValidatorBase> activeValidatorProperty() {\n        return this.activeValidator.getReadOnlyProperty();\n    }\n\n    /**\n     * list of validators that will validate the text value upon calling\n     * {{@link #validate()}\n     */\n    private ObservableList<ValidatorBase> validators = FXCollections.observableArrayList();\n\n    public ObservableList<ValidatorBase> getValidators() {\n        return validators;\n    }\n\n    public void setValidators(ValidatorBase... validators) {\n        this.validators.addAll(validators);\n    }\n\n    /**\n     * validates the text value using the list of validators provided by the user\n     * {{@link #setValidators(ValidatorBase...)}\n     *\n     * @return true if the value is valid else false\n     */\n    public boolean validate() {\n        for (ValidatorBase validator : validators) {\n            if (validator.getSrcControl() == null) {\n                validator.setSrcControl(this);\n            }\n            validator.validate();\n            if (validator.getHasErrors()) {\n                activeValidator.set(validator);\n                return false;\n            }\n        }\n        activeValidator.set(null);\n        return true;\n    }\n\n    public void resetValidation() {\n        pseudoClassStateChanged(ValidatorBase.PSEUDO_CLASS_ERROR, false);\n        activeValidator.set(null);\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * styleable Properties                                                    *\n     *                                                                         *\n     **************************************************************************/\n\n    /**\n     * set true to show a float the prompt text when focusing the field\n     */\n    private StyleableBooleanProperty labelFloat = new SimpleStyleableBooleanProperty(StyleableProperties.LABEL_FLOAT, JFXTextField.this, \"lableFloat\", false);\n\n    public final StyleableBooleanProperty labelFloatProperty() {\n        return this.labelFloat;\n    }\n\n    public final boolean isLabelFloat() {\n        return this.labelFloatProperty().get();\n    }\n\n    public final void setLabelFloat(final boolean labelFloat) {\n        this.labelFloatProperty().set(labelFloat);\n    }\n\n    /**\n     * default color used when the field is unfocused\n     */\n    private StyleableObjectProperty<Paint> unFocusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNFOCUS_COLOR, JFXTextField.this, \"unFocusColor\", Color.rgb(77, 77, 77));\n\n    public Paint getUnFocusColor() {\n        return unFocusColor == null ? Color.rgb(77, 77, 77) : unFocusColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> unFocusColorProperty() {\n        return this.unFocusColor;\n    }\n\n    public void setUnFocusColor(Paint color) {\n        this.unFocusColor.set(color);\n    }\n\n    /**\n     * default color used when the field is focused\n     */\n    private StyleableObjectProperty<Paint> focusColor = new SimpleStyleableObjectProperty<>(StyleableProperties.FOCUS_COLOR, JFXTextField.this, \"focusColor\", Color.valueOf(\"#4059A9\"));\n\n    public Paint getFocusColor() {\n        return focusColor == null ? Color.valueOf(\"#4059A9\") : focusColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> focusColorProperty() {\n        return this.focusColor;\n    }\n\n    public void setFocusColor(Paint color) {\n        this.focusColor.set(color);\n    }\n\n    /**\n     * disable animation on validation\n     */\n    private StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, JFXTextField.this, \"disableAnimation\", false);\n\n    public final StyleableBooleanProperty disableAnimationProperty() {\n        return this.disableAnimation;\n    }\n\n    public final Boolean isDisableAnimation() {\n        return disableAnimation != null && this.disableAnimationProperty().get();\n    }\n\n    public final void setDisableAnimation(final Boolean disabled) {\n        this.disableAnimationProperty().set(disabled);\n    }\n\n    private final static class StyleableProperties {\n        private static final CssMetaData<JFXTextField, Paint> UNFOCUS_COLOR = new CssMetaData<JFXTextField, Paint>(\"-jfx-unfocus-color\", PaintConverter.getInstance(), Color.valueOf(\"#A6A6A6\")) {\n            @Override\n            public boolean isSettable(JFXTextField control) {\n                return control.unFocusColor == null || !control.unFocusColor.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Paint> getStyleableProperty(JFXTextField control) {\n                return control.unFocusColorProperty();\n            }\n        };\n        private static final CssMetaData<JFXTextField, Paint> FOCUS_COLOR = new CssMetaData<JFXTextField, Paint>(\"-jfx-focus-color\", PaintConverter.getInstance(), Color.valueOf(\"#3f51b5\")) {\n            @Override\n            public boolean isSettable(JFXTextField control) {\n                return control.focusColor == null || !control.focusColor.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Paint> getStyleableProperty(JFXTextField control) {\n                return control.focusColorProperty();\n            }\n        };\n        private static final CssMetaData<JFXTextField, Boolean> LABEL_FLOAT = new CssMetaData<JFXTextField, Boolean>(\"-jfx-label-float\", BooleanConverter.getInstance(), false) {\n            @Override\n            public boolean isSettable(JFXTextField control) {\n                return control.labelFloat == null || !control.labelFloat.isBound();\n            }\n\n            @Override\n            public StyleableBooleanProperty getStyleableProperty(JFXTextField control) {\n                return control.labelFloatProperty();\n            }\n        };\n\n        private static final CssMetaData<JFXTextField, Boolean> DISABLE_ANIMATION = new CssMetaData<JFXTextField, Boolean>(\"-jfx-disable-animation\", BooleanConverter.getInstance(), false) {\n            @Override\n            public boolean isSettable(JFXTextField control) {\n                return control.disableAnimation == null || !control.disableAnimation.isBound();\n            }\n\n            @Override\n            public StyleableBooleanProperty getStyleableProperty(JFXTextField control) {\n                return control.disableAnimationProperty();\n            }\n        };\n\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());\n            Collections.addAll(styleables, UNFOCUS_COLOR, FOCUS_COLOR, LABEL_FLOAT, DISABLE_ANIMATION);\n            CHILD_STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n    }\n\n    // inherit the styleable properties from parent\n    private List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        if (STYLEABLES == null) {\n            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());\n            styleables.addAll(getClassCssMetaData());\n            styleables.addAll(TextField.getClassCssMetaData());\n            STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n        return STYLEABLES;\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.skins.JFXToggleButtonSkin;\nimport javafx.css.*;\nimport javafx.css.converter.BooleanConverter;\nimport javafx.css.converter.PaintConverter;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.ToggleButton;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * JFXToggleButton is the material design implementation of a toggle button.\n * important CSS Selectors:\n * <p>\n * .jfx-toggle-button{\n * -fx-toggle-color: color-value;\n * -fx-untoggle-color: color-value;\n * -fx-toggle-line-color: color-value;\n * -fx-untoggle-line-color: color-value;\n * }\n * <p>\n * To change the rippler color when toggled:\n * <p>\n * .jfx-toggle-button .jfx-rippler{\n * -fx-rippler-fill: color-value;\n * }\n * <p>\n * .jfx-toggle-button:selected .jfx-rippler{\n * -fx-rippler-fill: color-value;\n * }\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2016-03-09\n */\npublic class JFXToggleButton extends ToggleButton {\n\n    /**\n     * {@inheritDoc}\n     */\n    public JFXToggleButton() {\n        initialize();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JFXToggleButtonSkin(this);\n    }\n\n    private void initialize() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n        // it's up for the user to add this behavior\n//        toggleColor.addListener((o, oldVal, newVal) -> {\n//            // update line color in case not set by the user\n//            if(newVal instanceof Color)\n//                toggleLineColor.set(((Color)newVal).desaturate().desaturate().brighter());\n//        });\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * styleable Properties                                                    *\n     *                                                                         *\n     **************************************************************************/\n\n    /**\n     * Initialize the style class to 'jfx-toggle-button'.\n     * <p>\n     * This is the selector class from which CSS can be used to style\n     * this control.\n     */\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-toggle-button\";\n\n    /**\n     * default color used when the button is toggled\n     */\n    private final StyleableObjectProperty<Paint> toggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.TOGGLE_COLOR,\n            JFXToggleButton.this,\n            \"toggleColor\",\n            Color.valueOf(\n                    \"#009688\"));\n\n    public Paint getToggleColor() {\n        return toggleColor == null ? Color.valueOf(\"#009688\") : toggleColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> toggleColorProperty() {\n        return this.toggleColor;\n    }\n\n    public void setToggleColor(Paint color) {\n        this.toggleColor.set(color);\n    }\n\n    /**\n     * default color used when the button is not toggled\n     */\n    private StyleableObjectProperty<Paint> untoggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNTOGGLE_COLOR,\n            JFXToggleButton.this,\n            \"unToggleColor\",\n            Color.valueOf(\n                    \"#FAFAFA\"));\n\n    public Paint getUnToggleColor() {\n        return untoggleColor == null ? Color.valueOf(\"#FAFAFA\") : untoggleColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> unToggleColorProperty() {\n        return this.untoggleColor;\n    }\n\n    public void setUnToggleColor(Paint color) {\n        this.untoggleColor.set(color);\n    }\n\n    /**\n     * default line color used when the button is toggled\n     */\n    private final StyleableObjectProperty<Paint> toggleLineColor = new SimpleStyleableObjectProperty<>(\n            StyleableProperties.TOGGLE_LINE_COLOR,\n            JFXToggleButton.this,\n            \"toggleLineColor\",\n            Color.valueOf(\"#77C2BB\"));\n\n    public Paint getToggleLineColor() {\n        return toggleLineColor == null ? Color.valueOf(\"#77C2BB\") : toggleLineColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> toggleLineColorProperty() {\n        return this.toggleLineColor;\n    }\n\n    public void setToggleLineColor(Paint color) {\n        this.toggleLineColor.set(color);\n    }\n\n    /**\n     * default line color used when the button is not toggled\n     */\n    private final StyleableObjectProperty<Paint> untoggleLineColor = new SimpleStyleableObjectProperty<>(\n            StyleableProperties.UNTOGGLE_LINE_COLOR,\n            JFXToggleButton.this,\n            \"unToggleLineColor\",\n            Color.valueOf(\"#999999\"));\n\n    public Paint getUnToggleLineColor() {\n        return untoggleLineColor == null ? Color.valueOf(\"#999999\") : untoggleLineColor.get();\n    }\n\n    public StyleableObjectProperty<Paint> unToggleLineColorProperty() {\n        return this.untoggleLineColor;\n    }\n\n    public void setUnToggleLineColor(Paint color) {\n        this.untoggleLineColor.set(color);\n    }\n\n    /**\n     * Default size of the toggle button.\n     */\n    private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty(\n            StyleableProperties.SIZE,\n            JFXToggleButton.this,\n            \"size\",\n            10.0);\n\n    public double getSize() {\n        return size.get();\n    }\n\n    public StyleableDoubleProperty sizeProperty() {\n        return this.size;\n    }\n\n    public void setSize(double size) {\n        this.size.set(size);\n    }\n\n    /**\n     * Disable the visual indicator for focus\n     */\n    private final StyleableBooleanProperty disableVisualFocus = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_VISUAL_FOCUS,\n            JFXToggleButton.this,\n            \"disableVisualFocus\",\n            false);\n\n    public final StyleableBooleanProperty disableVisualFocusProperty() {\n        return this.disableVisualFocus;\n    }\n\n    public final Boolean isDisableVisualFocus() {\n        return disableVisualFocus != null && this.disableVisualFocusProperty().get();\n    }\n\n    public final void setDisableVisualFocus(final Boolean disabled) {\n        this.disableVisualFocusProperty().set(disabled);\n    }\n\n\n    /**\n     * disable animation on button action\n     */\n    private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION,\n            JFXToggleButton.this,\n            \"disableAnimation\",\n            !AnimationUtils.isAnimationEnabled());\n\n    public final StyleableBooleanProperty disableAnimationProperty() {\n        return this.disableAnimation;\n    }\n\n    public final Boolean isDisableAnimation() {\n        return disableAnimation != null && this.disableAnimationProperty().get();\n    }\n\n    public final void setDisableAnimation(final Boolean disabled) {\n        this.disableAnimationProperty().set(disabled);\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<JFXToggleButton, Paint> TOGGLE_COLOR =\n                new CssMetaData<>(\"-jfx-toggle-color\",\n                        PaintConverter.getInstance(), Color.valueOf(\"#009688\")) {\n                    @Override\n                    public boolean isSettable(JFXToggleButton control) {\n                        return control.toggleColor == null || !control.toggleColor.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Paint> getStyleableProperty(JFXToggleButton control) {\n                        return control.toggleColorProperty();\n                    }\n                };\n\n        private static final CssMetaData<JFXToggleButton, Paint> UNTOGGLE_COLOR =\n                new CssMetaData<>(\"-jfx-untoggle-color\",\n                        PaintConverter.getInstance(), Color.valueOf(\"#FAFAFA\")) {\n                    @Override\n                    public boolean isSettable(JFXToggleButton control) {\n                        return control.untoggleColor == null || !control.untoggleColor.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Paint> getStyleableProperty(JFXToggleButton control) {\n                        return control.unToggleColorProperty();\n                    }\n                };\n\n        private static final CssMetaData<JFXToggleButton, Paint> TOGGLE_LINE_COLOR =\n                new CssMetaData<>(\"-jfx-toggle-line-color\",\n                        PaintConverter.getInstance(), Color.valueOf(\"#77C2BB\")) {\n                    @Override\n                    public boolean isSettable(JFXToggleButton control) {\n                        return control.toggleLineColor == null || !control.toggleLineColor.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Paint> getStyleableProperty(JFXToggleButton control) {\n                        return control.toggleLineColorProperty();\n                    }\n                };\n\n        private static final CssMetaData<JFXToggleButton, Paint> UNTOGGLE_LINE_COLOR =\n                new CssMetaData<>(\"-jfx-untoggle-line-color\",\n                        PaintConverter.getInstance(), Color.valueOf(\"#999999\")) {\n                    @Override\n                    public boolean isSettable(JFXToggleButton control) {\n                        return control.untoggleLineColor == null || !control.untoggleLineColor.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Paint> getStyleableProperty(JFXToggleButton control) {\n                        return control.unToggleLineColorProperty();\n                    }\n                };\n\n        private static final CssMetaData<JFXToggleButton, Number> SIZE =\n                new CssMetaData<>(\"-jfx-size\",\n                        StyleConverter.getSizeConverter(), 10.0) {\n                    @Override\n                    public boolean isSettable(JFXToggleButton control) {\n                        return !control.size.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Number> getStyleableProperty(JFXToggleButton control) {\n                        return control.sizeProperty();\n                    }\n                };\n        private static final CssMetaData<JFXToggleButton, Boolean> DISABLE_VISUAL_FOCUS =\n                new CssMetaData<>(\"-jfx-disable-visual-focus\",\n                        BooleanConverter.getInstance(), false) {\n                    @Override\n                    public boolean isSettable(JFXToggleButton control) {\n                        return control.disableVisualFocus == null || !control.disableVisualFocus.isBound();\n                    }\n\n                    @Override\n                    public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) {\n                        return control.disableVisualFocusProperty();\n                    }\n                };\n\n        private static final CssMetaData<JFXToggleButton, Boolean> DISABLE_ANIMATION =\n                new CssMetaData<>(\"-jfx-disable-animation\",\n                        BooleanConverter.getInstance(), false) {\n                    @Override\n                    public boolean isSettable(JFXToggleButton control) {\n                        return control.disableAnimation == null || !control.disableAnimation.isBound();\n                    }\n\n                    @Override\n                    public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) {\n                        return control.disableAnimationProperty();\n                    }\n                };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables =\n                    new ArrayList<>(ToggleButton.getClassCssMetaData());\n            Collections.addAll(styleables,\n                    SIZE,\n                    TOGGLE_COLOR,\n                    UNTOGGLE_COLOR,\n                    TOGGLE_LINE_COLOR,\n                    UNTOGGLE_LINE_COLOR,\n                    DISABLE_VISUAL_FOCUS,\n                    DISABLE_ANIMATION\n            );\n            CHILD_STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.CHILD_STYLEABLES;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXTreeCell.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport com.jfoenix.utils.JFXNodeUtils;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.layout.*;\nimport javafx.scene.paint.Color;\n\nimport java.lang.ref.WeakReference;\n\n/// JFXTreeCell is simple material design implementation of a tree cell.\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2017-02-15\npublic class JFXTreeCell<T> extends TreeCell<T> {\n\n    protected JFXRippler cellRippler = new JFXRippler(this) {\n        @Override\n        protected Node getMask() {\n            Region clip = new Region();\n            JFXNodeUtils.updateBackground(JFXTreeCell.this.getBackground(), clip);\n            double width = control.getLayoutBounds().getWidth();\n            double height = control.getLayoutBounds().getHeight();\n            clip.resize(width, height);\n            return clip;\n        }\n\n        @Override\n        protected void positionControl(Node control) {\n            // do nothing\n        }\n    };\n    private HBox hbox;\n    private final StackPane selectedPane = new StackPane();\n\n    private final InvalidationListener treeItemGraphicInvalidationListener = observable -> updateDisplay(getItem(),\n            isEmpty());\n    private final WeakInvalidationListener weakTreeItemGraphicListener = new WeakInvalidationListener(\n            treeItemGraphicInvalidationListener);\n\n    private WeakReference<TreeItem<T>> treeItemRef;\n\n    public JFXTreeCell() {\n        selectedPane.getStyleClass().add(\"selection-bar\");\n        selectedPane.setBackground(new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY)));\n        selectedPane.setPrefWidth(3);\n        selectedPane.setMouseTransparent(true);\n        selectedProperty().addListener((o, oldVal, newVal) -> selectedPane.setVisible(newVal ? true : false));\n\n        final InvalidationListener treeItemInvalidationListener = observable -> {\n            TreeItem<T> oldTreeItem = treeItemRef == null ? null : treeItemRef.get();\n            if (oldTreeItem != null) {\n                oldTreeItem.graphicProperty().removeListener(weakTreeItemGraphicListener);\n            }\n\n            TreeItem<T> newTreeItem = getTreeItem();\n            if (newTreeItem != null) {\n                newTreeItem.graphicProperty().addListener(weakTreeItemGraphicListener);\n                treeItemRef = new WeakReference<>(newTreeItem);\n            }\n        };\n        final WeakInvalidationListener weakTreeItemListener = new WeakInvalidationListener(treeItemInvalidationListener);\n        treeItemProperty().addListener(weakTreeItemListener);\n        if (getTreeItem() != null) {\n            getTreeItem().graphicProperty().addListener(weakTreeItemGraphicListener);\n        }\n    }\n\n    @Override\n    protected void layoutChildren() {\n        super.layoutChildren();\n        if (!getChildren().contains(selectedPane)) {\n            getChildren().add(0, cellRippler);\n            cellRippler.rippler.clear();\n            getChildren().add(0, selectedPane);\n        }\n        cellRippler.resizeRelocate(0, 0, getWidth(), getHeight());\n        cellRippler.releaseRipple();\n        selectedPane.resizeRelocate(0, 0, selectedPane.prefWidth(-1), getHeight());\n        selectedPane.setVisible(isSelected());\n    }\n\n    private void updateDisplay(T item, boolean empty) {\n        if (item == null || empty) {\n            hbox = null;\n            setText(null);\n            setGraphic(null);\n        } else {\n            TreeItem<T> treeItem = getTreeItem();\n            if (treeItem != null && treeItem.getGraphic() != null) {\n                if (item instanceof Node) {\n                    setText(null);\n                    if (hbox == null) {\n                        hbox = new HBox(3);\n                    }\n                    hbox.getChildren().setAll(treeItem.getGraphic(), (Node) item);\n                    setGraphic(hbox);\n                } else {\n                    hbox = null;\n                    setText(item.toString());\n                    setGraphic(treeItem.getGraphic());\n                }\n            } else {\n                hbox = null;\n                if (item instanceof Node) {\n                    setText(null);\n                    setGraphic((Node) item);\n                } else {\n                    setText(item.toString());\n                    setGraphic(null);\n                }\n            }\n        }\n    }\n\n    @Override\n    protected void updateItem(T item, boolean empty) {\n        super.updateItem(item, empty);\n        updateDisplay(item, empty);\n        setMouseTransparent(item == null || empty);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/JFXTreeView.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls;\n\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\n\n/// JFXTreeView is the material design implementation of a TreeView\n/// with expand/collapse animation and selection indicator.\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2017-02-15\npublic class JFXTreeView<T> extends TreeView<T> {\n\n    private static final String DEFAULT_STYLE_CLASS = \"jfx-tree-view\";\n\n    public JFXTreeView() {\n        init();\n    }\n\n    public JFXTreeView(TreeItem<T> root) {\n        super(root);\n        init();\n    }\n\n    private void init() {\n        this.setCellFactory((view) -> new JFXTreeCell<>());\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/behavior/JFXGenericPickerBehavior.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls.behavior;\n\nimport com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior;\nimport javafx.scene.control.ComboBoxBase;\nimport javafx.scene.control.PopupControl;\n\n/**\n * @author Shadi Shaheen\n * @version 2.0\n * @since 2017-10-05\n */\npublic class JFXGenericPickerBehavior<T> extends ComboBoxBaseBehavior<T> {\n\n    public JFXGenericPickerBehavior(ComboBoxBase<T> var1) {\n        super(var1);\n    }\n\n    public void onAutoHide(PopupControl var1) {\n        if (!var1.isShowing() && this.getNode().isShowing()) {\n            this.getNode().hide();\n        }\n        if (!this.getNode().isShowing()) {\n            super.onAutoHide(var1);\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/datamodels/treetable/RecursiveTreeObject.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls.datamodels.treetable;\n\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.TreeTableColumn;\n\n/// data model that is used in JFXTreeTableView, it's used to implement\n/// the grouping feature.\n///\n/// **Note:** the data object used in JFXTreeTableView **must** extends this class\n///\n/// @param <T> is the concrete object of the Tree table\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class RecursiveTreeObject<T> {\n\n    /// grouped children objects\n    private ObservableList<T> children = FXCollections.observableArrayList();\n\n    public ObservableList<T> getChildren() {\n        return children;\n    }\n\n    public void setChildren(ObservableList<T> children) {\n        this.children = children;\n    }\n\n    /// Whether or not the object is grouped by a specified tree table column\n    ObjectProperty<TreeTableColumn<T, ?>> groupedColumn = new SimpleObjectProperty<>();\n\n    public final ObjectProperty<TreeTableColumn<T, ?>> groupedColumnProperty() {\n        return this.groupedColumn;\n    }\n\n    public final TreeTableColumn<T, ?> getGroupedColumn() {\n        return this.groupedColumnProperty().get();\n    }\n\n    public final void setGroupedColumn(final TreeTableColumn<T, ?> groupedColumn) {\n        this.groupedColumnProperty().set(groupedColumn);\n    }\n\n    /// the value that must be shown when grouped\n    ObjectProperty<Object> groupedValue = new SimpleObjectProperty<>();\n\n    public final ObjectProperty<Object> groupedValueProperty() {\n        return this.groupedValue;\n    }\n\n    public final Object getGroupedValue() {\n        return this.groupedValueProperty().get();\n    }\n\n    public final void setGroupedValue(final Object groupedValue) {\n        this.groupedValueProperty().set(groupedValue);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/controls/events/JFXDialogEvent.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.controls.events;\n\nimport javafx.event.Event;\nimport javafx.event.EventType;\n\nimport java.io.Serial;\n\n/// JFXDialog events, used exclusively by the following methods:\n///\n///   - [com.jfoenix.controls.JFXDialog#show()]\n///   - [com.jfoenix.controls.JFXDialog#close()]\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class JFXDialogEvent extends Event {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /// This event occurs when a JFXDialog is closed, no longer visible to the user\n    /// ( after the exit animation ends )\n    public static final EventType<JFXDialogEvent> CLOSED = new EventType<>(Event.ANY, \"JFX_DIALOG_CLOSED\");\n\n    /// This event occurs when a JFXDialog is opened, visible to the user\n    /// ( after the entrance animation ends )\n    public static final EventType<JFXDialogEvent> OPENED = new EventType<>(Event.ANY, \"JFX_DIALOG_OPENED\");\n\n    /// Construct a new JFXDialog `Event` with the specified event type\n    ///\n    /// @param eventType the event type\n    public JFXDialogEvent(EventType<? extends Event> eventType) {\n        super(eventType);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/effects/JFXDepthManager.java",
    "content": "/*\n * Copyright (c) 2016 JFoenix\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so,\n * subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\npackage com.jfoenix.effects;\n\nimport javafx.scene.Node;\nimport javafx.scene.effect.BlurType;\nimport javafx.scene.effect.DropShadow;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\n\n/**\n * it will create a shadow effect for a given node and a specified depth level.\n * depth levels are {0,1,2,3,4,5}\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2016-03-09\n */\npublic final class JFXDepthManager {\n    private JFXDepthManager() {\n        throw new AssertionError();\n    }\n\n    private static final DropShadow[] depth = new DropShadow[] {\n            new DropShadow(BlurType.GAUSSIAN, Color.TRANSPARENT, 0, 0, 0, 0),\n            new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.12), 4, 0, 0, 0),\n            new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.16), 8, 0, 0, 4),\n            new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.20), 12, 0, 0, 4),\n            new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.24), 16, 0, 0, 4),\n            new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.28), 20, 0, 0, 8)\n    };\n\n    /**\n     * this method is used to add shadow effect to the node,\n     * however the shadow is not real ( gets affected with node transformations)\n     * <p>\n     * use {@link #createMaterialNode(Node, int)} instead to generate a real shadow\n     */\n    public static void setDepth(Node control, int level) {\n        level = level < 0 ? 0 : level;\n        level = level > 5 ? 5 : level;\n        control.setEffect(new DropShadow(BlurType.GAUSSIAN,\n                depth[level].getColor(),\n                depth[level].getRadius(),\n                depth[level].getSpread(),\n                depth[level].getOffsetX(),\n                depth[level].getOffsetY()));\n    }\n\n    public static int getLevels() {\n        return depth.length;\n    }\n\n    public static DropShadow getShadowAt(int level) {\n        return depth[level];\n    }\n\n    /**\n     * this method will generate a new container node that prevent\n     * control transformation to be applied to the shadow effect\n     * (which makes it looks as a real shadow)\n     */\n    public static Node createMaterialNode(Node control, int level) {\n        Node container = new Pane(control) {\n            @Override\n            protected double computeMaxWidth(double height) {\n                return computePrefWidth(height);\n            }\n\n            @Override\n            protected double computeMaxHeight(double width) {\n                return computePrefHeight(width);\n            }\n\n            @Override\n            protected double computePrefWidth(double height) {\n                return control.prefWidth(height);\n            }\n\n            @Override\n            protected double computePrefHeight(double width) {\n                return control.prefHeight(width);\n            }\n        };\n        container.getStyleClass().add(\"depth-container\");\n        container.setPickOnBounds(false);\n        level = level < 0 ? 0 : level;\n        level = level > 5 ? 5 : level;\n        container.setEffect(new DropShadow(BlurType.GAUSSIAN,\n                depth[level].getColor(),\n                depth[level].getRadius(),\n                depth[level].getSpread(),\n                depth[level].getOffsetX(),\n                depth[level].getOffsetY()));\n        return container;\n    }\n\n    public static void pop(Node control) {\n        control.setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5, 0.05, 0, 1));\n    }\n\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXButtonSkin.java",
    "content": "//\n// Source code recreated from a .class file by IntelliJ IDEA\n// (powered by Fernflower decompiler)\n//\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.adapters.skins.ButtonSkin;\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXRippler;\nimport com.jfoenix.effects.JFXDepthManager;\nimport com.jfoenix.transitions.CachedTransition;\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.animation.Transition;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ReadOnlyBooleanProperty;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.effect.DropShadow;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundFill;\nimport javafx.scene.layout.CornerRadii;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Shape;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic class JFXButtonSkin extends ButtonSkin {\n    private final StackPane buttonContainer = new StackPane();\n    private final JFXRippler buttonRippler = new JFXRippler(new StackPane()) {\n        protected Node getMask() {\n            StackPane mask = new StackPane();\n            mask.shapeProperty().bind(JFXButtonSkin.this.buttonContainer.shapeProperty());\n            mask.backgroundProperty().bind(Bindings.createObjectBinding(() -> new Background(\n                    new BackgroundFill(Color.WHITE,\n                            JFXButtonSkin.this.buttonContainer.backgroundProperty().get() != null && !JFXButtonSkin.this.buttonContainer.getBackground().getFills().isEmpty()\n                                    ? JFXButtonSkin.this.buttonContainer.getBackground().getFills().get(0).getRadii()\n                                    : JFXButtonSkin.this.defaultRadii, JFXButtonSkin.this.buttonContainer.backgroundProperty().get() != null && !JFXButtonSkin.this.buttonContainer.getBackground().getFills().isEmpty() ? JFXButtonSkin.this.buttonContainer.getBackground().getFills().get(0).getInsets() : Insets.EMPTY)),\n                    JFXButtonSkin.this.buttonContainer.backgroundProperty()));\n            mask.resize(JFXButtonSkin.this.buttonContainer.getWidth() - JFXButtonSkin.this.buttonContainer.snappedRightInset() - JFXButtonSkin.this.buttonContainer.snappedLeftInset(), JFXButtonSkin.this.buttonContainer.getHeight() - JFXButtonSkin.this.buttonContainer.snappedBottomInset() - JFXButtonSkin.this.buttonContainer.snappedTopInset());\n            return mask;\n        }\n\n        private void initListeners() {\n            this.ripplerPane.setOnMousePressed((event) -> {\n                if (JFXButtonSkin.this.releaseManualRippler != null) {\n                    JFXButtonSkin.this.releaseManualRippler.run();\n                }\n\n                JFXButtonSkin.this.releaseManualRippler = null;\n                this.createRipple(event.getX(), event.getY());\n            });\n        }\n    };\n    private Transition clickedAnimation;\n    private final CornerRadii defaultRadii = new CornerRadii(3.0);\n    private boolean invalid = true;\n    private Runnable releaseManualRippler = null;\n\n    public JFXButtonSkin(JFXButton button) {\n        super(button);\n        this.getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> {\n            if (newVal) {\n                this.releaseManualRippler = this.buttonRippler.createManualRipple();\n                if (this.clickedAnimation != null) {\n                    this.clickedAnimation.setRate(1.0);\n                    this.clickedAnimation.play();\n                }\n            } else {\n                if (this.releaseManualRippler != null) {\n                    this.releaseManualRippler.run();\n                }\n\n                if (this.clickedAnimation != null) {\n                    this.clickedAnimation.setRate(-1.0);\n                    this.clickedAnimation.play();\n                }\n            }\n\n        });\n        this.buttonContainer.getChildren().add(this.buttonRippler);\n        button.buttonTypeProperty().addListener((o, oldVal, newVal) -> this.updateButtonType(newVal));\n        button.setOnMousePressed((e) -> {\n            if (this.clickedAnimation != null) {\n                this.clickedAnimation.setRate(1.0F);\n                this.clickedAnimation.play();\n            }\n        });\n        button.setOnMouseReleased((e) -> {\n            if (this.clickedAnimation != null) {\n                this.clickedAnimation.setRate(-1.0F);\n                this.clickedAnimation.play();\n            }\n        });\n\n        ReadOnlyBooleanProperty focusVisibleProperty = FXUtils.focusVisibleProperty(button);\n        if (focusVisibleProperty == null) {\n            focusVisibleProperty = button.focusedProperty();\n        }\n        focusVisibleProperty.addListener((o, oldVal, newVal) -> {\n            if (newVal) {\n                if (!this.getSkinnable().isPressed()) {\n                    this.buttonRippler.showOverlay();\n                }\n            } else {\n                this.buttonRippler.hideOverlay();\n            }\n        });\n        button.pressedProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.hideOverlay());\n        button.setPickOnBounds(false);\n        this.buttonContainer.setPickOnBounds(false);\n        this.buttonContainer.shapeProperty().bind(this.getSkinnable().shapeProperty());\n        this.buttonContainer.borderProperty().bind(this.getSkinnable().borderProperty());\n        this.buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(() -> {\n            if (button.getBackground() == null || this.isJavaDefaultBackground(button.getBackground()) || this.isJavaDefaultClickedBackground(button.getBackground())) {\n                button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null)));\n            }\n\n            try {\n                return new Background(new BackgroundFill(this.getSkinnable().getBackground() != null ? this.getSkinnable().getBackground().getFills().get(0).getFill() : Color.TRANSPARENT, this.getSkinnable().getBackground() != null ? this.getSkinnable().getBackground().getFills().get(0).getRadii() : this.defaultRadii, Insets.EMPTY));\n            } catch (Exception var3) {\n                return this.getSkinnable().getBackground();\n            }\n        }, this.getSkinnable().backgroundProperty()));\n        button.ripplerFillProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.setRipplerFill(newVal));\n        if (button.getBackground() == null || this.isJavaDefaultBackground(button.getBackground())) {\n            button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null)));\n        }\n\n        this.updateButtonType(button.getButtonType());\n        this.updateChildren();\n    }\n\n    protected void updateChildren() {\n        super.updateChildren();\n        if (this.buttonContainer != null) {\n            this.getChildren().add(0, this.buttonContainer);\n        }\n\n        for (int i = 1; i < this.getChildren().size(); ++i) {\n            this.getChildren().get(i).setMouseTransparent(true);\n        }\n    }\n\n    protected void layoutChildren(double x, double y, double w, double h) {\n        if (this.invalid) {\n            if (((JFXButton) this.getSkinnable()).getRipplerFill() == null) {\n                for (int i = this.getChildren().size() - 1; i >= 1; --i) {\n                    if (this.getChildren().get(i) instanceof Shape shape) {\n                        this.buttonRippler.setRipplerFill(shape.getFill());\n                        shape.fillProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.setRipplerFill(newVal));\n                        break;\n                    }\n\n                    if (this.getChildren().get(i) instanceof Label label) {\n                        this.buttonRippler.setRipplerFill(label.getTextFill());\n                        label.textFillProperty().addListener((o, oldVal, newVal) -> this.buttonRippler.setRipplerFill(newVal));\n                        break;\n                    }\n                }\n            } else {\n                this.buttonRippler.setRipplerFill(((JFXButton) this.getSkinnable()).getRipplerFill());\n            }\n\n            this.invalid = false;\n        }\n\n        double shift = 1.0F;\n        this.buttonContainer.resizeRelocate(this.getSkinnable().getLayoutBounds().getMinX() - shift, this.getSkinnable().getLayoutBounds().getMinY() - shift, this.getSkinnable().getWidth() + (double) 2.0F * shift, this.getSkinnable().getHeight() + (double) 2.0F * shift);\n        this.layoutLabelInArea(x, y, w, h);\n    }\n\n    private boolean isJavaDefaultBackground(Background background) {\n        try {\n            String firstFill = background.getFills().get(0).getFill().toString();\n            return \"0xffffffba\".equals(firstFill) || \"0xffffffbf\".equals(firstFill) || \"0xffffff12\".equals(firstFill) || \"0xffffffbd\".equals(firstFill);\n        } catch (Exception var3) {\n            return false;\n        }\n    }\n\n    private boolean isJavaDefaultClickedBackground(Background background) {\n        try {\n            return \"0x039ed3ff\".equals(background.getFills().get(0).getFill().toString());\n        } catch (Exception var3) {\n            return false;\n        }\n    }\n\n    private void updateButtonType(JFXButton.ButtonType type) {\n        switch (type) {\n            case RAISED -> {\n                JFXDepthManager.setDepth(this.buttonContainer, 2);\n                this.clickedAnimation = new ButtonClickTransition();\n            }\n            case FLAT -> this.buttonContainer.setEffect(null);\n        }\n    }\n\n    private class ButtonClickTransition extends CachedTransition {\n        public ButtonClickTransition() {\n            super(JFXButtonSkin.this.buttonContainer, new Timeline(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(2).radiusProperty().get(), Interpolator.EASE_BOTH),\n                            new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(2).spreadProperty().get(), Interpolator.EASE_BOTH),\n                            new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(2).offsetXProperty().get(), Interpolator.EASE_BOTH),\n                            new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(2).offsetYProperty().get(), Interpolator.EASE_BOTH)),\n                    new KeyFrame(Duration.millis(1000.0F),\n                            new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).radiusProperty(), JFXDepthManager.getShadowAt(5).radiusProperty().get(), Interpolator.EASE_BOTH),\n                            new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).spreadProperty(), JFXDepthManager.getShadowAt(5).spreadProperty().get(), Interpolator.EASE_BOTH),\n                            new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).offsetXProperty(), JFXDepthManager.getShadowAt(5).offsetXProperty().get(), Interpolator.EASE_BOTH),\n                            new KeyValue(((DropShadow) JFXButtonSkin.this.buttonContainer.getEffect()).offsetYProperty(), JFXDepthManager.getShadowAt(5).offsetYProperty().get(), Interpolator.EASE_BOTH))));\n            this.setCycleDuration(Duration.seconds(0.2));\n            this.setDelay(Duration.seconds(0.0F));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXCheckBoxSkin.java",
    "content": "//\n// Source code recreated from a .class file by IntelliJ IDEA\n// (powered by FernFlower decompiler)\n//\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXCheckBox;\nimport com.jfoenix.controls.JFXRippler;\nimport com.jfoenix.controls.JFXRippler.RipplerMask;\nimport com.jfoenix.controls.JFXRippler.RipplerPos;\nimport com.jfoenix.transitions.CachedTransition;\nimport com.jfoenix.transitions.JFXFillTransition;\nimport com.jfoenix.utils.JFXNodeUtils;\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.animation.Transition;\nimport javafx.beans.property.ReadOnlyBooleanProperty;\nimport javafx.geometry.HPos;\nimport javafx.geometry.Insets;\nimport javafx.geometry.VPos;\nimport javafx.scene.control.CheckBox;\nimport javafx.scene.control.skin.CheckBoxSkin;\nimport javafx.scene.layout.AnchorPane;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundFill;\nimport javafx.scene.layout.Border;\nimport javafx.scene.layout.BorderStroke;\nimport javafx.scene.layout.BorderStrokeStyle;\nimport javafx.scene.layout.BorderWidths;\nimport javafx.scene.layout.CornerRadii;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.SVGPath;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic class JFXCheckBoxSkin extends CheckBoxSkin {\n    private final StackPane box = new StackPane();\n    private final StackPane mark = new StackPane();\n    private final double lineThick = 2.0;\n    private final double padding = 10.0;\n    private final JFXRippler rippler;\n    private final AnchorPane container = new AnchorPane();\n    private final double labelOffset = -8.0;\n    private final Transition transition;\n    private boolean invalid = true;\n    private JFXFillTransition select;\n\n    public JFXCheckBoxSkin(JFXCheckBox control) {\n        super(control);\n        this.box.setMinSize(18.0, 18.0);\n        this.box.setPrefSize(18.0, 18.0);\n        this.box.setMaxSize(18.0, 18.0);\n        this.box.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, new CornerRadii(2.0), Insets.EMPTY)));\n        this.box.setBorder(new Border(new BorderStroke(Themes.getColorScheme().getOnSurfaceVariant(), BorderStrokeStyle.SOLID, new CornerRadii(2.0), new BorderWidths(this.lineThick))));\n        StackPane boxContainer = new StackPane();\n        boxContainer.getChildren().add(this.box);\n        boxContainer.setPadding(new Insets(this.padding));\n        this.rippler = new JFXRippler(boxContainer, RipplerMask.CIRCLE, RipplerPos.BACK);\n        this.updateRippleColor();\n        SVGPath shape = new SVGPath();\n        shape.setContent(\"M384 690l452-452 60 60-512 512-238-238 60-60z\");\n        this.mark.setShape(shape);\n        this.mark.setMaxSize(15.0, 12.0);\n        this.mark.setStyle(\"-fx-background-color:-monet-on-primary; -fx-border-color:-monet-on-primary; -fx-border-width:2px;\");\n        this.mark.setVisible(false);\n        this.mark.setScaleX(0.0);\n        this.mark.setScaleY(0.0);\n        boxContainer.getChildren().add(this.mark);\n        this.container.getChildren().add(this.rippler);\n        AnchorPane.setRightAnchor(this.rippler, this.labelOffset);\n        control.selectedProperty().addListener((o, oldVal, newVal) -> {\n            this.updateRippleColor();\n            this.playSelectAnimation(newVal);\n        });\n\n        ReadOnlyBooleanProperty focusVisibleProperty = FXUtils.focusVisibleProperty(control);\n        if (focusVisibleProperty == null)\n            focusVisibleProperty = control.focusedProperty();\n        focusVisibleProperty.addListener((o, oldVal, newVal) -> {\n            if (newVal) {\n                if (!this.getSkinnable().isPressed()) {\n                    this.rippler.showOverlay();\n                }\n            } else {\n                this.rippler.hideOverlay();\n            }\n        });\n        control.pressedProperty().addListener((o, oldVal, newVal) -> this.rippler.hideOverlay());\n        this.updateChildren();\n        this.registerChangeListener(control.checkedColorProperty(), ignored -> {\n            if (select != null) {\n                select.stop();\n            }\n            this.createFillTransition();\n            updateColors();\n        });\n        this.registerChangeListener(control.unCheckedColorProperty(), ignored -> updateColors());\n        this.transition = new CheckBoxTransition();\n        this.createFillTransition();\n    }\n\n    private void updateRippleColor() {\n        var control = (JFXCheckBox) this.getSkinnable();\n        this.rippler.setRipplerFill(control.isSelected()\n                ? control.getCheckedColor()\n                : control.getUnCheckedColor());\n    }\n\n    private void updateColors() {\n        var control = (JFXCheckBox) getSkinnable();\n        boolean isSelected = control.isSelected();\n        JFXNodeUtils.updateBackground(box.getBackground(), box, isSelected ? control.getCheckedColor() : Color.TRANSPARENT);\n        rippler.setRipplerFill(isSelected ? control.getCheckedColor() : control.getUnCheckedColor());\n        final BorderStroke borderStroke = box.getBorder().getStrokes().get(0);\n        box.setBorder(new Border(new BorderStroke(\n                isSelected ? control.getCheckedColor() : Themes.getColorScheme().getOnSurfaceVariant(),\n                borderStroke.getTopStyle(),\n                borderStroke.getRadii(),\n                borderStroke.getWidths())));\n    }\n\n    protected void updateChildren() {\n        super.updateChildren();\n        if (this.container != null) {\n            this.getChildren().remove(1);\n            this.getChildren().add(this.container);\n        }\n    }\n\n    protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.box.minWidth(-1.0)) + this.labelOffset + 2.0 * this.padding;\n    }\n\n    protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeY(this.box.prefWidth(-1.0)) + this.labelOffset + 2.0 * this.padding;\n    }\n\n    protected void layoutChildren(double x, double y, double w, double h) {\n        CheckBox checkBox = this.getSkinnable();\n        double boxWidth = this.snapSizeX(this.container.prefWidth(-1.0));\n        double boxHeight = this.snapSizeY(this.container.prefHeight(-1.0));\n        double computeWidth = Math.min(checkBox.prefWidth(-1.0), checkBox.minWidth(-1.0)) + this.labelOffset + 2.0 * this.padding;\n        double labelWidth = Math.min(computeWidth - boxWidth, w - this.snapSizeX(boxWidth)) + this.labelOffset + 2.0 * this.padding;\n        double labelHeight = Math.min(checkBox.prefHeight(labelWidth), h);\n        double maxHeight = Math.max(boxHeight, labelHeight);\n        double xOffset = computeXOffset(w, labelWidth + boxWidth, checkBox.getAlignment().getHpos()) + x;\n        double yOffset = computeYOffset(h, maxHeight, checkBox.getAlignment().getVpos()) + x;\n        if (this.invalid) {\n            if (this.getSkinnable().isSelected()) {\n                this.playSelectAnimation(true);\n            }\n\n            this.invalid = false;\n        }\n\n        this.layoutLabelInArea(xOffset + boxWidth, yOffset, labelWidth, maxHeight, checkBox.getAlignment());\n        this.container.resize(boxWidth, boxHeight);\n        this.positionInArea(this.container, xOffset, yOffset, boxWidth, maxHeight, 0.0, checkBox.getAlignment().getHpos(), checkBox.getAlignment().getVpos());\n    }\n\n    static double computeXOffset(double width, double contentWidth, HPos hpos) {\n        return switch (hpos) {\n            case LEFT -> 0.0;\n            case CENTER -> (width - contentWidth) / 2.0;\n            case RIGHT -> width - contentWidth;\n        };\n    }\n\n    static double computeYOffset(double height, double contentHeight, VPos vpos) {\n        return switch (vpos) {\n            case TOP -> 0.0;\n            case CENTER -> (height - contentHeight) / 2.0;\n            case BOTTOM -> height - contentHeight;\n            default -> 0.0;\n        };\n    }\n\n    private void playSelectAnimation(Boolean selection) {\n        if (selection == null) {\n            selection = false;\n        }\n\n        JFXCheckBox control = (JFXCheckBox) this.getSkinnable();\n        this.transition.setRate(selection ? 1.0 : -1.0);\n        this.select.setRate(selection ? 1.0 : -1.0);\n        this.transition.play();\n        this.select.play();\n        this.box.setBorder(new Border(new BorderStroke(\n                selection ? control.getCheckedColor() : Themes.getColorScheme().getOnSurfaceVariant(),\n                BorderStrokeStyle.SOLID,\n                new CornerRadii(2.0),\n                new BorderWidths(this.lineThick))));\n    }\n\n    private void createFillTransition() {\n        this.select = new JFXFillTransition(Duration.millis(120.0), this.box, Color.TRANSPARENT,\n                (Color) ((JFXCheckBox) this.getSkinnable()).getCheckedColor());\n        this.select.setInterpolator(Interpolator.EASE_OUT);\n    }\n\n    private final class CheckBoxTransition extends CachedTransition {\n        CheckBoxTransition() {\n            super(JFXCheckBoxSkin.this.mark,\n                    new Timeline(\n                            new KeyFrame(Duration.ZERO,\n                                    new KeyValue(JFXCheckBoxSkin.this.mark.visibleProperty(), false, Interpolator.EASE_OUT),\n                                    new KeyValue(JFXCheckBoxSkin.this.mark.scaleXProperty(), (double) 0.5F, Interpolator.EASE_OUT),\n                                    new KeyValue(JFXCheckBoxSkin.this.mark.scaleYProperty(), (double) 0.5F, Interpolator.EASE_OUT)),\n                            new KeyFrame(Duration.millis(400.0),\n                                    new KeyValue(JFXCheckBoxSkin.this.mark.visibleProperty(), true, Interpolator.EASE_OUT),\n                                    new KeyValue(JFXCheckBoxSkin.this.mark.scaleXProperty(), (double) 0.5F, Interpolator.EASE_OUT),\n                                    new KeyValue(JFXCheckBoxSkin.this.mark.scaleYProperty(), (double) 0.5F, Interpolator.EASE_OUT)),\n                            new KeyFrame(Duration.millis(1000.0),\n                                    new KeyValue(JFXCheckBoxSkin.this.mark.visibleProperty(), true, Interpolator.EASE_OUT),\n                                    new KeyValue(JFXCheckBoxSkin.this.mark.scaleXProperty(), 1, Interpolator.EASE_OUT),\n                                    new KeyValue(JFXCheckBoxSkin.this.mark.scaleYProperty(), 1, Interpolator.EASE_OUT))\n                    )\n            );\n            this.setCycleDuration(Duration.seconds(0.12));\n            this.setDelay(Duration.seconds(0.05));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXColorPalette.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXColorPicker;\nimport com.jfoenix.utils.JFXNodeUtils;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener.Change;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.event.Event;\nimport javafx.geometry.Bounds;\nimport javafx.geometry.NodeOrientation;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.PopupControl;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.*;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Rectangle;\nimport javafx.scene.shape.StrokeType;\n\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\n/**\n * @author Shadi Shaheen FUTURE WORK: this UI will get re-designed to match material design guidlines\n */\nfinal class JFXColorPalette extends Region {\n\n    private static final int SQUARE_SIZE = 15;\n\n    // package protected for testing purposes\n    JFXColorGrid colorPickerGrid;\n    final JFXButton customColorLink = new JFXButton(i18n(\"color.custom\"));\n    JFXCustomColorPickerDialog customColorDialog = null;\n\n    private final JFXColorPicker colorPicker;\n    private final GridPane customColorGrid = new GridPane();\n    private final Label customColorLabel = new Label(i18n(\"color.recent\"));\n\n    private PopupControl popupControl;\n    private ColorSquare focusedSquare;\n\n    private Color mouseDragColor = null;\n    private boolean dragDetected = false;\n\n    private final ColorSquare hoverSquare = new ColorSquare();\n\n    public JFXColorPalette(final JFXColorPicker colorPicker) {\n        getStyleClass().add(\"color-palette-region\");\n        this.colorPicker = colorPicker;\n        colorPickerGrid = new JFXColorGrid();\n        colorPickerGrid.getChildren().get(0).requestFocus();\n        customColorLabel.setAlignment(Pos.CENTER_LEFT);\n        customColorLink.setPrefWidth(colorPickerGrid.prefWidth(-1));\n        customColorLink.setAlignment(Pos.CENTER);\n        customColorLink.setFocusTraversable(true);\n        customColorLink.setOnAction(ev -> {\n            if (customColorDialog == null) {\n                customColorDialog = new JFXCustomColorPickerDialog(popupControl);\n                customColorDialog.customColorProperty().addListener((ov, t1, t2) -> {\n                    colorPicker.setValue(customColorDialog.customColorProperty().get());\n                });\n                customColorDialog.setOnSave(() -> {\n                    Color customColor = customColorDialog.customColorProperty().get();\n                    buildCustomColors();\n                    colorPicker.getCustomColors().add(customColor);\n                    updateSelection(customColor);\n                    Event.fireEvent(colorPicker, new ActionEvent());\n                    colorPicker.hide();\n                });\n            }\n            customColorDialog.setCurrentColor(colorPicker.valueProperty().get());\n            if (popupControl != null) {\n                popupControl.setAutoHide(false);\n            }\n            customColorDialog.show();\n            customColorDialog.setOnHidden(event -> {\n                if (popupControl != null) {\n                    popupControl.setAutoHide(true);\n                }\n            });\n        });\n\n        initNavigation();\n        customColorGrid.getStyleClass().add(\"color-picker-grid\");\n        customColorGrid.setVisible(false);\n\n        buildCustomColors();\n\n        colorPicker.getCustomColors().addListener((Change<? extends Color> change) -> buildCustomColors());\n        VBox paletteBox = new VBox();\n        paletteBox.getStyleClass().add(\"color-palette\");\n        paletteBox.getChildren().addAll(colorPickerGrid);\n        if (colorPicker.getPreDefinedColors() == null) {\n            paletteBox.getChildren().addAll(customColorLabel, customColorGrid, customColorLink);\n        }\n\n        hoverSquare.setMouseTransparent(true);\n        hoverSquare.getStyleClass().addAll(\"hover-square\");\n        setFocusedSquare(null);\n\n        getChildren().addAll(paletteBox, hoverSquare);\n    }\n\n    private void setFocusedSquare(ColorSquare square) {\n        hoverSquare.setVisible(square != null);\n\n        if (square == focusedSquare) {\n            return;\n        }\n        focusedSquare = square;\n\n        hoverSquare.setVisible(focusedSquare != null);\n        if (focusedSquare == null) {\n            return;\n        }\n\n        if (!focusedSquare.isFocused()) {\n            focusedSquare.requestFocus();\n        }\n\n        hoverSquare.rectangle.setFill(focusedSquare.rectangle.getFill());\n\n        Bounds b = square.localToScene(square.getLayoutBounds());\n\n        double x = b.getMinX();\n        double y = b.getMinY();\n\n        double xAdjust;\n        double scaleAdjust = hoverSquare.getScaleX() == 1.0 ? 0 : hoverSquare.getWidth() / 4.0;\n\n        if (colorPicker.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {\n            x = focusedSquare.getLayoutX();\n            xAdjust = -focusedSquare.getWidth() + scaleAdjust;\n        } else {\n            xAdjust = focusedSquare.getWidth() / 2.0 + scaleAdjust;\n        }\n\n        hoverSquare.setLayoutX(snapPositionX(x) - xAdjust);\n        hoverSquare.setLayoutY(snapPositionY(y) - focusedSquare.getHeight() / 2.0 + (hoverSquare.getScaleY() == 1.0 ? 0 : focusedSquare.getHeight() / 4.0));\n    }\n\n    private void buildCustomColors() {\n        final ObservableList<Color> customColors = colorPicker.getCustomColors();\n        customColorGrid.getChildren().clear();\n        if (customColors.isEmpty()) {\n            customColorLabel.setVisible(false);\n            customColorLabel.setManaged(false);\n            customColorGrid.setVisible(false);\n            customColorGrid.setManaged(false);\n            return;\n        } else {\n            customColorLabel.setVisible(true);\n            customColorLabel.setManaged(true);\n            customColorGrid.setVisible(true);\n            customColorGrid.setManaged(true);\n        }\n\n        int customColumnIndex = 0;\n        int customRowIndex = 0;\n        int remainingSquares = customColors.size() % NUM_OF_COLUMNS;\n        int numEmpty = (remainingSquares == 0) ? 0 : NUM_OF_COLUMNS - remainingSquares;\n\n        for (int i = 0; i < customColors.size(); i++) {\n            Color c = customColors.get(i);\n            ColorSquare square = new ColorSquare(c, i, true);\n            customColorGrid.add(square, customColumnIndex, customRowIndex);\n            customColumnIndex++;\n            if (customColumnIndex == NUM_OF_COLUMNS) {\n                customColumnIndex = 0;\n                customRowIndex++;\n            }\n        }\n        for (int i = 0; i < numEmpty; i++) {\n            ColorSquare emptySquare = new ColorSquare();\n            customColorGrid.add(emptySquare, customColumnIndex, customRowIndex);\n            customColumnIndex++;\n        }\n        requestLayout();\n    }\n\n    private void initNavigation() {\n        setOnKeyPressed(ke -> {\n            switch (ke.getCode()) {\n                case SPACE:\n                case ENTER:\n                    // select the focused color\n                    if (focusedSquare != null) {\n                        focusedSquare.selectColor(ke);\n                    }\n                    ke.consume();\n                    break;\n                default: // no-op\n            }\n        });\n    }\n\n    public void setPopupControl(PopupControl pc) {\n        this.popupControl = pc;\n    }\n\n    public JFXColorGrid getColorGrid() {\n        return colorPickerGrid;\n    }\n\n    public boolean isCustomColorDialogShowing() {\n        return customColorDialog != null && customColorDialog.isVisible();\n    }\n\n    class ColorSquare extends StackPane {\n        Rectangle rectangle;\n        boolean isEmpty;\n\n        public ColorSquare() {\n            this(null, -1, false);\n        }\n\n        public ColorSquare(Color color, int index) {\n            this(color, index, false);\n        }\n\n        public ColorSquare(Color color, int index, boolean isCustom) {\n            // Add style class to handle selected color square\n            getStyleClass().add(\"color-square\");\n            if (color != null) {\n                setFocusTraversable(true);\n                focusedProperty().addListener((s, ov, nv) -> setFocusedSquare(nv ? this : null));\n                addEventHandler(MouseEvent.MOUSE_ENTERED, event -> setFocusedSquare(ColorSquare.this));\n                addEventHandler(MouseEvent.MOUSE_EXITED, event -> setFocusedSquare(null));\n                addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {\n                    if (!dragDetected && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {\n                        if (!isEmpty) {\n                            Color fill = (Color) rectangle.getFill();\n                            colorPicker.setValue(fill);\n                            colorPicker.fireEvent(new ActionEvent());\n                            updateSelection(fill);\n                            event.consume();\n                        }\n                        colorPicker.hide();\n                    }\n                });\n            }\n            rectangle = new Rectangle(SQUARE_SIZE, SQUARE_SIZE);\n            if (color == null) {\n                rectangle.setFill(Color.WHITE);\n                isEmpty = true;\n            } else {\n                rectangle.setFill(color);\n            }\n\n            rectangle.setStrokeType(StrokeType.INSIDE);\n\n            String tooltipStr = JFXNodeUtils.colorToHex(color);\n            Tooltip.install(this, new Tooltip((tooltipStr == null) ? \"\" : tooltipStr));\n\n            rectangle.getStyleClass().add(\"color-rect\");\n            getChildren().add(rectangle);\n        }\n\n        public void selectColor(KeyEvent event) {\n            if (rectangle.getFill() != null) {\n                if (rectangle.getFill() instanceof Color) {\n                    colorPicker.setValue((Color) rectangle.getFill());\n                    colorPicker.fireEvent(new ActionEvent());\n                }\n                event.consume();\n            }\n            colorPicker.hide();\n        }\n    }\n\n    // The skin can update selection if colorpicker value changes..\n    public void updateSelection(Color color) {\n        setFocusedSquare(null);\n\n        for (ColorSquare c : colorPickerGrid.getSquares()) {\n            if (c.rectangle.getFill().equals(color)) {\n                setFocusedSquare(c);\n                return;\n            }\n        }\n        // check custom colors\n        for (Node n : customColorGrid.getChildren()) {\n            ColorSquare c = (ColorSquare) n;\n            if (c.rectangle.getFill().equals(color)) {\n                setFocusedSquare(c);\n                return;\n            }\n        }\n    }\n\n    class JFXColorGrid extends GridPane {\n\n        private final List<ColorSquare> squares;\n        final int NUM_OF_COLORS;\n        final int NUM_OF_ROWS;\n\n        public JFXColorGrid() {\n            getStyleClass().add(\"color-picker-grid\");\n            setId(\"ColorCustomizerColorGrid\");\n            int columnIndex = 0;\n            int rowIndex = 0;\n            squares = FXCollections.observableArrayList();\n            double[] limitedColors = colorPicker.getPreDefinedColors();\n            limitedColors = limitedColors == null ? RAW_VALUES : limitedColors;\n            NUM_OF_COLORS = limitedColors.length / 3;\n            NUM_OF_ROWS = (int) Math.ceil((double) NUM_OF_COLORS / (double) NUM_OF_COLUMNS);\n            final int numColors = limitedColors.length / 3;\n            Color[] colors = new Color[numColors];\n            for (int i = 0; i < numColors; i++) {\n                colors[i] = new Color(limitedColors[i * 3] / 255,\n                        limitedColors[(i * 3) + 1] / 255, limitedColors[(i * 3) + 2] / 255,\n                        1.0);\n                ColorSquare cs = new ColorSquare(colors[i], i);\n                squares.add(cs);\n            }\n\n            for (ColorSquare square : squares) {\n                add(square, columnIndex, rowIndex);\n                columnIndex++;\n                if (columnIndex == NUM_OF_COLUMNS) {\n                    columnIndex = 0;\n                    rowIndex++;\n                }\n            }\n            setOnMouseDragged(t -> {\n                if (!dragDetected) {\n                    dragDetected = true;\n                    mouseDragColor = colorPicker.getValue();\n                }\n                int xIndex = clamp(0,\n                        (int) t.getX() / (SQUARE_SIZE + 1), NUM_OF_COLUMNS - 1);\n                int yIndex = clamp(0,\n                        (int) t.getY() / (SQUARE_SIZE + 1), NUM_OF_ROWS - 1);\n                int index = xIndex + yIndex * NUM_OF_COLUMNS;\n                colorPicker.setValue((Color) squares.get(index).rectangle.getFill());\n                updateSelection(colorPicker.getValue());\n            });\n            addEventHandler(MouseEvent.MOUSE_RELEASED, t -> {\n                if (colorPickerGrid.getBoundsInLocal().contains(t.getX(), t.getY())) {\n                    updateSelection(colorPicker.getValue());\n                    colorPicker.fireEvent(new ActionEvent());\n                    colorPicker.hide();\n                } else {\n                    // restore color as mouse release happened outside the grid.\n                    if (mouseDragColor != null) {\n                        colorPicker.setValue(mouseDragColor);\n                        updateSelection(mouseDragColor);\n                    }\n                }\n                dragDetected = false;\n            });\n        }\n\n        public List<ColorSquare> getSquares() {\n            return squares;\n        }\n\n        @Override\n        protected double computePrefWidth(double height) {\n            return (SQUARE_SIZE + 1) * NUM_OF_COLUMNS;\n        }\n\n        @Override\n        protected double computePrefHeight(double width) {\n            return (SQUARE_SIZE + 1) * NUM_OF_ROWS;\n        }\n    }\n\n    private static final int NUM_OF_COLUMNS = 10;\n    private static final double[] RAW_VALUES = {\n            // WARNING: always make sure the number of colors is a divisable by NUM_OF_COLUMNS\n            250, 250, 250, // first row\n            245, 245, 245,\n            238, 238, 238,\n            224, 224, 224,\n            189, 189, 189,\n            158, 158, 158,\n            117, 117, 117,\n            97, 97, 97,\n            66, 66, 66,\n            33, 33, 33,\n            // second row\n            236, 239, 241,\n            207, 216, 220,\n            176, 190, 197,\n            144, 164, 174,\n            120, 144, 156,\n            96, 125, 139,\n            84, 110, 122,\n            69, 90, 100,\n            55, 71, 79,\n            38, 50, 56,\n            // third row\n            255, 235, 238,\n            255, 205, 210,\n            239, 154, 154,\n            229, 115, 115,\n            239, 83, 80,\n            244, 67, 54,\n            229, 57, 53,\n            211, 47, 47,\n            198, 40, 40,\n            183, 28, 28,\n            // forth row\n            252, 228, 236,\n            248, 187, 208,\n            244, 143, 177,\n            240, 98, 146,\n            236, 64, 122,\n            233, 30, 99,\n            216, 27, 96,\n            194, 24, 91,\n            173, 20, 87,\n            136, 14, 79,\n            // fifth row\n            243, 229, 245,\n            225, 190, 231,\n            206, 147, 216,\n            186, 104, 200,\n            171, 71, 188,\n            156, 39, 176,\n            142, 36, 170,\n            123, 31, 162,\n            106, 27, 154,\n            74, 20, 140,\n            // sixth row\n            237, 231, 246,\n            209, 196, 233,\n            179, 157, 219,\n            149, 117, 205,\n            126, 87, 194,\n            103, 58, 183,\n            94, 53, 177,\n            81, 45, 168,\n            69, 39, 160,\n            49, 27, 146,\n            // seventh row\n            232, 234, 246,\n            197, 202, 233,\n            159, 168, 218,\n            121, 134, 203,\n            92, 107, 192,\n            63, 81, 181,\n            57, 73, 171,\n            48, 63, 159,\n            40, 53, 147,\n            26, 35, 126,\n            // eigth row\n            227, 242, 253,\n            187, 222, 251,\n            144, 202, 249,\n            100, 181, 246,\n            66, 165, 245,\n            33, 150, 243,\n            30, 136, 229,\n            25, 118, 210,\n            21, 101, 192,\n            13, 71, 161,\n            // ninth row\n            225, 245, 254,\n            179, 229, 252,\n            129, 212, 250,\n            79, 195, 247,\n            41, 182, 246,\n            3, 169, 244,\n            3, 155, 229,\n            2, 136, 209,\n            2, 119, 189,\n            1, 87, 155,\n            // tenth row\n            224, 247, 250,\n            178, 235, 242,\n            128, 222, 234,\n            77, 208, 225,\n            38, 198, 218,\n            0, 188, 212,\n            0, 172, 193,\n            0, 151, 167,\n            0, 131, 143,\n            0, 96, 100,\n            // eleventh row\n            224, 242, 241,\n            178, 223, 219,\n            128, 203, 196,\n            77, 182, 172,\n            38, 166, 154,\n            0, 150, 136,\n            0, 137, 123,\n            0, 121, 107,\n            0, 105, 92,\n            0, 77, 64,\n            // twelfth row\n            232, 245, 233,\n            200, 230, 201,\n            165, 214, 167,\n            129, 199, 132,\n            102, 187, 106,\n            76, 175, 80,\n            67, 160, 71,\n            56, 142, 60,\n            46, 125, 50,\n            27, 94, 32,\n\n            // thirteenth row\n            241, 248, 233,\n            220, 237, 200,\n            197, 225, 165,\n            174, 213, 129,\n            156, 204, 101,\n            139, 195, 74,\n            124, 179, 66,\n            104, 159, 56,\n            85, 139, 47,\n            51, 105, 30,\n            // fourteenth row\n            249, 251, 231,\n            240, 244, 195,\n            230, 238, 156,\n            220, 231, 117,\n            212, 225, 87,\n            205, 220, 57,\n            192, 202, 51,\n            175, 180, 43,\n            158, 157, 36,\n            130, 119, 23,\n\n            // fifteenth row\n            255, 253, 231,\n            255, 249, 196,\n            255, 245, 157,\n            255, 241, 118,\n            255, 238, 88,\n            255, 235, 59,\n            253, 216, 53,\n            251, 192, 45,\n            249, 168, 37,\n            245, 127, 23,\n\n            // sixteenth row\n            255, 248, 225,\n            255, 236, 179,\n            255, 224, 130,\n            255, 213, 79,\n            255, 202, 40,\n            255, 193, 7,\n            255, 179, 0,\n            255, 160, 0,\n            255, 143, 0,\n            255, 111, 0,\n\n            // seventeenth row\n            255, 243, 224,\n            255, 224, 178,\n            255, 204, 128,\n            255, 183, 77,\n            255, 167, 38,\n            255, 152, 0,\n            251, 140, 0,\n            245, 124, 0,\n            239, 108, 0,\n            230, 81, 0,\n\n            // eighteenth row\n            251, 233, 231,\n            255, 204, 188,\n            255, 171, 145,\n            255, 138, 101,\n            255, 112, 67,\n            255, 87, 34,\n            244, 81, 30,\n            230, 74, 25,\n            216, 67, 21,\n            191, 54, 12,\n\n            // nineteenth row\n            239, 235, 233,\n            215, 204, 200,\n            188, 170, 164,\n            161, 136, 127,\n            141, 110, 99,\n            121, 85, 72,\n            109, 76, 65,\n            93, 64, 55,\n            78, 52, 46,\n            62, 39, 35,\n    };\n\n    private static int clamp(int min, int value, int max) {\n        if (value < min) {\n            return min;\n        }\n        if (value > max) {\n            return max;\n        }\n        return value;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXColorPickerSkin.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXClippedPane;\nimport com.jfoenix.controls.JFXColorPicker;\nimport com.jfoenix.controls.JFXRippler;\nimport com.jfoenix.effects.JFXDepthManager;\nimport com.jfoenix.utils.JFXNodeUtils;\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.css.*;\nimport javafx.css.converter.BooleanConverter;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.control.ColorPicker;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.control.skin.ComboBoxPopupControl;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundFill;\nimport javafx.scene.layout.CornerRadii;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Circle;\nimport javafx.util.Duration;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * @author Shadi Shaheen\n */\npublic final class JFXColorPickerSkin extends JFXGenericPickerSkin<Color> {\n\n    private final Label displayNode;\n    private final JFXClippedPane colorBox;\n    private JFXColorPalette popupContent;\n    StyleableBooleanProperty colorLabelVisible = new SimpleStyleableBooleanProperty(StyleableProperties.COLOR_LABEL_VISIBLE,\n            JFXColorPickerSkin.this,\n            \"colorLabelVisible\",\n            true);\n\n    private final ObjectProperty<Color> textFill = new SimpleObjectProperty<>(Color.BLACK);\n    private final ObjectProperty<Background> colorBoxBackground = new SimpleObjectProperty<>(null);\n\n    public JFXColorPickerSkin(final ColorPicker colorPicker) {\n        super(colorPicker);\n\n        // create displayNode\n        displayNode = new Label(\"\");\n        displayNode.getStyleClass().add(\"color-label\");\n        displayNode.setMouseTransparent(true);\n        displayNode.textFillProperty().bind(textFill);\n\n        // label graphic\n        colorBox = new JFXClippedPane(displayNode);\n        colorBox.getStyleClass().add(\"color-box\");\n        colorBox.setManaged(false);\n        colorBox.backgroundProperty().bind(colorBoxBackground);\n        initColor();\n        final JFXRippler rippler = new JFXRippler(colorBox, JFXRippler.RipplerMask.FIT);\n        rippler.ripplerFillProperty().bind(textFill);\n        getChildren().setAll(rippler);\n        JFXDepthManager.setDepth(getSkinnable(), 1);\n        getSkinnable().setPickOnBounds(false);\n\n        colorPicker.focusedProperty().addListener(observable -> {\n            if (colorPicker.isFocused()) {\n                if (!getSkinnable().isPressed()) {\n                    rippler.setOverlayVisible(true);\n                }\n            } else {\n                rippler.setOverlayVisible(false);\n            }\n        });\n\n        // add listeners\n        registerChangeListener(colorPicker.valueProperty(), obs -> updateColor());\n\n        colorLabelVisible.addListener(invalidate -> {\n            if (colorLabelVisible.get()) {\n                displayNode.setText(JFXNodeUtils.colorToHex(getSkinnable().getValue()));\n            } else {\n                displayNode.setText(\"\");\n            }\n        });\n    }\n\n    @Override\n    protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        double width = 100;\n        String displayNodeText = displayNode.getText();\n        displayNode.setText(\"#DDDDDD\");\n        width = Math.max(width, super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset));\n        displayNode.setText(displayNodeText);\n        return width + rightInset + leftInset;\n    }\n\n    @Override\n    protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {\n        if (colorBox == null) {\n            reflectUpdateDisplayArea();\n        }\n        return topInset + colorBox.prefHeight(width) + bottomInset;\n    }\n\n    @Override\n    protected void layoutChildren(double x, double y, double w, double h) {\n        super.layoutChildren(x, y, w, h);\n        double hInsets = snappedLeftInset() + snappedRightInset();\n        double vInsets = snappedTopInset() + snappedBottomInset();\n        double width = w + hInsets;\n        double height = h + vInsets;\n        colorBox.resizeRelocate(0, 0, width, height);\n    }\n\n    @Override\n    protected Node getPopupContent() {\n        if (popupContent == null) {\n            popupContent = new JFXColorPalette((JFXColorPicker) getSkinnable());\n        }\n        return popupContent;\n    }\n\n    @Override\n    public void show() {\n        super.show();\n        final ColorPicker colorPicker = (ColorPicker) getSkinnable();\n        popupContent.updateSelection(colorPicker.getValue());\n    }\n\n    @Override\n    public Node getDisplayNode() {\n        return displayNode;\n    }\n\n    private void updateColor() {\n        final ColorPicker colorPicker = (ColorPicker) getSkinnable();\n        Color color = colorPicker.getValue();\n        Color circleColor = color == null ? Color.WHITE : color;\n        // update picker box color\n        if (((JFXColorPicker) getSkinnable()).isDisableAnimation()) {\n            JFXNodeUtils.updateBackground(colorBox.getBackground(), colorBox, circleColor);\n        } else {\n            Circle colorCircle = new Circle();\n            colorCircle.setFill(circleColor);\n            colorCircle.setManaged(false);\n            colorCircle.setLayoutX(colorBox.getWidth() / 4);\n            colorCircle.setLayoutY(colorBox.getHeight() / 2);\n            colorBox.getChildren().add(colorCircle);\n            Timeline animateColor = new Timeline(new KeyFrame(Duration.millis(240),\n                    new KeyValue(colorCircle.radiusProperty(),\n                            200,\n                            Interpolator.EASE_BOTH)));\n            animateColor.setOnFinished((finish) -> {\n                colorBoxBackground.set(new Background(new BackgroundFill(colorCircle.getFill(), new CornerRadii(3), Insets.EMPTY)));\n                colorBox.getChildren().remove(colorCircle);\n            });\n            animateColor.play();\n        }\n        // update label color\n        textFill.set(circleColor.grayscale().getRed() < 0.5 ? Color.valueOf(\n                \"rgba(255, 255, 255, 0.87)\") : Color.valueOf(\"rgba(0, 0, 0, 0.87)\"));\n        if (colorLabelVisible.get()) {\n            displayNode.setText(JFXNodeUtils.colorToHex(circleColor));\n        } else {\n            displayNode.setText(\"\");\n        }\n    }\n\n    private void initColor() {\n        final ColorPicker colorPicker = (ColorPicker) getSkinnable();\n        Color color = colorPicker.getValue();\n        Color circleColor = color == null ? Color.WHITE : color;\n        // update picker box color\n        colorBoxBackground.set(new Background(new BackgroundFill(circleColor, new CornerRadii(3), Insets.EMPTY)));\n        // update label color\n        textFill.set(circleColor.grayscale().getRed() < 0.5 ? Color.valueOf(\n                \"rgba(255, 255, 255, 0.87)\") : Color.valueOf(\"rgba(0, 0, 0, 0.87)\"));\n        if (colorLabelVisible.get()) {\n            displayNode.setText(JFXNodeUtils.colorToHex(circleColor));\n        } else {\n            displayNode.setText(\"\");\n        }\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Stylesheet Handling                                                     *\n     *                                                                         *\n     **************************************************************************/\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<ColorPicker, Boolean> COLOR_LABEL_VISIBLE =\n                new CssMetaData<ColorPicker, Boolean>(\"-fx-color-label-visible\",\n                        BooleanConverter.getInstance(), Boolean.TRUE) {\n\n                    @Override\n                    public boolean isSettable(ColorPicker n) {\n                        final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin();\n                        return skin.colorLabelVisible == null || !skin.colorLabelVisible.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Boolean> getStyleableProperty(ColorPicker n) {\n                        final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin();\n                        return skin.colorLabelVisible;\n                    }\n                };\n        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n        static {\n            final List<CssMetaData<? extends Styleable, ?>> styleables =\n                    new ArrayList<>(ComboBoxPopupControl.getClassCssMetaData());\n            styleables.add(COLOR_LABEL_VISIBLE);\n            STYLEABLES = Collections.unmodifiableList(styleables);\n        }\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.STYLEABLES;\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    protected TextField getEditor() {\n        return null;\n    }\n\n    protected javafx.util.StringConverter<Color> getConverter() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXColorPickerUI.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.effects.JFXDepthManager;\nimport com.jfoenix.transitions.CachedTransition;\nimport javafx.animation.Animation.Status;\nimport javafx.animation.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Point2D;\nimport javafx.scene.Node;\nimport javafx.scene.effect.ColorAdjust;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.image.PixelWriter;\nimport javafx.scene.image.WritableImage;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Circle;\nimport javafx.scene.shape.Path;\nimport javafx.util.Duration;\n\n/**\n * @author Shadi Shaheen & Bassel El Mabsout this UI allows the user to pick a color using HSL color system\n */\nfinal class JFXColorPickerUI extends Pane {\n\n    private CachedTransition selectorTransition;\n    private int pickerSize = 400;\n    // sl circle selector size\n    private final int selectorSize = 20;\n    private final double centerX;\n    private final double centerY;\n    private final double huesRadius;\n    private final double slRadius;\n    private double currentHue = 0;\n\n    private final ImageView huesCircleView;\n    private final ImageView slCircleView;\n    private final Pane colorSelector;\n    private final Pane selector;\n    private CurveTransition colorsTransition;\n\n    public JFXColorPickerUI(int pickerSize) {\n        JFXDepthManager.setDepth(this, 1);\n\n        this.pickerSize = pickerSize;\n        this.centerX = (double) pickerSize / 2;\n        this.centerY = (double) pickerSize / 2;\n        final double pickerRadius = (double) pickerSize / 2;\n        this.huesRadius = pickerRadius * 0.9;\n        final double huesSmallR = pickerRadius * 0.8;\n        final double huesLargeR = pickerRadius;\n        this.slRadius = pickerRadius * 0.7;\n\n        // Create Hues Circle\n        huesCircleView = new ImageView(getHuesCircle(pickerSize, pickerSize));\n        // clip to smooth the edges\n        Circle outterCircle = new Circle(centerX, centerY, huesLargeR - 2);\n        Circle innterCircle = new Circle(centerX, centerY, huesSmallR + 2);\n        huesCircleView.setClip(Path.subtract(outterCircle, innterCircle));\n        this.getChildren().add(huesCircleView);\n\n        // create Hues Circle Selector\n        Circle r1 = new Circle(pickerRadius - huesSmallR);\n        Circle r2 = new Circle(pickerRadius - huesRadius);\n        colorSelector = new Pane();\n        colorSelector.setStyle(\"-fx-border-color:#424242; -fx-border-width:1px; -fx-background-color:rgba(255, 255, 255, 0.87);\");\n        colorSelector.setPrefSize(pickerRadius - huesSmallR, pickerRadius - huesSmallR);\n        colorSelector.setShape(Path.subtract(r1, r2));\n        colorSelector.setCache(true);\n        colorSelector.setMouseTransparent(true);\n        colorSelector.setPickOnBounds(false);\n        this.getChildren().add(colorSelector);\n\n        // add Hues Selection Listeners\n        huesCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event) -> {\n            if (colorsTransition != null) {\n                colorsTransition.stop();\n            }\n            double dx = event.getX() - centerX;\n            double dy = event.getY() - centerY;\n            double theta = Math.atan2(dy, dx);\n            double x = centerX + huesRadius * Math.cos(theta);\n            double y = centerY + huesRadius * Math.sin(theta);\n            colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(dy, dx)));\n            colorSelector.setTranslateX(x - colorSelector.getPrefWidth() / 2);\n            colorSelector.setTranslateY(y - colorSelector.getPrefHeight() / 2);\n        });\n        huesCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {\n            double dx = event.getX() - centerX;\n            double dy = event.getY() - centerY;\n            double theta = Math.atan2(dy, dx);\n            double x = centerX + huesRadius * Math.cos(theta);\n            double y = centerY + huesRadius * Math.sin(theta);\n            colorsTransition = new CurveTransition(new Point2D(colorSelector.getTranslateX() + colorSelector.getPrefWidth() / 2,\n                    colorSelector.getTranslateY() + colorSelector.getPrefHeight() / 2),\n                    new Point2D(x, y));\n            colorsTransition.play();\n        });\n        colorSelector.translateXProperty()\n                .addListener((o, oldVal, newVal) -> updateHSLCircleColor((int) (newVal.intValue() + colorSelector.getPrefWidth() / 2),\n                        (int) (colorSelector.getTranslateY() + colorSelector\n                                .getPrefHeight() / 2)));\n        colorSelector.translateYProperty()\n                .addListener((o, oldVal, newVal) -> updateHSLCircleColor((int) (colorSelector.getTranslateX() + colorSelector\n                        .getPrefWidth() / 2), (int) (newVal.intValue() + colorSelector.getPrefHeight() / 2)));\n\n\n        // Create SL Circle\n        slCircleView = new ImageView(getSLCricle(pickerSize, pickerSize));\n        slCircleView.setClip(new Circle(centerX, centerY, slRadius - 2));\n        slCircleView.setPickOnBounds(false);\n        this.getChildren().add(slCircleView);\n\n        // create SL Circle Selector\n        selector = new Pane();\n        Circle c1 = new Circle(selectorSize / 2);\n        Circle c2 = new Circle((selectorSize / 2) * 0.5);\n        selector.setShape(Path.subtract(c1, c2));\n        selector.setStyle(\n                \"-fx-border-color:#424242; -fx-border-width:1px;-fx-background-color:rgba(255, 255, 255, 0.87);\");\n        selector.setPrefSize(selectorSize, selectorSize);\n        selector.setMinSize(selectorSize, selectorSize);\n        selector.setMaxSize(selectorSize, selectorSize);\n        selector.setCache(true);\n        selector.setMouseTransparent(true);\n        this.getChildren().add(selector);\n\n\n        // add SL selection Listeners\n        slCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event) -> {\n            if (selectorTransition != null) {\n                selectorTransition.stop();\n            }\n            if (Math.pow(event.getX() - centerX, 2) + Math.pow(event.getY() - centerY, 2) < Math.pow(slRadius - 2, 2)) {\n                selector.setTranslateX(event.getX() - selector.getPrefWidth() / 2);\n                selector.setTranslateY(event.getY() - selector.getPrefHeight() / 2);\n            } else {\n                double dx = event.getX() - centerX;\n                double dy = event.getY() - centerY;\n                double theta = Math.atan2(dy, dx);\n                double x = centerX + (slRadius - 2) * Math.cos(theta);\n                double y = centerY + (slRadius - 2) * Math.sin(theta);\n                selector.setTranslateX(x - selector.getPrefWidth() / 2);\n                selector.setTranslateY(y - selector.getPrefHeight() / 2);\n            }\n        });\n        slCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {\n            selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000),\n                    new KeyValue(selector.translateXProperty(),\n                            event.getX() - selector.getPrefWidth() / 2,\n                            Interpolator.EASE_BOTH),\n                    new KeyValue(selector.translateYProperty(),\n                            event.getY() - selector.getPrefHeight() / 2,\n                            Interpolator.EASE_BOTH)))) {\n                {\n                    setCycleDuration(Duration.millis(160));\n                    setDelay(Duration.seconds(0));\n                }\n            };\n            selectorTransition.play();\n        });\n        // add slCircleView listener\n        selector.translateXProperty()\n                .addListener((o, oldVal, newVal) -> setColorAtLocation(newVal.intValue() + selectorSize / 2,\n                        (int) selector.getTranslateY() + selectorSize / 2));\n        selector.translateYProperty()\n                .addListener((o, oldVal, newVal) -> setColorAtLocation((int) selector.getTranslateX() + selectorSize / 2,\n                        newVal.intValue() + selectorSize / 2));\n\n\n        // initial color selection\n        double dx = 20 - centerX;\n        double dy = 20 - centerY;\n        double theta = Math.atan2(dy, dx);\n        double x = centerX + huesRadius * Math.cos(theta);\n        double y = centerY + huesRadius * Math.sin(theta);\n        colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(dy, dx)));\n        colorSelector.setTranslateX(x - colorSelector.getPrefWidth() / 2);\n        colorSelector.setTranslateY(y - colorSelector.getPrefHeight() / 2);\n        selector.setTranslateX(centerX - selector.getPrefWidth() / 2);\n        selector.setTranslateY(centerY - selector.getPrefHeight() / 2);\n    }\n\n    /**\n     * List of Color Nodes that needs to be updated when picking a color\n     */\n    private final ObservableList<Node> colorNodes = FXCollections.observableArrayList();\n\n    public void addColorSelectionNode(Node... nodes) {\n        colorNodes.addAll(nodes);\n    }\n\n    public void removeColorSelectionNode(Node... nodes) {\n        colorNodes.removeAll(nodes);\n    }\n\n    private void updateHSLCircleColor(int x, int y) {\n        // transform color to HSL space\n        Color color = huesCircleView.getImage().getPixelReader().getColor(x, y);\n        double max = Math.max(color.getRed(), Math.max(color.getGreen(), color.getBlue()));\n        double min = Math.min(color.getRed(), Math.min(color.getGreen(), color.getBlue()));\n        double hue = 0;\n        if (max != min) {\n            double d = max - min;\n            if (max == color.getRed()) {\n                hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0);\n            } else if (max == color.getGreen()) {\n                hue = (color.getBlue() - color.getRed()) / d + 2;\n            } else if (max == color.getBlue()) {\n                hue = (color.getRed() - color.getGreen()) / d + 4;\n            }\n            hue /= 6;\n        }\n        currentHue = map(hue, 0, 1, 0, 255);\n\n        // refresh the HSL circle\n        refreshHSLCircle();\n    }\n\n    private void refreshHSLCircle() {\n        ColorAdjust colorAdjust = new ColorAdjust();\n        colorAdjust.setHue(map(currentHue + (currentHue < 127.5 ? 1 : -1) * 127.5, 0, 255, -1, 1));\n        slCircleView.setEffect(colorAdjust);\n        setColorAtLocation((int) selector.getTranslateX() + selectorSize / 2,\n                (int) selector.getTranslateY() + selectorSize / 2);\n    }\n\n\n    /**\n     * this method is used to move selectors to a certain color\n     */\n    private boolean allowColorChange = true;\n    private ParallelTransition pTrans;\n\n    public void moveToColor(Color color) {\n        allowColorChange = false;\n        double max = Math.max(color.getRed(),\n                Math.max(color.getGreen(), color.getBlue())), min = Math.min(color.getRed(),\n                Math.min(color.getGreen(),\n                        color.getBlue()));\n        double hue = 0;\n        double l = (max + min) / 2;\n        double s = 0;\n        if (max == min) {\n            hue = s = 0; // achromatic\n        } else {\n            double d = max - min;\n            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n            if (max == color.getRed()) {\n                hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0);\n            } else if (max == color.getGreen()) {\n                hue = (color.getBlue() - color.getRed()) / d + 2;\n            } else if (max == color.getBlue()) {\n                hue = (color.getRed() - color.getGreen()) / d + 4;\n            }\n            hue /= 6;\n        }\n        currentHue = map(hue, 0, 1, 0, 255);\n\n        // Animate Hue\n        double theta = map(currentHue, 0, 255, -Math.PI, Math.PI);\n        double x = centerX + huesRadius * Math.cos(theta);\n        double y = centerY + huesRadius * Math.sin(theta);\n        colorsTransition = new CurveTransition(\n                new Point2D(\n                        colorSelector.getTranslateX() + colorSelector.getPrefWidth() / 2,\n                        colorSelector.getTranslateY() + colorSelector.getPrefHeight() / 2\n                ),\n                new Point2D(x, y));\n\n        // Animate SL\n        s = map(s, 0, 1, 0, 255);\n        l = map(l, 0, 1, 0, 255);\n        Point2D point = getPointFromSL((int) s, (int) l, slRadius);\n        double pX = centerX - point.getX();\n        double pY = centerY - point.getY();\n\n        double endPointX;\n        double endPointY;\n        if (Math.pow(pX - centerX, 2) + Math.pow(pY - centerY, 2) < Math.pow(slRadius - 2, 2)) {\n            endPointX = pX - selector.getPrefWidth() / 2;\n            endPointY = pY - selector.getPrefHeight() / 2;\n        } else {\n            double dx = pX - centerX;\n            double dy = pY - centerY;\n            theta = Math.atan2(dy, dx);\n            x = centerX + (slRadius - 2) * Math.cos(theta);\n            y = centerY + (slRadius - 2) * Math.sin(theta);\n            endPointX = x - selector.getPrefWidth() / 2;\n            endPointY = y - selector.getPrefHeight() / 2;\n        }\n        selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000),\n                new KeyValue(selector.translateXProperty(),\n                        endPointX,\n                        Interpolator.EASE_BOTH),\n                new KeyValue(selector.translateYProperty(),\n                        endPointY,\n                        Interpolator.EASE_BOTH)))) {\n            {\n                setCycleDuration(Duration.millis(160));\n                setDelay(Duration.seconds(0));\n            }\n        };\n\n        if (pTrans != null) {\n            pTrans.stop();\n        }\n        pTrans = new ParallelTransition(colorsTransition, selectorTransition);\n        pTrans.setOnFinished((finish) -> {\n            if (pTrans.getStatus() == Status.STOPPED) {\n                allowColorChange = true;\n            }\n        });\n        pTrans.play();\n\n        refreshHSLCircle();\n    }\n\n    private void setColorAtLocation(int x, int y) {\n        if (allowColorChange) {\n            Color color = getColorAtLocation(x, y);\n            String colorString = \"rgb(\" + color.getRed() * 255 + \",\" + color.getGreen() * 255 + \",\" + color.getBlue() * 255 + \");\";\n            for (Node node : colorNodes)\n                node.setStyle(\"-fx-background-color:\" + colorString + \"; -fx-fill:\" + colorString + \";\");\n        }\n    }\n\n    private Color getColorAtLocation(double x, double y) {\n        double dy = x - centerX;\n        double dx = y - centerY;\n        return getColor(dx, dy);\n    }\n\n    private Image getHuesCircle(int width, int height) {\n        WritableImage raster = new WritableImage(width, height);\n        PixelWriter pixelWriter = raster.getPixelWriter();\n        Point2D center = new Point2D((double) width / 2, (double) height / 2);\n        double rsmall = 0.8 * width / 2;\n        double rbig = (double) width / 2;\n        for (int y = 0; y < height; y++) {\n            for (int x = 0; x < width; x++) {\n                double dx = x - center.getX();\n                double dy = y - center.getY();\n                double distance = Math.sqrt((dx * dx) + (dy * dy));\n                double o = Math.atan2(dy, dx);\n                if (distance > rsmall && distance < rbig) {\n                    double H = map(o, -Math.PI, Math.PI, 0, 255);\n                    double S = 255;\n                    double L = 152;\n                    pixelWriter.setColor(x, y, HSL2RGB(H, S, L));\n                }\n            }\n        }\n        return raster;\n    }\n\n    private Image getSLCricle(int width, int height) {\n        WritableImage raster = new WritableImage(width, height);\n        PixelWriter pixelWriter = raster.getPixelWriter();\n        Point2D center = new Point2D((double) width / 2, (double) height / 2);\n        for (int y = 0; y < height; y++) {\n            for (int x = 0; x < width; x++) {\n                double dy = x - center.getX();\n                double dx = y - center.getY();\n                pixelWriter.setColor(x, y, getColor(dx, dy));\n            }\n        }\n        return raster;\n    }\n\n    private double clamp(double from, double small, double big) {\n        return Math.min(Math.max(from, small), big);\n    }\n\n    private Color getColor(double dx, double dy) {\n        double distance = Math.sqrt((dx * dx) + (dy * dy));\n        double rverysmall = 0.65 * ((double) pickerSize / 2);\n        Color pixelColor = Color.BLUE;\n\n        if (distance <= rverysmall * 1.1) {\n            double angle = -Math.PI / 2.;\n            double angle1 = angle + 2 * Math.PI / 3.;\n            double angle2 = angle1 + 2 * Math.PI / 3.;\n            double x1 = rverysmall * Math.sin(angle1);\n            double y1 = rverysmall * Math.cos(angle1);\n            double x2 = rverysmall * Math.sin(angle2);\n            double y2 = rverysmall * Math.cos(angle2);\n            dx += 0.01;\n            double[] circle = circleFrom3Points(new Point2D(x1, y1), new Point2D(x2, y2), new Point2D(dx, dy));\n            double xArc = circle[0];\n            double yArc = 0;\n            double arcR = circle[2];\n            double Arco = Math.atan2(dx - xArc, dy - yArc);\n            double Arco1 = Math.atan2(x1 - xArc, y1 - yArc);\n            double Arco2 = Math.atan2(x2 - xArc, y2 - yArc);\n\n            double finalX = xArc > 0 ? xArc - arcR : xArc + arcR;\n\n            double saturation = map(finalX, -rverysmall, rverysmall, 255, 0);\n\n            double lightness = 255;\n            double diffAngle = Arco2 - Arco1;\n            double diffArco = Arco - Arco1;\n            if (dx < x1) {\n                diffAngle = diffAngle < 0 ? 2 * Math.PI + diffAngle : diffAngle;\n                diffAngle = Math.abs(2 * Math.PI - diffAngle);\n                diffArco = diffArco < 0 ? 2 * Math.PI + diffArco : diffArco;\n                diffArco = Math.abs(2 * Math.PI - diffArco);\n            }\n            lightness = map(diffArco, 0, diffAngle, 0, 255);\n\n\n            if (distance > rverysmall) {\n                saturation = 255 - saturation;\n                if (lightness < 0 && dy < 0) {\n                    lightness = 255;\n                }\n            }\n            lightness = clamp(lightness, 0, 255);\n            if ((saturation < 10 && dx < x1) || (saturation > 240 && dx > x1)) {\n                saturation = 255 - saturation;\n            }\n            saturation = clamp(saturation, 0, 255);\n            pixelColor = HSL2RGB(currentHue, saturation, lightness);\n        }\n        return pixelColor;\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Hues Animation                                                          *\n     *                                                                         *\n     **************************************************************************/\n\n    private final class CurveTransition extends Transition {\n        Point2D from;\n        double fromTheta;\n        double toTheta;\n\n        public CurveTransition(Point2D from, Point2D to) {\n            this.from = from;\n            double fromDx = from.getX() - centerX;\n            double fromDy = from.getY() - centerY;\n            fromTheta = Math.atan2(fromDy, fromDx);\n            double toDx = to.getX() - centerX;\n            double toDy = to.getY() - centerY;\n            toTheta = Math.atan2(toDy, toDx);\n            setInterpolator(Interpolator.EASE_BOTH);\n            setDelay(Duration.millis(0));\n            setCycleDuration(Duration.millis(240));\n        }\n\n        @Override\n        protected void interpolate(double frac) {\n            double dif = Math.min(Math.abs(toTheta - fromTheta), 2 * Math.PI - Math.abs(toTheta - fromTheta));\n            if (dif == 2 * Math.PI - Math.abs(toTheta - fromTheta)) {\n                int dir = -1;\n                if (toTheta < fromTheta) {\n                    dir = 1;\n                }\n                dif = dir * dif;\n            } else {\n                dif = toTheta - fromTheta;\n            }\n\n            Point2D newP = rotate(from, new Point2D(centerX, centerY), frac * dif);\n            colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(newP.getY() - centerY, newP.getX() - centerX)));\n            colorSelector.setTranslateX(newP.getX() - colorSelector.getPrefWidth() / 2);\n            colorSelector.setTranslateY(newP.getY() - colorSelector.getPrefHeight() / 2);\n        }\n    }\n\n    /***************************************************************************\n     *                                                                         *\n     * Util methods                                                            *\n     *                                                                         *\n     **************************************************************************/\n\n    private double map(double val, double min1, double max1, double min2, double max2) {\n        return min2 + (max2 - min2) * ((val - min1) / (max1 - min1));\n    }\n\n    private Color HSL2RGB(double hue, double sat, double lum) {\n        hue = map(hue, 0, 255, 0, 359);\n        sat = map(sat, 0, 255, 0, 1);\n        lum = map(lum, 0, 255, 0, 1);\n        double v;\n        double red, green, blue;\n        double m;\n        double sv;\n        int sextant;\n        double fract, vsf, mid1, mid2;\n\n        red = lum;   // default to gray\n        green = lum;\n        blue = lum;\n        v = (lum <= 0.5) ? (lum * (1.0 + sat)) : (lum + sat - lum * sat);\n        m = lum + lum - v;\n        sv = (v - m) / v;\n        hue /= 60.0;  //get into range 0..6\n        sextant = (int) Math.floor(hue);  // int32 rounds up or down.\n        fract = hue - sextant;\n        vsf = v * sv * fract;\n        mid1 = m + vsf;\n        mid2 = v - vsf;\n\n        if (v > 0) {\n            switch (sextant) {\n                case 0:\n                    red = v;\n                    green = mid1;\n                    blue = m;\n                    break;\n                case 1:\n                    red = mid2;\n                    green = v;\n                    blue = m;\n                    break;\n                case 2:\n                    red = m;\n                    green = v;\n                    blue = mid1;\n                    break;\n                case 3:\n                    red = m;\n                    green = mid2;\n                    blue = v;\n                    break;\n                case 4:\n                    red = mid1;\n                    green = m;\n                    blue = v;\n                    break;\n                case 5:\n                    red = v;\n                    green = m;\n                    blue = mid2;\n                    break;\n            }\n        }\n        return new Color(red, green, blue, 1);\n    }\n\n    private double[] circleFrom3Points(Point2D a, Point2D b, Point2D c) {\n        double ax, ay, bx, by, cx, cy, x1, y11, dx1, dy1, x2, y2, dx2, dy2, ox, oy, dx, dy, radius; // Variables Used and to Declared\n        ax = a.getX();\n        ay = a.getY(); //first Point X and Y\n        bx = b.getX();\n        by = b.getY(); // Second Point X and Y\n        cx = c.getX();\n        cy = c.getY(); // Third Point X and Y\n\n        ////****************Following are Basic Procedure**********************///\n        x1 = (bx + ax) / 2;\n        y11 = (by + ay) / 2;\n        dy1 = bx - ax;\n        dx1 = -(by - ay);\n\n        x2 = (cx + bx) / 2;\n        y2 = (cy + by) / 2;\n        dy2 = cx - bx;\n        dx2 = -(cy - by);\n\n        ox = (y11 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2) / (dx1 * dy2 - dy1 * dx2);\n        oy = (ox - x1) * dy1 / dx1 + y11;\n\n        dx = ox - ax;\n        dy = oy - ay;\n        radius = Math.sqrt(dx * dx + dy * dy);\n        return new double[]{ox, oy, radius};\n    }\n\n    private Point2D getPointFromSL(int saturation, int lightness, double radius) {\n        double dy = map(saturation, 0, 255, -radius, radius);\n        double angle = 0.;\n        double angle1 = angle + 2 * Math.PI / 3.;\n        double angle2 = angle1 + 2 * Math.PI / 3.;\n        double x1 = radius * Math.sin(angle1);\n        double y1 = radius * Math.cos(angle1);\n        double x2 = radius * Math.sin(angle2);\n        double y2 = radius * Math.cos(angle2);\n        double dx = 0;\n        double[] circle = circleFrom3Points(new Point2D(x1, y1), new Point2D(dx, dy), new Point2D(x2, y2));\n        double xArc = circle[0];\n        double yArc = circle[1];\n        double arcR = circle[2];\n        double Arco1 = Math.atan2(x1 - xArc, y1 - yArc);\n        double Arco2 = Math.atan2(x2 - xArc, y2 - yArc);\n        double ArcoFinal = map(lightness, 0, 255, Arco2, Arco1);\n        double finalX = xArc + arcR * Math.sin(ArcoFinal);\n        double finalY = yArc + arcR * Math.cos(ArcoFinal);\n        if (dy < y1) {\n            ArcoFinal = map(lightness, 0, 255, Arco1, Arco2 + 2 * Math.PI);\n            finalX = -xArc - arcR * Math.sin(ArcoFinal);\n            finalY = yArc + arcR * Math.cos(ArcoFinal);\n        }\n        return new Point2D(finalX, finalY);\n    }\n\n    private Point2D rotate(Point2D a, Point2D center, double angle) {\n        double resultX = center.getX() + (a.getX() - center.getX()) * Math.cos(angle) - (a.getY() - center.getY()) * Math\n                .sin(angle);\n        double resultY = center.getY() + (a.getX() - center.getX()) * Math.sin(angle) + (a.getY() - center.getY()) * Math\n                .cos(angle);\n        return new Point2D(resultX, resultY);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPicker.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.effects.JFXDepthManager;\nimport com.jfoenix.transitions.CachedTransition;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.DoubleProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleDoubleProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.geometry.Point2D;\nimport javafx.scene.Node;\nimport javafx.scene.effect.DropShadow;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.*;\nimport javafx.scene.transform.Rotate;\nimport javafx.util.Duration;\n\nimport java.util.ArrayList;\n\nimport static javafx.animation.Interpolator.EASE_BOTH;\n\n/// @author Shadi Shaheen\nfinal class JFXCustomColorPicker extends Pane {\n\n    ObjectProperty<RecentColorPath> selectedPath = new SimpleObjectProperty<>();\n    private MoveTo startPoint;\n    private CubicCurveTo curve0To;\n    private CubicCurveTo outerCircleCurveTo;\n    private CubicCurveTo curve1To;\n    private CubicCurveTo innerCircleCurveTo;\n    private final ArrayList<CubicCurve> curves = new ArrayList<>();\n\n    private final double distance = 200;\n    private final double centerX = distance;\n    private final double centerY = distance;\n    private final double radius = 110;\n\n    private static final int shapesNumber = 13;\n    private final ArrayList<RecentColorPath> shapes = new ArrayList<>();\n    private CachedTransition showAnimation;\n    private final JFXColorPickerUI hslColorPicker;\n\n    public JFXCustomColorPicker() {\n        this.setPickOnBounds(false);\n        this.setMinSize(distance * 2, distance * 2);\n\n        final DoubleProperty rotationAngle = new SimpleDoubleProperty(2.1);\n\n        // draw recent colors shape using cubic curves\n        init(rotationAngle, centerX + 53, centerY + 162);\n\n        hslColorPicker = new JFXColorPickerUI((int) distance);\n        hslColorPicker.setLayoutX(centerX - distance / 2);\n        hslColorPicker.setLayoutY(centerY - distance / 2);\n        this.getChildren().add(hslColorPicker);\n        // add recent colors shapes\n        final int shapesStartIndex = this.getChildren().size();\n        final int shapesEndIndex = shapesStartIndex + shapesNumber;\n        for (int i = 0; i < shapesNumber; i++) {\n            final double angle = 2 * i * Math.PI / shapesNumber;\n            final RecentColorPath path = new RecentColorPath(startPoint,\n                    curve0To,\n                    outerCircleCurveTo,\n                    curve1To,\n                    innerCircleCurveTo);\n            shapes.add(path);\n            path.setPickOnBounds(false);\n            final Rotate rotate = new Rotate(Math.toDegrees(angle), centerX, centerY);\n            path.getTransforms().add(rotate);\n            this.getChildren().add(shapesStartIndex, path);\n            path.setFill(Color.valueOf(getDefaultColor(i)));\n            path.setFocusTraversable(true);\n            path.addEventHandler(MouseEvent.MOUSE_CLICKED, (event) -> {\n                path.requestFocus();\n                selectedPath.set(path);\n            });\n        }\n\n        // add selection listeners\n        selectedPath.addListener((o, oldVal, newVal) -> {\n            if (oldVal != null) {\n                hslColorPicker.removeColorSelectionNode(oldVal);\n                oldVal.playTransition(-1);\n            }\n            // re-arrange children\n            while (this.getChildren().indexOf(newVal) != shapesEndIndex - 1) {\n                final Node temp = this.getChildren().get(shapesEndIndex - 1);\n                this.getChildren().remove(shapesEndIndex - 1);\n                this.getChildren().add(shapesStartIndex, temp);\n            }\n            // update path fill according to the color picker\n            newVal.setStroke(Color.rgb(255, 255, 255, 0.87));\n            newVal.playTransition(1);\n            hslColorPicker.moveToColor((Color) newVal.getFill());\n            hslColorPicker.addColorSelectionNode(newVal);\n        });\n        // init selection\n        selectedPath.set((RecentColorPath) this.getChildren().get(shapesStartIndex));\n    }\n\n    public int getShapesNumber() {\n        return shapesNumber;\n    }\n\n    public int getSelectedIndex() {\n        if (selectedPath.get() != null) {\n            return shapes.indexOf(selectedPath.get());\n        }\n        return -1;\n    }\n\n    public void setColor(final Color color) {\n        shapes.get(getSelectedIndex()).setFill(color);\n        hslColorPicker.moveToColor(color);\n    }\n\n    public Color getColor(final int index) {\n        if (index >= 0 && index < shapes.size()) {\n            return (Color) shapes.get(index).getFill();\n        } else {\n            return Color.WHITE;\n        }\n    }\n\n    public void preAnimate() {\n        final CubicCurve firstCurve = curves.get(0);\n        final double x = firstCurve.getStartX();\n        final double y = firstCurve.getStartY();\n        firstCurve.setStartX(centerX);\n        firstCurve.setStartY(centerY);\n\n        final CubicCurve secondCurve = curves.get(1);\n        final double x1 = secondCurve.getStartX();\n        final double y1 = secondCurve.getStartY();\n        secondCurve.setStartX(centerX);\n        secondCurve.setStartY(centerY);\n\n        final double cx1 = firstCurve.getControlX1();\n        final double cy1 = firstCurve.getControlY1();\n        firstCurve.setControlX1(centerX + radius);\n        firstCurve.setControlY1(centerY + radius / 2);\n\n        final KeyFrame keyFrame = new KeyFrame(Duration.millis(1000),\n                new KeyValue(firstCurve.startXProperty(), x, EASE_BOTH),\n                new KeyValue(firstCurve.startYProperty(), y, EASE_BOTH),\n                new KeyValue(secondCurve.startXProperty(), x1, EASE_BOTH),\n                new KeyValue(secondCurve.startYProperty(), y1, EASE_BOTH),\n                new KeyValue(firstCurve.controlX1Property(), cx1, EASE_BOTH),\n                new KeyValue(firstCurve.controlY1Property(), cy1, EASE_BOTH)\n        );\n        final Timeline timeline = new Timeline(keyFrame);\n        showAnimation = new CachedTransition(this, timeline) {\n            {\n                setCycleDuration(Duration.millis(240));\n                setDelay(Duration.millis(0));\n            }\n        };\n    }\n\n    public void animate() {\n        showAnimation.play();\n    }\n\n    private void init(final DoubleProperty rotationAngle, final double initControlX1, final double initControlY1) {\n\n        final Circle innerCircle = new Circle(centerX, centerY, radius, Color.TRANSPARENT);\n        final Circle outerCircle = new Circle(centerX, centerY, radius * 2, Color.web(\"blue\", 0.5));\n\n        // Create a composite shape of 4 cubic curves\n        // create 2 cubic curves of the shape\n        createQuadraticCurve(rotationAngle, initControlX1, initControlY1);\n\n        // inner circle curve\n        final CubicCurve innerCircleCurve = new CubicCurve();\n        innerCircleCurve.startXProperty().bind(curves.get(0).startXProperty());\n        innerCircleCurve.startYProperty().bind(curves.get(0).startYProperty());\n        innerCircleCurve.endXProperty().bind(curves.get(1).startXProperty());\n        innerCircleCurve.endYProperty().bind(curves.get(1).startYProperty());\n        curves.get(0).startXProperty().addListener((o, oldVal, newVal) -> {\n            final Point2D controlPoint = makeControlPoint(newVal.doubleValue(),\n                    curves.get(0).getStartY(),\n                    innerCircle,\n                    shapesNumber,\n                    -1);\n            innerCircleCurve.setControlX1(controlPoint.getX());\n            innerCircleCurve.setControlY1(controlPoint.getY());\n        });\n        curves.get(0).startYProperty().addListener((o, oldVal, newVal) -> {\n            final Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(),\n                    newVal.doubleValue(),\n                    innerCircle,\n                    shapesNumber,\n                    -1);\n            innerCircleCurve.setControlX1(controlPoint.getX());\n            innerCircleCurve.setControlY1(controlPoint.getY());\n        });\n        curves.get(1).startXProperty().addListener((o, oldVal, newVal) -> {\n            final Point2D controlPoint = makeControlPoint(newVal.doubleValue(),\n                    curves.get(1).getStartY(),\n                    innerCircle,\n                    shapesNumber,\n                    1);\n            innerCircleCurve.setControlX2(controlPoint.getX());\n            innerCircleCurve.setControlY2(controlPoint.getY());\n        });\n        curves.get(1).startYProperty().addListener((o, oldVal, newVal) -> {\n            final Point2D controlPoint = makeControlPoint(curves.get(1).getStartX(),\n                    newVal.doubleValue(),\n                    innerCircle,\n                    shapesNumber,\n                    1);\n            innerCircleCurve.setControlX2(controlPoint.getX());\n            innerCircleCurve.setControlY2(controlPoint.getY());\n        });\n        Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(),\n                curves.get(0).getStartY(),\n                innerCircle,\n                shapesNumber,\n                -1);\n        innerCircleCurve.setControlX1(controlPoint.getX());\n        innerCircleCurve.setControlY1(controlPoint.getY());\n        controlPoint = makeControlPoint(curves.get(1).getStartX(),\n                curves.get(1).getStartY(),\n                innerCircle,\n                shapesNumber,\n                1);\n        innerCircleCurve.setControlX2(controlPoint.getX());\n        innerCircleCurve.setControlY2(controlPoint.getY());\n\n        // outer circle curve\n        final CubicCurve outerCircleCurve = new CubicCurve();\n        outerCircleCurve.startXProperty().bind(curves.get(0).endXProperty());\n        outerCircleCurve.startYProperty().bind(curves.get(0).endYProperty());\n        outerCircleCurve.endXProperty().bind(curves.get(1).endXProperty());\n        outerCircleCurve.endYProperty().bind(curves.get(1).endYProperty());\n        controlPoint = makeControlPoint(curves.get(0).getEndX(),\n                curves.get(0).getEndY(),\n                outerCircle,\n                shapesNumber,\n                -1);\n        outerCircleCurve.setControlX1(controlPoint.getX());\n        outerCircleCurve.setControlY1(controlPoint.getY());\n        controlPoint = makeControlPoint(curves.get(1).getEndX(), curves.get(1).getEndY(), outerCircle, shapesNumber, 1);\n        outerCircleCurve.setControlX2(controlPoint.getX());\n        outerCircleCurve.setControlY2(controlPoint.getY());\n\n        startPoint = new MoveTo();\n        startPoint.xProperty().bind(curves.get(0).startXProperty());\n        startPoint.yProperty().bind(curves.get(0).startYProperty());\n\n        curve0To = new CubicCurveTo();\n        curve0To.controlX1Property().bind(curves.get(0).controlX1Property());\n        curve0To.controlY1Property().bind(curves.get(0).controlY1Property());\n        curve0To.controlX2Property().bind(curves.get(0).controlX2Property());\n        curve0To.controlY2Property().bind(curves.get(0).controlY2Property());\n        curve0To.xProperty().bind(curves.get(0).endXProperty());\n        curve0To.yProperty().bind(curves.get(0).endYProperty());\n\n        outerCircleCurveTo = new CubicCurveTo();\n        outerCircleCurveTo.controlX1Property().bind(outerCircleCurve.controlX1Property());\n        outerCircleCurveTo.controlY1Property().bind(outerCircleCurve.controlY1Property());\n        outerCircleCurveTo.controlX2Property().bind(outerCircleCurve.controlX2Property());\n        outerCircleCurveTo.controlY2Property().bind(outerCircleCurve.controlY2Property());\n        outerCircleCurveTo.xProperty().bind(outerCircleCurve.endXProperty());\n        outerCircleCurveTo.yProperty().bind(outerCircleCurve.endYProperty());\n\n        curve1To = new CubicCurveTo();\n        curve1To.controlX1Property().bind(curves.get(1).controlX2Property());\n        curve1To.controlY1Property().bind(curves.get(1).controlY2Property());\n        curve1To.controlX2Property().bind(curves.get(1).controlX1Property());\n        curve1To.controlY2Property().bind(curves.get(1).controlY1Property());\n        curve1To.xProperty().bind(curves.get(1).startXProperty());\n        curve1To.yProperty().bind(curves.get(1).startYProperty());\n\n        innerCircleCurveTo = new CubicCurveTo();\n        innerCircleCurveTo.controlX1Property().bind(innerCircleCurve.controlX2Property());\n        innerCircleCurveTo.controlY1Property().bind(innerCircleCurve.controlY2Property());\n        innerCircleCurveTo.controlX2Property().bind(innerCircleCurve.controlX1Property());\n        innerCircleCurveTo.controlY2Property().bind(innerCircleCurve.controlY1Property());\n        innerCircleCurveTo.xProperty().bind(innerCircleCurve.startXProperty());\n        innerCircleCurveTo.yProperty().bind(innerCircleCurve.startYProperty());\n    }\n\n    private void createQuadraticCurve(final DoubleProperty rotationAngle, final double initControlX1, final double initControlY1) {\n        for (int i = 0; i < 2; i++) {\n\n            double angle = 2 * i * Math.PI / shapesNumber;\n            double xOffset = radius * Math.cos(angle);\n            double yOffset = radius * Math.sin(angle);\n            final double startx = centerX + xOffset;\n            final double starty = centerY + yOffset;\n\n            final double diffStartCenterX = startx - centerX;\n            final double diffStartCenterY = starty - centerY;\n            final double sinRotAngle = Math.sin(rotationAngle.get());\n            final double cosRotAngle = Math.cos(rotationAngle.get());\n            final double startXR = cosRotAngle * diffStartCenterX - sinRotAngle * diffStartCenterY + centerX;\n            final double startYR = sinRotAngle * diffStartCenterX + cosRotAngle * diffStartCenterY + centerY;\n\n            angle = 2 * i * Math.PI / shapesNumber;\n            xOffset = distance * Math.cos(angle);\n            yOffset = distance * Math.sin(angle);\n\n            final double endx = centerX + xOffset;\n            final double endy = centerY + yOffset;\n\n            final CubicCurve curvedLine = new CubicCurve();\n            curvedLine.setStartX(startXR);\n            curvedLine.setStartY(startYR);\n            curvedLine.setControlX1(startXR);\n            curvedLine.setControlY1(startYR);\n            curvedLine.setControlX2(endx);\n            curvedLine.setControlY2(endy);\n            curvedLine.setEndX(endx);\n            curvedLine.setEndY(endy);\n            curvedLine.setStroke(Color.FORESTGREEN);\n            curvedLine.setStrokeWidth(1);\n            curvedLine.setStrokeLineCap(StrokeLineCap.ROUND);\n            curvedLine.setFill(Color.TRANSPARENT);\n            curvedLine.setMouseTransparent(true);\n            rotationAngle.addListener((o, oldVal, newVal) -> {\n                final double newstartXR = ((cosRotAngle * diffStartCenterX) - (sinRotAngle * diffStartCenterY)) + centerX;\n                final double newstartYR = (sinRotAngle * diffStartCenterX) + (cosRotAngle * diffStartCenterY) + centerY;\n                curvedLine.setStartX(newstartXR);\n                curvedLine.setStartY(newstartYR);\n            });\n\n            curves.add(curvedLine);\n\n            if (i == 0) {\n                curvedLine.setControlX1(initControlX1);\n                curvedLine.setControlY1(initControlY1);\n            } else {\n                final CubicCurve firstCurve = curves.get(0);\n                final double curveTheta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber;\n\n                curvedLine.controlX1Property().bind(Bindings.createDoubleBinding(() -> {\n                    final double a = firstCurve.getControlX1() - centerX;\n                    final double b = Math.sin(curveTheta) * (firstCurve.getControlY1() - centerY);\n                    return ((Math.cos(curveTheta) * a) - b) + centerX;\n                }, firstCurve.controlX1Property(), firstCurve.controlY1Property()));\n\n                curvedLine.controlY1Property().bind(Bindings.createDoubleBinding(() -> {\n                    final double a = Math.sin(curveTheta) * (firstCurve.getControlX1() - centerX);\n                    final double b = Math.cos(curveTheta) * (firstCurve.getControlY1() - centerY);\n                    return a + b + centerY;\n                }, firstCurve.controlX1Property(), firstCurve.controlY1Property()));\n\n\n                curvedLine.controlX2Property().bind(Bindings.createDoubleBinding(() -> {\n                    final double a = firstCurve.getControlX2() - centerX;\n                    final double b = firstCurve.getControlY2() - centerY;\n                    return ((Math.cos(curveTheta) * a) - (Math.sin(curveTheta) * b)) + centerX;\n                }, firstCurve.controlX2Property(), firstCurve.controlY2Property()));\n\n                curvedLine.controlY2Property().bind(Bindings.createDoubleBinding(() -> {\n                    final double a = Math.sin(curveTheta) * (firstCurve.getControlX2() - centerX);\n                    final double b = Math.cos(curveTheta) * (firstCurve.getControlY2() - centerY);\n                    return a + b + centerY;\n                }, firstCurve.controlX2Property(), firstCurve.controlY2Property()));\n            }\n        }\n    }\n\n    private String getDefaultColor(final int i) {\n        String color = \"#FFFFFF\";\n        switch (i) {\n            case 0:\n                color = \"#8F3F7E\";\n                break;\n            case 1:\n                color = \"#B5305F\";\n                break;\n            case 2:\n                color = \"#CE584A\";\n                break;\n            case 3:\n                color = \"#DB8D5C\";\n                break;\n            case 4:\n                color = \"#DA854E\";\n                break;\n            case 5:\n                color = \"#E9AB44\";\n                break;\n            case 6:\n                color = \"#FEE435\";\n                break;\n            case 7:\n                color = \"#99C286\";\n                break;\n            case 8:\n                color = \"#01A05E\";\n                break;\n            case 9:\n                color = \"#4A8895\";\n                break;\n            case 10:\n                color = \"#16669B\";\n                break;\n            case 11:\n                color = \"#2F65A5\";\n                break;\n            case 12:\n                color = \"#4E6A9C\";\n                break;\n            default:\n                break;\n        }\n        return color;\n    }\n\n    private Point2D rotate(final Point2D a, final Point2D center, final double angle) {\n        final double resultX = center.getX() + (a.getX() - center.getX()) * Math.cos(angle) - (a.getY() - center.getY()) * Math\n                .sin(angle);\n        final double resultY = center.getY() + (a.getX() - center.getX()) * Math.sin(angle) + (a.getY() - center.getY()) * Math\n                .cos(angle);\n        return new Point2D(resultX, resultY);\n    }\n\n    private Point2D makeControlPoint(final double endX, final double endY, final Circle circle, final int numSegments, int direction) {\n        final double controlPointDistance = (4.0 / 3.0) * Math.tan(Math.PI / (2 * numSegments)) * circle.getRadius();\n        final Point2D center = new Point2D(circle.getCenterX(), circle.getCenterY());\n        final Point2D end = new Point2D(endX, endY);\n        Point2D perp = rotate(center, end, direction * Math.PI / 2.);\n        Point2D diff = perp.subtract(end);\n        diff = diff.normalize();\n        diff = scale(diff, controlPointDistance);\n        return end.add(diff);\n    }\n\n    private Point2D scale(final Point2D a, final double scale) {\n        return new Point2D(a.getX() * scale, a.getY() * scale);\n    }\n\n    final class RecentColorPath extends Path {\n        PathClickTransition transition;\n\n        RecentColorPath(final PathElement... elements) {\n            super(elements);\n            this.setStrokeLineCap(StrokeLineCap.ROUND);\n            this.setStrokeWidth(0);\n            this.setStrokeType(StrokeType.CENTERED);\n            this.setCache(true);\n            JFXDepthManager.setDepth(this, 2);\n            this.transition = new PathClickTransition(this);\n        }\n\n        void playTransition(final double rate) {\n            transition.setRate(rate);\n            transition.play();\n        }\n    }\n\n    private final class PathClickTransition extends CachedTransition {\n        PathClickTransition(final Path path) {\n            super(JFXCustomColorPicker.this, new Timeline(\n                            new KeyFrame(Duration.ZERO,\n                                    new KeyValue(((DropShadow) path.getEffect()).radiusProperty(),\n                                            JFXDepthManager.getShadowAt(2).radiusProperty().get(),\n                                            EASE_BOTH),\n                                    new KeyValue(((DropShadow) path.getEffect()).spreadProperty(),\n                                            JFXDepthManager.getShadowAt(2).spreadProperty().get(),\n                                            EASE_BOTH),\n                                    new KeyValue(((DropShadow) path.getEffect()).offsetXProperty(),\n                                            JFXDepthManager.getShadowAt(2).offsetXProperty().get(),\n                                            EASE_BOTH),\n                                    new KeyValue(((DropShadow) path.getEffect()).offsetYProperty(),\n                                            JFXDepthManager.getShadowAt(2).offsetYProperty().get(),\n                                            EASE_BOTH),\n                                    new KeyValue(path.strokeWidthProperty(), 0, EASE_BOTH)\n                            ),\n                            new KeyFrame(Duration.millis(1000),\n                                    new KeyValue(((DropShadow) path.getEffect()).radiusProperty(),\n                                            JFXDepthManager.getShadowAt(5).radiusProperty().get(),\n                                            EASE_BOTH),\n                                    new KeyValue(((DropShadow) path.getEffect()).spreadProperty(),\n                                            JFXDepthManager.getShadowAt(5).spreadProperty().get(),\n                                            EASE_BOTH),\n                                    new KeyValue(((DropShadow) path.getEffect()).offsetXProperty(),\n                                            JFXDepthManager.getShadowAt(5).offsetXProperty().get(),\n                                            EASE_BOTH),\n                                    new KeyValue(((DropShadow) path.getEffect()).offsetYProperty(),\n                                            JFXDepthManager.getShadowAt(5).offsetYProperty().get(),\n                                            EASE_BOTH),\n                                    new KeyValue(path.strokeWidthProperty(), 2, EASE_BOTH)\n                            )\n                    )\n            );\n            // reduce the number to increase the shifting , increase number to reduce shifting\n            setCycleDuration(Duration.millis(120));\n            setDelay(Duration.seconds(0));\n            setAutoReverse(false);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXCustomColorPickerDialog.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.*;\nimport com.jfoenix.svg.SVGGlyph;\nimport com.jfoenix.transitions.JFXFillTransition;\nimport javafx.animation.*;\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Rectangle2D;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Tab;\nimport javafx.scene.control.TextFormatter;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.*;\nimport javafx.scene.paint.Color;\nimport javafx.stage.*;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.setting.StyleSheets;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.regex.Pattern;\n\n/**\n * @author Shadi Shaheen\n */\npublic class JFXCustomColorPickerDialog extends StackPane {\n    private final Stage dialog = new Stage();\n    // used for concurrency control and preventing FX-thread over use\n    private final AtomicInteger concurrencyController = new AtomicInteger(-1);\n\n    private final ObjectProperty<Color> currentColorProperty = new SimpleObjectProperty<>(Color.WHITE);\n    private final ObjectProperty<Color> customColorProperty = new SimpleObjectProperty<>(Color.TRANSPARENT);\n    private Runnable onSave;\n\n    private final Scene customScene;\n    private final JFXCustomColorPicker curvedColorPicker;\n    private ParallelTransition paraTransition;\n    private final JFXDecorator pickerDecorator;\n    private boolean systemChange = false;\n    private boolean userChange = false;\n    private boolean initOnce = true;\n    private final Runnable initRun;\n\n    public JFXCustomColorPickerDialog(Window owner) {\n        getStyleClass().add(\"custom-color-dialog\");\n        if (owner != null) {\n            dialog.initOwner(owner);\n        }\n        dialog.initModality(Modality.APPLICATION_MODAL);\n        dialog.initStyle(StageStyle.TRANSPARENT);\n        dialog.setResizable(false);\n\n        // create JFX Decorator\n        pickerDecorator = new JFXDecorator(dialog, this, false, false, false);\n        pickerDecorator.setOnCloseButtonAction(this::updateColor);\n        pickerDecorator.setPickOnBounds(false);\n        customScene = new Scene(pickerDecorator, Color.TRANSPARENT);\n        StyleSheets.init(customScene);\n        curvedColorPicker = new JFXCustomColorPicker();\n\n        StackPane pane = new StackPane(curvedColorPicker);\n        pane.setPadding(new Insets(18));\n\n        VBox container = new VBox();\n        container.getChildren().add(pane);\n\n        JFXTabPane tabs = new JFXTabPane();\n\n        JFXTextField rgbField = new JFXTextField();\n        JFXTextField hsbField = new JFXTextField();\n        JFXTextField hexField = new JFXTextField();\n\n        rgbField.getStyleClass().add(\"custom-color-field\");\n        rgbField.setPromptText(\"RGB Color\");\n        rgbField.setTextFormatter(colorCharFormatter());\n        rgbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));\n\n        hsbField.getStyleClass().add(\"custom-color-field\");\n        hsbField.setPromptText(\"HSB Color\");\n        hsbField.setTextFormatter(colorCharFormatter());\n        hsbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));\n\n        hexField.getStyleClass().add(\"custom-color-field\");\n        hexField.setPromptText(\"#HEX Color\");\n        hexField.setTextFormatter(colorCharFormatter());\n        hexField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));\n\n        StackPane tabContent = new StackPane();\n        tabContent.getChildren().add(rgbField);\n        tabContent.setMinHeight(100);\n\n        Tab rgbTab = new Tab(\"RGB\");\n        rgbTab.setContent(tabContent);\n        Tab hsbTab = new Tab(\"HSB\");\n        hsbTab.setContent(hsbField);\n        Tab hexTab = new Tab(\"HEX\");\n        hexTab.setContent(hexField);\n\n        tabs.getTabs().add(hexTab);\n        tabs.getTabs().add(rgbTab);\n        tabs.getTabs().add(hsbTab);\n\n        curvedColorPicker.selectedPath.addListener((o, oldVal, newVal) -> {\n            if (paraTransition != null) {\n                paraTransition.stop();\n            }\n            Region tabsHeader = (Region) tabs.lookup(\".tab-header-background\");\n            pane.backgroundProperty().unbind();\n            tabsHeader.backgroundProperty().unbind();\n            JFXFillTransition fillTransition = new JFXFillTransition(Duration.millis(240),\n                    pane,\n                    (Color) oldVal.getFill(),\n                    (Color) newVal.getFill());\n            JFXFillTransition tabsFillTransition = new JFXFillTransition(Duration.millis(240),\n                    tabsHeader,\n                    (Color) oldVal.getFill(),\n                    (Color) newVal.getFill());\n            paraTransition = new ParallelTransition(fillTransition, tabsFillTransition);\n            paraTransition.setOnFinished((finish) -> {\n                tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(() -> {\n                    return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY));\n                }, newVal.fillProperty()));\n                pane.backgroundProperty().bind(Bindings.createObjectBinding(() -> {\n                    return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY));\n                }, newVal.fillProperty()));\n            });\n            paraTransition.play();\n        });\n\n        initRun = () -> {\n            // change tabs labels font color according to the selected color\n            pane.backgroundProperty().addListener((o, oldVal, newVal) -> {\n                if (concurrencyController.getAndSet(1) == -1) {\n                    Color fontColor = ((Color) newVal.getFills().get(0).getFill()).grayscale()\n                            .getRed() > 0.5 ? Color.valueOf(\n                            \"rgba(40, 40, 40, 0.87)\") : Color.valueOf(\"rgba(255, 255, 255, 0.87)\");\n//                    for (Node tabNode : tabs.lookupAll(\".tab\")) {\n//                        for (Node node : tabNode.lookupAll(\".tab-label\")) {\n//                            ((Label) node).setTextFill(fontColor);\n//                        }\n//                        for (Node node : tabNode.lookupAll(\".jfx-rippler\")) {\n//                            ((JFXRippler) node).setRipplerFill(fontColor);\n//                        }\n//                    }\n//                    ((Pane) tabs.lookup(\".tab-selected-line\")).setBackground(new Background(new BackgroundFill(fontColor, CornerRadii.EMPTY, Insets.EMPTY)));\n                    pickerDecorator.lookupAll(\".jfx-decorator-button\").forEach(button -> {\n                        ((JFXButton) button).setRipplerFill(fontColor);\n                        ((SVGGlyph) ((JFXButton) button).getGraphic()).setFill(fontColor);\n                    });\n\n                    Color newColor = (Color) newVal.getFills().get(0).getFill();\n                    String hex = String.format(\"#%02X%02X%02X\",\n                            (int) (newColor.getRed() * 255),\n                            (int) (newColor.getGreen() * 255),\n                            (int) (newColor.getBlue() * 255));\n                    String rgb = String.format(\"rgba(%d, %d, %d, 1)\",\n                            (int) (newColor.getRed() * 255),\n                            (int) (newColor.getGreen() * 255),\n                            (int) (newColor.getBlue() * 255));\n                    String hsb = String.format(\"hsl(%d, %d%%, %d%%)\",\n                            (int) (newColor.getHue()),\n                            (int) (newColor.getSaturation() * 100),\n                            (int) (newColor.getBrightness() * 100));\n\n                    if (!userChange) {\n                        systemChange = true;\n                        rgbField.setText(rgb);\n                        hsbField.setText(hsb);\n                        hexField.setText(hex);\n                        systemChange = false;\n                    }\n                    concurrencyController.getAndSet(-1);\n                }\n            });\n\n            // initial selected colors\n            Platform.runLater(() -> {\n                pane.setBackground(new Background(new BackgroundFill(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()),\n                        CornerRadii.EMPTY,\n                        Insets.EMPTY)));\n                ((Region) tabs.lookup(\".tab-header-background\")).setBackground(new Background(new BackgroundFill(\n                        curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()),\n                        CornerRadii.EMPTY,\n                        Insets.EMPTY)));\n                Region tabsHeader = (Region) tabs.lookup(\".tab-header-background\");\n                pane.backgroundProperty().unbind();\n                tabsHeader.backgroundProperty().unbind();\n                tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(() -> {\n                    return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(),\n                            CornerRadii.EMPTY,\n                            Insets.EMPTY));\n                }, curvedColorPicker.selectedPath.get().fillProperty()));\n                pane.backgroundProperty().bind(Bindings.createObjectBinding(() -> {\n                    return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(),\n                            CornerRadii.EMPTY,\n                            Insets.EMPTY));\n                }, curvedColorPicker.selectedPath.get().fillProperty()));\n\n                // bind text field line color\n                rgbField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {\n                    return pane.getBackground().getFills().get(0).getFill();\n                }, pane.backgroundProperty()));\n                hsbField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {\n                    return pane.getBackground().getFills().get(0).getFill();\n                }, pane.backgroundProperty()));\n                hexField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {\n                    return pane.getBackground().getFills().get(0).getFill();\n                }, pane.backgroundProperty()));\n\n\n                ((Pane) pickerDecorator.lookup(\".jfx-decorator-buttons-container\")).backgroundProperty()\n                        .bind(Bindings.createObjectBinding(() -> {\n                            return new Background(new BackgroundFill(\n                                    pane.getBackground()\n                                            .getFills()\n                                            .get(0)\n                                            .getFill(),\n                                    CornerRadii.EMPTY,\n                                    Insets.EMPTY));\n                        }, pane.backgroundProperty()));\n\n                ((Pane) pickerDecorator.lookup(\".jfx-decorator-content-container\")).borderProperty()\n                        .bind(Bindings.createObjectBinding(() -> {\n                            return new Border(new BorderStroke(\n                                    pane.getBackground()\n                                            .getFills()\n                                            .get(0)\n                                            .getFill(),\n                                    BorderStrokeStyle.SOLID,\n                                    CornerRadii.EMPTY,\n                                    new BorderWidths(0,\n                                            4,\n                                            4,\n                                            4)));\n                        }, pane.backgroundProperty()));\n            });\n        };\n\n\n        container.getChildren().add(tabs);\n\n        this.getChildren().add(container);\n        this.setPadding(new Insets(0));\n\n        dialog.setScene(customScene);\n        final EventHandler<KeyEvent> keyEventListener = key -> {\n            switch (key.getCode()) {\n                case ESCAPE:\n                    close();\n                    break;\n                case ENTER:\n                    updateColor();\n                    break;\n                default:\n                    break;\n            }\n        };\n        dialog.addEventHandler(KeyEvent.ANY, keyEventListener);\n    }\n\n    private void updateColor() {\n        close();\n        this.customColorProperty.set(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()));\n        this.onSave.run();\n    }\n\n    private void updateColorFromUserInput(String colorWebString) {\n        if (!systemChange) {\n            userChange = true;\n            try {\n                curvedColorPicker.setColor(Color.valueOf(colorWebString));\n            } catch (IllegalArgumentException ignored) {\n                // if color is not valid then do nothing\n            }\n            userChange = false;\n        }\n    }\n\n    private void close() {\n        dialog.setScene(null);\n        dialog.close();\n    }\n\n    public void setCurrentColor(Color currentColor) {\n        this.currentColorProperty.set(currentColor);\n        if (curvedColorPicker != null && currentColor != null) {\n            curvedColorPicker.setColor(currentColor);\n        }\n    }\n\n    Color getCurrentColor() {\n        return currentColorProperty.get();\n    }\n\n    ObjectProperty<Color> customColorProperty() {\n        return customColorProperty;\n    }\n\n    void setCustomColor(Color color) {\n        customColorProperty.set(color);\n    }\n\n    Color getCustomColor() {\n        return customColorProperty.get();\n    }\n\n    public Runnable getOnSave() {\n        return onSave;\n    }\n\n    public void setOnSave(Runnable onSave) {\n        this.onSave = onSave;\n    }\n\n    public void setOnHidden(EventHandler<WindowEvent> onHidden) {\n        dialog.setOnHidden(onHidden);\n    }\n\n    public void show() {\n        dialog.setOpacity(0);\n        // pickerDecorator.setOpacity(0);\n        if (dialog.getOwner() != null) {\n            dialog.widthProperty().addListener(positionAdjuster);\n            dialog.heightProperty().addListener(positionAdjuster);\n            positionAdjuster.invalidated(null);\n        }\n        if (dialog.getScene() == null) {\n            dialog.setScene(customScene);\n        }\n        curvedColorPicker.preAnimate();\n        dialog.show();\n        if (initOnce) {\n            initRun.run();\n            initOnce = false;\n        }\n\n        Timeline timeline = new Timeline(new KeyFrame(Duration.millis(120),\n                new KeyValue(dialog.opacityProperty(),\n                        1,\n                        Interpolator.EASE_BOTH)));\n        timeline.setOnFinished((finish) -> curvedColorPicker.animate());\n        timeline.play();\n    }\n\n\n    // add option to show color picker using JFX Dialog\n    private InvalidationListener positionAdjuster = new InvalidationListener() {\n        @Override\n        public void invalidated(Observable ignored) {\n            if (Double.isNaN(dialog.getWidth()) || Double.isNaN(dialog.getHeight())) {\n                return;\n            }\n            dialog.widthProperty().removeListener(positionAdjuster);\n            dialog.heightProperty().removeListener(positionAdjuster);\n            fixPosition();\n        }\n    };\n\n    private void fixPosition() {\n        Window w = dialog.getOwner();\n        Screen s = com.sun.javafx.util.Utils.getScreen(w);\n        Rectangle2D sb = s.getBounds();\n        double xR = w.getX() + w.getWidth();\n        double xL = w.getX() - dialog.getWidth();\n        double x;\n        double y;\n        if (sb.getMaxX() >= xR + dialog.getWidth()) {\n            x = xR;\n        } else if (sb.getMinX() <= xL) {\n            x = xL;\n        } else {\n            x = Math.max(sb.getMinX(), sb.getMaxX() - dialog.getWidth());\n        }\n        y = Math.max(sb.getMinY(), Math.min(sb.getMaxY() - dialog.getHeight(), w.getY()));\n        dialog.setX(x);\n        dialog.setY(y);\n    }\n\n    @Override\n    public void layoutChildren() {\n        super.layoutChildren();\n        if (dialog.getMinWidth() > 0 && dialog.getMinHeight() > 0) {\n            return;\n        }\n        double minWidth = Math.max(0, computeMinWidth(getHeight()) + (dialog.getWidth() - customScene.getWidth()));\n        double minHeight = Math.max(0, computeMinHeight(getWidth()) + (dialog.getHeight() - customScene.getHeight()));\n        dialog.setMinWidth(minWidth);\n        dialog.setMinHeight(minHeight);\n    }\n\n    private static final Pattern COLOR_CHAR_PATTERN = Pattern.compile(\"[0-9a-zA-Z#(),%.\\\\s]*\");\n\n    private static TextFormatter<String> colorCharFormatter() {\n        return new TextFormatter<>(change -> {\n            if (!change.isContentChange()) return change;\n\n            String ins = StringUtils.toHalfWidth(change.getText());\n            if (!COLOR_CHAR_PATTERN.matcher(ins).matches()) return null;\n            String full = StringUtils.toHalfWidth(change.getControlNewText());\n            long h = full.chars().filter(c -> c == '#').count();\n            if (h > 1 || (h == 1 && full.indexOf('#') != 0)) return null;\n            change.setText(ins);\n            return change;\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXGenericPickerSkin.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.adapters.ReflectionHelper;\nimport com.jfoenix.controls.behavior.JFXGenericPickerBehavior;\nimport com.sun.javafx.binding.ExpressionHelper;\nimport com.sun.javafx.event.EventHandlerManager;\nimport com.sun.javafx.stage.WindowEventDispatcher;\nimport javafx.beans.property.ReadOnlyBooleanProperty;\nimport javafx.beans.property.ReadOnlyBooleanPropertyBase;\nimport javafx.beans.value.ChangeListener;\nimport javafx.event.EventHandler;\nimport javafx.event.EventType;\nimport javafx.scene.control.ComboBoxBase;\nimport javafx.scene.control.PopupControl;\nimport javafx.scene.control.TextField;\nimport javafx.scene.control.skin.ComboBoxBaseSkin;\nimport javafx.scene.control.skin.ComboBoxPopupControl;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.Pane;\nimport javafx.stage.Window;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.BiConsumer;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic abstract class JFXGenericPickerSkin<T> extends ComboBoxPopupControl<T> {\n\n    private final EventHandler<MouseEvent> mouseEnteredEventHandler;\n    private final EventHandler<MouseEvent> mousePressedEventHandler;\n    private final EventHandler<MouseEvent> mouseReleasedEventHandler;\n    private final EventHandler<MouseEvent> mouseExitedEventHandler;\n\n    protected JFXGenericPickerBehavior<T> behavior;\n\n    // reference of the arrow button node in getChildren (not the actual field)\n    protected Pane arrowButton;\n    protected PopupControl popup;\n\n    public JFXGenericPickerSkin(ComboBoxBase<T> comboBoxBase) {\n        super(comboBoxBase);\n        behavior = new JFXGenericPickerBehavior<T>(comboBoxBase);\n\n        removeParentFakeFocusListener(comboBoxBase);\n\n        this.mouseEnteredEventHandler = event -> behavior.mouseEntered(event);\n        this.mousePressedEventHandler = event -> {\n            behavior.mousePressed(event);\n            event.consume();\n        };\n        this.mouseReleasedEventHandler = event -> {\n            behavior.mouseReleased(event);\n            event.consume();\n        };\n        this.mouseExitedEventHandler = event -> behavior.mouseExited(event);\n\n        arrowButton = (Pane) getChildren().get(0);\n\n        parentArrowEventHandlerTerminator.accept(\"mouseEnteredEventHandler\", MouseEvent.MOUSE_ENTERED);\n        parentArrowEventHandlerTerminator.accept(\"mousePressedEventHandler\", MouseEvent.MOUSE_PRESSED);\n        parentArrowEventHandlerTerminator.accept(\"mouseReleasedEventHandler\", MouseEvent.MOUSE_RELEASED);\n        parentArrowEventHandlerTerminator.accept(\"mouseExitedEventHandler\", MouseEvent.MOUSE_EXITED);\n        this.unregisterChangeListeners(comboBoxBase.editableProperty());\n\n        updateArrowButtonListeners();\n        registerChangeListener(comboBoxBase.editableProperty(), obs -> {\n            updateArrowButtonListeners();\n            reflectUpdateDisplayArea();\n        });\n\n        removeParentPopupHandlers();\n\n        popup = ReflectionHelper.getFieldContent(ComboBoxPopupControl.class, this, \"popup\");\n    }\n\n    @Override\n    public void dispose() {\n        super.dispose();\n        if (this.behavior != null) {\n            this.behavior.dispose();\n        }\n    }\n\n\n    /***************************************************************************\n     *                                                                         *\n     * Reflections internal API                                                *\n     *                                                                         *\n     **************************************************************************/\n\n    private final BiConsumer<String, EventType<?>> parentArrowEventHandlerTerminator = (handlerName, eventType) -> {\n        try {\n            EventHandler handler = ReflectionHelper.getFieldContent(ComboBoxBaseSkin.class, this, handlerName);\n            arrowButton.removeEventHandler(eventType, handler);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    };\n\n    private static final VarHandle READ_ONLY_BOOLEAN_PROPERTY_BASE_HELPER =\n            findVarHandle(ReadOnlyBooleanPropertyBase.class, \"helper\", ExpressionHelper.class);\n\n    /// @author Glavo\n    private static VarHandle findVarHandle(Class<?> targetClass, String fieldName, Class<?> type) {\n        try {\n            return MethodHandles.privateLookupIn(targetClass, MethodHandles.lookup()).findVarHandle(targetClass, fieldName, type);\n        } catch (NoSuchFieldException | IllegalAccessException e) {\n            LOG.warning(\"Failed to get var handle\", e);\n            return null;\n        }\n    }\n\n    private void removeParentFakeFocusListener(ComboBoxBase<T> comboBoxBase) {\n        // handle FakeFocusField cast exception\n        try {\n            final ReadOnlyBooleanProperty focusedProperty = comboBoxBase.focusedProperty();\n            //noinspection unchecked\n            ExpressionHelper<Boolean> value = (ExpressionHelper<Boolean>) READ_ONLY_BOOLEAN_PROPERTY_BASE_HELPER.get(focusedProperty);\n            ChangeListener<? super Boolean>[] changeListeners = ReflectionHelper.getFieldContent(value.getClass(), value, \"changeListeners\");\n            // remove parent focus listener to prevent editor class cast exception\n            for (int i = changeListeners.length - 1; i > 0; i--) {\n                if (changeListeners[i] != null && changeListeners[i].getClass().getName().contains(\"ComboBoxPopupControl\")) {\n                    focusedProperty.removeListener(changeListeners[i]);\n                    break;\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    private void removeParentPopupHandlers() {\n        try {\n            PopupControl popup = ReflectionHelper.invoke(ComboBoxPopupControl.class, this, \"getPopup\");\n            popup.setOnAutoHide(event -> behavior.onAutoHide(popup));\n            WindowEventDispatcher dispatcher = ReflectionHelper.invoke(Window.class, popup, \"getInternalEventDispatcher\");\n            Map compositeEventHandlersMap = ReflectionHelper.getFieldContent(EventHandlerManager.class, dispatcher.getEventHandlerManager(), \"eventHandlerMap\");\n            compositeEventHandlersMap.remove(MouseEvent.MOUSE_CLICKED);\n//            CompositeEventHandler compositeEventHandler = (CompositeEventHandler) compositeEventHandlersMap.get(MouseEvent.MOUSE_CLICKED);\n//            Object obj = fieldConsumer.apply(()->CompositeEventHandler.class.getDeclaredField(\"firstRecord\"),compositeEventHandler);\n//            EventHandler handler = (EventHandler) fieldConsumer.apply(() -> obj.getClass().getDeclaredField(\"eventHandler\"), obj);\n//            popup.removeEventHandler(MouseEvent.MOUSE_CLICKED, handler);\n            popup.addEventHandler(MouseEvent.MOUSE_CLICKED, click -> behavior.onAutoHide(popup));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    private void updateArrowButtonListeners() {\n        if (getSkinnable().isEditable()) {\n            arrowButton.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredEventHandler);\n            arrowButton.addEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedEventHandler);\n            arrowButton.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedEventHandler);\n            arrowButton.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedEventHandler);\n        } else {\n            arrowButton.removeEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredEventHandler);\n            arrowButton.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedEventHandler);\n            arrowButton.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedEventHandler);\n            arrowButton.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedEventHandler);\n        }\n    }\n\n\n    /***************************************************************************\n     *                                                                         *\n     * Reflections internal API for ComboBoxPopupControl                       *\n     *                                                                         *\n     **************************************************************************/\n\n    private final HashMap<String, Method> parentCachedMethods = new HashMap<>();\n\n    Function<String, Method> methodSupplier = name -> {\n        if (!parentCachedMethods.containsKey(name)) {\n            try {\n                Method method = ComboBoxPopupControl.class.getDeclaredMethod(name);\n                method.setAccessible(true);\n                parentCachedMethods.put(name, method);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n        return parentCachedMethods.get(name);\n    };\n\n    final Consumer<Method> methodInvoker = method -> {\n        try {\n            method.invoke(this);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    };\n\n    final Function<Method, Object> methodReturnInvoker = method -> {\n        try {\n            return method.invoke(this);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    };\n\n    protected void reflectUpdateDisplayArea() {\n        methodInvoker.accept(methodSupplier.apply(\"updateDisplayArea\"));\n    }\n\n    protected void reflectSetTextFromTextFieldIntoComboBoxValue() {\n        methodInvoker.accept(methodSupplier.apply(\"setTextFromTextFieldIntoComboBoxValue\"));\n    }\n\n    protected TextField reflectGetEditableInputNode() {\n        return (TextField) methodReturnInvoker.apply(methodSupplier.apply(\"getEditableInputNode\"));\n    }\n\n    protected void reflectUpdateDisplayNode() {\n        methodInvoker.accept(methodSupplier.apply(\"updateDisplayNode\"));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXListViewSkin.java",
    "content": "// Copy from https://github.com/sshahine/JFoenix/blob/d427fd801a338f934307ba41ce604eb5c79f0b20/jfoenix/src/main/java/com/jfoenix/skins/JFXListViewSkin.java\n\n/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXListView;\nimport com.jfoenix.effects.JFXDepthManager;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.skin.ListViewSkin;\nimport javafx.scene.control.skin.VirtualFlow;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic class JFXListViewSkin<T> extends ListViewSkin<T> {\n\n    public JFXListViewSkin(final JFXListView<T> listView) {\n        super(listView);\n        VirtualFlow<ListCell<T>> flow = getVirtualFlow();\n        FXUtils.onChangeAndOperate(listView.depthProperty(), depth -> JFXDepthManager.setDepth(flow, depth.intValue()));\n\n        if (!Boolean.TRUE.equals(listView.getProperties().get(\"no-smooth-scrolling\"))) {\n            FXUtils.smoothScrolling(flow);\n        }\n    }\n\n    @Override\n    protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        return 200;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java",
    "content": "/*\n * Copyright (c) 2016 JFoenix\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so,\n * subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXPopup;\nimport com.jfoenix.controls.JFXPopup.PopupHPosition;\nimport com.jfoenix.controls.JFXPopup.PopupVPosition;\nimport com.jfoenix.effects.JFXDepthManager;\nimport javafx.animation.*;\nimport javafx.animation.Animation.Status;\nimport javafx.scene.Node;\nimport javafx.scene.control.Skin;\nimport javafx.scene.layout.*;\nimport javafx.scene.transform.Scale;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.ui.animation.Motion;\n\n/// # Material Design Popup Skin\n/// TODO: REWORK\n///\n/// @author Shadi Shaheen\n/// @version 2.0\n/// @since 2017-03-01\npublic class JFXPopupSkin implements Skin<JFXPopup> {\n\n    protected JFXPopup control;\n    protected StackPane container = new StackPane();\n    protected Region popupContent;\n    protected Node root;\n\n    private Animation animation;\n    protected Scale scale;\n\n    public JFXPopupSkin(JFXPopup control) {\n        this.control = control;\n        // set scale y to 0.01 instead of 0 to allow layout of the content,\n        // otherwise it will cause exception in traverse engine, when focusing the 1st node\n        scale = new Scale(1.0, 0.01, 0, 0);\n        popupContent = control.getPopupContent();\n        container.getStyleClass().add(\"jfx-popup-container\");\n        container.getChildren().add(popupContent);\n        container.getTransforms().add(scale);\n        container.setOpacity(0);\n        root = JFXDepthManager.createMaterialNode(container, 4);\n        animation = AnimationUtils.isAnimationEnabled() ? getAnimation() : null;\n    }\n\n    public void reset(PopupVPosition vAlign, PopupHPosition hAlign, double offsetX, double offsetY) {\n        // postion the popup according to its animation\n        scale.setPivotX(hAlign == PopupHPosition.RIGHT ? container.getWidth() : 0);\n        scale.setPivotY(vAlign == PopupVPosition.BOTTOM ? container.getHeight() : 0);\n        control.setX(control.getX() + (hAlign == PopupHPosition.RIGHT ? -container.getWidth() + offsetX : offsetX));\n        control.setY(control.getY() + (vAlign == PopupVPosition.BOTTOM ? -container.getHeight() + offsetY : offsetY));\n    }\n\n    public final void animate() {\n        if (animation != null) {\n            if (animation.getStatus() == Status.STOPPED) {\n                container.setOpacity(1);\n                animation.playFromStart();\n            }\n        } else {\n            container.setOpacity(1);\n            popupContent.setOpacity(1);\n            scale.setX(1.0);\n            scale.setY(1.0);\n        }\n    }\n\n    @Override\n    public JFXPopup getSkinnable() {\n        return control;\n    }\n\n    @Override\n    public Node getNode() {\n        return root;\n    }\n\n    @Override\n    public void dispose() {\n        if (animation != null) {\n            animation.stop();\n            animation = null;\n        }\n        container = null;\n        control = null;\n        popupContent = null;\n        root = null;\n    }\n\n    protected Animation getAnimation() {\n        Interpolator interpolator = Motion.EASE;\n        return new Timeline(\n                new KeyFrame(\n                        Duration.ZERO,\n                        new KeyValue(popupContent.opacityProperty(), 0, interpolator),\n                        new KeyValue(scale.xProperty(), 0, interpolator),\n                        new KeyValue(scale.yProperty(), 0, interpolator)\n                ),\n                new KeyFrame(Motion.SHORT4,\n                        new KeyValue(popupContent.opacityProperty(), 0, interpolator),\n                        new KeyValue(scale.xProperty(), 1, interpolator)\n                ),\n                new KeyFrame(Motion.MEDIUM2,\n                        new KeyValue(popupContent.opacityProperty(), 1, interpolator),\n                        new KeyValue(scale.yProperty(), 1, interpolator)\n                )\n        );\n    }\n\n    public void init() {\n        if (animation != null)\n            animation.stop();\n        container.setOpacity(0);\n        scale.setX(1.0);\n        scale.setY(0.01);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXProgressBarSkin.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXProgressBar;\nimport com.jfoenix.utils.TreeShowingProperty;\nimport javafx.animation.*;\nimport javafx.scene.Node;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.*;\nimport javafx.scene.shape.Rectangle;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\n\n/// # Material Design ProgressBar Skin\n///\n/// @author Shadi Shaheen\n/// @version 2.0\n/// @since 2017-10-06\npublic class JFXProgressBarSkin extends SkinBase<JFXProgressBar> {\n\n    private static final double DEFAULT_HEIGHT = 4;\n\n    private final StackPane track;\n    private final StackPane bar;\n    private final Rectangle clip;\n    private Animation transition;\n    private final TreeShowingProperty treeShowingProperty;\n    private double fullWidth;\n\n    public JFXProgressBarSkin(JFXProgressBar control) {\n        super(control);\n\n        this.treeShowingProperty = new TreeShowingProperty(control);\n\n        registerChangeListener(treeShowingProperty, obs -> updateProgress(false));\n        registerChangeListener(control.progressProperty(), obs -> updateProgress(true));\n\n        track = new StackPane();\n        track.getStyleClass().setAll(\"track\");\n\n        bar = new StackPane();\n        bar.getStyleClass().setAll(\"bar\");\n\n        clip = new Rectangle();\n        clip.setArcWidth(DEFAULT_HEIGHT);\n        clip.setArcHeight(DEFAULT_HEIGHT);\n        bar.setClip(clip);\n\n        getChildren().setAll(track, bar);\n\n        getSkinnable().requestLayout();\n    }\n\n    @Override\n    public double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {\n        return Node.BASELINE_OFFSET_SAME_AS_HEIGHT;\n    }\n\n    @Override\n    protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        return Math.max(100, leftInset + bar.prefWidth(getSkinnable().getWidth()) + rightInset);\n    }\n\n    @Override\n    protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {\n        return topInset + DEFAULT_HEIGHT + bottomInset;\n    }\n\n    @Override\n    protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        return getSkinnable().prefWidth(height);\n    }\n\n    @Override\n    protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {\n        return getSkinnable().prefHeight(width);\n    }\n\n    @Override\n    protected void layoutChildren(double x, double y, double w, double h) {\n        track.resizeRelocate(x, y, w, h);\n        bar.resizeRelocate(x, y, w, h);\n\n        clip.relocate(0, 0);\n        clip.setWidth(0);\n        clip.setHeight(h);\n        clip.setTranslateX(0);\n\n        fullWidth = w;\n\n        clearAnimation();\n        updateProgress(false);\n    }\n\n    private boolean wasIndeterminate = false;\n\n    private void updateProgress(boolean playProgressAnimation) {\n        double progress = Math.min(getSkinnable().getProgress(), 1.0);\n        boolean isIndeterminate = progress < 0.0;\n        boolean isTreeShowing = treeShowingProperty.get();\n\n        if (isIndeterminate != wasIndeterminate) {\n            wasIndeterminate = isIndeterminate;\n            clearAnimation();\n            clip.setTranslateX(0);\n        }\n\n        if (isIndeterminate) { // indeterminate\n            if (isTreeShowing) {\n                if (transition == null) {\n                    transition = createIndeterminateTransition();\n                    transition.playFromStart();\n                } else {\n                    transition.play();\n                }\n            } else if (transition != null) {\n                transition.pause();\n            }\n        } else { // determinate\n            clearAnimation();\n            if (isTreeShowing && playProgressAnimation\n                    && AnimationUtils.isAnimationEnabled()\n                    && getSkinnable().isSmoothProgress()) {\n                transition = createDeterminateTransition(progress);\n                transition.playFromStart();\n            } else {\n                clip.setWidth(computeBarWidth(progress));\n            }\n        }\n    }\n\n    private static final Duration INDETERMINATE_DURATION = Duration.seconds(1);\n\n    private Transition createIndeterminateTransition() {\n        double minWidth = 0;\n        double maxWidth = fullWidth * 0.4;\n        Transition transition = new Transition() {\n            {\n                setInterpolator(Interpolator.LINEAR);\n                setCycleDuration(INDETERMINATE_DURATION);\n            }\n\n            @Override\n            protected void interpolate(double frac) {\n                double currentWidth;\n\n                if (frac <= 0.5) {\n                    currentWidth = Interpolator.EASE_IN.interpolate(minWidth, maxWidth, frac / 0.5);\n                } else {\n                    currentWidth = Interpolator.EASE_OUT.interpolate(maxWidth, minWidth, (frac - 0.5) / 0.5);\n                }\n\n                double targetCenter;\n                if (frac <= 0.1) {\n                    targetCenter = 0.0;\n                } else if (frac >= 0.9) {\n                    targetCenter = fullWidth;\n                } else {\n                    targetCenter = ((frac - 0.1) / 0.8) * fullWidth;\n                }\n\n                clip.setWidth(currentWidth);\n                clip.setTranslateX(targetCenter - currentWidth / 2.0);\n            }\n        };\n\n        transition.setCycleCount(Timeline.INDEFINITE);\n        return transition;\n    }\n\n    private static final Duration DETERMINATE_DURATION = Duration.seconds(0.2);\n\n    private Timeline createDeterminateTransition(double targetProgress) {\n        Timeline timeline = new Timeline(\n                new KeyFrame(Duration.ZERO,\n                        new KeyValue(clip.widthProperty(), clip.getWidth(), Interpolator.EASE_OUT)),\n                new KeyFrame(DETERMINATE_DURATION,\n                        new KeyValue(clip.widthProperty(), computeBarWidth(targetProgress), Interpolator.EASE_OUT))\n        );\n        timeline.setOnFinished(e -> {\n            if (transition == timeline) {\n                transition = null;\n            }\n        });\n        return timeline;\n    }\n\n    private void clearAnimation() {\n        if (transition != null) {\n            transition.stop();\n            transition = null;\n        }\n    }\n\n    @Override\n    public void dispose() {\n        super.dispose();\n        treeShowingProperty.dispose();\n        clearAnimation();\n    }\n\n    private double computeBarWidth(double progress) {\n        assert progress >= 0 && progress <= 1;\n        double barWidth = ((int) fullWidth * 2 * progress) / 2.0;\n        return progress > 0 ? Math.max(barWidth, DEFAULT_HEIGHT) : barWidth;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXRadioButtonSkin.java",
    "content": "//\n// Source code recreated from a .class file by IntelliJ IDEA\n// (powered by FernFlower decompiler)\n//\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXRadioButton;\nimport com.jfoenix.controls.JFXRippler;\nimport com.jfoenix.controls.JFXRippler.RipplerMask;\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.beans.property.ReadOnlyBooleanProperty;\nimport javafx.geometry.HPos;\nimport javafx.geometry.Insets;\nimport javafx.geometry.VPos;\nimport javafx.scene.control.RadioButton;\nimport javafx.scene.control.skin.RadioButtonSkin;\nimport javafx.scene.layout.AnchorPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Circle;\nimport javafx.scene.text.Text;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\n\npublic class JFXRadioButtonSkin extends RadioButtonSkin {\n    private static final double PADDING = 15.0;\n\n    private boolean invalid = true;\n    private final JFXRippler rippler;\n    private final Circle radio;\n    private final Circle dot;\n    private Timeline timeline;\n    private final AnchorPane container = new AnchorPane();\n    private final double labelOffset = -10.0;\n\n    public JFXRadioButtonSkin(JFXRadioButton control) {\n        super(control);\n        double radioRadius = 7.0;\n        this.radio = new Circle(radioRadius);\n        this.radio.getStyleClass().setAll(\"radio\");\n        this.radio.setStrokeWidth(2.0);\n        this.radio.setFill(Color.TRANSPARENT);\n\n        this.dot = new Circle(4);\n        this.dot.getStyleClass().setAll(\"dot\");\n        this.dot.fillProperty().bind(control.selectedColorProperty());\n        this.dot.setScaleX(0.0);\n        this.dot.setScaleY(0.0);\n\n        StackPane boxContainer = new StackPane();\n        boxContainer.getChildren().addAll(this.radio, this.dot);\n        boxContainer.setPadding(new Insets(PADDING));\n        this.rippler = new JFXRippler(boxContainer, RipplerMask.CIRCLE);\n        this.container.getChildren().add(this.rippler);\n        AnchorPane.setRightAnchor(this.rippler, this.labelOffset);\n\n        this.updateChildren();\n        ReadOnlyBooleanProperty focusVisibleProperty = FXUtils.focusVisibleProperty(control);\n        if (focusVisibleProperty == null) {\n            focusVisibleProperty = control.focusedProperty();\n        }\n\n        focusVisibleProperty.addListener((o, oldVal, newVal) -> {\n            if (newVal) {\n                if (!this.getSkinnable().isPressed()) {\n                    this.rippler.showOverlay();\n                }\n            } else {\n                this.rippler.hideOverlay();\n            }\n        });\n        control.pressedProperty().addListener((o, oldVal, newVal) -> this.rippler.hideOverlay());\n        this.registerChangeListener(control.selectedColorProperty(), ignored -> updateColors());\n        this.registerChangeListener(control.unSelectedColorProperty(), ignored -> updateColors());\n        this.registerChangeListener(control.selectedProperty(), ignored -> {\n            updateColors();\n            this.playAnimation();\n        });\n    }\n\n    protected void updateChildren() {\n        super.updateChildren();\n        if (this.radio != null) {\n            this.removeRadio();\n            this.getChildren().add(this.container);\n        }\n    }\n\n    protected void layoutChildren(double x, double y, double w, double h) {\n        RadioButton radioButton = this.getSkinnable();\n        double contWidth = this.snapSizeX(this.container.prefWidth(-1.0)) + (double) (this.invalid ? 2 : 0);\n        double contHeight = this.snapSizeY(this.container.prefHeight(-1.0)) + (double) (this.invalid ? 2 : 0);\n        double computeWidth = Math.min(radioButton.prefWidth(-1.0), radioButton.minWidth(-1.0)) + this.labelOffset + 2.0 * this.PADDING;\n        double labelWidth = Math.min(computeWidth - contWidth, w - this.snapSizeX(contWidth)) + this.labelOffset + 2.0 * PADDING;\n        double labelHeight = Math.min(radioButton.prefHeight(labelWidth), h);\n        double maxHeight = Math.max(contHeight, labelHeight);\n        double xOffset = computeXOffset(w, labelWidth + contWidth, radioButton.getAlignment().getHpos()) + x;\n        double yOffset = computeYOffset(h, maxHeight, radioButton.getAlignment().getVpos()) + x;\n        if (this.invalid) {\n            this.initializeComponents();\n            this.invalid = false;\n        }\n\n        this.layoutLabelInArea(xOffset + contWidth, yOffset, labelWidth, maxHeight, radioButton.getAlignment());\n        ((Text) this.getChildren().get(this.getChildren().get(0) instanceof Text ? 0 : 1)).textProperty().set(this.getSkinnable().textProperty().get());\n        this.container.resize(this.snapSizeX(contWidth), this.snapSizeY(contHeight));\n        this.positionInArea(this.container, xOffset, yOffset, contWidth, maxHeight, 0.0, radioButton.getAlignment().getHpos(), radioButton.getAlignment().getVpos());\n    }\n\n    private void initializeComponents() {\n        this.updateColors();\n        this.playAnimation();\n    }\n\n    private void playAnimation() {\n        if (AnimationUtils.isAnimationEnabled()) {\n            if (this.timeline == null) {\n                this.timeline = new Timeline(\n                        new KeyFrame(Duration.ZERO,\n                                new KeyValue(this.dot.scaleXProperty(), 0, Interpolator.EASE_BOTH),\n                                new KeyValue(this.dot.scaleYProperty(), 0, Interpolator.EASE_BOTH)),\n                        new KeyFrame(Duration.millis(200.0),\n                                new KeyValue(this.dot.scaleXProperty(), 1, Interpolator.EASE_BOTH),\n                                new KeyValue(this.dot.scaleYProperty(), 1, Interpolator.EASE_BOTH))\n                );\n            } else {\n                this.timeline.stop();\n            }\n            this.timeline.setRate(this.getSkinnable().isSelected() ? 1.0 : -1.0);\n            this.timeline.play();\n        } else {\n            double endScale = this.getSkinnable().isSelected() ? 1.0 : 0.0;\n            this.dot.setScaleX(endScale);\n            this.dot.setScaleY(endScale);\n        }\n    }\n\n    private void removeRadio() {\n        this.getChildren().removeIf(node -> \"radio\".equals(node.getStyleClass().get(0)));\n    }\n\n    private void updateColors() {\n        var control = (JFXRadioButton) getSkinnable();\n        boolean isSelected = control.isSelected();\n        Color unSelectedColor = control.getUnSelectedColor();\n        Color selectedColor = control.getSelectedColor();\n        rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor);\n        radio.setStroke(isSelected ? selectedColor : unSelectedColor);\n    }\n\n    protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.radio.minWidth(-1.0)) + this.labelOffset + 2.0 * PADDING;\n    }\n\n    protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.radio.prefWidth(-1.0)) + this.labelOffset + 2.0 * PADDING;\n    }\n\n    static double computeXOffset(double width, double contentWidth, HPos hpos) {\n        return switch (hpos) {\n            case LEFT -> 0.0;\n            case CENTER -> (width - contentWidth) / 2.0;\n            case RIGHT -> width - contentWidth;\n        };\n    }\n\n    static double computeYOffset(double height, double contentHeight, VPos vpos) {\n        return switch (vpos) {\n            case TOP, BASELINE -> 0.0;\n            case CENTER -> (height - contentHeight) / 2.0;\n            case BOTTOM -> height - contentHeight;\n        };\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXSliderSkin.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXSlider;\nimport com.jfoenix.controls.JFXSlider.IndicatorPosition;\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.DoubleProperty;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Orientation;\nimport javafx.scene.Node;\nimport javafx.scene.chart.NumberAxis;\nimport javafx.scene.control.Slider;\nimport javafx.scene.control.skin.SliderSkin;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.text.Text;\nimport javafx.util.Duration;\n\n/// # Material Design Slider Skin\n///\n/// rework of JFXSliderSkin by extending Java SliderSkin\n/// this solves padding and resizing issues\n///\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2016-03-09\npublic class JFXSliderSkin extends SliderSkin {\n\n    private static final PseudoClass MIN_VALUE = PseudoClass.getPseudoClass(\"min\");\n    private static final PseudoClass MAX_VALUE = PseudoClass.getPseudoClass(\"max\");\n\n    private final Pane mouseHandlerPane = new Pane();\n    private final Text sliderValue;\n    private final StackPane coloredTrack;\n    private final StackPane thumb;\n    private final StackPane track;\n    private final StackPane animatedThumb;\n    private NumberAxis tickLine;\n\n    private Timeline timeline;\n\n    private double indicatorRotation;\n    private double horizontalRotation;\n    private double shifting;\n\n    public JFXSliderSkin(JFXSlider slider) {\n        super(slider);\n\n        track = (StackPane) getSkinnable().lookup(\".track\");\n        thumb = (StackPane) getSkinnable().lookup(\".thumb\");\n        tickLine = (NumberAxis) getSkinnable().lookup(\".axis\");\n        if (tickLine != null) tickLine.setAnimated(false);\n\n        coloredTrack = new StackPane();\n        coloredTrack.getStyleClass().add(\"colored-track\");\n        coloredTrack.setMouseTransparent(true);\n\n        sliderValue = new Text();\n        sliderValue.getStyleClass().setAll(\"slider-value\");\n\n        animatedThumb = new StackPane();\n        animatedThumb.getStyleClass().add(\"animated-thumb\");\n        animatedThumb.getChildren().add(sliderValue);\n        animatedThumb.setMouseTransparent(true);\n        animatedThumb.setScaleX(0);\n        animatedThumb.setScaleY(0);\n\n        thumb.layoutXProperty().addListener(x -> {\n            if (slider.getOrientation() == Orientation.VERTICAL) initAnimation(Orientation.VERTICAL);\n        });\n        thumb.layoutYProperty().addListener(y -> {\n            if (slider.getOrientation() == Orientation.HORIZONTAL) initAnimation(Orientation.HORIZONTAL);\n        });\n\n        addJFXChildren();\n        getChildren().addListener((ListChangeListener<Node>) c -> {\n            while (c.next()) {\n                if (c.wasAdded()) {\n                    c.getAddedSubList().forEach(added -> {\n                        if (added instanceof NumberAxis) {\n                            tickLine = (NumberAxis) added;\n                            tickLine.setAnimated(false);\n                        }\n                    });\n                }\n            }\n        });\n        registerChangeListener(slider.showTickMarksProperty(), e -> addJFXChildren());\n        registerChangeListener(slider.showTickLabelsProperty(), e -> addJFXChildren());\n        registerChangeListener(slider.valueFactoryProperty(), obs -> refreshSliderValueBinding());\n\n        initListeners();\n    }\n\n    private void addJFXChildren() {\n        ObservableList<Node> children = getChildren();\n        Slider slider = getSkinnable();\n        if ((slider.isShowTickMarks() || slider.isShowTickLabels()) && tickLine != null && !children.contains(tickLine)) {\n            children.add(0, tickLine);\n        }\n        if (children.contains(coloredTrack)) return;\n        children.add(children.indexOf(thumb), coloredTrack);\n        children.add(children.indexOf(thumb), animatedThumb);\n        children.add(0, mouseHandlerPane);\n    }\n\n    private void refreshSliderValueBinding() {\n        sliderValue.textProperty().unbind();\n        if (((JFXSlider) getSkinnable()).getValueFactory() != null) {\n            sliderValue.textProperty()\n                    .bind(((JFXSlider) getSkinnable()).getValueFactory().call((JFXSlider) getSkinnable()));\n        } else {\n            sliderValue.textProperty().bind(Bindings.createStringBinding(() -> {\n                if (getSkinnable().getLabelFormatter() != null) {\n                    return getSkinnable().getLabelFormatter().toString(getSkinnable().getValue());\n                } else {\n                    return String.valueOf(Math.round(getSkinnable().getValue()));\n                }\n            }, getSkinnable().valueProperty()));\n        }\n    }\n\n    @Override\n    protected void layoutChildren(double x, double y, double w, double h) {\n        super.layoutChildren(x, y, w, h);\n\n        if (timeline == null) {\n            initAnimation(getSkinnable().getOrientation());\n        }\n\n        double prefWidth = animatedThumb.prefWidth(-1);\n        animatedThumb.resize(prefWidth, animatedThumb.prefHeight(prefWidth));\n\n        boolean horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL;\n        double width, height, layoutX, layoutY;\n        if (horizontal) {\n            width = thumb.getLayoutX() - snappedLeftInset();\n            height = track.getHeight();\n            layoutX = track.getLayoutX();\n            layoutY = track.getLayoutY();\n            animatedThumb.setLayoutX(thumb.getLayoutX() + thumb.getWidth() / 2 - animatedThumb.getWidth() / 2);\n        } else {\n            height = track.getLayoutBounds().getMaxY() + track.getLayoutY() - thumb.getLayoutY() - snappedBottomInset();\n            width = track.getWidth();\n            layoutX = track.getLayoutX();\n            layoutY = thumb.getLayoutY();\n            animatedThumb.setLayoutY(thumb.getLayoutY() + thumb.getHeight() / 2 - animatedThumb.getHeight() / 2);\n        }\n\n        coloredTrack.resizeRelocate(layoutX, layoutY, width, height);\n        mouseHandlerPane.resizeRelocate(x, y, w, h);\n    }\n\n    private void initializeVariables() {\n        shifting = 30 + thumb.getWidth();\n        if (getSkinnable().getOrientation() != Orientation.HORIZONTAL) {\n            horizontalRotation = -90;\n        }\n        if (((JFXSlider) getSkinnable()).getIndicatorPosition() != IndicatorPosition.LEFT) {\n            indicatorRotation = 180;\n            shifting = -shifting;\n        }\n        final double rotationAngle = 45;\n        sliderValue.setRotate(rotationAngle + indicatorRotation + 3 * horizontalRotation);\n        animatedThumb.setRotate(-rotationAngle + indicatorRotation + horizontalRotation);\n    }\n\n    private void initListeners() {\n        // delegate slider mouse events to track node\n        mouseHandlerPane.setOnMousePressed(this::delegateToTrack);\n        mouseHandlerPane.setOnMouseReleased(this::delegateToTrack);\n        mouseHandlerPane.setOnMouseDragged(this::delegateToTrack);\n\n        // animate value node\n        track.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {\n            timeline.setRate(1);\n            timeline.play();\n        });\n        track.addEventHandler(MouseEvent.MOUSE_RELEASED, (event) -> {\n            timeline.setRate(-1);\n            timeline.play();\n        });\n        thumb.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {\n            timeline.setRate(1);\n            timeline.play();\n        });\n        thumb.addEventHandler(MouseEvent.MOUSE_RELEASED, (event) -> {\n            timeline.setRate(-1);\n            timeline.play();\n        });\n\n        refreshSliderValueBinding();\n        updateValueStyleClass();\n\n        getSkinnable().valueProperty().addListener(observable -> updateValueStyleClass());\n    }\n\n    private void delegateToTrack(MouseEvent event) {\n        if (!event.isConsumed()) {\n            event.consume();\n            track.fireEvent(event);\n        }\n    }\n\n    private void updateValueStyleClass() {\n        getSkinnable().pseudoClassStateChanged(MIN_VALUE, getSkinnable().getMin() == getSkinnable().getValue());\n        getSkinnable().pseudoClassStateChanged(MAX_VALUE, getSkinnable().getMax() == getSkinnable().getValue());\n    }\n\n    private void initAnimation(Orientation orientation) {\n        initializeVariables();\n\n        double thumbPos, thumbNewPos;\n        DoubleProperty layoutProperty;\n\n        if (orientation == Orientation.HORIZONTAL) {\n            if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) {\n                thumbPos = thumb.getLayoutY() - thumb.getHeight();\n                thumbNewPos = thumbPos - shifting;\n            } else {\n                double height = animatedThumb.prefHeight(animatedThumb.prefWidth(-1));\n                thumbPos = thumb.getLayoutY() - height / 2;\n                thumbNewPos = thumb.getLayoutY() - height - thumb.getHeight();\n            }\n            layoutProperty = animatedThumb.translateYProperty();\n        } else {\n            if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) {\n                thumbPos = thumb.getLayoutX() - thumb.getWidth();\n                thumbNewPos = thumbPos - shifting;\n            } else {\n                double width = animatedThumb.prefWidth(-1);\n                thumbPos = thumb.getLayoutX() - width / 2;\n                thumbNewPos = thumb.getLayoutX() - width - thumb.getWidth();\n            }\n            layoutProperty = animatedThumb.translateXProperty();\n        }\n\n        clearAnimation();\n\n        timeline = new Timeline(\n                new KeyFrame(\n                        Duration.ZERO,\n                        new KeyValue(animatedThumb.scaleXProperty(), 0, Interpolator.EASE_BOTH),\n                        new KeyValue(animatedThumb.scaleYProperty(), 0, Interpolator.EASE_BOTH),\n                        new KeyValue(layoutProperty, thumbPos, Interpolator.EASE_BOTH)),\n                new KeyFrame(\n                        Duration.seconds(0.2),\n                        new KeyValue(animatedThumb.scaleXProperty(), 1, Interpolator.EASE_BOTH),\n                        new KeyValue(animatedThumb.scaleYProperty(), 1, Interpolator.EASE_BOTH),\n                        new KeyValue(layoutProperty, thumbNewPos, Interpolator.EASE_BOTH)));\n    }\n\n    @Override\n    public void dispose() {\n        super.dispose();\n        clearAnimation();\n    }\n\n    private void clearAnimation() {\n        if (timeline != null) {\n            timeline.stop();\n            timeline.getKeyFrames().clear();\n            timeline = null;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXSpinnerSkin.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXSpinner;\nimport com.jfoenix.utils.TreeShowingProperty;\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.scene.Group;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Arc;\nimport javafx.scene.shape.Rectangle;\nimport javafx.scene.shape.StrokeLineCap;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/// JFXSpinner material design skin\n///\n/// @author Shadi Shaheen & Gerard Moubarak\n/// @version 1.0\n/// @since 2017-09-25\npublic class JFXSpinnerSkin extends SkinBase<JFXSpinner> {\n\n    private static final double DEFAULT_STROKE_WIDTH = 4;\n\n    private JFXSpinner control;\n    private final TreeShowingProperty treeShowingProperty;\n    private boolean isValid = false;\n\n    private Timeline timeline;\n    private Arc arc;\n    private Arc track;\n    private final StackPane arcPane;\n    private final Rectangle fillRect;\n\n    private final double startingAngle;\n\n    public JFXSpinnerSkin(JFXSpinner control) {\n        super(control);\n\n        this.control = control;\n        this.treeShowingProperty = new TreeShowingProperty(control);\n        this.startingAngle = control.getStartingAngle();\n\n        arc = new Arc();\n        arc.setManaged(false);\n        arc.setLength(180);\n        arc.getStyleClass().setAll(\"arc\");\n        arc.setFill(Color.TRANSPARENT);\n        arc.setStrokeWidth(DEFAULT_STROKE_WIDTH);\n        arc.setStrokeLineCap(StrokeLineCap.ROUND);\n\n        track = new Arc();\n        track.setManaged(false);\n        track.setLength(360);\n        track.setStrokeWidth(DEFAULT_STROKE_WIDTH);\n        track.getStyleClass().setAll(\"track\");\n        track.setFill(Color.TRANSPARENT);\n\n        fillRect = new Rectangle();\n        fillRect.setFill(Color.TRANSPARENT);\n        final Group group = new Group(fillRect, track, arc);\n        group.setManaged(false);\n        arcPane = new StackPane(group);\n        arcPane.setPrefSize(50, 50);\n        getChildren().setAll(arcPane);\n\n        // register listeners\n        registerChangeListener(control.progressProperty(), obs -> updateProgress());\n        registerChangeListener(treeShowingProperty, obs -> updateProgress());\n    }\n\n    private void updateProgress() {\n        double progress = Double.min(getSkinnable().getProgress(), 1.0);\n        if (progress < 0) { // indeterminate\n            boolean treeShowing = treeShowingProperty.get();\n            if (treeShowing) {\n                if (timeline == null) {\n                    timeline = createTransition();\n                    timeline.playFromStart();\n                } else {\n                    timeline.play();\n                }\n            } else if (timeline != null) {\n                timeline.pause();\n            }\n        } else { // determinate\n            clearAnimation();\n            arc.setStartAngle(90);\n            arc.setLength(-360 * progress);\n        }\n    }\n\n    private double computeSize() {\n        return control.getRadius() * 2 + arc.getStrokeWidth() * 2;\n    }\n\n    @Override\n    protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {\n        if (Region.USE_COMPUTED_SIZE == control.getRadius()) {\n            return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset);\n        } else {\n            return computeSize();\n        }\n    }\n\n    @Override\n    protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        if (Region.USE_COMPUTED_SIZE == control.getRadius()) {\n            return super.computeMaxWidth(height, topInset, rightInset, bottomInset, leftInset);\n        } else {\n            return computeSize();\n        }\n    }\n\n    @Override\n    protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        if (Region.USE_COMPUTED_SIZE == control.getRadius()) {\n            return arcPane.prefWidth(-1);\n        } else {\n            return computeSize();\n        }\n    }\n\n    @Override\n    protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {\n        if (Region.USE_COMPUTED_SIZE == control.getRadius()) {\n            return arcPane.prefHeight(-1);\n        } else {\n            return computeSize();\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {\n        final double strokeWidth = arc.getStrokeWidth();\n        double radius = control.getRadius();\n        final double arcSize = snapSizeX(radius * 2 + strokeWidth);\n\n        arcPane.resizeRelocate((contentWidth - arcSize) / 2 + 1, (contentHeight - arcSize) / 2 + 1, arcSize, arcSize);\n        updateArcLayout(radius, arcSize);\n\n        fillRect.setWidth(arcSize);\n        fillRect.setHeight(arcSize);\n\n        if (!isValid) {\n            updateProgress();\n            isValid = true;\n        }\n    }\n\n    private void updateArcLayout(double radius, double arcSize) {\n        arc.setRadiusX(radius);\n        arc.setRadiusY(radius);\n        arc.setCenterX(arcSize / 2);\n        arc.setCenterY(arcSize / 2);\n\n        track.setRadiusX(radius);\n        track.setRadiusY(radius);\n        track.setCenterX(arcSize / 2);\n        track.setCenterY(arcSize / 2);\n        track.setStrokeWidth(arc.getStrokeWidth());\n    }\n\n    private void addKeyFrames(List<KeyFrame> frames, double angle, double duration) {\n        frames.add(new KeyFrame(Duration.seconds(duration),\n                new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),\n                new KeyValue(arc.startAngleProperty(),\n                        angle + 45 + startingAngle,\n                        Interpolator.LINEAR)));\n        frames.add(new KeyFrame(Duration.seconds(duration + 0.4),\n                new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),\n                new KeyValue(arc.startAngleProperty(),\n                        angle + 90 + startingAngle,\n                        Interpolator.LINEAR)));\n        frames.add(new KeyFrame(Duration.seconds(duration + 0.7),\n                new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),\n                new KeyValue(arc.startAngleProperty(),\n                        angle + 135 + startingAngle,\n                        Interpolator.LINEAR)));\n        frames.add(new KeyFrame(Duration.seconds(duration + 1.1),\n                new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),\n                new KeyValue(arc.startAngleProperty(),\n                        angle + 435 + startingAngle,\n                        Interpolator.LINEAR)));\n    }\n\n    private Timeline createTransition() {\n        Timeline timeline;\n        if (AnimationUtils.isAnimationEnabled()) {\n            var keyFrames = new ArrayList<KeyFrame>(17);\n            addKeyFrames(keyFrames, 0, 0);\n            addKeyFrames(keyFrames, 450, 1.4);\n            addKeyFrames(keyFrames, 900, 2.8);\n            addKeyFrames(keyFrames, 1350, 4.2);\n            keyFrames.add(new KeyFrame(Duration.seconds(5.6),\n                    new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),\n                    new KeyValue(arc.startAngleProperty(),\n                            1845 + startingAngle,\n                            Interpolator.LINEAR)));\n\n            timeline = new Timeline();\n            timeline.getKeyFrames().setAll(keyFrames);\n        } else {\n            final double arcLength = 250;\n            timeline = new Timeline(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(arc.startAngleProperty(), 45 + startingAngle, Interpolator.LINEAR),\n                            new KeyValue(arc.lengthProperty(), arcLength, Interpolator.DISCRETE)),\n                    new KeyFrame(Duration.seconds(1.2),\n                            new KeyValue(arc.startAngleProperty(), 45 + 360 + startingAngle, Interpolator.LINEAR),\n                            new KeyValue(arc.lengthProperty(), arcLength, Interpolator.DISCRETE))\n            );\n        }\n\n        timeline.setCycleCount(Timeline.INDEFINITE);\n        return timeline;\n    }\n\n    private void clearAnimation() {\n        if (timeline != null) {\n            timeline.stop();\n            timeline = null;\n        }\n    }\n\n    @Override\n    public void dispose() {\n        super.dispose();\n        treeShowingProperty.dispose();\n        clearAnimation();\n        arc = null;\n        track = null;\n        control = null;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXTabPaneSkin.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXRippler;\nimport com.jfoenix.controls.JFXRippler.RipplerMask;\nimport com.jfoenix.controls.JFXRippler.RipplerPos;\nimport com.jfoenix.effects.JFXDepthManager;\nimport com.jfoenix.event.MultiplePropertyChangeListenerHandler;\nimport com.jfoenix.svg.SVGGlyph;\nimport com.jfoenix.transitions.CachedTransition;\nimport javafx.animation.*;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.property.DoubleProperty;\nimport javafx.beans.property.SimpleDoubleProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ListChangeListener.Change;\nimport javafx.collections.ObservableList;\nimport javafx.collections.WeakListChangeListener;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.HPos;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Side;\nimport javafx.geometry.VPos;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.control.skin.TabPaneSkin;\nimport javafx.scene.input.ContextMenuEvent;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.input.ScrollEvent;\nimport javafx.scene.layout.*;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Rectangle;\nimport javafx.scene.text.Font;\nimport javafx.scene.text.FontWeight;\nimport javafx.scene.transform.Rotate;\nimport javafx.scene.transform.Scale;\nimport javafx.util.Duration;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n/**\n * <h1>Material Design TabPane Skin</h1>\n *\n * @author Shadi Shaheen\n */\npublic class JFXTabPaneSkin extends TabPaneSkin {\n\n    private final Color defaultColor = Color.valueOf(\"#00BCD4\");\n    private final Color ripplerColor = Color.valueOf(\"#FFFF8D\");\n    private final Color selectedTabText = Color.WHITE;\n    private Color tempLabelColor = Color.WHITE;\n\n    private HeaderContainer header;\n    private ObservableList<TabContentHolder> tabContentHolders;\n    private Rectangle clip;\n    private Rectangle tabsClip;\n    private Tab selectedTab;\n    private boolean isSelectingTab = false;\n    private double dragStart, offsetStart;\n    private AnchorPane tabsContainer;\n    private AnchorPane tabsContainerHolder;\n    private static final int SPACER = 10;\n    private double maxWidth = 0.0d;\n    private double maxHeight = 0.0d;\n\n    public JFXTabPaneSkin(TabPane tabPane) {\n        super(tabPane);\n        tabContentHolders = FXCollections.observableArrayList();\n        header = new HeaderContainer();\n        getChildren().add(JFXDepthManager.createMaterialNode(header, 1));\n\n        tabsContainer = new AnchorPane();\n        tabsContainerHolder = new AnchorPane();\n        tabsContainerHolder.getChildren().add(tabsContainer);\n        tabsClip = new Rectangle();\n        tabsContainerHolder.setClip(tabsClip);\n        getChildren().add(tabsContainerHolder);\n\n        // add tabs\n        for (Tab tab : getSkinnable().getTabs()) {\n            addTabContentHolder(tab);\n        }\n\n        // clipping tabpane/header pane\n        clip = new Rectangle(tabPane.getWidth(), tabPane.getHeight());\n        getSkinnable().setClip(clip);\n        if (getSkinnable().getTabs().size() == 0) {\n            header.setVisible(false);\n        }\n\n        // select a tab\n        selectedTab = getSkinnable().getSelectionModel().getSelectedItem();\n        if (selectedTab == null && getSkinnable().getSelectionModel().getSelectedIndex() != -1) {\n            getSkinnable().getSelectionModel().select(getSkinnable().getSelectionModel().getSelectedIndex());\n            selectedTab = getSkinnable().getSelectionModel().getSelectedItem();\n        }\n        // if no selected tab, then select the first tab\n        if (selectedTab == null) {\n            getSkinnable().getSelectionModel().selectFirst();\n        }\n        selectedTab = getSkinnable().getSelectionModel().getSelectedItem();\n\n        header.headersRegion.setOnMouseDragged(me -> {\n            header.updateScrollOffset(offsetStart + (isHorizontal() ? me.getSceneX() : me.getSceneY()) - dragStart);\n            me.consume();\n        });\n        getSkinnable().setOnMousePressed(me -> {\n            dragStart = (isHorizontal() ? me.getSceneX() : me.getSceneY());\n            offsetStart = header.scrollOffset;\n        });\n\n        // add listeners on tab list\n        getSkinnable().getTabs().addListener((ListChangeListener<Tab>) change -> {\n            List<Tab> tabsToBeRemoved = new ArrayList<>();\n            List<Tab> tabsToBeAdded = new ArrayList<>();\n            int insertIndex = -1;\n            while (change.next()) {\n                if (change.wasPermutated()) {\n                    Tab selectedTab = getSkinnable().getSelectionModel().getSelectedItem();\n                    List<Tab> permutatedTabs = new ArrayList<>(change.getTo() - change.getFrom());\n                    getSkinnable().getSelectionModel().clearSelection();\n                    for (int i = change.getFrom(); i < change.getTo(); i++) {\n                        permutatedTabs.add(getSkinnable().getTabs().get(i));\n                    }\n                    removeTabs(permutatedTabs);\n                    addTabs(permutatedTabs, change.getFrom());\n                    getSkinnable().getSelectionModel().select(selectedTab);\n                }\n                if (change.wasRemoved()) {\n                    tabsToBeRemoved.addAll(change.getRemoved());\n                }\n                if (change.wasAdded()) {\n                    tabsToBeAdded.addAll(change.getAddedSubList());\n                    insertIndex = change.getFrom();\n                }\n            }\n            // only remove the tabs that are not in tabsToBeAdded\n            tabsToBeRemoved.removeAll(tabsToBeAdded);\n            removeTabs(tabsToBeRemoved);\n            // add the new tabs\n            if (!tabsToBeAdded.isEmpty()) {\n                for (TabContentHolder tabContentHolder : tabContentHolders) {\n                    TabHeaderContainer tabHeaderContainer = header.getTabHeaderContainer(tabContentHolder.tab);\n                    if (!tabHeaderContainer.isClosing && tabsToBeAdded.contains(tabContentHolder.tab)) {\n                        tabsToBeAdded.remove(tabContentHolder.tab);\n                    }\n                }\n                addTabs(tabsToBeAdded, insertIndex == -1 ? tabContentHolders.size() : insertIndex);\n            }\n            getSkinnable().requestLayout();\n        });\n\n        registerChangeListener(tabPane.getSelectionModel().selectedItemProperty(), (e) -> handleControlPropertyChanged(\"SELECTED_TAB\"));\n        registerChangeListener(tabPane.widthProperty(), (e) -> handleControlPropertyChanged(\"WIDTH\"));\n        registerChangeListener(tabPane.heightProperty(), (e) -> handleControlPropertyChanged(\"HEIGHT\"));\n\n    }\n\n    protected void handleControlPropertyChanged(String property) {\n        if (\"SELECTED_TAB\".equals(property)) {\n            isSelectingTab = true;\n            selectedTab = getSkinnable().getSelectionModel().getSelectedItem();\n            getSkinnable().requestLayout();\n        } else if (\"WIDTH\".equals(property)) {\n            clip.setWidth(getSkinnable().getWidth());\n        } else if (\"HEIGHT\".equals(property)) {\n            clip.setHeight(getSkinnable().getHeight());\n        }\n    }\n\n    private void removeTabs(List<? extends Tab> removedTabs) {\n        for (Tab tab : removedTabs) {\n            TabHeaderContainer tabHeaderContainer = header.getTabHeaderContainer(tab);\n            if (tabHeaderContainer != null) {\n                tabHeaderContainer.isClosing = true;\n                removeTab(tab);\n                // if tabs list is empty hide the header container\n                if (getSkinnable().getTabs().isEmpty()) {\n                    header.setVisible(false);\n                }\n            }\n        }\n    }\n\n    private void addTabs(List<? extends Tab> addedTabs, int startIndex) {\n        int i = 0;\n        for (Tab tab : addedTabs) {\n            // show header container if we are adding the 1st tab\n            if (!header.isVisible()) {\n                header.setVisible(true);\n            }\n            header.addTab(tab, startIndex + i++, false);\n            addTabContentHolder(tab);\n            final TabHeaderContainer tabHeaderContainer = header.getTabHeaderContainer(tab);\n            if (tabHeaderContainer != null) {\n                tabHeaderContainer.setVisible(true);\n                tabHeaderContainer.inner.requestLayout();\n            }\n        }\n    }\n\n    private void addTabContentHolder(Tab tab) {\n        // create new content place holder\n        TabContentHolder tabContentHolder = new TabContentHolder(tab);\n        tabContentHolder.setClip(new Rectangle());\n        tabContentHolders.add(tabContentHolder);\n        // always add tab content holder below its header\n        tabsContainer.getChildren().add(0, tabContentHolder);\n    }\n\n    private void removeTabContentHolder(Tab tab) {\n        for (TabContentHolder tabContentHolder : tabContentHolders) {\n            if (tabContentHolder.tab.equals(tab)) {\n                tabContentHolder.removeListeners(tab);\n                getChildren().remove(tabContentHolder);\n                tabContentHolders.remove(tabContentHolder);\n                tabsContainer.getChildren().remove(tabContentHolder);\n                break;\n            }\n        }\n    }\n\n    private void removeTab(Tab tab) {\n        final TabHeaderContainer tabHeaderContainer = header.getTabHeaderContainer(tab);\n        if (tabHeaderContainer != null) {\n            tabHeaderContainer.removeListeners(tab);\n        }\n        header.removeTab(tab);\n        removeTabContentHolder(tab);\n        header.requestLayout();\n    }\n\n    private boolean isHorizontal() {\n        final Side tabPosition = getSkinnable().getSide();\n        return Side.TOP.equals(tabPosition) || Side.BOTTOM.equals(tabPosition);\n    }\n\n    private static int getRotation(Side pos) {\n        switch (pos) {\n            case TOP:\n                return 0;\n            case BOTTOM:\n                return 180;\n            case LEFT:\n                return -90;\n            case RIGHT:\n                return 90;\n            default:\n                return 0;\n        }\n    }\n\n    @Override\n    protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {\n        for (TabContentHolder tabContentHolder : tabContentHolders) {\n            maxWidth = Math.max(maxWidth, snapSize(tabContentHolder.prefWidth(-1)));\n        }\n        final double headerContainerWidth = snapSize(header.prefWidth(-1));\n        double prefWidth = Math.max(maxWidth, headerContainerWidth);\n        return snapSize(prefWidth) + rightInset + leftInset;\n    }\n\n    @Override\n    protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {\n        for (TabContentHolder tabContentHolder : tabContentHolders) {\n            maxHeight = Math.max(maxHeight, snapSize(tabContentHolder.prefHeight(-1)));\n        }\n        final double headerContainerHeight = snapSize(header.prefHeight(-1));\n        double prefHeight = maxHeight + snapSize(headerContainerHeight);\n        return snapSize(prefHeight) + topInset + bottomInset;\n    }\n\n    @Override\n    public double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {\n        return header.getBaselineOffset() + topInset;\n    }\n\n    /*\n     *  keep track of indices after changing the tabs, it used to fix\n     *  tabs animation after changing the tabs (remove/add)\n     */\n    private int diffTabsIndices = 0;\n\n    @Override\n    protected void layoutChildren(final double x, final double y, final double w, final double h) {\n        final double headerHeight = snapSize(header.prefHeight(-1));\n        final Side side = getSkinnable().getSide();\n        double tabsX = side == Side.RIGHT ? x + w - headerHeight : x;\n        double tabsY = side == Side.BOTTOM ? y + h - headerHeight : y;\n        final int rotation = getRotation(side);\n\n        // update header\n        switch (side) {\n            case TOP:\n                header.resize(w, headerHeight);\n                header.relocate(tabsX, tabsY);\n                break;\n            case LEFT:\n                header.resize(h, headerHeight);\n                header.relocate(tabsX + headerHeight, h - headerHeight);\n                break;\n            case RIGHT:\n                header.resize(h, headerHeight);\n                header.relocate(tabsX, y - headerHeight);\n                break;\n            case BOTTOM:\n                header.resize(w, headerHeight);\n                header.relocate(w, tabsY - headerHeight);\n                break;\n        }\n        header.getTransforms().setAll(new Rotate(rotation, 0, headerHeight, 1));\n\n        // update header clip\n//        header.clip.setX(0);\n//        header.clip.setY(0);\n//        header.clip.setWidth(isHorizontal() ? w : h);\n//        header.clip.setHeight(headerHeight + 10); // 10 is the height of the shadow effect\n\n        // position the tab content of the current selected tab\n        double contentStartX = x + (side == Side.LEFT ? headerHeight : 0);\n        double contentStartY = y + (side == Side.TOP ? headerHeight : 0);\n        double contentWidth = w - (isHorizontal() ? 0 : headerHeight);\n        double contentHeight = h - (isHorizontal() ? headerHeight : 0);\n\n        // update tabs container\n        tabsClip.setWidth(contentWidth);\n        tabsClip.setHeight(contentHeight);\n        tabsContainerHolder.resize(contentWidth, contentHeight);\n        tabsContainerHolder.relocate(contentStartX, contentStartY);\n        tabsContainer.resize(contentWidth * tabContentHolders.size(), contentHeight);\n\n        for (int i = 0, max = tabContentHolders.size(); i < max; i++) {\n            TabContentHolder tabContentHolder = tabContentHolders.get(i);\n            tabContentHolder.setVisible(true);\n            tabContentHolder.setTranslateX(contentWidth * i);\n            if (tabContentHolder.getClip() != null) {\n                ((Rectangle) tabContentHolder.getClip()).setWidth(contentWidth);\n                ((Rectangle) tabContentHolder.getClip()).setHeight(contentHeight);\n            }\n            if (tabContentHolder.tab == selectedTab) {\n                int index = getSkinnable().getTabs().indexOf(selectedTab);\n                if (index != i) {\n                    tabsContainer.setTranslateX(-contentWidth * i);\n                    diffTabsIndices = i - index;\n                } else {\n                    // fix X translation after changing the tabs\n                    if (diffTabsIndices != 0) {\n                        tabsContainer.setTranslateX(tabsContainer.getTranslateX() + contentWidth * diffTabsIndices);\n                        diffTabsIndices = 0;\n                    }\n                    // animate upon tab selection only otherwise just translate the selected tab\n                    if (isSelectingTab) {\n                        new CachedTransition(tabsContainer,\n                            new Timeline(new KeyFrame(Duration.millis(1000), new KeyValue(tabsContainer.translateXProperty(), -contentWidth * index, Interpolator.EASE_BOTH)))) {{\n                                    setCycleDuration(Duration.seconds(0.320));\n                                    setDelay(Duration.seconds(0));\n                                }\n                        }.play();\n                    } else {\n                        tabsContainer.setTranslateX(-contentWidth * index);\n                    }\n                }\n            }\n            tabContentHolder.resize(contentWidth, contentHeight);\n//            tabContentHolder.relocate(contentStartX, contentStartY);\n        }\n    }\n\n    /**************************************************************************\n     *                                                                          *\n     * HeaderContainer: tabs headers container                                    *\n     *                                                                          *\n     **************************************************************************/\n    protected class HeaderContainer extends StackPane {\n\n        private Rectangle clip;\n        private StackPane headersRegion;\n        private StackPane headerBackground;\n\n        private HeaderControl rightControlButton;\n        private HeaderControl leftControlButton;\n        private StackPane selectedTabLine;\n        private boolean initialized = false;\n        private boolean measureClosingTabs = false;\n        private double scrollOffset, selectedTabLineOffset;\n\n        private final Scale scale;\n        private final Rotate rotate;\n        private int direction;\n        private Timeline timeline;\n        private final double translateScaleFactor = 1.3;\n\n        public HeaderContainer() {\n            // keep track of the current side\n            getSkinnable().sideProperty().addListener(observable -> updateDirection());\n            updateDirection();\n\n            getStyleClass().setAll(\"tab-header-area\");\n            setManaged(false);\n            clip = new Rectangle();\n            headersRegion = new StackPane() {\n                @Override\n                protected double computePrefWidth(double height) {\n                    double width = 0.0F;\n                    for (Node child : getChildren()) {\n                        if (child instanceof TabHeaderContainer && child.isVisible() && (measureClosingTabs || !((TabHeaderContainer) child).isClosing)) {\n                            width += child.prefWidth(height);\n                        }\n                    }\n                    return snapSize(width) + snappedLeftInset() + snappedRightInset();\n                }\n\n                @Override\n                protected double computePrefHeight(double width) {\n                    double height = 0.0F;\n                    for (Node child : getChildren()) {\n                        if (child instanceof TabHeaderContainer) {\n                            height = Math.max(height, child.prefHeight(width));\n                        }\n                    }\n                    return snapSize(height) + snappedTopInset() + snappedBottomInset();\n                }\n\n                @Override\n                protected void layoutChildren() {\n                    if (isTabsFitHeaderWidth()) {\n                        updateScrollOffset(0.0);\n                    } else {\n                        if (!removedTabsHeaders.isEmpty()) {\n                            double offset = 0;\n                            double w = header.getWidth() - snapSize(rightControlButton.prefWidth(-1)) - snapSize(leftControlButton.prefWidth(-1)) - snappedLeftInset() - SPACER;\n                            Iterator<Node> itr = getChildren().iterator();\n                            while (itr.hasNext()) {\n                                Node temp = itr.next();\n                                if (temp instanceof TabHeaderContainer) {\n                                    TabHeaderContainer tabHeaderContainer = (TabHeaderContainer) temp;\n                                    double containerPrefWidth = snapSize(tabHeaderContainer.prefWidth(-1));\n                                    // if tab has been removed\n                                    if (removedTabsHeaders.contains(tabHeaderContainer)) {\n                                        if (offset < w) {\n                                            isSelectingTab = true;\n                                        }\n                                        itr.remove();\n                                        removedTabsHeaders.remove(tabHeaderContainer);\n                                        if (removedTabsHeaders.isEmpty()) {\n                                            break;\n                                        }\n                                    }\n                                    offset += containerPrefWidth;\n                                }\n                            }\n                        }\n                    }\n\n                    if (isSelectingTab) {\n                        // make sure the selected tab is visible\n                        animateSelectionLine();\n                        isSelectingTab = false;\n                    } else {\n                        // validate scroll offset\n                        updateScrollOffset(scrollOffset);\n                    }\n\n                    final double tabBackgroundHeight = snapSize(prefHeight(-1));\n                    final Side side = getSkinnable().getSide();\n                    double tabStartX = (side == Side.LEFT || side == Side.BOTTOM) ? snapSize(getWidth()) - scrollOffset : scrollOffset;\n                    updateHeaderContainerClip();\n                    for (Node node : getChildren()) {\n                        if (node instanceof TabHeaderContainer) {\n                            TabHeaderContainer tabHeaderContainer = (TabHeaderContainer) node;\n                            double tabHeaderPrefWidth = snapSize(tabHeaderContainer.prefWidth(-1));\n                            double tabHeaderPrefHeight = snapSize(tabHeaderContainer.prefHeight(-1));\n                            tabHeaderContainer.resize(tabHeaderPrefWidth, tabHeaderPrefHeight);\n\n                            double tabStartY = side == Side.BOTTOM ? 0 : tabBackgroundHeight - tabHeaderPrefHeight - snappedBottomInset();\n                            if (side == Side.LEFT || side == Side.BOTTOM) {\n                                // build from the right\n                                tabStartX -= tabHeaderPrefWidth;\n                                tabHeaderContainer.relocate(tabStartX, tabStartY);\n                            } else {\n                                // build from the left\n                                tabHeaderContainer.relocate(tabStartX, tabStartY);\n                                tabStartX += tabHeaderPrefWidth;\n                            }\n                        }\n                    }\n                    selectedTabLine.resizeRelocate((side == Side.LEFT || side == Side.BOTTOM) ? snapSize(headersRegion.getWidth()) : 0, tabBackgroundHeight - selectedTabLine.prefHeight(-1), snapSize(selectedTabLine.prefWidth(-1)), snapSize(selectedTabLine.prefHeight(-1)));\n                }\n            };\n\n            headersRegion.getStyleClass().setAll(\"headers-region\");\n            headersRegion.setCache(true);\n            headersRegion.setClip(clip);\n\n            headerBackground = new StackPane();\n            headerBackground.setBackground(new Background(new BackgroundFill(defaultColor, CornerRadii.EMPTY, Insets.EMPTY)));\n            headerBackground.getStyleClass().setAll(\"tab-header-background\");\n            selectedTabLine = new StackPane();\n            scale = new Scale(1, 1, 0, 0);\n            rotate = new Rotate(0, 0, 1);\n            rotate.pivotYProperty().bind(selectedTabLine.heightProperty().divide(2));\n\n            selectedTabLine.getTransforms().addAll(scale, rotate);\n            selectedTabLine.setCache(true);\n            selectedTabLine.getStyleClass().add(\"tab-selected-line\");\n            selectedTabLine.setPrefHeight(2);\n            selectedTabLine.setPrefWidth(1);\n            selectedTabLine.setBackground(new Background(new BackgroundFill(ripplerColor, CornerRadii.EMPTY, Insets.EMPTY)));\n            headersRegion.getChildren().add(selectedTabLine);\n\n            rightControlButton = new HeaderControl(ArrowPosition.RIGHT);\n            leftControlButton = new HeaderControl(ArrowPosition.LEFT);\n            rightControlButton.setVisible(false);\n            leftControlButton.setVisible(false);\n            rightControlButton.inner.prefHeightProperty().bind(headersRegion.heightProperty());\n            leftControlButton.inner.prefHeightProperty().bind(headersRegion.heightProperty());\n\n            getChildren().addAll(headerBackground, headersRegion, leftControlButton, rightControlButton);\n\n            int i = 0;\n            for (Tab tab : getSkinnable().getTabs()) {\n                addTab(tab, i++, true);\n            }\n\n            // support for mouse scroll of header area\n            addEventHandler(ScrollEvent.SCROLL, (ScrollEvent e) -> updateScrollOffset(scrollOffset + e.getDeltaY() * (isHorizontal() ? -1 : 1)));\n        }\n\n        private void updateDirection() {\n            final Side side = getSkinnable().getSide();\n            direction = (side == Side.BOTTOM || side == Side.LEFT) ? -1 : 1;\n        }\n\n        private void updateHeaderContainerClip() {\n            final double clipOffset = getClipOffset();\n            final Side side = getSkinnable().getSide();\n            double controlPrefWidth = 2 * snapSize(rightControlButton.prefWidth(-1));\n            // Add the spacer if the control buttons are shown\n//            controlPrefWidth = controlPrefWidth > 0 ? controlPrefWidth + SPACER : controlPrefWidth;\n\n            measureClosingTabs = true;\n            final double headersPrefWidth = snapSize(headersRegion.prefWidth(-1));\n            final double headersPrefHeight = snapSize(headersRegion.prefHeight(-1));\n            measureClosingTabs = false;\n\n            final double maxWidth = snapSize(getWidth()) - controlPrefWidth - clipOffset;\n            final double clipWidth = headersPrefWidth < maxWidth ? headersPrefWidth : maxWidth;\n            final double clipHeight = headersPrefHeight;\n\n            clip.setX((side == Side.LEFT || side == Side.BOTTOM) && headersPrefWidth >= maxWidth ? headersPrefWidth - maxWidth : 0);\n            clip.setY(0);\n            clip.setWidth(clipWidth);\n            clip.setHeight(clipHeight);\n        }\n\n        private double getClipOffset() {\n            return isHorizontal() ? snappedLeftInset() : snappedRightInset();\n        }\n\n        private void addTab(Tab tab, int addToIndex, boolean visible) {\n            TabHeaderContainer tabHeaderContainer = new TabHeaderContainer(tab);\n            tabHeaderContainer.setVisible(visible);\n            headersRegion.getChildren().add(addToIndex, tabHeaderContainer);\n        }\n\n        private List<TabHeaderContainer> removedTabsHeaders = new ArrayList<>();\n\n        private void removeTab(Tab tab) {\n            TabHeaderContainer tabHeaderContainer = getTabHeaderContainer(tab);\n            if (tabHeaderContainer != null) {\n                if (isTabsFitHeaderWidth()) {\n                    headersRegion.getChildren().remove(tabHeaderContainer);\n                } else {\n                    // we need to keep track of the removed tab headers\n                    // to compute scroll offset of the header\n                    removedTabsHeaders.add(tabHeaderContainer);\n                    tabHeaderContainer.removeListeners(tab);\n                }\n            }\n        }\n\n        private TabHeaderContainer getTabHeaderContainer(Tab tab) {\n            for (Node child : headersRegion.getChildren()) {\n                if (child instanceof TabHeaderContainer) {\n                    if (((TabHeaderContainer) child).tab.equals(tab)) {\n                        return (TabHeaderContainer) child;\n                    }\n                }\n            }\n            return null;\n        }\n\n        private boolean isTabsFitHeaderWidth() {\n            double headerPrefWidth = snapSize(headersRegion.prefWidth(-1));\n            double rightControlWidth = 2 * snapSize(rightControlButton.prefWidth(-1));\n            double visibleWidth = headerPrefWidth + rightControlWidth + snappedLeftInset() + SPACER;\n            return visibleWidth < getWidth();\n        }\n\n        private void runTimeline(double newTransX, double newWidth) {\n            newWidth = snapSizeX(newWidth);\n            newTransX = snapPositionX(newTransX);\n\n            double tempScaleX = 0;\n            double tempWidth = 0;\n            final double lineWidth = snapSizeX(selectedTabLine.prefWidth(-1));\n\n            if (isAnimating()) {\n                timeline.stop();\n                tempScaleX = scale.getX();\n                if (rotate.getAngle() != 0) {\n                    rotate.setAngle(0);\n                    tempWidth = snapSizeX(tempScaleX * lineWidth);\n                    selectedTabLine.setTranslateX(snapPositionX(selectedTabLine.getTranslateX() - tempWidth));\n                }\n            }\n\n            final double oldScaleX = scale.getX();\n            final double oldWidth = snapSizeX(lineWidth * oldScaleX);\n            final double oldTransX = snapPositionX(selectedTabLine.getTranslateX());\n\n            final double newScaleX = newWidth / lineWidth;\n\n            selectedTabLineOffset = newTransX;\n            newTransX = snapPositionX(newTransX + offsetStart * direction);\n\n            final double transDiff = newTransX - oldTransX;\n\n            double midScaleX = tempScaleX != 0 ? tempScaleX : snapSizeX(((Math.abs(transDiff) / translateScaleFactor + oldWidth)) / lineWidth);\n\n            if (transDiff < 0) {\n                selectedTabLine.setTranslateX(snapPositionX(selectedTabLine.getTranslateX() + oldWidth));\n                newTransX = snapPositionX(newTransX + newWidth);\n                rotate.setAngle(180);\n            }\n\n            timeline = new Timeline(new KeyFrame(Duration.ZERO, new KeyValue(selectedTabLine.translateXProperty(), selectedTabLine.getTranslateX(), Interpolator.EASE_BOTH)), new KeyFrame(Duration.seconds(0.12), new KeyValue(scale.xProperty(), midScaleX, Interpolator.EASE_BOTH)), new KeyFrame(Duration.seconds(0.24), new KeyValue(scale.xProperty(), newScaleX, Interpolator.EASE_BOTH), new KeyValue(selectedTabLine.translateXProperty(), newTransX, Interpolator.EASE_BOTH)));\n\n            timeline.setOnFinished(finish -> {\n                if (rotate.getAngle() != 0) {\n                    rotate.setAngle(0);\n                    double finalX = snapPositionX(selectedTabLine.getTranslateX() - (lineWidth * newScaleX));\n                    selectedTabLine.setTranslateX(finalX);\n                }\n            });\n            timeline.play();\n        }\n\n        private boolean isAnimating() {\n            return timeline != null && timeline.getStatus() == Animation.Status.RUNNING;\n        }\n\n        public void updateScrollOffset(double newOffset) {\n            double tabPaneWidth = snapSize(isHorizontal() ? getSkinnable().getWidth() : getSkinnable().getHeight());\n            double controlTabWidth = 2 * snapSize(rightControlButton.getWidth());\n            double visibleWidth = tabPaneWidth - controlTabWidth - snappedLeftInset() - SPACER;\n\n            // compute all tabs headers width\n            double offset = 0.0;\n            for (Node node : headersRegion.getChildren()) {\n                if (node instanceof TabHeaderContainer) {\n                    double tabHeaderPrefWidth = snapSize(node.prefWidth(-1));\n                    offset += tabHeaderPrefWidth;\n                }\n            }\n\n            double actualOffset = newOffset;\n            if ((visibleWidth - newOffset) > offset && newOffset < 0) {\n                actualOffset = visibleWidth - offset;\n            } else if (newOffset > 0) {\n                actualOffset = 0;\n            }\n\n            if (actualOffset != scrollOffset) {\n                scrollOffset = actualOffset;\n                headersRegion.requestLayout();\n                if (!isAnimating()) {\n                    selectedTabLine.setTranslateX(selectedTabLineOffset + scrollOffset * direction);\n                }\n            }\n        }\n\n        @Override\n        protected double computePrefWidth(double height) {\n            final double padding = isHorizontal() ? 2 * snappedLeftInset() + snappedRightInset() : 2 * snappedTopInset() + snappedBottomInset();\n            return snapSize(headersRegion.prefWidth(height)) + 2 * rightControlButton.prefWidth(height) + padding + SPACER;\n        }\n\n        @Override\n        protected double computePrefHeight(double width) {\n            final double padding = isHorizontal() ? snappedTopInset() + snappedBottomInset() : snappedLeftInset() + snappedRightInset();\n            return snapSize(headersRegion.prefHeight(-1)) + padding;\n        }\n\n        @Override\n        public double getBaselineOffset() {\n            return getSkinnable().getSide() == Side.TOP ? headersRegion.getBaselineOffset() + snappedTopInset() : 0;\n        }\n\n        @Override\n        protected void layoutChildren() {\n            final double leftInset = snappedLeftInset();\n            final double rightInset = snappedRightInset();\n            final double topInset = snappedTopInset();\n            final double bottomInset = snappedBottomInset();\n            final double padding = isHorizontal() ? leftInset + rightInset : topInset + bottomInset;\n            final double w = snapSize(getWidth()) - padding;\n            final double h = snapSize(getHeight()) - padding;\n            final double tabBackgroundHeight = snapSize(prefHeight(-1));\n            final double headersPrefWidth = snapSize(headersRegion.prefWidth(-1));\n            final double headersPrefHeight = snapSize(headersRegion.prefHeight(-1));\n\n            rightControlButton.showTabsMenu(!isTabsFitHeaderWidth());\n            leftControlButton.showTabsMenu(!isTabsFitHeaderWidth());\n\n            updateHeaderContainerClip();\n            headersRegion.requestLayout();\n\n            // layout left/right controls buttons\n            final double btnWidth = snapSize(rightControlButton.prefWidth(-1));\n            final double btnHeight = rightControlButton.prefHeight(btnWidth);\n            rightControlButton.resize(btnWidth, btnHeight);\n            leftControlButton.resize(btnWidth, btnHeight);\n\n            // layout tabs\n            headersRegion.resize(headersPrefWidth, headersPrefHeight);\n            headerBackground.resize(snapSize(getWidth()), snapSize(getHeight()));\n\n            final Side side = getSkinnable().getSide();\n            double startX = 0;\n            double startY = 0;\n            double controlStartX = 0;\n            double controlStartY = 0;\n            switch (side) {\n                case TOP:\n                    startX = leftInset;\n                    startY = tabBackgroundHeight - headersPrefHeight - bottomInset;\n                    controlStartX = w - btnWidth + leftInset;\n                    controlStartY = snapSize(getHeight()) - btnHeight - bottomInset;\n                    break;\n                case BOTTOM:\n                    startX = snapSize(getWidth()) - headersPrefWidth - leftInset;\n                    startY = tabBackgroundHeight - headersPrefHeight - topInset;\n                    controlStartX = rightInset;\n                    controlStartY = snapSize(getHeight()) - btnHeight - topInset;\n                    break;\n                case LEFT:\n                    startX = snapSize(getWidth()) - headersPrefWidth - topInset;\n                    startY = tabBackgroundHeight - headersPrefHeight - rightInset;\n                    controlStartX = leftInset;\n                    controlStartY = snapSize(getHeight()) - btnHeight - rightInset;\n                    break;\n                case RIGHT:\n                    startX = topInset;\n                    startY = tabBackgroundHeight - headersPrefHeight - leftInset;\n                    controlStartX = w - btnWidth + topInset;\n                    controlStartY = snapSize(getHeight()) - btnHeight - leftInset;\n                    break;\n            }\n\n            if (headerBackground.isVisible()) {\n                positionInArea(headerBackground, 0, 0, snapSize(getWidth()), snapSize(getHeight()), 0, HPos.CENTER, VPos.CENTER);\n            }\n\n            positionInArea(headersRegion, startX + btnWidth * ((side == Side.LEFT || side == Side.BOTTOM) ? -1 : 1), startY, w, h, 0, HPos.LEFT, VPos.CENTER);\n\n            positionInArea(rightControlButton, controlStartX, controlStartY, btnWidth, btnHeight, 0, HPos.CENTER, VPos.CENTER);\n\n            positionInArea(leftControlButton, (side == Side.LEFT || side == Side.BOTTOM) ? w - btnWidth : 0, controlStartY, btnWidth, btnHeight, 0, HPos.CENTER, VPos.CENTER);\n\n            rightControlButton.setRotate((side == Side.LEFT || side == Side.BOTTOM) ? 180.0F : 0.0F);\n            leftControlButton.setRotate((side == Side.LEFT || side == Side.BOTTOM) ? 180.0F : 0.0F);\n\n            if (!initialized) {\n                animateSelectionLine();\n                initialized = true;\n            }\n        }\n\n        private void animateSelectionLine() {\n            double offset = 0.0;\n            double selectedTabOffset = 0.0;\n            double selectedTabWidth = 0.0;\n            final Side side = getSkinnable().getSide();\n            for (Node node : headersRegion.getChildren()) {\n                if (node instanceof TabHeaderContainer) {\n                    TabHeaderContainer tabHeader = (TabHeaderContainer) node;\n                    double tabHeaderPrefWidth = snapSize(tabHeader.prefWidth(-1));\n                    if (selectedTab != null && selectedTab.equals(tabHeader.tab)) {\n                        selectedTabOffset = (side == Side.LEFT || side == Side.BOTTOM) ? -offset - tabHeaderPrefWidth : offset;\n                        selectedTabWidth = tabHeaderPrefWidth;\n                        break;\n                    }\n                    offset += tabHeaderPrefWidth;\n                }\n            }\n            // animate the tab selection\n            runTimeline(selectedTabOffset, selectedTabWidth);\n        }\n    }\n\n    /**************************************************************************\n     *                                                                          *\n     * TabHeaderContainer: each tab Container                                  *\n     *                                                                          *\n     **************************************************************************/\n\n    protected class TabHeaderContainer extends StackPane {\n\n        private Tab tab = null;\n        private Label tabText;\n        private Tooltip oldTooltip;\n        private Tooltip tooltip;\n        private BorderPane inner;\n        private JFXRippler rippler;\n        private boolean systemChange = false;\n        private boolean isClosing = false;\n\n        private final MultiplePropertyChangeListenerHandler listener = new MultiplePropertyChangeListenerHandler(param -> {\n            handlePropertyChanged(param);\n            return null;\n        });\n\n        private final ListChangeListener<String> styleClassListener = (Change<? extends String> change) -> getStyleClass().setAll(tab.getStyleClass());\n\n        private final WeakListChangeListener<String> weakStyleClassListener = new WeakListChangeListener<>(styleClassListener);\n\n        public TabHeaderContainer(final Tab tab) {\n            this.tab = tab;\n\n            getStyleClass().setAll(tab.getStyleClass());\n            setId(tab.getId());\n            setStyle(tab.getStyle());\n\n            tabText = new Label(tab.getText(), tab.getGraphic());\n            tabText.setFont(Font.font(\"\", FontWeight.BOLD, 16));\n            tabText.setPadding(new Insets(5, 10, 5, 10));\n            tabText.getStyleClass().setAll(\"tab-label\");\n\n            inner = new BorderPane();\n            inner.setCenter(tabText);\n            inner.getStyleClass().add(\"tab-container\");\n            inner.setRotate(getSkinnable().getSide().equals(Side.BOTTOM) ? 180.0F : 0.0F);\n\n            rippler = new JFXRippler(inner, RipplerPos.FRONT);\n            rippler.setRipplerFill(ripplerColor);\n            getChildren().addAll(rippler);\n\n            tooltip = tab.getTooltip();\n            if (tooltip != null) {\n                Tooltip.install(this, tooltip);\n                oldTooltip = tooltip;\n            }\n\n            if (tab.isSelected()) {\n                tabText.setTextFill(selectedTabText);\n            } else {\n                tabText.setTextFill(tempLabelColor.deriveColor(0, 0, 0.9, 1));\n            }\n\n\n            tabText.textFillProperty().addListener((o, oldVal, newVal) -> {\n                if (!systemChange) {\n                    tempLabelColor = (Color) newVal;\n                }\n            });\n\n            tab.selectedProperty().addListener((o, oldVal, newVal) -> {\n                systemChange = true;\n                if (newVal) {\n                    tabText.setTextFill(tempLabelColor);\n                } else {\n                    tabText.setTextFill(tempLabelColor.deriveColor(0, 0, 0.9, 1));\n                }\n                systemChange = false;\n            });\n\n\n            listener.registerChangeListener(tab.selectedProperty(), \"SELECTED\");\n            listener.registerChangeListener(tab.textProperty(), \"TEXT\");\n            listener.registerChangeListener(tab.graphicProperty(), \"GRAPHIC\");\n            listener.registerChangeListener(tab.tooltipProperty(), \"TOOLTIP\");\n            listener.registerChangeListener(tab.disableProperty(), \"DISABLE\");\n            listener.registerChangeListener(tab.styleProperty(), \"STYLE\");\n            listener.registerChangeListener(getSkinnable().tabMinWidthProperty(), \"TAB_MIN_WIDTH\");\n            listener.registerChangeListener(getSkinnable().tabMaxWidthProperty(), \"TAB_MAX_WIDTH\");\n            listener.registerChangeListener(getSkinnable().tabMinHeightProperty(), \"TAB_MIN_HEIGHT\");\n            listener.registerChangeListener(getSkinnable().tabMaxHeightProperty(), \"TAB_MAX_HEIGHT\");\n            listener.registerChangeListener(getSkinnable().sideProperty(), \"SIDE\");\n            tab.getStyleClass().addListener(weakStyleClassListener);\n\n            getProperties().put(Tab.class, tab);\n\n            setOnMouseClicked((event) -> {\n                if (tab.isDisable() || !event.isStillSincePress()) {\n                    return;\n                }\n                if (event.getButton() == MouseButton.PRIMARY) {\n                    setOpacity(1);\n                    TabPane tabPane = tab.getTabPane();\n                    if (tabPane != null) {\n                        tabPane.getSelectionModel().select(tab);\n                    }\n                }\n            });\n\n            addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {\n                ContextMenu contextMenu = tab.getContextMenu();\n                if (contextMenu != null) {\n                    contextMenu.show(tabText, event.getScreenX(), event.getScreenY());\n                    event.consume();\n                }\n            });\n\n            // initialize pseudo-class state\n            pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());\n            pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());\n            final Side side = getSkinnable().getSide();\n            pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));\n            pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));\n            pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));\n            pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));\n        }\n\n        private void handlePropertyChanged(final String p) {\n            if (\"SELECTED\".equals(p)) {\n                pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());\n                inner.requestLayout();\n                requestLayout();\n            } else if (\"TEXT\".equals(p)) {\n                tabText.setText(tab.getText());\n            } else if (\"GRAPHIC\".equals(p)) {\n                tabText.setGraphic(tab.getGraphic());\n            } else if (\"TOOLTIP\".equals(p)) {\n                // install new Tooltip / uninstall the old one\n                if (oldTooltip != null) {\n                    Tooltip.uninstall(this, oldTooltip);\n                }\n                tooltip = tab.getTooltip();\n                if (tooltip != null) {\n                    Tooltip.install(this, tooltip);\n                    oldTooltip = tooltip;\n                }\n            } else if (\"DISABLE\".equals(p)) {\n                pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());\n                inner.requestLayout();\n                requestLayout();\n            } else if (\"STYLE\".equals(p)) {\n                setStyle(tab.getStyle());\n            } else if (\"TAB_MIN_WIDTH\".equals(p)) {\n                requestLayout();\n                getSkinnable().requestLayout();\n            } else if (\"TAB_MAX_WIDTH\".equals(p)) {\n                requestLayout();\n                getSkinnable().requestLayout();\n            } else if (\"TAB_MIN_HEIGHT\".equals(p)) {\n                requestLayout();\n                getSkinnable().requestLayout();\n            } else if (\"TAB_MAX_HEIGHT\".equals(p)) {\n                requestLayout();\n                getSkinnable().requestLayout();\n            } else if (\"SIDE\".equals(p)) {\n                final Side side = getSkinnable().getSide();\n                pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));\n                pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));\n                pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));\n                pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));\n                inner.setRotate(side == Side.BOTTOM ? 180.0F : 0.0F);\n            }\n        }\n\n        private void removeListeners(Tab tab) {\n            listener.dispose();\n            inner.getChildren().clear();\n            getChildren().clear();\n        }\n\n        @Override\n        protected double computePrefWidth(double height) {\n            double minWidth = snapSize(getSkinnable().getTabMinWidth());\n            double maxWidth = snapSize(getSkinnable().getTabMaxWidth());\n            double paddingRight = snappedRightInset();\n            double paddingLeft = snappedLeftInset();\n            double tmpPrefWidth = snapSize(tabText.prefWidth(-1));\n\n            if (tmpPrefWidth > maxWidth) {\n                tmpPrefWidth = maxWidth;\n            } else if (tmpPrefWidth < minWidth) {\n                tmpPrefWidth = minWidth;\n            }\n            tmpPrefWidth += paddingRight + paddingLeft;\n            return tmpPrefWidth;\n        }\n\n        @Override\n        protected double computePrefHeight(double width) {\n            double minHeight = snapSize(getSkinnable().getTabMinHeight());\n            double maxHeight = snapSize(getSkinnable().getTabMaxHeight());\n            double paddingTop = snappedTopInset();\n            double paddingBottom = snappedBottomInset();\n            double tmpPrefHeight = snapSize(tabText.prefHeight(width));\n\n            if (tmpPrefHeight > maxHeight) {\n                tmpPrefHeight = maxHeight;\n            } else if (tmpPrefHeight < minHeight) {\n                tmpPrefHeight = minHeight;\n            }\n            tmpPrefHeight += paddingTop + paddingBottom;\n            return tmpPrefHeight;\n        }\n\n        @Override\n        protected void layoutChildren() {\n            double w = snapSize(getWidth()) - snappedRightInset() - snappedLeftInset();\n            rippler.resize(w, snapSize(getHeight()) - snappedTopInset() - snappedBottomInset());\n            rippler.relocate(snappedLeftInset(), snappedTopInset());\n        }\n\n        @Override\n        protected void setWidth(double value) {\n            super.setWidth(value);\n        }\n\n        @Override\n        protected void setHeight(double value) {\n            super.setHeight(value);\n        }\n    }\n\n    private static final PseudoClass SELECTED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass(\"selected\");\n    private static final PseudoClass DISABLED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass(\"disabled\");\n    private static final PseudoClass TOP_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass(\"top\");\n    private static final PseudoClass BOTTOM_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass(\"bottom\");\n    private static final PseudoClass LEFT_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass(\"left\");\n    private static final PseudoClass RIGHT_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass(\"right\");\n\n    /**************************************************************************\n     *                                                                          *\n     * TabContentHolder: each tab content container                              *\n     *                                                                          *\n     **************************************************************************/\n    protected static class TabContentHolder extends StackPane {\n        private Tab tab;\n        private InvalidationListener tabContentListener = valueModel -> updateContent();\n        private InvalidationListener tabSelectedListener = valueModel -> setVisible(tab.isSelected());\n        private WeakInvalidationListener weakTabContentListener = new WeakInvalidationListener(tabContentListener);\n        private WeakInvalidationListener weakTabSelectedListener = new WeakInvalidationListener(tabSelectedListener);\n\n        public TabContentHolder(Tab tab) {\n            this.tab = tab;\n            getStyleClass().setAll(\"tab-content-area\");\n            setManaged(false);\n            updateContent();\n            setVisible(tab.isSelected());\n            tab.selectedProperty().addListener(weakTabSelectedListener);\n            tab.contentProperty().addListener(weakTabContentListener);\n        }\n\n        private void updateContent() {\n            Node newContent = tab.getContent();\n            if (newContent == null) {\n                getChildren().clear();\n            } else {\n                getChildren().setAll(newContent);\n            }\n        }\n\n        private void removeListeners(Tab tab) {\n            tab.selectedProperty().removeListener(weakTabSelectedListener);\n            tab.contentProperty().removeListener(weakTabContentListener);\n        }\n    }\n\n    private enum ArrowPosition {\n        RIGHT, LEFT\n    }\n\n    /**************************************************************************\n     *                                                                          *\n     * HeaderControl: left/right controls to interact with HeaderContainer*\n     *                                                                          *\n     **************************************************************************/\n    protected class HeaderControl extends StackPane {\n        private StackPane inner;\n        private boolean showControlButtons, isLeftArrow;\n        private Timeline arrowAnimation;\n        private SVGGlyph arrowButton;\n        private SVGGlyph leftChevron = new SVGGlyph(0, \"CHEVRON_LEFT\", \"M 742,-37 90,614 Q 53,651 53,704.5 53,758 90,795 l 652,651 q 37,37 90.5,37 53.5,0 90.5,-37 l 75,-75 q 37,-37 37,-90.5 0,-53.5 -37,-90.5 L 512,704 998,219 q 37,-38 37,-91 0,-53 -37,-90 L 923,-37 Q 886,-74 832.5,-74 779,-74 742,-37 z\", Color.WHITE);\n        private SVGGlyph rightChevron = new SVGGlyph(0, \"CHEVRON_RIGHT\", \"m 1099,704 q 0,-52 -37,-91 L 410,-38 q -37,-37 -90,-37 -53,0 -90,37 l -76,75 q -37,39 -37,91 0,53 37,90 l 486,486 -486,485 q -37,39 -37,91 0,53 37,90 l 76,75 q 36,38 90,38 54,0 90,-38 l 652,-651 q 37,-37 37,-90 z\", Color.WHITE);\n\n        public HeaderControl(ArrowPosition pos) {\n            getStyleClass().setAll(\"control-buttons-tab\");\n            isLeftArrow = pos == ArrowPosition.LEFT;\n            arrowButton = isLeftArrow ? leftChevron : rightChevron;\n            arrowButton.setStyle(\"-fx-min-width:0.8em;-fx-max-width:0.8em;-fx-min-height:1.3em;-fx-max-height:1.3em;\");\n            arrowButton.getStyleClass().setAll(\"tab-down-button\");\n            arrowButton.setVisible(isControlButtonShown());\n            arrowButton.setFill(selectedTabText);\n\n            DoubleProperty offsetProperty = new SimpleDoubleProperty(0);\n            offsetProperty.addListener((o, oldVal, newVal) -> header.updateScrollOffset(newVal.doubleValue()));\n\n            StackPane container = new StackPane(arrowButton);\n            container.getStyleClass().add(\"container\");\n            container.setPadding(new Insets(7));\n            container.setCursor(Cursor.HAND);\n\n            container.setOnMousePressed(press -> {\n                offsetProperty.set(header.scrollOffset);\n                double offset = isLeftArrow ? header.scrollOffset + header.headersRegion.getWidth() : header.scrollOffset - header.headersRegion.getWidth();\n                arrowAnimation = new Timeline(new KeyFrame(Duration.seconds(1), new KeyValue(offsetProperty, offset, Interpolator.LINEAR)));\n                arrowAnimation.play();\n            });\n            container.setOnMouseReleased(release -> arrowAnimation.stop());\n            JFXRippler arrowRippler = new JFXRippler(container, RipplerMask.CIRCLE, RipplerPos.BACK);\n            arrowRippler.ripplerFillProperty().bind(arrowButton.fillProperty());\n            StackPane.setMargin(arrowButton, new Insets(0, 0, 0, isLeftArrow ? -4 : 4));\n\n            inner = new StackPane() {\n                @Override\n                protected double computePrefWidth(double height) {\n                    double preferWidth = 0.0d;\n                    double maxArrowWidth = !isControlButtonShown() ? 0 : snapSize(arrowRippler.prefWidth(getHeight()));\n                    preferWidth += isControlButtonShown() ? maxArrowWidth : 0;\n                    preferWidth += (preferWidth > 0) ? snappedLeftInset() + snappedRightInset() : 0;\n                    return preferWidth;\n                }\n\n                @Override\n                protected double computePrefHeight(double width) {\n                    double prefHeight = 0.0d;\n                    prefHeight = isControlButtonShown() ? Math.max(prefHeight, snapSize(arrowRippler.prefHeight(width))) : 0;\n                    prefHeight += prefHeight > 0 ? snappedTopInset() + snappedBottomInset() : 0;\n                    return prefHeight;\n                }\n\n                @Override\n                protected void layoutChildren() {\n                    if (isControlButtonShown()) {\n                        double x = 0;\n                        double y = snappedTopInset();\n                        double width = snapSize(getWidth()) - x + snappedLeftInset();\n                        double height = snapSize(getHeight()) - y + snappedBottomInset();\n                        positionArrow(arrowRippler, x, y, width, height);\n                    }\n                }\n\n                private void positionArrow(JFXRippler rippler, double x, double y, double width, double height) {\n                    rippler.resize(width, height);\n                    positionInArea(rippler, x, y, width, height, 0, HPos.CENTER, VPos.CENTER);\n                }\n            };\n\n            arrowRippler.setPadding(new Insets(0, 5, 0, 5));\n            inner.getChildren().add(arrowRippler);\n            StackPane.setMargin(arrowRippler, new Insets(0, 4, 0, 4));\n            getChildren().add(inner);\n\n            showControlButtons = false;\n            if (isControlButtonShown()) {\n                showControlButtons = true;\n                requestLayout();\n            }\n        }\n\n        private boolean showTabsHeaderControls = false;\n\n        private void showTabsMenu(boolean value) {\n            final boolean wasTabsMenuShowing = isControlButtonShown();\n            this.showTabsHeaderControls = value;\n            if (showTabsHeaderControls && !wasTabsMenuShowing) {\n                arrowButton.setVisible(true);\n                showControlButtons = true;\n                inner.requestLayout();\n                header.requestLayout();\n            } else if (!showTabsHeaderControls && wasTabsMenuShowing) {\n                // hide control button\n                if (isControlButtonShown()) {\n                    showControlButtons = true;\n                } else {\n                    setVisible(false);\n                }\n                requestLayout();\n            }\n        }\n\n        private boolean isControlButtonShown() {\n            return showTabsHeaderControls;\n        }\n\n        @Override\n        protected double computePrefWidth(double height) {\n            double prefWidth = snapSize(inner.prefWidth(height));\n            if (prefWidth > 0) {\n                prefWidth += snappedLeftInset() + snappedRightInset();\n            }\n            return prefWidth;\n        }\n\n        @Override\n        protected double computePrefHeight(double width) {\n            return Math.max(getSkinnable().getTabMinHeight(), snapSize(inner.prefHeight(width))) + snappedTopInset() + snappedBottomInset();\n        }\n\n        @Override\n        protected void layoutChildren() {\n            double x = snappedLeftInset();\n            double y = snappedTopInset();\n            double width = snapSize(getWidth()) - x + snappedRightInset();\n            double height = snapSize(getHeight()) - y + snappedBottomInset();\n            if (showControlButtons) {\n                setVisible(true);\n                showControlButtons = false;\n            }\n            inner.resize(width, height);\n            positionInArea(inner, x, y, width, height, 0, HPos.CENTER, VPos.BOTTOM);\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.skins;\n\nimport com.jfoenix.controls.JFXRippler;\nimport com.jfoenix.controls.JFXRippler.RipplerMask;\nimport com.jfoenix.controls.JFXRippler.RipplerPos;\nimport com.jfoenix.controls.JFXToggleButton;\nimport com.jfoenix.effects.JFXDepthManager;\nimport com.jfoenix.transitions.JFXAnimationTimer;\nimport com.jfoenix.transitions.JFXKeyFrame;\nimport com.jfoenix.transitions.JFXKeyValue;\nimport javafx.animation.Interpolator;\nimport javafx.geometry.Insets;\nimport javafx.scene.Cursor;\nimport javafx.scene.control.skin.ToggleButtonSkin;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.shape.Circle;\nimport javafx.scene.shape.Line;\nimport javafx.scene.shape.StrokeLineCap;\nimport javafx.util.Duration;\n\n/**\n * <h1>Material Design ToggleButton Skin</h1>\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2016-03-09\n */\npublic class JFXToggleButtonSkin extends ToggleButtonSkin {\n\n\n    private Runnable releaseManualRippler = null;\n\n    private JFXAnimationTimer timer;\n    private final Circle circle;\n    private final Line line;\n\n    public JFXToggleButtonSkin(JFXToggleButton toggleButton) {\n        super(toggleButton);\n\n        double circleRadius = toggleButton.getSize();\n\n        line = new Line();\n        line.setStroke(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor());\n        line.setStartX(0);\n        line.setStartY(0);\n        line.setEndX(circleRadius * 2 + 2);\n        line.setEndY(0);\n        line.setStrokeWidth(circleRadius * 1.5);\n        line.setStrokeLineCap(StrokeLineCap.ROUND);\n        line.setSmooth(true);\n\n        circle = new Circle();\n        circle.setFill(getSkinnable().isSelected() ? toggleButton.getToggleColor() : toggleButton.getUnToggleColor());\n        circle.setCenterX(-circleRadius);\n        circle.setCenterY(0);\n        circle.setRadius(circleRadius);\n        circle.setSmooth(true);\n        JFXDepthManager.setDepth(circle, 1);\n\n        StackPane circlePane = new StackPane();\n        circlePane.getChildren().add(circle);\n        circlePane.setPadding(new Insets(circleRadius * 1.5));\n\n        JFXRippler rippler = new JFXRippler(circlePane, RipplerMask.CIRCLE, RipplerPos.BACK);\n        rippler.setRipplerFill(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor());\n        rippler.setTranslateX(computeTranslation(circleRadius, line));\n\n        final StackPane main = new StackPane();\n        main.getChildren().setAll(line, rippler);\n        main.setCursor(Cursor.HAND);\n\n        // show focus traversal effect\n        getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> {\n            if (newVal) {\n                releaseManualRippler = rippler.createManualRipple();\n            } else if (releaseManualRippler != null) {\n                releaseManualRippler.run();\n            }\n        });\n        toggleButton.focusedProperty().addListener((o, oldVal, newVal) -> {\n            if (!toggleButton.isDisableVisualFocus()) {\n                if (newVal) {\n                    if (!getSkinnable().isPressed()) {\n                        rippler.setOverlayVisible(true);\n                    }\n                } else {\n                    rippler.setOverlayVisible(false);\n                }\n            }\n        });\n        toggleButton.pressedProperty().addListener(observable -> rippler.setOverlayVisible(false));\n\n        // add change listener to selected property\n        getSkinnable().selectedProperty().addListener(observable -> {\n            rippler.setRipplerFill(toggleButton.isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor());\n            if (!toggleButton.isDisableAnimation()) {\n                timer.reverseAndContinue();\n            } else {\n                rippler.setTranslateX(computeTranslation(circleRadius, line));\n            }\n        });\n\n        getSkinnable().setGraphic(main);\n\n        timer = new JFXAnimationTimer(\n                new JFXKeyFrame(Duration.millis(100),\n                        JFXKeyValue.builder()\n                                .setTarget(rippler.translateXProperty())\n                                .setEndValueSupplier(() -> computeTranslation(circleRadius, line))\n                                .setInterpolator(Interpolator.EASE_BOTH)\n                                .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation())\n                                .build(),\n\n                        JFXKeyValue.builder()\n                                .setTarget(line.strokeProperty())\n                                .setEndValueSupplier(() -> getSkinnable().isSelected() ?\n                                        ((JFXToggleButton) getSkinnable()).getToggleLineColor()\n                                        : ((JFXToggleButton) getSkinnable()).getUnToggleLineColor())\n                                .setInterpolator(Interpolator.EASE_BOTH)\n                                .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation())\n                                .build(),\n\n                        JFXKeyValue.builder()\n                                .setTarget(circle.fillProperty())\n                                .setEndValueSupplier(() -> getSkinnable().isSelected() ?\n                                        ((JFXToggleButton) getSkinnable()).getToggleColor()\n                                        : ((JFXToggleButton) getSkinnable()).getUnToggleColor())\n                                .setInterpolator(Interpolator.EASE_BOTH)\n                                .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation())\n                                .build()\n                )\n        );\n        timer.setCacheNodes(circle, line);\n\n        registerChangeListener(toggleButton.toggleColorProperty(), observableValue -> {\n            if (getSkinnable().isSelected()) {\n                circle.setFill(((JFXToggleButton) getSkinnable()).getToggleColor());\n            }\n        });\n        registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> {\n            if (!getSkinnable().isSelected()) {\n                circle.setFill(((JFXToggleButton) getSkinnable()).getUnToggleColor());\n            }\n        });\n        registerChangeListener(toggleButton.toggleLineColorProperty(), observableValue -> {\n            if (getSkinnable().isSelected()) {\n                line.setStroke(((JFXToggleButton) getSkinnable()).getToggleLineColor());\n            }\n        });\n        registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> {\n            if (!getSkinnable().isSelected()) {\n                line.setStroke(((JFXToggleButton) getSkinnable()).getUnToggleLineColor());\n            }\n        });\n    }\n\n    private double computeTranslation(double circleRadius, Line line) {\n        return (getSkinnable().isSelected() ? 1 : -1) * ((line.getLayoutBounds().getWidth() / 2) - circleRadius + 2);\n    }\n\n    @Override\n    public void dispose() {\n        super.dispose();\n        timer.dispose();\n        timer = null;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/transitions/CacheMemento.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.transitions;\n\nimport javafx.scene.CacheHint;\nimport javafx.scene.Node;\nimport javafx.scene.layout.Region;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\npublic final class CacheMemento {\n    private boolean cache;\n    private boolean cacheShape;\n    private boolean snapToPixel;\n    private CacheHint cacheHint = CacheHint.DEFAULT;\n    private final Node node;\n    private final AtomicBoolean isCached = new AtomicBoolean(false);\n\n    public CacheMemento(Node node) {\n        this.node = node;\n    }\n\n    /**\n     * this method will cache the node only if it wasn't cached before\n     */\n    public void cache() {\n        if (!isCached.getAndSet(true)) {\n            this.cache = node.isCache();\n            this.cacheHint = node.getCacheHint();\n            node.setCache(true);\n            node.setCacheHint(CacheHint.SPEED);\n            if (node instanceof Region region) {\n                this.cacheShape = region.isCacheShape();\n                this.snapToPixel = region.isSnapToPixel();\n                region.setCacheShape(true);\n                region.setSnapToPixel(true);\n            }\n        }\n    }\n\n    public void restore() {\n        if (isCached.getAndSet(false)) {\n            node.setCache(cache);\n            node.setCacheHint(cacheHint);\n            if (node instanceof Region region) {\n                region.setCacheShape(cacheShape);\n                region.setSnapToPixel(snapToPixel);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/transitions/JFXAnimationTimer.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.transitions;\n\nimport javafx.animation.AnimationTimer;\nimport javafx.beans.value.WritableValue;\nimport javafx.scene.Node;\nimport javafx.util.Duration;\n\nimport java.util.*;\nimport java.util.function.Supplier;\n\n/**\n * Custom AnimationTimer that can be created the same way as a timeline,\n * however it doesn't behave the same yet. it only animates in one direction,\n * it doesn't support animation 0 -> 1 -> 0.5\n *\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2017-09-21\n */\n\npublic class JFXAnimationTimer extends AnimationTimer {\n\n    private Set<AnimationHandler> animationHandlers = new HashSet<>();\n    private long startTime = -1;\n    private boolean running = false;\n    private List<CacheMemento> caches = new ArrayList<>();\n    private double totalElapsedMilliseconds;\n\n    public JFXAnimationTimer(JFXKeyFrame... keyFrames) {\n        for (JFXKeyFrame keyFrame : keyFrames) {\n            Duration duration = keyFrame.getDuration();\n            final Set<JFXKeyValue<?>> keyValuesSet = keyFrame.getValues();\n            if (!keyValuesSet.isEmpty()) {\n                animationHandlers.add(new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues()));\n            }\n        }\n    }\n\n    private final HashMap<JFXKeyFrame, AnimationHandler> mutableFrames = new HashMap<>();\n\n    public void addKeyFrame(JFXKeyFrame keyFrame) throws Exception {\n        if (isRunning()) {\n            throw new Exception(\"Can't update animation timer while running\");\n        }\n        Duration duration = keyFrame.getDuration();\n        final Set<JFXKeyValue<?>> keyValuesSet = keyFrame.getValues();\n        if (!keyValuesSet.isEmpty()) {\n            final AnimationHandler handler = new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues());\n            animationHandlers.add(handler);\n            mutableFrames.put(keyFrame, handler);\n        }\n    }\n\n    public void removeKeyFrame(JFXKeyFrame keyFrame) throws Exception {\n        if (isRunning()) {\n            throw new Exception(\"Can't update animation timer while running\");\n        }\n        AnimationHandler handler = mutableFrames.get(keyFrame);\n        animationHandlers.remove(handler);\n    }\n\n    @Override\n    public void start() {\n        super.start();\n        running = true;\n        startTime = -1;\n        for (AnimationHandler animationHandler : animationHandlers) {\n            animationHandler.init();\n        }\n        for (CacheMemento cache : caches) {\n            cache.cache();\n        }\n    }\n\n    @Override\n    public void handle(long now) {\n        startTime = startTime == -1 ? now : startTime;\n        totalElapsedMilliseconds = (now - startTime) / 1000000.0;\n        boolean stop = true;\n        for (AnimationHandler handler : animationHandlers) {\n            handler.animate(totalElapsedMilliseconds);\n            if (!handler.finished) {\n                stop = false;\n            }\n        }\n        if (stop) {\n            this.stop();\n        }\n    }\n\n    /**\n     * this method will pause the timer and reverse the animation if the timer already\n     * started otherwise it will start the animation.\n     */\n    public void reverseAndContinue() {\n        if (isRunning()) {\n            super.stop();\n            for (AnimationHandler handler : animationHandlers) {\n                handler.reverse(totalElapsedMilliseconds);\n            }\n            startTime = -1;\n            super.start();\n        } else {\n            start();\n        }\n    }\n\n    @Override\n    public void stop() {\n        super.stop();\n        running = false;\n        for (AnimationHandler handler : animationHandlers) {\n            handler.clear();\n        }\n        for (CacheMemento cache : caches) {\n            cache.restore();\n        }\n        if (onFinished != null) {\n            onFinished.run();\n        }\n    }\n\n    public void applyEndValues() {\n        if (isRunning()) {\n            super.stop();\n        }\n        for (AnimationHandler handler : animationHandlers) {\n            handler.applyEndValues();\n        }\n        startTime = -1;\n    }\n\n    public boolean isRunning() {\n        return running;\n    }\n\n    private Runnable onFinished = null;\n\n    public void setOnFinished(Runnable onFinished) {\n        this.onFinished = onFinished;\n    }\n\n    public void setCacheNodes(Node... nodesToCache) {\n        caches.clear();\n        if (nodesToCache != null) {\n            for (Node node : nodesToCache) {\n                caches.add(new CacheMemento(node));\n            }\n        }\n    }\n\n    public void dispose() {\n        caches.clear();\n        for (AnimationHandler handler : animationHandlers) {\n            handler.dispose();\n        }\n        animationHandlers.clear();\n    }\n\n    static class AnimationHandler {\n        private final double duration;\n        private double currentDuration;\n        private final Set<JFXKeyValue<?>> keyValues;\n        private Supplier<Boolean> animationCondition = null;\n        private boolean finished = false;\n\n        private final HashMap<WritableValue<?>, Object> initialValuesMap = new HashMap<>();\n        private final HashMap<WritableValue<?>, Object> endValuesMap = new HashMap<>();\n\n        AnimationHandler(Duration duration, Supplier<Boolean> animationCondition, Set<JFXKeyValue<?>> keyValues) {\n            this.duration = duration.toMillis();\n            currentDuration = this.duration;\n            this.keyValues = keyValues;\n            this.animationCondition = animationCondition;\n        }\n\n        public void init() {\n            finished = animationCondition != null && !animationCondition.get();\n            for (JFXKeyValue<?> keyValue : keyValues) {\n                if (keyValue.getTarget() != null) {\n                    // replaced putIfAbsent for mobile compatibility\n                    if (!initialValuesMap.containsKey(keyValue.getTarget())) {\n                        initialValuesMap.put(keyValue.getTarget(), keyValue.getTarget().getValue());\n                    }\n                    if (!endValuesMap.containsKey(keyValue.getTarget())) {\n                        endValuesMap.put(keyValue.getTarget(), keyValue.getEndValue());\n                    }\n                }\n            }\n        }\n\n        void reverse(double now) {\n            finished = animationCondition != null && !animationCondition.get();\n            currentDuration = duration - (currentDuration - now);\n            // update initial values\n            for (JFXKeyValue<?> keyValue : keyValues) {\n                final WritableValue<?> target = keyValue.getTarget();\n                if (target != null) {\n                    initialValuesMap.put(target, target.getValue());\n                    endValuesMap.put(target, keyValue.getEndValue());\n                }\n            }\n        }\n\n        // now in milliseconds\n        @SuppressWarnings({\"unchecked\"})\n        public void animate(double now) {\n            // if animate condition for the key frame is not met then do nothing\n            if (finished) {\n                return;\n            }\n            if (now <= currentDuration) {\n                for (JFXKeyValue<?> keyValue : keyValues) {\n                    if (keyValue.isValid()) {\n                        @SuppressWarnings(\"rawtypes\") final WritableValue target = keyValue.getTarget();\n                        final Object endValue = endValuesMap.get(target);\n                        if (endValue != null && target != null && !target.getValue().equals(endValue)) {\n                            target.setValue(keyValue.getInterpolator().interpolate(initialValuesMap.get(target), endValue, now / currentDuration));\n                        }\n                    }\n                }\n            } else {\n                if (!finished) {\n                    finished = true;\n                    for (JFXKeyValue<?> keyValue : keyValues) {\n                        if (keyValue.isValid()) {\n                            @SuppressWarnings(\"rawtypes\") final WritableValue target = keyValue.getTarget();\n                            if (target != null) {\n                                // set updated end value instead of cached\n                                final Object endValue = keyValue.getEndValue();\n                                if (endValue != null) {\n                                    target.setValue(endValue);\n                                }\n                            }\n                        }\n                    }\n                    currentDuration = duration;\n                }\n            }\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        public void applyEndValues() {\n            for (JFXKeyValue<?> keyValue : keyValues) {\n                if (keyValue.isValid()) {\n                    @SuppressWarnings(\"rawtypes\") final WritableValue target = keyValue.getTarget();\n                    if (target != null) {\n                        final Object endValue = keyValue.getEndValue();\n                        if (endValue != null && !target.getValue().equals(endValue)) {\n                            target.setValue(endValue);\n                        }\n                    }\n                }\n            }\n        }\n\n        public void clear() {\n            initialValuesMap.clear();\n            endValuesMap.clear();\n        }\n\n        void dispose() {\n            clear();\n            keyValues.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/transitions/JFXKeyFrame.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.transitions;\n\nimport javafx.util.Duration;\n\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArraySet;\nimport java.util.function.Supplier;\n\n/**\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2017-09-21\n */\n\npublic class JFXKeyFrame {\n\n    private Duration duration;\n    private Set<JFXKeyValue<?>> keyValues = new CopyOnWriteArraySet<>();\n    private Supplier<Boolean> animateCondition = null;\n\n    public JFXKeyFrame(Duration duration, JFXKeyValue<?>... keyValues) {\n        this.duration = duration;\n        for (final JFXKeyValue<?> keyValue : keyValues) {\n            if (keyValue != null) {\n                this.keyValues.add(keyValue);\n            }\n        }\n    }\n\n    private JFXKeyFrame() {\n\n    }\n\n    public final Duration getDuration() {\n        return duration;\n    }\n\n    public final Set<JFXKeyValue<?>> getValues() {\n        return keyValues;\n    }\n\n    public Supplier<Boolean> getAnimateCondition() {\n        return animateCondition;\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public static final class Builder {\n        private Duration duration;\n        private final Set<JFXKeyValue<?>> keyValues = new CopyOnWriteArraySet<>();\n        private Supplier<Boolean> animateCondition = null;\n\n        private Builder() {\n        }\n\n        public Builder setDuration(Duration duration) {\n            this.duration = duration;\n            return this;\n        }\n\n        public Builder setKeyValues(JFXKeyValue<?>... keyValues) {\n            for (final JFXKeyValue<?> keyValue : keyValues) {\n                if (keyValue != null) {\n                    this.keyValues.add(keyValue);\n                }\n            }\n            return this;\n        }\n\n        public Builder setAnimateCondition(Supplier<Boolean> animateCondition) {\n            this.animateCondition = animateCondition;\n            return this;\n        }\n\n        public JFXKeyFrame build() {\n            JFXKeyFrame jFXKeyFrame = new JFXKeyFrame();\n            jFXKeyFrame.duration = this.duration;\n            jFXKeyFrame.keyValues = this.keyValues;\n            jFXKeyFrame.animateCondition = this.animateCondition;\n            return jFXKeyFrame;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/transitions/JFXKeyValue.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.transitions;\n\nimport javafx.animation.Interpolator;\nimport javafx.beans.value.WritableValue;\n\nimport java.util.function.Supplier;\n\n/**\n * @author Shadi Shaheen\n * @version 1.0\n * @since 2017-09-21\n */\n\npublic final class JFXKeyValue<T> {\n\n    private WritableValue<T> target;\n    private Supplier<WritableValue<T>> targetSupplier;\n    private Supplier<T> endValueSupplier;\n    private T endValue;\n    private Supplier<Boolean> animateCondition = () -> true;\n    private Interpolator interpolator;\n\n    private JFXKeyValue() {\n    }\n\n    // this builder is created to ensure type inference from method arguments\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public T getEndValue() {\n        return endValue == null ? endValueSupplier.get() : endValue;\n    }\n\n    public WritableValue<T> getTarget() {\n        return target == null ? targetSupplier.get() : target;\n    }\n\n    public Interpolator getInterpolator() {\n        return interpolator;\n    }\n\n    public boolean isValid() {\n        return animateCondition == null || animateCondition.get();\n    }\n\n    public static final class Builder {\n        public <T> JFXKeyValueBuilder<T> setTarget(WritableValue<T> target) {\n            JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();\n            builder.setTarget(target);\n            return builder;\n        }\n\n        public <T> JFXKeyValueBuilder<T> setTargetSupplier(Supplier<WritableValue<T>> targetSupplier) {\n            JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();\n            builder.setTargetSupplier(targetSupplier);\n            return builder;\n        }\n\n        public <T> JFXKeyValueBuilder<T> setEndValueSupplier(Supplier<T> endValueSupplier) {\n            JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();\n            builder.setEndValueSupplier(endValueSupplier);\n            return builder;\n        }\n\n        public <T> JFXKeyValueBuilder<T> setEndValue(T endValue) {\n            JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();\n            builder.setEndValue(endValue);\n            return builder;\n        }\n\n        public <T> JFXKeyValueBuilder<T> setAnimateCondition(Supplier<Boolean> animateCondition) {\n            JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();\n            builder.setAnimateCondition(animateCondition);\n            return builder;\n        }\n\n        public <T> JFXKeyValueBuilder<T> setInterpolator(Interpolator interpolator) {\n            JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();\n            builder.setInterpolator(interpolator);\n            return builder;\n        }\n    }\n\n    public static final class JFXKeyValueBuilder<T> {\n\n        private WritableValue<T> target;\n        private Supplier<WritableValue<T>> targetSupplier;\n        private Supplier<T> endValueSupplier;\n        private T endValue;\n        private Supplier<Boolean> animateCondition = () -> true;\n        private Interpolator interpolator = Interpolator.EASE_BOTH;\n\n        private JFXKeyValueBuilder() {\n        }\n\n        public JFXKeyValueBuilder<T> setTarget(WritableValue<T> target) {\n            this.target = target;\n            return this;\n        }\n\n        public JFXKeyValueBuilder<T> setTargetSupplier(Supplier<WritableValue<T>> targetSupplier) {\n            this.targetSupplier = targetSupplier;\n            return this;\n        }\n\n        public JFXKeyValueBuilder<T> setEndValueSupplier(Supplier<T> endValueSupplier) {\n            this.endValueSupplier = endValueSupplier;\n            return this;\n        }\n\n        public JFXKeyValueBuilder<T> setEndValue(T endValue) {\n            this.endValue = endValue;\n            return this;\n        }\n\n        public JFXKeyValueBuilder<T> setAnimateCondition(Supplier<Boolean> animateCondition) {\n            this.animateCondition = animateCondition;\n            return this;\n        }\n\n        public JFXKeyValueBuilder<T> setInterpolator(Interpolator interpolator) {\n            this.interpolator = interpolator;\n            return this;\n        }\n\n        public JFXKeyValue<T> build() {\n            JFXKeyValue<T> jFXKeyValue = new JFXKeyValue<>();\n            jFXKeyValue.target = this.target;\n            jFXKeyValue.interpolator = this.interpolator;\n            jFXKeyValue.targetSupplier = this.targetSupplier;\n            jFXKeyValue.endValue = this.endValue;\n            jFXKeyValue.endValueSupplier = this.endValueSupplier;\n            jFXKeyValue.animateCondition = this.animateCondition;\n            return jFXKeyValue;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/utils/JFXNodeUtils.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership.  The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License.  You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied.  See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n\npackage com.jfoenix.utils;\n\nimport javafx.beans.value.ObservableBooleanValue;\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundFill;\nimport javafx.scene.layout.Region;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\nimport javafx.stage.Window;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.lang.reflect.Method;\nimport java.util.Locale;\nimport java.util.function.Function;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author Shadi Shaheen\n/// @version 1.0\n/// @since 2017-02-11\npublic final class JFXNodeUtils {\n\n    public static void updateBackground(Background newBackground, Region nodeToUpdate) {\n        updateBackground(newBackground, nodeToUpdate, Color.BLACK);\n    }\n\n    public static void updateBackground(Background newBackground, Region nodeToUpdate, Paint fill) {\n        if (newBackground != null && !newBackground.getFills().isEmpty()) {\n            final BackgroundFill[] fills = new BackgroundFill[newBackground.getFills().size()];\n            for (int i = 0; i < newBackground.getFills().size(); i++) {\n                BackgroundFill bf = newBackground.getFills().get(i);\n                fills[i] = new BackgroundFill(fill, bf.getRadii(), bf.getInsets());\n            }\n            nodeToUpdate.setBackground(new Background(fills));\n        }\n    }\n\n    public static String colorToHex(Color c) {\n        if (c != null) {\n            return String.format((Locale) null, \"#%02X%02X%02X\",\n                    Math.round(c.getRed() * 255),\n                    Math.round(c.getGreen() * 255),\n                    Math.round(c.getBlue() * 255));\n        } else {\n            return null;\n        }\n    }\n\n    private static final @NotNull Function<Node, ObservableBooleanValue> treeVisiblePropertyGetter = initTreeVisiblePropertyGetter();\n\n    private static @NotNull Function<Node, ObservableBooleanValue> initTreeVisiblePropertyGetter() {\n\n        MethodHandles.Lookup lookup;\n        try {\n            lookup = MethodHandles.privateLookupIn(Node.class, MethodHandles.lookup());\n        } catch (IllegalAccessException e) {\n            LOG.warning(\"Failed to get private lookup for Node\", e);\n            return JFXNodeUtils::defaultTreeVisibleProperty;\n        }\n\n        try {\n            Method treeVisiblePropertyMethod = Node.class.getDeclaredMethod(\"treeVisibleProperty\");\n            if (!ObservableBooleanValue.class.isAssignableFrom(treeVisiblePropertyMethod.getReturnType())) {\n                LOG.warning(\"Node.treeVisibleProperty() does not return ObservableBooleanValue: \" + treeVisiblePropertyMethod.getReturnType());\n                return JFXNodeUtils::defaultTreeVisibleProperty;\n            }\n\n            MethodHandle handle = lookup.unreflect(treeVisiblePropertyMethod)\n                    .asType(MethodType.methodType(ObservableBooleanValue.class, Node.class));\n            return item -> {\n                try {\n                    return (ObservableBooleanValue) handle.invokeExact((Node) item);\n                } catch (RuntimeException | Error e) {\n                    throw e;\n                } catch (Throwable e) {\n                    throw new AssertionError(\"Unreachable\", e);\n                }\n            };\n        } catch (Exception e) {\n            LOG.warning(\"Failed to get method handle for Node.treeVisibleProperty()\", e);\n            return JFXNodeUtils::defaultTreeVisibleProperty;\n        }\n    }\n\n    /// If `Node.treeVisibleProperty()` does not exist, use `Node.visibleProperty()` as a fallback\n    private static @NotNull ObservableBooleanValue defaultTreeVisibleProperty(Node item) {\n        return item.visibleProperty();\n    }\n\n    public static @NotNull ObservableBooleanValue treeVisibleProperty(Node item) {\n        return treeVisiblePropertyGetter.apply(item);\n    }\n\n    public static boolean isTreeVisible(Node item) {\n        return treeVisibleProperty(item).getValue();\n    }\n\n    public static boolean isTreeShowing(Node node) {\n        if (node == null)\n            return false;\n\n        Scene scene = node.getScene();\n        if (scene == null)\n            return false;\n\n        Window window = scene.getWindow();\n        if (window == null || !window.isShowing())\n            return false;\n\n        return isTreeVisible(node);\n    }\n\n    private JFXNodeUtils() {\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/com/jfoenix/utils/TreeShowingProperty.java",
    "content": "/*\n * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\n\npackage com.jfoenix.utils;\n\nimport javafx.beans.property.ReadOnlyBooleanPropertyBase;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableBooleanValue;\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.stage.Window;\n\n/**\n * Used to observe changes in tree showing status for a {@link Node}.  For a Node's tree to be showing\n * it must be visible, its ancestors must be visible, the node must be part of a {@link Scene} and\n * the scene must have a {@link Window} which is currently showing.<p>\n *\n * This class provides the exact same functionality as {@link JFXNodeUtils#isTreeShowing(Node)} in\n * an observable form.\n */\npublic class TreeShowingProperty extends ReadOnlyBooleanPropertyBase {\n    private final ChangeListener<Boolean> windowShowingChangedListener = (obs, old, current) -> updateTreeShowing();\n    private final ChangeListener<Window> sceneWindowChangedListener = (obs, old, current) -> windowChanged(old, current);\n    private final ChangeListener<Scene> nodeSceneChangedListener = (obs, old, current) -> sceneChanged(old, current);\n\n    private final Node node;\n    private final ObservableBooleanValue treeVisibleProperty;\n\n    private boolean valid;\n    private boolean treeShowing;\n\n    /**\n     * Constructs a new instance.\n     *\n     * @param node a {@link Node} for which the tree showing status should be observed, cannot be null\n     */\n    public TreeShowingProperty(Node node) {\n        this.node = node;\n        this.treeVisibleProperty = JFXNodeUtils.treeVisibleProperty(node);\n\n        this.node.sceneProperty().addListener(nodeSceneChangedListener);\n        this.treeVisibleProperty.addListener(windowShowingChangedListener);\n\n        sceneChanged(null, node.getScene());\n    }\n\n    @Override\n    public Object getBean() {\n        return node;\n    }\n\n    @Override\n    public String getName() {\n        return \"treeShowing\";\n    }\n\n    /**\n     * Cleans up any listeners that this class may have registered on the {@link Node}\n     * that was supplied at construction.\n     */\n    public void dispose() {\n        node.sceneProperty().removeListener(nodeSceneChangedListener);\n\n        if (treeVisibleProperty != null)\n            treeVisibleProperty.removeListener(windowShowingChangedListener);\n\n        valid = false;  // prevents unregistration from triggering an invalidation notification\n        sceneChanged(node.getScene(), null);\n    }\n\n    protected void invalidate() {\n        if (valid) {\n            valid = false;\n            fireValueChangedEvent();\n        }\n    }\n\n    @Override\n    public boolean get() {\n        if (!valid) {\n            updateTreeShowing();\n            valid = true;\n        }\n\n        return treeShowing;\n    }\n\n    private void sceneChanged(Scene oldScene, Scene newScene) {\n        if (oldScene != null) {\n            oldScene.windowProperty().removeListener(sceneWindowChangedListener);\n        }\n        if (newScene != null) {\n            newScene.windowProperty().addListener(sceneWindowChangedListener);\n        }\n\n        windowChanged(\n                oldScene == null ? null : oldScene.getWindow(),\n                newScene == null ? null : newScene.getWindow()\n        );\n    }\n\n    private void windowChanged(Window oldWindow, Window newWindow) {\n        if (oldWindow != null) {\n            oldWindow.showingProperty().removeListener(windowShowingChangedListener);\n        }\n        if (newWindow != null) {\n            newWindow.showingProperty().addListener(windowShowingChangedListener);\n        }\n\n        updateTreeShowing();\n    }\n\n    private void updateTreeShowing() {\n        boolean newValue = JFXNodeUtils.isTreeShowing(node);\n\n        if (newValue != treeShowing) {\n            treeShowing = newValue;\n            invalidate();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/EntryPoint.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\nimport org.jackhuang.hmcl.util.FileSaver;\nimport org.jackhuang.hmcl.util.SelfDependencyPatcher;\nimport org.jackhuang.hmcl.util.SwingUtils;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.io.IOException;\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.concurrent.CancellationException;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class EntryPoint {\n\n    private EntryPoint() {\n    }\n\n    public static void main(String[] args) {\n        System.getProperties().putIfAbsent(\"java.net.useSystemProxies\", \"true\");\n        System.getProperties().putIfAbsent(\"javafx.autoproxy.disable\", \"true\");\n        System.getProperties().putIfAbsent(\"http.agent\", \"HMCL/\" + Metadata.VERSION);\n\n        createHMCLDirectories();\n        LOG.start(Metadata.HMCL_CURRENT_DIRECTORY.resolve(\"logs\"));\n\n        setupJavaFXVMOptions();\n\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n            System.getProperties().putIfAbsent(\"apple.awt.application.appearance\", \"system\");\n            if (!isInsideMacAppBundle())\n                initIcon();\n        }\n\n        checkJavaFX();\n        verifyJavaFX();\n        addEnableNativeAccess();\n        enableUnsafeMemoryAccess();\n\n        Launcher.main(args);\n    }\n\n    public static void exit(int exitCode) {\n        FileSaver.shutdown();\n        LOG.shutdown();\n        System.exit(exitCode);\n    }\n\n    private static void setupJavaFXVMOptions() {\n        if (\"true\".equalsIgnoreCase(System.getenv(\"HMCL_FORCE_GPU\"))) {\n            LOG.info(\"HMCL_FORCE_GPU: true\");\n            System.getProperties().putIfAbsent(\"prism.forceGPU\", \"true\");\n        }\n\n        String animationFrameRate = System.getenv(\"HMCL_ANIMATION_FRAME_RATE\");\n        if (animationFrameRate != null) {\n            LOG.info(\"HMCL_ANIMATION_FRAME_RATE: \" + animationFrameRate);\n\n            try {\n                if (Integer.parseInt(animationFrameRate) <= 0)\n                    throw new NumberFormatException(animationFrameRate);\n\n                System.getProperties().putIfAbsent(\"javafx.animation.pulse\", animationFrameRate);\n            } catch (NumberFormatException e) {\n                LOG.warning(\"Invalid animation frame rate: \" + animationFrameRate);\n            }\n        }\n\n        String uiScale = System.getProperty(\"hmcl.uiScale\", System.getenv(\"HMCL_UI_SCALE\"));\n        if (uiScale != null) {\n            uiScale = uiScale.trim();\n\n            LOG.info(\"HMCL_UI_SCALE: \" + uiScale);\n\n            try {\n                float scaleValue;\n                if (uiScale.endsWith(\"%\")) {\n                    scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 1)) / 100.0f;\n                } else if (uiScale.endsWith(\"dpi\") || uiScale.endsWith(\"DPI\")) {\n                    scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 3)) / 96.0f;\n                } else {\n                    scaleValue = Float.parseFloat(uiScale);\n                }\n\n                float lowerBound;\n                float upperBound;\n\n                if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                    // JavaFX behavior may be abnormal when the DPI scaling factor is too high\n                    lowerBound = 0.25f;\n                    upperBound = 4f;\n                } else {\n                    lowerBound = 0.01f;\n                    upperBound = 10f;\n                }\n\n                if (scaleValue >= lowerBound && scaleValue <= upperBound) {\n                    if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                        System.getProperties().putIfAbsent(\"glass.win.uiScale\", uiScale);\n                    } else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n                        LOG.warning(\"macOS does not support setting UI scale, so it will be ignored\");\n                    } else {\n                        System.getProperties().putIfAbsent(\"glass.gtk.uiScale\", uiScale);\n                    }\n                } else {\n                    LOG.warning(\"UI scale out of range: \" + uiScale);\n                }\n            } catch (Throwable e) {\n                LOG.warning(\"Invalid UI scale: \" + uiScale);\n            }\n        }\n    }\n\n    private static void createHMCLDirectories() {\n        if (!Files.isDirectory(Metadata.HMCL_CURRENT_DIRECTORY)) {\n            try {\n                Files.createDirectories(Metadata.HMCL_CURRENT_DIRECTORY);\n                if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                    try {\n                        Files.setAttribute(Metadata.HMCL_CURRENT_DIRECTORY, \"dos:hidden\", true);\n                    } catch (IOException e) {\n                        LOG.warning(\"Failed to set hidden attribute of \" + Metadata.HMCL_CURRENT_DIRECTORY, e);\n                    }\n                }\n            } catch (IOException e) {\n                // Logger has not been started yet, so print directly to System.err\n                System.err.println(\"Failed to create HMCL directory: \" + Metadata.HMCL_CURRENT_DIRECTORY);\n                e.printStackTrace(System.err);\n                showErrorAndExit(i18n(\"fatal.create_hmcl_current_directory_failure\", Metadata.HMCL_CURRENT_DIRECTORY));\n            }\n        }\n\n        if (!Files.isDirectory(Metadata.HMCL_GLOBAL_DIRECTORY)) {\n            try {\n                Files.createDirectories(Metadata.HMCL_GLOBAL_DIRECTORY);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to create HMCL global directory \" + Metadata.HMCL_GLOBAL_DIRECTORY, e);\n            }\n        }\n    }\n\n    private static boolean isInsideMacAppBundle() {\n        Path thisJar = JarUtils.thisJarPath();\n        if (thisJar == null)\n            return false;\n\n        for (Path current = thisJar.getParent();\n             current != null && current.getParent() != null;\n             current = current.getParent()\n        ) {\n            if (\"Contents\".equals(FileUtils.getName(current))\n                    && FileUtils.getName(current.getParent()).endsWith(\".app\")\n                    && Files.exists(current.resolve(\"Info.plist\"))\n            ) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private static void initIcon() {\n        try {\n            if (java.awt.Taskbar.isTaskbarSupported()) {\n                var image = java.awt.Toolkit.getDefaultToolkit().getImage(EntryPoint.class.getResource(\"/assets/img/icon-mac.png\"));\n                java.awt.Taskbar.getTaskbar().setIconImage(image);\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to set application icon\", e);\n        }\n    }\n\n    private static void checkJavaFX() {\n        try {\n            SelfDependencyPatcher.patch();\n        } catch (SelfDependencyPatcher.PatchException e) {\n            LOG.error(\"Unable to patch JVM\", e);\n            showErrorAndExit(i18n(\"fatal.javafx.missing\"));\n        } catch (CancellationException e) {\n            LOG.error(\"User cancels downloading JavaFX\", e);\n            exit(0);\n        }\n    }\n\n    /**\n     * Check if JavaFX exists but is incomplete\n     */\n    private static void verifyJavaFX() {\n        try {\n            Class.forName(\"javafx.beans.binding.Binding\"); // javafx.base\n            Class.forName(\"javafx.stage.Stage\");           // javafx.graphics\n            Class.forName(\"javafx.scene.control.Skin\");    // javafx.controls\n        } catch (Exception e) {\n            LOG.warning(\"JavaFX is incomplete or not found\", e);\n            showErrorAndExit(i18n(\"fatal.javafx.incomplete\"));\n        }\n    }\n\n    private static void addEnableNativeAccess() {\n        if (JavaRuntime.CURRENT_VERSION > 21) {\n            try {\n                // javafx.graphics\n                Module module = Class.forName(\"javafx.stage.Stage\").getModule();\n                if (module.isNamed()) {\n                    try {\n                        MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());\n                        MethodHandle implAddEnableNativeAccess = lookup.findVirtual(Module.class,\n                                \"implAddEnableNativeAccess\", MethodType.methodType(Module.class));\n                        Module ignored = (Module) implAddEnableNativeAccess.invokeExact(module);\n                    } catch (Throwable e) {\n                        e.printStackTrace(System.err);\n                    }\n                }\n            } catch (ClassNotFoundException e) {\n                LOG.error(\"Failed to add enable native access for JavaFX\", e);\n                showErrorAndExit(i18n(\"fatal.javafx.incomplete\"));\n            }\n        }\n    }\n\n    private static void enableUnsafeMemoryAccess() {\n        // https://openjdk.org/jeps/498\n        if (JavaRuntime.CURRENT_VERSION == 24 || JavaRuntime.CURRENT_VERSION == 25) {\n            try {\n                Class<?> clazz = Class.forName(\"sun.misc.Unsafe\");\n                boolean ignored = (boolean) MethodHandles.privateLookupIn(clazz, MethodHandles.lookup())\n                        .findStatic(clazz, \"trySetMemoryAccessWarned\", MethodType.methodType(boolean.class))\n                        .invokeExact();\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to enable unsafe memory access\", e);\n            }\n        }\n    }\n\n    /**\n     * Indicates that a fatal error has occurred, and that the application cannot start.\n     */\n    private static void showErrorAndExit(String message) {\n        SwingUtils.showErrorDialog(message);\n        exit(1);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\nimport javafx.application.Application;\nimport javafx.application.Platform;\nimport javafx.beans.value.ObservableBooleanValue;\nimport javafx.geometry.Rectangle2D;\nimport javafx.scene.control.Alert;\nimport javafx.scene.control.Alert.AlertType;\nimport javafx.scene.control.ButtonType;\nimport javafx.scene.input.Clipboard;\nimport javafx.scene.input.DataFormat;\nimport javafx.stage.Screen;\nimport javafx.stage.Stage;\nimport org.jackhuang.hmcl.setting.ConfigHolder;\nimport org.jackhuang.hmcl.setting.SambaException;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.FileSaver;\nimport org.jackhuang.hmcl.task.AsyncTaskExecutor;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.upgrade.UpdateChecker;\nimport org.jackhuang.hmcl.upgrade.UpdateHandler;\nimport org.jackhuang.hmcl.util.CrashReporter;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.CommandBuilder;\nimport org.jackhuang.hmcl.util.platform.NativeUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemInfo;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.MemoryPoolMXBean;\nimport java.net.CookieHandler;\nimport java.net.CookieManager;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.text.DecimalFormat;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class Launcher extends Application {\n    public static final CookieManager COOKIE_MANAGER = new CookieManager();\n\n    @Override\n    public void start(Stage primaryStage) {\n        Thread.currentThread().setUncaughtExceptionHandler(CRASH_REPORTER);\n\n        CookieHandler.setDefault(COOKIE_MANAGER);\n\n        LOG.info(\"JavaFX Version: \" + System.getProperty(\"javafx.runtime.version\"));\n        LOG.info(\"Prism Pipeline: \" + FXUtils.GRAPHICS_PIPELINE);\n        LOG.info(\"Dark Mode: \" + Optional.ofNullable(FXUtils.DARK_MODE).map(ObservableBooleanValue::get).orElse(false));\n        LOG.info(\"Reduced Motion: \" + Objects.requireNonNullElse(FXUtils.REDUCED_MOTION, false));\n\n        if (Screen.getScreens().isEmpty()) {\n            LOG.info(\"No screen\");\n        } else {\n            StringBuilder builder = new StringBuilder(\"Screens:\");\n            int count = 0;\n            for (Screen screen : Screen.getScreens()) {\n                builder.append(\"\\n - Screen \").append(++count).append(\": \");\n                appendScreen(builder, screen);\n            }\n            LOG.info(builder.toString());\n        }\n\n        try {\n            try {\n                ConfigHolder.init();\n            } catch (SambaException e) {\n                showAlert(AlertType.WARNING, i18n(\"fatal.samba\"));\n            } catch (IOException e) {\n                LOG.error(\"Failed to load config\", e);\n                checkConfigInTempDir();\n                checkConfigOwner();\n                showAlert(AlertType.ERROR, i18n(\"fatal.config_loading_failure\", ConfigHolder.configLocation().getParent()));\n                EntryPoint.exit(1);\n            }\n\n            // https://lapcatsoftware.com/articles/app-translocation.html\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS\n                    && ConfigHolder.isNewlyCreated()\n                    && System.getProperty(\"user.dir\").startsWith(\"/private/var/folders/\")) {\n                if (showAlert(AlertType.WARNING, i18n(\"fatal.mac_app_translocation\"), ButtonType.YES, ButtonType.NO) == ButtonType.NO)\n                    return;\n            } else {\n                checkConfigInTempDir();\n            }\n\n            if (ConfigHolder.isOwnerChanged()) {\n                if (showAlert(AlertType.WARNING, i18n(\"fatal.config_change_owner_root\"), ButtonType.YES, ButtonType.NO) == ButtonType.NO)\n                    return;\n            }\n\n            if (ConfigHolder.isUnsupportedVersion()) {\n                showAlert(AlertType.WARNING, i18n(\"fatal.config_unsupported_version\"));\n            }\n\n            if (Metadata.HMCL_CURRENT_DIRECTORY.toString().indexOf('=') >= 0) {\n                showAlert(AlertType.WARNING, i18n(\"fatal.illegal_char\"));\n            }\n\n            // runLater to ensure ConfigHolder.init() finished initialization\n            Platform.runLater(() -> {\n                // When launcher visibility is set to \"hide and reopen\" without Platform.implicitExit = false,\n                // Stage.show() cannot work again because JavaFX Toolkit have already shut down.\n                Platform.setImplicitExit(false);\n                Controllers.initialize(primaryStage);\n\n                UpdateChecker.init();\n\n                primaryStage.show();\n            });\n        } catch (Throwable e) {\n            CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);\n        }\n    }\n\n    private static void appendScreen(StringBuilder builder, Screen screen) {\n        Rectangle2D bounds = screen.getBounds();\n        double scale = screen.getOutputScaleX();\n\n        builder.append(Math.round(bounds.getWidth() * scale));\n        builder.append('x');\n        builder.append(Math.round(bounds.getHeight() * scale));\n\n        DecimalFormat decimalFormat = new DecimalFormat(\"#.##\");\n\n        if (scale != 1.0) {\n            builder.append(\" @ \");\n            builder.append(decimalFormat.format(scale));\n            builder.append('x');\n        }\n\n        double dpi = screen.getDpi();\n        builder.append(' ');\n        builder.append(decimalFormat.format(dpi));\n        builder.append(\"dpi\");\n\n        builder.append(\" in \")\n                .append(Math.round(Math.sqrt(bounds.getWidth() * bounds.getWidth() + bounds.getHeight() * bounds.getHeight()) / dpi))\n                .append('\"');\n\n        builder.append(\" (\").append(decimalFormat.format(bounds.getMinX()))\n                .append(\", \").append(decimalFormat.format(bounds.getMinY()))\n                .append(\", \").append(decimalFormat.format(bounds.getMaxX()))\n                .append(\", \").append(decimalFormat.format(bounds.getMaxY()))\n                .append(\")\");\n    }\n\n    private static ButtonType showAlert(AlertType alertType, String contentText, ButtonType... buttons) {\n        return new Alert(alertType, contentText, buttons).showAndWait().orElse(null);\n    }\n\n    private static boolean isConfigInTempDir() {\n        String configPath = ConfigHolder.configLocation().toString();\n\n        String tmpdir = System.getProperty(\"java.io.tmpdir\");\n        if (StringUtils.isNotBlank(tmpdir) && configPath.startsWith(tmpdir))\n            return true;\n\n        String[] tempFolderNames = {\"Temp\", \"Cache\", \"Caches\"};\n        for (String name : tempFolderNames) {\n            if (configPath.contains(File.separator + name + File.separator))\n                return true;\n        }\n\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n            return configPath.contains(\"\\\\Temporary Internet Files\\\\\")\n                    || configPath.contains(\"\\\\INetCache\\\\\")\n                    || configPath.contains(\"\\\\$Recycle.Bin\\\\\")\n                    || configPath.contains(\"\\\\recycler\\\\\");\n        } else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {\n            return configPath.startsWith(\"/tmp/\")\n                    || configPath.startsWith(\"/var/tmp/\")\n                    || configPath.startsWith(\"/var/cache/\")\n                    || configPath.startsWith(\"/dev/shm/\")\n                    || configPath.contains(\"/Trash/\");\n        } else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n            return configPath.startsWith(\"/var/folders/\")\n                    || configPath.startsWith(\"/private/var/folders/\")\n                    || configPath.startsWith(\"/tmp/\")\n                    || configPath.startsWith(\"/private/tmp/\")\n                    || configPath.startsWith(\"/var/tmp/\")\n                    || configPath.startsWith(\"/private/var/tmp/\")\n                    || configPath.contains(\"/.Trash/\");\n        } else {\n            return false;\n        }\n    }\n\n    private static void checkConfigInTempDir() {\n        if (ConfigHolder.isNewlyCreated() && isConfigInTempDir()\n                && showAlert(AlertType.WARNING, i18n(\"fatal.config_in_temp_dir\"), ButtonType.YES, ButtonType.NO) == ButtonType.NO) {\n            EntryPoint.exit(0);\n        }\n    }\n\n    private static void checkConfigOwner() {\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)\n            return;\n\n        String userName = System.getProperty(\"user.name\");\n        String owner;\n        try {\n            owner = Files.getOwner(ConfigHolder.configLocation()).getName();\n        } catch (IOException ioe) {\n            LOG.warning(\"Failed to get file owner\", ioe);\n            return;\n        }\n\n        if (Files.isWritable(ConfigHolder.configLocation()) || userName.equals(\"root\") || userName.equals(owner))\n            return;\n\n        ArrayList<String> files = new ArrayList<>();\n        files.add(ConfigHolder.configLocation().toString());\n        if (Files.exists(Metadata.HMCL_GLOBAL_DIRECTORY))\n            files.add(Metadata.HMCL_GLOBAL_DIRECTORY.toString());\n        if (Files.exists(Metadata.HMCL_CURRENT_DIRECTORY))\n            files.add(Metadata.HMCL_CURRENT_DIRECTORY.toString());\n\n        Path mcDir = Paths.get(\".minecraft\").toAbsolutePath().normalize();\n        if (Files.exists(mcDir))\n            files.add(mcDir.toString());\n\n        String command = new CommandBuilder().addAll(\"sudo\", \"chown\", \"-R\", userName).addAll(files).toString();\n        ButtonType copyAndExit = new ButtonType(i18n(\"button.copy_and_exit\"));\n\n        if (showAlert(AlertType.ERROR,\n                i18n(\"fatal.config_loading_failure.unix\", owner, command),\n                copyAndExit, ButtonType.CLOSE) == copyAndExit) {\n            Clipboard.getSystemClipboard()\n                    .setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, command));\n        }\n        EntryPoint.exit(1);\n    }\n\n    @Override\n    public void stop() throws Exception {\n        Controllers.onApplicationStop();\n        FileSaver.shutdown();\n        LOG.shutdown();\n    }\n\n    public static void main(String[] args) {\n        if (UpdateHandler.processArguments(args)) {\n            LOG.shutdown();\n            return;\n        }\n\n        Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);\n        AsyncTaskExecutor.setUncaughtExceptionHandler(new CrashReporter(false));\n\n        try {\n            LOG.info(\"*** \" + Metadata.TITLE + \" ***\");\n            LOG.info(\"Operating System: \" + (OperatingSystem.OS_RELEASE_PRETTY_NAME == null\n                    ? OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION.getVersion()\n                    : OperatingSystem.OS_RELEASE_PRETTY_NAME + \" (\" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION.getVersion() + ')'));\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                LOG.info(\"Processor Identifier: \" + System.getenv(\"PROCESSOR_IDENTIFIER\"));\n            }\n            LOG.info(\"System Architecture: \" + Architecture.SYSTEM_ARCH.getDisplayName());\n            LOG.info(\"Native Encoding: \" + OperatingSystem.NATIVE_CHARSET);\n            LOG.info(\"JNU Encoding: \" + System.getProperty(\"sun.jnu.encoding\"));\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                LOG.info(\"Code Page: \" + OperatingSystem.CODE_PAGE);\n            }\n            LOG.info(\"Java Architecture: \" + Architecture.CURRENT_ARCH.getDisplayName());\n            LOG.info(\"Java Version: \" + System.getProperty(\"java.version\") + \", \" + System.getProperty(\"java.vendor\"));\n            LOG.info(\"Java VM Version: \" + System.getProperty(\"java.vm.name\") + \" (\" + System.getProperty(\"java.vm.info\") + \"), \" + System.getProperty(\"java.vm.vendor\"));\n            LOG.info(\"Java Home: \" + System.getProperty(\"java.home\"));\n            LOG.info(\"Current Directory: \" + Metadata.CURRENT_DIRECTORY);\n            LOG.info(\"HMCL Global Directory: \" + Metadata.HMCL_GLOBAL_DIRECTORY);\n            LOG.info(\"HMCL Current Directory: \" + Metadata.HMCL_CURRENT_DIRECTORY);\n            LOG.info(\"HMCL Jar Path: \" + Lang.requireNonNullElse(JarUtils.thisJarPath(), \"Not Found\"));\n            LOG.info(\"HMCL Log File: \" + Lang.requireNonNullElse(LOG.getLogFile(), \"In Memory\"));\n            LOG.info(\"JVM Max Memory: \" + MEGABYTES.formatBytes(Runtime.getRuntime().maxMemory()));\n            try {\n                for (MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) {\n                    if (\"Metaspace\".equals(bean.getName())) {\n                        long bytes = bean.getUsage().getUsed();\n                        LOG.info(\"Metaspace: \" + MEGABYTES.formatBytes(bytes));\n                        break;\n                    }\n                }\n            } catch (NoClassDefFoundError ignored) {\n            }\n            LOG.info(\"Native Backend: \" + (NativeUtils.USE_JNA ? \"JNA\" : \"None\"));\n            if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {\n                LOG.info(\"XDG Session Type: \" + System.getenv(\"XDG_SESSION_TYPE\"));\n                LOG.info(\"XDG Current Desktop: \" + System.getenv(\"XDG_CURRENT_DESKTOP\"));\n            }\n\n            Lang.thread(SystemInfo::initialize, \"Detection System Information\", true);\n\n            launch(Launcher.class, args);\n        } catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.\n            CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);\n        }\n    }\n\n    public static void stopApplication() {\n        LOG.info(\"Stopping application.\\n\" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));\n\n        runInFX(() -> {\n            if (Controllers.getStage() == null)\n                return;\n            Controllers.getStage().close();\n            Schedulers.shutdown();\n            Controllers.shutdown();\n            Platform.exit();\n        });\n    }\n\n    public static void stopWithoutPlatform() {\n        LOG.info(\"Stopping application without JavaFX Toolkit.\\n\" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));\n\n        runInFX(() -> {\n            if (Controllers.getStage() == null)\n                return;\n            Controllers.getStage().close();\n            Schedulers.shutdown();\n            Controllers.shutdown();\n            Lang.executeDelayed(System::gc, TimeUnit.SECONDS, 5, true);\n        });\n    }\n\n    public static final CrashReporter CRASH_REPORTER = new CrashReporter(true);\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.EnumSet;\n\n/**\n * Stores metadata about this application.\n */\npublic final class Metadata {\n    private Metadata() {\n    }\n\n    public static final String NAME = \"HMCL\";\n    public static final String FULL_NAME = \"Hello Minecraft! Launcher\";\n    public static final String VERSION = System.getProperty(\"hmcl.version.override\", JarUtils.getAttribute(\"hmcl.version\", \"@develop@\"));\n\n    public static final String TITLE = NAME + \" \" + VERSION;\n    public static final String FULL_TITLE = FULL_NAME + \" v\" + VERSION;\n\n    public static final int MINIMUM_REQUIRED_JAVA_VERSION = 17;\n    public static final int MINIMUM_SUPPORTED_JAVA_VERSION = 17;\n    public static final int RECOMMENDED_JAVA_VERSION = 21;\n\n    public static final String PUBLISH_URL = \"https://hmcl.huangyuhui.net\";\n    public static final String ABOUT_URL = PUBLISH_URL + \"/about\";\n    public static final String DOWNLOAD_URL = PUBLISH_URL + \"/download\";\n    public static final String HMCL_UPDATE_URL = System.getProperty(\"hmcl.update_source.override\", PUBLISH_URL + \"/api/update_link\");\n\n    public static final String DOCS_URL = \"https://docs.hmcl.net\";\n    public static final String CONTACT_URL = DOCS_URL + \"/help.html\";\n    public static final String CHANGELOG_URL = DOCS_URL + \"/changelog/\";\n    public static final String EULA_URL = DOCS_URL + \"/eula/hmcl.html\";\n    public static final String GROUPS_URL = \"https://www.bilibili.com/opus/905435541874409529\";\n\n    public static final String BUILD_CHANNEL = JarUtils.getAttribute(\"hmcl.version.type\", \"nightly\");\n    public static final String GITHUB_SHA = JarUtils.getAttribute(\"hmcl.version.hash\", null);\n\n    public static final Path CURRENT_DIRECTORY = Paths.get(System.getProperty(\"user.dir\")).toAbsolutePath().normalize();\n    public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory(\"minecraft\");\n    public static final Path HMCL_GLOBAL_DIRECTORY;\n    public static final Path HMCL_CURRENT_DIRECTORY;\n    public static final Path DEPENDENCIES_DIRECTORY;\n\n    static {\n        String hmclHome = System.getProperty(\"hmcl.home\");\n        if (hmclHome == null) {\n            if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {\n                String xdgData = System.getenv(\"XDG_DATA_HOME\");\n                if (StringUtils.isNotBlank(xdgData)) {\n                    HMCL_GLOBAL_DIRECTORY = Paths.get(xdgData, \"hmcl\").toAbsolutePath().normalize();\n                } else {\n                    HMCL_GLOBAL_DIRECTORY = Paths.get(System.getProperty(\"user.home\"), \".local\", \"share\", \"hmcl\").toAbsolutePath().normalize();\n                }\n            } else {\n                HMCL_GLOBAL_DIRECTORY = OperatingSystem.getWorkingDirectory(\"hmcl\");\n            }\n        } else {\n            HMCL_GLOBAL_DIRECTORY = Paths.get(hmclHome).toAbsolutePath().normalize();\n        }\n\n        String hmclCurrentDir = System.getProperty(\"hmcl.dir\");\n        HMCL_CURRENT_DIRECTORY = hmclCurrentDir != null\n                ? Paths.get(hmclCurrentDir).toAbsolutePath().normalize()\n                : CURRENT_DIRECTORY.resolve(\".hmcl\");\n        DEPENDENCIES_DIRECTORY = HMCL_CURRENT_DIRECTORY.resolve(\"dependencies\");\n    }\n\n    public static boolean isStable() {\n        return \"stable\".equals(BUILD_CHANNEL);\n    }\n\n    public static boolean isDev() {\n        return \"dev\".equals(BUILD_CHANNEL);\n    }\n\n    public static boolean isNightly() {\n        return !isStable() && !isDev();\n    }\n\n    public static @Nullable String getSuggestedJavaDownloadLink() {\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.LOONGARCH64_OW)\n            return \"https://www.loongnix.cn/zh/api/java/downloads-jdk21/index.html\";\n        else {\n            EnumSet<Architecture> supportedArchitectures;\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)\n                supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.X86, Architecture.ARM64);\n            else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX)\n                supportedArchitectures = EnumSet.of(\n                        Architecture.X86_64, Architecture.X86,\n                        Architecture.ARM64, Architecture.ARM32,\n                        Architecture.RISCV64, Architecture.LOONGARCH64\n                );\n            else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)\n                supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.ARM64);\n            else\n                supportedArchitectures = EnumSet.noneOf(Architecture.class);\n            if (supportedArchitectures.contains(Architecture.SYSTEM_ARCH))\n                return String.format(\"https://docs.hmcl.net/downloads/%s/%s.html\",\n                        OperatingSystem.CURRENT_OS.getCheckedName(),\n                        Architecture.SYSTEM_ARCH.getCheckedName()\n                );\n            else\n                return null;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/countly/CrashReport.java",
    "content": "package org.jackhuang.hmcl.countly;\n\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\n\npublic class CrashReport {\n\n    private final Thread thread;\n    private final Throwable throwable;\n    private final String stackTrace;\n\n    public CrashReport(Thread thread, Throwable throwable) {\n        this.thread = thread;\n        this.throwable = throwable;\n        stackTrace = StringUtils.getStackTrace(throwable);\n    }\n\n    public Throwable getThrowable() {\n        return this.throwable;\n    }\n\n    public boolean shouldBeReport() {\n        if (!stackTrace.contains(\"org.jackhuang\"))\n            return false;\n\n        if (throwable instanceof VirtualMachineError)\n            return false;\n\n        return true;\n    }\n\n    public String getDisplayText() {\n        return \"---- Hello Minecraft! Crash Report ----\\n\" +\n                \"  Version: \" + Metadata.VERSION + \"\\n\" +\n                \"  Time: \" + DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\").format(LocalDateTime.now()) + \"\\n\" +\n                \"  Thread: \" + thread + \"\\n\" +\n                \"\\n  Content: \\n    \" +\n                stackTrace + \"\\n\\n\" +\n                \"-- System Details --\\n\" +\n                \"  Operating System: \" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION.getVersion() + \"\\n\" +\n                \"  System Architecture: \" + Architecture.SYSTEM_ARCH.getDisplayName() + \"\\n\" +\n                \"  Java Architecture: \" + Architecture.CURRENT_ARCH.getDisplayName() + \"\\n\" +\n                \"  Java Version: \" + System.getProperty(\"java.version\") + \", \" + System.getProperty(\"java.vendor\") + \"\\n\" +\n                \"  Java VM Version: \" + System.getProperty(\"java.vm.name\") + \" (\" + System.getProperty(\"java.vm.info\") + \"), \" + System.getProperty(\"java.vm.vendor\") + \"\\n\" +\n                \"  JVM Max Memory: \" + Runtime.getRuntime().maxMemory() + \"\\n\" +\n                \"  JVM Total Memory: \" + Runtime.getRuntime().totalMemory() + \"\\n\" +\n                \"  JVM Free Memory: \" + Runtime.getRuntime().freeMemory() + \"\\n\";\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLCacheRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport org.jackhuang.hmcl.download.DefaultCacheRepository;\n\nimport java.nio.file.Paths;\n\npublic class HMCLCacheRepository extends DefaultCacheRepository {\n\n    private final StringProperty directory = new SimpleStringProperty();\n\n    public HMCLCacheRepository() {\n        directory.addListener((a, b, t) -> changeDirectory(Paths.get(t)));\n    }\n\n    public String getDirectory() {\n        return directory.get();\n    }\n\n    public StringProperty directoryProperty() {\n        return directory;\n    }\n\n    public void setDirectory(String directory) {\n        this.directory.set(directory);\n    }\n\n    public static final HMCLCacheRepository REPOSITORY = new HMCLCacheRepository();\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.auth.AuthInfo;\nimport org.jackhuang.hmcl.launch.DefaultLauncher;\nimport org.jackhuang.hmcl.launch.ProcessListener;\nimport org.jackhuang.hmcl.util.i18n.LocaleUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.io.IOException;\nimport java.nio.file.*;\nimport java.util.*;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huangyuhui\n */\npublic final class HMCLGameLauncher extends DefaultLauncher {\n\n    public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {\n        this(repository, version, authInfo, options, null);\n    }\n\n    public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {\n        this(repository, version, authInfo, options, listener, true);\n    }\n\n    public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {\n        super(repository, version, authInfo, options, listener, daemon);\n    }\n\n    @Override\n    protected Map<String, String> getConfigurations() {\n        Map<String, String> res = super.getConfigurations();\n        res.put(\"${launcher_name}\", Metadata.NAME);\n        res.put(\"${launcher_version}\", Metadata.VERSION);\n        return res;\n    }\n\n    private void generateOptionsTxt() {\n        if (config().isDisableAutoGameOptions())\n            return;\n\n        Path runDir = repository.getRunDirectory(version.getId());\n        Path optionsFile = runDir.resolve(\"options.txt\");\n        Path configFolder = runDir.resolve(\"config\");\n\n        if (Files.exists(optionsFile))\n            return;\n\n        if (Files.isDirectory(configFolder)) {\n            try (Stream<Path> stream = Files.walk(configFolder, 2, FileVisitOption.FOLLOW_LINKS)) {\n                if (stream.anyMatch(file -> \"options.txt\".equals(FileUtils.getName(file))))\n                    return;\n            } catch (IOException e) {\n                LOG.warning(\"Failed to visit config folder\", e);\n            }\n        }\n\n        Locale locale = Locale.getDefault();\n\n        /*\n         *  1.0         : No language option, do not set for these versions\n         *  1.1  ~ 1.5  : zh_CN works fine, zh_cn will crash (the last two letters must be uppercase, otherwise it will cause an NPE crash)\n         *  1.6  ~ 1.10 : zh_CN works fine, zh_cn will automatically switch to English\n         *  1.11 ~ 1.12 : zh_cn works fine, zh_CN will display Chinese but the language setting will incorrectly show English as selected\n         *  1.13+       : zh_cn works fine, zh_CN will automatically switch to English\n         */\n        GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(repository.getGameVersion(version));\n        if (gameVersion.compareTo(\"1.1\") < 0)\n            return;\n\n        String lang = normalizedLanguageTag(locale, gameVersion);\n        if (lang.isEmpty())\n            return;\n\n        if (gameVersion.compareTo(\"1.11\") >= 0)\n            lang = lang.toLowerCase(Locale.ROOT);\n\n        try {\n            Files.createDirectories(optionsFile.getParent());\n            Files.writeString(optionsFile, String.format(\"lang:%s\\n\", lang));\n        } catch (IOException e) {\n            LOG.warning(\"Unable to generate options.txt\", e);\n        }\n    }\n\n    private static String normalizedLanguageTag(Locale locale, GameVersionNumber gameVersion) {\n        String region = locale.getCountry();\n\n        return switch (LocaleUtils.getRootLanguage(locale)) {\n            case \"ar\" -> \"ar_SA\";\n            case \"es\" -> \"es_ES\";\n            case \"ja\" -> \"ja_JP\";\n            case \"ru\" -> \"ru_RU\";\n            case \"uk\" -> \"uk_UA\";\n            case \"zh\" -> {\n                if (\"lzh\".equals(locale.getLanguage()) && gameVersion.compareTo(\"1.16\") >= 0)\n                    yield \"lzh\";\n\n                String script = LocaleUtils.getScript(locale);\n                if (\"Hant\".equals(script)) {\n                    if ((region.equals(\"HK\") || region.equals(\"MO\") && gameVersion.compareTo(\"1.16\") >= 0))\n                        yield \"zh_HK\";\n                    yield \"zh_TW\";\n                }\n                yield \"zh_CN\";\n            }\n            case \"en\" -> {\n                if (\"Qabs\".equals(LocaleUtils.getScript(locale)) && gameVersion.compareTo(\"1.16\") >= 0) {\n                    yield \"en_UD\";\n                }\n\n                yield \"\";\n            }\n            default -> \"\";\n        };\n    }\n\n    @Override\n    public ManagedProcess launch() throws IOException, InterruptedException {\n        generateOptionsTxt();\n        return super.launch();\n    }\n\n    @Override\n    public void makeLaunchScript(Path scriptFile) throws IOException {\n        generateOptionsTxt();\n        super.makeLaunchScript(scriptFile);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonParseException;\nimport javafx.scene.image.Image;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.event.Event;\nimport org.jackhuang.hmcl.event.EventManager;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.mod.ModAdviser;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.VersionIconType;\nimport org.jackhuang.hmcl.setting.VersionSetting;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.FileSaver;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemInfo;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.net.Proxy;\nimport java.nio.file.Files;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class HMCLGameRepository extends DefaultGameRepository {\n    private final Profile profile;\n\n    // local version settings\n    private final Map<String, VersionSetting> localVersionSettings = new HashMap<>();\n    private final Set<String> beingModpackVersions = new HashSet<>();\n\n    public final EventManager<Event> onVersionIconChanged = new EventManager<>();\n\n    public HMCLGameRepository(Profile profile, Path baseDirectory) {\n        super(baseDirectory);\n        this.profile = profile;\n    }\n\n    public Profile getProfile() {\n        return profile;\n    }\n\n    @Override\n    public GameDirectoryType getGameDirectoryType(String id) {\n        if (beingModpackVersions.contains(id) || isModpack(id)) {\n            return GameDirectoryType.VERSION_FOLDER;\n        } else {\n            return getVersionSetting(id).getGameDirType();\n        }\n    }\n\n    @Override\n    public Path getRunDirectory(String id) {\n        switch (getGameDirectoryType(id)) {\n            case VERSION_FOLDER:\n                return getVersionRoot(id);\n            case ROOT_FOLDER:\n                return super.getRunDirectory(id);\n            case CUSTOM:\n                try {\n                    return Path.of(getVersionSetting(id).getGameDir());\n                } catch (InvalidPathException ignored) {\n                    return getVersionRoot(id);\n                }\n            default:\n                throw new AssertionError(\"Unreachable\");\n        }\n    }\n\n    public Stream<Version> getDisplayVersions() {\n        return getVersions().stream()\n                .filter(v -> !v.isHidden())\n                .sorted(Comparator.comparing((Version v) -> Lang.requireNonNullElse(v.getReleaseTime(), Instant.EPOCH))\n                        .thenComparing(v -> VersionNumber.asVersion(v.getId())));\n    }\n\n    @Override\n    protected void refreshVersionsImpl() {\n        localVersionSettings.clear();\n        super.refreshVersionsImpl();\n        versions.keySet().forEach(this::loadLocalVersionSetting);\n        versions.keySet().forEach(version -> {\n            if (isModpack(version)) {\n                specializeVersionSetting(version);\n            }\n        });\n\n        try {\n            Path file = getBaseDirectory().resolve(\"launcher_profiles.json\");\n            if (!Files.exists(file) && !versions.isEmpty()) {\n                Files.createDirectories(file.getParent());\n                Files.writeString(file, PROFILE);\n            }\n        } catch (IOException ex) {\n            LOG.warning(\"Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.\", ex);\n        }\n    }\n\n    public void changeDirectory(Path newDirectory) {\n        setBaseDirectory(newDirectory);\n        refreshVersionsAsync().start();\n    }\n\n    private void clean(Path directory) throws IOException {\n        FileUtils.deleteDirectory(directory.resolve(\"crash-reports\"));\n        FileUtils.deleteDirectory(directory.resolve(\"logs\"));\n    }\n\n    public void clean(String id) throws IOException {\n        clean(getBaseDirectory());\n        clean(getRunDirectory(id));\n    }\n\n    public void duplicateVersion(String srcId, String dstId, boolean copySaves) throws IOException {\n        Path srcDir = getVersionRoot(srcId);\n        Path dstDir = getVersionRoot(dstId);\n\n        Version fromVersion = getVersion(srcId);\n\n        List<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);\n        blackList.add(srcId + \".jar\");\n        blackList.add(srcId + \".json\");\n        if (!copySaves)\n            blackList.add(\"saves\");\n\n        if (Files.exists(dstDir)) throw new IOException(\"Version exists\");\n\n        Files.createDirectories(dstDir);\n        FileUtils.copyDirectory(srcDir, dstDir, path -> Modpack.acceptFile(path, blackList, null));\n\n        Path fromJson = srcDir.resolve(srcId + \".json\");\n        Path fromJar = srcDir.resolve(srcId + \".jar\");\n        Path toJson = dstDir.resolve(dstId + \".json\");\n        Path toJar = dstDir.resolve(dstId + \".jar\");\n\n        if (Files.exists(fromJar)) {\n            Files.copy(fromJar, toJar);\n        }\n        Files.copy(fromJson, toJson);\n\n        JsonUtils.writeToJsonFile(toJson, fromVersion.setId(dstId).setJar(dstId));\n\n        VersionSetting oldVersionSetting = getVersionSetting(srcId).clone();\n        GameDirectoryType originalGameDirType = oldVersionSetting.getGameDirType();\n        oldVersionSetting.setUsesGlobal(false);\n        oldVersionSetting.setGameDirType(GameDirectoryType.VERSION_FOLDER);\n        VersionSetting newVersionSetting = initLocalVersionSetting(dstId, oldVersionSetting);\n        saveVersionSetting(dstId);\n\n        Path srcGameDir = getRunDirectory(srcId);\n        Path dstGameDir = getRunDirectory(dstId);\n\n        if (originalGameDirType != GameDirectoryType.VERSION_FOLDER)\n            FileUtils.copyDirectory(srcGameDir, dstGameDir, path -> Modpack.acceptFile(path, blackList, null));\n    }\n\n    private Path getLocalVersionSettingFile(String id) {\n        return getVersionRoot(id).resolve(\"hmclversion.cfg\");\n    }\n\n    private void loadLocalVersionSetting(String id) {\n        Path file = getLocalVersionSettingFile(id);\n        if (Files.exists(file))\n            try {\n                VersionSetting versionSetting = JsonUtils.fromJsonFile(file, VersionSetting.class);\n                initLocalVersionSetting(id, versionSetting);\n            } catch (Exception ex) {\n                // If [JsonParseException], [IOException] or [NullPointerException] happens, the json file is malformed and needed to be recreated.\n                initLocalVersionSetting(id, new VersionSetting());\n            }\n    }\n\n    /**\n     * Create new version setting if version id has no version setting.\n     *\n     * @param id the version id.\n     * @return new version setting, null if given version does not exist.\n     */\n    public VersionSetting createLocalVersionSetting(String id) {\n        if (!hasVersion(id))\n            return null;\n        if (localVersionSettings.containsKey(id))\n            return getLocalVersionSetting(id);\n        else\n            return initLocalVersionSetting(id, new VersionSetting());\n    }\n\n    private VersionSetting initLocalVersionSetting(String id, VersionSetting vs) {\n        localVersionSettings.put(id, vs);\n        vs.addListener(a -> saveVersionSetting(id));\n        return vs;\n    }\n\n    /**\n     * Get the version setting for version id.\n     *\n     * @param id version id\n     * @return corresponding version setting, null if the version has no its own version setting.\n     */\n    @Nullable\n    public VersionSetting getLocalVersionSetting(String id) {\n        if (!localVersionSettings.containsKey(id))\n            loadLocalVersionSetting(id);\n        VersionSetting setting = localVersionSettings.get(id);\n        if (setting != null && isModpack(id))\n            setting.setGameDirType(GameDirectoryType.VERSION_FOLDER);\n        return setting;\n    }\n\n    @Nullable\n    public VersionSetting getLocalVersionSettingOrCreate(String id) {\n        VersionSetting vs = getLocalVersionSetting(id);\n        if (vs == null) {\n            vs = createLocalVersionSetting(id);\n        }\n        return vs;\n    }\n\n    public VersionSetting getVersionSetting(String id) {\n        VersionSetting vs = getLocalVersionSetting(id);\n        if (vs == null || vs.isUsesGlobal()) {\n            profile.getGlobal().setUsesGlobal(true);\n            return profile.getGlobal();\n        } else\n            return vs;\n    }\n\n    public Optional<Path> getVersionIconFile(String id) {\n        Path root = getVersionRoot(id);\n\n        for (String extension : FXUtils.IMAGE_EXTENSIONS) {\n            Path file = root.resolve(\"icon.\" + extension);\n            if (Files.exists(file)) {\n                return Optional.of(file);\n            }\n        }\n\n        return Optional.empty();\n    }\n\n    public void setVersionIconFile(String id, Path iconFile) throws IOException {\n        String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);\n        if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) {\n            throw new IllegalArgumentException(\"Unsupported icon file: \" + ext);\n        }\n\n        deleteIconFile(id);\n\n        FileUtils.copyFile(iconFile, getVersionRoot(id).resolve(\"icon.\" + ext));\n    }\n\n    public void deleteIconFile(String id) {\n        Path root = getVersionRoot(id);\n        for (String extension : FXUtils.IMAGE_EXTENSIONS) {\n            Path file = root.resolve(\"icon.\" + extension);\n            try {\n                Files.deleteIfExists(file);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to delete icon file: \" + file, e);\n            }\n        }\n    }\n\n    public Image getVersionIconImage(String id) {\n        if (id == null || !isLoaded())\n            return VersionIconType.DEFAULT.getIcon();\n\n        VersionSetting vs = getLocalVersionSettingOrCreate(id);\n        VersionIconType iconType = vs != null ? Lang.requireNonNullElse(vs.getVersionIcon(), VersionIconType.DEFAULT) : VersionIconType.DEFAULT;\n\n        if (iconType == VersionIconType.DEFAULT) {\n            Version version = getVersion(id).resolve(this);\n            Optional<Path> iconFile = getVersionIconFile(id);\n            if (iconFile.isPresent()) {\n                try {\n                    return FXUtils.loadImage(iconFile.get());\n                } catch (Exception e) {\n                    LOG.warning(\"Failed to load version icon of \" + id, e);\n                }\n            }\n\n            if (LibraryAnalyzer.isModded(this, version)) {\n                LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);\n                if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FABRIC))\n                    return VersionIconType.FABRIC.getIcon();\n                else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.QUILT))\n                    return VersionIconType.QUILT.getIcon();\n                else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.LEGACY_FABRIC))\n                    return VersionIconType.LEGACY_FABRIC.getIcon();\n                else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE))\n                    return VersionIconType.NEO_FORGE.getIcon();\n                else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE))\n                    return VersionIconType.FORGE.getIcon();\n                else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM))\n                    return VersionIconType.CLEANROOM.getIcon();\n                else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.LITELOADER))\n                    return VersionIconType.CHICKEN.getIcon();\n                else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.OPTIFINE))\n                    return VersionIconType.OPTIFINE.getIcon();\n            }\n\n            String gameVersion = getGameVersion(version).orElse(null);\n            if (gameVersion != null) {\n                GameVersionNumber versionNumber = GameVersionNumber.asGameVersion(gameVersion);\n                if (versionNumber.isAprilFools()) {\n                    return VersionIconType.APRIL_FOOLS.getIcon();\n                } else if (versionNumber instanceof GameVersionNumber.LegacySnapshot) {\n                    return VersionIconType.COMMAND.getIcon();\n                } else if (versionNumber instanceof GameVersionNumber.Old) {\n                    return VersionIconType.CRAFT_TABLE.getIcon();\n                }\n            }\n            return VersionIconType.GRASS.getIcon();\n        } else {\n            return iconType.getIcon();\n        }\n    }\n\n    public void saveVersionSetting(String id) {\n        if (!localVersionSettings.containsKey(id))\n            return;\n        Path file = getLocalVersionSettingFile(id).toAbsolutePath().normalize();\n        try {\n            Files.createDirectories(file.getParent());\n        } catch (IOException e) {\n            LOG.warning(\"Failed to create directory: \" + file.getParent(), e);\n        }\n\n        FileSaver.save(file, GSON.toJson(localVersionSettings.get(id)));\n    }\n\n    /**\n     * Make version use self version settings instead of the global one.\n     *\n     * @param id the version id.\n     * @return specialized version setting, null if given version does not exist.\n     */\n    public VersionSetting specializeVersionSetting(String id) {\n        VersionSetting vs = getLocalVersionSetting(id);\n        if (vs == null)\n            vs = createLocalVersionSetting(id);\n        if (vs == null)\n            return null;\n        if (vs.isUsesGlobal()) {\n            vs.setUsesGlobal(false);\n        }\n        return vs;\n    }\n\n    public void globalizeVersionSetting(String id) {\n        VersionSetting vs = getLocalVersionSetting(id);\n        if (vs != null)\n            vs.setUsesGlobal(true);\n    }\n\n    public LaunchOptions.Builder getLaunchOptions(String version, JavaRuntime javaVersion, Path gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {\n        VersionSetting vs = getVersionSetting(version);\n\n        LaunchOptions.Builder builder = new LaunchOptions.Builder()\n                .setGameDir(gameDir)\n                .setJava(javaVersion)\n                .setVersionType(Metadata.TITLE)\n                .setVersionName(version)\n                .setProfileName(Metadata.TITLE)\n                .setGameArguments(StringUtils.tokenize(vs.getMinecraftArgs()))\n                .setOverrideJavaArguments(StringUtils.tokenize(vs.getJavaArgs()))\n                .setMaxMemory(vs.isNoJVMArgs() && vs.isAutoMemory() ? null : (int) (getAllocatedMemory(\n                        vs.getMaxMemory() * 1024L * 1024L,\n                        SystemInfo.getPhysicalMemoryStatus().getAvailable(),\n                        vs.isAutoMemory()\n                ) / 1024 / 1024))\n                .setMinMemory(vs.getMinMemory())\n                .setMetaspace(Lang.toIntOrNull(vs.getPermSize()))\n                .setEnvironmentVariables(\n                        Lang.mapOf(StringUtils.tokenize(vs.getEnvironmentVariables())\n                                .stream()\n                                .map(it -> {\n                                    int idx = it.indexOf('=');\n                                    return idx >= 0 ? pair(it.substring(0, idx), it.substring(idx + 1)) : pair(it, \"\");\n                                })\n                                .collect(Collectors.toList())\n                        )\n                )\n                .setWidth(vs.getWidth())\n                .setHeight(vs.getHeight())\n                .setFullscreen(vs.isFullscreen())\n                .setWrapper(vs.getWrapper())\n                .setProxyOption(getProxyOption())\n                .setPreLaunchCommand(vs.getPreLaunchCommand())\n                .setPostExitCommand(vs.getPostExitCommand())\n                .setNoGeneratedJVMArgs(vs.isNoJVMArgs())\n                .setNoGeneratedOptimizingJVMArgs(vs.isNoOptimizingJVMArgs())\n                .setNativesDirType(vs.getNativesDirType())\n                .setNativesDir(vs.getNativesDir())\n                .setProcessPriority(vs.getProcessPriority())\n                .setRenderer(vs.getRenderer())\n                .setEnableDebugLogOutput(vs.isEnableDebugLogOutput())\n                .setUseNativeGLFW(vs.isUseNativeGLFW())\n                .setUseNativeOpenAL(vs.isUseNativeOpenAL())\n                .setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())\n                .setJavaAgents(javaAgents)\n                .setJavaArguments(javaArguments);\n\n        if (StringUtils.isNotBlank(vs.getServerIp())) {\n            builder.setQuickPlayOption(new QuickPlayOption.MultiPlayer(vs.getServerIp()));\n        }\n\n        Path json = getModpackConfiguration(version);\n        if (Files.exists(json)) {\n            try {\n                String jsonText = Files.readString(json);\n                ModpackConfiguration<?> modpackConfiguration = JsonUtils.GSON.fromJson(jsonText, ModpackConfiguration.class);\n                ModpackProvider provider = ModpackHelper.getProviderByType(modpackConfiguration.getType());\n                if (provider != null) provider.injectLaunchOptions(jsonText, builder);\n            } catch (IOException | JsonParseException e) {\n                LOG.warning(\"Failed to parse modpack configuration file \" + json, e);\n            }\n        }\n\n        if (vs.isAutoMemory() && builder.getJavaArguments().stream().anyMatch(it -> it.startsWith(\"-Xmx\")))\n            builder.setMaxMemory(null);\n\n        return builder;\n    }\n\n    @Override\n    public Path getModpackConfiguration(String version) {\n        return getVersionRoot(version).resolve(\"modpack.cfg\");\n    }\n\n    public void markVersionAsModpack(String id) {\n        beingModpackVersions.add(id);\n    }\n\n    public void undoMark(String id) {\n        beingModpackVersions.remove(id);\n    }\n\n    public void markVersionLaunchedAbnormally(String id) {\n        try {\n            Files.createFile(getVersionRoot(id).resolve(\".abnormal\"));\n        } catch (IOException ignored) {\n        }\n    }\n\n    public boolean unmarkVersionLaunchedAbnormally(String id) {\n        Path file = getVersionRoot(id).resolve(\".abnormal\");\n        if (Files.isRegularFile(file)) {\n            try {\n                Files.delete(file);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to delete abnormal mark file: \" + file, e);\n            }\n\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    private static final Gson GSON = new GsonBuilder()\n            .setPrettyPrinting()\n            .create();\n\n    private static final String PROFILE = \"{\\\"selectedProfile\\\": \\\"(Default)\\\",\\\"profiles\\\": {\\\"(Default)\\\": {\\\"name\\\": \\\"(Default)\\\"}},\\\"clientToken\\\": \\\"88888888-8888-8888-8888-888888888888\\\"}\";\n\n\n    // These version ids are forbidden because they may conflict with modpack configuration filenames\n    private static final Set<String> FORBIDDEN_VERSION_IDS = new HashSet<>(Arrays.asList(\n            \"modpack\", \"minecraftinstance\", \"manifest\"));\n\n    public static boolean isValidVersionId(String id) {\n        if (FORBIDDEN_VERSION_IDS.contains(id))\n            return false;\n\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS &&\n                FORBIDDEN_VERSION_IDS.contains(id.toLowerCase(Locale.ROOT)))\n            return false;\n\n        return FileUtils.isNameValid(id);\n    }\n\n    /**\n     * Returns true if the given version id conflicts with an existing version.\n     */\n    public boolean versionIdConflicts(String id) {\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n            // on Windows, filenames are case-insensitive\n            for (String existingId : versions.keySet()) {\n                if (existingId.equalsIgnoreCase(id)) {\n                    return true;\n                }\n            }\n            return false;\n        } else {\n            return versions.containsKey(id);\n        }\n    }\n\n    public static long getAllocatedMemory(long minimum, long available, boolean auto) {\n        if (auto) {\n            available -= 512 * 1024 * 1024; // Reserve 512 MiB memory for off-heap memory and HMCL itself\n            if (available <= 0) {\n                return minimum;\n            }\n\n            final long threshold = 8L * 1024 * 1024 * 1024; // 8 GiB\n            final long suggested = Math.min(available <= threshold\n                            ? (long) (available * 0.8)\n                            : (long) (threshold * 0.8 + (available - threshold) * 0.2),\n                    16L * 1024 * 1024 * 1024);\n            return Math.max(minimum, suggested);\n        } else {\n            return minimum;\n        }\n    }\n\n    public static ProxyOption getProxyOption() {\n        if (!config().hasProxy() || config().getProxyType() == null) {\n            return ProxyOption.Default.INSTANCE;\n        }\n\n        return switch (config().getProxyType()) {\n            case DIRECT -> ProxyOption.Direct.INSTANCE;\n            case HTTP, SOCKS -> {\n                String proxyHost = config().getProxyHost();\n                int proxyPort = config().getProxyPort();\n\n                if (StringUtils.isBlank(proxyHost) || proxyPort < 0 || proxyPort > 0xFFFF) {\n                    yield ProxyOption.Default.INSTANCE;\n                }\n\n                String proxyUser = config().getProxyUser();\n                String proxyPass = config().getProxyPass();\n\n                if (StringUtils.isBlank(proxyUser)) {\n                    proxyUser = null;\n                    proxyPass = null;\n                } else if (proxyPass == null) {\n                    proxyPass = \"\";\n                }\n\n                if (config().getProxyType() == Proxy.Type.HTTP) {\n                    yield new ProxyOption.Http(proxyHost, proxyPort, proxyUser, proxyPass);\n                } else {\n                    yield new ProxyOption.Socks(proxyHost, proxyPort, proxyUser, proxyPass);\n                }\n            }\n            default -> ProxyOption.Default.INSTANCE;\n        };\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.mod.MinecraftInstanceTask;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.mod.ModpackInstallTask;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic final class HMCLModpackInstallTask extends Task<Void> {\n    private final Path zipFile;\n    private final String name;\n    private final HMCLGameRepository repository;\n    private final DefaultDependencyManager dependency;\n    private final Modpack modpack;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n    private final List<Task<?>> dependents = new ArrayList<>(4);\n\n    public HMCLModpackInstallTask(Profile profile, Path zipFile, Modpack modpack, String name) {\n        dependency = profile.getDependency();\n        repository = profile.getRepository();\n        this.zipFile = zipFile;\n        this.name = name;\n        this.modpack = modpack;\n\n        Path run = repository.getRunDirectory(name);\n        Path json = repository.getModpackConfiguration(name);\n        if (repository.hasVersion(name) && Files.notExists(json))\n            throw new IllegalArgumentException(\"Version \" + name + \" already exists\");\n\n        dependents.add(dependency.gameBuilder().name(name).gameVersion(modpack.getGameVersion()).buildAsync());\n\n        onDone().register(event -> {\n            if (event.isFailed()) repository.removeVersionFromDisk(name);\n        });\n\n        ModpackConfiguration<Modpack> config = null;\n        try {\n            if (Files.exists(json)) {\n                config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(Modpack.class));\n\n                if (!HMCLModpackProvider.INSTANCE.getName().equals(config.getType()))\n                    throw new IllegalArgumentException(\"Version \" + name + \" is not a HMCL modpack. Cannot update this version.\");\n            }\n        } catch (JsonParseException | IOException ignore) {\n        }\n        dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(\"/minecraft\"), it -> !\"pack.json\".equals(it), config));\n        dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(\"/minecraft\"), modpack, HMCLModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage(\"hmcl.modpack\"));\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public List<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        String json = CompressingUtils.readTextZipEntry(zipFile, \"minecraft/pack.json\");\n        Version originalVersion = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);\n        LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(originalVersion, null);\n        Task<Version> libraryTask = Task.supplyAsync(() -> originalVersion);\n        // reinstall libraries\n        // libraries of Forge and OptiFine should be obtained by installation.\n        for (LibraryAnalyzer.LibraryMark mark : analyzer) {\n            if (LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(mark.getLibraryId()))\n                continue;\n            libraryTask = libraryTask.thenComposeAsync(version -> dependency.installLibraryAsync(modpack.getGameVersion(), version, mark.getLibraryId(), mark.getLibraryVersion()));\n        }\n\n        dependencies.add(libraryTask.thenComposeAsync(repository::saveAsync));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackManifest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.mod.ModpackManifest;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\n\npublic final class HMCLModpackManifest implements ModpackManifest {\n    public static final HMCLModpackManifest INSTANCE = new HMCLModpackManifest();\n\n    private HMCLModpackManifest() {}\n\n    @Override\n    public ModpackProvider getProvider() {\n        return HMCLModpackProvider.INSTANCE;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.mod.MismatchedModpackTypeException;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.mod.ModpackUpdateTask;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\n\npublic final class HMCLModpackProvider implements ModpackProvider {\n    public static final HMCLModpackProvider INSTANCE = new HMCLModpackProvider();\n\n    @Override\n    public String getName() {\n        return \"HMCL\";\n    }\n\n    @Override\n    public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        return null;\n    }\n\n    @Override\n    public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException {\n        if (!(modpack.getManifest() instanceof HMCLModpackManifest))\n            throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());\n\n        if (!(dependencyManager.getGameRepository() instanceof HMCLGameRepository repository)) {\n            throw new IllegalArgumentException(\"HMCLModpackProvider requires HMCLGameRepository\");\n        }\n\n        Profile profile = repository.getProfile();\n\n        return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));\n    }\n\n    @Override\n    public Modpack readManifest(ZipArchiveReader file, Path path, Charset encoding) throws IOException, JsonParseException {\n        String manifestJson = CompressingUtils.readTextZipEntry(file, \"modpack.json\");\n        Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, HMCLModpack.class).setEncoding(encoding);\n        String gameJson = CompressingUtils.readTextZipEntry(file, \"minecraft/pack.json\");\n        Version game = JsonUtils.fromNonNullJson(gameJson, Version.class);\n        if (game.getJar() == null)\n            if (StringUtils.isBlank(manifest.getVersion()))\n                throw new JsonParseException(\"Cannot recognize the game version of modpack \" + file + \".\");\n            else\n                manifest.setManifest(HMCLModpackManifest.INSTANCE);\n        else\n            manifest.setManifest(HMCLModpackManifest.INSTANCE).setGameVersion(game.getJar());\n        return manifest;\n    }\n\n    private final static class HMCLModpack extends Modpack {\n        @Override\n        public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {\n            return new HMCLModpackInstallTask(((HMCLGameRepository) dependencyManager.getGameRepository()).getProfile(), zipFile, this, name);\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.stage.Stage;\nimport org.jackhuang.hmcl.Launcher;\nimport org.jackhuang.hmcl.auth.*;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;\nimport org.jackhuang.hmcl.auth.offline.OfflineAccount;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.MaintainTask;\nimport org.jackhuang.hmcl.download.game.*;\nimport org.jackhuang.hmcl.java.JavaManager;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.launch.*;\nimport org.jackhuang.hmcl.mod.ModpackCompletionException;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.setting.*;\nimport org.jackhuang.hmcl.task.*;\nimport org.jackhuang.hmcl.ui.*;\nimport org.jackhuang.hmcl.ui.construct.DialogCloseEvent;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;\nimport org.jackhuang.hmcl.ui.construct.PromptDialogPane;\nimport org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.ResponseCodeException;\nimport org.jackhuang.hmcl.util.platform.*;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.lang.ref.WeakReference;\nimport java.net.SocketTimeoutException;\nimport java.net.URI;\nimport java.nio.file.AccessDeniedException;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.stream.Collectors;\n\nimport static javafx.application.Platform.runLater;\nimport static javafx.application.Platform.setImplicitExit;\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;\nimport static org.jackhuang.hmcl.util.Lang.resolveException;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.platform.Platform.SYSTEM_PLATFORM;\nimport static org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java;\n\npublic final class LauncherHelper {\n\n    private final Profile profile;\n    private Account account;\n    private final String selectedVersion;\n    private Path scriptFile;\n    private final VersionSetting setting;\n    private LauncherVisibility launcherVisibility;\n    private boolean showLogs;\n    private QuickPlayOption quickPlayOption;\n    private boolean disableOfflineSkin = false;\n\n    public LauncherHelper(Profile profile, Account account, String selectedVersion) {\n        this.profile = Objects.requireNonNull(profile);\n        this.account = Objects.requireNonNull(account);\n        this.selectedVersion = Objects.requireNonNull(selectedVersion);\n        this.setting = profile.getVersionSetting(selectedVersion);\n        this.launcherVisibility = setting.getLauncherVisibility();\n        this.showLogs = setting.isShowLogs();\n        this.launchingStepsPane.setTitle(i18n(\"version.launch\"));\n    }\n\n    private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(TaskCancellationAction.NORMAL);\n\n    public Account getAccount() {\n        return account;\n    }\n\n    public void setAccount(Account account) {\n        this.account = account;\n    }\n\n    public void setTestMode() {\n        launcherVisibility = LauncherVisibility.KEEP;\n        showLogs = true;\n    }\n\n    public void setKeep() {\n        launcherVisibility = LauncherVisibility.KEEP;\n    }\n\n    public void setQuickPlayOption(QuickPlayOption quickPlayOption) {\n        this.quickPlayOption = quickPlayOption;\n    }\n\n    public void setDisableOfflineSkin() {\n        disableOfflineSkin = true;\n    }\n\n    public void launch() {\n        FXUtils.checkFxUserThread();\n\n        LOG.info(\"Launching game version: \" + selectedVersion);\n\n        Controllers.dialog(launchingStepsPane);\n        launch0();\n    }\n\n    public void makeLaunchScript(Path scriptFile) {\n        this.scriptFile = Objects.requireNonNull(scriptFile);\n        launch();\n    }\n\n    private void launch0() {\n        // https://github.com/HMCL-dev/HMCL/pull/4121\n        PROCESSES.removeIf(it -> it.get() == null);\n\n        HMCLGameRepository repository = profile.getRepository();\n        DefaultDependencyManager dependencyManager = profile.getDependency();\n        AtomicReference<Version> version = new AtomicReference<>(MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion)));\n        Optional<String> gameVersion = repository.getGameVersion(version.get());\n        boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);\n        CountDownLatch launchingLatch = new CountDownLatch(1);\n        List<String> javaAgents = new ArrayList<>(0);\n        List<String> javaArguments = new ArrayList<>(0);\n\n        AtomicReference<JavaRuntime> javaVersionRef = new AtomicReference<>();\n\n        TaskExecutor executor = checkGameState(profile, setting, version.get())\n                .thenComposeAsync(java -> {\n                    javaVersionRef.set(Objects.requireNonNull(java));\n                    version.set(NativePatcher.patchNative(repository, version.get(), gameVersion.orElse(null), java, setting, javaArguments));\n                    if (setting.isNotCheckGame())\n                        return null;\n                    return Task.allOf(\n                            dependencyManager.checkGameCompletionAsync(version.get(), integrityCheck),\n                            Task.composeAsync(() -> {\n                                try {\n                                    ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));\n                                    ModpackProvider provider = ModpackHelper.getProviderByType(configuration.getType());\n                                    if (provider == null) return null;\n                                    else return provider.createCompletionTask(dependencyManager, selectedVersion);\n                                } catch (IOException e) {\n                                    return null;\n                                }\n                            }),\n                            Task.composeAsync(() -> {\n                                Renderer renderer = setting.getRenderer();\n                                if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                                    Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION);\n                                    if (lib == null)\n                                        return null;\n                                    Path file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);\n                                    if (file.toAbsolutePath().toString().indexOf('=') >= 0) {\n                                        LOG.warning(\"Invalid character '=' in the libraries directory path, unable to attach software renderer loader\");\n                                        return null;\n                                    }\n\n                                    String agent = FileUtils.getAbsolutePath(file) + \"=\" + renderer.name().toLowerCase(Locale.ROOT);\n\n                                    if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {\n                                        return new LibraryDownloadTask(dependencyManager, file, lib)\n                                                .thenRunAsync(() -> javaAgents.add(agent));\n                                    } else {\n                                        javaAgents.add(agent);\n                                        return null;\n                                    }\n                                } else {\n                                    return null;\n                                }\n                            })\n                    );\n                }).withStage(\"launch.state.dependencies\")\n                .thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))\n                .thenComposeAsync(() -> logIn(account).withStage(\"launch.state.logging_in\"))\n                .thenComposeAsync(authInfo -> Task.supplyAsync(() -> {\n                    LaunchOptions.Builder launchOptionsBuilder = repository.getLaunchOptions(\n                            selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, javaArguments, scriptFile != null);\n                    if (disableOfflineSkin) {\n                        launchOptionsBuilder.setDaemon(false);\n                    }\n                    if (quickPlayOption != null) {\n                        launchOptionsBuilder.setQuickPlayOption(quickPlayOption);\n                    }\n                    LaunchOptions launchOptions = launchOptionsBuilder.create();\n\n                    LOG.info(\"Here's the structure of game mod directory:\\n\" + FileUtils.printFileStructure(repository.getModsDirectory(selectedVersion), 10));\n\n                    return new HMCLGameLauncher(\n                            repository,\n                            version.get(),\n                            authInfo,\n                            launchOptions,\n                            launcherVisibility == LauncherVisibility.CLOSE\n                                    ? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.\n                                    : new HMCLProcessListener(repository, version.get(), authInfo, launchOptions, launchingLatch, gameVersion.isPresent())\n                    );\n                }).thenComposeAsync(launcher -> { // launcher is prev task's result\n                    if (scriptFile == null) {\n                        return Task.supplyAsync(launcher::launch);\n                    } else {\n                        return Task.supplyAsync(() -> {\n                            launcher.makeLaunchScript(scriptFile);\n                            return null;\n                        });\n                    }\n                }).thenAcceptAsync(process -> { // process is LaunchTask's result\n                    if (scriptFile == null) {\n                        PROCESSES.add(new WeakReference<>(process));\n                        if (launcherVisibility == LauncherVisibility.CLOSE)\n                            Launcher.stopApplication();\n                        else\n                            launchingStepsPane.setCancel(new TaskCancellationAction(it -> {\n                                process.stop();\n                                it.fireEvent(new DialogCloseEvent());\n                            }));\n                    } else {\n                        runLater(() -> {\n                            launchingStepsPane.fireEvent(new DialogCloseEvent());\n                            Controllers.dialog(i18n(\"version.launch_script.success\", FileUtils.getAbsolutePath(scriptFile)));\n                        });\n                    }\n                }).withFakeProgress(\n                        i18n(\"message.doing\"),\n                        () -> launchingLatch.getCount() == 0, 6.95\n                ).withStage(\"launch.state.waiting_launching\"))\n                .withStagesHints(\n                        new Task.StagesHint(\"launch.state.java\"),\n                        new Task.StagesHint(\"launch.state.dependencies\", List.of(\"hmcl.install.assets\", \"hmcl.install.libraries\", \"hmcl.modpack.download\")),\n                        new Task.StagesHint(\"launch.state.logging_in\"),\n                        new Task.StagesHint(\"launch.state.waiting_launching\"))\n                .executor();\n        launchingStepsPane.setExecutor(executor, false);\n        executor.addTaskListener(new TaskListener() {\n\n            @Override\n            public void onStop(boolean success, TaskExecutor executor) {\n                runLater(() -> {\n                    // Check if the application has stopped\n                    // because onStop will be invoked if tasks fail when the executor service shut down.\n                    if (!Controllers.isStopped()) {\n                        launchingStepsPane.fireEvent(new DialogCloseEvent());\n                        if (!success) {\n                            Exception ex = executor.getException();\n                            if (ex != null && !(ex instanceof CancellationException)) {\n                                String message;\n                                if (ex instanceof ModpackCompletionException) {\n                                    if (ex.getCause() instanceof FileNotFoundException)\n                                        message = i18n(\"modpack.type.curse.not_found\");\n                                    else\n                                        message = i18n(\"modpack.type.curse.error\");\n                                } else if (ex instanceof PermissionException) {\n                                    message = i18n(\"launch.failed.executable_permission\");\n                                } else if (ex instanceof ProcessCreationException) {\n                                    message = i18n(\"launch.failed.creating_process\") + \"\\n\" + ex.getLocalizedMessage();\n                                } else if (ex instanceof NotDecompressingNativesException) {\n                                    message = i18n(\"launch.failed.decompressing_natives\") + \"\\n\" + ex.getLocalizedMessage();\n                                } else if (ex instanceof LibraryDownloadException) {\n                                    message = i18n(\"launch.failed.download_library\", ((LibraryDownloadException) ex).getLibrary().getName()) + \"\\n\";\n                                    if (ex.getCause() instanceof ResponseCodeException) {\n                                        ResponseCodeException rce = (ResponseCodeException) ex.getCause();\n                                        int responseCode = rce.getResponseCode();\n                                        String uri = rce.getUri();\n                                        if (responseCode == 404)\n                                            message += i18n(\"download.code.404\", uri);\n                                        else\n                                            message += i18n(\"download.failed\", uri, responseCode);\n                                    } else {\n                                        message += StringUtils.getStackTrace(ex.getCause());\n                                    }\n                                } else if (ex instanceof DownloadException) {\n                                    URI uri = ((DownloadException) ex).getUri();\n                                    if (ex.getCause() instanceof SocketTimeoutException) {\n                                        message = i18n(\"install.failed.downloading.timeout\", uri);\n                                    } else if (ex.getCause() instanceof ResponseCodeException) {\n                                        ResponseCodeException responseCodeException = (ResponseCodeException) ex.getCause();\n                                        if (I18n.hasKey(\"download.code.\" + responseCodeException.getResponseCode())) {\n                                            message = i18n(\"download.code.\" + responseCodeException.getResponseCode(), uri);\n                                        } else {\n                                            message = i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + StringUtils.getStackTrace(ex.getCause());\n                                        }\n                                    } else {\n                                        message = i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + StringUtils.getStackTrace(ex.getCause());\n                                    }\n                                } else if (ex instanceof GameAssetIndexDownloadTask.GameAssetIndexMalformedException) {\n                                    message = i18n(\"assets.index.malformed\");\n                                } else if (ex instanceof AuthlibInjectorDownloadException) {\n                                    message = i18n(\"account.failed.injector_download_failure\");\n                                } else if (ex instanceof CharacterDeletedException) {\n                                    message = i18n(\"account.failed.character_deleted\");\n                                } else if (ex instanceof ResponseCodeException) {\n                                    ResponseCodeException rce = (ResponseCodeException) ex;\n                                    int responseCode = rce.getResponseCode();\n                                    String uri = rce.getUri();\n                                    if (responseCode == 404)\n                                        message = i18n(\"download.code.404\", uri);\n                                    else\n                                        message = i18n(\"download.failed\", uri, responseCode);\n                                } else if (ex instanceof CommandTooLongException) {\n                                    message = i18n(\"launch.failed.command_too_long\");\n                                } else if (ex instanceof ExecutionPolicyLimitException) {\n                                    Controllers.prompt(new PromptDialogPane.Builder(i18n(\"launch.failed.execution_policy\"),\n                                            (result, handler) -> {\n                                                if (CommandBuilder.setExecutionPolicy()) {\n                                                    LOG.info(\"Set the ExecutionPolicy for the scope 'CurrentUser' to 'RemoteSigned'\");\n                                                    handler.resolve();\n                                                } else {\n                                                    LOG.warning(\"Failed to set ExecutionPolicy\");\n                                                    handler.reject(i18n(\"launch.failed.execution_policy.failed_to_set\"));\n                                                }\n                                            })\n                                            .addQuestion(new PromptDialogPane.Builder.HintQuestion(i18n(\"launch.failed.execution_policy.hint\")))\n                                    );\n\n                                    return;\n                                } else if (ex instanceof AccessDeniedException) {\n                                    message = i18n(\"exception.access_denied\", ((AccessDeniedException) ex).getFile());\n                                } else {\n                                    message = StringUtils.getStackTrace(ex);\n                                }\n                                Controllers.dialog(message,\n                                        scriptFile == null ? i18n(\"launch.failed\") : i18n(\"version.launch_script.failed\"),\n                                        MessageType.ERROR);\n                            }\n                        }\n                    }\n                    launchingStepsPane.setExecutor(null);\n                });\n            }\n        });\n\n        executor.start();\n    }\n\n    private static Task<JavaRuntime> checkGameState(Profile profile, VersionSetting setting, Version version) {\n        LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, profile.getRepository().getGameVersion(version).orElse(null));\n        GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(analyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT));\n\n        Task<JavaRuntime> getJavaTask = Task.supplyAsync(() -> {\n            try {\n                return setting.getJava(gameVersion, version);\n            } catch (InterruptedException e) {\n                throw new CancellationException();\n            }\n        });\n        Task<JavaRuntime> task;\n        if (setting.isNotCheckJVM()) {\n            task = getJavaTask.thenApplyAsync(java -> Lang.requireNonNullElse(java, JavaRuntime.getDefault()));\n        } else if (setting.getJavaVersionType() == JavaVersionType.AUTO || setting.getJavaVersionType() == JavaVersionType.VERSION) {\n            task = getJavaTask.thenComposeAsync(Schedulers.javafx(), java -> {\n                if (java != null) {\n                    return Task.completed(java);\n                }\n\n                // Reset invalid java version\n                CompletableFuture<JavaRuntime> future = new CompletableFuture<>();\n                Task<JavaRuntime> result = Task.fromCompletableFuture(future);\n                Runnable breakAction = () -> future.completeExceptionally(new CancellationException(\"No accepted java\"));\n                List<GameJavaVersion> supportedVersions = GameJavaVersion.getSupportedVersions(SYSTEM_PLATFORM);\n\n                GameJavaVersion targetJavaVersion = null;\n                if (setting.getJavaVersionType() == JavaVersionType.VERSION) {\n                    try {\n                        int targetJavaVersionMajor = Integer.parseInt(setting.getJavaVersion());\n                        GameJavaVersion minimumJavaVersion = null;\n                        if (gameVersion.compareTo(\"1.12.2\") == 0) {\n                            Optional<String> cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM);\n                            if (cleanroomVersion.isPresent()) {\n                                minimumJavaVersion = GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion.get());\n                            }\n                        }\n\n                        if (minimumJavaVersion == null)\n                            minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersion);\n\n                        if (minimumJavaVersion != null && targetJavaVersionMajor < minimumJavaVersion.majorVersion()) {\n                            Controllers.dialog(\n                                    i18n(\"launch.failed.java_version_too_low\"),\n                                    i18n(\"message.error\"),\n                                    MessageType.ERROR,\n                                    breakAction\n                            );\n                            return result;\n                        }\n\n                        targetJavaVersion = GameJavaVersion.get(targetJavaVersionMajor);\n                    } catch (NumberFormatException ignored) {\n                    }\n                } else {\n                    if (gameVersion.compareTo(\"1.12.2\") == 0) {\n                        Optional<String> cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM);\n                        if (cleanroomVersion.isPresent()) {\n                            targetJavaVersion = GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion.get());\n                        }\n                    }\n\n                    if (targetJavaVersion == null)\n                        targetJavaVersion = version.getJavaVersion();\n                }\n\n                if (targetJavaVersion != null && supportedVersions.contains(targetJavaVersion)) {\n                    downloadJava(targetJavaVersion, profile)\n                            .whenCompleteAsync((downloadedJava, exception) -> {\n                                if (exception == null) {\n                                    future.complete(downloadedJava);\n                                } else {\n                                    LOG.warning(\"Failed to download java\", exception);\n                                    Controllers.confirm(i18n(\"launch.failed.no_accepted_java\"), i18n(\"message.warning\"), MessageType.WARNING,\n                                            () -> future.complete(JavaRuntime.getDefault()),\n                                            breakAction);\n                                }\n                            }, Schedulers.javafx());\n                } else {\n                    Controllers.confirm(i18n(\"launch.failed.no_accepted_java\"), i18n(\"message.warning\"), MessageType.WARNING,\n                            () -> future.complete(JavaRuntime.getDefault()),\n                            breakAction);\n                }\n\n                return result;\n            });\n        } else {\n            task = getJavaTask.thenComposeAsync(java -> {\n                Set<JavaVersionConstraint> violatedMandatoryConstraints = EnumSet.noneOf(JavaVersionConstraint.class);\n                Set<JavaVersionConstraint> violatedSuggestedConstraints = EnumSet.noneOf(JavaVersionConstraint.class);\n\n                if (java != null) {\n                    for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) {\n                        if (constraint.appliesToVersion(gameVersion, version, java, analyzer)) {\n                            if (!constraint.checkJava(gameVersion, version, java, analyzer)) {\n                                if (constraint.isMandatory()) {\n                                    violatedMandatoryConstraints.add(constraint);\n                                } else {\n                                    violatedSuggestedConstraints.add(constraint);\n                                }\n                            }\n                        }\n                    }\n                }\n\n                CompletableFuture<JavaRuntime> future = new CompletableFuture<>();\n                Task<JavaRuntime> result = Task.fromCompletableFuture(future);\n                Runnable breakAction = () -> future.completeExceptionally(new CancellationException(\"Launch operation was cancelled by user\"));\n\n                if (java == null || !violatedMandatoryConstraints.isEmpty()) {\n                    JavaRuntime suggestedJava = JavaManager.findSuitableJava(gameVersion, version);\n                    if (suggestedJava != null) {\n                        FXUtils.runInFX(() -> {\n                            Controllers.confirm(i18n(\"launch.advice.java.auto\"), i18n(\"message.warning\"), () -> {\n                                setting.setJavaAutoSelected();\n                                future.complete(suggestedJava);\n                            }, breakAction);\n                        });\n                        return result;\n                    } else if (java == null) {\n                        FXUtils.runInFX(() -> Controllers.dialog(\n                                i18n(\"launch.invalid_java\"),\n                                i18n(\"message.error\"),\n                                MessageType.ERROR,\n                                breakAction\n                        ));\n                        return result;\n                    } else {\n                        GameJavaVersion gameJavaVersion;\n                        if (violatedMandatoryConstraints.contains(JavaVersionConstraint.CLEANROOM)) {\n                            String cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM)\n                                    .orElse(\"\");\n\n                            gameJavaVersion = !cleanroomVersion.isEmpty()\n                                    ? GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion)\n                                    : GameJavaVersion.JAVA_21;\n                        } else if (violatedMandatoryConstraints.contains(JavaVersionConstraint.GAME_JSON))\n                            gameJavaVersion = version.getJavaVersion();\n                        else if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA))\n                            gameJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersion);\n                        else\n                            gameJavaVersion = null;\n\n                        if (gameJavaVersion != null) {\n                            FXUtils.runInFX(() -> downloadJava(gameJavaVersion, profile).whenCompleteAsync((downloadedJava, throwable) -> {\n                                if (throwable == null) {\n                                    setting.setJavaAutoSelected();\n                                    future.complete(downloadedJava);\n                                } else {\n                                    LOG.warning(\"Failed to download java\", throwable);\n                                    breakAction.run();\n                                }\n                            }, Schedulers.javafx()));\n                            return result;\n                        }\n\n                        if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA_LINUX_JAVA_8)) {\n                            if (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {\n                                FXUtils.runInFX(() -> Controllers.dialog(i18n(\"launch.advice.vanilla_linux_java_8\"), i18n(\"message.error\"), MessageType.ERROR, breakAction));\n                                return result;\n                            } else {\n                                violatedMandatoryConstraints.remove(JavaVersionConstraint.VANILLA_LINUX_JAVA_8);\n                            }\n                        }\n\n                        if (violatedMandatoryConstraints.contains(JavaVersionConstraint.LAUNCH_WRAPPER)) {\n                            FXUtils.runInFX(() -> Controllers.dialog(\n                                    i18n(\"launch.advice.java9\") + \"\\n\" + i18n(\"launch.advice.uncorrected\"),\n                                    i18n(\"message.error\"),\n                                    MessageType.ERROR,\n                                    breakAction\n                            ));\n                            return result;\n                        }\n\n                        if (!violatedMandatoryConstraints.isEmpty()) {\n                            FXUtils.runInFX(() -> Controllers.dialog(\n                                    i18n(\"launch.advice.unknown\") + \"\\n\" + violatedMandatoryConstraints,\n                                    i18n(\"message.error\"),\n                                    MessageType.ERROR,\n                                    breakAction\n                            ));\n                            return result;\n                        }\n                    }\n                }\n\n                List<String> suggestions = new ArrayList<>();\n\n                if (Architecture.SYSTEM_ARCH == Architecture.X86_64 && java.getPlatform().getArchitecture() == Architecture.X86) {\n                    suggestions.add(i18n(\"launch.advice.different_platform\"));\n                }\n\n                // 32-bit JVM cannot make use of too much memory.\n                if (java.getBits() == Bits.BIT_32 && setting.getMaxMemory() > 1.5 * 1024) {\n                    // 1.5 * 1024 is an inaccurate number.\n                    // Actual memory limit depends on operating system and memory.\n                    suggestions.add(i18n(\"launch.advice.too_large_memory_for_32bit\"));\n                }\n\n                for (JavaVersionConstraint violatedSuggestedConstraint : violatedSuggestedConstraints) {\n                    switch (violatedSuggestedConstraint) {\n                        case MODDED_JAVA_7:\n                            suggestions.add(i18n(\"launch.advice.java.modded_java_7\"));\n                            break;\n                        case MODDED_JAVA_8:\n                            // Minecraft>=1.7.10+Forge accepts Java 8\n                            if (java.getParsedVersion() < 8)\n                                suggestions.add(i18n(\"launch.advice.newer_java\"));\n                            else\n                                suggestions.add(i18n(\"launch.advice.modded_java\", 8, gameVersion));\n                            break;\n                        case MODDED_JAVA_16:\n                            // Minecraft<=1.17.1+Forge[37.0.0,37.0.60) not compatible with Java 17\n                            String forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE).orElse(null);\n                            if (forgePatchVersion != null && VersionNumber.compare(forgePatchVersion, \"37.0.60\") < 0)\n                                suggestions.add(i18n(\"launch.advice.forge37_0_60\"));\n                            else\n                                suggestions.add(i18n(\"launch.advice.modded_java\", 16, gameVersion));\n                            break;\n                        case MODDED_JAVA_17:\n                            suggestions.add(i18n(\"launch.advice.modded_java\", 17, gameVersion));\n                            break;\n                        case MODDED_JAVA_21:\n                            suggestions.add(i18n(\"launch.advice.modded_java\", 21, gameVersion));\n                            break;\n                        case CLEANROOM: {\n                            String cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM).orElse(\"\");\n                            if (!cleanroomVersion.isEmpty())\n                                suggestions.add(i18n(\"launch.advice.cleanroom\", GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion).majorVersion(), cleanroomVersion));\n                            else\n                                suggestions.add(i18n(\"launch.advice.cleanroom\", 21, \"\"));\n                            break;\n                        }\n                        case VANILLA_JAVA_8_51:\n                            suggestions.add(i18n(\"launch.advice.java8_51_1_13\"));\n                            break;\n                        case MODLAUNCHER_8:\n                            suggestions.add(i18n(\"launch.advice.modlauncher8\"));\n                            break;\n                        case VANILLA_X86:\n                            if (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER\n                                    && isCompatibleWithX86Java()) {\n                                suggestions.add(i18n(\"launch.advice.vanilla_x86.translation\"));\n                            }\n                            break;\n                        default:\n                            suggestions.add(violatedSuggestedConstraint.name());\n                    }\n                }\n\n                // Cannot allocate too much memory exceeding free space.\n                long totalMemorySizeMB = (long) MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize());\n                if (totalMemorySizeMB > 0 && totalMemorySizeMB < setting.getMaxMemory()) {\n                    suggestions.add(i18n(\"launch.advice.not_enough_space\", totalMemorySizeMB));\n                }\n\n                VersionNumber forgeVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE)\n                        .map(VersionNumber::asVersion)\n                        .orElse(null);\n\n                // Forge 2760~2773 will crash game with LiteLoader.\n                boolean hasForge2760 = forgeVersion != null && (forgeVersion.compareTo(\"1.12.2-14.23.5.2760\") >= 0) && (forgeVersion.compareTo(\"1.12.2-14.23.5.2773\") < 0);\n                boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is(\"com.mumfrey\", \"liteloader\"));\n                if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(\"1.12.2\") == 0) {\n                    suggestions.add(i18n(\"launch.advice.forge2760_liteloader\"));\n                }\n\n                // OptiFine 1.14.4 is not compatible with Forge 28.2.2 and later versions.\n                boolean hasForge28_2_2 = forgeVersion != null && (forgeVersion.compareTo(\"1.14.4-28.2.2\") >= 0);\n                boolean hasOptiFine = version.getLibraries().stream().anyMatch(it -> it.is(\"optifine\", \"OptiFine\"));\n                if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo(\"1.14.4\") == 0) {\n                    suggestions.add(i18n(\"launch.advice.forge28_2_2_optifine\"));\n                }\n\n                if (suggestions.isEmpty()) {\n                    if (!future.isDone()) {\n                        future.complete(java);\n                    }\n                } else {\n                    String message;\n                    if (suggestions.size() == 1) {\n                        message = i18n(\"launch.advice\", suggestions.get(0));\n                    } else {\n                        message = i18n(\"launch.advice.multi\", suggestions.stream().map(it -> \"→ \" + it).collect(Collectors.joining(\"\\n\")));\n                    }\n\n                    FXUtils.runInFX(() -> Controllers.confirm(\n                            message,\n                            i18n(\"message.warning\"),\n                            MessageType.WARNING,\n                            () -> future.complete(java),\n                            breakAction));\n                }\n\n                return result;\n            });\n        }\n\n        return task.withStage(\"launch.state.java\");\n    }\n\n    private static CompletableFuture<JavaRuntime> downloadJava(GameJavaVersion javaVersion, Profile profile) {\n        CompletableFuture<JavaRuntime> future = new CompletableFuture<>();\n        Controllers.dialog(new MessageDialogPane.Builder(\n                i18n(\"launch.advice.require_newer_java_version\", javaVersion.majorVersion()),\n                i18n(\"message.warning\"),\n                MessageType.QUESTION)\n                .yesOrNo(() -> {\n                    DownloadProvider downloadProvider = profile.getDependency().getDownloadProvider();\n                    Controllers.taskDialog(JavaManager.getDownloadJavaTask(downloadProvider, SYSTEM_PLATFORM, javaVersion)\n                            .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                                if (exception == null) {\n                                    future.complete(result);\n                                } else {\n                                    Throwable resolvedException = resolveException(exception);\n                                    LOG.warning(\"Failed to download java\", exception);\n                                    if (!(resolvedException instanceof CancellationException)) {\n                                        Controllers.dialog(DownloadProviders.localizeErrorMessage(resolvedException), i18n(\"install.failed\"));\n                                    }\n                                    future.completeExceptionally(new CancellationException());\n                                }\n                            }), i18n(\"download.java\"), new TaskCancellationAction(() -> future.completeExceptionally(new CancellationException())));\n                }, () -> future.completeExceptionally(new CancellationException())).build());\n\n        return future;\n    }\n\n    private Task<AuthInfo> logIn(Account account) {\n        return Task.composeAsync(() -> {\n            try {\n                if (disableOfflineSkin && account instanceof OfflineAccount offlineAccount)\n                    return Task.completed(offlineAccount.logInWithoutSkin());\n                else\n                    return Task.completed(account.logIn());\n            } catch (CredentialExpiredException e) {\n                LOG.info(\"Credential has expired\", e);\n\n                return Task.completed(DialogController.logIn(account));\n            } catch (AuthenticationException e) {\n                LOG.warning(\"Authentication failed, try skipping refresh\", e);\n\n                CompletableFuture<Task<AuthInfo>> future = new CompletableFuture<>();\n                runInFX(() -> {\n                    JFXButton loginOfflineButton = new JFXButton(i18n(\"account.login.skip\"));\n                    loginOfflineButton.setOnAction(event -> {\n                        try {\n                            future.complete(Task.completed(account.playOffline()));\n                        } catch (AuthenticationException e2) {\n                            future.completeExceptionally(e2);\n                        }\n                    });\n                    JFXButton retryButton = new JFXButton(i18n(\"account.login.retry\"));\n                    retryButton.setOnAction(event -> {\n                        future.complete(logIn(account));\n                    });\n                    Controllers.dialog(new MessageDialogPane.Builder(i18n(\"account.failed.server_disconnected\"), i18n(\"account.failed\"), MessageType.ERROR)\n                            .addAction(loginOfflineButton)\n                            .addAction(retryButton)\n                            .addCancel(() ->\n                                    future.completeExceptionally(new CancellationException()))\n                            .build());\n                });\n                return Task.fromCompletableFuture(future).thenComposeAsync(task -> task);\n            }\n        });\n    }\n\n    private void checkExit() {\n        switch (launcherVisibility) {\n            case HIDE_AND_REOPEN:\n                runLater(() -> {\n                    Optional.ofNullable(Controllers.getStage())\n                            .ifPresent(Stage::show);\n                });\n                break;\n            case KEEP:\n                // No operations here\n                break;\n            case CLOSE:\n                throw new Error(\"Never get to here\");\n            case HIDE:\n                runLater(() -> {\n                    // Shut down the platform when user closed log window.\n                    setImplicitExit(true);\n                    // If we use Launcher.stop(), log window will be halt immediately.\n                    Launcher.stopWithoutPlatform();\n                });\n                break;\n        }\n    }\n\n    /**\n     * The managed process listener.\n     * Guarantee that one [JavaProcess], one [HMCLProcessListener].\n     * Because every time we launched a game, we generates a new [HMCLProcessListener]\n     */\n    private final class HMCLProcessListener implements ProcessListener {\n\n        private final ReentrantLock lock = new ReentrantLock();\n        private final HMCLGameRepository repository;\n        private final Version version;\n        private final LaunchOptions launchOptions;\n        private ManagedProcess process;\n        private volatile boolean lwjgl;\n        private LogWindow logWindow;\n        private final boolean detectWindow;\n        private final CircularArrayList<Log> logs;\n        private final CountDownLatch launchingLatch;\n        private final String forbiddenAccessToken;\n        private Thread submitLogThread;\n        private LinkedBlockingQueue<Log> logBuffer;\n\n        public HMCLProcessListener(HMCLGameRepository repository, Version version, AuthInfo authInfo, LaunchOptions launchOptions, CountDownLatch launchingLatch, boolean detectWindow) {\n            this.repository = repository;\n            this.version = version;\n            this.launchOptions = launchOptions;\n            this.launchingLatch = launchingLatch;\n            this.detectWindow = detectWindow;\n            this.forbiddenAccessToken = authInfo != null ? authInfo.getAccessToken() : null;\n            this.logs = new CircularArrayList<>(Log.getLogLines() + 1);\n        }\n\n        @Override\n        public void setProcess(ManagedProcess process) {\n            this.process = process;\n\n            String command = new CommandBuilder().addAll(process.getCommands()).toString();\n\n            LOG.info(\"Launched process: \" + command);\n\n            String classpath = process.getClasspath();\n            if (classpath != null) {\n                LOG.info(\"Process ClassPath: \" + classpath);\n            }\n\n            if (showLogs) {\n                CountDownLatch logWindowLatch = new CountDownLatch(1);\n                runLater(() -> {\n                    logWindow = new LogWindow(process, logs);\n                    logWindow.show();\n                    logWindowLatch.countDown();\n                });\n\n                logBuffer = new LinkedBlockingQueue<>();\n                submitLogThread = Lang.thread(new Runnable() {\n                    private final ArrayList<Log> currentLogs = new ArrayList<>();\n                    private final Semaphore semaphore = new Semaphore(0);\n\n                    private void submitLogs() {\n                        if (currentLogs.size() == 1) {\n                            Log log = currentLogs.get(0);\n                            runLater(() -> logWindow.logLine(log));\n                        } else {\n                            runLater(() -> {\n                                logWindow.logLines(currentLogs);\n                                semaphore.release();\n                            });\n                            semaphore.acquireUninterruptibly();\n                        }\n                        currentLogs.clear();\n                    }\n\n                    @Override\n                    public void run() {\n                        while (true) {\n                            try {\n                                currentLogs.add(logBuffer.take());\n                                //noinspection BusyWait\n                                Thread.sleep(200); // Wait for more logs\n                            } catch (InterruptedException e) {\n                                break;\n                            }\n\n                            logBuffer.drainTo(currentLogs);\n                            submitLogs();\n                        }\n\n                        do {\n                            submitLogs();\n                        } while (logBuffer.drainTo(currentLogs) > 0);\n                    }\n                }, \"Game Log Submitter\", true);\n\n                try {\n                    logWindowLatch.await();\n                } catch (InterruptedException e) {\n                    Thread.currentThread().interrupt();\n                }\n            }\n        }\n\n        private void finishLaunch() {\n            switch (launcherVisibility) {\n                case HIDE_AND_REOPEN:\n                    runLater(() -> {\n                        // If application was stopped and execution services did not finish termination,\n                        // these codes will be executed.\n                        if (Controllers.getStage() != null) {\n                            Controllers.getStage().hide();\n                            launchingLatch.countDown();\n                        }\n                    });\n                    break;\n                case CLOSE:\n                    // Never come to here.\n                    break;\n                case KEEP:\n                    runLater(launchingLatch::countDown);\n                    break;\n                case HIDE:\n                    launchingLatch.countDown();\n                    runLater(() -> {\n                        // If application was stopped and execution services did not finish termination,\n                        // these codes will be executed.\n                        if (Controllers.getStage() != null) {\n                            Controllers.getStage().close();\n                            Controllers.shutdown();\n                            Schedulers.shutdown();\n                            System.gc();\n                        }\n                    });\n                    break;\n            }\n        }\n\n        @Override\n        public void onLog(String log, boolean isErrorStream) {\n            if (isErrorStream)\n                System.err.println(log);\n            else\n                System.out.println(log);\n\n            log = StringUtils.parseEscapeSequence(log);\n            if (forbiddenAccessToken != null)\n                log = log.replace(forbiddenAccessToken, \"<access token>\");\n\n            Log4jLevel level = isErrorStream && !log.startsWith(\"[authlib-injector]\") ? Log4jLevel.ERROR : null;\n            if (showLogs) {\n                if (level == null)\n                    level = Lang.requireNonNullElse(Log4jLevel.guessLevel(log), Log4jLevel.INFO);\n                logBuffer.add(new Log(log, level));\n            } else {\n                lock.lock();\n                try {\n                    logs.addLast(new Log(log, level));\n                    if (logs.size() > Log.getLogLines())\n                        logs.removeFirst();\n                } finally {\n                    lock.unlock();\n                }\n            }\n\n            if (!lwjgl) {\n                String lowerCaseLog = log.toLowerCase(Locale.ROOT);\n                if (!detectWindow || lowerCaseLog.contains(\"lwjgl version\") || lowerCaseLog.contains(\"lwjgl openal\")) {\n                    lock.lock();\n                    try {\n                        if (!lwjgl) {\n                            lwjgl = true;\n                            finishLaunch();\n                        }\n                    } finally {\n                        lock.unlock();\n                    }\n                }\n            }\n        }\n\n        @Override\n        public void onExit(int exitCode, ExitType exitType) {\n            if (showLogs) {\n                logBuffer.add(new Log(String.format(\"[HMCL ProcessListener] Minecraft exit with code %d(0x%x), type is %s.\", exitCode, exitCode, exitType), Log4jLevel.INFO));\n                submitLogThread.interrupt();\n                try {\n                    submitLogThread.join();\n                } catch (InterruptedException e) {\n                    Thread.currentThread().interrupt();\n                }\n            }\n\n            launchingLatch.countDown();\n\n            if (exitType == ExitType.INTERRUPTED)\n                return;\n\n            // Game crashed before opening the game window.\n            if (!lwjgl) {\n                lock.lock();\n                try {\n                    if (!lwjgl)\n                        finishLaunch();\n                } finally {\n                    lock.unlock();\n                }\n            }\n\n            if (exitType != ExitType.NORMAL) {\n                repository.markVersionLaunchedAbnormally(version.getId());\n                runLater(() -> new GameCrashWindow(process, exitType, repository, version, launchOptions, logs).show());\n            }\n\n            checkExit();\n        }\n\n    }\n\n    private static final Queue<WeakReference<ManagedProcess>> PROCESSES = new ConcurrentLinkedQueue<>();\n\n    public static int countMangedProcesses() {\n        PROCESSES.removeIf(it -> {\n            ManagedProcess process = it.get();\n            return process == null || !process.isRunning();\n        });\n        return PROCESSES.size();\n    }\n\n    public static void stopManagedProcesses() {\n        while (!PROCESSES.isEmpty())\n            Optional.ofNullable(PROCESSES.poll()).map(WeakReference::get).ifPresent(ManagedProcess::stop);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.RemoteModRepository;\nimport org.jackhuang.hmcl.ui.versions.ModTranslations;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic abstract class LocalizedRemoteModRepository implements RemoteModRepository {\n    private static final int CONTAIN_CHINESE_WEIGHT = 10;\n\n    private static final int INITIAL_CAPACITY = 16;\n\n    protected abstract RemoteModRepository getBackedRemoteModRepository();\n\n    protected abstract SortType getBackedRemoteModRepositorySortOrder();\n\n    @Override\n    public SearchResult search(DownloadProvider downloadProvider, String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {\n        if (!StringUtils.containsChinese(searchFilter)) {\n            return getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder);\n        }\n\n        Set<String> englishSearchFiltersSet = new HashSet<>(INITIAL_CAPACITY);\n\n        int count = 0;\n        for (ModTranslations.Mod mod : ModTranslations.getTranslationsByRepositoryType(getType()).searchMod(searchFilter)) {\n            for (String englishWord : StringUtils.tokenize(StringUtils.isNotBlank(mod.getSubname()) ? mod.getSubname() : mod.getName())) {\n                if (englishSearchFiltersSet.contains(englishWord)) {\n                    continue;\n                }\n\n                englishSearchFiltersSet.add(englishWord);\n            }\n\n            count++;\n            if (count >= 3) break;\n        }\n\n        RemoteMod[] searchResultArray = new RemoteMod[pageSize];\n        int totalPages, chineseIndex = 0, englishIndex = pageSize - 1;\n        {\n            SearchResult searchResult = getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, String.join(\" \", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder);\n            for (Iterator<RemoteMod> iterator = searchResult.getUnsortedResults().iterator(); iterator.hasNext(); ) {\n                if (chineseIndex > englishIndex) {\n                    LOG.warning(\"Too many search results! Are the backed remote mod repository broken? Or are the API broken?\");\n                    continue;\n                }\n\n                RemoteMod remoteMod = iterator.next();\n                ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug());\n                if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) {\n                    searchResultArray[chineseIndex++] = remoteMod;\n                } else {\n                    searchResultArray[englishIndex--] = remoteMod;\n                }\n            }\n            totalPages = searchResult.getTotalPages();\n        }\n\n        StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();\n        return new SearchResult(Stream.concat(Arrays.stream(searchResultArray, 0, chineseIndex).map(remoteMod -> {\n            ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug());\n            if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) {\n                return Pair.pair(remoteMod, Integer.MAX_VALUE);\n            }\n            String chineseRemoteModName = chineseRemoteMod.getName();\n            if (searchFilter.isEmpty() || chineseRemoteModName.isEmpty()) {\n                return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length()));\n            }\n            int l = levCalculator.calc(searchFilter, chineseRemoteModName);\n            for (int i = 0; i < searchFilter.length(); i++) {\n                if (chineseRemoteModName.indexOf(searchFilter.charAt(i)) >= 0) {\n                    l -= CONTAIN_CHINESE_WEIGHT;\n                }\n            }\n            return Pair.pair(remoteMod, l);\n        }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), Arrays.stream(searchResultArray, englishIndex + 1, searchResultArray.length)), totalPages);\n    }\n\n    @Override\n    public Stream<Category> getCategories() throws IOException {\n        return getBackedRemoteModRepository().getCategories();\n    }\n\n    @Override\n    public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {\n        return getBackedRemoteModRepository().getRemoteVersionByLocalFile(localModFile, file);\n    }\n\n    @Override\n    public RemoteMod getModById(DownloadProvider downloadProvider, String id) throws IOException {\n        return getBackedRemoteModRepository().getModById(downloadProvider, id);\n    }\n\n    @Override\n    public RemoteMod.File getModFile(String modId, String fileId) throws IOException {\n        return getBackedRemoteModRepository().getModFile(modId, fileId);\n    }\n\n    @Override\n    public Stream<RemoteMod.Version> getRemoteVersionsById(DownloadProvider downloadProvider, String id) throws IOException {\n        return getBackedRemoteModRepository().getRemoteVersionsById(downloadProvider, id);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/Log.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.Log4jLevel;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\n\npublic final class Log {\n    public static final int DEFAULT_LOG_LINES = 2000;\n\n    public static int getLogLines() {\n        Integer lines = config().getLogLines();\n        return lines != null && lines > 0 ? lines : DEFAULT_LOG_LINES;\n    }\n\n    private final String log;\n    private Log4jLevel level;\n    private boolean selected = false;\n\n    public Log(String log) {\n        this.log = log;\n    }\n\n    public Log(String log, Log4jLevel level) {\n        this.log = log;\n        this.level = level;\n    }\n\n    public String getLog() {\n        return log;\n    }\n\n    public Log4jLevel getLevel() {\n        Log4jLevel level = this.level;\n        if (level == null) {\n            level = Log4jLevel.guessLevel(log);\n            if (level == null)\n                level = Log4jLevel.INFO;\n            this.level = level;\n        }\n        return level;\n    }\n\n    public boolean isSelected() {\n        return selected;\n    }\n\n    public void setSelected(boolean selected) {\n        this.selected = selected;\n    }\n\n    @Override\n    public String toString() {\n        return log;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jackhuang.hmcl.util.io.Zipper;\nimport org.jackhuang.hmcl.util.logging.Logger;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.nio.file.DirectoryStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.PathMatcher;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class LogExporter {\n    private LogExporter() {\n    }\n\n    public static CompletableFuture<Void> exportLogs(\n            Path zipFile, DefaultGameRepository gameRepository, String versionId, String logs, String launchScript,\n            PathMatcher logMatcher) {\n        Path runDirectory = gameRepository.getRunDirectory(versionId);\n        Path baseDirectory = gameRepository.getBaseDirectory();\n        List<String> versions = new ArrayList<>();\n\n        String currentVersionId = versionId;\n        HashSet<String> resolvedSoFar = new HashSet<>();\n        while (true) {\n            if (resolvedSoFar.contains(currentVersionId)) break;\n            resolvedSoFar.add(currentVersionId);\n            Version currentVersion = gameRepository.getVersion(currentVersionId);\n            versions.add(currentVersionId);\n\n            if (StringUtils.isNotBlank(currentVersion.getInheritsFrom())) {\n                currentVersionId = currentVersion.getInheritsFrom();\n            } else {\n                break;\n            }\n        }\n\n        return CompletableFuture.runAsync(() -> {\n            try (Zipper zipper = new Zipper(zipFile, true)) {\n                processLogs(runDirectory.resolve(\"liteconfig\"), \"*.log\", \"liteconfig\", zipper, logMatcher);\n                processLogs(runDirectory.resolve(\"logs\"), \"*.log\", \"logs\", zipper, logMatcher);\n                processLogs(runDirectory, \"*.log\", \"runDirectory\", zipper, logMatcher);\n                processLogs(runDirectory.resolve(\"crash-reports\"), \"*.txt\", \"crash-reports\", zipper, logMatcher);\n\n                zipper.putTextFile(LOG.getLogs(), \"hmcl.log\");\n                zipper.putTextFile(logs, \"minecraft.log\");\n                zipper.putTextFile(Logger.filterForbiddenToken(launchScript), OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? \"launch.bat\" : \"launch.sh\");\n\n                for (String id : versions) {\n                    Path versionJson = baseDirectory.resolve(\"versions\").resolve(id).resolve(id + \".json\");\n                    if (Files.exists(versionJson)) {\n                        zipper.putFile(versionJson, id + \".json\");\n                    }\n                }\n            } catch (IOException e) {\n                throw new UncheckedIOException(e);\n            }\n        });\n    }\n\n    private static void processLogs(Path directory, String fileExtension, String logDirectory, Zipper zipper, PathMatcher logMatcher) {\n        try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory, fileExtension)) {\n            for (Path file : stream) {\n                if (Files.isRegularFile(file)) {\n                    if (logMatcher == null || logMatcher.matches(file)) {\n                        try (BufferedReader reader = IOUtils.newBufferedReaderMaybeNativeEncoding(file)) {\n                            zipper.putLines(reader.lines().map(Logger::filterForbiddenToken), file.getFileName().toString());\n                        } catch (IOException e) {\n                            LOG.warning(\"Failed to read log file: \" + file, e);\n                        }\n                    }\n                }\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to find any log on \" + logDirectory, e);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/ManuallyCreatedModpackException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport java.nio.file.Path;\n\npublic class ManuallyCreatedModpackException extends Exception {\n    private final Path path;\n\n    public ManuallyCreatedModpackException(Path path) {\n        this.path = path;\n    }\n\n    public Path getPath() {\n        return path;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/ManuallyCreatedModpackInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.Unzipper;\n\nimport java.nio.charset.Charset;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\npublic class ManuallyCreatedModpackInstallTask extends Task<Path> {\n\n    private final Profile profile;\n    private final Path zipFile;\n    private final Charset charset;\n    private final String name;\n\n    public ManuallyCreatedModpackInstallTask(Profile profile, Path zipFile, Charset charset, String name) {\n        this.profile = profile;\n        this.zipFile = zipFile;\n        this.charset = charset;\n        this.name = name;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        Path subdirectory;\n        try (FileSystem fs = CompressingUtils.readonly(zipFile).setEncoding(charset).build()) {\n            subdirectory = ModpackHelper.findMinecraftDirectoryInManuallyCreatedModpack(zipFile.toString(), fs);\n        }\n\n        Path dest = Paths.get(\"externalgames\").resolve(name);\n\n        setResult(dest);\n\n        new Unzipper(zipFile, dest)\n                .setSubDirectory(subdirectory.toString())\n                .setTerminateIfSubDirectoryNotExists()\n                .setEncoding(charset)\n                .unzip();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.mod.*;\nimport org.jackhuang.hmcl.mod.curse.CurseModpackProvider;\nimport org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;\nimport org.jackhuang.hmcl.mod.mcbbs.McbbsModpackProvider;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthModpackProvider;\nimport org.jackhuang.hmcl.mod.multimc.MultiMCComponents;\nimport org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;\nimport org.jackhuang.hmcl.mod.multimc.MultiMCModpackProvider;\nimport org.jackhuang.hmcl.mod.server.ServerModpackManifest;\nimport org.jackhuang.hmcl.mod.server.ServerModpackProvider;\nimport org.jackhuang.hmcl.mod.server.ServerModpackRemoteInstallTask;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.setting.VersionSetting;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.function.ExceptionalConsumer;\nimport org.jackhuang.hmcl.util.function.ExceptionalRunnable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Lang.toIterable;\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\npublic final class ModpackHelper {\n    private ModpackHelper() {\n    }\n\n    private static final Map<String, ModpackProvider> providers = mapOf(\n            pair(CurseModpackProvider.INSTANCE.getName(), CurseModpackProvider.INSTANCE),\n            pair(McbbsModpackProvider.INSTANCE.getName(), McbbsModpackProvider.INSTANCE),\n            pair(ModrinthModpackProvider.INSTANCE.getName(), ModrinthModpackProvider.INSTANCE),\n            pair(MultiMCModpackProvider.INSTANCE.getName(), MultiMCModpackProvider.INSTANCE),\n            pair(ServerModpackProvider.INSTANCE.getName(), ServerModpackProvider.INSTANCE),\n            pair(HMCLModpackProvider.INSTANCE.getName(), HMCLModpackProvider.INSTANCE)\n    );\n\n    static {\n        MultiMCComponents.setImplementation(Metadata.FULL_TITLE);\n    }\n\n    @Nullable\n    public static ModpackProvider getProviderByType(String type) {\n        return providers.get(type);\n    }\n\n    public static boolean isFileModpackByExtension(Path file) {\n        String ext = FileUtils.getExtension(file);\n        return \"zip\".equals(ext) || \"mrpack\".equals(ext);\n    }\n\n    public static Modpack readModpackManifest(Path file, Charset charset) throws UnsupportedModpackException, ManuallyCreatedModpackException {\n        try (ZipArchiveReader zipFile = CompressingUtils.openZipFile(file, charset)) {\n            // Order for trying detecting manifest is necessary here.\n            // Do not change to iterating providers.\n            for (ModpackProvider provider : new ModpackProvider[]{\n                    McbbsModpackProvider.INSTANCE,\n                    CurseModpackProvider.INSTANCE,\n                    ModrinthModpackProvider.INSTANCE,\n                    HMCLModpackProvider.INSTANCE,\n                    MultiMCModpackProvider.INSTANCE,\n                    ServerModpackProvider.INSTANCE}) {\n                try {\n                    return provider.readManifest(zipFile, file, charset);\n                } catch (Exception ignored) {\n                }\n            }\n        } catch (IOException ignored) {\n        }\n\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(file, charset)) {\n            findMinecraftDirectoryInManuallyCreatedModpack(file.toString(), fs);\n            throw new ManuallyCreatedModpackException(file);\n        } catch (IOException e) {\n            // ignore it\n        }\n\n        throw new UnsupportedModpackException(file.toString());\n    }\n\n    public static Path findMinecraftDirectoryInManuallyCreatedModpack(String modpackName, FileSystem fs) throws IOException, UnsupportedModpackException {\n        Path root = fs.getPath(\"/\");\n        if (isMinecraftDirectory(root)) return root;\n        try (Stream<Path> firstLayer = Files.list(root)) {\n            for (Path dir : toIterable(firstLayer)) {\n                if (isMinecraftDirectory(dir)) return dir;\n\n                try (Stream<Path> secondLayer = Files.list(dir)) {\n                    for (Path subdir : toIterable(secondLayer)) {\n                        if (isMinecraftDirectory(subdir)) return subdir;\n                    }\n                } catch (IOException ignored) {\n                }\n            }\n        } catch (IOException ignored) {\n        }\n        throw new UnsupportedModpackException(modpackName);\n    }\n\n    private static boolean isMinecraftDirectory(Path path) {\n        return Files.isDirectory(path.resolve(\"versions\")) &&\n                (path.getFileName() == null || \".minecraft\".equals(FileUtils.getName(path)));\n    }\n\n    public static ModpackConfiguration<?> readModpackConfiguration(Path file) throws IOException {\n        try {\n            return JsonUtils.fromJsonFile(file, ModpackConfiguration.class);\n        } catch (JsonParseException e) {\n            throw new IOException(\"Malformed modpack configuration\");\n        }\n    }\n\n    public static Task<?> getInstallTask(Profile profile, ServerModpackManifest manifest, String name, Modpack modpack) {\n        profile.getRepository().markVersionAsModpack(name);\n\n        ExceptionalRunnable<?> success = () -> {\n            HMCLGameRepository repository = profile.getRepository();\n            repository.refreshVersions();\n            VersionSetting vs = repository.specializeVersionSetting(name);\n            repository.undoMark(name);\n            if (vs != null)\n                vs.setGameDirType(GameDirectoryType.VERSION_FOLDER);\n        };\n\n        ExceptionalConsumer<Exception, ?> failure = ex -> {\n            if (ex instanceof ModpackCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {\n                success.run();\n                // This is tolerable and we will not delete the game\n            }\n        };\n\n        return new ServerModpackRemoteInstallTask(profile.getDependency(), manifest, name)\n                .whenComplete(Schedulers.defaultScheduler(), success, failure)\n                .withStagesHints(\"hmcl.modpack\", \"hmcl.modpack.download\");\n    }\n\n    public static boolean isExternalGameNameConflicts(String name) {\n        return Files.exists(Paths.get(\"externalgames\").resolve(name));\n    }\n\n    public static Task<?> getInstallManuallyCreatedModpackTask(Profile profile, Path zipFile, String name, Charset charset) {\n        if (isExternalGameNameConflicts(name)) {\n            throw new IllegalArgumentException(\"name existing\");\n        }\n\n        return new ManuallyCreatedModpackInstallTask(profile, zipFile, charset, name)\n                .thenAcceptAsync(Schedulers.javafx(), location -> {\n                    Profile newProfile = new Profile(name, location);\n                    newProfile.setUseRelativePath(true);\n                    Profiles.getProfiles().add(newProfile);\n                    Profiles.setSelectedProfile(newProfile);\n                });\n    }\n\n    public static Task<?> getInstallTask(Profile profile, Path zipFile, String name, Modpack modpack, String iconUrl) {\n        profile.getRepository().markVersionAsModpack(name);\n\n        ExceptionalRunnable<?> success = () -> {\n            HMCLGameRepository repository = profile.getRepository();\n            repository.refreshVersions();\n            VersionSetting vs = repository.specializeVersionSetting(name);\n            repository.undoMark(name);\n            if (vs != null)\n                vs.setGameDirType(GameDirectoryType.VERSION_FOLDER);\n        };\n\n        ExceptionalConsumer<Exception, ?> failure = ex -> {\n            if (ex instanceof ModpackCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {\n                success.run();\n                // This is tolerable and we will not delete the game\n            }\n        };\n\n        if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)\n            return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)\n                    .whenComplete(Schedulers.defaultScheduler(), success, failure)\n                    .thenComposeAsync(createMultiMCPostInstallTask(profile, (MultiMCInstanceConfiguration) modpack.getManifest(), name))\n                    .withStagesHints(\"hmcl.modpack\", \"hmcl.modpack.download\");\n        else if (modpack.getManifest() instanceof McbbsModpackManifest)\n            return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)\n                    .whenComplete(Schedulers.defaultScheduler(), success, failure)\n                    .thenComposeAsync(createMcbbsPostInstallTask(profile, (McbbsModpackManifest) modpack.getManifest(), name))\n                    .withStagesHints(\"hmcl.modpack\", \"hmcl.modpack.download\");\n        else\n            return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)\n                    .whenComplete(Schedulers.javafx(), success, failure)\n                    .withStagesHints(\"hmcl.modpack\", \"hmcl.modpack.download\");\n    }\n\n    public static Task<Void> getUpdateTask(Profile profile, ServerModpackManifest manifest, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException {\n        switch (configuration.getType()) {\n            case ServerModpackRemoteInstallTask.MODPACK_TYPE:\n                return new ModpackUpdateTask(profile.getRepository(), name, new ServerModpackRemoteInstallTask(profile.getDependency(), manifest, name))\n                        .thenComposeAsync(profile.getRepository().refreshVersionsAsync())\n                        .withStagesHints(\"hmcl.modpack\", \"hmcl.modpack.download\");\n            default:\n                throw new UnsupportedModpackException();\n        }\n    }\n\n    public static Task<?> getUpdateTask(Profile profile, Path zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {\n        Modpack modpack = ModpackHelper.readModpackManifest(zipFile, charset);\n        ModpackProvider provider = getProviderByType(configuration.getType());\n        if (provider == null) {\n            throw new UnsupportedModpackException();\n        }\n        if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)\n            return provider.createUpdateTask(profile.getDependency(), name, zipFile, modpack)\n                    .thenComposeAsync(() -> createMultiMCPostUpdateTask(profile, (MultiMCInstanceConfiguration) modpack.getManifest(), name))\n                    .thenComposeAsync(profile.getRepository().refreshVersionsAsync());\n        else\n            return provider.createUpdateTask(profile.getDependency(), name, zipFile, modpack)\n                    .thenComposeAsync(profile.getRepository().refreshVersionsAsync());\n    }\n\n    public static void toVersionSetting(MultiMCInstanceConfiguration c, VersionSetting vs) {\n        vs.setUsesGlobal(false);\n        vs.setGameDirType(GameDirectoryType.VERSION_FOLDER);\n\n        if (c.isOverrideJavaLocation()) {\n            vs.setJavaDir(Lang.nonNull(c.getJavaPath(), \"\"));\n        }\n\n        if (c.isOverrideMemory()) {\n            vs.setPermSize(Optional.ofNullable(c.getPermGen()).map(Object::toString).orElse(\"\"));\n            if (c.getMaxMemory() != null)\n                vs.setMaxMemory(c.getMaxMemory());\n            vs.setMinMemory(c.getMinMemory());\n        }\n\n        if (c.isOverrideCommands()) {\n            vs.setWrapper(Lang.nonNull(c.getWrapperCommand(), \"\"));\n            vs.setPreLaunchCommand(Lang.nonNull(c.getPreLaunchCommand(), \"\"));\n        }\n\n        if (c.isOverrideJavaArgs()) {\n            vs.setJavaArgs(Lang.nonNull(c.getJvmArgs(), \"\"));\n        }\n\n        if (c.isOverrideConsole()) {\n            vs.setShowLogs(c.isShowConsole());\n        }\n\n        if (c.isOverrideWindow()) {\n            vs.setFullscreen(c.isFullscreen());\n            if (c.getWidth() != null)\n                vs.setWidth(c.getWidth());\n            if (c.getHeight() != null)\n                vs.setHeight(c.getHeight());\n        }\n    }\n\n    private static void applyCommandAndJvmSettings(MultiMCInstanceConfiguration c, VersionSetting vs) {\n        if (c.isOverrideCommands()) {\n            vs.setWrapper(Lang.nonNull(c.getWrapperCommand(), \"\"));\n            vs.setPreLaunchCommand(Lang.nonNull(c.getPreLaunchCommand(), \"\"));\n        }\n\n        if (c.isOverrideJavaArgs()) {\n            vs.setJavaArgs(Lang.nonNull(c.getJvmArgs(), \"\"));\n        }\n    }\n\n    private static Task<Void> createMultiMCPostUpdateTask(Profile profile, MultiMCInstanceConfiguration manifest, String version) {\n        return Task.runAsync(Schedulers.javafx(), () -> {\n            VersionSetting vs = Objects.requireNonNull(profile.getRepository().specializeVersionSetting(version));\n            ModpackHelper.applyCommandAndJvmSettings(manifest, vs);\n        });\n    }\n\n    private static Task<Void> createMultiMCPostInstallTask(Profile profile, MultiMCInstanceConfiguration manifest, String version) {\n        return Task.runAsync(Schedulers.javafx(), () -> {\n            VersionSetting vs = Objects.requireNonNull(profile.getRepository().specializeVersionSetting(version));\n            ModpackHelper.toVersionSetting(manifest, vs);\n        });\n    }\n\n    private static Task<Void> createMcbbsPostInstallTask(Profile profile, McbbsModpackManifest manifest, String version) {\n        return Task.runAsync(Schedulers.javafx(), () -> {\n            VersionSetting vs = Objects.requireNonNull(profile.getRepository().specializeVersionSetting(version));\n            if (manifest.getLaunchInfo().getMinMemory() > vs.getMaxMemory())\n                vs.setMaxMemory(manifest.getLaunchInfo().getMinMemory());\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport fi.iki.elonen.NanoHTTPD;\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.OAuth;\nimport org.jackhuang.hmcl.event.Event;\nimport org.jackhuang.hmcl.event.EventManager;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.io.IOException;\nimport java.security.SecureRandom;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Lang.thread;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class OAuthServer extends NanoHTTPD implements OAuth.Session {\n    private final int port;\n    private final CompletableFuture<String> future = new CompletableFuture<>();\n    private final String codeVerifier;\n    private final String state;\n\n    public static String lastlyOpenedURL;\n\n    private String idToken;\n\n    private OAuthServer(int port) {\n        super(port);\n\n        this.port = port;\n\n        var encoder = Base64.getUrlEncoder().withoutPadding();\n        var random = new SecureRandom();\n\n        {\n            // https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1\n            // https://datatracker.ietf.org/doc/html/rfc6749#section-10.12\n            byte[] bytes = new byte[32];\n            random.nextBytes(bytes);\n            this.state = encoder.encodeToString(bytes);\n        }\n\n        {\n            // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1\n            byte[] bytes = new byte[64];\n            random.nextBytes(bytes);\n            this.codeVerifier = encoder.encodeToString(bytes);\n        }\n    }\n\n    @Override\n    public String getCodeVerifier() {\n        return codeVerifier;\n    }\n\n    @Override\n    public String getState() {\n        return state;\n    }\n\n    @Override\n    public String getRedirectURI() {\n        return String.format(\"http://localhost:%d/auth-response\", port);\n    }\n\n    @Override\n    public String waitFor() throws InterruptedException, ExecutionException {\n        return future.get();\n    }\n\n    @Override\n    public String getIdToken() {\n        return idToken;\n    }\n\n    @Override\n    public Response serve(IHTTPSession session) {\n        if (!\"/auth-response\".equals(session.getUri())) {\n            return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_HTML, \"\");\n        }\n\n        if (session.getMethod() == Method.POST) {\n            Map<String, String> files = new HashMap<>();\n            try {\n                session.parseBody(files);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to read post data\", e);\n                return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_HTML, \"\");\n            } catch (ResponseException re) {\n                return newFixedLengthResponse(re.getStatus(), MIME_PLAINTEXT, re.getMessage());\n            }\n        } else if (session.getMethod() == Method.GET) {\n            // do nothing\n        } else {\n            return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_HTML, \"\");\n        }\n        String parameters = session.getQueryParameterString();\n\n        Map<String, String> query = mapOf(NetworkUtils.parseQuery(parameters));\n\n        String code = query.get(\"code\");\n        if (code != null) {\n            if (this.state.equals(query.get(\"state\"))) {\n                idToken = query.get(\"id_token\");\n                future.complete(code);\n            } else if (query.containsKey(\"state\")) {\n                LOG.warning(\"Failed to authenticate: invalid state in parameters\");\n                future.completeExceptionally(new AuthenticationException(\"Failed to authenticate: invalid state\"));\n            } else {\n                LOG.warning(\"Failed to authenticate: missing state in parameters\");\n                future.completeExceptionally(new AuthenticationException(\"Failed to authenticate: missing state\"));\n            }\n        } else {\n            LOG.warning(\"Failed to authenticate: missing authorization code in parameters\");\n            future.completeExceptionally(new AuthenticationException(\"Failed to authenticate: missing authorization code\"));\n        }\n\n        String html;\n        try {\n            html = IOUtils.readFullyAsString(OAuthServer.class.getResourceAsStream(\"/assets/microsoft_auth.html\"))\n                    .replace(\"%style%\", Themes.getTheme().toColorScheme().toStyleSheet().replace(\"-monet\", \"--monet\"))\n                    .replace(\"%lang%\", Locale.getDefault().toLanguageTag())\n                    .replace(\"%success%\", i18n(\"message.success\"))\n                    .replace(\"%ok%\", i18n(\"button.ok\"))\n                    .replace(\"%close_page%\", i18n(\"account.methods.microsoft.close_page\"));\n        } catch (IOException e) {\n            LOG.error(\"Failed to load html\", e);\n            return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_HTML, \"\");\n        }\n        thread(() -> {\n            try {\n                Thread.sleep(1000);\n                stop();\n            } catch (InterruptedException e) {\n                LOG.error(\"Failed to sleep for 1 second\");\n            }\n        });\n        return newFixedLengthResponse(Response.Status.OK, \"text/html; charset=UTF-8\", html);\n    }\n\n    public static class Factory implements OAuth.Callback {\n        public final EventManager<GrantDeviceCodeEvent> onGrantDeviceCode = new EventManager<>();\n        public final EventManager<OpenBrowserEvent> onOpenBrowserAuthorizationCode = new EventManager<>();\n        public final EventManager<OpenBrowserEvent> onOpenBrowserDevice = new EventManager<>();\n\n        @Override\n        public OAuth.Session startServer() throws IOException, AuthenticationException {\n            if (StringUtils.isBlank(getClientId())) {\n                throw new MicrosoftAuthenticationNotSupportedException();\n            }\n\n            IOException exception = null;\n            for (int port : new int[]{29111, 29112, 29113, 29114, 29115}) {\n                try {\n                    OAuthServer server = new OAuthServer(port);\n                    server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, true);\n                    return server;\n                } catch (IOException e) {\n                    exception = e;\n                }\n            }\n            throw exception;\n        }\n\n        @Override\n        public void grantDeviceCode(String userCode, String verificationURI) {\n            onGrantDeviceCode.fireEvent(new GrantDeviceCodeEvent(this, userCode, verificationURI));\n        }\n\n        @Override\n        public void openBrowser(OAuth.GrantFlow grantFlow, String url) throws IOException {\n            lastlyOpenedURL = url;\n\n            switch (grantFlow) {\n                case AUTHORIZATION_CODE -> onOpenBrowserAuthorizationCode.fireEvent(new OpenBrowserEvent(this, url));\n                case DEVICE -> onOpenBrowserDevice.fireEvent(new OpenBrowserEvent(this, url));\n            }\n        }\n\n        @Override\n        public String getClientId() {\n            return System.getProperty(\"hmcl.microsoft.auth.id\",\n                    JarUtils.getAttribute(\"hmcl.microsoft.auth.id\", \"\"));\n        }\n    }\n\n    public static class GrantDeviceCodeEvent extends Event {\n        private final String userCode;\n        private final String verificationUri;\n\n        public GrantDeviceCodeEvent(Object source, String userCode, String verificationUri) {\n            super(source);\n            this.userCode = userCode;\n            this.verificationUri = verificationUri;\n        }\n\n        public String getUserCode() {\n            return userCode;\n        }\n\n        public String getVerificationUri() {\n            return verificationUri;\n        }\n    }\n\n    public static class OpenBrowserEvent extends Event {\n        private final String url;\n\n        public OpenBrowserEvent(Object source, String url) {\n            super(source);\n            this.url = url;\n        }\n\n        public String getUrl() {\n            return url;\n        }\n    }\n\n    public static class MicrosoftAuthenticationNotSupportedException extends AuthenticationException {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.binding.ObjectBinding;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.canvas.Canvas;\nimport javafx.scene.canvas.GraphicsContext;\nimport javafx.scene.image.Image;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.auth.ServerResponseMalformedException;\nimport org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;\nimport org.jackhuang.hmcl.auth.offline.OfflineAccount;\nimport org.jackhuang.hmcl.auth.offline.Skin;\nimport org.jackhuang.hmcl.auth.yggdrasil.*;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.Holder;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.ref.WeakReference;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.singletonMap;\nimport static java.util.Objects.requireNonNull;\nimport static org.jackhuang.hmcl.util.Lang.threadPool;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author yushijinhun\n */\npublic final class TexturesLoader {\n\n    private TexturesLoader() {\n    }\n\n    // ==== Texture Loading ====\n    public static class LoadedTexture {\n        private final Image image;\n        private final Map<String, String> metadata;\n\n        public LoadedTexture(Image image, Map<String, String> metadata) {\n            this.image = requireNonNull(image);\n            this.metadata = requireNonNull(metadata);\n        }\n\n        public Image getImage() {\n            return image;\n        }\n\n        public Map<String, String> getMetadata() {\n            return metadata;\n        }\n    }\n\n    private static final ThreadPoolExecutor POOL = threadPool(\"TexturesDownload\", true, 2, 10, TimeUnit.SECONDS);\n    private static final Path TEXTURES_DIR = Metadata.HMCL_GLOBAL_DIRECTORY.resolve(\"skins\");\n\n    private static Path getTexturePath(Texture texture) {\n        String url = texture.getUrl();\n        int slash = url.lastIndexOf('/');\n        int dot = url.lastIndexOf('.');\n        if (dot < slash) {\n            dot = url.length();\n        }\n        String hash = url.substring(slash + 1, dot);\n        String prefix = hash.length() > 2 ? hash.substring(0, 2) : \"xx\";\n        return TEXTURES_DIR.resolve(prefix).resolve(hash);\n    }\n\n    public static LoadedTexture loadTexture(Texture texture) throws Throwable {\n        if (StringUtils.isBlank(texture.getUrl())) {\n            throw new IOException(\"Texture url is empty\");\n        }\n\n        Path file = getTexturePath(texture);\n        if (!Files.isRegularFile(file)) {\n            // download it\n            try {\n                new FileDownloadTask(texture.getUrl(), file).run();\n                LOG.info(\"Texture downloaded: \" + texture.getUrl());\n            } catch (Exception e) {\n                if (Files.isRegularFile(file)) {\n                    // concurrency conflict?\n                    LOG.warning(\"Failed to download texture \" + texture.getUrl() + \", but the file is available\", e);\n                } else {\n                    throw new IOException(\"Failed to download texture \" + texture.getUrl());\n                }\n            }\n        }\n\n        Image img;\n        try (InputStream in = Files.newInputStream(file)) {\n            img = new Image(in);\n        }\n\n        if (img.isError())\n            throw img.getException();\n\n        Map<String, String> metadata = texture.getMetadata();\n        if (metadata == null) {\n            metadata = emptyMap();\n        }\n        return new LoadedTexture(img, metadata);\n    }\n    // ====\n\n    // ==== Skins ====\n    private static final String[] DEFAULT_SKINS = {\"alex\", \"ari\", \"efe\", \"kai\", \"makena\", \"noor\", \"steve\", \"sunny\", \"zuri\"};\n\n    public static Image getDefaultSkinImage() {\n        return FXUtils.newBuiltinImage(\"/assets/img/skin/wide/steve.png\");\n    }\n\n    public static LoadedTexture getDefaultSkin(UUID uuid) {\n        int idx = Math.floorMod(uuid.hashCode(), DEFAULT_SKINS.length * 2);\n        TextureModel model;\n        Image skin;\n        if (idx < DEFAULT_SKINS.length) {\n            model = TextureModel.SLIM;\n            skin = FXUtils.newBuiltinImage(\"/assets/img/skin/slim/\" + DEFAULT_SKINS[idx] + \".png\");\n        } else {\n            model = TextureModel.WIDE;\n            skin = FXUtils.newBuiltinImage(\"/assets/img/skin/wide/\" + DEFAULT_SKINS[idx - DEFAULT_SKINS.length] + \".png\");\n        }\n\n        return new LoadedTexture(skin, singletonMap(\"model\", model.modelName));\n    }\n\n    public static TextureModel getDefaultModel(UUID uuid) {\n        return TextureModel.WIDE.modelName.equals(getDefaultSkin(uuid).getMetadata().get(\"model\"))\n                ? TextureModel.WIDE\n                : TextureModel.SLIM;\n    }\n\n    public static ObjectBinding<LoadedTexture> skinBinding(YggdrasilService service, UUID uuid) {\n        LoadedTexture uuidFallback = getDefaultSkin(uuid);\n        return BindingMapping.of(service.getProfileRepository().binding(uuid))\n                .map(profile -> profile\n                        .flatMap(it -> {\n                            try {\n                                return YggdrasilService.getTextures(it);\n                            } catch (ServerResponseMalformedException e) {\n                                LOG.warning(\"Failed to parse texture payload\", e);\n                                return Optional.empty();\n                            }\n                        })\n                        .flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))\n                        .filter(it -> StringUtils.isNotBlank(it.getUrl())))\n                .asyncMap(it -> {\n                    if (it.isPresent()) {\n                        Texture texture = it.get();\n                        return CompletableFuture.supplyAsync(() -> {\n                            try {\n                                return loadTexture(texture);\n                            } catch (Throwable e) {\n                                LOG.warning(\"Failed to load texture \" + texture.getUrl() + \", using fallback texture\", e);\n                                return uuidFallback;\n                            }\n                        }, POOL);\n                    } else {\n                        return CompletableFuture.completedFuture(uuidFallback);\n                    }\n                }, uuidFallback);\n    }\n\n    public static ObservableValue<LoadedTexture> skinBinding(Account account) {\n        LoadedTexture uuidFallback = getDefaultSkin(account.getUUID());\n        if (account instanceof OfflineAccount) {\n            OfflineAccount offlineAccount = (OfflineAccount) account;\n\n            SimpleObjectProperty<LoadedTexture> binding = new SimpleObjectProperty<>();\n            InvalidationListener listener = o -> {\n                Skin skin = offlineAccount.getSkin();\n                String username = offlineAccount.getUsername();\n\n                binding.set(uuidFallback);\n                if (skin != null) {\n                    skin.load(username).setExecutor(POOL).whenComplete(Schedulers.javafx(), (result, exception) -> {\n                        if (exception != null) {\n                            LOG.warning(\"Failed to load texture\", exception);\n                        } else if (result != null && result.getSkin() != null && result.getSkin().getImage() != null) {\n                            Map<String, String> metadata;\n                            if (result.getModel() != null) {\n                                metadata = singletonMap(\"model\", result.getModel().modelName);\n                            } else {\n                                metadata = emptyMap();\n                            }\n\n                            binding.set(new LoadedTexture(result.getSkin().getImage(), metadata));\n                        }\n                    }).start();\n                }\n            };\n\n            listener.invalidated(offlineAccount);\n\n            binding.addListener(new Holder<>(listener));\n            offlineAccount.addListener(new WeakInvalidationListener(listener));\n\n            return binding;\n        } else {\n            return BindingMapping.of(account.getTextures())\n                    .asyncMap(textures -> {\n                        if (textures.isPresent()) {\n                            Texture texture = textures.get().get(TextureType.SKIN);\n                            if (texture != null && StringUtils.isNotBlank(texture.getUrl())) {\n                                return CompletableFuture.supplyAsync(() -> {\n                                    try {\n                                        return loadTexture(texture);\n                                    } catch (Throwable e) {\n                                        LOG.warning(\"Failed to load texture \" + texture.getUrl() + \", using fallback texture\", e);\n                                        return uuidFallback;\n                                    }\n                                }, POOL);\n                            }\n                        }\n\n                        return CompletableFuture.completedFuture(uuidFallback);\n                    }, uuidFallback);\n        }\n    }\n\n    // ====\n\n    // ==== Avatar ====\n    public static void drawAvatar(Canvas canvas, Image skin) {\n        GraphicsContext g = canvas.getGraphicsContext2D();\n        g.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());\n\n        int size = (int) canvas.getWidth();\n        int scale = (int) skin.getWidth() / 64;\n        int faceOffset = (int) Math.round(size / 18.0);\n\n        g.setImageSmoothing(false);\n        drawAvatar(g, skin, size, scale, faceOffset);\n    }\n\n    private static void drawAvatar(GraphicsContext g, Image skin, int size, int scale, int faceOffset) {\n        g.drawImage(skin,\n                8 * scale, 8 * scale, 8 * scale, 8 * scale,\n                faceOffset, faceOffset, size - 2 * faceOffset, size - 2 * faceOffset);\n        g.drawImage(skin,\n                40 * scale, 8 * scale, 8 * scale, 8 * scale,\n                0, 0, size, size);\n    }\n\n    private static final class SkinBindingChangeListener implements ChangeListener<LoadedTexture> {\n        static final WeakHashMap<Canvas, SkinBindingChangeListener> hole = new WeakHashMap<>();\n\n        final WeakReference<Canvas> canvasRef;\n        final ObservableValue<LoadedTexture> binding;\n\n        SkinBindingChangeListener(Canvas canvas, ObservableValue<LoadedTexture> binding) {\n            this.canvasRef = new WeakReference<>(canvas);\n            this.binding = binding;\n        }\n\n        @Override\n        public void changed(ObservableValue<? extends LoadedTexture> observable,\n                            LoadedTexture oldValue, LoadedTexture loadedTexture) {\n            Canvas canvas = canvasRef.get();\n            if (canvas != null)\n                drawAvatar(canvas, loadedTexture.image);\n        }\n    }\n\n    public static void fxAvatarBinding(Canvas canvas, ObservableValue<LoadedTexture> skinBinding) {\n        synchronized (SkinBindingChangeListener.hole) {\n            SkinBindingChangeListener oldListener = SkinBindingChangeListener.hole.remove(canvas);\n            if (oldListener != null)\n                oldListener.binding.removeListener(oldListener);\n\n            SkinBindingChangeListener listener = new SkinBindingChangeListener(canvas, skinBinding);\n            listener.changed(skinBinding, null, skinBinding.getValue());\n            skinBinding.addListener(listener);\n\n            SkinBindingChangeListener.hole.put(canvas, listener);\n        }\n    }\n\n    public static void bindAvatar(Canvas canvas, YggdrasilService service, UUID uuid) {\n        fxAvatarBinding(canvas, skinBinding(service, uuid));\n    }\n\n    public static void bindAvatar(Canvas canvas, Account account) {\n        if (account instanceof YggdrasilAccount || account instanceof MicrosoftAccount || account instanceof OfflineAccount)\n            fxAvatarBinding(canvas, skinBinding(account));\n        else {\n            unbindAvatar(canvas);\n            drawAvatar(canvas, getDefaultSkin(account.getUUID()).image);\n        }\n    }\n\n    public static void unbindAvatar(Canvas canvas) {\n        synchronized (SkinBindingChangeListener.hole) {\n            SkinBindingChangeListener oldListener = SkinBindingChangeListener.hole.remove(canvas);\n            if (oldListener != null)\n                oldListener.binding.removeListener(oldListener);\n        }\n    }\n    // ====\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/java/HMCLJavaRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.java;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.java.mojang.MojangJavaDownloadTask;\nimport org.jackhuang.hmcl.download.java.mojang.MojangJavaRemoteFiles;\nimport org.jackhuang.hmcl.game.DownloadInfo;\nimport org.jackhuang.hmcl.game.GameJavaVersion;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.DirectoryStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class HMCLJavaRepository implements JavaRepository {\n    public static final String MOJANG_JAVA_PREFIX = \"mojang-\";\n\n    private final Path root;\n\n    public HMCLJavaRepository(Path root) {\n        this.root = root;\n    }\n\n    public Path getPlatformRoot(Platform platform) {\n        return root.resolve(platform.toString());\n    }\n\n    @Override\n    public Path getJavaDir(Platform platform, String name) {\n        return getPlatformRoot(platform).resolve(name);\n    }\n\n    public Path getJavaDir(Platform platform, GameJavaVersion gameJavaVersion) {\n        return getJavaDir(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.component());\n    }\n\n    @Override\n    public Path getManifestFile(Platform platform, String name) {\n        return getPlatformRoot(platform).resolve(name + \".json\");\n    }\n\n    public Path getManifestFile(Platform platform, GameJavaVersion gameJavaVersion) {\n        return getManifestFile(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.component());\n    }\n\n    public boolean isInstalled(Platform platform, String name) {\n        return Files.exists(getManifestFile(platform, name));\n    }\n\n    public boolean isInstalled(Platform platform, GameJavaVersion gameJavaVersion) {\n        return isInstalled(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.component());\n    }\n\n    public @Nullable Path getJavaExecutable(Platform platform, String name) {\n        Path javaDir = getJavaDir(platform, name);\n        try {\n            return JavaManager.getExecutable(javaDir).toRealPath();\n        } catch (IOException ignored) {\n            if (platform.getOperatingSystem() == OperatingSystem.MACOS) {\n                try {\n                    return JavaManager.getMacExecutable(javaDir).toRealPath();\n                } catch (IOException ignored1) {\n                }\n            }\n        }\n\n        return null;\n    }\n\n    public @Nullable Path getJavaExecutable(Platform platform, GameJavaVersion gameJavaVersion) {\n        return getJavaExecutable(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.component());\n    }\n\n    private static void getAllJava(List<JavaRuntime> list, Platform platform, Path platformRoot, boolean isManaged) {\n        try (DirectoryStream<Path> stream = Files.newDirectoryStream(platformRoot)) {\n            for (Path file : stream) {\n                try {\n                    String name = file.getFileName().toString();\n                    if (name.endsWith(\".json\") && Files.isRegularFile(file)) {\n                        Path javaDir = file.resolveSibling(name.substring(0, name.length() - \".json\".length()));\n                        Path executable;\n                        try {\n                            executable = JavaManager.getExecutable(javaDir).toRealPath();\n                        } catch (IOException e) {\n                            if (platform.getOperatingSystem() == OperatingSystem.MACOS)\n                                executable = JavaManager.getMacExecutable(javaDir).toRealPath();\n                            else\n                                throw e;\n                        }\n\n                        if (Files.isDirectory(javaDir)) {\n                            JavaManifest manifest = JsonUtils.fromJsonFile(file, JavaManifest.class);\n                            list.add(JavaRuntime.of(executable, manifest.getInfo(), isManaged));\n                        }\n                    }\n                } catch (Throwable e) {\n                    LOG.warning(\"Failed to parse \" + file, e);\n                }\n            }\n\n        } catch (IOException ignored) {\n        }\n    }\n\n    @Override\n    public Collection<JavaRuntime> getAllJava(Platform platform) {\n        Path platformRoot = getPlatformRoot(platform);\n        if (!Files.isDirectory(platformRoot))\n            return Collections.emptyList();\n\n        ArrayList<JavaRuntime> list = new ArrayList<>();\n\n        getAllJava(list, platform, platformRoot, true);\n        if (platform.getOperatingSystem() == OperatingSystem.MACOS) {\n            platformRoot = root.resolve(platform.getOperatingSystem().getMojangName() + \"-\" + platform.getArchitecture().getCheckedName());\n            if (Files.isDirectory(platformRoot))\n                getAllJava(list, platform, platformRoot, false);\n        }\n\n        return list;\n    }\n\n    @Override\n    public Task<JavaRuntime> getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion) {\n        Path javaDir = getJavaDir(platform, gameJavaVersion);\n\n        return new MojangJavaDownloadTask(downloadProvider, javaDir, gameJavaVersion, JavaManager.getMojangJavaPlatform(platform)).thenApplyAsync(result -> {\n            Path executable;\n            try {\n                executable = JavaManager.getExecutable(javaDir).toRealPath();\n            } catch (IOException e) {\n                if (platform.getOperatingSystem() == OperatingSystem.MACOS)\n                    executable = JavaManager.getMacExecutable(javaDir).toRealPath();\n                else\n                    throw e;\n            }\n\n            JavaInfo info;\n            if (JavaManager.isCompatible(platform))\n                info = JavaInfoUtils.fromExecutable(executable, false);\n            else\n                info = new JavaInfo(platform, result.download.getVersion().getName(), null);\n\n            Map<String, Object> update = new LinkedHashMap<>();\n            update.put(\"provider\", \"mojang\");\n            update.put(\"component\", gameJavaVersion.component());\n\n            Map<String, JavaLocalFiles.Local> files = new LinkedHashMap<>();\n            result.remoteFiles.getFiles().forEach((path, file) -> {\n                if (file instanceof MojangJavaRemoteFiles.RemoteFile) {\n                    DownloadInfo downloadInfo = ((MojangJavaRemoteFiles.RemoteFile) file).getDownloads().get(\"raw\");\n                    if (downloadInfo != null) {\n                        files.put(path, new JavaLocalFiles.LocalFile(downloadInfo.getSha1(), downloadInfo.getSize()));\n                    }\n                } else if (file instanceof MojangJavaRemoteFiles.RemoteDirectory) {\n                    files.put(path, new JavaLocalFiles.LocalDirectory());\n                } else if (file instanceof MojangJavaRemoteFiles.RemoteLink) {\n                    files.put(path, new JavaLocalFiles.LocalLink(((MojangJavaRemoteFiles.RemoteLink) file).getTarget()));\n                }\n            });\n\n            JavaManifest manifest = new JavaManifest(info, update, files);\n            JsonUtils.writeToJsonFile(getManifestFile(platform, gameJavaVersion), manifest);\n            return JavaRuntime.of(executable, info, true);\n        });\n    }\n\n    public Task<JavaRuntime> getInstallJavaTask(Platform platform, String name, Map<String, Object> update, Path archiveFile) {\n        Path javaDir = getJavaDir(platform, name);\n        return new JavaInstallTask(javaDir, update, archiveFile).thenApplyAsync(result -> {\n            if (!result.getInfo().getPlatform().equals(platform))\n                throw new IOException(\"Platform is mismatch: expected \" + platform + \" but got \" + result.getInfo().getPlatform());\n\n            Path executable = javaDir.resolve(\"bin\").resolve(platform.getOperatingSystem().getJavaExecutable()).toRealPath();\n            JsonUtils.writeToJsonFile(getManifestFile(platform, name), result);\n            return JavaRuntime.of(executable, result.getInfo(), true);\n        });\n    }\n\n    @Override\n    public Task<Void> getUninstallJavaTask(Platform platform, String name) {\n        return Task.runAsync(() -> {\n            Files.deleteIfExists(getManifestFile(platform, name));\n            FileUtils.deleteDirectory(getJavaDir(platform, name));\n        });\n    }\n\n    @Override\n    public Task<Void> getUninstallJavaTask(JavaRuntime java) {\n        return Task.runAsync(() -> {\n            Path root = getPlatformRoot(java.getPlatform());\n            Path relativized = root.relativize(java.getBinary());\n\n            if (relativized.getNameCount() > 1) {\n                String name = relativized.getName(0).toString();\n                Files.deleteIfExists(getManifestFile(java.getPlatform(), name));\n                FileUtils.deleteDirectory(getJavaDir(java.getPlatform(), name));\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/java/JavaInfoUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.java;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\n/**\n * @author Glavo\n * @see <a href=\"https://github.com/Glavo/java-info\">Glavo/java-info</a>\n */\npublic final class JavaInfoUtils {\n\n    private JavaInfoUtils() {\n    }\n\n    private static Path tryFindReleaseFile(Path executable) {\n        Path parent = executable.getParent();\n        if (parent != null && parent.getFileName() != null && parent.getFileName().toString().equals(\"bin\")) {\n            Path javaHome = parent.getParent();\n            if (javaHome != null && javaHome.getFileName() != null) {\n                Path releaseFile = javaHome.resolve(\"release\");\n                String javaHomeName = javaHome.getFileName().toString();\n                if ((javaHomeName.contains(\"jre\") || javaHomeName.contains(\"jdk\") || javaHomeName.contains(\"openj9\"))\n                        && Files.isRegularFile(releaseFile)) {\n                    return releaseFile;\n                }\n            }\n        }\n        return null;\n    }\n\n    public static @NotNull JavaInfo fromExecutable(Path executable, boolean tryFindReleaseFile) throws IOException {\n        assert executable.isAbsolute();\n\n        Path releaseFile;\n        if (tryFindReleaseFile && (releaseFile = tryFindReleaseFile(executable)) != null) {\n            try {\n                return JavaInfo.fromReleaseFile(releaseFile);\n            } catch (IOException ignored) {\n            }\n        }\n\n        Path thisPath = JarUtils.thisJarPath();\n\n        if (thisPath == null) {\n            throw new IOException(\"Failed to find current HMCL location\");\n        }\n\n        try {\n            Result result = JsonUtils.GSON.fromJson(SystemUtils.run(\n                    executable.toString(),\n                    \"-classpath\",\n                    thisPath.toString(),\n                    org.glavo.info.Main.class.getName()\n            ), Result.class);\n\n            if (result == null) {\n                throw new IOException(\"Failed to get Java info from \" + executable);\n            }\n\n            if (result.javaVersion == null) {\n                throw new IOException(\"Failed to get Java version from \" + executable);\n            }\n\n            Architecture architecture = Architecture.parseArchName(result.osArch);\n            Platform platform = Platform.getPlatform(OperatingSystem.CURRENT_OS,\n                    architecture != Architecture.UNKNOWN\n                            ? architecture\n                            : Architecture.SYSTEM_ARCH);\n\n\n            return new JavaInfo(platform, result.javaVersion, result.javaVendor);\n        } catch (IOException e) {\n            throw e;\n        } catch (Throwable e) {\n            throw new IOException(e);\n        }\n    }\n\n    private static final class Result {\n        @SerializedName(\"os.name\")\n        public String osName;\n        @SerializedName(\"os.arch\")\n        public String osArch;\n        @SerializedName(\"java.version\")\n        public String javaVersion;\n        @SerializedName(\"java.vendor\")\n        public String javaVendor;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/java/JavaInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.java;\n\nimport kala.compress.archivers.ArchiveEntry;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jackhuang.hmcl.util.tree.ArchiveFileTree;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.StandardOpenOption;\nimport java.security.MessageDigest;\nimport java.util.ArrayList;\nimport java.util.HexFormat;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * @author Glavo\n */\npublic final class JavaInstallTask extends Task<JavaManifest> {\n\n    private final Path targetDir;\n    private final Map<String, Object> update;\n    private final Path archiveFile;\n\n    private final Map<String, JavaLocalFiles.Local> files = new LinkedHashMap<>();\n    private final ArrayList<String> nameStack = new ArrayList<>();\n    private final byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];\n    private final MessageDigest messageDigest = DigestUtils.getDigest(\"SHA-1\");\n\n    public JavaInstallTask(Path targetDir, Map<String, Object> update, Path archiveFile) {\n        this.targetDir = targetDir;\n        this.update = update;\n        this.archiveFile = archiveFile;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        JavaInfo info;\n\n        try (ArchiveFileTree<?, ?> tree = ArchiveFileTree.open(archiveFile)) {\n            info = JavaInfo.fromArchive(tree);\n            copyDirContent(tree, targetDir);\n        }\n\n        setResult(new JavaManifest(info, update, files));\n    }\n\n    private <F, E extends ArchiveEntry> void copyDirContent(ArchiveFileTree<F, E> tree, Path targetDir) throws IOException {\n        copyDirContent(tree, tree.getRoot().getSubDirs().values().iterator().next(), targetDir);\n    }\n\n    private <F, E extends ArchiveEntry> void copyDirContent(ArchiveFileTree<F, E> tree, ArchiveFileTree.Dir<E> dir, Path targetDir) throws IOException {\n        Files.createDirectories(targetDir);\n\n        for (Map.Entry<String, E> pair : dir.getFiles().entrySet()) {\n            Path path = targetDir.resolve(pair.getKey());\n            E entry = pair.getValue();\n\n            nameStack.add(pair.getKey());\n            if (tree.isLink(entry)) {\n                String linkTarget = tree.getLink(entry);\n                files.put(String.join(\"/\", nameStack), new JavaLocalFiles.LocalLink(linkTarget));\n                Files.createSymbolicLink(path, Paths.get(linkTarget));\n            } else {\n                long size = 0L;\n\n                try (InputStream input = tree.getInputStream(entry);\n                     OutputStream output = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {\n                    messageDigest.reset();\n\n                    int c;\n                    while ((c = input.read(buffer)) > 0) {\n                        size += c;\n                        output.write(buffer, 0, c);\n                        messageDigest.update(buffer, 0, c);\n                    }\n                }\n\n                if (tree.isExecutable(entry))\n                    FileUtils.setExecutable(path);\n\n                files.put(String.join(\"/\", nameStack), new JavaLocalFiles.LocalFile(HexFormat.of().formatHex(messageDigest.digest()), size));\n            }\n            nameStack.remove(nameStack.size() - 1);\n        }\n\n        for (Map.Entry<String, ArchiveFileTree.Dir<E>> pair : dir.getSubDirs().entrySet()) {\n            nameStack.add(pair.getKey());\n            files.put(String.join(\"/\", nameStack), new JavaLocalFiles.LocalDirectory());\n            copyDirContent(tree, pair.getValue(), targetDir.resolve(pair.getKey()));\n            nameStack.remove(nameStack.size() - 1);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/java/JavaLocalFiles.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.java;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\n\nimport java.lang.reflect.Type;\n\n/**\n * @author Glavo\n */\npublic final class JavaLocalFiles {\n    @JsonAdapter(Serializer.class)\n    public abstract static class Local {\n        private final String type;\n\n        Local(String type) {\n            this.type = type;\n        }\n\n        public String getType() {\n            return type;\n        }\n    }\n\n    public static final class LocalFile extends Local {\n        private final String sha1;\n        private final long size;\n\n        public LocalFile(String sha1, long size) {\n            super(\"file\");\n            this.sha1 = sha1;\n            this.size = size;\n        }\n\n        public String getSha1() {\n            return sha1;\n        }\n\n        public long getSize() {\n            return size;\n        }\n    }\n\n    public static final class LocalDirectory extends Local {\n        public LocalDirectory() {\n            super(\"directory\");\n        }\n    }\n\n    public static final class LocalLink extends Local {\n        private final String target;\n\n        public LocalLink(String target) {\n            super(\"link\");\n            this.target = target;\n        }\n\n        public String getTarget() {\n            return target;\n        }\n    }\n\n    public static class Serializer implements JsonSerializer<Local>, JsonDeserializer<Local> {\n\n        @Override\n        public JsonElement serialize(Local src, Type typeOfSrc, JsonSerializationContext context) {\n            JsonObject obj = new JsonObject();\n            obj.addProperty(\"type\", src.getType());\n            if (src instanceof LocalFile) {\n                obj.addProperty(\"sha1\", ((LocalFile) src).getSha1());\n                obj.addProperty(\"size\", ((LocalFile) src).getSize());\n            } else if (src instanceof LocalLink) {\n                obj.addProperty(\"target\", ((LocalLink) src).getTarget());\n            }\n            return obj;\n        }\n\n        @Override\n        public Local deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (!json.isJsonObject())\n                throw new JsonParseException(json.toString());\n\n            JsonObject obj = json.getAsJsonObject();\n            if (!obj.has(\"type\"))\n                throw new JsonParseException(json.toString());\n\n            String type = obj.getAsJsonPrimitive(\"type\").getAsString();\n\n            try {\n                switch (type) {\n                    case \"file\": {\n                        String sha1 = obj.getAsJsonPrimitive(\"sha1\").getAsString();\n                        long size = obj.getAsJsonPrimitive(\"size\").getAsLong();\n                        return new LocalFile(sha1, size);\n                    }\n                    case \"directory\": {\n                        return new LocalDirectory();\n                    }\n                    case \"link\": {\n                        String target = obj.getAsJsonPrimitive(\"target\").getAsString();\n                        return new LocalLink(target);\n                    }\n                    default:\n                        throw new AssertionError(\"unknown type: \" + type);\n                }\n            } catch (Throwable e) {\n                throw new JsonParseException(json.toString());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.java;\n\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.GameJavaVersion;\nimport org.jackhuang.hmcl.game.JavaVersionConstraint;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.setting.ConfigHolder;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.CacheRepository;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.*;\nimport org.jackhuang.hmcl.util.platform.windows.WinReg;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.*;\nimport java.util.*;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class JavaManager {\n\n    private JavaManager() {\n    }\n\n    public static final HMCLJavaRepository REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_GLOBAL_DIRECTORY.resolve(\"java\"));\n    public static final HMCLJavaRepository LOCAL_REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_CURRENT_DIRECTORY.resolve(\"java\"));\n\n    public static String getMojangJavaPlatform(Platform platform) {\n        if (platform.getOperatingSystem() == OperatingSystem.WINDOWS) {\n            if (Architecture.SYSTEM_ARCH == Architecture.X86) {\n                return \"windows-x86\";\n            } else if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {\n                return \"windows-x64\";\n            } else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {\n                return \"windows-arm64\";\n            }\n        } else if (platform.getOperatingSystem() == OperatingSystem.LINUX) {\n            if (Architecture.SYSTEM_ARCH == Architecture.X86) {\n                return \"linux-i386\";\n            } else if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {\n                return \"linux\";\n            }\n        } else if (platform.getOperatingSystem() == OperatingSystem.MACOS) {\n            if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {\n                return \"mac-os\";\n            } else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {\n                return \"mac-os-arm64\";\n            }\n        }\n\n        return null;\n    }\n\n    public static Path getExecutable(Path javaHome) {\n        return javaHome.resolve(\"bin\").resolve(OperatingSystem.CURRENT_OS.getJavaExecutable());\n    }\n\n    public static Path getMacExecutable(Path javaHome) {\n        return javaHome.resolve(\"jre.bundle/Contents/Home/bin/java\");\n    }\n\n    public static boolean isCompatible(Platform platform) {\n        if (platform.getOperatingSystem() != OperatingSystem.CURRENT_OS)\n            return false;\n\n        Architecture architecture = platform.getArchitecture();\n        if (architecture == Architecture.SYSTEM_ARCH || architecture == Architecture.CURRENT_ARCH)\n            return true;\n\n        switch (OperatingSystem.CURRENT_OS) {\n            case WINDOWS:\n                if (Architecture.SYSTEM_ARCH == Architecture.X86_64)\n                    return architecture == Architecture.X86;\n                if (Architecture.SYSTEM_ARCH == Architecture.ARM64)\n                    return OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277 && architecture == Architecture.X86_64 || architecture == Architecture.X86;\n                break;\n            case LINUX:\n                if (Architecture.SYSTEM_ARCH == Architecture.X86_64)\n                    return architecture == Architecture.X86;\n                break;\n            case MACOS:\n                if (Architecture.SYSTEM_ARCH == Architecture.ARM64)\n                    return architecture == Architecture.X86_64;\n                break;\n        }\n\n        return false;\n    }\n\n    private static volatile Map<Path, JavaRuntime> allJava;\n    private static final CountDownLatch LATCH = new CountDownLatch(1);\n\n    private static final ObjectProperty<Collection<JavaRuntime>> allJavaProperty = new SimpleObjectProperty<>();\n\n    private static Map<Path, JavaRuntime> getAllJavaMap() throws InterruptedException {\n        Map<Path, JavaRuntime> map = allJava;\n        if (map == null) {\n            LATCH.await();\n            map = allJava;\n        }\n        return map;\n    }\n\n    private static void updateAllJavaProperty(Map<Path, JavaRuntime> javaRuntimes) {\n        JavaRuntime[] array = javaRuntimes.values().toArray(new JavaRuntime[0]);\n        Arrays.sort(array);\n        allJavaProperty.set(Arrays.asList(array));\n    }\n\n    public static boolean isInitialized() {\n        return allJava != null;\n    }\n\n    public static Collection<JavaRuntime> getAllJava() throws InterruptedException {\n        return getAllJavaMap().values();\n    }\n\n    public static ObjectProperty<Collection<JavaRuntime>> getAllJavaProperty() {\n        return allJavaProperty;\n    }\n\n    public static JavaRuntime getJava(Path executable) throws IOException, InterruptedException {\n        executable = executable.toRealPath();\n\n        JavaRuntime javaRuntime = getAllJavaMap().get(executable);\n        if (javaRuntime != null) {\n            return javaRuntime;\n        }\n\n        JavaInfo info = JavaInfoUtils.fromExecutable(executable, true);\n        return JavaRuntime.of(executable, info, false);\n    }\n\n    public static void refresh() {\n        Task.supplyAsync(JavaManager::searchPotentialJavaExecutables).whenComplete(Schedulers.javafx(), (result, exception) -> {\n            if (result != null) {\n                LATCH.await();\n                allJava = result;\n                updateAllJavaProperty(result);\n            }\n        }).start();\n    }\n\n    public static Task<JavaRuntime> getAddJavaTask(Path binary) {\n        return Task.supplyAsync(\"Get Java\", () -> JavaManager.getJava(binary))\n                .thenApplyAsync(Schedulers.javafx(), javaRuntime -> {\n                    if (!JavaManager.isCompatible(javaRuntime.getPlatform())) {\n                        throw new UnsupportedPlatformException(\"Incompatible platform: \" + javaRuntime.getPlatform());\n                    }\n\n                    String pathString = javaRuntime.getBinary().toString();\n\n                    ConfigHolder.globalConfig().getDisabledJava().remove(pathString);\n                    if (ConfigHolder.globalConfig().getUserJava().add(pathString)) {\n                        addJava(javaRuntime);\n                    }\n                    return javaRuntime;\n                });\n    }\n\n    public static Task<JavaRuntime> getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion) {\n        return REPOSITORY.getDownloadJavaTask(downloadProvider, platform, gameJavaVersion)\n                .thenApplyAsync(Schedulers.javafx(), java -> {\n                    addJava(java);\n                    return java;\n                });\n    }\n\n    public static Task<JavaRuntime> getInstallJavaTask(Platform platform, String name, Map<String, Object> update, Path archiveFile) {\n        return REPOSITORY.getInstallJavaTask(platform, name, update, archiveFile)\n                .thenApplyAsync(Schedulers.javafx(), java -> {\n                    addJava(java);\n                    return java;\n                });\n    }\n\n    public static Task<Void> getUninstallJavaTask(JavaRuntime java) {\n        assert java.isManaged();\n\n        Path platformRoot;\n        try {\n            platformRoot = REPOSITORY.getPlatformRoot(java.getPlatform()).toRealPath();\n        } catch (Throwable ignored) {\n            return Task.completed(null);\n        }\n\n        if (!java.getBinary().startsWith(platformRoot))\n            return Task.completed(null);\n\n        Path relativized = platformRoot.relativize(java.getBinary());\n        if (relativized.getNameCount() > 1) {\n            FXUtils.runInFX(() -> {\n                try {\n                    removeJava(java);\n                } catch (InterruptedException e) {\n                    throw new AssertionError(\"Unreachable code\", e);\n                }\n            });\n\n            String name = relativized.getName(0).toString();\n            return REPOSITORY.getUninstallJavaTask(java.getPlatform(), name);\n        } else {\n            return Task.completed(null);\n        }\n    }\n\n    // FXThread\n    public static void addJava(JavaRuntime java) throws InterruptedException {\n        Map<Path, JavaRuntime> oldMap = getAllJavaMap();\n        if (!oldMap.containsKey(java.getBinary())) {\n            HashMap<Path, JavaRuntime> newMap = new HashMap<>(oldMap);\n            newMap.put(java.getBinary(), java);\n            allJava = newMap;\n            updateAllJavaProperty(newMap);\n        }\n    }\n\n    // FXThread\n    public static void removeJava(JavaRuntime java) throws InterruptedException {\n        removeJava(java.getBinary());\n    }\n\n    // FXThread\n    public static void removeJava(Path realPath) throws InterruptedException {\n        Map<Path, JavaRuntime> oldMap = getAllJavaMap();\n        if (oldMap.containsKey(realPath)) {\n            HashMap<Path, JavaRuntime> newMap = new HashMap<>(oldMap);\n            newMap.remove(realPath);\n            allJava = newMap;\n            updateAllJavaProperty(newMap);\n        }\n    }\n\n    private static JavaRuntime chooseJava(@Nullable JavaRuntime java1, JavaRuntime java2) {\n        if (java1 == null)\n            return java2;\n\n        if (java1.getParsedVersion() != java2.getParsedVersion())\n            // Prefer the Java version that is closer to the game's recommended Java version\n            return java1.getParsedVersion() < java2.getParsedVersion() ? java1 : java2;\n        else\n            return java1.getVersionNumber().compareTo(java2.getVersionNumber()) >= 0 ? java1 : java2;\n    }\n\n    @Nullable\n    public static JavaRuntime findSuitableJava(GameVersionNumber gameVersion, Version version) throws InterruptedException {\n        return findSuitableJava(getAllJava(), gameVersion, version);\n    }\n\n    @Nullable\n    public static JavaRuntime findSuitableJava(Collection<JavaRuntime> javaRuntimes, GameVersionNumber gameVersion, Version version) {\n        LibraryAnalyzer analyzer = version != null ? LibraryAnalyzer.analyze(version, gameVersion != null ? gameVersion.toString() : null) : null;\n\n        boolean forceX86 = Architecture.SYSTEM_ARCH == Architecture.ARM64\n                && (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)\n                && (gameVersion == null || gameVersion.compareTo(\"1.6\") < 0);\n\n        JavaRuntime mandatory = null;\n        JavaRuntime suggested = null;\n        for (JavaRuntime java : javaRuntimes) {\n            if (forceX86) {\n                if (!java.getArchitecture().isX86())\n                    continue;\n            } else {\n                if (java.getArchitecture() != Architecture.SYSTEM_ARCH)\n                    continue;\n            }\n\n            boolean violationMandatory = false;\n            boolean violationSuggested = false;\n\n            for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) {\n                if (constraint.appliesToVersion(gameVersion, version, java, analyzer)) {\n                    if (!constraint.checkJava(gameVersion, version, java, analyzer)) {\n                        if (constraint.isMandatory()) {\n                            violationMandatory = true;\n                        } else {\n                            violationSuggested = true;\n                        }\n                    }\n                }\n            }\n\n            if (!violationMandatory) {\n                mandatory = chooseJava(mandatory, java);\n\n                if (!violationSuggested)\n                    suggested = chooseJava(suggested, java);\n            }\n        }\n\n        return suggested != null ? suggested : mandatory;\n    }\n\n    public static void initialize() {\n        Map<Path, JavaRuntime> allJava = searchPotentialJavaExecutables();\n        JavaManager.allJava = allJava;\n        LATCH.countDown();\n        FXUtils.runInFX(() -> updateAllJavaProperty(allJava));\n    }\n\n    // search java\n\n    private static Map<Path, JavaRuntime> searchPotentialJavaExecutables() {\n        Map<Path, JavaRuntime> javaRuntimes = new HashMap<>();\n        searchAllJavaInRepository(javaRuntimes, Platform.SYSTEM_PLATFORM);\n        switch (OperatingSystem.CURRENT_OS) {\n            case WINDOWS:\n                if (Architecture.SYSTEM_ARCH == Architecture.X86_64)\n                    searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86);\n                if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {\n                    if (OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277)\n                        searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86_64);\n                    searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86);\n                }\n                break;\n            case MACOS:\n                if (Architecture.SYSTEM_ARCH == Architecture.ARM64)\n                    searchAllJavaInRepository(javaRuntimes, Platform.MACOS_X86_64);\n                break;\n        }\n\n        switch (OperatingSystem.CURRENT_OS) {\n            case WINDOWS:\n                queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, \"SOFTWARE\\\\JavaSoft\\\\Java Runtime Environment\");\n                queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, \"SOFTWARE\\\\JavaSoft\\\\Java Development Kit\");\n                queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, \"SOFTWARE\\\\JavaSoft\\\\JRE\");\n                queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, \"SOFTWARE\\\\JavaSoft\\\\JDK\");\n\n                searchJavaInProgramFiles(javaRuntimes, \"ProgramFiles\", \"C:\\\\Program Files\");\n                searchJavaInProgramFiles(javaRuntimes, \"ProgramFiles(x86)\", \"C:\\\\Program Files (x86)\");\n                if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {\n                    searchJavaInProgramFiles(javaRuntimes, \"ProgramFiles(ARM)\", \"C:\\\\Program Files (ARM)\");\n                }\n                break;\n            case LINUX:\n                searchAllJavaInDirectory(javaRuntimes, Paths.get(\"/usr/java\"));      // Oracle RPMs\n                searchAllJavaInDirectory(javaRuntimes, Paths.get(\"/usr/lib/jvm\"));   // General locations\n                searchAllJavaInDirectory(javaRuntimes, Paths.get(\"/usr/lib32/jvm\")); // General locations\n                searchAllJavaInDirectory(javaRuntimes, Paths.get(\"/usr/lib64/jvm\")); // General locations\n                searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty(\"user.home\"), \"/.sdkman/candidates/java\")); // SDKMAN!\n                break;\n            case MACOS:\n                searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get(\"/Library/Java/JavaVirtualMachines\"));\n                searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get(System.getProperty(\"user.home\"), \"/Library/Java/JavaVirtualMachines\"));\n                tryAddJavaExecutable(javaRuntimes, Paths.get(\"/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java\"));\n                tryAddJavaExecutable(javaRuntimes, Paths.get(\"/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java\"));\n                // Homebrew\n                tryAddJavaExecutable(javaRuntimes, Paths.get(\"/opt/homebrew/opt/java/bin/java\"));\n                searchAllJavaInDirectory(javaRuntimes, Paths.get(\"/opt/homebrew/Cellar/openjdk\"));\n                try (DirectoryStream<Path> dirs = Files.newDirectoryStream(Paths.get(\"/opt/homebrew/Cellar\"), \"openjdk@*\")) {\n                    for (Path dir : dirs) {\n                        searchAllJavaInDirectory(javaRuntimes, dir);\n                    }\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to get subdirectories of /opt/homebrew/Cellar\");\n                }\n                break;\n\n            default:\n                break;\n        }\n\n        // Search Minecraft bundled runtimes\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && Architecture.SYSTEM_ARCH.isX86()) {\n            FileUtils.tryGetPath(System.getenv(\"localappdata\"), \"Packages\\\\Microsoft.4297127D64EC6_8wekyb3d8bbwe\\\\LocalCache\\\\Local\\\\runtime\")\n                    .ifPresent(it -> searchAllOfficialJava(javaRuntimes, it, false));\n\n            FileUtils.tryGetPath(Lang.requireNonNullElse(System.getenv(\"ProgramFiles(x86)\"), \"C:\\\\Program Files (x86)\"), \"Minecraft Launcher\\\\runtime\")\n                    .ifPresent(it -> searchAllOfficialJava(javaRuntimes, it, false));\n        } else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.X86_64) {\n            searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty(\"user.home\"), \".minecraft/runtime\"), false);\n        } else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n            searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty(\"user.home\"), \"Library/Application Support/minecraft/runtime\"), false);\n        }\n        searchAllOfficialJava(javaRuntimes, CacheRepository.getInstance().getCacheDirectory().resolve(\"java\"), true);\n\n        // Search in PATH.\n        if (System.getenv(\"PATH\") != null) {\n            String[] paths = System.getenv(\"PATH\").split(File.pathSeparator);\n            for (String path : paths) {\n                // https://github.com/HMCL-dev/HMCL/issues/4079\n                // https://github.com/Meloong-Git/PCL/issues/4261\n                if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && path.toLowerCase(Locale.ROOT)\n                        .contains(\"\\\\common files\\\\oracle\\\\java\\\\\")) {\n                    continue;\n                }\n\n                try {\n                    tryAddJavaExecutable(javaRuntimes, Path.of(path, OperatingSystem.CURRENT_OS.getJavaExecutable()));\n                } catch (InvalidPathException ignored) {\n                }\n            }\n        }\n\n        if (System.getenv(\"HMCL_JRES\") != null) {\n            String[] paths = System.getenv(\"HMCL_JRES\").split(File.pathSeparator);\n            for (String path : paths) {\n                try {\n                    tryAddJavaHome(javaRuntimes, Paths.get(path));\n                } catch (InvalidPathException ignored) {\n                }\n            }\n        }\n        searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty(\"user.home\"), \".jdks\"));\n\n        for (String javaPath : ConfigHolder.globalConfig().getUserJava()) {\n            try {\n                tryAddJavaExecutable(javaRuntimes, Paths.get(javaPath));\n            } catch (InvalidPathException e) {\n                LOG.warning(\"Invalid Java path: \" + javaPath);\n            }\n        }\n\n        JavaRuntime currentJava = JavaRuntime.CURRENT_JAVA;\n        if (currentJava != null\n                && !javaRuntimes.containsKey(currentJava.getBinary())\n                && !ConfigHolder.globalConfig().getDisabledJava().contains(currentJava.getBinary().toString())) {\n            javaRuntimes.put(currentJava.getBinary(), currentJava);\n        }\n\n        LOG.trace(javaRuntimes.values().stream().sorted()\n                .map(it -> String.format(\" - %s %s (%s, %s): %s\",\n                        it.isJDK() ? \"JDK\" : \"JRE\",\n                        it.getVersion(),\n                        it.getPlatform().getArchitecture().getDisplayName(),\n                        Lang.requireNonNullElse(it.getVendor(), \"Unknown\"),\n                        it.getBinary()))\n                .collect(Collectors.joining(\"\\n\", \"Finished Java lookup, found \" + javaRuntimes.size() + \"\\n\", \"\")));\n\n        return javaRuntimes;\n    }\n\n    private static void tryAddJavaHome(Map<Path, JavaRuntime> javaRuntimes, Path javaHome) {\n        Path executable = getExecutable(javaHome);\n        if (!Files.isRegularFile(executable)) {\n            return;\n        }\n\n        try {\n            executable = executable.toRealPath();\n        } catch (IOException e) {\n            LOG.warning(\"Failed to resolve path \" + executable, e);\n            return;\n        }\n\n        if (javaRuntimes.containsKey(executable) || ConfigHolder.globalConfig().getDisabledJava().contains(executable.toString())) {\n            return;\n        }\n\n        JavaInfo info = null;\n\n        Path releaseFile = javaHome.resolve(\"release\");\n        if (Files.exists(releaseFile)) {\n            try {\n                info = JavaInfo.fromReleaseFile(releaseFile);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to read release file \" + releaseFile, e);\n            }\n        }\n\n        if (info == null) {\n            try {\n                info = JavaInfoUtils.fromExecutable(executable, false);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to lookup Java executable at \" + executable, e);\n            }\n        }\n\n        if (info != null && isCompatible(info.getPlatform()))\n            javaRuntimes.put(executable, JavaRuntime.of(executable, info, false));\n    }\n\n    private static void tryAddJavaExecutable(Map<Path, JavaRuntime> javaRuntimes, Path executable) {\n        try {\n            executable = executable.toRealPath();\n        } catch (IOException e) {\n            return;\n        }\n\n        if (javaRuntimes.containsKey(executable) || ConfigHolder.globalConfig().getDisabledJava().contains(executable.toString())) {\n            return;\n        }\n\n        JavaInfo info = null;\n        try {\n            info = JavaInfoUtils.fromExecutable(executable, true);\n        } catch (IOException e) {\n            LOG.warning(\"Failed to lookup Java executable at \" + executable, e);\n        }\n\n        if (info != null && isCompatible(info.getPlatform())) {\n            javaRuntimes.put(executable, JavaRuntime.of(executable, info, false));\n        }\n    }\n\n    private static void tryAddJavaInComponentDir(Map<Path, JavaRuntime> javaRuntimes, String platform, Path component, boolean verify) {\n        Path sha1File = component.resolve(platform).resolve(component.getFileName() + \".sha1\");\n        if (!Files.isRegularFile(sha1File))\n            return;\n\n        Path dir = component.resolve(platform).resolve(component.getFileName());\n\n        if (verify) {\n            try (BufferedReader reader = Files.newBufferedReader(sha1File)) {\n                String line;\n                while ((line = reader.readLine()) != null) {\n                    if (line.isEmpty()) continue;\n\n                    int idx = line.indexOf(\" /#//\");\n                    if (idx <= 0)\n                        throw new IOException(\"Illegal line: \" + line);\n\n                    Path file = dir.resolve(line.substring(0, idx));\n\n                    // Should we check the sha1 of files? This will take a lot of time.\n                    if (Files.notExists(file))\n                        throw new NoSuchFileException(file.toAbsolutePath().toString());\n                }\n            } catch (IOException e) {\n                LOG.warning(\"Failed to verify Java in \" + component, e);\n                return;\n            }\n        }\n\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n            Path macPath = dir.resolve(\"jre.bundle/Contents/Home\");\n            if (Files.exists(macPath)) {\n                tryAddJavaHome(javaRuntimes, macPath);\n                return;\n            } else\n                LOG.warning(\"The Java is not in 'jre.bundle/Contents/Home'\");\n        }\n\n        tryAddJavaHome(javaRuntimes, dir);\n    }\n\n    private static void searchAllJavaInRepository(Map<Path, JavaRuntime> javaRuntimes, Platform platform) {\n        for (JavaRuntime java : REPOSITORY.getAllJava(platform)) {\n            javaRuntimes.put(java.getBinary(), java);\n        }\n\n        for (JavaRuntime java : LOCAL_REPOSITORY.getAllJava(platform)) {\n            javaRuntimes.put(java.getBinary(), java);\n        }\n    }\n\n    private static void searchAllOfficialJava(Map<Path, JavaRuntime> javaRuntimes, Path directory, boolean verify) {\n        if (!Files.isDirectory(directory))\n            return;\n        // Examples:\n        // $HOME/Library/Application Support/minecraft/runtime/java-runtime-beta/mac-os/java-runtime-beta/jre.bundle/Contents/Home\n        // $HOME/.minecraft/runtime/java-runtime-beta/linux/java-runtime-beta\n\n        String javaPlatform = getMojangJavaPlatform(Platform.SYSTEM_PLATFORM);\n        if (javaPlatform != null) {\n            searchAllOfficialJava(javaRuntimes, directory, javaPlatform, verify);\n        }\n\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n            if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {\n                searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.WINDOWS_X86), verify);\n            } else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {\n                if (OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277) {\n                    searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.WINDOWS_X86_64), verify);\n                }\n                searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.WINDOWS_X86), verify);\n            }\n        } else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && Architecture.CURRENT_ARCH == Architecture.ARM64) {\n            searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.MACOS_X86_64), verify);\n        }\n    }\n\n    private static void searchAllOfficialJava(Map<Path, JavaRuntime> javaRuntimes, Path directory, String platform, boolean verify) {\n        try (DirectoryStream<Path> dir = Files.newDirectoryStream(directory)) {\n            // component can be jre-legacy, java-runtime-alpha, java-runtime-beta, java-runtime-gamma or any other being added in the future.\n            for (Path component : dir) {\n                tryAddJavaInComponentDir(javaRuntimes, platform, component, verify);\n            }\n        } catch (IOException e) {\n            LOG.warning(\"Failed to list java-runtime directory \" + directory, e);\n        }\n    }\n\n    private static void searchAllJavaInDirectory(Map<Path, JavaRuntime> javaRuntimes, Path directory) {\n        if (!Files.isDirectory(directory)) {\n            return;\n        }\n\n        try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {\n            for (Path subDir : stream) {\n                tryAddJavaHome(javaRuntimes, subDir);\n            }\n        } catch (IOException e) {\n            LOG.warning(\"Failed to find Java in \" + directory, e);\n        }\n    }\n\n    private static void searchJavaInProgramFiles(Map<Path, JavaRuntime> javaRuntimes, String env, String defaultValue) {\n        String programFiles = Lang.requireNonNullElse(System.getenv(env), defaultValue);\n        Path path;\n        try {\n            path = Paths.get(programFiles);\n        } catch (InvalidPathException ignored) {\n            return;\n        }\n\n        for (String vendor : new String[]{\"Java\", \"BellSoft\", \"AdoptOpenJDK\", \"Zulu\", \"Microsoft\", \"Eclipse Foundation\", \"Semeru\"}) {\n            searchAllJavaInDirectory(javaRuntimes, path.resolve(vendor));\n        }\n    }\n\n    private static void searchJavaInMacJavaVirtualMachines(Map<Path, JavaRuntime> javaRuntimes, Path directory) {\n        if (!Files.isDirectory(directory)) {\n            return;\n        }\n\n        try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {\n            for (Path subDir : stream) {\n                tryAddJavaHome(javaRuntimes, subDir.resolve(\"Contents/Home\"));\n            }\n        } catch (IOException e) {\n            LOG.warning(\"Failed to find Java in \" + directory, e);\n        }\n    }\n\n    // ==== Windows Registry Support ====\n    private static void queryJavaInRegistryKey(Map<Path, JavaRuntime> javaRuntimes, WinReg.HKEY hkey, String location) {\n        WinReg reg = WinReg.INSTANCE;\n        if (reg == null)\n            return;\n\n        for (String java : reg.querySubKeys(hkey, location)) {\n            if (!reg.querySubKeys(hkey, java).contains(java + \"\\\\MSI\"))\n                continue;\n            Object home = reg.queryValue(hkey, java, \"JavaHome\");\n            if (home instanceof String) {\n                try {\n                    tryAddJavaHome(javaRuntimes, Paths.get((String) home));\n                } catch (InvalidPathException e) {\n                    LOG.warning(\"Invalid Java path in system registry: \" + home);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.java;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.reflect.Type;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;\n\n/**\n * @author Glavo\n */\n@JsonAdapter(JavaManifest.Serializer.class)\npublic final class JavaManifest {\n\n    private final JavaInfo info;\n\n    @Nullable\n    private final Map<String, Object> update;\n\n    @Nullable\n    private final Map<String, JavaLocalFiles.Local> files;\n\n    public JavaManifest(JavaInfo info, @Nullable Map<String, Object> update, @Nullable Map<String, JavaLocalFiles.Local> files) {\n        this.info = info;\n        this.update = update;\n        this.files = files;\n    }\n\n    public JavaInfo getInfo() {\n        return info;\n    }\n\n    public Map<String, Object> getUpdate() {\n        return update;\n    }\n\n    public Map<String, JavaLocalFiles.Local> getFiles() {\n        return files;\n    }\n\n    public static final class Serializer implements JsonSerializer<JavaManifest>, JsonDeserializer<JavaManifest> {\n\n        private static final Type LOCAL_FILES_TYPE = mapTypeOf(String.class, JavaLocalFiles.Local.class).getType();\n\n        @Override\n        public JsonElement serialize(JavaManifest src, Type typeOfSrc, JsonSerializationContext context) {\n            JsonObject res = new JsonObject();\n            res.addProperty(\"os.name\", src.getInfo().getPlatform().getOperatingSystem().getCheckedName());\n            res.addProperty(\"os.arch\", src.getInfo().getPlatform().getArchitecture().getCheckedName());\n            res.addProperty(\"java.version\", src.getInfo().getVersion());\n            res.addProperty(\"java.vendor\", src.getInfo().getVendor());\n\n            if (src.getUpdate() != null)\n                res.add(\"update\", context.serialize(src.getUpdate()));\n\n            if (src.getFiles() != null)\n                res.add(\"files\", context.serialize(src.getFiles(), LOCAL_FILES_TYPE));\n\n            return res;\n        }\n\n        @Override\n        public JavaManifest deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (!json.isJsonObject())\n                throw new JsonParseException(json.toString());\n\n            try {\n                JsonObject jsonObject = json.getAsJsonObject();\n                OperatingSystem osName = OperatingSystem.parseOSName(jsonObject.getAsJsonPrimitive(\"os.name\").getAsString());\n                Architecture osArch = Architecture.parseArchName(jsonObject.getAsJsonPrimitive(\"os.arch\").getAsString());\n                String javaVersion = jsonObject.getAsJsonPrimitive(\"java.version\").getAsString();\n                String javaVendor = Optional.ofNullable(jsonObject.get(\"java.vendor\")).map(JsonElement::getAsString).orElse(null);\n\n                Map<String, Object> update = jsonObject.has(\"update\") ? context.deserialize(jsonObject.get(\"update\"), Map.class) : null;\n                Map<String, JavaLocalFiles.Local> files = jsonObject.has(\"files\") ? context.deserialize(jsonObject.get(\"files\"), LOCAL_FILES_TYPE) : null;\n\n                if (osName == null || osArch == null || javaVersion == null)\n                    throw new JsonParseException(json.toString());\n\n                return new JavaManifest(new JavaInfo(Platform.getPlatform(osName, osArch), javaVersion, javaVendor), update, files);\n            } catch (JsonParseException e) {\n                throw e;\n            } catch (Throwable e) {\n                throw new JsonParseException(e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.auth.*;\nimport org.jackhuang.hmcl.auth.authlibinjector.*;\nimport org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;\nimport org.jackhuang.hmcl.auth.microsoft.MicrosoftAccountFactory;\nimport org.jackhuang.hmcl.auth.microsoft.MicrosoftService;\nimport org.jackhuang.hmcl.auth.offline.OfflineAccount;\nimport org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;\nimport org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;\nimport org.jackhuang.hmcl.game.OAuthServer;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.util.FileSaver;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.skin.InvalidSkinException;\n\nimport javax.net.ssl.SSLException;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\n\nimport static java.util.stream.Collectors.toList;\nimport static javafx.collections.FXCollections.observableArrayList;\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;\nimport static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;\nimport static org.jackhuang.hmcl.util.Lang.immutableListOf;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huangyuhui\n */\npublic final class Accounts {\n    private Accounts() {\n    }\n\n    private static final AuthlibInjectorArtifactProvider AUTHLIB_INJECTOR_DOWNLOADER = createAuthlibInjectorArtifactProvider();\n\n    public static final OAuthServer.Factory OAUTH_CALLBACK = new OAuthServer.Factory();\n\n    public static final OfflineAccountFactory FACTORY_OFFLINE = new OfflineAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER);\n    public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, Accounts::getOrCreateAuthlibInjectorServer);\n    public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(OAUTH_CALLBACK));\n    public static final List<AccountFactory<?>> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);\n\n    // ==== login type / account factory mapping ====\n    private static final Map<String, AccountFactory<?>> type2factory = new HashMap<>();\n    private static final Map<AccountFactory<?>, String> factory2type = new HashMap<>();\n\n    static {\n        type2factory.put(\"offline\", FACTORY_OFFLINE);\n        type2factory.put(\"authlibInjector\", FACTORY_AUTHLIB_INJECTOR);\n        type2factory.put(\"microsoft\", FACTORY_MICROSOFT);\n\n        type2factory.forEach((type, factory) -> factory2type.put(factory, type));\n    }\n\n    public static String getLoginType(AccountFactory<?> factory) {\n        String type = factory2type.get(factory);\n        if (type != null) return type;\n\n        if (factory instanceof BoundAuthlibInjectorAccountFactory) {\n            return factory2type.get(FACTORY_AUTHLIB_INJECTOR);\n        }\n\n        throw new IllegalArgumentException(\"Unrecognized account factory\");\n    }\n\n    public static AccountFactory<?> getAccountFactory(String loginType) {\n        return Optional.ofNullable(type2factory.get(loginType))\n                .orElseThrow(() -> new IllegalArgumentException(\"Unrecognized login type\"));\n    }\n\n    public static BoundAuthlibInjectorAccountFactory getAccountFactoryByAuthlibInjectorServer(AuthlibInjectorServer server) {\n        return new BoundAuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, server);\n    }\n    // ====\n\n    public static AccountFactory<?> getAccountFactory(Account account) {\n        if (account instanceof OfflineAccount)\n            return FACTORY_OFFLINE;\n        else if (account instanceof AuthlibInjectorAccount)\n            return FACTORY_AUTHLIB_INJECTOR;\n        else if (account instanceof MicrosoftAccount)\n            return FACTORY_MICROSOFT;\n        else\n            throw new IllegalArgumentException(\"Failed to determine account type: \" + account);\n    }\n\n    private static final String GLOBAL_PREFIX = \"$GLOBAL:\";\n    private static final ObservableList<Map<Object, Object>> globalAccountStorages = FXCollections.observableArrayList();\n\n    private static final ObservableList<Account> accounts = observableArrayList(account -> new Observable[]{account});\n    private static final ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<>(Accounts.class, \"selectedAccount\");\n\n    /**\n     * True if {@link #init()} hasn't been called.\n     */\n    private static boolean initialized = false;\n\n    private static Map<Object, Object> getAccountStorage(Account account) {\n        Map<Object, Object> storage = account.toStorage();\n        storage.put(\"type\", getLoginType(getAccountFactory(account)));\n        return storage;\n    }\n\n    private static void updateAccountStorages() {\n        // don't update the underlying storage before data loading is completed\n        // otherwise it might cause data loss\n        if (!initialized)\n            return;\n        // update storage\n\n        ArrayList<Map<Object, Object>> global = new ArrayList<>();\n        ArrayList<Map<Object, Object>> portable = new ArrayList<>();\n\n        for (Account account : accounts) {\n            Map<Object, Object> storage = getAccountStorage(account);\n            if (account.isPortable())\n                portable.add(storage);\n            else\n                global.add(storage);\n        }\n\n        if (!global.equals(globalAccountStorages))\n            globalAccountStorages.setAll(global);\n        if (!portable.equals(config().getAccountStorages()))\n            config().getAccountStorages().setAll(portable);\n    }\n\n    private static void loadGlobalAccountStorages() {\n        Path globalAccountsFile = Metadata.HMCL_GLOBAL_DIRECTORY.resolve(\"accounts.json\");\n        if (Files.exists(globalAccountsFile)) {\n            try (Reader reader = Files.newBufferedReader(globalAccountsFile)) {\n                globalAccountStorages.setAll(Config.CONFIG_GSON.fromJson(reader, listTypeOf(mapTypeOf(Object.class, Object.class))));\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to load global accounts\", e);\n            }\n        }\n\n        globalAccountStorages.addListener(onInvalidating(() ->\n                FileSaver.save(globalAccountsFile, Config.CONFIG_GSON.toJson(globalAccountStorages))));\n    }\n\n    private static Account parseAccount(Map<Object, Object> storage) {\n        AccountFactory<?> factory = type2factory.get(storage.get(\"type\"));\n        if (factory == null) {\n            LOG.warning(\"Unrecognized account type: \" + storage);\n            return null;\n        }\n\n        try {\n            return factory.fromStorage(storage);\n        } catch (Exception e) {\n            LOG.warning(\"Failed to load account: \" + storage, e);\n            return null;\n        }\n    }\n\n    /**\n     * Called when it's ready to load accounts from {@link ConfigHolder#config()}.\n     */\n    static void init() {\n        if (initialized)\n            throw new IllegalStateException(\"Already initialized\");\n\n        if (!config().isAddedLittleSkin()) {\n            AuthlibInjectorServer littleSkin = new AuthlibInjectorServer(\"https://littleskin.cn/api/yggdrasil/\");\n\n            if (config().getAuthlibInjectorServers().stream().noneMatch(it -> littleSkin.getUrl().equals(it.getUrl()))) {\n                config().getAuthlibInjectorServers().add(0, littleSkin);\n            }\n\n            config().setAddedLittleSkin(true);\n        }\n\n        loadGlobalAccountStorages();\n\n        // load accounts\n        Account selected = null;\n        for (Map<Object, Object> storage : config().getAccountStorages()) {\n            Account account = parseAccount(storage);\n            if (account != null) {\n                account.setPortable(true);\n                accounts.add(account);\n                if (Boolean.TRUE.equals(storage.get(\"selected\"))) {\n                    selected = account;\n                }\n            }\n        }\n\n        for (Map<Object, Object> storage : globalAccountStorages) {\n            Account account = parseAccount(storage);\n            if (account != null) {\n                accounts.add(account);\n            }\n        }\n\n        String selectedAccountIdentifier = config().getSelectedAccount();\n        if (selected == null && selectedAccountIdentifier != null) {\n            boolean portable = true;\n            if (selectedAccountIdentifier.startsWith(GLOBAL_PREFIX)) {\n                portable = false;\n                selectedAccountIdentifier = selectedAccountIdentifier.substring(GLOBAL_PREFIX.length());\n            }\n\n            for (Account account : accounts) {\n                if (selectedAccountIdentifier.equals(account.getIdentifier())) {\n                    if (portable == account.isPortable()) {\n                        selected = account;\n                        break;\n                    } else if (selected == null) {\n                        selected = account;\n                    }\n                }\n            }\n        }\n\n        if (selected == null && !accounts.isEmpty()) {\n            selected = accounts.get(0);\n        }\n\n        if (!globalConfig().isEnableOfflineAccount())\n            for (Account account : accounts) {\n                if (account instanceof MicrosoftAccount) {\n                    globalConfig().setEnableOfflineAccount(true);\n                    break;\n                }\n            }\n\n        if (!globalConfig().isEnableOfflineAccount())\n            accounts.addListener(new ListChangeListener<Account>() {\n                @Override\n                public void onChanged(Change<? extends Account> change) {\n                    while (change.next()) {\n                        for (Account account : change.getAddedSubList()) {\n                            if (account instanceof MicrosoftAccount) {\n                                accounts.removeListener(this);\n                                globalConfig().setEnableOfflineAccount(true);\n                                return;\n                            }\n                        }\n                    }\n                }\n            });\n\n        selectedAccount.set(selected);\n\n        InvalidationListener listener = o -> {\n            // this method first checks whether the current selection is valid\n            // if it's valid, the underlying storage will be updated\n            // otherwise, the first account will be selected as an alternative(or null if accounts is empty)\n            Account account = selectedAccount.get();\n            if (accounts.isEmpty()) {\n                if (account == null) {\n                    // valid\n                } else {\n                    // the previously selected account is gone, we can only set it to null here\n                    selectedAccount.set(null);\n                }\n            } else {\n                if (accounts.contains(account)) {\n                    // valid\n                } else {\n                    // the previously selected account is gone\n                    selectedAccount.set(accounts.get(0));\n                }\n            }\n        };\n        selectedAccount.addListener(listener);\n        selectedAccount.addListener(onInvalidating(() -> {\n            Account account = selectedAccount.get();\n            if (account != null)\n                config().setSelectedAccount(account.isPortable() ? account.getIdentifier() : GLOBAL_PREFIX + account.getIdentifier());\n            else\n                config().setSelectedAccount(null);\n        }));\n        accounts.addListener(listener);\n        accounts.addListener(onInvalidating(Accounts::updateAccountStorages));\n\n        initialized = true;\n\n        config().getAuthlibInjectorServers().addListener(onInvalidating(Accounts::removeDanglingAuthlibInjectorAccounts));\n\n        if (selected != null) {\n            Account finalSelected = selected;\n            Schedulers.io().execute(() -> {\n                try {\n                    finalSelected.logIn();\n                } catch (Throwable e) {\n                    LOG.warning(\"Failed to log \" + finalSelected + \" in\", e);\n                }\n            });\n        }\n\n        for (AuthlibInjectorServer server : config().getAuthlibInjectorServers()) {\n            if (selected instanceof AuthlibInjectorAccount && ((AuthlibInjectorAccount) selected).getServer() == server)\n                continue;\n            Schedulers.io().execute(() -> {\n                try {\n                    server.fetchMetadataResponse();\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to fetch authlib-injector server metadata: \" + server, e);\n                }\n            });\n        }\n    }\n\n    public static ObservableList<Account> getAccounts() {\n        return accounts;\n    }\n\n    public static Account getSelectedAccount() {\n        return selectedAccount.get();\n    }\n\n    public static void setSelectedAccount(Account selectedAccount) {\n        Accounts.selectedAccount.set(selectedAccount);\n    }\n\n    public static ObjectProperty<Account> selectedAccountProperty() {\n        return selectedAccount;\n    }\n\n    // ==== authlib-injector ====\n    private static AuthlibInjectorArtifactProvider createAuthlibInjectorArtifactProvider() {\n        String authlibinjectorLocation = System.getProperty(\"hmcl.authlibinjector.location\");\n        if (authlibinjectorLocation != null) {\n            LOG.info(\"Using specified authlib-injector: \" + authlibinjectorLocation);\n            return new SimpleAuthlibInjectorArtifactProvider(Paths.get(authlibinjectorLocation));\n        }\n\n        String authlibInjectorVersion = JarUtils.getAttribute(\"hmcl.authlib-injector.version\", null);\n        if (authlibInjectorVersion == null)\n            throw new AssertionError(\"Missing hmcl.authlib-injector.version\");\n\n        String authlibInjectorFileName = \"authlib-injector-\" + authlibInjectorVersion + \".jar\";\n        return new AuthlibInjectorExtractor(Accounts.class.getResource(\"/assets/\" + authlibInjectorFileName),\n                Metadata.DEPENDENCIES_DIRECTORY.resolve(\"universal\").resolve(authlibInjectorFileName));\n    }\n\n    private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {\n        return config().getAuthlibInjectorServers().stream()\n                .filter(server -> url.equals(server.getUrl()))\n                .findFirst()\n                .orElseGet(() -> {\n                    AuthlibInjectorServer server = new AuthlibInjectorServer(url);\n                    config().getAuthlibInjectorServers().add(server);\n                    return server;\n                });\n    }\n\n    /**\n     * After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.\n     * This method performs a check and removes the dangling accounts.\n     */\n    private static void removeDanglingAuthlibInjectorAccounts() {\n        accounts.stream()\n                .filter(AuthlibInjectorAccount.class::isInstance)\n                .map(AuthlibInjectorAccount.class::cast)\n                .filter(it -> !config().getAuthlibInjectorServers().contains(it.getServer()))\n                .collect(toList())\n                .forEach(accounts::remove);\n    }\n    // ====\n\n    // ==== Login type name i18n ===\n    private static final Map<AccountFactory<?>, String> unlocalizedLoginTypeNames = mapOf(\n            pair(Accounts.FACTORY_OFFLINE, \"account.methods.offline\"),\n            pair(Accounts.FACTORY_AUTHLIB_INJECTOR, \"account.methods.authlib_injector\"),\n            pair(Accounts.FACTORY_MICROSOFT, \"account.methods.microsoft\"));\n\n    public static String getLocalizedLoginTypeName(AccountFactory<?> factory) {\n        return i18n(Optional.ofNullable(unlocalizedLoginTypeNames.get(factory))\n                .orElseThrow(() -> new IllegalArgumentException(\"Unrecognized account factory\")));\n    }\n    // ====\n\n    public static String localizeErrorMessage(Exception exception) {\n        if (exception instanceof NoCharacterException) {\n            return i18n(\"account.failed.no_character\");\n        } else if (exception instanceof ServerDisconnectException) {\n            if (exception.getCause() instanceof SSLException) {\n                if (exception.getCause().getMessage() != null && exception.getCause().getMessage().contains(\"Remote host terminated\")) {\n                    return i18n(\"account.failed.connect_authentication_server\");\n                }\n                if (exception.getCause().getMessage() != null && (exception.getCause().getMessage().contains(\"No name matching\") || exception.getCause().getMessage().contains(\"No subject alternative DNS name matching\"))) {\n                    return i18n(\"account.failed.dns\");\n                }\n                return i18n(\"account.failed.ssl\");\n            } else {\n                return i18n(\"account.failed.connect_authentication_server\");\n            }\n        } else if (exception instanceof ServerResponseMalformedException) {\n            return i18n(\"account.failed.server_response_malformed\");\n        } else if (exception instanceof RemoteAuthenticationException) {\n            RemoteAuthenticationException remoteException = (RemoteAuthenticationException) exception;\n            String remoteMessage = remoteException.getRemoteMessage();\n            if (\"ForbiddenOperationException\".equals(remoteException.getRemoteName()) && remoteMessage != null) {\n                if (remoteMessage.contains(\"Invalid credentials\")) {\n                    return i18n(\"account.failed.invalid_credentials\");\n                } else if (remoteMessage.contains(\"Invalid token\")) {\n                    return i18n(\"account.failed.invalid_token\");\n                } else if (remoteMessage.contains(\"Invalid username or password\")) {\n                    return i18n(\"account.failed.invalid_password\");\n                } else {\n                    return remoteMessage;\n                }\n            } else if (\"ResourceException\".equals(remoteException.getRemoteName()) && remoteMessage != null) {\n                if (remoteMessage.contains(\"The requested resource is no longer available\")) {\n                    return i18n(\"account.failed.migration\");\n                } else {\n                    return remoteMessage;\n                }\n            }\n            return exception.getMessage();\n        } else if (exception instanceof AuthlibInjectorDownloadException) {\n            return i18n(\"account.failed.injector_download_failure\");\n        } else if (exception instanceof CharacterDeletedException) {\n            return i18n(\"account.failed.character_deleted\");\n        } else if (exception instanceof InvalidSkinException) {\n            return i18n(\"account.skin.invalid_skin\");\n        } else if (exception instanceof MicrosoftService.XboxAuthorizationException) {\n            long errorCode = ((MicrosoftService.XboxAuthorizationException) exception).getErrorCode();\n            if (errorCode == MicrosoftService.XboxAuthorizationException.ADD_FAMILY) {\n                return i18n(\"account.methods.microsoft.error.add_family\");\n            } else if (errorCode == MicrosoftService.XboxAuthorizationException.COUNTRY_UNAVAILABLE) {\n                return i18n(\"account.methods.microsoft.error.country_unavailable\");\n            } else if (errorCode == MicrosoftService.XboxAuthorizationException.MISSING_XBOX_ACCOUNT) {\n                return i18n(\"account.methods.microsoft.error.missing_xbox_account\");\n            } else if (errorCode == MicrosoftService.XboxAuthorizationException.BANNED) {\n                return i18n(\"account.methods.microsoft.error.banned\");\n            } else {\n                return i18n(\"account.methods.microsoft.error.unknown\", errorCode);\n            }\n        } else if (exception instanceof MicrosoftService.XBox400Exception) {\n            return i18n(\"account.methods.microsoft.error.wrong_verify_method\");\n        } else if (exception instanceof MicrosoftService.NoMinecraftJavaEditionProfileException) {\n            return i18n(\"account.methods.microsoft.error.no_character\");\n        } else if (exception instanceof MicrosoftService.NoXuiException) {\n            return i18n(\"account.methods.microsoft.error.add_family\");\n        } else if (exception instanceof OAuthServer.MicrosoftAuthenticationNotSupportedException) {\n            return i18n(\"account.methods.microsoft.snapshot\");\n        } else if (exception instanceof OAuthAccount.WrongAccountException) {\n            return i18n(\"account.failed.wrong_account\");\n        } else if (exception.getClass() == AuthenticationException.class) {\n            return exception.getLocalizedMessage();\n        } else {\n            return exception.getClass().getName() + \": \" + exception.getLocalizedMessage();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/AuthlibInjectorServers.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jackhuang.hmcl.util.io.JarUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n@JsonSerializable\npublic final class AuthlibInjectorServers implements Validation {\n\n    public static final String CONFIG_FILENAME = \"authlib-injectors.json\";\n\n    private static final Set<AuthlibInjectorServer> servers = new CopyOnWriteArraySet<>();\n\n    public static Set<AuthlibInjectorServer> getServers() {\n        return servers;\n    }\n\n    private final List<String> urls;\n\n    private AuthlibInjectorServers(List<String> urls) {\n        this.urls = urls;\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        if (this.urls == null) {\n            throw new JsonParseException(\"authlib-injectors.json -> urls cannot be null.\");\n        }\n    }\n\n    public static void init() {\n        Path configLocation;\n        Path jarPath = JarUtils.thisJarPath();\n        if (jarPath != null && Files.isRegularFile(jarPath) && Files.isWritable(jarPath)) {\n            configLocation = jarPath.getParent().resolve(CONFIG_FILENAME);\n        } else {\n            configLocation = Paths.get(CONFIG_FILENAME);\n        }\n\n        if (ConfigHolder.isNewlyCreated() && Files.exists(configLocation)) {\n            AuthlibInjectorServers configInstance;\n            try {\n                configInstance = JsonUtils.fromJsonFile(configLocation, AuthlibInjectorServers.class);\n            } catch (IOException | JsonParseException e) {\n                LOG.warning(\"Malformed authlib-injectors.json\", e);\n                return;\n            }\n\n            if (!configInstance.urls.isEmpty()) {\n                config().setPreferredLoginType(Accounts.getLoginType(Accounts.FACTORY_AUTHLIB_INJECTOR));\n                for (String url : configInstance.urls) {\n                    Task.supplyAsync(Schedulers.io(), () -> AuthlibInjectorServer.locateServer(url))\n                            .thenAcceptAsync(Schedulers.javafx(), server -> {\n                                config().getAuthlibInjectorServers().add(server);\n                                servers.add(server);\n                            })\n                            .start();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport com.google.gson.annotations.SerializedName;\nimport javafx.beans.Observable;\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.collections.ObservableMap;\nimport javafx.collections.ObservableSet;\nimport javafx.scene.paint.Paint;\nimport org.hildan.fxgson.creators.ObservableListCreator;\nimport org.hildan.fxgson.creators.ObservableMapCreator;\nimport org.hildan.fxgson.creators.ObservableSetCreator;\nimport org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.theme.ThemeColor;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.gson.*;\nimport org.jackhuang.hmcl.util.i18n.SupportedLocale;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.net.Proxy;\nimport java.nio.file.Path;\nimport java.util.*;\n\n@JsonAdapter(value = Config.Adapter.class)\npublic final class Config extends ObservableSetting {\n\n    public static final int CURRENT_VERSION = 2;\n    public static final int CURRENT_UI_VERSION = 0;\n\n    public static final Gson CONFIG_GSON = new GsonBuilder()\n            .registerTypeAdapter(Path.class, PathTypeAdapter.INSTANCE)\n            .registerTypeAdapter(ObservableList.class, new ObservableListCreator())\n            .registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())\n            .registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())\n            .registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))\n            .registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType\n            .registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy\n            .registerTypeAdapter(Paint.class, new PaintAdapter())\n            .setPrettyPrinting()\n            .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)\n            .create();\n\n    @Nullable\n    public static Config fromJson(String json) throws JsonParseException {\n        return CONFIG_GSON.fromJson(json, Config.class);\n    }\n\n    public Config() {\n        tracker.markDirty(configVersion);\n        tracker.markDirty(uiVersion);\n        register();\n    }\n\n    public String toJson() {\n        return CONFIG_GSON.toJson(this);\n    }\n\n    // Properties\n\n    @SerializedName(\"_version\")\n    private final IntegerProperty configVersion = new SimpleIntegerProperty(CURRENT_VERSION);\n\n    public IntegerProperty configVersionProperty() {\n        return configVersion;\n    }\n\n    public int getConfigVersion() {\n        return configVersion.get();\n    }\n\n    public void setConfigVersion(int configVersion) {\n        this.configVersion.set(configVersion);\n    }\n\n    /**\n     * The version of UI that the user have last used.\n     * If there is a major change in UI, {@link Config#CURRENT_UI_VERSION} should be increased.\n     * When {@link #CURRENT_UI_VERSION} is higher than the property, the user guide should be shown,\n     * then this property is set to the same value as {@link #CURRENT_UI_VERSION}.\n     * In particular, the property is default to 0, so that whoever open the application for the first time will see the guide.\n     */\n    @SerializedName(\"uiVersion\")\n    private final IntegerProperty uiVersion = new SimpleIntegerProperty(CURRENT_UI_VERSION);\n\n    public IntegerProperty uiVersionProperty() {\n        return uiVersion;\n    }\n\n    public int getUiVersion() {\n        return uiVersion.get();\n    }\n\n    public void setUiVersion(int uiVersion) {\n        this.uiVersion.set(uiVersion);\n    }\n\n    @SerializedName(\"x\")\n    private final DoubleProperty x = new SimpleDoubleProperty();\n\n    public DoubleProperty xProperty() {\n        return x;\n    }\n\n    public double getX() {\n        return x.get();\n    }\n\n    public void setX(double x) {\n        this.x.set(x);\n    }\n\n    @SerializedName(\"y\")\n    private final DoubleProperty y = new SimpleDoubleProperty();\n\n    public DoubleProperty yProperty() {\n        return y;\n    }\n\n    public double getY() {\n        return y.get();\n    }\n\n    public void setY(double y) {\n        this.y.set(y);\n    }\n\n    @SerializedName(\"width\")\n    private final DoubleProperty width = new SimpleDoubleProperty();\n\n    public DoubleProperty widthProperty() {\n        return width;\n    }\n\n    public double getWidth() {\n        return width.get();\n    }\n\n    public void setWidth(double width) {\n        this.width.set(width);\n    }\n\n    @SerializedName(\"height\")\n    private final DoubleProperty height = new SimpleDoubleProperty();\n\n    public DoubleProperty heightProperty() {\n        return height;\n    }\n\n    public double getHeight() {\n        return height.get();\n    }\n\n    public void setHeight(double height) {\n        this.height.set(height);\n    }\n\n    @SerializedName(\"localization\")\n    private final ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(SupportedLocale.DEFAULT);\n\n    public ObjectProperty<SupportedLocale> localizationProperty() {\n        return localization;\n    }\n\n    public SupportedLocale getLocalization() {\n        return localization.get();\n    }\n\n    public void setLocalization(SupportedLocale localization) {\n        this.localization.set(localization);\n    }\n\n    @SerializedName(\"promptedVersion\")\n    private final StringProperty promptedVersion = new SimpleStringProperty();\n\n    public String getPromptedVersion() {\n        return promptedVersion.get();\n    }\n\n    public StringProperty promptedVersionProperty() {\n        return promptedVersion;\n    }\n\n    public void setPromptedVersion(String promptedVersion) {\n        this.promptedVersion.set(promptedVersion);\n    }\n\n    @SerializedName(\"acceptPreviewUpdate\")\n    private final BooleanProperty acceptPreviewUpdate = new SimpleBooleanProperty(false);\n\n    public BooleanProperty acceptPreviewUpdateProperty() {\n        return acceptPreviewUpdate;\n    }\n\n    public boolean isAcceptPreviewUpdate() {\n        return acceptPreviewUpdate.get();\n    }\n\n    public void setAcceptPreviewUpdate(boolean acceptPreviewUpdate) {\n        this.acceptPreviewUpdate.set(acceptPreviewUpdate);\n    }\n\n    @SerializedName(\"disableAutoShowUpdateDialog\")\n    private final BooleanProperty disableAutoShowUpdateDialog = new SimpleBooleanProperty(false);\n\n    public BooleanProperty disableAutoShowUpdateDialogProperty() {\n        return disableAutoShowUpdateDialog;\n    }\n\n    public boolean isDisableAutoShowUpdateDialog() {\n        return disableAutoShowUpdateDialog.get();\n    }\n\n    public void setDisableAutoShowUpdateDialog(boolean disableAutoShowUpdateDialog) {\n        this.disableAutoShowUpdateDialog.set(disableAutoShowUpdateDialog);\n    }\n\n    @SerializedName(\"disableAprilFools\")\n    private final BooleanProperty disableAprilFools = new SimpleBooleanProperty(false);\n\n    public BooleanProperty disableAprilFoolsProperty() {\n        return disableAprilFools;\n    }\n\n    public boolean isDisableAprilFools() {\n        return disableAprilFools.get();\n    }\n\n    public void setDisableAprilFools(boolean disableAprilFools) {\n        this.disableAprilFools.set(disableAprilFools);\n    }\n\n    @SerializedName(\"shownTips\")\n    private final ObservableMap<String, Object> shownTips = FXCollections.observableHashMap();\n\n    public ObservableMap<String, Object> getShownTips() {\n        return shownTips;\n    }\n\n    @SerializedName(\"commonDirType\")\n    private final ObjectProperty<EnumCommonDirectory> commonDirType = new RawPreservingObjectProperty<>(EnumCommonDirectory.DEFAULT);\n\n    public ObjectProperty<EnumCommonDirectory> commonDirTypeProperty() {\n        return commonDirType;\n    }\n\n    public EnumCommonDirectory getCommonDirType() {\n        return commonDirType.get();\n    }\n\n    public void setCommonDirType(EnumCommonDirectory commonDirType) {\n        this.commonDirType.set(commonDirType);\n    }\n\n    @SerializedName(\"commonpath\")\n    private final StringProperty commonDirectory = new SimpleStringProperty(Metadata.MINECRAFT_DIRECTORY.toString());\n\n    public StringProperty commonDirectoryProperty() {\n        return commonDirectory;\n    }\n\n    public String getCommonDirectory() {\n        return commonDirectory.get();\n    }\n\n    public void setCommonDirectory(String commonDirectory) {\n        this.commonDirectory.set(commonDirectory);\n    }\n\n    @SerializedName(\"logLines\")\n    private final ObjectProperty<Integer> logLines = new SimpleObjectProperty<>();\n\n    public ObjectProperty<Integer> logLinesProperty() {\n        return logLines;\n    }\n\n    public Integer getLogLines() {\n        return logLines.get();\n    }\n\n    public void setLogLines(Integer logLines) {\n        this.logLines.set(logLines);\n    }\n\n    // UI\n\n    @SerializedName(\"themeBrightness\")\n    private final StringProperty themeBrightness = new SimpleStringProperty(\"light\");\n\n    public StringProperty themeBrightnessProperty() {\n        return themeBrightness;\n    }\n\n    public String getThemeBrightness() {\n        return themeBrightness.get();\n    }\n\n    public void setThemeBrightness(String themeBrightness) {\n        this.themeBrightness.set(themeBrightness);\n    }\n\n    @SerializedName(\"theme\")\n    private final ObjectProperty<ThemeColor> themeColor = new SimpleObjectProperty<>(ThemeColor.DEFAULT);\n\n    public ObjectProperty<ThemeColor> themeColorProperty() {\n        return themeColor;\n    }\n\n    public ThemeColor getThemeColor() {\n        return themeColor.get();\n    }\n\n    public void setThemeColor(ThemeColor themeColor) {\n        this.themeColor.set(themeColor);\n    }\n\n    @SerializedName(\"fontFamily\")\n    private final StringProperty fontFamily = new SimpleStringProperty();\n\n    public StringProperty fontFamilyProperty() {\n        return fontFamily;\n    }\n\n    public String getFontFamily() {\n        return fontFamily.get();\n    }\n\n    public void setFontFamily(String fontFamily) {\n        this.fontFamily.set(fontFamily);\n    }\n\n    @SerializedName(\"fontSize\")\n    private final DoubleProperty fontSize = new SimpleDoubleProperty(12);\n\n    public DoubleProperty fontSizeProperty() {\n        return fontSize;\n    }\n\n    public double getFontSize() {\n        return fontSize.get();\n    }\n\n    public void setFontSize(double fontSize) {\n        this.fontSize.set(fontSize);\n    }\n\n    @SerializedName(\"launcherFontFamily\")\n    private final StringProperty launcherFontFamily = new SimpleStringProperty();\n\n    public StringProperty launcherFontFamilyProperty() {\n        return launcherFontFamily;\n    }\n\n    public String getLauncherFontFamily() {\n        return launcherFontFamily.get();\n    }\n\n    public void setLauncherFontFamily(String launcherFontFamily) {\n        this.launcherFontFamily.set(launcherFontFamily);\n    }\n\n    @SerializedName(\"animationDisabled\")\n    private final BooleanProperty animationDisabled = new SimpleBooleanProperty(\n            FXUtils.REDUCED_MOTION == Boolean.TRUE\n                    || !JavaRuntime.CURRENT_JIT_ENABLED\n                    || !FXUtils.GPU_ACCELERATION_ENABLED\n    );\n\n    public BooleanProperty animationDisabledProperty() {\n        return animationDisabled;\n    }\n\n    public boolean isAnimationDisabled() {\n        return animationDisabled.get();\n    }\n\n    public void setAnimationDisabled(boolean animationDisabled) {\n        this.animationDisabled.set(animationDisabled);\n    }\n\n    @SerializedName(\"titleTransparent\")\n    private final BooleanProperty titleTransparent = new SimpleBooleanProperty(false);\n\n    public BooleanProperty titleTransparentProperty() {\n        return titleTransparent;\n    }\n\n    public boolean isTitleTransparent() {\n        return titleTransparent.get();\n    }\n\n    public void setTitleTransparent(boolean titleTransparent) {\n        this.titleTransparent.set(titleTransparent);\n    }\n\n    @SerializedName(\"backgroundType\")\n    private final ObjectProperty<EnumBackgroundImage> backgroundImageType = new RawPreservingObjectProperty<>(EnumBackgroundImage.DEFAULT);\n\n    public ObjectProperty<EnumBackgroundImage> backgroundImageTypeProperty() {\n        return backgroundImageType;\n    }\n\n    public EnumBackgroundImage getBackgroundImageType() {\n        return backgroundImageType.get();\n    }\n\n    public void setBackgroundImageType(EnumBackgroundImage backgroundImageType) {\n        this.backgroundImageType.set(backgroundImageType);\n    }\n\n    @SerializedName(\"bgpath\")\n    private final StringProperty backgroundImage = new SimpleStringProperty();\n\n    public StringProperty backgroundImageProperty() {\n        return backgroundImage;\n    }\n\n    public String getBackgroundImage() {\n        return backgroundImage.get();\n    }\n\n    public void setBackgroundImage(String backgroundImage) {\n        this.backgroundImage.set(backgroundImage);\n    }\n\n    @SerializedName(\"bgurl\")\n    private final StringProperty backgroundImageUrl = new SimpleStringProperty();\n\n    public StringProperty backgroundImageUrlProperty() {\n        return backgroundImageUrl;\n    }\n\n    public String getBackgroundImageUrl() {\n        return backgroundImageUrl.get();\n    }\n\n    public void setBackgroundImageUrl(String backgroundImageUrl) {\n        this.backgroundImageUrl.set(backgroundImageUrl);\n    }\n\n    @SerializedName(\"bgpaint\")\n    private final ObjectProperty<Paint> backgroundPaint = new SimpleObjectProperty<>();\n\n    public Paint getBackgroundPaint() {\n        return backgroundPaint.get();\n    }\n\n    public ObjectProperty<Paint> backgroundPaintProperty() {\n        return backgroundPaint;\n    }\n\n    public void setBackgroundPaint(Paint backgroundPaint) {\n        this.backgroundPaint.set(backgroundPaint);\n    }\n\n    @SerializedName(\"bgImageOpacity\")\n    private final IntegerProperty backgroundImageOpacity = new SimpleIntegerProperty(100);\n\n    public IntegerProperty backgroundImageOpacityProperty() {\n        return backgroundImageOpacity;\n    }\n\n    public int getBackgroundImageOpacity() {\n        return backgroundImageOpacity.get();\n    }\n\n    public void setBackgroundImageOpacity(int backgroundImageOpacity) {\n        this.backgroundImageOpacity.set(backgroundImageOpacity);\n    }\n\n    // Networks\n\n    @SerializedName(\"autoDownloadThreads\")\n    private final BooleanProperty autoDownloadThreads = new SimpleBooleanProperty(true);\n\n    public BooleanProperty autoDownloadThreadsProperty() {\n        return autoDownloadThreads;\n    }\n\n    public boolean getAutoDownloadThreads() {\n        return autoDownloadThreads.get();\n    }\n\n    public void setAutoDownloadThreads(boolean autoDownloadThreads) {\n        this.autoDownloadThreads.set(autoDownloadThreads);\n    }\n\n    @SerializedName(\"downloadThreads\")\n    private final IntegerProperty downloadThreads = new SimpleIntegerProperty(64);\n\n    public IntegerProperty downloadThreadsProperty() {\n        return downloadThreads;\n    }\n\n    public int getDownloadThreads() {\n        return downloadThreads.get();\n    }\n\n    public void setDownloadThreads(int downloadThreads) {\n        this.downloadThreads.set(downloadThreads);\n    }\n\n    @SerializedName(\"downloadType\")\n    private final StringProperty downloadType = new SimpleStringProperty(DownloadProviders.DEFAULT_DIRECT_PROVIDER_ID);\n\n    public StringProperty downloadTypeProperty() {\n        return downloadType;\n    }\n\n    public String getDownloadType() {\n        return downloadType.get();\n    }\n\n    public void setDownloadType(String downloadType) {\n        this.downloadType.set(downloadType);\n    }\n\n    @SerializedName(\"autoChooseDownloadType\")\n    private final BooleanProperty autoChooseDownloadType = new SimpleBooleanProperty(true);\n\n    public BooleanProperty autoChooseDownloadTypeProperty() {\n        return autoChooseDownloadType;\n    }\n\n    public boolean isAutoChooseDownloadType() {\n        return autoChooseDownloadType.get();\n    }\n\n    public void setAutoChooseDownloadType(boolean autoChooseDownloadType) {\n        this.autoChooseDownloadType.set(autoChooseDownloadType);\n    }\n\n    @SerializedName(\"versionListSource\")\n    private final StringProperty versionListSource = new SimpleStringProperty(DownloadProviders.DEFAULT_AUTO_PROVIDER_ID);\n\n    public StringProperty versionListSourceProperty() {\n        return versionListSource;\n    }\n\n    public String getVersionListSource() {\n        return versionListSource.get();\n    }\n\n    public void setVersionListSource(String versionListSource) {\n        this.versionListSource.set(versionListSource);\n    }\n\n    @SerializedName(\"hasProxy\")\n    private final BooleanProperty hasProxy = new SimpleBooleanProperty();\n\n    public BooleanProperty hasProxyProperty() {\n        return hasProxy;\n    }\n\n    public boolean hasProxy() {\n        return hasProxy.get();\n    }\n\n    public void setHasProxy(boolean hasProxy) {\n        this.hasProxy.set(hasProxy);\n    }\n\n    @SerializedName(\"hasProxyAuth\")\n    private final BooleanProperty hasProxyAuth = new SimpleBooleanProperty();\n\n    public BooleanProperty hasProxyAuthProperty() {\n        return hasProxyAuth;\n    }\n\n    public boolean hasProxyAuth() {\n        return hasProxyAuth.get();\n    }\n\n    public void setHasProxyAuth(boolean hasProxyAuth) {\n        this.hasProxyAuth.set(hasProxyAuth);\n    }\n\n    @SerializedName(\"proxyType\")\n    private final ObjectProperty<Proxy.Type> proxyType = new SimpleObjectProperty<>(Proxy.Type.HTTP);\n\n    public ObjectProperty<Proxy.Type> proxyTypeProperty() {\n        return proxyType;\n    }\n\n    public Proxy.Type getProxyType() {\n        return proxyType.get();\n    }\n\n    public void setProxyType(Proxy.Type proxyType) {\n        this.proxyType.set(proxyType);\n    }\n\n    @SerializedName(\"proxyHost\")\n    private final StringProperty proxyHost = new SimpleStringProperty();\n\n    public StringProperty proxyHostProperty() {\n        return proxyHost;\n    }\n\n    public String getProxyHost() {\n        return proxyHost.get();\n    }\n\n    public void setProxyHost(String proxyHost) {\n        this.proxyHost.set(proxyHost);\n    }\n\n    @SerializedName(\"proxyPort\")\n    private final IntegerProperty proxyPort = new SimpleIntegerProperty();\n\n    public IntegerProperty proxyPortProperty() {\n        return proxyPort;\n    }\n\n    public int getProxyPort() {\n        return proxyPort.get();\n    }\n\n    public void setProxyPort(int proxyPort) {\n        this.proxyPort.set(proxyPort);\n    }\n\n    @SerializedName(\"proxyUserName\")\n    private final StringProperty proxyUser = new SimpleStringProperty();\n\n    public StringProperty proxyUserProperty() {\n        return proxyUser;\n    }\n\n    public String getProxyUser() {\n        return proxyUser.get();\n    }\n\n    public void setProxyUser(String proxyUser) {\n        this.proxyUser.set(proxyUser);\n    }\n\n    @SerializedName(\"proxyPassword\")\n    private final StringProperty proxyPass = new SimpleStringProperty();\n\n    public StringProperty proxyPassProperty() {\n        return proxyPass;\n    }\n\n    public String getProxyPass() {\n        return proxyPass.get();\n    }\n\n    public void setProxyPass(String proxyPass) {\n        this.proxyPass.set(proxyPass);\n    }\n\n    // Game\n\n    @SerializedName(\"disableAutoGameOptions\")\n    private final BooleanProperty disableAutoGameOptions = new SimpleBooleanProperty(false);\n\n    public BooleanProperty disableAutoGameOptionsProperty() {\n        return disableAutoGameOptions;\n    }\n\n    public boolean isDisableAutoGameOptions() {\n        return disableAutoGameOptions.get();\n    }\n\n    public void setDisableAutoGameOptions(boolean disableAutoGameOptions) {\n        this.disableAutoGameOptions.set(disableAutoGameOptions);\n    }\n\n    // Accounts\n\n    @SerializedName(\"authlibInjectorServers\")\n    private final ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList(server -> new Observable[]{server});\n\n    public ObservableList<AuthlibInjectorServer> getAuthlibInjectorServers() {\n        return authlibInjectorServers;\n    }\n\n    @SerializedName(\"addedLittleSkin\")\n    private final BooleanProperty addedLittleSkin = new SimpleBooleanProperty(false);\n\n    public BooleanProperty addedLittleSkinProperty() {\n        return addedLittleSkin;\n    }\n\n    public boolean isAddedLittleSkin() {\n        return addedLittleSkin.get();\n    }\n\n    public void setAddedLittleSkin(boolean addedLittleSkin) {\n        this.addedLittleSkin.set(addedLittleSkin);\n    }\n\n    /**\n     * The preferred login type to use when the user wants to add an account.\n     */\n    @SerializedName(\"preferredLoginType\")\n    private final StringProperty preferredLoginType = new SimpleStringProperty();\n\n    public StringProperty preferredLoginTypeProperty() {\n        return preferredLoginType;\n    }\n\n    public String getPreferredLoginType() {\n        return preferredLoginType.get();\n    }\n\n    public void setPreferredLoginType(String preferredLoginType) {\n        this.preferredLoginType.set(preferredLoginType);\n    }\n\n    @SerializedName(\"selectedAccount\")\n    private final StringProperty selectedAccount = new SimpleStringProperty();\n\n    public StringProperty selectedAccountProperty() {\n        return selectedAccount;\n    }\n\n    public String getSelectedAccount() {\n        return selectedAccount.get();\n    }\n\n    public void setSelectedAccount(String selectedAccount) {\n        this.selectedAccount.set(selectedAccount);\n    }\n\n    @SerializedName(\"accounts\")\n    private final ObservableList<Map<Object, Object>> accountStorages = FXCollections.observableArrayList();\n\n    public ObservableList<Map<Object, Object>> getAccountStorages() {\n        return accountStorages;\n    }\n\n    // Configurations\n\n    @SerializedName(\"last\")\n    private final StringProperty selectedProfile = new SimpleStringProperty(\"\");\n\n    public StringProperty selectedProfileProperty() {\n        return selectedProfile;\n    }\n\n    public String getSelectedProfile() {\n        return selectedProfile.get();\n    }\n\n    public void setSelectedProfile(String selectedProfile) {\n        this.selectedProfile.set(selectedProfile);\n    }\n\n    @SerializedName(\"configurations\")\n    private final SimpleMapProperty<String, Profile> configurations = new SimpleMapProperty<>(FXCollections.observableMap(new TreeMap<>()));\n\n    public MapProperty<String, Profile> getConfigurations() {\n        return configurations;\n    }\n\n    public static final class Adapter extends ObservableSetting.Adapter<Config> {\n        @Override\n        protected Config createInstance() {\n            return new Config();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.util.FileSaver;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.io.IOException;\nimport java.nio.file.*;\nimport java.util.Locale;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class ConfigHolder {\n\n    private ConfigHolder() {\n    }\n\n    public static final String CONFIG_FILENAME = \"hmcl.json\";\n    public static final String CONFIG_FILENAME_LINUX = \".hmcl.json\";\n    public static final Path GLOBAL_CONFIG_PATH = Metadata.HMCL_GLOBAL_DIRECTORY.resolve(\"config.json\");\n\n    private static Path configLocation;\n    private static Config configInstance;\n    private static GlobalConfig globalConfigInstance;\n    private static boolean newlyCreated;\n    private static boolean ownerChanged = false;\n    private static boolean unsupportedVersion = false;\n\n    public static Config config() {\n        if (configInstance == null) {\n            throw new IllegalStateException(\"Configuration hasn't been loaded\");\n        }\n        return configInstance;\n    }\n\n    public static GlobalConfig globalConfig() {\n        if (globalConfigInstance == null) {\n            throw new IllegalStateException(\"Configuration hasn't been loaded\");\n        }\n        return globalConfigInstance;\n    }\n\n    public static Path configLocation() {\n        return configLocation;\n    }\n\n    public static boolean isNewlyCreated() {\n        return newlyCreated;\n    }\n\n    public static boolean isOwnerChanged() {\n        return ownerChanged;\n    }\n\n    public static boolean isUnsupportedVersion() {\n        return unsupportedVersion;\n    }\n\n    public static void init() throws IOException {\n        if (configInstance != null) {\n            throw new IllegalStateException(\"Configuration is already loaded\");\n        }\n\n        configLocation = locateConfig();\n\n        LOG.info(\"Config location: \" + configLocation);\n\n        configInstance = loadConfig();\n        if (!unsupportedVersion)\n            configInstance.addListener(source -> FileSaver.save(configLocation, configInstance.toJson()));\n\n        globalConfigInstance = loadGlobalConfig();\n        globalConfigInstance.addListener(source -> FileSaver.save(GLOBAL_CONFIG_PATH, globalConfigInstance.toJson()));\n\n        Locale.setDefault(config().getLocalization().getLocale());\n        I18n.setLocale(configInstance.getLocalization());\n        LOG.setLogRetention(globalConfig().getLogRetention());\n        Settings.init();\n\n        if (newlyCreated) {\n            LOG.info(\"Creating config file \" + configLocation);\n            FileUtils.saveSafely(configLocation, configInstance.toJson());\n        }\n\n        if (!Files.isWritable(configLocation)) {\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS\n                    && configLocation.getFileSystem() == FileSystems.getDefault()\n                    && configLocation.toFile().canWrite()) {\n                LOG.warning(\"Config at \" + configLocation + \" is not writable, but it seems to be a Samba share or OpenJDK bug\");\n                // There are some serious problems with the implementation of Samba or OpenJDK\n                throw new SambaException();\n            } else {\n                // the config cannot be saved\n                // throw up the error now to prevent further data loss\n                throw new IOException(\"Config at \" + configLocation + \" is not writable\");\n            }\n        }\n    }\n\n    private static Path locateConfig() {\n        Path defaultConfigFile = Metadata.HMCL_CURRENT_DIRECTORY.resolve(CONFIG_FILENAME);\n        if (Files.isRegularFile(defaultConfigFile))\n            return defaultConfigFile;\n\n        try {\n            Path jarPath = JarUtils.thisJarPath();\n            if (jarPath != null && Files.isRegularFile(jarPath) && Files.isWritable(jarPath)) {\n                jarPath = jarPath.getParent();\n\n                Path config = jarPath.resolve(CONFIG_FILENAME);\n                if (Files.isRegularFile(config))\n                    return config;\n\n                Path dotConfig = jarPath.resolve(CONFIG_FILENAME_LINUX);\n                if (Files.isRegularFile(dotConfig))\n                    return dotConfig;\n            }\n\n        } catch (Throwable ignore) {\n        }\n\n        Path config = Paths.get(CONFIG_FILENAME);\n        if (Files.isRegularFile(config))\n            return config;\n\n        Path dotConfig = Paths.get(CONFIG_FILENAME_LINUX);\n        if (Files.isRegularFile(dotConfig))\n            return dotConfig;\n\n        // create new\n        return defaultConfigFile;\n    }\n\n    private static Config loadConfig() throws IOException {\n        if (Files.exists(configLocation)) {\n            try {\n                if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS\n                        && \"root\".equals(System.getProperty(\"user.name\"))\n                        && !\"root\".equals(Files.getOwner(configLocation).getName())) {\n                    ownerChanged = true;\n                }\n            } catch (IOException e1) {\n                LOG.warning(\"Failed to get owner\");\n            }\n            try {\n                String content = Files.readString(configLocation);\n                Config deserialized = Config.fromJson(content);\n                if (deserialized == null) {\n                    LOG.info(\"Config is empty\");\n                } else {\n                    int configVersion = deserialized.getConfigVersion();\n                    if (configVersion < Config.CURRENT_VERSION) {\n                        ConfigUpgrader.upgradeConfig(deserialized, content);\n                    } else if (configVersion > Config.CURRENT_VERSION) {\n                        unsupportedVersion = true;\n                        LOG.warning(String.format(\"Current HMCL only support the configuration version up to %d. However, the version now is %d.\", Config.CURRENT_VERSION, configVersion));\n                    }\n\n                    return deserialized;\n                }\n            } catch (JsonParseException e) {\n                LOG.warning(\"Malformed config.\", e);\n            }\n        }\n\n        newlyCreated = true;\n        return new Config();\n    }\n\n    // Global Config\n\n    private static GlobalConfig loadGlobalConfig() throws IOException {\n        if (Files.exists(GLOBAL_CONFIG_PATH)) {\n            try {\n                String content = Files.readString(GLOBAL_CONFIG_PATH);\n                GlobalConfig deserialized = GlobalConfig.fromJson(content);\n                if (deserialized == null) {\n                    LOG.info(\"Config is empty\");\n                } else {\n                    return deserialized;\n                }\n            } catch (JsonParseException e) {\n                LOG.warning(\"Malformed config.\", e);\n            }\n        }\n\n        LOG.info(\"Creating an empty global config\");\n        return new GlobalConfig();\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport com.google.gson.Gson;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.util.Lang.tryCast;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\nfinal class ConfigUpgrader {\n    private ConfigUpgrader() {\n    }\n\n    /**\n     * This method is for the compatibility with old HMCL versions.\n     *\n     * @param deserialized deserialized config settings\n     * @param rawContent   raw json content of the config settings without modification\n     */\n    static void upgradeConfig(Config deserialized, String rawContent) {\n        int configVersion = deserialized.getConfigVersion();\n\n        if (configVersion >= Config.CURRENT_VERSION)\n            return;\n\n        LOG.info(String.format(\"Updating configuration from %d to %d.\", configVersion, Config.CURRENT_VERSION));\n        Map<?, ?> rawJson = Collections.unmodifiableMap(new Gson().<Map<?, ?>>fromJson(rawContent, Map.class));\n\n        if (configVersion < 1) {\n            // Upgrade configuration of HMCL 2.x: Convert OfflineAccounts whose stored uuid is important.\n            tryCast(rawJson.get(\"auth\"), Map.class).ifPresent(auth -> {\n                tryCast(auth.get(\"offline\"), Map.class).ifPresent(offline -> {\n                    String selected = rawJson.containsKey(\"selectedAccount\") ? null\n                            : tryCast(offline.get(\"IAuthenticator_UserName\"), String.class).orElse(null);\n\n                    tryCast(offline.get(\"uuidMap\"), Map.class).ifPresent(uuidMap -> {\n                        ((Map<?, ?>) uuidMap).forEach((key, value) -> {\n                            Map<Object, Object> storage = new HashMap<>();\n                            storage.put(\"type\", \"offline\");\n                            storage.put(\"username\", key);\n                            storage.put(\"uuid\", value);\n                            if (key.equals(selected)) {\n                                storage.put(\"selected\", true);\n                            }\n                            deserialized.getAccountStorages().add(storage);\n                        });\n                    });\n                });\n            });\n\n            // Upgrade configuration of HMCL earlier than 3.1.70\n            if (!rawJson.containsKey(\"commonDirType\"))\n                deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);\n            if (!rawJson.containsKey(\"backgroundType\"))\n                deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);\n            if (!rawJson.containsKey(\"hasProxy\"))\n                deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));\n            if (!rawJson.containsKey(\"hasProxyAuth\"))\n                deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));\n\n            if (!rawJson.containsKey(\"downloadType\")) {\n                tryCast(rawJson.get(\"downloadtype\"), Number.class)\n                        .map(Number::intValue)\n                        .ifPresent(id -> {\n                            if (id == 0) {\n                                deserialized.setDownloadType(\"mojang\");\n                            } else if (id == 1) {\n                                deserialized.setDownloadType(\"bmclapi\");\n                            }\n                        });\n            }\n        }\n\n        deserialized.setConfigVersion(Config.CURRENT_VERSION);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport javafx.beans.InvalidationListener;\nimport org.jackhuang.hmcl.download.*;\nimport org.jackhuang.hmcl.task.DownloadException;\nimport org.jackhuang.hmcl.task.FetchTask;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.i18n.LocaleUtils;\nimport org.jackhuang.hmcl.util.io.ResponseCodeException;\n\nimport javax.net.ssl.SSLHandshakeException;\nimport java.io.FileNotFoundException;\nimport java.net.SocketTimeoutException;\nimport java.net.URI;\nimport java.nio.file.AccessDeniedException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CancellationException;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.task.FetchTask.DEFAULT_CONCURRENCY;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class DownloadProviders {\n    private DownloadProviders() {\n    }\n\n    public static final String DEFAULT_AUTO_PROVIDER_ID = \"balanced\";\n    public static final String DEFAULT_DIRECT_PROVIDER_ID = \"mojang\";\n\n    private static final DownloadProviderWrapper PROVIDER_WRAPPER;\n\n    private static final DownloadProvider DEFAULT_PROVIDER;\n    public static final Map<String, DownloadProvider> DIRECT_PROVIDERS;\n    public static final Map<String, DownloadProvider> AUTO_PROVIDERS;\n\n    static {\n        String bmclapiRoot = System.getProperty(\"hmcl.bmclapi.override\", \"https://bmclapi2.bangbang93.com\");\n        BMCLAPIDownloadProvider bmclapiRaw = new BMCLAPIDownloadProvider(bmclapiRoot);\n\n        DownloadProvider mojang = new MojangDownloadProvider();\n        DownloadProvider bmclapi = new AutoDownloadProvider(bmclapiRaw, mojang);\n\n        DEFAULT_PROVIDER = mojang;\n        DIRECT_PROVIDERS = Lang.mapOf(\n                pair(\"mojang\", mojang),\n                pair(\"bmclapi\", bmclapi)\n        );\n\n        AUTO_PROVIDERS = Lang.mapOf(\n                pair(\"balanced\", LocaleUtils.IS_CHINA_MAINLAND ? bmclapi : mojang),\n                pair(\"official\", LocaleUtils.IS_CHINA_MAINLAND ? new AutoDownloadProvider(\n                        List.of(mojang, bmclapiRaw),\n                        List.of(bmclapiRaw, mojang)\n                ) : mojang),\n                pair(\"mirror\", bmclapi)\n        );\n\n        PROVIDER_WRAPPER = new DownloadProviderWrapper(DEFAULT_PROVIDER);\n    }\n\n    static void init() {\n        InvalidationListener onChangeDownloadThreads = observable -> {\n            FetchTask.setDownloadExecutorConcurrency(config().getAutoDownloadThreads()\n                    ? DEFAULT_CONCURRENCY\n                    : config().getDownloadThreads());\n        };\n        config().autoDownloadThreadsProperty().addListener(onChangeDownloadThreads);\n        config().downloadThreadsProperty().addListener(onChangeDownloadThreads);\n        onChangeDownloadThreads.invalidated(null);\n\n        InvalidationListener onChangeDownloadSource = observable -> {\n            if (config().isAutoChooseDownloadType()) {\n                String versionListSource = config().getVersionListSource();\n                DownloadProvider downloadProvider = versionListSource != null\n                        ? AUTO_PROVIDERS.getOrDefault(versionListSource, DEFAULT_PROVIDER)\n                        : DEFAULT_PROVIDER;\n                PROVIDER_WRAPPER.setProvider(downloadProvider);\n            } else {\n                String downloadType = config().getDownloadType();\n                PROVIDER_WRAPPER.setProvider(downloadType != null\n                        ? DIRECT_PROVIDERS.getOrDefault(downloadType, DEFAULT_PROVIDER)\n                        : DEFAULT_PROVIDER);\n            }\n        };\n        config().versionListSourceProperty().addListener(onChangeDownloadSource);\n        config().autoChooseDownloadTypeProperty().addListener(onChangeDownloadSource);\n        config().downloadTypeProperty().addListener(onChangeDownloadSource);\n        onChangeDownloadSource.invalidated(null);\n    }\n\n    /**\n     * Get current primary preferred download provider\n     */\n    public static DownloadProvider getDownloadProvider() {\n        return PROVIDER_WRAPPER;\n    }\n\n    public static String localizeErrorMessage(Throwable exception) {\n        if (exception instanceof DownloadException) {\n            URI uri = ((DownloadException) exception).getUri();\n            if (exception.getCause() instanceof SocketTimeoutException) {\n                return i18n(\"install.failed.downloading.timeout\", uri);\n            } else if (exception.getCause() instanceof ResponseCodeException) {\n                ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause();\n                if (I18n.hasKey(\"download.code.\" + responseCodeException.getResponseCode())) {\n                    return i18n(\"download.code.\" + responseCodeException.getResponseCode(), uri);\n                } else {\n                    return i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + StringUtils.getStackTrace(exception.getCause());\n                }\n            } else if (exception.getCause() instanceof FileNotFoundException) {\n                return i18n(\"download.code.404\", uri);\n            } else if (exception.getCause() instanceof AccessDeniedException) {\n                return i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + i18n(\"exception.access_denied\", ((AccessDeniedException) exception.getCause()).getFile());\n            } else if (exception.getCause() instanceof ArtifactMalformedException) {\n                return i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + i18n(\"exception.artifact_malformed\");\n            } else if (exception.getCause() instanceof SSLHandshakeException && !(exception.getCause().getMessage() != null && exception.getCause().getMessage().contains(\"Remote host terminated\"))) {\n                if (exception.getCause().getMessage() != null && (exception.getCause().getMessage().contains(\"No name matching\") || exception.getCause().getMessage().contains(\"No subject alternative DNS name matching\"))) {\n                    return i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + i18n(\"exception.dns.pollution\");\n                }\n                return i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + i18n(\"exception.ssl_handshake\");\n            } else {\n                return i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + StringUtils.getStackTrace(exception.getCause());\n            }\n        } else if (exception instanceof ArtifactMalformedException) {\n            return i18n(\"exception.artifact_malformed\");\n        } else if (exception instanceof CancellationException) {\n            return i18n(\"message.cancelled\");\n        }\n        return StringUtils.getStackTrace(exception);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/EnumBackgroundImage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\npublic enum EnumBackgroundImage {\n    DEFAULT,\n    CUSTOM,\n    CLASSIC,\n    NETWORK,\n    TRANSLUCENT, // Deprecated\n    PAINT\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/EnumCommonDirectory.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\npublic enum EnumCommonDirectory {\n    /**\n     * %appdata%/.minecraft or ~/.minecraft\n     */\n    DEFAULT,\n    /**\n     * user customized common directory.\n     */\n    CUSTOM\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/FontManager.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.scene.text.Font;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.util.Lazy;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.i18n.LocaleUtils;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.net.MalformedURLException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class FontManager {\n\n    public static final String[] FONT_EXTENSIONS = {\n            \"ttf\", \"otf\", \"woff\"\n    };\n\n    public static final double DEFAULT_FONT_SIZE = 12.0f;\n\n    private static final Lazy<Font> DEFAULT_FONT = new Lazy<>(() -> {\n        Font font;\n\n        // Recommended\n\n        font = tryLoadLocalizedFont(Metadata.HMCL_CURRENT_DIRECTORY.resolve(\"font\"));\n        if (font != null)\n            return font;\n\n        font = tryLoadLocalizedFont(Metadata.HMCL_GLOBAL_DIRECTORY.resolve(\"font\"));\n        if (font != null)\n            return font;\n\n        // Legacy\n\n        font = tryLoadDefaultFont(Metadata.HMCL_CURRENT_DIRECTORY);\n        if (font != null)\n            return font;\n\n        font = tryLoadDefaultFont(Metadata.CURRENT_DIRECTORY);\n        if (font != null)\n            return font;\n\n        font = tryLoadDefaultFont(Metadata.HMCL_GLOBAL_DIRECTORY);\n        if (font != null)\n            return font;\n\n        Path thisJar = JarUtils.thisJarPath();\n        if (thisJar != null && thisJar.getParent() != null) {\n            font = tryLoadDefaultFont(thisJar.getParent());\n            if (font != null)\n                return font;\n        }\n\n        // Default\n\n        String fcMatchPattern;\n        if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()\n                && !(fcMatchPattern = I18n.getLocale().getFcMatchPattern()).isEmpty())\n            return findByFcMatch(fcMatchPattern);\n        else\n            return null;\n    });\n\n    private static final ObjectProperty<FontReference> font = new SimpleObjectProperty<>();\n\n    static {\n        updateFont();\n        LOG.info(\"Font: \" + (font.get() != null ? font.get().family() : \"System\"));\n    }\n\n    private static void updateFont() {\n        String fontFamily = config().getLauncherFontFamily();\n        if (fontFamily == null)\n            fontFamily = System.getProperty(\"hmcl.font.override\");\n        if (fontFamily == null)\n            fontFamily = System.getenv(\"HMCL_FONT\");\n\n        if (fontFamily == null) {\n            Font defaultFont = DEFAULT_FONT.get();\n            font.set(defaultFont != null ? new FontReference(defaultFont) : null);\n        } else {\n            font.set(new FontReference(fontFamily));\n        }\n    }\n\n    private static Font tryLoadDefaultFont(Path dir) {\n        for (String extension : FONT_EXTENSIONS) {\n            Path path = dir.resolve(\"font.\" + extension);\n            if (Files.isRegularFile(path)) {\n                LOG.info(\"Load font file: \" + path);\n                try {\n                    Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), DEFAULT_FONT_SIZE);\n                    if (font != null) {\n                        return font;\n                    }\n                } catch (MalformedURLException ignored) {\n                }\n\n                LOG.warning(\"Failed to load font \" + path);\n            }\n        }\n\n        return null;\n    }\n\n    private static Font tryLoadLocalizedFont(Path dir) {\n        Map<String, Map<String, Path>> fontFiles = LocaleUtils.findAllLocalizedFiles(dir, \"font\", Set.of(FONT_EXTENSIONS));\n        if (fontFiles.isEmpty())\n            return null;\n\n        List<Locale> candidateLocales = I18n.getLocale().getCandidateLocales();\n\n        for (Locale locale : candidateLocales) {\n            Map<String, Path> extToFiles = fontFiles.get(LocaleUtils.toLanguageKey(locale));\n            if (extToFiles != null) {\n                for (String ext : FONT_EXTENSIONS) {\n                    Path fontFile = extToFiles.get(ext);\n                    if (fontFile != null) {\n                        LOG.info(\"Load font file: \" + fontFile);\n                        try {\n                            Font font = Font.loadFont(\n                                    fontFile.toAbsolutePath().normalize().toUri().toURL().toExternalForm(),\n                                    DEFAULT_FONT_SIZE);\n                            if (font != null)\n                                return font;\n                        } catch (MalformedURLException ignored) {\n                        }\n\n                        LOG.warning(\"Failed to load font \" + fontFile);\n                    }\n                }\n            }\n        }\n\n        return null;\n    }\n\n    public static Font findByFcMatch(String pattern) {\n        Path fcMatch = SystemUtils.which(\"fc-match\");\n        if (fcMatch == null)\n            return null;\n\n        try {\n            String result = SystemUtils.run(fcMatch.toString(),\n                    pattern,\n                    \"--format\", \"%{family}\\\\n%{file}\").trim();\n            String[] results = result.split(\"\\\\n\");\n            if (results.length != 2 || results[0].isEmpty() || results[1].isEmpty()) {\n                LOG.warning(\"Unexpected output from fc-match: \" + result);\n                return null;\n            }\n\n            String family = results[0].trim();\n            String path = results[1];\n\n            Path file = Paths.get(path).toAbsolutePath().normalize();\n            if (!Files.isRegularFile(file)) {\n                LOG.warning(\"Font file does not exist: \" + path);\n                return null;\n            }\n\n            LOG.info(\"Load font file: \" + path);\n            Font[] fonts = Font.loadFonts(file.toUri().toURL().toExternalForm(), DEFAULT_FONT_SIZE);\n            if (fonts == null) {\n                LOG.warning(\"Failed to load font from \" + path);\n                return null;\n            } else if (fonts.length == 0) {\n                LOG.warning(\"No fonts loaded from \" + path);\n                return null;\n            }\n\n            for (Font font : fonts) {\n                if (font.getFamily().equalsIgnoreCase(family)) {\n                    return font;\n                }\n            }\n\n            if (family.indexOf(',') >= 0) {\n                for (String candidateFamily : family.split(\",\")) {\n                    for (Font font : fonts) {\n                        if (font.getFamily().equalsIgnoreCase(candidateFamily)) {\n                            return font;\n                        }\n                    }\n                }\n            }\n\n            LOG.warning(String.format(\"Family '%s' not found in font file '%s'\", family, path));\n            return fonts[0];\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get default font with fc-match\", e);\n            return null;\n        }\n    }\n\n    public static ReadOnlyObjectProperty<FontReference> fontProperty() {\n        return font;\n    }\n\n    public static FontReference getFont() {\n        return font.get();\n    }\n\n    public static void setFontFamily(String fontFamily) {\n        config().setLauncherFontFamily(fontFamily);\n        updateFont();\n    }\n\n    // https://github.com/HMCL-dev/HMCL/issues/4072\n    public record FontReference(@NotNull String family, @Nullable String style) {\n        public FontReference {\n            Objects.requireNonNull(family);\n        }\n\n        public FontReference(@NotNull String family) {\n            this(family, null);\n        }\n\n        public FontReference(@NotNull Font font) {\n            this(font.getFamily(), font.getStyle());\n        }\n    }\n\n    private FontManager() {\n    }\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport com.google.gson.annotations.SerializedName;\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableSet;\nimport org.jackhuang.hmcl.util.gson.ObservableSetting;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\n\n@JsonAdapter(GlobalConfig.Adapter.class)\npublic final class GlobalConfig extends ObservableSetting {\n\n    @Nullable\n    public static GlobalConfig fromJson(String json) throws JsonParseException {\n        return Config.CONFIG_GSON.fromJson(json, GlobalConfig.class);\n    }\n\n    public GlobalConfig() {\n        register();\n    }\n\n    public String toJson() {\n        return Config.CONFIG_GSON.toJson(this);\n    }\n\n    @SerializedName(\"agreementVersion\")\n    private final IntegerProperty agreementVersion = new SimpleIntegerProperty();\n\n    public IntegerProperty agreementVersionProperty() {\n        return agreementVersion;\n    }\n\n    public int getAgreementVersion() {\n        return agreementVersion.get();\n    }\n\n    public void setAgreementVersion(int agreementVersion) {\n        this.agreementVersion.set(agreementVersion);\n    }\n\n    @SerializedName(\"terracottaAgreementVersion\")\n    private final IntegerProperty terracottaAgreementVersion = new SimpleIntegerProperty();\n\n    public IntegerProperty terracottaAgreementVersionProperty() {\n        return terracottaAgreementVersion;\n    }\n\n    public int getTerracottaAgreementVersion() {\n        return terracottaAgreementVersion.get();\n    }\n\n    public void setTerracottaAgreementVersion(int terracottaAgreementVersion) {\n        this.terracottaAgreementVersion.set(terracottaAgreementVersion);\n    }\n\n    @SerializedName(\"platformPromptVersion\")\n    private final IntegerProperty platformPromptVersion = new SimpleIntegerProperty();\n\n    public IntegerProperty platformPromptVersionProperty() {\n        return platformPromptVersion;\n    }\n\n    public int getPlatformPromptVersion() {\n        return platformPromptVersion.get();\n    }\n\n    public void setPlatformPromptVersion(int platformPromptVersion) {\n        this.platformPromptVersion.set(platformPromptVersion);\n    }\n\n    @SerializedName(\"logRetention\")\n    private final IntegerProperty logRetention = new SimpleIntegerProperty(20);\n\n    public IntegerProperty logRetentionProperty() {\n        return logRetention;\n    }\n\n    public int getLogRetention() {\n        return logRetention.get();\n    }\n\n    public void setLogRetention(int logRetention) {\n        this.logRetention.set(logRetention);\n    }\n\n    @SerializedName(\"enableOfflineAccount\")\n    private final BooleanProperty enableOfflineAccount = new SimpleBooleanProperty(false);\n\n    public BooleanProperty enableOfflineAccountProperty() {\n        return enableOfflineAccount;\n    }\n\n    public boolean isEnableOfflineAccount() {\n        return enableOfflineAccount.get();\n    }\n\n    public void setEnableOfflineAccount(boolean value) {\n        enableOfflineAccount.set(value);\n    }\n\n    @SerializedName(\"fontAntiAliasing\")\n    private final StringProperty fontAntiAliasing = new SimpleStringProperty();\n\n    public StringProperty fontAntiAliasingProperty() {\n        return fontAntiAliasing;\n    }\n\n    public String getFontAntiAliasing() {\n        return fontAntiAliasing.get();\n    }\n\n    public void setFontAntiAliasing(String value) {\n        this.fontAntiAliasing.set(value);\n    }\n\n    @SerializedName(\"userJava\")\n    private final ObservableSet<String> userJava = FXCollections.observableSet(new LinkedHashSet<>());\n\n    public ObservableSet<String> getUserJava() {\n        return userJava;\n    }\n\n    @SerializedName(\"disabledJava\")\n    private final ObservableSet<String> disabledJava = FXCollections.observableSet(new LinkedHashSet<>());\n\n    public ObservableSet<String> getDisabledJava() {\n        return disabledJava;\n    }\n\n    static final class Adapter extends ObservableSetting.Adapter<GlobalConfig> {\n        @Override\n        protected GlobalConfig createInstance() {\n            return new GlobalConfig();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/JavaVersionType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\n/**\n * @author Glavo\n */\npublic enum JavaVersionType {\n    DEFAULT, AUTO, VERSION, DETECTED, CUSTOM\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherVisibility.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\n/**\n * The visibility of launcher.\n * @author huangyuhui\n */\npublic enum LauncherVisibility {\n\n    /**\n     * Close the launcher anyway when the game process created even if failed to\n     * launch game.\n     */\n    CLOSE,\n\n    /**\n     * Hide the launcher when the game process created, if failed to launch\n     * game, will show the log window.\n     */\n    HIDE,\n\n    /**\n     * Keep the launcher visible even if the game launched successfully.\n     */\n    KEEP,\n\n    /**\n     * Hide the launcher and reopen it when game closes.\n     */\n    HIDE_AND_REOPEN;\n\n    public boolean isDaemon() {\n        return this != CLOSE;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\nimport javafx.beans.property.*;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.event.EventBus;\nimport org.jackhuang.hmcl.event.EventPriority;\nimport org.jackhuang.hmcl.event.RefreshedVersionsEvent;\nimport org.jackhuang.hmcl.game.HMCLCacheRepository;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jackhuang.hmcl.util.javafx.ObservableHelper;\n\nimport java.lang.reflect.Type;\nimport java.nio.file.Path;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\n\n/**\n *\n * @author huangyuhui\n */\n@JsonAdapter(Profile.Serializer.class)\npublic final class Profile implements Observable {\n    private final WeakListenerHolder listenerHolder = new WeakListenerHolder();\n    private final HMCLGameRepository repository;\n\n    private final StringProperty selectedVersion = new SimpleStringProperty();\n\n    public StringProperty selectedVersionProperty() {\n        return selectedVersion;\n    }\n\n    public String getSelectedVersion() {\n        return selectedVersion.get();\n    }\n\n    public void setSelectedVersion(String selectedVersion) {\n        this.selectedVersion.set(selectedVersion);\n    }\n\n    private final ObjectProperty<Path> gameDir;\n\n    public ObjectProperty<Path> gameDirProperty() {\n        return gameDir;\n    }\n\n    public Path getGameDir() {\n        return gameDir.get();\n    }\n\n    public void setGameDir(Path gameDir) {\n        this.gameDir.set(gameDir);\n    }\n\n    private final ReadOnlyObjectWrapper<VersionSetting> global = new ReadOnlyObjectWrapper<>(this, \"global\");\n\n    public ReadOnlyObjectProperty<VersionSetting> globalProperty() {\n        return global.getReadOnlyProperty();\n    }\n\n    public VersionSetting getGlobal() {\n        return global.get();\n    }\n\n    private final SimpleStringProperty name;\n\n    public StringProperty nameProperty() {\n        return name;\n    }\n\n    public String getName() {\n        return name.get();\n    }\n\n    public void setName(String name) {\n        this.name.set(name);\n    }\n\n    private final BooleanProperty useRelativePath = new SimpleBooleanProperty(this, \"useRelativePath\", false);\n\n    public BooleanProperty useRelativePathProperty() {\n        return useRelativePath;\n    }\n\n    public boolean isUseRelativePath() {\n        return useRelativePath.get();\n    }\n\n    public void setUseRelativePath(boolean useRelativePath) {\n        this.useRelativePath.set(useRelativePath);\n    }\n\n    public Profile(String name) {\n        this(name, Path.of(\".minecraft\"));\n    }\n\n    public Profile(String name, Path initialGameDir) {\n        this(name, initialGameDir, new VersionSetting());\n    }\n\n    public Profile(String name, Path initialGameDir, VersionSetting global) {\n        this(name, initialGameDir, global, null, false);\n    }\n\n    public Profile(String name, Path initialGameDir, VersionSetting global, String selectedVersion, boolean useRelativePath) {\n        this.name = new SimpleStringProperty(this, \"name\", name);\n        gameDir = new SimpleObjectProperty<>(this, \"gameDir\", initialGameDir);\n        repository = new HMCLGameRepository(this, initialGameDir);\n        this.global.set(global == null ? new VersionSetting() : global);\n        this.selectedVersion.set(selectedVersion);\n        this.useRelativePath.set(useRelativePath);\n\n        gameDir.addListener((a, b, newValue) -> repository.changeDirectory(newValue));\n        this.selectedVersion.addListener(o -> checkSelectedVersion());\n        listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));\n\n        addPropertyChangedListener(onInvalidating(this::invalidate));\n    }\n\n    private void checkSelectedVersion() {\n        runInFX(() -> {\n            if (!repository.isLoaded()) return;\n            String newValue = selectedVersion.get();\n            if (!repository.hasVersion(newValue)) {\n                Optional<String> version = repository.getVersions().stream().findFirst().map(Version::getId);\n                if (version.isPresent())\n                    selectedVersion.setValue(version.get());\n                else if (newValue != null)\n                    selectedVersion.setValue(null);\n            }\n        });\n    }\n\n    public HMCLGameRepository getRepository() {\n        return repository;\n    }\n\n    public DefaultDependencyManager getDependency() {\n        return getDependency(DownloadProviders.getDownloadProvider());\n    }\n\n    public DefaultDependencyManager getDependency(DownloadProvider downloadProvider) {\n        return new DefaultDependencyManager(repository, downloadProvider, HMCLCacheRepository.REPOSITORY);\n    }\n\n    public VersionSetting getVersionSetting(String id) {\n        return repository.getVersionSetting(id);\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"gameDir\", getGameDir())\n                .append(\"name\", getName())\n                .append(\"useRelativePath\", isUseRelativePath())\n                .toString();\n    }\n\n    private void addPropertyChangedListener(InvalidationListener listener) {\n        name.addListener(listener);\n        global.addListener(listener);\n        gameDir.addListener(listener);\n        useRelativePath.addListener(listener);\n        global.get().addListener(listener);\n        selectedVersion.addListener(listener);\n    }\n\n    private ObservableHelper observableHelper = new ObservableHelper(this);\n\n    @Override\n    public void addListener(InvalidationListener listener) {\n        observableHelper.addListener(listener);\n    }\n\n    @Override\n    public void removeListener(InvalidationListener listener) {\n        observableHelper.removeListener(listener);\n    }\n\n    private void invalidate() {\n        Platform.runLater(observableHelper::invalidate);\n    }\n\n    public static class ProfileVersion {\n        private final Profile profile;\n        private final String version;\n\n        public ProfileVersion(Profile profile, String version) {\n            this.profile = profile;\n            this.version = version;\n        }\n\n        public Profile getProfile() {\n            return profile;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n    }\n\n    public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {\n        @Override\n        public JsonElement serialize(Profile src, Type typeOfSrc, JsonSerializationContext context) {\n            if (src == null)\n                return JsonNull.INSTANCE;\n\n            JsonObject jsonObject = new JsonObject();\n            jsonObject.add(\"global\", context.serialize(src.getGlobal()));\n            jsonObject.addProperty(\"gameDir\", src.getGameDir().toString());\n            jsonObject.addProperty(\"useRelativePath\", src.isUseRelativePath());\n            jsonObject.addProperty(\"selectedMinecraftVersion\", src.getSelectedVersion());\n\n            return jsonObject;\n        }\n\n        @Override\n        public Profile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (!(json instanceof JsonObject obj)) return null;\n            String gameDir = Optional.ofNullable(obj.get(\"gameDir\")).map(JsonElement::getAsString).orElse(\"\");\n\n            return new Profile(\"Default\",\n                    Path.of(gameDir),\n                    context.deserialize(obj.get(\"global\"), VersionSetting.class),\n                    Optional.ofNullable(obj.get(\"selectedMinecraftVersion\")).map(JsonElement::getAsString).orElse(\"\"),\n                    Optional.ofNullable(obj.get(\"useRelativePath\")).map(JsonElement::getAsBoolean).orElse(false));\n        }\n\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport javafx.application.Platform;\nimport javafx.beans.Observable;\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.event.EventBus;\nimport org.jackhuang.hmcl.event.RefreshedVersionsEvent;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.TreeMap;\nimport java.util.function.Consumer;\n\nimport static javafx.collections.FXCollections.observableArrayList;\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class Profiles {\n\n    public static final String DEFAULT_PROFILE = \"Default\";\n    public static final String HOME_PROFILE = \"Home\";\n\n    private Profiles() {\n    }\n\n    public static String getProfileDisplayName(Profile profile) {\n        return switch (profile.getName()) {\n            case Profiles.DEFAULT_PROFILE -> i18n(\"profile.default\");\n            case Profiles.HOME_PROFILE -> i18n(\"profile.home\");\n            default -> profile.getName();\n        };\n    }\n\n    private static final ObservableList<Profile> profiles = observableArrayList(profile -> new Observable[] { profile });\n    private static final ReadOnlyListWrapper<Profile> profilesWrapper = new ReadOnlyListWrapper<>(profiles);\n\n    private static final ObjectProperty<Profile> selectedProfile = new SimpleObjectProperty<Profile>() {\n        {\n            profiles.addListener(onInvalidating(this::invalidated));\n        }\n\n        @Override\n        protected void invalidated() {\n            if (!initialized)\n                return;\n\n            Profile profile = get();\n\n            if (profiles.isEmpty()) {\n                if (profile != null) {\n                    set(null);\n                    return;\n                }\n            } else {\n                if (!profiles.contains(profile)) {\n                    set(profiles.get(0));\n                    return;\n                }\n            }\n\n            config().setSelectedProfile(profile == null ? \"\" : profile.getName());\n            if (profile != null) {\n                if (profile.getRepository().isLoaded())\n                    selectedVersion.bind(profile.selectedVersionProperty());\n                else {\n                    selectedVersion.unbind();\n                    selectedVersion.set(null);\n                    // bind when repository was reloaded.\n                    profile.getRepository().refreshVersionsAsync().start();\n                }\n            } else {\n                selectedVersion.unbind();\n                selectedVersion.set(null);\n            }\n        }\n    };\n\n    private static void checkProfiles() {\n        if (profiles.isEmpty()) {\n            Profile current = new Profile(Profiles.DEFAULT_PROFILE, Path.of(\".minecraft\"), new VersionSetting(), null, true);\n            Profile home = new Profile(Profiles.HOME_PROFILE, Metadata.MINECRAFT_DIRECTORY);\n            Platform.runLater(() -> profiles.addAll(current, home));\n        }\n    }\n\n    /**\n     * True if {@link #init()} hasn't been called.\n     */\n    private static boolean initialized = false;\n\n    static {\n        profiles.addListener(onInvalidating(Profiles::updateProfileStorages));\n        profiles.addListener(onInvalidating(Profiles::checkProfiles));\n\n        selectedProfile.addListener((a, b, newValue) -> {\n            if (newValue != null)\n                newValue.getRepository().refreshVersionsAsync().start();\n        });\n    }\n\n    private static void updateProfileStorages() {\n        // don't update the underlying storage before data loading is completed\n        // otherwise it might cause data loss\n        if (!initialized)\n            return;\n        // update storage\n        TreeMap<String, Profile> newConfigurations = new TreeMap<>();\n        for (Profile profile : profiles) {\n            newConfigurations.put(profile.getName(), profile);\n        }\n        config().getConfigurations().setValue(FXCollections.observableMap(newConfigurations));\n    }\n\n    /**\n     * Called when it's ready to load profiles from {@link ConfigHolder#config()}.\n     */\n    static void init() {\n        if (initialized)\n            throw new IllegalStateException(\"Already initialized\");\n\n        HashSet<String> names = new HashSet<>();\n        config().getConfigurations().forEach((name, profile) -> {\n            if (!names.add(name)) return;\n            profiles.add(profile);\n            profile.setName(name);\n        });\n        checkProfiles();\n\n        // Platform.runLater is necessary or profiles will be empty\n        // since checkProfiles adds 2 base profile later.\n        Platform.runLater(() -> {\n            initialized = true;\n\n            selectedProfile.set(\n                    profiles.stream()\n                            .filter(it -> it.getName().equals(config().getSelectedProfile()))\n                            .findFirst()\n                            .orElse(profiles.get(0)));\n        });\n\n        EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {\n            runInFX(() -> {\n                Profile profile = selectedProfile.get();\n                if (profile != null && profile.getRepository() == event.getSource()) {\n                    selectedVersion.bind(profile.selectedVersionProperty());\n                    for (Consumer<Profile> listener : versionsListeners)\n                        listener.accept(profile);\n                }\n            });\n        });\n    }\n\n    public static ObservableList<Profile> getProfiles() {\n        return profiles;\n    }\n\n    public static ReadOnlyListProperty<Profile> profilesProperty() {\n        return profilesWrapper.getReadOnlyProperty();\n    }\n\n    public static Profile getSelectedProfile() {\n        return selectedProfile.get();\n    }\n\n    public static void setSelectedProfile(Profile profile) {\n        selectedProfile.set(profile);\n    }\n\n    public static ObjectProperty<Profile> selectedProfileProperty() {\n        return selectedProfile;\n    }\n\n    private static final ReadOnlyStringWrapper selectedVersion = new ReadOnlyStringWrapper();\n\n    public static ReadOnlyStringProperty selectedVersionProperty() {\n        return selectedVersion.getReadOnlyProperty();\n    }\n\n    // Guaranteed that the repository is loaded.\n    public static String getSelectedVersion() {\n        return selectedVersion.get();\n    }\n\n    private static final List<Consumer<Profile>> versionsListeners = new ArrayList<>(4);\n\n    public static void registerVersionsListener(Consumer<Profile> listener) {\n        Profile profile = getSelectedProfile();\n        if (profile != null && profile.getRepository().isLoaded())\n            listener.accept(profile);\n        versionsListeners.add(listener);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/ProxyManager.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport javafx.beans.InvalidationListener;\nimport org.jackhuang.hmcl.task.FetchTask;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.net.*;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class ProxyManager {\n\n    private static final SimpleProxySelector NO_PROXY = new SimpleProxySelector(Proxy.NO_PROXY);\n    private static final ProxySelector SYSTEM_DEFAULT;\n\n    static {\n        ProxySelector systemProxySelector = ProxySelector.getDefault();\n        SYSTEM_DEFAULT = systemProxySelector != null\n                ? new ProxySelectorWrapper(systemProxySelector)\n                : NO_PROXY;\n    }\n\n    private static volatile @NotNull ProxySelector defaultProxySelector = SYSTEM_DEFAULT;\n    private static volatile @Nullable SimpleAuthenticator defaultAuthenticator = null;\n\n    private static ProxySelector getProxySelector() {\n        if (config().hasProxy()) {\n            Proxy.Type proxyType = config().getProxyType();\n            String host = config().getProxyHost();\n            int port = config().getProxyPort();\n\n            if (proxyType == Proxy.Type.DIRECT || StringUtils.isBlank(host)) {\n                return NO_PROXY;\n            } else if (port < 0 || port > 0xFFFF) {\n                LOG.warning(\"Illegal proxy port: \" + port);\n                return NO_PROXY;\n            } else {\n                return new ProxySelectorWrapper(new SimpleProxySelector(new Proxy(proxyType, new InetSocketAddress(host, port))));\n            }\n        } else {\n            return ProxyManager.SYSTEM_DEFAULT;\n        }\n    }\n\n    private static SimpleAuthenticator getAuthenticator() {\n        if (config().hasProxy() && config().hasProxyAuth()) {\n            String username = config().getProxyUser();\n            String password = config().getProxyPass();\n\n            if (username != null || password != null)\n                return new SimpleAuthenticator(\n                        Objects.requireNonNullElse(username, \"\"),\n                        Objects.requireNonNullElse(password, \"\").toCharArray()\n                );\n            else\n                return null;\n        } else\n            return null;\n    }\n\n    static void init() {\n        ProxySelector.setDefault(new ProxySelector() {\n            @Override\n            public List<Proxy> select(URI uri) {\n                return defaultProxySelector.select(uri);\n            }\n\n            @Override\n            public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {\n                defaultProxySelector.connectFailed(uri, sa, ioe);\n            }\n        });\n        Authenticator.setDefault(new Authenticator() {\n            @Override\n            protected PasswordAuthentication getPasswordAuthentication() {\n                var defaultAuthenticator = ProxyManager.defaultAuthenticator;\n                return defaultAuthenticator != null ? defaultAuthenticator.getPasswordAuthentication() : null;\n            }\n        });\n\n        defaultProxySelector = getProxySelector();\n        InvalidationListener updateProxySelector = observable -> defaultProxySelector = getProxySelector();\n        config().proxyTypeProperty().addListener(updateProxySelector);\n        config().proxyHostProperty().addListener(updateProxySelector);\n        config().proxyPortProperty().addListener(updateProxySelector);\n        config().hasProxyProperty().addListener(updateProxySelector);\n\n        defaultAuthenticator = getAuthenticator();\n        InvalidationListener updateAuthenticator = observable -> defaultAuthenticator = getAuthenticator();\n        config().hasProxyProperty().addListener(updateAuthenticator);\n        config().hasProxyAuthProperty().addListener(updateAuthenticator);\n        config().proxyUserProperty().addListener(updateAuthenticator);\n        config().proxyPassProperty().addListener(updateAuthenticator);\n\n        FetchTask.notifyInitialized();\n    }\n\n    private static abstract class AbstractProxySelector extends ProxySelector {\n        @Override\n        public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {\n            if (uri == null || sa == null || ioe == null) {\n                throw new IllegalArgumentException(\"Arguments can't be null.\");\n            }\n        }\n    }\n\n    private static final class SimpleProxySelector extends AbstractProxySelector {\n        private final List<Proxy> proxies;\n\n        SimpleProxySelector(Proxy proxy) {\n            this.proxies = List.of(proxy);\n        }\n\n        @Override\n        public List<Proxy> select(URI uri) {\n            if (uri == null)\n                throw new IllegalArgumentException(\"URI can't be null.\");\n            return proxies;\n        }\n\n        @Override\n        public String toString() {\n            return \"SimpleProxySelector\" + proxies;\n        }\n    }\n\n    /// Wraps another ProxySelector to avoid using proxy for loopback addresses.\n    private static final class ProxySelectorWrapper extends AbstractProxySelector {\n        private final ProxySelector source;\n\n        ProxySelectorWrapper(ProxySelector source) {\n            this.source = source;\n        }\n\n        @Override\n        public List<Proxy> select(URI uri) {\n            if (uri == null)\n                throw new IllegalArgumentException(\"URI can't be null.\");\n\n            if (NetworkUtils.isLoopbackAddress(uri))\n                return NO_PROXY.proxies;\n\n            return source.select(uri);\n        }\n    }\n\n    private static final class SimpleAuthenticator extends Authenticator {\n        private final String username;\n        private final char[] password;\n\n        private SimpleAuthenticator(String username, char[] password) {\n            this.username = username;\n            this.password = password;\n        }\n\n        @Override\n        public PasswordAuthentication getPasswordAuthentication() {\n            return getRequestorType() == RequestorType.PROXY ? new PasswordAuthentication(username, password) : null;\n        }\n    }\n\n    private ProxyManager() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/SambaException.java",
    "content": "package org.jackhuang.hmcl.setting;\n\npublic final class SambaException extends RuntimeException {\n    public SambaException() {\n    }\n\n    public SambaException(String message) {\n        super(message);\n    }\n\n    public SambaException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public SambaException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport javafx.beans.binding.Bindings;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.game.HMCLCacheRepository;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.util.CacheRepository;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\n\npublic final class Settings {\n\n    private static Settings instance;\n\n    public static Settings instance() {\n        if (instance == null) {\n            throw new IllegalStateException(\"Settings hasn't been initialized\");\n        }\n        return instance;\n    }\n\n    /**\n     * Should be called from {@link ConfigHolder#init()}.\n     */\n    static void init() {\n        instance = new Settings();\n    }\n\n    private Settings() {\n        DownloadProviders.init();\n        ProxyManager.init();\n        Accounts.init();\n        Profiles.init();\n        AuthlibInjectorServers.init();\n        AnimationUtils.init();\n\n        CacheRepository.setInstance(HMCLCacheRepository.REPOSITORY);\n        HMCLCacheRepository.REPOSITORY.directoryProperty().bind(Bindings.createStringBinding(() -> {\n            if (FileUtils.canCreateDirectory(getCommonDirectory())) {\n                return getCommonDirectory();\n            } else {\n                return getDefaultCommonDirectory();\n            }\n        }, config().commonDirectoryProperty(), config().commonDirTypeProperty()));\n    }\n\n    public static String getDefaultCommonDirectory() {\n        return Metadata.MINECRAFT_DIRECTORY.toString();\n    }\n\n    public String getCommonDirectory() {\n        switch (config().getCommonDirType()) {\n            case DEFAULT:\n                return getDefaultCommonDirectory();\n            case CUSTOM:\n                return config().getCommonDirectory();\n            default:\n                return null;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.scene.Scene;\nimport javafx.scene.paint.Color;\nimport org.glavo.monetfx.Brightness;\nimport org.glavo.monetfx.ColorRole;\nimport org.glavo.monetfx.ColorScheme;\nimport org.jackhuang.hmcl.theme.Theme;\nimport org.jackhuang.hmcl.theme.ThemeColor;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.Base64;\nimport java.util.Locale;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class StyleSheets {\n    private static final int FONT_STYLE_SHEET_INDEX = 0;\n    private static final int THEME_STYLE_SHEET_INDEX = 1;\n    private static final int BRIGHTNESS_SHEET_INDEX = 2;\n\n    private static final ObservableList<String> stylesheets;\n\n    static {\n        String[] array = new String[]{\n                getFontStyleSheet(),\n                getThemeStyleSheet(),\n                getBrightnessStyleSheet(),\n                \"/assets/css/root.css\"\n        };\n        stylesheets = FXCollections.observableList(Arrays.asList(array));\n\n        FontManager.fontProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet()));\n        Themes.colorSchemeProperty().addListener(o -> {\n            stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet());\n            stylesheets.set(BRIGHTNESS_SHEET_INDEX, getBrightnessStyleSheet());\n        });\n    }\n\n    private static String toStyleSheetUri(String styleSheet, String fallback) {\n        if (FXUtils.JAVAFX_MAJOR_VERSION >= 17)\n            // JavaFX 17+ support loading stylesheets from data URIs\n            // https://bugs.openjdk.org/browse/JDK-8267554\n            return \"data:text/css;charset=UTF-8;base64,\" + Base64.getEncoder().encodeToString(styleSheet.getBytes(StandardCharsets.UTF_8));\n        else\n            try {\n                Path temp = Files.createTempFile(\"hmcl\", \".css\");\n                // For JavaFX 17 or earlier, CssParser uses the default charset\n                // https://bugs.openjdk.org/browse/JDK-8279328\n                Files.writeString(temp, styleSheet, Charset.defaultCharset());\n                temp.toFile().deleteOnExit();\n                return temp.toUri().toString();\n            } catch (IOException | NullPointerException e) {\n                LOG.error(\"Unable to create stylesheet, fallback to \" + fallback, e);\n                return fallback;\n            }\n    }\n\n    private static String getFontStyleSheet() {\n        final String defaultCss = \"/assets/css/font.css\";\n        final FontManager.FontReference font = FontManager.getFont();\n\n        if (font == null || \"System\".equals(font.family()))\n            return defaultCss;\n\n        String fontFamily = font.family();\n        String style = font.style();\n        String weight = null;\n        String posture = null;\n\n        if (style != null) {\n            style = style.toLowerCase(Locale.ROOT);\n\n            if (style.contains(\"thin\"))\n                weight = \"100\";\n            else if (style.contains(\"extralight\") || style.contains(\"extra light\") || style.contains(\"ultralight\") | style.contains(\"ultra light\"))\n                weight = \"200\";\n            else if (style.contains(\"medium\"))\n                weight = \"500\";\n            else if (style.contains(\"semibold\") || style.contains(\"semi bold\") || style.contains(\"demibold\") || style.contains(\"demi bold\"))\n                weight = \"600\";\n            else if (style.contains(\"extrabold\") || style.contains(\"extra bold\") || style.contains(\"ultrabold\") || style.contains(\"ultra bold\"))\n                weight = \"800\";\n            else if (style.contains(\"black\") || style.contains(\"heavy\"))\n                weight = \"900\";\n            else if (style.contains(\"light\"))\n                weight = \"lighter\";\n            else if (style.contains(\"bold\"))\n                weight = \"bold\";\n\n            posture = style.contains(\"italic\") || style.contains(\"oblique\") ? \"italic\" : null;\n        }\n\n        StringBuilder builder = new StringBuilder();\n        builder.append(\".root {\");\n        builder.append(\"-fx-font-family:\\\"\").append(fontFamily).append(\"\\\";\");\n\n        if (weight != null)\n            builder.append(\"-fx-font-weight:\").append(weight).append(\";\");\n\n        if (posture != null)\n            builder.append(\"-fx-font-style:\").append(posture).append(\";\");\n\n        builder.append('}');\n\n        return toStyleSheetUri(builder.toString(), defaultCss);\n    }\n\n    private static String getBrightnessStyleSheet() {\n        return Themes.getColorScheme().getBrightness() == Brightness.LIGHT\n                ? \"/assets/css/brightness-light.css\"\n                : \"/assets/css/brightness-dark.css\";\n    }\n\n    private static void addColor(StringBuilder builder, String name, Color color) {\n        builder.append(\"  \").append(name)\n                .append(\": \").append(ThemeColor.getColorDisplayName(color)).append(\";\\n\");\n    }\n\n    private static void addColor(StringBuilder builder, String name, Color color, double opacity) {\n        builder.append(\"  \").append(name)\n                .append(\": \").append(ThemeColor.getColorDisplayNameWithOpacity(color, opacity)).append(\";\\n\");\n    }\n\n    private static void addColor(StringBuilder builder, ColorScheme scheme, ColorRole role, double opacity) {\n        builder.append(\"  \").append(role.getVariableName()).append(\"-transparent-%02d\".formatted((int) (100 * opacity)))\n                .append(\": \").append(ThemeColor.getColorDisplayNameWithOpacity(scheme.getColor(role), opacity))\n                .append(\";\\n\");\n    }\n\n    private static String getThemeStyleSheet() {\n        final String blueCss = \"/assets/css/blue.css\";\n\n        if (Theme.DEFAULT.equals(Themes.getTheme()))\n            return blueCss;\n\n        ColorScheme scheme = Themes.getColorScheme();\n\n        StringBuilder builder = new StringBuilder();\n        builder.append(\"* {\\n\");\n        for (ColorRole colorRole : ColorRole.ALL) {\n            addColor(builder, colorRole.getVariableName(), scheme.getColor(colorRole));\n        }\n\n        addColor(builder, \"-monet-primary-seed\", scheme.getPrimaryColorSeed());\n\n        addColor(builder, scheme, ColorRole.PRIMARY, 0.5);\n        addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.5);\n        addColor(builder, scheme, ColorRole.SURFACE, 0.5);\n        addColor(builder, scheme, ColorRole.SURFACE, 0.8);\n        addColor(builder, scheme, ColorRole.ON_SURFACE_VARIANT, 0.38);\n        addColor(builder, scheme, ColorRole.SURFACE_CONTAINER_LOW, 0.8);\n        addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.8);\n        addColor(builder, scheme, ColorRole.INVERSE_SURFACE, 0.8);\n\n        builder.append(\"}\\n\");\n        return toStyleSheetUri(builder.toString(), blueCss);\n    }\n\n    public static void init(Scene scene) {\n        Bindings.bindContent(scene.getStylesheets(), stylesheets);\n    }\n\n    private StyleSheets() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport javafx.scene.image.Image;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic enum VersionIconType {\n    DEFAULT(\"/assets/img/grass.png\"),\n\n    GRASS(\"/assets/img/grass.png\"),\n    CHEST(\"/assets/img/chest.png\"),\n    CHICKEN(\"/assets/img/chicken.png\"),\n    COMMAND(\"/assets/img/command.png\"),\n    OPTIFINE(\"/assets/img/optifine.png\"),\n    CRAFT_TABLE(\"/assets/img/craft_table.png\"),\n    FABRIC(\"/assets/img/fabric.png\"),\n    FORGE(\"/assets/img/forge.png\"),\n    NEO_FORGE(\"/assets/img/neoforge.png\"),\n    FURNACE(\"/assets/img/furnace.png\"),\n    QUILT(\"/assets/img/quilt.png\"),\n    APRIL_FOOLS(\"/assets/img/april_fools.png\"),\n    CLEANROOM(\"/assets/img/cleanroom.png\"),\n    LEGACY_FABRIC(\"/assets/img/legacyfabric.png\")\n    ;\n\n    // Please append new items at last\n\n    public static VersionIconType getIconType(ModLoaderType modLoaderType) {\n        return switch (modLoaderType) {\n            case FORGE -> VersionIconType.FORGE;\n            case NEO_FORGED -> VersionIconType.NEO_FORGE;\n            case FABRIC -> VersionIconType.FABRIC;\n            case QUILT -> VersionIconType.QUILT;\n            case LITE_LOADER -> VersionIconType.CHICKEN;\n            case CLEANROOM -> VersionIconType.CLEANROOM;\n            default -> VersionIconType.COMMAND;\n        };\n    }\n\n    private final String resourceUrl;\n\n    VersionIconType(String resourceUrl) {\n        this.resourceUrl = resourceUrl;\n    }\n\n    public Image getIcon() {\n        return FXUtils.newBuiltinImage(resourceUrl);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\nimport javafx.beans.property.*;\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.java.JavaManager;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.javafx.ObservableHelper;\nimport org.jackhuang.hmcl.util.javafx.PropertyUtils;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.platform.SystemInfo;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huangyuhui\n */\n@JsonAdapter(VersionSetting.Serializer.class)\npublic final class VersionSetting implements Cloneable, Observable {\n\n    private static final int SUGGESTED_MEMORY;\n\n    static {\n        double totalMemoryMB = MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize());\n        SUGGESTED_MEMORY = totalMemoryMB >= 32768\n                ? 8192\n                : Integer.max((int) (Math.round(totalMemoryMB / 4.0 / 128.0) * 128), 256);\n    }\n\n    private final transient ObservableHelper helper = new ObservableHelper(this);\n\n    public VersionSetting() {\n        PropertyUtils.attachListener(this, helper);\n    }\n\n    private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, \"usesGlobal\", true);\n\n    public BooleanProperty usesGlobalProperty() {\n        return usesGlobalProperty;\n    }\n\n    /**\n     * HMCL Version Settings have been divided into 2 parts.\n     * 1. Global settings.\n     * 2. Version settings.\n     * If a version claims that it uses global settings, its version setting will be disabled.\n     * <p>\n     * Defaults false because if one version uses global first, custom version file will not be generated.\n     */\n    public boolean isUsesGlobal() {\n        return usesGlobalProperty.get();\n    }\n\n    public void setUsesGlobal(boolean usesGlobal) {\n        usesGlobalProperty.set(usesGlobal);\n    }\n\n    // java\n\n    private final ObjectProperty<JavaVersionType> javaVersionTypeProperty = new SimpleObjectProperty<>(this, \"javaVersionType\", JavaVersionType.AUTO);\n\n    public ObjectProperty<JavaVersionType> javaVersionTypeProperty() {\n        return javaVersionTypeProperty;\n    }\n\n    public JavaVersionType getJavaVersionType() {\n        return javaVersionTypeProperty.get();\n    }\n\n    public void setJavaVersionType(JavaVersionType javaVersionType) {\n        javaVersionTypeProperty.set(javaVersionType);\n    }\n\n    private final StringProperty javaVersionProperty = new SimpleStringProperty(this, \"javaVersion\", \"\");\n\n    public StringProperty javaVersionProperty() {\n        return javaVersionProperty;\n    }\n\n    public String getJavaVersion() {\n        return javaVersionProperty.get();\n    }\n\n    public void setJavaVersion(String java) {\n        javaVersionProperty.set(java);\n    }\n\n    public void setUsesCustomJavaDir() {\n        setJavaVersionType(JavaVersionType.CUSTOM);\n        setJavaVersion(\"\");\n        setDefaultJavaPath(null);\n    }\n\n    public void setJavaAutoSelected() {\n        setJavaVersionType(JavaVersionType.AUTO);\n        setJavaVersion(\"\");\n        setDefaultJavaPath(null);\n    }\n\n    private final StringProperty defaultJavaPathProperty = new SimpleStringProperty(this, \"defaultJavaPath\", \"\");\n\n    /**\n     * Path to Java executable, or null if user customizes java directory.\n     * It's used to determine which JRE to use when multiple JREs match the selected Java version.\n     */\n    public String getDefaultJavaPath() {\n        return defaultJavaPathProperty.get();\n    }\n\n    public StringProperty defaultJavaPathPropertyProperty() {\n        return defaultJavaPathProperty;\n    }\n\n    public void setDefaultJavaPath(String defaultJavaPath) {\n        defaultJavaPathProperty.set(defaultJavaPath);\n    }\n\n    /**\n     * 0 - .minecraft/versions/&lt;version&gt;/natives/<br/>\n     */\n    private final ObjectProperty<NativesDirectoryType> nativesDirTypeProperty = new SimpleObjectProperty<>(this, \"nativesDirType\", NativesDirectoryType.VERSION_FOLDER);\n\n    public ObjectProperty<NativesDirectoryType> nativesDirTypeProperty() {\n        return nativesDirTypeProperty;\n    }\n\n    public NativesDirectoryType getNativesDirType() {\n        return nativesDirTypeProperty.get();\n    }\n\n    public void setNativesDirType(NativesDirectoryType nativesDirType) {\n        nativesDirTypeProperty.set(nativesDirType);\n    }\n\n    // Path to lwjgl natives directory\n\n    private final StringProperty nativesDirProperty = new SimpleStringProperty(this, \"nativesDirProperty\", \"\");\n\n    public StringProperty nativesDirProperty() {\n        return nativesDirProperty;\n    }\n\n    public String getNativesDir() {\n        return nativesDirProperty.get();\n    }\n\n    public void setNativesDir(String nativesDir) {\n        nativesDirProperty.set(nativesDir);\n    }\n\n    private final StringProperty javaDirProperty = new SimpleStringProperty(this, \"javaDir\", \"\");\n\n    public StringProperty javaDirProperty() {\n        return javaDirProperty;\n    }\n\n    /**\n     * User customized java directory or null if user uses system Java.\n     */\n    public String getJavaDir() {\n        return javaDirProperty.get();\n    }\n\n    public void setJavaDir(String javaDir) {\n        javaDirProperty.set(javaDir);\n    }\n\n    private final StringProperty wrapperProperty = new SimpleStringProperty(this, \"wrapper\", \"\");\n\n    public StringProperty wrapperProperty() {\n        return wrapperProperty;\n    }\n\n    /**\n     * The command to launch java, i.e. optirun.\n     */\n    public String getWrapper() {\n        return wrapperProperty.get();\n    }\n\n    public void setWrapper(String wrapper) {\n        wrapperProperty.set(wrapper);\n    }\n\n    private final StringProperty permSizeProperty = new SimpleStringProperty(this, \"permSize\", \"\");\n\n    public StringProperty permSizeProperty() {\n        return permSizeProperty;\n    }\n\n    /**\n     * The permanent generation size of JVM garbage collection.\n     */\n    public String getPermSize() {\n        return permSizeProperty.get();\n    }\n\n    public void setPermSize(String permSize) {\n        permSizeProperty.set(permSize);\n    }\n\n    private final IntegerProperty maxMemoryProperty = new SimpleIntegerProperty(this, \"maxMemory\", SUGGESTED_MEMORY);\n\n    public IntegerProperty maxMemoryProperty() {\n        return maxMemoryProperty;\n    }\n\n    /**\n     * The maximum memory/MB that JVM can allocate for heap.\n     */\n    public int getMaxMemory() {\n        return maxMemoryProperty.get();\n    }\n\n    public void setMaxMemory(int maxMemory) {\n        maxMemoryProperty.set(maxMemory);\n    }\n\n    /**\n     * The minimum memory that JVM can allocate for heap.\n     */\n    private final ObjectProperty<Integer> minMemoryProperty = new SimpleObjectProperty<>(this, \"minMemory\", null);\n\n    public ObjectProperty<Integer> minMemoryProperty() {\n        return minMemoryProperty;\n    }\n\n    public Integer getMinMemory() {\n        return minMemoryProperty.get();\n    }\n\n    public void setMinMemory(Integer minMemory) {\n        minMemoryProperty.set(minMemory);\n    }\n\n    private final BooleanProperty autoMemory = new SimpleBooleanProperty(this, \"autoMemory\", true);\n\n    public boolean isAutoMemory() {\n        return autoMemory.get();\n    }\n\n    public BooleanProperty autoMemoryProperty() {\n        return autoMemory;\n    }\n\n    public void setAutoMemory(boolean autoMemory) {\n        this.autoMemory.set(autoMemory);\n    }\n\n    private final StringProperty preLaunchCommandProperty = new SimpleStringProperty(this, \"precalledCommand\", \"\");\n\n    public StringProperty preLaunchCommandProperty() {\n        return preLaunchCommandProperty;\n    }\n\n    /**\n     * The command that will be executed before launching the Minecraft.\n     * Operating system relevant.\n     */\n    public String getPreLaunchCommand() {\n        return preLaunchCommandProperty.get();\n    }\n\n    public void setPreLaunchCommand(String preLaunchCommand) {\n        preLaunchCommandProperty.set(preLaunchCommand);\n    }\n\n    private final StringProperty postExitCommand = new SimpleStringProperty(this, \"postExitCommand\", \"\");\n\n    public StringProperty postExitCommandProperty() {\n        return postExitCommand;\n    }\n\n    /**\n     * The command that will be executed after game exits.\n     * Operating system relevant.\n     */\n    public String getPostExitCommand() {\n        return postExitCommand.get();\n    }\n\n    public void setPostExitCommand(String postExitCommand) {\n        this.postExitCommand.set(postExitCommand);\n    }\n\n    // options\n\n    private final StringProperty javaArgsProperty = new SimpleStringProperty(this, \"javaArgs\", \"\");\n\n    public StringProperty javaArgsProperty() {\n        return javaArgsProperty;\n    }\n\n    /**\n     * The user customized arguments passed to JVM.\n     */\n    public String getJavaArgs() {\n        return javaArgsProperty.get();\n    }\n\n    public void setJavaArgs(String javaArgs) {\n        javaArgsProperty.set(javaArgs);\n    }\n\n    private final StringProperty minecraftArgsProperty = new SimpleStringProperty(this, \"minecraftArgs\", \"\");\n\n    public StringProperty minecraftArgsProperty() {\n        return minecraftArgsProperty;\n    }\n\n    /**\n     * The user customized arguments passed to Minecraft.\n     */\n    public String getMinecraftArgs() {\n        return minecraftArgsProperty.get();\n    }\n\n    public void setMinecraftArgs(String minecraftArgs) {\n        minecraftArgsProperty.set(minecraftArgs);\n    }\n\n    private final StringProperty environmentVariablesProperty = new SimpleStringProperty(this, \"environmentVariables\", \"\");\n\n    public StringProperty environmentVariablesProperty() {\n        return environmentVariablesProperty;\n    }\n\n    public String getEnvironmentVariables() {\n        return environmentVariablesProperty.get();\n    }\n\n    public void setEnvironmentVariables(String env) {\n        environmentVariablesProperty.set(env);\n    }\n\n    private final BooleanProperty noJVMArgsProperty = new SimpleBooleanProperty(this, \"noJVMArgs\", false);\n\n    public BooleanProperty noJVMArgsProperty() {\n        return noJVMArgsProperty;\n    }\n\n    /**\n     * True if disallow HMCL use default JVM arguments.\n     */\n    public boolean isNoJVMArgs() {\n        return noJVMArgsProperty.get();\n    }\n\n    public void setNoJVMArgs(boolean noJVMArgs) {\n        noJVMArgsProperty.set(noJVMArgs);\n    }\n\n    private final BooleanProperty noOptimizingJVMArgsProperty = new SimpleBooleanProperty(this, \"noOptimizingJVMArgs\", false);\n\n    public BooleanProperty noOptimizingJVMArgsProperty() {\n        return noOptimizingJVMArgsProperty;\n    }\n\n    public boolean isNoOptimizingJVMArgs() {\n        return noOptimizingJVMArgsProperty.get();\n    }\n\n    public void setNoOptimizingJVMArgs(boolean noOptimizingJVMArgs) {\n        noOptimizingJVMArgsProperty.set(noOptimizingJVMArgs);\n    }\n\n    private final BooleanProperty notCheckJVMProperty = new SimpleBooleanProperty(this, \"notCheckJVM\", false);\n\n    public BooleanProperty notCheckJVMProperty() {\n        return notCheckJVMProperty;\n    }\n\n    /**\n     * True if HMCL does not check JVM validity.\n     */\n    public boolean isNotCheckJVM() {\n        return notCheckJVMProperty.get();\n    }\n\n    public void setNotCheckJVM(boolean notCheckJVM) {\n        notCheckJVMProperty.set(notCheckJVM);\n    }\n\n    private final BooleanProperty notCheckGameProperty = new SimpleBooleanProperty(this, \"notCheckGame\", false);\n\n    public BooleanProperty notCheckGameProperty() {\n        return notCheckGameProperty;\n    }\n\n    /**\n     * True if HMCL does not check game's completeness.\n     */\n    public boolean isNotCheckGame() {\n        return notCheckGameProperty.get();\n    }\n\n    public void setNotCheckGame(boolean notCheckGame) {\n        notCheckGameProperty.set(notCheckGame);\n    }\n\n    private final BooleanProperty notPatchNativesProperty = new SimpleBooleanProperty(this, \"notPatchNatives\", false);\n\n    public BooleanProperty notPatchNativesProperty() {\n        return notPatchNativesProperty;\n    }\n\n    public boolean isNotPatchNatives() {\n        return notPatchNativesProperty.get();\n    }\n\n    public void setNotPatchNatives(boolean notPatchNatives) {\n        notPatchNativesProperty.set(notPatchNatives);\n    }\n\n    private final BooleanProperty showLogsProperty = new SimpleBooleanProperty(this, \"showLogs\", false);\n\n    public BooleanProperty showLogsProperty() {\n        return showLogsProperty;\n    }\n\n    /**\n     * True if show the logs after game launched.\n     */\n    public boolean isShowLogs() {\n        return showLogsProperty.get();\n    }\n\n    public void setShowLogs(boolean showLogs) {\n        showLogsProperty.set(showLogs);\n    }\n\n    private final BooleanProperty enableDebugLogOutputProperty = new SimpleBooleanProperty(this, \"enableDebugLogOutput\", false);\n\n    public BooleanProperty enableDebugLogOutputProperty() {\n        return enableDebugLogOutputProperty;\n    }\n\n    public boolean isEnableDebugLogOutput() {\n        return enableDebugLogOutputProperty.get();\n    }\n\n    public void setEnableDebugLogOutput(boolean u) {\n        this.enableDebugLogOutputProperty.set(u);\n    }\n\n    // Minecraft settings.\n\n    private final StringProperty serverIpProperty = new SimpleStringProperty(this, \"serverIp\", \"\");\n\n    public StringProperty serverIpProperty() {\n        return serverIpProperty;\n    }\n\n    /**\n     * The server ip that will be entered after Minecraft successfully loaded ly.\n     * <p>\n     * Format: ip:port or without port.\n     */\n    public String getServerIp() {\n        return serverIpProperty.get();\n    }\n\n    public void setServerIp(String serverIp) {\n        serverIpProperty.set(serverIp);\n    }\n\n\n    private final BooleanProperty fullscreenProperty = new SimpleBooleanProperty(this, \"fullscreen\", false);\n\n    public BooleanProperty fullscreenProperty() {\n        return fullscreenProperty;\n    }\n\n    /**\n     * True if Minecraft started in fullscreen mode.\n     */\n    public boolean isFullscreen() {\n        return fullscreenProperty.get();\n    }\n\n    public void setFullscreen(boolean fullscreen) {\n        fullscreenProperty.set(fullscreen);\n    }\n\n    private final IntegerProperty widthProperty = new SimpleIntegerProperty(this, \"width\", 854);\n\n    public IntegerProperty widthProperty() {\n        return widthProperty;\n    }\n\n    /**\n     * The width of Minecraft window, defaults 800.\n     * <p>\n     * The field saves int value.\n     * String type prevents unexpected value from JsonParseException.\n     * We can only reset this field instead of recreating the whole setting file.\n     */\n    public int getWidth() {\n        return widthProperty.get();\n    }\n\n    public void setWidth(int width) {\n        widthProperty.set(width);\n    }\n\n    private final IntegerProperty heightProperty = new SimpleIntegerProperty(this, \"height\", 480);\n\n    public IntegerProperty heightProperty() {\n        return heightProperty;\n    }\n\n    /**\n     * The height of Minecraft window, defaults 480.\n     * <p>\n     * The field saves int value.\n     * String type prevents unexpected value from JsonParseException.\n     * We can only reset this field instead of recreating the whole setting file.\n     */\n    public int getHeight() {\n        return heightProperty.get();\n    }\n\n    public void setHeight(int height) {\n        heightProperty.set(height);\n    }\n\n    /**\n     * 0 - .minecraft<br/>\n     * 1 - .minecraft/versions/&lt;version&gt;/<br/>\n     */\n    private final ObjectProperty<GameDirectoryType> gameDirTypeProperty = new SimpleObjectProperty<>(this, \"gameDirType\", GameDirectoryType.ROOT_FOLDER);\n\n    public ObjectProperty<GameDirectoryType> gameDirTypeProperty() {\n        return gameDirTypeProperty;\n    }\n\n    public GameDirectoryType getGameDirType() {\n        return gameDirTypeProperty.get();\n    }\n\n    public void setGameDirType(GameDirectoryType gameDirType) {\n        gameDirTypeProperty.set(gameDirType);\n    }\n\n    /**\n     * Your custom gameDir\n     */\n    private final StringProperty gameDirProperty = new SimpleStringProperty(this, \"gameDir\", \"\");\n\n    public StringProperty gameDirProperty() {\n        return gameDirProperty;\n    }\n\n    public String getGameDir() {\n        return gameDirProperty.get();\n    }\n\n    public void setGameDir(String gameDir) {\n        gameDirProperty.set(gameDir);\n    }\n\n    private final ObjectProperty<ProcessPriority> processPriorityProperty = new SimpleObjectProperty<>(this, \"processPriority\", ProcessPriority.NORMAL);\n\n    public ObjectProperty<ProcessPriority> processPriorityProperty() {\n        return processPriorityProperty;\n    }\n\n    public ProcessPriority getProcessPriority() {\n        return processPriorityProperty.get();\n    }\n\n    public void setProcessPriority(ProcessPriority processPriority) {\n        processPriorityProperty.set(processPriority);\n    }\n\n    private final ObjectProperty<Renderer> rendererProperty = new SimpleObjectProperty<>(this, \"renderer\", Renderer.DEFAULT);\n\n    public Renderer getRenderer() {\n        return rendererProperty.get();\n    }\n\n    public ObjectProperty<Renderer> rendererProperty() {\n        return rendererProperty;\n    }\n\n    public void setRenderer(Renderer renderer) {\n        this.rendererProperty.set(renderer);\n    }\n\n    private final BooleanProperty useNativeGLFW = new SimpleBooleanProperty(this, \"nativeGLFW\", false);\n\n    public boolean isUseNativeGLFW() {\n        return useNativeGLFW.get();\n    }\n\n    public BooleanProperty useNativeGLFWProperty() {\n        return useNativeGLFW;\n    }\n\n    public void setUseNativeGLFW(boolean useNativeGLFW) {\n        this.useNativeGLFW.set(useNativeGLFW);\n    }\n\n    private final BooleanProperty useNativeOpenAL = new SimpleBooleanProperty(this, \"nativeOpenAL\", false);\n\n    public boolean isUseNativeOpenAL() {\n        return useNativeOpenAL.get();\n    }\n\n    public BooleanProperty useNativeOpenALProperty() {\n        return useNativeOpenAL;\n    }\n\n    public void setUseNativeOpenAL(boolean useNativeOpenAL) {\n        this.useNativeOpenAL.set(useNativeOpenAL);\n    }\n\n    private final ObjectProperty<VersionIconType> versionIcon = new SimpleObjectProperty<>(this, \"versionIcon\", VersionIconType.DEFAULT);\n\n    public VersionIconType getVersionIcon() {\n        return versionIcon.get();\n    }\n\n    public ObjectProperty<VersionIconType> versionIconProperty() {\n        return versionIcon;\n    }\n\n    public void setVersionIcon(VersionIconType versionIcon) {\n        this.versionIcon.set(versionIcon);\n    }\n\n    // launcher settings\n\n    /**\n     * 0 - Close the launcher when the game starts.<br/>\n     * 1 - Hide the launcher when the game starts.<br/>\n     * 2 - Keep the launcher open.<br/>\n     */\n    private final ObjectProperty<LauncherVisibility> launcherVisibilityProperty = new SimpleObjectProperty<>(this, \"launcherVisibility\", LauncherVisibility.HIDE);\n\n    public ObjectProperty<LauncherVisibility> launcherVisibilityProperty() {\n        return launcherVisibilityProperty;\n    }\n\n    public LauncherVisibility getLauncherVisibility() {\n        return launcherVisibilityProperty.get();\n    }\n\n    public void setLauncherVisibility(LauncherVisibility launcherVisibility) {\n        launcherVisibilityProperty.set(launcherVisibility);\n    }\n\n    public JavaRuntime getJava(GameVersionNumber gameVersion, Version version) throws InterruptedException {\n        switch (getJavaVersionType()) {\n            case DEFAULT:\n                return JavaRuntime.getDefault();\n            case AUTO:\n                return JavaManager.findSuitableJava(gameVersion, version);\n            case CUSTOM:\n                try {\n                    return JavaManager.getJava(Paths.get(getJavaDir()));\n                } catch (IOException | InvalidPathException e) {\n                    return null; // Custom Java not found\n                }\n            case VERSION: {\n                String javaVersion = getJavaVersion();\n                if (StringUtils.isBlank(javaVersion)) {\n                    return JavaManager.findSuitableJava(gameVersion, version);\n                }\n\n                int majorVersion = -1;\n                try {\n                    majorVersion = Integer.parseInt(javaVersion);\n                } catch (NumberFormatException ignored) {\n                }\n\n                if (majorVersion < 0) {\n                    LOG.warning(\"Invalid Java version: \" + javaVersion);\n                    return null;\n                }\n\n                final int finalMajorVersion = majorVersion;\n                Collection<JavaRuntime> allJava = JavaManager.getAllJava().stream()\n                        .filter(it -> it.getParsedVersion() == finalMajorVersion)\n                        .collect(Collectors.toList());\n                return JavaManager.findSuitableJava(allJava, gameVersion, version);\n            }\n            case DETECTED: {\n                String javaVersion = getJavaVersion();\n                if (StringUtils.isBlank(javaVersion)) {\n                    return JavaManager.findSuitableJava(gameVersion, version);\n                }\n\n                try {\n                    String defaultJavaPath = getDefaultJavaPath();\n                    if (StringUtils.isNotBlank(defaultJavaPath)) {\n                        JavaRuntime java = JavaManager.getJava(Paths.get(defaultJavaPath).toRealPath());\n                        if (java != null && java.getVersion().equals(javaVersion)) {\n                            return java;\n                        }\n                    }\n                } catch (IOException | InvalidPathException ignored) {\n                }\n\n                for (JavaRuntime java : JavaManager.getAllJava()) {\n                    if (java.getVersion().equals(javaVersion)) {\n                        return java;\n                    }\n                }\n\n                return null;\n            }\n            default:\n                throw new AssertionError(\"JavaVersionType: \" + getJavaVersionType());\n        }\n    }\n\n    @Override\n    public void addListener(InvalidationListener listener) {\n        helper.addListener(listener);\n    }\n\n    @Override\n    public void removeListener(InvalidationListener listener) {\n        helper.removeListener(listener);\n    }\n\n    @Override\n    public VersionSetting clone() {\n        VersionSetting cloned = new VersionSetting();\n        PropertyUtils.copyProperties(this, cloned);\n        return cloned;\n    }\n\n    public static class Serializer implements JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {\n        @Override\n        public JsonElement serialize(VersionSetting src, Type typeOfSrc, JsonSerializationContext context) {\n            if (src == null) return JsonNull.INSTANCE;\n            JsonObject obj = new JsonObject();\n\n            obj.addProperty(\"usesGlobal\", src.isUsesGlobal());\n            obj.addProperty(\"javaArgs\", src.getJavaArgs());\n            obj.addProperty(\"minecraftArgs\", src.getMinecraftArgs());\n            obj.addProperty(\"environmentVariables\", src.getEnvironmentVariables());\n            obj.addProperty(\"maxMemory\", src.getMaxMemory() <= 0 ? SUGGESTED_MEMORY : src.getMaxMemory());\n            obj.addProperty(\"minMemory\", src.getMinMemory());\n            obj.addProperty(\"autoMemory\", src.isAutoMemory());\n            obj.addProperty(\"permSize\", src.getPermSize());\n            obj.addProperty(\"width\", src.getWidth());\n            obj.addProperty(\"height\", src.getHeight());\n            obj.addProperty(\"javaDir\", src.getJavaDir());\n            obj.addProperty(\"precalledCommand\", src.getPreLaunchCommand());\n            obj.addProperty(\"postExitCommand\", src.getPostExitCommand());\n            obj.addProperty(\"serverIp\", src.getServerIp());\n            obj.addProperty(\"wrapper\", src.getWrapper());\n            obj.addProperty(\"fullscreen\", src.isFullscreen());\n            obj.addProperty(\"noJVMArgs\", src.isNoJVMArgs());\n            obj.addProperty(\"noOptimizingJVMArgs\", src.isNoOptimizingJVMArgs());\n            obj.addProperty(\"notCheckGame\", src.isNotCheckGame());\n            obj.addProperty(\"notCheckJVM\", src.isNotCheckJVM());\n            obj.addProperty(\"notPatchNatives\", src.isNotPatchNatives());\n            obj.addProperty(\"showLogs\", src.isShowLogs());\n            obj.addProperty(\"enableDebugLogOutput\", src.isEnableDebugLogOutput());\n            obj.addProperty(\"gameDir\", src.getGameDir());\n            obj.addProperty(\"launcherVisibility\", src.getLauncherVisibility().ordinal());\n            obj.addProperty(\"processPriority\", src.getProcessPriority().ordinal());\n            obj.addProperty(\"useNativeGLFW\", src.isUseNativeGLFW());\n            obj.addProperty(\"useNativeOpenAL\", src.isUseNativeOpenAL());\n            obj.addProperty(\"gameDirType\", src.getGameDirType().ordinal());\n            obj.addProperty(\"defaultJavaPath\", src.getDefaultJavaPath());\n            obj.addProperty(\"nativesDir\", src.getNativesDir());\n            obj.addProperty(\"nativesDirType\", src.getNativesDirType().ordinal());\n            obj.addProperty(\"versionIcon\", src.getVersionIcon().ordinal());\n\n            obj.addProperty(\"javaVersionType\", src.getJavaVersionType().name());\n            String java;\n            switch (src.getJavaVersionType()) {\n                case DEFAULT:\n                    java = \"Default\";\n                    break;\n                case AUTO:\n                    java = \"Auto\";\n                    break;\n                case CUSTOM:\n                    java = \"Custom\";\n                    break;\n                default:\n                    java = src.getJavaVersion();\n                    break;\n            }\n            obj.addProperty(\"java\", java);\n\n            obj.addProperty(\"renderer\", src.getRenderer().name());\n            if (src.getRenderer() == Renderer.LLVMPIPE)\n                obj.addProperty(\"useSoftwareRenderer\", true);\n\n            return obj;\n        }\n\n        @Override\n        public VersionSetting deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (!(json instanceof JsonObject))\n                return null;\n            JsonObject obj = (JsonObject) json;\n\n            int maxMemoryN = parseJsonPrimitive(Optional.ofNullable(obj.get(\"maxMemory\")).map(JsonElement::getAsJsonPrimitive).orElse(null), SUGGESTED_MEMORY);\n            if (maxMemoryN <= 0) maxMemoryN = SUGGESTED_MEMORY;\n\n            VersionSetting vs = new VersionSetting();\n\n            vs.setUsesGlobal(Optional.ofNullable(obj.get(\"usesGlobal\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setJavaArgs(Optional.ofNullable(obj.get(\"javaArgs\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setMinecraftArgs(Optional.ofNullable(obj.get(\"minecraftArgs\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setEnvironmentVariables(Optional.ofNullable(obj.get(\"environmentVariables\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setMaxMemory(maxMemoryN);\n            vs.setMinMemory(Optional.ofNullable(obj.get(\"minMemory\")).map(JsonElement::getAsInt).orElse(null));\n            vs.setAutoMemory(Optional.ofNullable(obj.get(\"autoMemory\")).map(JsonElement::getAsBoolean).orElse(true));\n            vs.setPermSize(Optional.ofNullable(obj.get(\"permSize\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setWidth(Optional.ofNullable(obj.get(\"width\")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0));\n            vs.setHeight(Optional.ofNullable(obj.get(\"height\")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0));\n            vs.setJavaDir(Optional.ofNullable(obj.get(\"javaDir\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setPreLaunchCommand(Optional.ofNullable(obj.get(\"precalledCommand\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setPostExitCommand(Optional.ofNullable(obj.get(\"postExitCommand\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setServerIp(Optional.ofNullable(obj.get(\"serverIp\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setWrapper(Optional.ofNullable(obj.get(\"wrapper\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setGameDir(Optional.ofNullable(obj.get(\"gameDir\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setNativesDir(Optional.ofNullable(obj.get(\"nativesDir\")).map(JsonElement::getAsString).orElse(\"\"));\n            vs.setFullscreen(Optional.ofNullable(obj.get(\"fullscreen\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setNoJVMArgs(Optional.ofNullable(obj.get(\"noJVMArgs\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setNoOptimizingJVMArgs(Optional.ofNullable(obj.get(\"noOptimizingJVMArgs\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setNotCheckGame(Optional.ofNullable(obj.get(\"notCheckGame\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setNotCheckJVM(Optional.ofNullable(obj.get(\"notCheckJVM\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setNotPatchNatives(Optional.ofNullable(obj.get(\"notPatchNatives\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setShowLogs(Optional.ofNullable(obj.get(\"showLogs\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setEnableDebugLogOutput(Optional.ofNullable(obj.get(\"enableDebugLogOutput\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setLauncherVisibility(parseJsonPrimitive(obj.getAsJsonPrimitive(\"launcherVisibility\"), LauncherVisibility.class, LauncherVisibility.HIDE));\n            vs.setProcessPriority(parseJsonPrimitive(obj.getAsJsonPrimitive(\"processPriority\"), ProcessPriority.class, ProcessPriority.NORMAL));\n            vs.setUseNativeGLFW(Optional.ofNullable(obj.get(\"useNativeGLFW\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setUseNativeOpenAL(Optional.ofNullable(obj.get(\"useNativeOpenAL\")).map(JsonElement::getAsBoolean).orElse(false));\n            vs.setGameDirType(parseJsonPrimitive(obj.getAsJsonPrimitive(\"gameDirType\"), GameDirectoryType.class, GameDirectoryType.ROOT_FOLDER));\n            vs.setDefaultJavaPath(Optional.ofNullable(obj.get(\"defaultJavaPath\")).map(JsonElement::getAsString).orElse(null));\n            vs.setNativesDirType(parseJsonPrimitive(obj.getAsJsonPrimitive(\"nativesDirType\"), NativesDirectoryType.class, NativesDirectoryType.VERSION_FOLDER));\n            vs.setVersionIcon(parseJsonPrimitive(obj.getAsJsonPrimitive(\"versionIcon\"), VersionIconType.class, VersionIconType.DEFAULT));\n\n            if (obj.get(\"javaVersionType\") != null) {\n                JavaVersionType javaVersionType = parseJsonPrimitive(obj.getAsJsonPrimitive(\"javaVersionType\"), JavaVersionType.class, JavaVersionType.AUTO);\n                vs.setJavaVersionType(javaVersionType);\n                vs.setJavaVersion(Optional.ofNullable(obj.get(\"java\")).map(JsonElement::getAsString).orElse(null));\n            } else {\n                String java = Optional.ofNullable(obj.get(\"java\")).map(JsonElement::getAsString).orElse(\"\");\n                switch (java) {\n                    case \"Default\":\n                        vs.setJavaVersionType(JavaVersionType.DEFAULT);\n                        break;\n                    case \"Auto\":\n                        vs.setJavaVersionType(JavaVersionType.AUTO);\n                        break;\n                    case \"Custom\":\n                        vs.setJavaVersionType(JavaVersionType.CUSTOM);\n                        break;\n                    default:\n                        vs.setJavaVersion(java);\n                }\n            }\n\n            vs.setRenderer(Optional.ofNullable(obj.get(\"renderer\")).map(JsonElement::getAsString)\n                    .flatMap(name -> {\n                        try {\n                            return Optional.of(Renderer.valueOf(name.toUpperCase(Locale.ROOT)));\n                        } catch (IllegalArgumentException ignored) {\n                            return Optional.empty();\n                        }\n                    }).orElseGet(() -> {\n                        boolean useSoftwareRenderer = Optional.ofNullable(obj.get(\"useSoftwareRenderer\")).map(JsonElement::getAsBoolean).orElse(false);\n                        return useSoftwareRenderer ? Renderer.LLVMPIPE : Renderer.DEFAULT;\n                    }));\n\n            return vs;\n        }\n\n        private int parseJsonPrimitive(JsonPrimitive primitive) {\n            return parseJsonPrimitive(primitive, 0);\n        }\n\n        private int parseJsonPrimitive(JsonPrimitive primitive, int defaultValue) {\n            if (primitive == null)\n                return defaultValue;\n            else if (primitive.isNumber())\n                return primitive.getAsInt();\n            else\n                return Lang.parseInt(primitive.getAsString(), defaultValue);\n        }\n\n        private <E extends Enum<E>> E parseJsonPrimitive(JsonPrimitive primitive, Class<E> clazz, E defaultValue) {\n            if (primitive == null)\n                return defaultValue;\n            else {\n                E[] enumConstants = clazz.getEnumConstants();\n                if (primitive.isNumber()) {\n                    int index = primitive.getAsInt();\n                    return index >= 0 && index < enumConstants.length ? enumConstants[index] : defaultValue;\n                } else {\n                    String name = primitive.getAsString();\n                    for (E enumConstant : enumConstants) {\n                        if (enumConstant.name().equalsIgnoreCase(name)) {\n                            return enumConstant;\n                        }\n                    }\n                    return defaultValue;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/TerracottaBundle.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta;\n\nimport kala.compress.archivers.tar.TarArchiveEntry;\nimport org.jackhuang.hmcl.download.ArtifactMalformedException;\nimport org.jackhuang.hmcl.task.FetchTask;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.terracotta.provider.AbstractTerracottaProvider;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.io.ChecksumMismatchException;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.logging.Logger;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.tree.TarFileTree;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.URI;\nimport java.net.http.HttpResponse;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.DigestInputStream;\nimport java.security.DigestOutputStream;\nimport java.security.MessageDigest;\nimport java.util.HexFormat;\nimport java.util.List;\nimport java.util.Map;\n\npublic final class TerracottaBundle {\n    private final Path root;\n\n    private final List<URI> links;\n\n    private final FileDownloadTask.IntegrityCheck hash;\n\n    private final Map<String, FileDownloadTask.IntegrityCheck> files;\n\n    public TerracottaBundle(Path root, List<URI> links, FileDownloadTask.IntegrityCheck hash, Map<String, FileDownloadTask.IntegrityCheck> files) {\n        this.root = root;\n        this.links = links;\n        this.hash = hash;\n        this.files = files;\n    }\n\n    public Task<Path> download(AbstractTerracottaProvider.DownloadContext context) {\n        return Task.supplyAsync(() -> Files.createTempFile(\"terracotta-\", \".tar.gz\"))\n                .thenComposeAsync(Schedulers.javafx(), pkg -> {\n                    FileDownloadTask download = new FileDownloadTask(links, pkg, hash) {\n                        @Override\n                        protected Context getContext(HttpResponse<?> response, boolean checkETag, String bmclapiHash) throws IOException {\n                            FetchTask.Context delegate = super.getContext(response, checkETag, bmclapiHash);\n                            return new Context() {\n                                @Override\n                                public void withResult(boolean success) {\n                                    delegate.withResult(success);\n                                }\n\n                                @Override\n                                public void write(byte[] buffer, int offset, int len) throws IOException {\n                                    context.checkCancellation();\n                                    delegate.write(buffer, offset, len);\n                                }\n\n                                @Override\n                                public void close() throws IOException {\n                                    if (isSuccess()) {\n                                        context.checkCancellation();\n                                    }\n\n                                    delegate.close();\n                                }\n                            };\n                        }\n                    };\n\n                    context.bindProgress(download.progressProperty());\n                    return download.thenSupplyAsync(() -> pkg);\n                });\n    }\n\n    public Task<?> install(Path pkg) {\n        return Task.runAsync(() -> {\n            Files.createDirectories(root);\n            FileUtils.cleanDirectory(root);\n\n            try (TarFileTree tree = TarFileTree.open(pkg)) {\n                for (Map.Entry<String, FileDownloadTask.IntegrityCheck> entry : files.entrySet()) {\n                    String file = entry.getKey();\n                    FileDownloadTask.IntegrityCheck check = entry.getValue();\n\n                    Path path = root.resolve(file);\n                    TarArchiveEntry archive = tree.getEntry(\"/\" + file);\n                    if (archive == null) {\n                        throw new ArtifactMalformedException(String.format(\"Expecting %s file in terracotta bundle.\", file));\n                    }\n\n                    MessageDigest digest = DigestUtils.getDigest(check.getAlgorithm());\n                    try (\n                            InputStream is = tree.getInputStream(archive);\n                            OutputStream os = new DigestOutputStream(Files.newOutputStream(path), digest)\n                    ) {\n                        is.transferTo(os);\n                    }\n\n                    String hash = HexFormat.of().formatHex(digest.digest());\n                    if (!check.getChecksum().equalsIgnoreCase(hash)) {\n                        throw new ChecksumMismatchException(check.getAlgorithm(), check.getChecksum(), hash);\n                    }\n\n                    switch (OperatingSystem.CURRENT_OS) {\n                        case LINUX, MACOS, FREEBSD -> Files.setPosixFilePermissions(path, FileUtils.parsePosixFilePermission(archive.getMode()));\n                    }\n                }\n            }\n        }).whenComplete(exception -> {\n            if (exception != null) {\n                FileUtils.deleteDirectory(root);\n            }\n        });\n    }\n\n    public Path locate(String file) {\n        FileDownloadTask.IntegrityCheck check = files.get(file);\n        if (check == null) {\n            throw new AssertionError(String.format(\"Expecting %s file in terracotta bundle.\", file));\n        }\n        return root.resolve(file).toAbsolutePath();\n    }\n\n    public AbstractTerracottaProvider.Status status() throws IOException {\n        if (Files.exists(root) && isLocalBundleValid()) {\n            return AbstractTerracottaProvider.Status.READY;\n        }\n\n        try {\n            if (TerracottaMetadata.hasLegacyVersionFiles()) {\n                return AbstractTerracottaProvider.Status.LEGACY_VERSION;\n            }\n        } catch (IOException e) {\n            Logger.LOG.warning(\"Cannot determine whether legacy versions exist.\", e);\n        }\n        return AbstractTerracottaProvider.Status.NOT_EXIST;\n    }\n\n    private boolean isLocalBundleValid() throws IOException { // FIXME: Make control flow clearer.\n        long total = 0;\n        byte[] buffer = new byte[8192];\n\n        for (Map.Entry<String, FileDownloadTask.IntegrityCheck> entry : files.entrySet()) {\n            Path path = root.resolve(entry.getKey());\n            FileDownloadTask.IntegrityCheck check = entry.getValue();\n            if (!Files.isReadable(path)) {\n                return false;\n            }\n\n            MessageDigest digest = DigestUtils.getDigest(check.getAlgorithm());\n            try (InputStream is = new DigestInputStream(Files.newInputStream(path), digest)) {\n                int n;\n                while ((n = is.read(buffer)) >= 0) {\n                    total += n;\n                    if (total >= 50 * 1024 * 1024) { // >=50MB\n                        return false;\n                    }\n                }\n            }\n            if (!HexFormat.of().formatHex(digest.digest()).equalsIgnoreCase(check.getChecksum())) {\n                return false;\n            }\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/TerracottaManager.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta;\n\nimport com.google.gson.JsonObject;\nimport javafx.application.Platform;\nimport javafx.beans.property.ReadOnlyDoubleWrapper;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.task.DownloadException;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.terracotta.provider.AbstractTerracottaProvider;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.FXThread;\nimport org.jackhuang.hmcl.util.InvocationDispatcher;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.HttpRequest;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.concurrent.locks.LockSupport;\n\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class TerracottaManager {\n    private TerracottaManager() {\n    }\n\n    private static final AtomicReference<TerracottaState> STATE_V = new AtomicReference<>(TerracottaState.Bootstrap.INSTANCE);\n    private static final ReadOnlyObjectWrapper<TerracottaState> STATE = new ReadOnlyObjectWrapper<>(STATE_V.getPlain());\n    private static final InvocationDispatcher<TerracottaState> STATE_D = InvocationDispatcher.runOn(Platform::runLater, STATE::set);\n\n    static {\n        Schedulers.io().execute(() -> {\n            try {\n                if (TerracottaMetadata.PROVIDER == null)\n                    throw new IOException(\"Unsupported platform: \" + org.jackhuang.hmcl.util.platform.Platform.CURRENT_PLATFORM);\n\n                switch (TerracottaMetadata.PROVIDER.status()) {\n                    case NOT_EXIST -> setState(new TerracottaState.Uninitialized(false));\n                    case LEGACY_VERSION -> setState(new TerracottaState.Uninitialized(true));\n                    case READY -> launch(setState(new TerracottaState.Launching()), false);\n                }\n            } catch (Exception e) {\n                LOG.warning(\"Cannot initialize Terracotta.\", e);\n                compareAndSet(TerracottaState.Bootstrap.INSTANCE, new TerracottaState.Fatal(TerracottaState.Fatal.Type.UNKNOWN));\n            }\n        });\n    }\n\n    public static ReadOnlyObjectProperty<TerracottaState> stateProperty() {\n        return STATE.getReadOnlyProperty();\n    }\n\n    private static final Thread DAEMON = Lang.thread(TerracottaManager::runBackground, \"Terracotta Background Daemon\", true);\n\n    @FXThread // Written in FXThread, read-only on background daemon\n    private static volatile boolean daemonRunning = false;\n\n    private static void runBackground() {\n        final long ACTIVE = TimeUnit.MILLISECONDS.toNanos(500);\n        final long BACKGROUND = TimeUnit.SECONDS.toMillis(15);\n\n        while (true) {\n            if (daemonRunning) {\n                LockSupport.parkNanos(ACTIVE);\n            } else {\n                long deadline = System.currentTimeMillis() + BACKGROUND;\n                do {\n                    LockSupport.parkUntil(deadline);\n                } while (!daemonRunning && System.currentTimeMillis() < deadline - 100);\n            }\n\n            if (!(STATE_V.get() instanceof TerracottaState.PortSpecific state)) {\n                continue;\n            }\n            int port = state.port;\n            int index = state instanceof TerracottaState.Ready ready ? ready.index : Integer.MIN_VALUE;\n\n            TerracottaState next;\n            try {\n                TerracottaState.Ready object = HttpRequest.GET(String.format(\"http://127.0.0.1:%d/state\", port))\n                        .retry(5)\n                        .getJson(TerracottaState.Ready.class);\n                if (object.index <= index) {\n                    continue;\n                }\n                object.port = port;\n                next = object;\n            } catch (Exception e) {\n                LOG.warning(\"Cannot fetch state from Terracotta.\", e);\n                next = new TerracottaState.Fatal(TerracottaState.Fatal.Type.TERRACOTTA);\n            }\n\n            compareAndSet(state, next);\n        }\n    }\n\n    @FXThread\n    public static void switchDaemon(boolean active) {\n        FXUtils.checkFxUserThread();\n\n        boolean dr = daemonRunning;\n        if (dr != active) {\n            daemonRunning = active;\n            if (active) {\n                LockSupport.unpark(DAEMON);\n            }\n        }\n    }\n\n    private static AbstractTerracottaProvider getProvider() {\n        AbstractTerracottaProvider provider = TerracottaMetadata.PROVIDER;\n        if (provider == null) {\n            throw new AssertionError(\"Terracotta Provider must NOT be null.\");\n        }\n        return provider;\n    }\n\n    public static boolean isInvalidBundle(Path file) {\n        return !FileUtils.getName(file).equalsIgnoreCase(TerracottaMetadata.PACKAGE_NAME);\n    }\n\n    @FXThread\n    public static TerracottaState.Preparing download() {\n        FXUtils.checkFxUserThread();\n\n        TerracottaState state = STATE_V.get();\n        if (!(state instanceof TerracottaState.Uninitialized || state instanceof TerracottaState.Fatal && ((TerracottaState.Fatal) state).isRecoverable())\n        ) {\n            return null;\n        }\n\n        TerracottaState.Preparing preparing = new TerracottaState.Preparing(new ReadOnlyDoubleWrapper(-1), true);\n\n        Task.composeAsync(() -> getProvider().download(preparing))\n                .thenComposeAsync(pkg -> {\n                    if (!preparing.requestInstallFence()) {\n                        return null;\n                    }\n\n                    return getProvider().install(pkg).thenRunAsync(() -> {\n                        TerracottaState.Launching launching = new TerracottaState.Launching();\n                        if (compareAndSet(preparing, launching)) {\n                            launch(launching, true);\n                        }\n                    });\n                }).whenComplete(exception -> {\n                    if (exception instanceof CancellationException) {\n                        // no-op\n                    } else if (exception instanceof DownloadException) {\n                        compareAndSet(preparing, new TerracottaState.Fatal(TerracottaState.Fatal.Type.NETWORK));\n                    } else {\n                        compareAndSet(preparing, new TerracottaState.Fatal(TerracottaState.Fatal.Type.INSTALL));\n                    }\n                }).start();\n\n        return compareAndSet(state, preparing) ? preparing : null;\n    }\n\n    @FXThread\n    public static TerracottaState.Preparing install(Path bundle) {\n        FXUtils.checkFxUserThread();\n        if (isInvalidBundle(bundle)) {\n            return null;\n        }\n\n        TerracottaState state = STATE_V.get();\n        TerracottaState.Preparing preparing;\n        if (state instanceof TerracottaState.Preparing previousPreparing && previousPreparing.requestInstallFence()) {\n            preparing = previousPreparing;\n        } else if (state instanceof TerracottaState.Uninitialized || state instanceof TerracottaState.Fatal && ((TerracottaState.Fatal) state).isRecoverable()) {\n            preparing = new TerracottaState.Preparing(new ReadOnlyDoubleWrapper(-1), false);\n        } else {\n            return null;\n        }\n\n        Task.composeAsync(() -> getProvider().install(bundle))\n                .thenRunAsync(() -> {\n                    TerracottaState.Launching launching = new TerracottaState.Launching();\n                    if (compareAndSet(preparing, launching)) {\n                        launch(launching, true);\n                    }\n                })\n                .whenComplete(exception -> {\n                    compareAndSet(preparing, new TerracottaState.Fatal(TerracottaState.Fatal.Type.INSTALL));\n                }).start();\n\n        return state != preparing && compareAndSet(state, preparing) ? preparing : null;\n    }\n\n    @FXThread\n    public static TerracottaState recover() {\n        FXUtils.checkFxUserThread();\n\n        TerracottaState state = STATE_V.get();\n        if (!(state instanceof TerracottaState.Fatal && ((TerracottaState.Fatal) state).isRecoverable())) {\n            return null;\n        }\n\n        try {\n            // FIXME: A temporary limit has been employed in TerracottaBundle#checkExisting, making\n            //        hash check accept 50MB at most. Calling it on JavaFX should be safe.\n            return switch (getProvider().status()) {\n                case NOT_EXIST, LEGACY_VERSION -> download();\n                case READY -> {\n                    TerracottaState.Launching launching = setState(new TerracottaState.Launching());\n                    launch(launching, false);\n                    yield launching;\n                }\n            };\n        } catch (RuntimeException | IOException e) {\n            LOG.warning(\"Cannot determine Terracotta state.\", e);\n            return setState(new TerracottaState.Fatal(TerracottaState.Fatal.Type.UNKNOWN));\n        }\n    }\n\n    private static void launch(TerracottaState.Launching state, boolean removeLegacy) {\n        Task.supplyAsync(() -> {\n            Path path = Files.createTempDirectory(String.format(\"hmcl-terracotta-%d\", ThreadLocalRandom.current().nextLong())).resolve(\"http\").toAbsolutePath();\n            ManagedProcess process = new ManagedProcess(new ProcessBuilder(getProvider().ofCommandLine(path)));\n            process.pumpInputStream(SystemUtils::onLogLine);\n            process.pumpErrorStream(SystemUtils::onLogLine);\n\n            long exitTime = -1;\n            while (true) {\n                if (Files.exists(path)) {\n                    JsonObject object = JsonUtils.fromNonNullJson(Files.readString(path), JsonObject.class);\n                    return object.get(\"port\").getAsInt();\n                }\n\n                if (!process.isRunning()) {\n                    if (exitTime == -1) {\n                        exitTime = System.currentTimeMillis();\n                    } else if (System.currentTimeMillis() - exitTime >= 10000) {\n                        throw new IllegalStateException(String.format(\"Process has exited for 10s, code = %s\", process.getExitCode()));\n                    }\n                }\n            }\n        }).whenComplete(Schedulers.javafx(), (port, exception) -> {\n            TerracottaState next;\n            if (exception == null) {\n                next = new TerracottaState.Unknown(port);\n                if (removeLegacy) {\n                    TerracottaMetadata.removeLegacyVersionFiles();\n                }\n            } else {\n                next = new TerracottaState.Fatal(TerracottaState.Fatal.Type.TERRACOTTA);\n            }\n            compareAndSet(state, next);\n        }).start();\n    }\n\n    public static Task<String> exportLogs() {\n        if (STATE_V.get() instanceof TerracottaState.PortSpecific portSpecific) {\n            return new GetTask(URI.create(String.format(\"http://127.0.0.1:%d/log?fetch=true\", portSpecific.port)))\n                    .setSignificance(Task.TaskSignificance.MINOR);\n        }\n        return Task.completed(null);\n    }\n\n    public static TerracottaState.Waiting setWaiting() {\n        TerracottaState state = STATE_V.get();\n        if (state instanceof TerracottaState.PortSpecific portSpecific) {\n            new GetTask(URI.create(String.format(\"http://127.0.0.1:%d/state/ide\", portSpecific.port)))\n                    .setSignificance(Task.TaskSignificance.MINOR)\n                    .start();\n            return new TerracottaState.Waiting(-1, -1, null);\n        }\n        return null;\n    }\n\n    private static String getPlayerName() {\n        Account account = Accounts.getSelectedAccount();\n        return account != null ? account.getCharacter() : i18n(\"terracotta.player_anonymous\");\n    }\n\n    public static TerracottaState.HostScanning setScanning() {\n        TerracottaState state = STATE_V.get();\n        if (state instanceof TerracottaState.PortSpecific portSpecific) {\n            Task.supplyAsync(Schedulers.io(), TerracottaNodeList::fetch)\n                    .thenComposeAsync(nodes -> {\n                        List<Pair<String, String>> query = new ArrayList<>(nodes.size() + 1);\n                        query.add(pair(\"player\", getPlayerName()));\n                        for (URI node : nodes) {\n                            query.add(pair(\"public_nodes\", node.toString()));\n                        }\n                        return new GetTask(NetworkUtils.withQuery(\n                                \"http://127.0.0.1:%d/state/scanning\".formatted(portSpecific.port), query\n                        )).setSignificance(Task.TaskSignificance.MINOR);\n                    }).start();\n\n            return new TerracottaState.HostScanning(-1, -1, null);\n        }\n        return null;\n    }\n\n    public static Task<TerracottaState.GuestConnecting> setGuesting(String room) {\n        TerracottaState state = STATE_V.get();\n        if (state instanceof TerracottaState.PortSpecific portSpecific) {\n            return Task.supplyAsync(Schedulers.io(), TerracottaNodeList::fetch)\n                    .thenComposeAsync(nodes -> {\n                        ArrayList<Pair<String, String>> query = new ArrayList<>(nodes.size() + 2);\n                        query.add(pair(\"room\", room));\n                        query.add(pair(\"player\", getPlayerName()));\n                        for (URI node : nodes) {\n                            query.add(pair(\"public_nodes\", node.toString()));\n                        }\n                        return new GetTask(NetworkUtils.withQuery(\"http://127.0.0.1:%d/state/guesting\".formatted(portSpecific.port), query))\n                                .setSignificance(Task.TaskSignificance.MINOR)\n                                .thenSupplyAsync(() -> new TerracottaState.GuestConnecting(-1, -1, null))\n                                .setSignificance(Task.TaskSignificance.MINOR);\n                    });\n        } else {\n            return null;\n        }\n    }\n\n    private static <T extends TerracottaState> T setState(T value) {\n        if (value == null) {\n            throw new AssertionError();\n        }\n\n        STATE_V.set(value);\n        STATE_D.accept(value);\n        return value;\n    }\n\n    private static boolean compareAndSet(TerracottaState previous, TerracottaState next) {\n        if (next == null) {\n            throw new AssertionError();\n        }\n\n        if (STATE_V.compareAndSet(previous, next)) {\n            STATE_D.accept(next);\n            return true;\n        } else {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/TerracottaMetadata.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.terracotta.provider.AbstractTerracottaProvider;\nimport org.jackhuang.hmcl.terracotta.provider.GeneralProvider;\nimport org.jackhuang.hmcl.terracotta.provider.MacOSProvider;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.i18n.LocaleUtils;\nimport org.jackhuang.hmcl.util.i18n.LocalizedText;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OSVersion;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jackhuang.hmcl.util.versioning.VersionRange;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.file.DirectoryStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class TerracottaMetadata {\n    private TerracottaMetadata() {\n    }\n\n    private record Options(String version, String classifier) {\n        public String replace(String value) {\n            return value.replace(\"${version}\", version).replace(\"${classifier}\", classifier);\n        }\n    }\n\n    @JsonSerializable\n    public record Link(\n            @SerializedName(\"desc\") LocalizedText description,\n            @SerializedName(\"link\") String link\n    ) {\n    }\n\n    @JsonSerializable\n    private record Package(\n            @SerializedName(\"hash\") String hash,\n            @SerializedName(\"files\") Map<String, String> files\n    ) {\n    }\n\n    @JsonSerializable\n    private record Config(\n            @SerializedName(\"version_latest\") String latest,\n\n            @SerializedName(\"packages\") Map<String, Package> pkgs,\n            @SerializedName(\"downloads\") List<String> downloads,\n            @SerializedName(\"downloads_CN\") List<String> downloadsCN,\n            @SerializedName(\"links\") List<Link> links\n    ) {\n        private @Nullable TerracottaBundle resolve(Options options) {\n            Package pkg = pkgs.get(options.classifier);\n            if (pkg == null) {\n                return null;\n            }\n\n            Stream<String> stream = downloads.stream(), streamCN = downloadsCN.stream();\n            List<URI> links = (LocaleUtils.IS_CHINA_MAINLAND ? Stream.concat(streamCN, stream) : Stream.concat(stream, streamCN))\n                    .map(link -> URI.create(options.replace(link)))\n                    .toList();\n\n            Map<String, FileDownloadTask.IntegrityCheck> files = pkg.files.entrySet().stream().collect(Collectors.toUnmodifiableMap(\n                    Map.Entry::getKey,\n                    entry -> new FileDownloadTask.IntegrityCheck(\"SHA-512\", entry.getValue())\n            ));\n\n            return new TerracottaBundle(\n                    Metadata.DEPENDENCIES_DIRECTORY.resolve(options.replace(\"terracotta/${version}\")).toAbsolutePath(),\n                    links, new FileDownloadTask.IntegrityCheck(\"SHA-512\", pkg.hash),\n                    files\n            );\n        }\n    }\n\n    public static final AbstractTerracottaProvider PROVIDER;\n    public static final String PACKAGE_NAME;\n    public static final List<Link> PACKAGE_LINKS;\n    public static final String FEEDBACK_LINK = NetworkUtils.withQuery(\"https://docs.hmcl.net/multiplayer/feedback.html\", Map.of(\n            \"v\", \"v1\",\n            \"launcher_version\", Metadata.VERSION\n    ));\n\n    private static final String LATEST;\n\n    static {\n        Config config;\n        try (InputStream is = TerracottaMetadata.class.getResourceAsStream(\"/assets/terracotta.json\")) {\n            config = JsonUtils.fromNonNullJsonFully(is, Config.class);\n        } catch (IOException e) {\n            throw new ExceptionInInitializerError(e);\n        }\n\n        LATEST = config.latest;\n\n        Options options = new Options(config.latest, OperatingSystem.CURRENT_OS.getCheckedName() + \"-\" + Architecture.SYSTEM_ARCH.getCheckedName());\n        TerracottaBundle bundle = config.resolve(options);\n        AbstractTerracottaProvider provider;\n        if (bundle == null || (provider = locateProvider(bundle, options)) == null) {\n            PROVIDER = null;\n            PACKAGE_NAME = null;\n            PACKAGE_LINKS = null;\n        } else {\n            PROVIDER = provider;\n            PACKAGE_NAME = options.replace(\"terracotta-${version}-${classifier}-pkg.tar.gz\");\n\n            List<Link> packageLinks = config.links.stream()\n                    .map(link -> new Link(link.description, options.replace(link.link)))\n                    .collect(Collectors.toList());\n            Collections.shuffle(packageLinks);\n            PACKAGE_LINKS = Collections.unmodifiableList(packageLinks);\n        }\n    }\n\n    @Nullable\n    private static AbstractTerracottaProvider locateProvider(TerracottaBundle bundle, Options options) {\n        String prefix = options.replace(\"terracotta-${version}-${classifier}\");\n\n        // FIXME: As HMCL is a cross-platform application, developers may mistakenly locate\n        //        non-existent files in non-native platform logic without assertion errors during debugging.\n        return switch (OperatingSystem.CURRENT_OS) {\n            case WINDOWS -> {\n                if (!OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_10))\n                    yield null;\n\n                yield new GeneralProvider(bundle, bundle.locate(prefix + \".exe\"));\n            }\n            case LINUX, FREEBSD -> new GeneralProvider(bundle, bundle.locate(prefix));\n            case MACOS -> new MacOSProvider(\n                    bundle, bundle.locate(prefix), bundle.locate(prefix + \".pkg\")\n            );\n            default -> null;\n        };\n    }\n\n    public static void removeLegacyVersionFiles() {\n        try (DirectoryStream<Path> terracotta = collectLegacyVersionFiles()) {\n            if (terracotta == null)\n                return;\n\n            for (Path path : terracotta) {\n                try {\n                    FileUtils.deleteDirectory(path);\n                } catch (IOException e) {\n                    LOG.warning(String.format(\"Unable to remove legacy terracotta files: %s\", path), e);\n                }\n            }\n        } catch (IOException e) {\n            LOG.warning(\"Unable to remove legacy terracotta files.\", e);\n        }\n    }\n\n    public static boolean hasLegacyVersionFiles() throws IOException {\n        try (DirectoryStream<Path> terracotta = collectLegacyVersionFiles()) {\n            return terracotta != null && terracotta.iterator().hasNext();\n        }\n    }\n\n    private static @Nullable DirectoryStream<Path> collectLegacyVersionFiles() throws IOException {\n        Path terracottaDir = Metadata.DEPENDENCIES_DIRECTORY.resolve(\"terracotta\");\n        if (Files.notExists(terracottaDir))\n            return null;\n\n        VersionRange<VersionNumber> range = VersionNumber.atMost(LATEST);\n        return Files.newDirectoryStream(terracottaDir, path -> {\n            String name = FileUtils.getName(path);\n            return !LATEST.equals(name) && range.contains(VersionNumber.asVersion(name));\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/TerracottaNodeList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jackhuang.hmcl.util.i18n.LocaleUtils;\nimport org.jackhuang.hmcl.util.io.HttpRequest;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author Glavo\npublic final class TerracottaNodeList {\n    private static final String NODE_LIST_URL = \"https://terracotta.glavo.site/nodes\";\n\n    @JsonSerializable\n    private record TerracottaNode(String url, @Nullable String region) implements Validation {\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            Validation.requireNonNull(url, \"TerracottaNode.url cannot be null\");\n            try {\n                new URI(url);\n            } catch (URISyntaxException e) {\n                throw new JsonParseException(\"Invalid URL: \" + url, e);\n            }\n        }\n    }\n\n    private static volatile List<URI> list;\n\n    public static List<URI> fetch() {\n        List<URI> list = TerracottaNodeList.list;\n        if (list != null)\n            return list;\n\n        synchronized (TerracottaNodeList.class) {\n            list = TerracottaNodeList.list;\n            if (list != null)\n                return list;\n\n            try {\n                List<TerracottaNode> nodes = HttpRequest.GET(NODE_LIST_URL)\n                        .getJson(JsonUtils.listTypeOf(TerracottaNode.class));\n\n                if (nodes == null) {\n                    list = List.of();\n                    LOG.info(\"No available Terracotta nodes found\");\n                } else {\n                    list = nodes.stream()\n                            .filter(node -> {\n                                if (node == null)\n                                    return false;\n\n                                try {\n                                    node.validate();\n                                } catch (Exception e) {\n                                    LOG.warning(\"Invalid terracotta node: \" + node, e);\n                                    return false;\n                                }\n\n                                return StringUtils.isBlank(node.region) || LocaleUtils.IS_CHINA_MAINLAND == \"CN\".equalsIgnoreCase(node.region);\n                            })\n                            .map(it -> URI.create(it.url()))\n                            .toList();\n                    LOG.info(\"Terracotta node list: \" + list);\n                }\n            } catch (Exception e) {\n                LOG.warning(\"Failed to fetch terracotta node list\", e);\n                list = List.of();\n            }\n\n            TerracottaNodeList.list = list;\n            return list;\n        }\n    }\n\n    private TerracottaNodeList() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/TerracottaState.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport javafx.beans.property.ReadOnlyDoubleProperty;\nimport javafx.beans.property.ReadOnlyDoubleWrapper;\nimport javafx.beans.value.ObservableDoubleValue;\nimport org.jackhuang.hmcl.terracotta.profile.TerracottaProfile;\nimport org.jackhuang.hmcl.terracotta.provider.AbstractTerracottaProvider;\nimport org.jackhuang.hmcl.util.gson.JsonSubtype;\nimport org.jackhuang.hmcl.util.gson.JsonType;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport java.util.List;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\npublic abstract sealed class TerracottaState {\n    protected TerracottaState() {\n    }\n\n    public boolean isUIFakeState() {\n        return false;\n    }\n\n    public boolean isForkOf(TerracottaState state) {\n        return false;\n    }\n\n    public static final class Bootstrap extends TerracottaState {\n        static final Bootstrap INSTANCE = new Bootstrap();\n\n        private Bootstrap() {\n        }\n    }\n\n    public static final class Uninitialized extends TerracottaState {\n        private final boolean hasLegacy;\n\n        Uninitialized(boolean hasLegacy) {\n            this.hasLegacy = hasLegacy;\n        }\n\n        public boolean hasLegacy() {\n            return hasLegacy;\n        }\n    }\n\n    public static final class Preparing extends TerracottaState implements AbstractTerracottaProvider.DownloadContext {\n        private final ReadOnlyDoubleWrapper progress;\n\n        private final AtomicBoolean hasInstallFence;\n\n        Preparing(ReadOnlyDoubleWrapper progress, boolean hasInstallFence) {\n            this.progress = progress;\n            this.hasInstallFence = new AtomicBoolean(hasInstallFence);\n        }\n\n        public ReadOnlyDoubleProperty progressProperty() {\n            return progress.getReadOnlyProperty();\n        }\n\n        public boolean requestInstallFence() {\n            return hasInstallFence.compareAndSet(true, false);\n        }\n\n        public boolean hasInstallFence() {\n            return hasInstallFence.get();\n        }\n\n        @Override\n        public void bindProgress(ObservableDoubleValue progress) {\n            this.progress.bind(progress);\n        }\n\n        @Override\n        public void checkCancellation() {\n            if (!hasInstallFence()) {\n                throw new CancellationException(\"User has installed terracotta from local archives.\");\n            }\n        }\n    }\n\n    public static final class Launching extends TerracottaState {\n        Launching() {\n        }\n    }\n\n    static abstract sealed class PortSpecific extends TerracottaState {\n        transient int port;\n\n        protected PortSpecific(int port) {\n            this.port = port;\n        }\n    }\n\n    @JsonType(\n            property = \"state\",\n            subtypes = {\n                    @JsonSubtype(clazz = Waiting.class, name = \"waiting\"),\n                    @JsonSubtype(clazz = HostScanning.class, name = \"host-scanning\"),\n                    @JsonSubtype(clazz = HostStarting.class, name = \"host-starting\"),\n                    @JsonSubtype(clazz = HostOK.class, name = \"host-ok\"),\n                    @JsonSubtype(clazz = GuestConnecting.class, name = \"guest-connecting\"),\n                    @JsonSubtype(clazz = GuestStarting.class, name = \"guest-starting\"),\n                    @JsonSubtype(clazz = GuestOK.class, name = \"guest-ok\"),\n                    @JsonSubtype(clazz = Exception.class, name = \"exception\"),\n            }\n    )\n    static abstract sealed class Ready extends PortSpecific {\n        @SerializedName(\"index\")\n        final int index;\n\n        @SerializedName(\"state\")\n        private final String state;\n\n        Ready(int port, int index, String state) {\n            super(port);\n            this.index = index;\n            this.state = state;\n        }\n\n        @Override\n        public boolean isUIFakeState() {\n            return this.index == -1;\n        }\n    }\n\n    public static final class Unknown extends PortSpecific {\n        Unknown(int port) {\n            super(port);\n        }\n    }\n\n    public static final class Waiting extends Ready {\n        Waiting(int port, int index, String state) {\n            super(port, index, state);\n        }\n    }\n\n    public static final class HostScanning extends Ready {\n        HostScanning(int port, int index, String state) {\n            super(port, index, state);\n        }\n    }\n\n    public static final class HostStarting extends Ready {\n        HostStarting(int port, int index, String state) {\n            super(port, index, state);\n        }\n    }\n\n    public static final class HostOK extends Ready implements Validation {\n        @SerializedName(\"room\")\n        private final String code;\n\n        @SerializedName(\"profile_index\")\n        private final int profileIndex;\n\n        @SerializedName(\"profiles\")\n        private final List<TerracottaProfile> profiles;\n\n        HostOK(int port, int index, String state, String code, int profileIndex, List<TerracottaProfile> profiles) {\n            super(port, index, state);\n            this.code = code;\n            this.profileIndex = profileIndex;\n            this.profiles = profiles;\n        }\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            if (code == null) {\n                throw new JsonParseException(\"code is null\");\n            }\n            if (profiles == null) {\n                throw new JsonParseException(\"profiles is null\");\n            }\n        }\n\n        public String getCode() {\n            return code;\n        }\n\n        public List<TerracottaProfile> getProfiles() {\n            return profiles;\n        }\n\n        @Override\n        public boolean isForkOf(TerracottaState state) {\n            return state instanceof HostOK hostOK && this.index - hostOK.index <= profileIndex;\n        }\n    }\n\n    public static final class GuestConnecting extends Ready {\n        GuestConnecting(int port, int index, String state) {\n            super(port, index, state);\n        }\n    }\n\n    public static final class GuestStarting extends Ready {\n        public enum Difficulty {\n            UNKNOWN,\n            EASIEST,\n            SIMPLE,\n            MEDIUM,\n            TOUGH\n        }\n\n        @SerializedName(\"difficulty\")\n        private final Difficulty difficulty;\n\n        GuestStarting(int port, int index, String state, Difficulty difficulty) {\n            super(port, index, state);\n            this.difficulty = difficulty;\n        }\n\n        public Difficulty getDifficulty() {\n            return difficulty;\n        }\n    }\n\n    public static final class GuestOK extends Ready implements Validation {\n        @SerializedName(\"url\")\n        private final String url;\n\n        @SerializedName(\"profile_index\")\n        private final int profileIndex;\n\n        @SerializedName(\"profiles\")\n        private final List<TerracottaProfile> profiles;\n\n        GuestOK(int port, int index, String state, String url, int profileIndex, List<TerracottaProfile> profiles) {\n            super(port, index, state);\n            this.url = url;\n            this.profileIndex = profileIndex;\n            this.profiles = profiles;\n        }\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            if (profiles == null) {\n                throw new JsonParseException(\"profiles is null\");\n            }\n        }\n\n        public String getUrl() {\n            return url;\n        }\n\n        public List<TerracottaProfile> getProfiles() {\n            return profiles;\n        }\n\n        @Override\n        public boolean isForkOf(TerracottaState state) {\n            return state instanceof GuestOK guestOK && this.index - guestOK.index <= profileIndex;\n        }\n    }\n\n    public static final class Exception extends Ready implements Validation {\n        public enum Type {\n            PING_HOST_FAIL,\n            PING_HOST_RST,\n            GUEST_ET_CRASH,\n            HOST_ET_CRASH,\n            PING_SERVER_RST,\n            SCAFFOLDING_INVALID_RESPONSE\n        }\n\n        private static final TerracottaState.Exception.Type[] LOOKUP = Type.values();\n\n        @SerializedName(\"type\")\n        private final int type;\n\n        Exception(int port, int index, String state, int type) {\n            super(port, index, state);\n            this.type = type;\n        }\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            if (type < 0 || type >= LOOKUP.length) {\n                throw new JsonParseException(String.format(\"Type must between [0, %s)\", LOOKUP.length));\n            }\n        }\n\n        public Type getType() {\n            return LOOKUP[type];\n        }\n    }\n\n    public static final class Fatal extends TerracottaState {\n        public enum Type {\n            OS,\n            NETWORK,\n            INSTALL,\n            TERRACOTTA,\n            UNKNOWN;\n        }\n\n        private final Type type;\n\n        public Fatal(Type type) {\n            this.type = type;\n        }\n\n        public Type getType() {\n            return type;\n        }\n\n        public boolean isRecoverable() {\n            return this.type != Type.UNKNOWN;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/profile/ProfileKind.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta.profile;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic enum ProfileKind {\n    @SerializedName(\"HOST\")\n    HOST,\n    @SerializedName(\"LOCAL\")\n    LOCAL,\n    @SerializedName(\"GUEST\")\n    GUEST\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/profile/TerracottaProfile.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta.profile;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic final class TerracottaProfile {\n    @SerializedName(\"machine_id\")\n    private final String machineID;\n\n    @SerializedName(\"name\")\n    private final String name;\n\n    @SerializedName(\"vendor\")\n    private final String vendor;\n\n    @SerializedName(\"kind\")\n    private final ProfileKind type;\n\n    private TerracottaProfile(String machineID, String name, String vendor, ProfileKind type) {\n        this.machineID = machineID;\n        this.name = name;\n        this.vendor = vendor;\n        this.type = type;\n    }\n\n    public String getMachineID() {\n        return machineID;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getVendor() {\n        return vendor;\n    }\n\n    public ProfileKind getType() {\n        return type;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/provider/AbstractTerracottaProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta.provider;\n\nimport javafx.beans.value.ObservableDoubleValue;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.terracotta.TerracottaBundle;\nimport org.jackhuang.hmcl.util.FXThread;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.concurrent.CancellationException;\n\npublic abstract class AbstractTerracottaProvider {\n    public enum Status {\n        NOT_EXIST,\n        LEGACY_VERSION,\n        READY\n    }\n\n    public interface DownloadContext {\n        @FXThread\n        void bindProgress(ObservableDoubleValue progress);\n\n        void checkCancellation() throws CancellationException;\n    }\n\n    protected final TerracottaBundle bundle;\n\n    protected AbstractTerracottaProvider(TerracottaBundle bundle) {\n        this.bundle = bundle;\n    }\n\n    public Status status() throws IOException {\n        return bundle.status();\n    }\n\n    public final Task<Path> download(DownloadContext progress) {\n        return bundle.download(progress);\n    }\n\n    public Task<?> install(Path pkg) throws IOException {\n        return bundle.install(pkg);\n    }\n\n    public abstract List<String> ofCommandLine(Path portTransfer);\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/provider/GeneralProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta.provider;\n\nimport org.jackhuang.hmcl.terracotta.TerracottaBundle;\n\nimport java.nio.file.Path;\nimport java.util.List;\n\npublic final class GeneralProvider extends AbstractTerracottaProvider {\n    private final Path executable;\n\n    public GeneralProvider(TerracottaBundle bundle, Path executable) {\n        super(bundle);\n        this.executable = executable;\n    }\n\n    @Override\n    public List<String> ofCommandLine(Path portTransfer) {\n        return List.of(executable.toString(), \"--hmcl\", portTransfer.toString());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/terracotta/provider/MacOSProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.terracotta.provider;\n\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.terracotta.TerracottaBundle;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class MacOSProvider extends AbstractTerracottaProvider {\n    private final Path executable, installer;\n\n    public MacOSProvider(TerracottaBundle bundle, Path executable, Path installer) {\n        super(bundle);\n        this.executable = executable;\n        this.installer = installer;\n    }\n\n    @Override\n    public Status status() throws IOException {\n        if (!Files.exists(Path.of(\"/Applications/terracotta.app\"))) {\n            return Status.NOT_EXIST;\n        }\n\n        return bundle.status();\n    }\n\n    @Override\n    public Task<?> install(Path pkg) throws IOException {\n        return super.install(pkg).thenComposeAsync(() -> {\n            Path osascript = SystemUtils.which(\"osascript\");\n            if (osascript == null) {\n                throw new IllegalStateException(\"Cannot locate 'osascript' system executable on MacOS for installing Terracotta.\");\n            }\n\n            Path movedInstaller = Files.createTempDirectory(Metadata.HMCL_GLOBAL_DIRECTORY, \"terracotta-pkg\")\n                    .toRealPath()\n                    .resolve(FileUtils.getName(installer));\n            Files.copy(installer, movedInstaller, StandardCopyOption.REPLACE_EXISTING);\n\n            ManagedProcess process = new ManagedProcess(new ProcessBuilder(\n                    osascript.toString(), \"-e\", String.format(\n                    \"do shell script \\\"installer -pkg '%s' -target /\\\" with prompt \\\"%s\\\" with administrator privileges\",\n                    movedInstaller, i18n(\"terracotta.sudo_installing\")\n            )));\n            process.pumpInputStream(SystemUtils::onLogLine);\n            process.pumpErrorStream(SystemUtils::onLogLine);\n\n            return Task.fromCompletableFuture(process.getProcess().onExit()).thenRunAsync(() -> {\n                try {\n                    FileUtils.cleanDirectory(movedInstaller.getParent());\n                } catch (IOException e) {\n                    LOG.warning(\"Cannot remove temporary Terracotta package file.\", e);\n                }\n\n                if (process.getExitCode() != 0) {\n                    throw new IllegalStateException(String.format(\n                            \"Cannot install Terracotta %s: system installer exited with code %d\", movedInstaller, process.getExitCode()\n                    ));\n                }\n            });\n        });\n    }\n\n    @Override\n    public List<String> ofCommandLine(Path path) {\n        return List.of(executable.toString(), \"--hmcl\", path.toString());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/theme/Theme.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.theme;\n\nimport org.glavo.monetfx.*;\n\nimport java.util.*;\n\n/// @author Glavo\npublic final class Theme {\n\n    public static final Theme DEFAULT = new Theme(ThemeColor.DEFAULT, Brightness.DEFAULT, ColorStyle.FIDELITY, Contrast.DEFAULT);\n\n    private final ThemeColor primaryColorSeed;\n    private final Brightness brightness;\n    private final ColorStyle colorStyle;\n    private final Contrast contrast;\n\n    public Theme(ThemeColor primaryColorSeed,\n                 Brightness brightness,\n                 ColorStyle colorStyle,\n                 Contrast contrast\n    ) {\n        this.primaryColorSeed = primaryColorSeed;\n        this.brightness = brightness;\n        this.colorStyle = colorStyle;\n        this.contrast = contrast;\n    }\n\n    public ColorScheme toColorScheme() {\n        return ColorScheme.newBuilder()\n                .setPrimaryColorSeed(primaryColorSeed.color())\n                .setColorStyle(colorStyle)\n                .setBrightness(brightness)\n                .setSpecVersion(ColorSpecVersion.SPEC_2025)\n                .setContrast(contrast)\n                .build();\n    }\n\n    public ThemeColor primaryColorSeed() {\n        return primaryColorSeed;\n    }\n\n    public Brightness brightness() {\n        return brightness;\n    }\n\n    public ColorStyle colorStyle() {\n        return colorStyle;\n    }\n\n    public Contrast contrast() {\n        return contrast;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return obj == this || obj instanceof Theme that\n                && this.primaryColorSeed.color().equals(that.primaryColorSeed.color())\n                && this.brightness.equals(that.brightness)\n                && this.colorStyle.equals(that.colorStyle)\n                && this.contrast.equals(that.contrast);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(primaryColorSeed, brightness, colorStyle, contrast);\n    }\n\n    @Override\n    public String toString() {\n        return \"Theme[\" +\n                \"primaryColorSeed=\" + primaryColorSeed + \", \" +\n                \"brightness=\" + brightness + \", \" +\n                \"colorStyle=\" + colorStyle + \", \" +\n                \"contrast=\" + contrast + ']';\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemeColor.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.theme;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\nimport javafx.beans.WeakListener;\nimport javafx.beans.property.Property;\nimport javafx.scene.control.ColorPicker;\nimport javafx.scene.paint.Color;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jetbrains.annotations.Contract;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.lang.ref.WeakReference;\nimport java.util.List;\nimport java.util.Objects;\n\n/// @author Glavo\n@JsonAdapter(ThemeColor.TypeAdapter.class)\n@JsonSerializable\npublic record ThemeColor(@NotNull String name, @NotNull Color color) {\n\n    public static final ThemeColor DEFAULT = new ThemeColor(\"blue\", Color.web(\"#5C6BC0\"));\n\n    public static final List<ThemeColor> STANDARD_COLORS = List.of(\n            DEFAULT,\n            new ThemeColor(\"darker_blue\", Color.web(\"#283593\")),\n            new ThemeColor(\"green\", Color.web(\"#43A047\")),\n            new ThemeColor(\"orange\", Color.web(\"#E67E22\")),\n            new ThemeColor(\"purple\", Color.web(\"#9C27B0\")),\n            new ThemeColor(\"red\", Color.web(\"#B71C1C\"))\n    );\n\n    public static String getColorDisplayName(Color c) {\n        return c != null ? String.format(\"#%02X%02X%02X\",\n                Math.round(c.getRed() * 255.0D),\n                Math.round(c.getGreen() * 255.0D),\n                Math.round(c.getBlue() * 255.0D))\n                : null;\n    }\n\n    public static String getColorDisplayNameWithOpacity(Color c, double opacity) {\n        return c != null ? String.format(\"#%02X%02X%02X%02X\",\n                Math.round(c.getRed() * 255.0D),\n                Math.round(c.getGreen() * 255.0D),\n                Math.round(c.getBlue() * 255.0D),\n                Math.round(opacity * 255.0))\n                : null;\n    }\n\n    public static @Nullable ThemeColor of(String name) {\n        if (name == null)\n            return null;\n\n        if (!name.startsWith(\"#\")) {\n            for (ThemeColor color : STANDARD_COLORS) {\n                if (name.equalsIgnoreCase(color.name()))\n                    return color;\n            }\n        }\n\n        try {\n            return new ThemeColor(name, Color.web(name));\n        } catch (IllegalArgumentException e) {\n            return null;\n        }\n    }\n\n    @Contract(\"null -> null; !null -> !null\")\n    public static ThemeColor of(Color color) {\n        return color != null ? new ThemeColor(getColorDisplayName(color), color) : null;\n    }\n\n    private static final class BidirectionalBinding implements InvalidationListener, WeakListener {\n        private final WeakReference<ColorPicker> colorPickerRef;\n        private final WeakReference<Property<ThemeColor>> propertyRef;\n        private final int hashCode;\n\n        private boolean updating = false;\n\n        private BidirectionalBinding(ColorPicker colorPicker, Property<ThemeColor> property) {\n            this.colorPickerRef = new WeakReference<>(colorPicker);\n            this.propertyRef = new WeakReference<>(property);\n            this.hashCode = System.identityHashCode(colorPicker) ^ System.identityHashCode(property);\n        }\n\n        @Override\n        public void invalidated(Observable sourceProperty) {\n            if (!updating) {\n                final ColorPicker colorPicker = colorPickerRef.get();\n                final Property<ThemeColor> property = propertyRef.get();\n\n                if (colorPicker == null || property == null) {\n                    if (colorPicker != null) {\n                        colorPicker.valueProperty().removeListener(this);\n                    }\n\n                    if (property != null) {\n                        property.removeListener(this);\n                    }\n                } else {\n                    updating = true;\n                    try {\n                        if (property == sourceProperty) {\n                            ThemeColor newValue = property.getValue();\n                            colorPicker.setValue(newValue != null ? newValue.color() : null);\n                        } else {\n                            Color newValue = colorPicker.getValue();\n                            property.setValue(newValue != null ? ThemeColor.of(newValue) : null);\n                        }\n                    } finally {\n                        updating = false;\n                    }\n                }\n            }\n        }\n\n        @Override\n        public boolean wasGarbageCollected() {\n            return colorPickerRef.get() == null || propertyRef.get() == null;\n        }\n\n        @Override\n        public int hashCode() {\n            return hashCode;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (!(o instanceof BidirectionalBinding that))\n                return false;\n\n            final ColorPicker colorPicker = this.colorPickerRef.get();\n            final Property<ThemeColor> property = this.propertyRef.get();\n\n            final ColorPicker thatColorPicker = that.colorPickerRef.get();\n            final Property<?> thatProperty = that.propertyRef.get();\n\n            if (colorPicker == null || property == null || thatColorPicker == null || thatProperty == null)\n                return false;\n\n            return colorPicker == thatColorPicker && property == thatProperty;\n        }\n    }\n\n    public static void bindBidirectional(ColorPicker colorPicker, Property<ThemeColor> property) {\n        var binding = new BidirectionalBinding(colorPicker, property);\n\n        colorPicker.valueProperty().removeListener(binding);\n        property.removeListener(binding);\n\n        ThemeColor themeColor = property.getValue();\n        colorPicker.setValue(themeColor != null ? themeColor.color() : null);\n\n        colorPicker.valueProperty().addListener(binding);\n        property.addListener(binding);\n    }\n\n    static final class TypeAdapter extends com.google.gson.TypeAdapter<ThemeColor> {\n        @Override\n        public void write(JsonWriter out, ThemeColor value) throws IOException {\n            out.value(value.name());\n        }\n\n        @Override\n        public ThemeColor read(JsonReader in) throws IOException {\n            return Objects.requireNonNullElse(of(in.nextString()), ThemeColor.DEFAULT);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.theme;\n\nimport com.sun.jna.Pointer;\nimport javafx.beans.Observable;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.binding.ObjectBinding;\nimport javafx.beans.binding.ObjectExpression;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.event.EventHandler;\nimport javafx.scene.paint.Color;\nimport javafx.stage.Stage;\nimport javafx.stage.WindowEvent;\nimport org.glavo.monetfx.Brightness;\nimport org.glavo.monetfx.ColorScheme;\nimport org.glavo.monetfx.Contrast;\nimport org.glavo.monetfx.beans.property.ColorSchemeProperty;\nimport org.glavo.monetfx.beans.property.ReadOnlyColorSchemeProperty;\nimport org.glavo.monetfx.beans.property.SimpleColorSchemeProperty;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.ui.WindowsNativeUtils;\nimport org.jackhuang.hmcl.util.platform.NativeUtils;\nimport org.jackhuang.hmcl.util.platform.OSVersion;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jackhuang.hmcl.util.platform.windows.Dwmapi;\nimport org.jackhuang.hmcl.util.platform.windows.WinConstants;\nimport org.jackhuang.hmcl.util.platform.windows.WinReg;\nimport org.jackhuang.hmcl.util.platform.windows.WinTypes;\n\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author Glavo\npublic final class Themes {\n\n    private static final ObjectExpression<Theme> theme = new ObjectBinding<>() {\n        {\n            List<Observable> observables = new ArrayList<>();\n\n            observables.add(config().themeBrightnessProperty());\n            observables.add(config().themeColorProperty());\n            if (FXUtils.DARK_MODE != null) {\n                observables.add(FXUtils.DARK_MODE);\n            }\n            bind(observables.toArray(new Observable[0]));\n        }\n\n        private Brightness getBrightness() {\n            String themeBrightness = config().getThemeBrightness();\n            if (themeBrightness == null)\n                return Brightness.DEFAULT;\n\n            return switch (themeBrightness.toLowerCase(Locale.ROOT).trim()) {\n                case \"auto\" -> {\n                    if (FXUtils.DARK_MODE != null) {\n                        yield FXUtils.DARK_MODE.get() ? Brightness.DARK : Brightness.LIGHT;\n                    } else {\n                        yield getDefaultBrightness();\n                    }\n                }\n                case \"dark\" -> Brightness.DARK;\n                case \"light\" -> Brightness.LIGHT;\n                default -> Brightness.DEFAULT;\n            };\n        }\n\n        @Override\n        protected Theme computeValue() {\n            ThemeColor themeColor = Objects.requireNonNullElse(config().getThemeColor(), ThemeColor.DEFAULT);\n\n            return new Theme(themeColor, getBrightness(), Theme.DEFAULT.colorStyle(), Contrast.DEFAULT);\n        }\n    };\n    private static final ColorSchemeProperty colorScheme = new SimpleColorSchemeProperty();\n    private static final BooleanBinding darkMode = Bindings.createBooleanBinding(\n            () -> colorScheme.get().getBrightness() == Brightness.DARK,\n            colorScheme\n    );\n\n    static {\n        ChangeListener<Theme> listener = (observable, oldValue, newValue) -> {\n            if (!Objects.equals(oldValue, newValue)) {\n                colorScheme.set(newValue != null ? newValue.toColorScheme() : Theme.DEFAULT.toColorScheme());\n            }\n        };\n        listener.changed(theme, null, theme.get());\n        theme.addListener(listener);\n    }\n\n    private static Brightness defaultBrightness;\n\n    private static Brightness getDefaultBrightness() {\n        if (defaultBrightness != null)\n            return defaultBrightness;\n\n        LOG.info(\"Detecting system theme brightness\");\n        Brightness brightness = Brightness.DEFAULT;\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n            WinReg reg = WinReg.INSTANCE;\n            if (reg != null) {\n                Object appsUseLightTheme = reg.queryValue(WinReg.HKEY.HKEY_CURRENT_USER, \"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Themes\\\\Personalize\", \"AppsUseLightTheme\");\n                if (appsUseLightTheme instanceof Integer value) {\n                    brightness = value == 0 ? Brightness.DARK : Brightness.LIGHT;\n                }\n            }\n        } else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n            try {\n                String result = SystemUtils.run(\"/usr/bin/defaults\", \"read\", \"-g\", \"AppleInterfaceStyle\").trim();\n                brightness = \"Dark\".equalsIgnoreCase(result) ? Brightness.DARK : Brightness.LIGHT;\n            } catch (Exception e) {\n                // If the key does not exist, it means Light mode is used\n                brightness = Brightness.LIGHT;\n            }\n        } else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) {\n            Path dbusSend = SystemUtils.which(\"dbus-send\");\n            if (dbusSend != null) {\n                try {\n                    String[] result = SystemUtils.run(List.of(\n                            FileUtils.getAbsolutePath(dbusSend),\n                            \"--session\",\n                            \"--print-reply=literal\",\n                            \"--reply-timeout=1000\",\n                            \"--dest=org.freedesktop.portal.Desktop\",\n                            \"/org/freedesktop/portal/desktop\",\n                            \"org.freedesktop.portal.Settings.Read\",\n                            \"string:org.freedesktop.appearance\",\n                            \"string:color-scheme\"\n                    ), Duration.ofSeconds(2)).trim().split(\" \");\n\n                    if (result.length > 0) {\n                        String value = result[result.length - 1];\n                        // 1: prefer dark\n                        // 2: prefer light\n                        if (\"1\".equals(value)) {\n                            brightness = Brightness.DARK;\n                        } else if (\"2\".equals(value)) {\n                            brightness = Brightness.LIGHT;\n                        }\n                    }\n                } catch (Exception e) {\n                    LOG.warning(\"Failed to get system theme from D-Bus\", e);\n                }\n            }\n        }\n        LOG.info(\"Detected system theme brightness: \" + brightness);\n\n        return defaultBrightness = brightness;\n    }\n\n    public static ObjectExpression<Theme> themeProperty() {\n        return theme;\n    }\n\n    public static Theme getTheme() {\n        return themeProperty().get();\n    }\n\n    public static ReadOnlyColorSchemeProperty colorSchemeProperty() {\n        return colorScheme;\n    }\n\n    public static ColorScheme getColorScheme() {\n        return colorScheme.get();\n    }\n\n    private static final ObjectBinding<Color> titleFill = Bindings.createObjectBinding(\n            () -> config().isTitleTransparent()\n                    ? getColorScheme().getOnSurface()\n                    : getColorScheme().getOnPrimaryContainer(),\n            colorSchemeProperty(),\n            config().titleTransparentProperty()\n    );\n\n    public static ObservableValue<Color> titleFillProperty() {\n        return titleFill;\n    }\n\n    public static BooleanBinding darkModeProperty() {\n        return darkMode;\n    }\n\n    public static void applyNativeDarkMode(Stage stage) {\n        if (OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_11) && NativeUtils.USE_JNA && Dwmapi.INSTANCE != null) {\n            ChangeListener<Boolean> listener = FXUtils.onWeakChange(Themes.darkModeProperty(), darkMode -> {\n                if (stage.isShowing()) {\n                    WindowsNativeUtils.getWindowHandle(stage).ifPresent(handle -> {\n                        if (handle == WinTypes.HANDLE.INVALID_VALUE)\n                            return;\n\n                        Dwmapi.INSTANCE.DwmSetWindowAttribute(\n                                new WinTypes.HANDLE(Pointer.createConstant(handle)),\n                                WinConstants.DWMWA_USE_IMMERSIVE_DARK_MODE,\n                                new WinTypes.BOOLByReference(new WinTypes.BOOL(darkMode)),\n                                WinTypes.BOOL.SIZE\n                        );\n                    });\n                }\n            });\n            stage.getProperties().put(\"Themes.applyNativeDarkMode.listener\", listener);\n\n            if (stage.isShowing()) {\n                listener.changed(null, false, Themes.darkModeProperty().get());\n            } else {\n                stage.addEventFilter(WindowEvent.WINDOW_SHOWN, new EventHandler<>() {\n                    @Override\n                    public void handle(WindowEvent event) {\n                        stage.removeEventFilter(WindowEvent.WINDOW_SHOWN, this);\n                        listener.changed(null, false, Themes.darkModeProperty().get());\n                    }\n                });\n            }\n        }\n    }\n\n    private Themes() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.property.DoubleProperty;\nimport javafx.beans.property.ReadOnlyDoubleProperty;\nimport javafx.beans.property.SimpleDoubleProperty;\nimport javafx.geometry.Rectangle2D;\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.scene.control.ButtonBase;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.Region;\nimport javafx.scene.paint.Color;\nimport javafx.stage.Screen;\nimport javafx.stage.Stage;\nimport javafx.stage.StageStyle;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.Launcher;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.game.LauncherHelper;\nimport org.jackhuang.hmcl.game.ModpackHelper;\nimport org.jackhuang.hmcl.java.JavaManager;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.setting.*;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.jackhuang.hmcl.ui.account.AccountListPage;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.Motion;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorController;\nimport org.jackhuang.hmcl.ui.download.DownloadPage;\nimport org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;\nimport org.jackhuang.hmcl.ui.main.LauncherSettingsPage;\nimport org.jackhuang.hmcl.ui.main.RootPage;\nimport org.jackhuang.hmcl.ui.terracotta.TerracottaPage;\nimport org.jackhuang.hmcl.ui.versions.GameListPage;\nimport org.jackhuang.hmcl.ui.versions.VersionPage;\nimport org.jackhuang.hmcl.ui.versions.Versions;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.i18n.SupportedLocale;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.time.LocalDate;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class Controllers {\n    public static final String JAVA_VERSION_TIP = \"javaVersion\";\n    public static final String JAVA_INTERPRETED_MODE_TIP = \"javaInterpretedMode\";\n    public static final String SOFTWARE_RENDERING = \"softwareRendering\";\n    public static final String APRIL_FOOLS = \"aprilFools\";\n\n    public static final int MIN_WIDTH = 800 + 2 + 16; // bg width + border width*2 + shadow width*2\n    public static final int MIN_HEIGHT = 450 + 2 + 40 + 16; // bg height + border width*2 + toolbar height + shadow width*2\n    public static final Screen SCREEN = Screen.getPrimary();\n    private static InvalidationListener stageSizeChangeListener;\n    private static DoubleProperty stageX = new SimpleDoubleProperty();\n    private static DoubleProperty stageY = new SimpleDoubleProperty();\n    private static DoubleProperty stageWidth = new SimpleDoubleProperty();\n    private static DoubleProperty stageHeight = new SimpleDoubleProperty();\n\n    private static Scene scene;\n    private static Stage stage;\n    private static VersionPage versionPage;\n    private static Lazy<GameListPage> gameListPage = new Lazy<>(() -> {\n        GameListPage gameListPage = new GameListPage();\n        gameListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());\n        gameListPage.profilesProperty().bindContent(Profiles.profilesProperty());\n        FXUtils.applyDragListener(gameListPage, ModpackHelper::isFileModpackByExtension, modpacks -> {\n            Path modpack = modpacks.get(0);\n            Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n(\"install.modpack\"));\n        });\n        return gameListPage;\n    });\n    private static Lazy<RootPage> rootPage = new Lazy<>(RootPage::new);\n    private static DecoratorController decorator;\n    private static DownloadPage downloadPage;\n    private static Lazy<AccountListPage> accountListPage = new Lazy<>(() -> {\n        AccountListPage accountListPage = new AccountListPage();\n        accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty());\n        accountListPage.accountsProperty().bindContent(Accounts.getAccounts());\n        accountListPage.authServersProperty().bindContentBidirectional(config().getAuthlibInjectorServers());\n        return accountListPage;\n    });\n    private static LauncherSettingsPage settingsPage;\n    private static Lazy<TerracottaPage> terracottaPage = new Lazy<>(TerracottaPage::new);\n\n    private Controllers() {\n    }\n\n    public static Scene getScene() {\n        return scene;\n    }\n\n    public static Stage getStage() {\n        return stage;\n    }\n\n    // FXThread\n    public static VersionPage getVersionPage() {\n        if (versionPage == null) {\n            versionPage = new VersionPage();\n        }\n        return versionPage;\n    }\n\n    @FXThread\n    public static void prepareVersionPage() {\n        if (versionPage == null) {\n            LOG.info(\"Prepare the version page\");\n            versionPage = FXUtils.prepareNode(new VersionPage());\n        }\n    }\n\n    // FXThread\n    public static GameListPage getGameListPage() {\n        return gameListPage.get();\n    }\n\n    // FXThread\n    public static RootPage getRootPage() {\n        return rootPage.get();\n    }\n\n    // FXThread\n    public static LauncherSettingsPage getSettingsPage() {\n        if (settingsPage == null) {\n            settingsPage = new LauncherSettingsPage();\n        }\n        return settingsPage;\n    }\n\n    @FXThread\n    public static void prepareSettingsPage() {\n        if (settingsPage == null) {\n            LOG.info(\"Prepare the settings page\");\n            settingsPage = FXUtils.prepareNode(new LauncherSettingsPage());\n        }\n    }\n\n    // FXThread\n    public static AccountListPage getAccountListPage() {\n        return accountListPage.get();\n    }\n\n    // FXThread\n    public static DownloadPage getDownloadPage() {\n        if (downloadPage == null) {\n            downloadPage = new DownloadPage();\n        }\n        return downloadPage;\n    }\n\n    @FXThread\n    public static void prepareDownloadPage() {\n        if (downloadPage == null) {\n            LOG.info(\"Prepare the download page\");\n            downloadPage = FXUtils.prepareNode(new DownloadPage());\n        }\n    }\n\n    // FXThread\n    public static Node getTerracottaPage() {\n        return terracottaPage.get();\n    }\n\n    // FXThread\n    public static DecoratorController getDecorator() {\n        return decorator;\n    }\n\n    public static void onApplicationStop() {\n        stageSizeChangeListener = null;\n        if (stageX != null) {\n            config().setX(stageX.get() / SCREEN.getBounds().getWidth());\n            stageX = null;\n        }\n        if (stageY != null) {\n            config().setY(stageY.get() / SCREEN.getBounds().getHeight());\n            stageY = null;\n        }\n        if (stageHeight != null) {\n            config().setHeight(stageHeight.get());\n            stageHeight = null;\n        }\n        if (stageWidth != null) {\n            config().setWidth(stageWidth.get());\n            stageWidth = null;\n        }\n    }\n\n    public static void initialize(Stage stage) {\n        LOG.info(\"Start initializing application\");\n\n        LOG.info(\"April Fools: \" + AprilFools.isEnabled());\n\n        if (System.getProperty(\"prism.lcdtext\") == null) {\n            String fontAntiAliasing = globalConfig().getFontAntiAliasing();\n            if (\"lcd\".equalsIgnoreCase(fontAntiAliasing)) {\n                LOG.info(\"Enable sub-pixel antialiasing\");\n                System.getProperties().put(\"prism.lcdtext\", \"true\");\n            } else if (\"gray\".equalsIgnoreCase(fontAntiAliasing)\n                    || OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && SCREEN.getOutputScaleX() > 1) {\n                LOG.info(\"Disable sub-pixel antialiasing\");\n                System.getProperties().put(\"prism.lcdtext\", \"false\");\n            }\n        }\n\n        Controllers.stage = stage;\n\n        stageSizeChangeListener = o -> {\n            ReadOnlyDoubleProperty sourceProperty = (ReadOnlyDoubleProperty) o;\n            DoubleProperty targetProperty;\n            switch (sourceProperty.getName()) {\n                case \"x\": {\n                    targetProperty = stageX;\n                    break;\n                }\n                case \"y\": {\n                    targetProperty = stageY;\n                    break;\n                }\n                case \"width\": {\n                    targetProperty = stageWidth;\n                    break;\n                }\n                case \"height\": {\n                    targetProperty = stageHeight;\n                    break;\n                }\n                default: {\n                    targetProperty = null;\n                }\n            }\n\n            if (targetProperty != null\n                    && Controllers.stage != null\n                    && !Controllers.stage.isIconified()\n                    // https://github.com/HMCL-dev/HMCL/issues/4290\n                    && (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS ||\n                    !Controllers.stage.isFullScreen() && !Controllers.stage.isMaximized())\n            ) {\n                targetProperty.set(sourceProperty.get());\n            }\n        };\n\n        WeakInvalidationListener weakListener = new WeakInvalidationListener(stageSizeChangeListener);\n\n        double initWidth = Math.max(MIN_WIDTH, config().getWidth());\n        double initHeight = Math.max(MIN_HEIGHT, config().getHeight());\n\n        {\n            double initX = config().getX() * SCREEN.getBounds().getWidth();\n            double initY = config().getY() * SCREEN.getBounds().getHeight();\n\n            boolean invalid = true;\n            double border = 20D;\n            for (Screen screen : Screen.getScreens()) {\n                Rectangle2D bound = screen.getBounds();\n\n                if (bound.getMinX() + border <= initX + initWidth && initX <= bound.getMaxX() - border && bound.getMinY() + border <= initY && initY <= bound.getMaxY() - border) {\n                    invalid = false;\n                    break;\n                }\n            }\n\n            if (invalid) {\n                initX = (0.5D - initWidth / SCREEN.getBounds().getWidth() / 2) * SCREEN.getBounds().getWidth();\n                initY = (0.5D - initHeight / SCREEN.getBounds().getHeight() / 2) * SCREEN.getBounds().getHeight();\n            }\n\n            stage.setX(initX);\n            stage.setY(initY);\n            stageX.set(initX);\n            stageY.set(initY);\n        }\n\n        stage.setHeight(initHeight);\n        stage.setWidth(initWidth);\n        stageHeight.set(initHeight);\n        stageWidth.set(initWidth);\n        stage.xProperty().addListener(weakListener);\n        stage.yProperty().addListener(weakListener);\n        stage.heightProperty().addListener(weakListener);\n        stage.widthProperty().addListener(weakListener);\n\n        stage.setOnCloseRequest(e -> Launcher.stopApplication());\n\n        decorator = new DecoratorController(stage, getRootPage());\n\n        if (config().getCommonDirType() == EnumCommonDirectory.CUSTOM &&\n                !FileUtils.canCreateDirectory(config().getCommonDirectory())) {\n            config().setCommonDirType(EnumCommonDirectory.DEFAULT);\n            dialog(i18n(\"launcher.cache_directory.invalid\"));\n        }\n\n        Lang.thread(JavaManager::initialize, \"Search Java\", true);\n\n        scene = new Scene(decorator.getDecorator());\n        scene.setFill(Color.TRANSPARENT);\n        stage.setMinWidth(MIN_WIDTH);\n        stage.setMinHeight(MIN_HEIGHT);\n        decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty());\n        decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty());\n        StyleSheets.init(scene);\n\n        FXUtils.setIcon(stage);\n        stage.setTitle(Metadata.FULL_TITLE);\n        stage.initStyle(StageStyle.TRANSPARENT);\n        stage.setScene(scene);\n\n        if (AnimationUtils.playWindowAnimation()) {\n            Timeline timeline = new Timeline(\n                    new KeyFrame(Duration.millis(0),\n                            new KeyValue(decorator.getDecorator().opacityProperty(), 0, Motion.EASE),\n                            new KeyValue(decorator.getDecorator().scaleXProperty(), 0.8, Motion.EASE),\n                            new KeyValue(decorator.getDecorator().scaleYProperty(), 0.8, Motion.EASE),\n                            new KeyValue(decorator.getDecorator().scaleZProperty(), 0.8, Motion.EASE)\n                    ),\n                    new KeyFrame(Duration.millis(600),\n                            new KeyValue(decorator.getDecorator().opacityProperty(), 1, Motion.EASE),\n                            new KeyValue(decorator.getDecorator().scaleXProperty(), 1, Motion.EASE),\n                            new KeyValue(decorator.getDecorator().scaleYProperty(), 1, Motion.EASE),\n                            new KeyValue(decorator.getDecorator().scaleZProperty(), 1, Motion.EASE)\n                    )\n            );\n            timeline.play();\n        }\n\n        if (!Architecture.SYSTEM_ARCH.isX86() && globalConfig().getPlatformPromptVersion() < 1) {\n            Runnable continueAction = () -> globalConfig().setPlatformPromptVersion(1);\n\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && Architecture.SYSTEM_ARCH == Architecture.ARM64) {\n                Controllers.dialog(i18n(\"fatal.unsupported_platform.macos_arm64\"), null, MessageType.INFO, continueAction);\n            } else if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && Architecture.SYSTEM_ARCH == Architecture.ARM64) {\n                Controllers.dialog(i18n(\"fatal.unsupported_platform.windows_arm64\"), null, MessageType.INFO, continueAction);\n            } else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX &&\n                    (Architecture.SYSTEM_ARCH == Architecture.LOONGARCH64\n                            || Architecture.SYSTEM_ARCH == Architecture.LOONGARCH64_OW\n                            || Architecture.SYSTEM_ARCH == Architecture.MIPS64EL)) {\n                Controllers.dialog(i18n(\"fatal.unsupported_platform.loongarch\"), null, MessageType.INFO, continueAction);\n            } else {\n                Controllers.dialog(i18n(\"fatal.unsupported_platform\"), null, MessageType.WARNING, continueAction);\n            }\n        }\n\n        if (JavaRuntime.CURRENT_VERSION < Metadata.MINIMUM_SUPPORTED_JAVA_VERSION) {\n            Number shownTipVersion = null;\n            try {\n                shownTipVersion = (Number) config().getShownTips().get(JAVA_VERSION_TIP);\n            } catch (ClassCastException e) {\n                LOG.warning(\"Invalid type for shown tips key: \" + JAVA_VERSION_TIP, e);\n            }\n            if (shownTipVersion == null || shownTipVersion.intValue() < Metadata.MINIMUM_SUPPORTED_JAVA_VERSION) {\n                MessageDialogPane.Builder builder = new MessageDialogPane.Builder(i18n(\"fatal.deprecated_java_version\"), null, MessageType.WARNING);\n                String downloadLink = Metadata.getSuggestedJavaDownloadLink();\n                if (downloadLink != null)\n                    builder.addHyperLink(\n                            i18n(\"fatal.deprecated_java_version.download_link\", Metadata.RECOMMENDED_JAVA_VERSION),\n                            downloadLink\n                    );\n                Controllers.dialog(builder\n                        .ok(() -> config().getShownTips().put(JAVA_VERSION_TIP, Metadata.MINIMUM_SUPPORTED_JAVA_VERSION))\n                        .build());\n            }\n        }\n\n        // Check whether JIT is enabled in the current environment\n        if (!JavaRuntime.CURRENT_JIT_ENABLED && !Boolean.TRUE.equals(config().getShownTips().get(JAVA_INTERPRETED_MODE_TIP))) {\n            Controllers.dialog(new MessageDialogPane.Builder(i18n(\"warning.java_interpreted_mode\"), i18n(\"message.warning\"), MessageType.WARNING)\n                    .ok(null)\n                    .addCancel(i18n(\"button.do_not_show_again\"), () ->\n                            config().getShownTips().put(JAVA_INTERPRETED_MODE_TIP, true))\n                    .build());\n        }\n\n        // Check whether hardware acceleration is enabled\n        if (!FXUtils.GPU_ACCELERATION_ENABLED && !Boolean.TRUE.equals(config().getShownTips().get(SOFTWARE_RENDERING))) {\n            Controllers.dialog(new MessageDialogPane.Builder(i18n(\"warning.software_rendering\"), i18n(\"message.warning\"), MessageType.WARNING)\n                    .ok(null)\n                    .addCancel(i18n(\"button.do_not_show_again\"), () ->\n                            config().getShownTips().put(SOFTWARE_RENDERING, true))\n                    .build());\n        }\n\n        if (globalConfig().getAgreementVersion() < 1) {\n            JFXDialogLayout agreementPane = new JFXDialogLayout();\n            agreementPane.setHeading(new Label(i18n(\"launcher.agreement\")));\n            agreementPane.setBody(new Label(i18n(\"launcher.agreement.hint\")));\n            JFXHyperlink agreementLink = new JFXHyperlink(i18n(\"launcher.agreement\"));\n            agreementLink.setExternalLink(Metadata.EULA_URL);\n            JFXButton yesButton = new JFXButton(i18n(\"launcher.agreement.accept\"));\n            yesButton.getStyleClass().add(\"dialog-accept\");\n            yesButton.setOnAction(e -> {\n                globalConfig().setAgreementVersion(1);\n                agreementPane.fireEvent(new DialogCloseEvent());\n            });\n            JFXButton noButton = new JFXButton(i18n(\"launcher.agreement.decline\"));\n            noButton.getStyleClass().add(\"dialog-cancel\");\n            noButton.setOnAction(e -> javafx.application.Platform.exit());\n            agreementPane.setActions(agreementLink, yesButton, noButton);\n            Controllers.dialog(agreementPane);\n        }\n\n        aprilFools:\n        if (AprilFools.isEnabled()) {\n            int currentYear = LocalDate.now().getYear();\n            if (config().getShownTips().get(APRIL_FOOLS) instanceof Number year && year.intValue() >= currentYear)\n                break aprilFools;\n\n            if (!I18n.getLocale().getLocale().getLanguage().equals(\"zh\"))\n                break aprilFools;\n\n            SupportedLocale lzh = SupportedLocale.getSupportedLocales().stream()\n                    .filter(locale -> \"lzh\".equals(locale.getName()))\n                    .findFirst().orElse(null);\n\n            if (lzh == null) {\n                LOG.warning(\"No supported locale found for lzh\");\n                break aprilFools;\n            }\n\n            Runnable updateShowTips = () -> config().getShownTips().put(APRIL_FOOLS, currentYear);\n\n            Controllers.confirmWithCountdown(i18n(\"launcher.april_fools.switch_lzh\"), null, 10,\n                    MessageType.QUESTION, () -> {\n                        Controllers.confirm(i18n(\"launcher.april_fools.switch_lzh.confirm\"), null, MessageType.QUESTION, () -> {\n                            LOG.info(\"Switching locale to \" + lzh);\n\n                            updateShowTips.run();\n                            config().setLocalization(lzh);\n\n                            Controllers.onApplicationStop();\n\n                            try {\n                                FileSaver.waitForAllSaves();\n                            } catch (InterruptedException ignored) {\n                                // Ignore\n                            }\n\n                            try {\n                                Restarter.restartSelf();\n                            } catch (IOException e) {\n                                LOG.warning(\"Failed to restart self\", e);\n                            }\n\n                            Platform.exit();\n                        }, updateShowTips);\n                    }, updateShowTips);\n        }\n    }\n\n    public static void dialog(Region content) {\n        if (decorator != null)\n            decorator.showDialog(content);\n    }\n\n    public static void dialog(String text) {\n        dialog(text, null);\n    }\n\n    public static void dialog(String text, String title) {\n        dialog(text, title, MessageType.INFO);\n    }\n\n    public static void dialog(String text, String title, MessageType type) {\n        dialog(text, title, type, null);\n    }\n\n    public static void dialog(String text, String title, MessageType type, Runnable ok) {\n        dialog(new MessageDialogPane.Builder(text, title, type).ok(ok).build());\n    }\n\n    public static void confirm(String text, String title, Runnable yes, Runnable no) {\n        confirm(text, title, MessageType.QUESTION, yes, no);\n    }\n\n    public static void confirm(String text, String title, MessageType type, Runnable yes, Runnable no) {\n        dialog(new MessageDialogPane.Builder(text, title, type).yesOrNo(yes, no).build());\n    }\n\n    public static void confirmAction(String text, String title, MessageType type, ButtonBase actionButton) {\n        dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, null).build());\n    }\n\n    public static void confirmAction(String text, String title, MessageType type, ButtonBase actionButton, Runnable cancel) {\n        dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, cancel).build());\n    }\n\n    public static void confirmWithCountdown(String text, String title, int seconds, MessageType messageType,\n                                            @Nullable Runnable ok, @Nullable Runnable cancel) {\n        if (seconds <= 0)\n            throw new IllegalArgumentException(\"Seconds must be greater than 0\");\n\n        JFXButton btnOk = new JFXButton(i18n(\"button.ok\"));\n        btnOk.getStyleClass().add(messageType == MessageType.WARNING || messageType == MessageType.ERROR\n                ? \"dialog-error\"\n                : \"dialog-accept\");\n\n        if (ok != null)\n            btnOk.setOnAction(e -> ok.run());\n        btnOk.setDisable(true);\n\n        KeyFrame[] keyFrames = new KeyFrame[seconds + 1];\n        for (int i = 0; i < seconds; i++) {\n            keyFrames[i] = new KeyFrame(Duration.seconds(i),\n                    new KeyValue(btnOk.textProperty(), i18n(\"button.ok.countdown\", seconds - i)));\n        }\n        keyFrames[seconds] = new KeyFrame(Duration.seconds(seconds),\n                new KeyValue(btnOk.textProperty(), i18n(\"button.ok\")),\n                new KeyValue(btnOk.disableProperty(), false));\n\n        Timeline timeline = new Timeline(keyFrames);\n        confirmAction(text, title, messageType, btnOk, () -> {\n            timeline.stop();\n            if (cancel != null)\n                cancel.run();\n        });\n        timeline.play();\n    }\n\n    public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult) {\n        return prompt(title, onResult, \"\");\n    }\n\n    public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult, String initialValue, ValidatorBase... validators) {\n        InputDialogPane pane = new InputDialogPane(title, initialValue, onResult, validators);\n        dialog(pane);\n        return pane.getCompletableFuture();\n    }\n\n    public static CompletableFuture<List<PromptDialogPane.Builder.Question<?>>> prompt(PromptDialogPane.Builder builder) {\n        PromptDialogPane pane = new PromptDialogPane(builder);\n        dialog(pane);\n        return pane.getCompletableFuture();\n    }\n\n    public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title, TaskCancellationAction onCancel) {\n        TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);\n        pane.setTitle(title);\n        pane.setExecutor(executor);\n        dialog(pane);\n        return pane;\n    }\n\n    public static TaskExecutorDialogPane taskDialog(Task<?> task, String title, TaskCancellationAction onCancel) {\n        TaskExecutor executor = task.executor();\n        TaskExecutorDialogPane pane = taskDialog(executor, title, onCancel);\n        executor.start();\n        return pane;\n    }\n\n    public static void navigate(Node node) {\n        decorator.navigate(node, ContainerAnimations.NAVIGATION, Motion.SHORT4, Motion.EASE);\n    }\n\n    public static void navigateForward(Node node) {\n        decorator.navigate(node, ContainerAnimations.FORWARD, Motion.SHORT4, Motion.EASE);\n    }\n\n    public static void showToast(String content) {\n        decorator.showToast(content);\n    }\n\n    public static void onHyperlinkAction(String href) {\n        if (href.startsWith(\"hmcl://\")) {\n            switch (href) {\n                case \"hmcl://settings/feedback\":\n                    Controllers.getSettingsPage().showFeedback();\n                    Controllers.navigate(Controllers.getSettingsPage());\n                    break;\n                case \"hmcl://game/launch\":\n                    Profile profile = Profiles.getSelectedProfile();\n                    Versions.launch(profile, profile.getSelectedVersion(), LauncherHelper::setKeep);\n                    break;\n            }\n        } else {\n            FXUtils.openLink(href);\n        }\n    }\n\n    public static boolean isStopped() {\n        return decorator == null;\n    }\n\n    public static void shutdown() {\n        rootPage = null;\n        versionPage = null;\n        gameListPage = null;\n        downloadPage = null;\n        accountListPage = null;\n        settingsPage = null;\n        terracottaPage = null;\n        decorator = null;\n        stage = null;\n        scene = null;\n        onApplicationStop();\n\n        FXUtils.shutdown();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.geometry.Pos;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextArea;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport javafx.stage.Stage;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.countly.CrashReport;\nimport org.jackhuang.hmcl.upgrade.UpdateChecker;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\n/**\n * @author huangyuhui\n */\npublic class CrashWindow extends Stage {\n\n    public CrashWindow(CrashReport report) {\n        Label lblCrash = new Label();\n        if (report.getThrowable() instanceof InternalError)\n            lblCrash.setText(i18n(\"launcher.crash.java_internal_error\"));\n        else if (UpdateChecker.isOutdated())\n            lblCrash.setText(i18n(\"launcher.crash.hmcl_out_dated\"));\n        else\n            lblCrash.setText(i18n(\"launcher.crash\"));\n        lblCrash.setWrapText(true);\n\n        TextArea textArea = new TextArea();\n        textArea.setText(report.getDisplayText());\n        textArea.setEditable(false);\n\n        Button btnContact = new Button();\n        btnContact.setText(i18n(\"launcher.contact\"));\n        btnContact.setOnAction(event -> FXUtils.openLink(Metadata.CONTACT_URL));\n        HBox box = new HBox();\n        box.setStyle(\"-fx-padding: 8px;\");\n        box.getChildren().add(btnContact);\n        box.setAlignment(Pos.CENTER_RIGHT);\n\n        BorderPane pane = new BorderPane();\n        StackPane stackPane = new StackPane();\n        stackPane.setStyle(\"-fx-padding: 8px;\");\n        stackPane.getChildren().add(lblCrash);\n        pane.setTop(stackPane);\n        pane.setCenter(textArea);\n        pane.setBottom(box);\n\n        Scene scene = new Scene(pane, 800, 480);\n        setScene(scene);\n        FXUtils.setIcon(this);\n        setTitle(i18n(\"message.error\"));\n\n        setOnCloseRequest(e -> javafx.application.Platform.exit());\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport org.jackhuang.hmcl.auth.*;\nimport org.jackhuang.hmcl.ui.account.ClassicAccountLoginDialog;\nimport org.jackhuang.hmcl.ui.account.MicrosoftAccountLoginPane;\n\nimport java.util.Optional;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\n\npublic final class DialogController {\n    private DialogController() {\n    }\n\n    public static AuthInfo logIn(Account account) throws CancellationException, AuthenticationException, InterruptedException {\n        if (account instanceof ClassicAccount) {\n            CountDownLatch latch = new CountDownLatch(1);\n            AtomicReference<AuthInfo> res = new AtomicReference<>(null);\n            runInFX(() -> {\n                ClassicAccountLoginDialog pane = new ClassicAccountLoginDialog((ClassicAccount) account, it -> {\n                    res.set(it);\n                    latch.countDown();\n                }, latch::countDown);\n                Controllers.dialog(pane);\n            });\n            latch.await();\n            return Optional.ofNullable(res.get()).orElseThrow(CancellationException::new);\n        } else if (account instanceof OAuthAccount) {\n            CountDownLatch latch = new CountDownLatch(1);\n            AtomicReference<AuthInfo> res = new AtomicReference<>(null);\n            runInFX(() -> {\n                MicrosoftAccountLoginPane pane = new MicrosoftAccountLoginPane(account, it -> {\n                    res.set(it);\n                    latch.countDown();\n                }, latch::countDown, false);\n                Controllers.dialog(pane);\n            });\n            latch.await();\n            return Optional.ofNullable(res.get()).orElseThrow(CancellationException::new);\n        }\n        return account.logIn();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport com.jfoenix.controls.JFXDialog;\nimport javafx.application.Platform;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.event.EventHandler;\nimport javafx.scene.Node;\nimport javafx.scene.layout.StackPane;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.ui.construct.DialogAware;\nimport org.jackhuang.hmcl.ui.construct.DialogCloseEvent;\nimport org.jackhuang.hmcl.ui.construct.JFXDialogPane;\nimport org.jackhuang.hmcl.ui.decorator.Decorator;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Optional;\nimport java.util.function.Consumer;\n\npublic final class DialogUtils {\n    private DialogUtils() {\n    }\n\n    public static final String PROPERTY_DIALOG_INSTANCE = DialogUtils.class.getName() + \".dialog.instance\";\n    public static final String PROPERTY_DIALOG_PANE_INSTANCE = DialogUtils.class.getName() + \".dialog.pane.instance\";\n    public static final String PROPERTY_DIALOG_CLOSE_HANDLER = DialogUtils.class.getName() + \".dialog.closeListener\";\n\n    public static final String PROPERTY_PARENT_PANE_REF = DialogUtils.class.getName() + \".dialog.parentPaneRef\";\n    public static final String PROPERTY_PARENT_DIALOG_REF = DialogUtils.class.getName() + \".dialog.parentDialogRef\";\n\n    public static void show(Decorator decorator, Node content) {\n        if (decorator.getDrawerWrapper() == null) {\n            Platform.runLater(() -> show(decorator, content));\n            return;\n        }\n\n        show(decorator.getDrawerWrapper(), content, (dialog) -> {\n            JFXDialogPane pane = (JFXDialogPane) dialog.getContent();\n            decorator.capableDraggingWindow(dialog);\n            decorator.forbidDraggingWindow(pane);\n            dialog.setDialogContainer(decorator.getDrawerWrapper());\n        });\n    }\n\n    public static void show(StackPane container, Node content) {\n        show(container, content, null);\n    }\n\n    public static void show(StackPane container, Node content, @Nullable Consumer<JFXDialog> onDialogCreated) {\n        FXUtils.checkFxUserThread();\n\n        JFXDialog dialog = (JFXDialog) container.getProperties().get(PROPERTY_DIALOG_INSTANCE);\n        JFXDialogPane dialogPane = (JFXDialogPane) container.getProperties().get(PROPERTY_DIALOG_PANE_INSTANCE);\n\n        if (dialog == null) {\n            dialog = new JFXDialog(AnimationUtils.isAnimationEnabled()\n                    ? JFXDialog.DialogTransition.CENTER\n                    : JFXDialog.DialogTransition.NONE);\n            dialogPane = new JFXDialogPane();\n\n            dialog.setContent(dialogPane);\n            dialog.setDialogContainer(container);\n            dialog.setOverlayClose(false);\n\n            container.getProperties().put(PROPERTY_DIALOG_INSTANCE, dialog);\n            container.getProperties().put(PROPERTY_DIALOG_PANE_INSTANCE, dialogPane);\n\n            if (onDialogCreated != null) {\n                onDialogCreated.accept(dialog);\n            }\n\n            dialog.show();\n        }\n\n        content.getProperties().put(PROPERTY_PARENT_PANE_REF, dialogPane);\n        content.getProperties().put(PROPERTY_PARENT_DIALOG_REF, dialog);\n\n        dialogPane.push(content);\n\n        EventHandler<DialogCloseEvent> handler = event -> close(content);\n        content.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);\n        content.addEventHandler(DialogCloseEvent.CLOSE, handler);\n\n        handleDialogShown(dialog, content);\n    }\n\n    private static void handleDialogShown(JFXDialog dialog, Node node) {\n        if (dialog.isVisible()) {\n            dialog.requestFocus();\n            if (node instanceof DialogAware dialogAware)\n                dialogAware.onDialogShown();\n        } else {\n            dialog.visibleProperty().addListener(new ChangeListener<>() {\n                @Override\n                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {\n                    if (newValue) {\n                        dialog.requestFocus();\n                        if (node instanceof DialogAware dialogAware)\n                            dialogAware.onDialogShown();\n                        observable.removeListener(this);\n                    }\n                }\n            });\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static void close(Node content) {\n        FXUtils.checkFxUserThread();\n\n        Optional.ofNullable(content.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))\n                .ifPresent(handler -> content.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));\n\n        JFXDialogPane pane = (JFXDialogPane) content.getProperties().get(PROPERTY_PARENT_PANE_REF);\n        JFXDialog dialog = (JFXDialog) content.getProperties().get(PROPERTY_PARENT_DIALOG_REF);\n\n        if (dialog != null && pane != null) {\n            if (pane.size() == 1 && pane.peek().orElse(null) == content) {\n                dialog.setOnDialogClosed(e -> pane.pop(content));\n                dialog.close();\n\n                StackPane container = dialog.getDialogContainer();\n                if (container != null) {\n                    container.getProperties().remove(PROPERTY_DIALOG_INSTANCE);\n                    container.getProperties().remove(PROPERTY_DIALOG_PANE_INSTANCE);\n                    container.getProperties().remove(PROPERTY_PARENT_DIALOG_REF);\n                    container.getProperties().remove(PROPERTY_PARENT_PANE_REF);\n                }\n            } else {\n                pane.pop(content);\n            }\n\n            if (content instanceof DialogAware dialogAware) {\n                dialogAware.onDialogClosed();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport com.jfoenix.controls.*;\nimport javafx.animation.Animation;\nimport javafx.animation.Interpolator;\nimport javafx.animation.PauseTransition;\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.WeakListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.*;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableBooleanValue;\nimport javafx.beans.value.ObservableValue;\nimport javafx.beans.value.WeakChangeListener;\nimport javafx.collections.ObservableMap;\nimport javafx.event.Event;\nimport javafx.event.EventDispatcher;\nimport javafx.event.EventHandler;\nimport javafx.event.EventType;\nimport javafx.geometry.Bounds;\nimport javafx.geometry.Rectangle2D;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.Parent;\nimport javafx.scene.Scene;\nimport javafx.scene.control.*;\nimport javafx.scene.control.skin.VirtualFlow;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.input.*;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.Region;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\nimport javafx.scene.shape.Rectangle;\nimport javafx.scene.text.Text;\nimport javafx.scene.text.TextFlow;\nimport javafx.stage.FileChooser;\nimport javafx.stage.Screen;\nimport javafx.stage.Stage;\nimport javafx.util.Callback;\nimport javafx.util.Duration;\nimport javafx.util.StringConverter;\nimport org.jackhuang.hmcl.setting.StyleSheets;\nimport org.jackhuang.hmcl.task.CacheFileTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.ui.animation.Motion;\nimport org.jackhuang.hmcl.ui.construct.IconedMenuItem;\nimport org.jackhuang.hmcl.ui.construct.MenuSeparator;\nimport org.jackhuang.hmcl.ui.construct.PopupMenu;\nimport org.jackhuang.hmcl.ui.image.ImageLoader;\nimport org.jackhuang.hmcl.ui.image.ImageUtils;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.ResourceNotFoundError;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.javafx.ExtendedProperties;\nimport org.jackhuang.hmcl.util.javafx.SafeStringConverter;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jetbrains.annotations.Nullable;\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.NodeList;\nimport org.xml.sax.InputSource;\nimport org.xml.sax.SAXException;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.parsers.ParserConfigurationException;\nimport java.io.BufferedInputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.lang.ref.WeakReference;\nimport java.net.HttpURLConnection;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URLConnection;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.PathMatcher;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.Lang.thread;\nimport static org.jackhuang.hmcl.util.Lang.tryCast;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class FXUtils {\n    private FXUtils() {\n    }\n\n    public static final int JAVAFX_MAJOR_VERSION;\n\n    public static final String GRAPHICS_PIPELINE;\n    public static final boolean GPU_ACCELERATION_ENABLED;\n\n    static {\n        String pipelineName = \"\";\n\n        try {\n            Object pipeline = Class.forName(\"com.sun.prism.GraphicsPipeline\").getMethod(\"getPipeline\").invoke(null);\n            if (pipeline != null) {\n                pipelineName = pipeline.getClass().getName();\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get prism pipeline\", e);\n        }\n\n        GRAPHICS_PIPELINE = pipelineName;\n        GPU_ACCELERATION_ENABLED = !pipelineName.endsWith(\".SWPipeline\");\n    }\n\n    /// @see Platform.Preferences\n    public static final @Nullable ObservableMap<String, Object> PREFERENCES;\n    public static final @Nullable ObservableBooleanValue DARK_MODE;\n    public static final @Nullable Boolean REDUCED_MOTION;\n    public static final @Nullable ReadOnlyObjectProperty<Color> ACCENT_COLOR;\n\n    public static final @Nullable MethodHandle TEXT_TRUNCATED_PROPERTY;\n    public static final @Nullable MethodHandle FOCUS_VISIBLE_PROPERTY;\n\n    static {\n        String jfxVersion = System.getProperty(\"javafx.version\");\n        int majorVersion = -1;\n        if (jfxVersion != null) {\n            Matcher matcher = Pattern.compile(\"^(?<version>[0-9]+)\").matcher(jfxVersion);\n            if (matcher.find()) {\n                majorVersion = Lang.parseInt(matcher.group(), -1);\n            }\n        }\n        JAVAFX_MAJOR_VERSION = majorVersion;\n\n        ObservableMap<String, Object> preferences = null;\n        ObservableBooleanValue darkMode = null;\n        ReadOnlyObjectProperty<Color> accentColorProperty = null;\n        Boolean reducedMotion = null;\n        if (JAVAFX_MAJOR_VERSION >= 22) {\n            try {\n                MethodHandles.Lookup lookup = MethodHandles.publicLookup();\n                Class<?> preferencesClass = Class.forName(\"javafx.application.Platform$Preferences\");\n                @SuppressWarnings(\"unchecked\")\n                var preferences0 = (ObservableMap<String, Object>) lookup.findStatic(Platform.class, \"getPreferences\", MethodType.methodType(preferencesClass))\n                        .invoke();\n                preferences = preferences0;\n\n                @SuppressWarnings(\"unchecked\")\n                var colorSchemeProperty = (ReadOnlyObjectProperty<? extends Enum<?>>)\n                        lookup.findVirtual(preferencesClass, \"colorSchemeProperty\", MethodType.methodType(ReadOnlyObjectProperty.class))\n                                .invoke(preferences);\n\n                darkMode = Bindings.createBooleanBinding(() ->\n                        \"DARK\".equals(colorSchemeProperty.get().name()), colorSchemeProperty);\n\n                @SuppressWarnings(\"unchecked\")\n                var accentColorProperty0 = (ReadOnlyObjectProperty<Color>)\n                        lookup.findVirtual(preferencesClass, \"accentColorProperty\", MethodType.methodType(ReadOnlyObjectProperty.class))\n                                .invoke(preferences);\n                accentColorProperty = accentColorProperty0;\n\n                if (JAVAFX_MAJOR_VERSION >= 24) {\n                    reducedMotion = (boolean)\n                            lookup.findVirtual(preferencesClass, \"isReducedMotion\", MethodType.methodType(boolean.class))\n                                    .invoke(preferences);\n                }\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to get preferences\", e);\n            }\n        }\n        PREFERENCES = preferences;\n        DARK_MODE = darkMode;\n        REDUCED_MOTION = reducedMotion;\n        ACCENT_COLOR = accentColorProperty;\n\n        MethodHandle textTruncatedProperty = null;\n        if (JAVAFX_MAJOR_VERSION >= 23) {\n            try {\n                textTruncatedProperty = MethodHandles.publicLookup().findVirtual(\n                        Labeled.class,\n                        \"textTruncatedProperty\",\n                        MethodType.methodType(ReadOnlyBooleanProperty.class)\n                );\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to lookup textTruncatedProperty\", e);\n            }\n        }\n        TEXT_TRUNCATED_PROPERTY = textTruncatedProperty;\n\n        MethodHandle focusVisibleProperty = null;\n        if (JAVAFX_MAJOR_VERSION >= 19) {\n            try {\n                focusVisibleProperty = MethodHandles.publicLookup().findVirtual(\n                        Node.class,\n                        \"focusVisibleProperty\",\n                        MethodType.methodType(ReadOnlyBooleanProperty.class)\n                );\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to lookup focusVisibleProperty\", e);\n            }\n        }\n        FOCUS_VISIBLE_PROPERTY = focusVisibleProperty;\n    }\n\n    public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? \"Consolas\" : \"Monospace\";\n\n    public static final List<String> IMAGE_EXTENSIONS = Lang.immutableListOf(\n            \"png\", \"jpg\", \"jpeg\", \"bmp\", \"gif\", \"webp\", \"apng\"\n    );\n\n    private static final Map<String, Image> builtinImageCache = new ConcurrentHashMap<>();\n\n    public static void shutdown() {\n        builtinImageCache.clear();\n    }\n\n    public static void runInFX(Runnable runnable) {\n        if (Platform.isFxApplicationThread()) {\n            runnable.run();\n        } else {\n            Platform.runLater(runnable);\n        }\n    }\n\n    public static void checkFxUserThread() {\n        if (!Platform.isFxApplicationThread()) {\n            throw new IllegalStateException(\"Not on FX application thread; currentThread = \"\n                    + Thread.currentThread().getName());\n        }\n    }\n\n    public static InvalidationListener onInvalidating(Runnable action) {\n        return arg -> action.run();\n    }\n\n    public static <T> void onChange(ObservableValue<T> value, Consumer<T> consumer) {\n        value.addListener((a, b, c) -> consumer.accept(c));\n    }\n\n    public static <T> ChangeListener<T> onWeakChange(ObservableValue<T> value, Consumer<T> consumer) {\n        ChangeListener<T> listener = (a, b, c) -> consumer.accept(c);\n        value.addListener(new WeakChangeListener<>(listener));\n        return listener;\n    }\n\n    public static <T> void onChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {\n        consumer.accept(value.getValue());\n        onChange(value, consumer);\n    }\n\n    public static <T> ChangeListener<T> onWeakChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {\n        consumer.accept(value.getValue());\n        return onWeakChange(value, consumer);\n    }\n\n    public static InvalidationListener observeWeak(Runnable runnable, Observable... observables) {\n        InvalidationListener originalListener = observable -> runnable.run();\n        WeakInvalidationListener listener = new WeakInvalidationListener(originalListener);\n        for (Observable observable : observables) {\n            observable.addListener(listener);\n        }\n        runnable.run();\n        return originalListener;\n    }\n\n    public static void runLaterIf(BooleanSupplier condition, Runnable runnable) {\n        if (condition.getAsBoolean()) Platform.runLater(() -> runLaterIf(condition, runnable));\n        else runnable.run();\n    }\n\n    public static void limitSize(ImageView imageView, double maxWidth, double maxHeight) {\n        imageView.setPreserveRatio(true);\n        onChangeAndOperate(imageView.imageProperty(), image -> {\n            if (image != null && (image.getWidth() > maxWidth || image.getHeight() > maxHeight)) {\n                imageView.setFitHeight(maxHeight);\n                imageView.setFitWidth(maxWidth);\n            } else {\n                imageView.setFitHeight(-1);\n                imageView.setFitWidth(-1);\n            }\n        });\n    }\n\n    private static class ListenerPair<T> {\n        private final ObservableValue<T> value;\n        private final ChangeListener<? super T> listener;\n\n        ListenerPair(ObservableValue<T> value, ChangeListener<? super T> listener) {\n            this.value = value;\n            this.listener = listener;\n        }\n\n        void bind() {\n            value.addListener(listener);\n        }\n\n        void unbind() {\n            value.removeListener(listener);\n        }\n    }\n\n    public static <T> void addListener(Node node, String key, ObservableValue<T> value, Consumer<? super T> callback) {\n        ListenerPair<T> pair = new ListenerPair<>(value, (a, b, newValue) -> callback.accept(newValue));\n        node.getProperties().put(key, pair);\n        pair.bind();\n    }\n\n    public static void removeListener(Node node, String key) {\n        tryCast(node.getProperties().get(key), ListenerPair.class)\n                .ifPresent(info -> {\n                    info.unbind();\n                    node.getProperties().remove(key);\n                });\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T extends Event> void ignoreEvent(Node node, EventType<T> type, Predicate<? super T> filter) {\n        EventDispatcher oldDispatcher = node.getEventDispatcher();\n        node.setEventDispatcher((event, tail) -> {\n            EventType<?> t = event.getEventType();\n            while (t != null && t != type)\n                t = t.getSuperType();\n            if (t == type && filter.test((T) event)) {\n                return tail.dispatchEvent(event);\n            } else {\n                return oldDispatcher.dispatchEvent(event, tail);\n            }\n        });\n    }\n\n    public static void setValidateWhileTextChanged(Node field, boolean validate) {\n        if (field instanceof JFXTextField) {\n            if (validate) {\n                addListener(field, \"FXUtils.validation\", ((JFXTextField) field).textProperty(), o -> ((JFXTextField) field).validate());\n            } else {\n                removeListener(field, \"FXUtils.validation\");\n            }\n            ((JFXTextField) field).validate();\n        } else if (field instanceof JFXPasswordField) {\n            if (validate) {\n                addListener(field, \"FXUtils.validation\", ((JFXPasswordField) field).textProperty(), o -> ((JFXPasswordField) field).validate());\n            } else {\n                removeListener(field, \"FXUtils.validation\");\n            }\n            ((JFXPasswordField) field).validate();\n        } else\n            throw new IllegalArgumentException(\"Only JFXTextField and JFXPasswordField allowed\");\n    }\n\n    public static boolean getValidateWhileTextChanged(Node field) {\n        return field.getProperties().containsKey(\"FXUtils.validation\");\n    }\n\n    public static Rectangle setOverflowHidden(Region region) {\n        Rectangle rectangle = new Rectangle();\n        rectangle.widthProperty().bind(region.widthProperty());\n        rectangle.heightProperty().bind(region.heightProperty());\n        region.setClip(rectangle);\n        return rectangle;\n    }\n\n    public static Rectangle setOverflowHidden(Region region, double arc) {\n        Rectangle rectangle = setOverflowHidden(region);\n        rectangle.setArcWidth(arc);\n        rectangle.setArcHeight(arc);\n        return rectangle;\n    }\n\n    public static void setLimitWidth(Region region, double width) {\n        region.setMaxWidth(width);\n        region.setMinWidth(width);\n        region.setPrefWidth(width);\n    }\n\n    public static double getLimitWidth(Region region) {\n        return region.getMaxWidth();\n    }\n\n    public static void setLimitHeight(Region region, double height) {\n        region.setMaxHeight(height);\n        region.setMinHeight(height);\n        region.setPrefHeight(height);\n    }\n\n    public static double getLimitHeight(Region region) {\n        return region.getMaxHeight();\n    }\n\n    public static void limitCellWidth(ListView<?> listView, ListCell<?> cell) {\n        ReadOnlyDoubleProperty widthProperty;\n\n        if (listView.lookup(\".clipped-container\") instanceof Region clippedContainer) {\n            widthProperty = clippedContainer.widthProperty();\n        } else {\n            widthProperty = listView.widthProperty();\n        }\n\n        cell.maxWidthProperty().bind(widthProperty);\n        cell.prefWidthProperty().bind(widthProperty);\n        cell.minWidthProperty().bind(widthProperty);\n    }\n\n    public static void smoothScrolling(ScrollPane scrollPane) {\n        if (AnimationUtils.isAnimationEnabled())\n            ScrollUtils.addSmoothScrolling(scrollPane);\n    }\n\n    public static void smoothScrolling(VirtualFlow<?> virtualFlow) {\n        if (AnimationUtils.isAnimationEnabled())\n            ScrollUtils.addSmoothScrolling(virtualFlow);\n    }\n\n    /// If the current environment is JavaFX 23 or higher, this method returns [Labeled#textTruncatedProperty()];\n    /// Otherwise, it returns `null`.\n    public static @Nullable ReadOnlyBooleanProperty textTruncatedProperty(Labeled labeled) {\n        if (TEXT_TRUNCATED_PROPERTY != null) {\n            try {\n                return (ReadOnlyBooleanProperty) TEXT_TRUNCATED_PROPERTY.invokeExact(labeled);\n            } catch (RuntimeException | Error e) {\n                throw e;\n            } catch (Throwable e) {\n                throw new RuntimeException(e);\n            }\n        } else {\n            return null;\n        }\n    }\n\n    public static @Nullable ReadOnlyBooleanProperty focusVisibleProperty(Node node) {\n        if (FOCUS_VISIBLE_PROPERTY != null) {\n            try {\n                return (ReadOnlyBooleanProperty) FOCUS_VISIBLE_PROPERTY.invokeExact(node);\n            } catch (RuntimeException | Error e) {\n                throw e;\n            } catch (Throwable e) {\n                throw new RuntimeException(e);\n            }\n        } else {\n            return null;\n        }\n    }\n\n    private static final Duration TOOLTIP_FAST_SHOW_DELAY = Duration.millis(50);\n    private static final Duration TOOLTIP_SLOW_SHOW_DELAY = Duration.millis(500);\n    private static final Duration TOOLTIP_SHOW_DURATION = Duration.millis(5000);\n\n    public static void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) {\n        tooltip.setShowDelay(showDelay);\n        tooltip.setShowDuration(showDuration);\n        tooltip.setHideDelay(hideDelay);\n        Tooltip.install(node, tooltip);\n    }\n\n    public static void installFastTooltip(Node node, Tooltip tooltip) {\n        runInFX(() -> installTooltip(node, TOOLTIP_FAST_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip));\n    }\n\n    public static void installFastTooltip(Node node, String tooltip) {\n        installFastTooltip(node, new Tooltip(tooltip));\n    }\n\n    public static void installSlowTooltip(Node node, Tooltip tooltip) {\n        runInFX(() -> installTooltip(node, TOOLTIP_SLOW_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip));\n    }\n\n    public static void installSlowTooltip(Node node, String tooltip) {\n        installSlowTooltip(node, new Tooltip(tooltip));\n    }\n\n    public static void playAnimation(Node node, String animationKey, Animation animation) {\n        animationKey = \"hmcl.animations.\" + animationKey;\n        if (node.getProperties().get(animationKey) instanceof Animation oldAnimation)\n            oldAnimation.stop();\n        animation.play();\n        node.getProperties().put(animationKey, animation);\n    }\n\n    public static void openFolder(Path file) {\n        if (file.getFileSystem() != FileSystems.getDefault()) {\n            LOG.warning(\"Cannot open folder as the file system is not supported: \" + file);\n            return;\n        }\n\n        try {\n            Files.createDirectories(file);\n        } catch (IOException e) {\n            LOG.warning(\"Failed to create directory \" + file);\n            return;\n        }\n\n        String path = FileUtils.getAbsolutePath(file);\n\n        String openCommand;\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)\n            openCommand = \"explorer.exe\";\n        else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)\n            openCommand = \"/usr/bin/open\";\n        else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && Files.exists(Path.of(\"/usr/bin/xdg-open\")))\n            openCommand = \"/usr/bin/xdg-open\";\n        else\n            openCommand = null;\n\n        thread(() -> {\n            if (openCommand != null) {\n                try {\n                    int exitCode = SystemUtils.callExternalProcess(openCommand, path);\n\n                    // explorer.exe always return 1\n                    if (exitCode == 0 || (exitCode == 1 && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS))\n                        return;\n                    else\n                        LOG.warning(\"Open \" + path + \" failed with code \" + exitCode);\n                } catch (Throwable e) {\n                    LOG.warning(\"Unable to open \" + path + \" by executing \" + openCommand, e);\n                }\n            }\n\n            // Fallback to java.awt.Desktop::open\n            try {\n                java.awt.Desktop.getDesktop().open(file.toFile());\n            } catch (Throwable e) {\n                LOG.error(\"Unable to open \" + path + \" by java.awt.Desktop.getDesktop()::open\", e);\n            }\n        });\n    }\n\n    public static void showFileInExplorer(Path file) {\n        String path = file.toAbsolutePath().toString();\n\n        String[] openCommands;\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)\n            openCommands = new String[]{\"explorer.exe\", \"/select,\", path};\n        else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)\n            openCommands = new String[]{\"/usr/bin/open\", \"-R\", path};\n        else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && SystemUtils.which(\"dbus-send\") != null)\n            openCommands = new String[]{\n                    \"dbus-send\",\n                    \"--print-reply\",\n                    \"--dest=org.freedesktop.FileManager1\",\n                    \"/org/freedesktop/FileManager1\",\n                    \"org.freedesktop.FileManager1.ShowItems\",\n                    \"array:string:\" + file.toAbsolutePath().toUri(),\n                    \"string:\"\n            };\n        else\n            openCommands = null;\n\n        if (openCommands != null) {\n            thread(() -> {\n                try {\n                    int exitCode = SystemUtils.callExternalProcess(openCommands);\n\n                    // explorer.exe always return 1\n                    if (exitCode == 0 || (exitCode == 1 && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS))\n                        return;\n                    else\n                        LOG.warning(\"Show \" + path + \" in explorer failed with code \" + exitCode);\n                } catch (Throwable e) {\n                    LOG.warning(\"Unable to show \" + path + \" in explorer\", e);\n                }\n\n                // Fallback to open folder\n                openFolder(file.getParent());\n            });\n        } else {\n            // We do not have a universal method to show file in file manager.\n            openFolder(file.getParent());\n        }\n    }\n\n    private static final String[] linuxBrowsers = {\n            \"xdg-open\",\n            \"google-chrome\",\n            \"firefox\",\n            \"microsoft-edge\",\n            \"opera\",\n            \"konqueror\",\n            \"mozilla\"\n    };\n\n    /**\n     * Open URL in browser\n     *\n     * @param link null is allowed but will be ignored\n     */\n    public static void openLink(String link) {\n        if (link == null)\n            return;\n\n        String uri = NetworkUtils.encodeLocation(link);\n        thread(() -> {\n            try {\n                if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                    Runtime.getRuntime().exec(new String[]{\"rundll32.exe\", \"url.dll,FileProtocolHandler\", uri});\n                    return;\n                } else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n                    Runtime.getRuntime().exec(new String[]{\"open\", uri});\n                    return;\n                } else {\n                    for (String browser : linuxBrowsers) {\n                        Path path = SystemUtils.which(browser);\n                        if (path != null) {\n                            try {\n                                Runtime.getRuntime().exec(new String[]{path.toString(), uri});\n                                return;\n                            } catch (Throwable ignored) {\n                            }\n                        }\n                    }\n                    LOG.warning(\"No known browser found\");\n                }\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to open link: \" + link + \", fallback to java.awt.Desktop\", e);\n            }\n\n            try {\n                java.awt.Desktop.getDesktop().browse(new URI(uri));\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to open link: \" + link, e);\n            }\n        });\n    }\n\n    public static <T> void bind(JFXTextField textField, Property<T> property, StringConverter<T> converter) {\n        TextFieldBinding<T> binding = new TextFieldBinding<>(textField, property, converter);\n        binding.updateTextField();\n        textField.getProperties().put(\"FXUtils.bind.binding\", binding);\n        textField.focusedProperty().addListener(binding.focusedListener);\n        textField.sceneProperty().addListener(binding.sceneListener);\n        property.addListener(binding.propertyListener);\n    }\n\n    public static void bindInt(JFXTextField textField, Property<Number> property) {\n        bind(textField, property, SafeStringConverter.fromInteger());\n    }\n\n    public static void bindString(JFXTextField textField, Property<String> property) {\n        bind(textField, property, null);\n    }\n\n    public static void unbind(JFXTextField textField, Property<?> property) {\n        TextFieldBinding<?> binding = (TextFieldBinding<?>) textField.getProperties().remove(\"FXUtils.bind.binding\");\n        if (binding != null) {\n            textField.focusedProperty().removeListener(binding.focusedListener);\n            textField.sceneProperty().removeListener(binding.sceneListener);\n            property.removeListener(binding.propertyListener);\n        }\n    }\n\n    private static final class TextFieldBinding<T> {\n        private final JFXTextField textField;\n        private final Property<T> property;\n        private final StringConverter<T> converter;\n\n        public final ChangeListener<Boolean> focusedListener;\n        public final ChangeListener<Scene> sceneListener;\n        public final InvalidationListener propertyListener;\n\n        public TextFieldBinding(JFXTextField textField, Property<T> property, StringConverter<T> converter) {\n            this.textField = textField;\n            this.property = property;\n            this.converter = converter;\n\n            focusedListener = (observable, oldFocused, newFocused) -> {\n                if (oldFocused && !newFocused) {\n                    if (textField.validate()) {\n                        updateProperty();\n                    } else {\n                        // Rollback to old value\n                        updateTextField();\n                    }\n                }\n            };\n\n            sceneListener = (observable, oldScene, newScene) -> {\n                if (oldScene != null && newScene == null) {\n                    // Component is being removed from scene\n                    if (textField.validate()) {\n                        updateProperty();\n                    }\n                }\n            };\n\n            propertyListener = observable -> {\n                updateTextField();\n            };\n        }\n\n        public void updateProperty() {\n            String newText = textField.getText();\n            @SuppressWarnings(\"unchecked\")\n            T newValue = converter == null ? (T) newText : converter.fromString(newText);\n\n            if (!Objects.equals(newValue, property.getValue())) {\n                property.setValue(newValue);\n            }\n        }\n\n        public void updateTextField() {\n            T value = property.getValue();\n            textField.setText(converter == null ? (String) value : converter.toString(value));\n        }\n    }\n\n    private static final class EnumBidirectionalBinding<E extends Enum<E>> implements InvalidationListener, WeakListener {\n        private final WeakReference<JFXComboBox<E>> comboBoxRef;\n        private final WeakReference<Property<E>> propertyRef;\n        private final int hashCode;\n\n        private boolean updating = false;\n\n        private EnumBidirectionalBinding(JFXComboBox<E> comboBox, Property<E> property) {\n            this.comboBoxRef = new WeakReference<>(comboBox);\n            this.propertyRef = new WeakReference<>(property);\n            this.hashCode = System.identityHashCode(comboBox) ^ System.identityHashCode(property);\n        }\n\n        @Override\n        public void invalidated(Observable sourceProperty) {\n            if (!updating) {\n                final JFXComboBox<E> comboBox = comboBoxRef.get();\n                final Property<E> property = propertyRef.get();\n\n                if (comboBox == null || property == null) {\n                    if (comboBox != null) {\n                        comboBox.getSelectionModel().selectedItemProperty().removeListener(this);\n                    }\n\n                    if (property != null) {\n                        property.removeListener(this);\n                    }\n                } else {\n                    updating = true;\n                    try {\n                        if (property == sourceProperty) {\n                            E newValue = property.getValue();\n                            comboBox.getSelectionModel().select(newValue);\n                        } else {\n                            E newValue = comboBox.getSelectionModel().getSelectedItem();\n                            property.setValue(newValue);\n                        }\n                    } finally {\n                        updating = false;\n                    }\n                }\n            }\n        }\n\n        @Override\n        public boolean wasGarbageCollected() {\n            return comboBoxRef.get() == null || propertyRef.get() == null;\n        }\n\n        @Override\n        public int hashCode() {\n            return hashCode;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (!(o instanceof EnumBidirectionalBinding))\n                return false;\n\n            EnumBidirectionalBinding<?> that = (EnumBidirectionalBinding<?>) o;\n\n            final JFXComboBox<E> comboBox = this.comboBoxRef.get();\n            final Property<E> property = this.propertyRef.get();\n\n            final JFXComboBox<?> thatComboBox = that.comboBoxRef.get();\n            final Property<?> thatProperty = that.propertyRef.get();\n\n            if (comboBox == null || property == null || thatComboBox == null || thatProperty == null)\n                return false;\n\n            return comboBox == thatComboBox && property == thatProperty;\n        }\n    }\n\n    /**\n     * Bind combo box selection with given enum property bidirectionally.\n     * You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.\n     *\n     * @param comboBox the combo box being bound with {@code property}.\n     * @param property the property being bound with {@code combo box}.\n     * @see #unbindEnum(JFXComboBox, Property)\n     * @see ExtendedProperties#selectedItemPropertyFor(ComboBox)\n     */\n    public static <T extends Enum<T>> void bindEnum(JFXComboBox<T> comboBox, Property<T> property) {\n        EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);\n\n        comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);\n        property.removeListener(binding);\n\n        comboBox.getSelectionModel().select(property.getValue());\n        comboBox.getSelectionModel().selectedItemProperty().addListener(binding);\n        property.addListener(binding);\n    }\n\n    /**\n     * Unbind combo box selection with given enum property bidirectionally.\n     * You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.\n     *\n     * @param comboBox the combo box being bound with the property which can be inferred by {@code bindEnum}.\n     * @see #bindEnum(JFXComboBox, Property)\n     * @see ExtendedProperties#selectedItemPropertyFor(ComboBox)\n     */\n    public static <T extends Enum<T>> void unbindEnum(JFXComboBox<T> comboBox, Property<T> property) {\n        EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);\n        comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);\n        property.removeListener(binding);\n    }\n\n    private static final class PaintBidirectionalBinding implements InvalidationListener, WeakListener {\n        private final WeakReference<ColorPicker> colorPickerRef;\n        private final WeakReference<Property<Paint>> propertyRef;\n        private final int hashCode;\n\n        private boolean updating = false;\n\n        private PaintBidirectionalBinding(ColorPicker colorPicker, Property<Paint> property) {\n            this.colorPickerRef = new WeakReference<>(colorPicker);\n            this.propertyRef = new WeakReference<>(property);\n            this.hashCode = System.identityHashCode(colorPicker) ^ System.identityHashCode(property);\n        }\n\n        @Override\n        public void invalidated(Observable sourceProperty) {\n            if (!updating) {\n                final ColorPicker colorPicker = colorPickerRef.get();\n                final Property<Paint> property = propertyRef.get();\n\n                if (colorPicker == null || property == null) {\n                    if (colorPicker != null) {\n                        colorPicker.valueProperty().removeListener(this);\n                    }\n\n                    if (property != null) {\n                        property.removeListener(this);\n                    }\n                } else {\n                    updating = true;\n                    try {\n                        if (property == sourceProperty) {\n                            Paint newValue = property.getValue();\n                            if (newValue instanceof Color)\n                                colorPicker.setValue((Color) newValue);\n                            else\n                                colorPicker.setValue(null);\n                        } else {\n                            Paint newValue = colorPicker.getValue();\n                            property.setValue(newValue);\n                        }\n                    } finally {\n                        updating = false;\n                    }\n                }\n            }\n        }\n\n        @Override\n        public boolean wasGarbageCollected() {\n            return colorPickerRef.get() == null || propertyRef.get() == null;\n        }\n\n        @Override\n        public int hashCode() {\n            return hashCode;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (!(o instanceof FXUtils.PaintBidirectionalBinding))\n                return false;\n\n            var that = (FXUtils.PaintBidirectionalBinding) o;\n\n            final ColorPicker colorPicker = this.colorPickerRef.get();\n            final Property<Paint> property = this.propertyRef.get();\n\n            final ColorPicker thatColorPicker = that.colorPickerRef.get();\n            final Property<?> thatProperty = that.propertyRef.get();\n\n            if (colorPicker == null || property == null || thatColorPicker == null || thatProperty == null)\n                return false;\n\n            return colorPicker == thatColorPicker && property == thatProperty;\n        }\n    }\n\n    public static void bindPaint(ColorPicker colorPicker, Property<Paint> property) {\n        PaintBidirectionalBinding binding = new PaintBidirectionalBinding(colorPicker, property);\n\n        colorPicker.valueProperty().removeListener(binding);\n        property.removeListener(binding);\n\n        if (property.getValue() instanceof Color)\n            colorPicker.setValue((Color) property.getValue());\n        else\n            colorPicker.setValue(null);\n\n        colorPicker.valueProperty().addListener(binding);\n        property.addListener(binding);\n    }\n\n    private static final class WindowsSizeBidirectionalBinding implements InvalidationListener, WeakListener {\n        private final WeakReference<JFXComboBox<String>> comboBoxRef;\n        private final WeakReference<IntegerProperty> widthPropertyRef;\n        private final WeakReference<IntegerProperty> heightPropertyRef;\n\n        private final int hashCode;\n\n        private boolean updating = false;\n\n        private WindowsSizeBidirectionalBinding(JFXComboBox<String> comboBox,\n                                                IntegerProperty widthProperty,\n                                                IntegerProperty heightProperty) {\n            this.comboBoxRef = new WeakReference<>(comboBox);\n            this.widthPropertyRef = new WeakReference<>(widthProperty);\n            this.heightPropertyRef = new WeakReference<>(heightProperty);\n            this.hashCode = System.identityHashCode(comboBox)\n                    ^ System.identityHashCode(widthProperty)\n                    ^ System.identityHashCode(heightProperty);\n        }\n\n        @Override\n        public void invalidated(Observable observable) {\n            if (!updating) {\n                var comboBox = this.comboBoxRef.get();\n                var widthProperty = this.widthPropertyRef.get();\n                var heightProperty = this.heightPropertyRef.get();\n\n                if (comboBox == null || widthProperty == null || heightProperty == null) {\n                    if (comboBox != null) {\n                        comboBox.focusedProperty().removeListener(this);\n                        comboBox.sceneProperty().removeListener(this);\n                    }\n                    if (widthProperty != null)\n                        widthProperty.removeListener(this);\n                    if (heightProperty != null)\n                        heightProperty.removeListener(this);\n                } else {\n                    updating = true;\n                    try {\n                        int width = widthProperty.get();\n                        int height = heightProperty.get();\n\n                        if (observable instanceof ReadOnlyProperty<?>\n                                && ((ReadOnlyProperty<?>) observable).getBean() == comboBox) {\n                            String value = comboBox.valueProperty().get();\n                            if (value == null)\n                                value = \"\";\n                            int idx = value.indexOf('x');\n                            if (idx < 0)\n                                idx = value.indexOf('*');\n\n                            if (idx < 0) {\n                                LOG.warning(\"Bad window size: \" + value);\n                                comboBox.setValue(width + \"x\" + height);\n                                return;\n                            }\n\n                            String widthStr = value.substring(0, idx).trim();\n                            String heightStr = value.substring(idx + 1).trim();\n\n                            int newWidth;\n                            int newHeight;\n                            try {\n                                newWidth = Integer.parseInt(widthStr);\n                                newHeight = Integer.parseInt(heightStr);\n                            } catch (NumberFormatException e) {\n                                LOG.warning(\"Bad window size: \" + value);\n                                comboBox.setValue(width + \"x\" + height);\n                                return;\n                            }\n\n                            widthProperty.set(newWidth);\n                            heightProperty.set(newHeight);\n                        } else {\n                            comboBox.setValue(width + \"x\" + height);\n                        }\n                    } finally {\n                        updating = false;\n                    }\n                }\n            }\n        }\n\n        @Override\n        public boolean wasGarbageCollected() {\n            return this.comboBoxRef.get() == null\n                    || this.widthPropertyRef.get() == null\n                    || this.heightPropertyRef.get() == null;\n        }\n\n        @Override\n        public int hashCode() {\n            return hashCode;\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            if (this == obj)\n                return true;\n            if (!(obj instanceof WindowsSizeBidirectionalBinding))\n                return false;\n\n            var that = (WindowsSizeBidirectionalBinding) obj;\n\n            var comboBox = this.comboBoxRef.get();\n            var widthProperty = this.widthPropertyRef.get();\n            var heightProperty = this.heightPropertyRef.get();\n\n            var thatComboBox = that.comboBoxRef.get();\n            var thatWidthProperty = that.widthPropertyRef.get();\n            var thatHeightProperty = that.heightPropertyRef.get();\n\n            if (comboBox == null || widthProperty == null || heightProperty == null\n                    || thatComboBox == null || thatWidthProperty == null || thatHeightProperty == null) {\n                return false;\n            }\n\n            return comboBox == thatComboBox\n                    && widthProperty == thatWidthProperty\n                    && heightProperty == thatHeightProperty;\n        }\n    }\n\n    public static void bindWindowsSize(JFXComboBox<String> comboBox, IntegerProperty widthProperty, IntegerProperty heightProperty) {\n        comboBox.setValue(widthProperty.get() + \"x\" + heightProperty.get());\n        var binding = new WindowsSizeBidirectionalBinding(comboBox, widthProperty, heightProperty);\n        comboBox.focusedProperty().addListener(binding);\n        comboBox.sceneProperty().addListener(binding);\n        widthProperty.addListener(binding);\n        heightProperty.addListener(binding);\n    }\n\n    public static void unbindWindowsSize(JFXComboBox<String> comboBox, IntegerProperty widthProperty, IntegerProperty heightProperty) {\n        var binding = new WindowsSizeBidirectionalBinding(comboBox, widthProperty, heightProperty);\n        comboBox.focusedProperty().removeListener(binding);\n        comboBox.sceneProperty().removeListener(binding);\n        widthProperty.removeListener(binding);\n        heightProperty.removeListener(binding);\n    }\n\n    public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {\n        int itemCount = children.length;\n        int childSelectedCount = 0;\n        for (BooleanProperty child : children) {\n            if (child.get())\n                childSelectedCount++;\n        }\n\n        allEnabled.set(childSelectedCount == itemCount);\n\n        class Listener implements InvalidationListener {\n            private int childSelectedCount;\n            private boolean updating = false;\n\n            public Listener(int childSelectedCount) {\n                this.childSelectedCount = childSelectedCount;\n            }\n\n            @Override\n            public void invalidated(Observable observable) {\n                if (updating)\n                    return;\n\n                updating = true;\n                try {\n                    boolean value = ((BooleanProperty) observable).get();\n\n                    if (observable == allEnabled) {\n                        for (BooleanProperty child : children) {\n                            child.setValue(value);\n                        }\n                        childSelectedCount = value ? itemCount : 0;\n                    } else {\n                        if (value)\n                            childSelectedCount++;\n                        else\n                            childSelectedCount--;\n\n                        allEnabled.set(childSelectedCount == itemCount);\n                    }\n                } finally {\n                    updating = false;\n                }\n            }\n        }\n\n        InvalidationListener listener = new Listener(childSelectedCount);\n\n        WeakInvalidationListener weakListener = new WeakInvalidationListener(listener);\n        allEnabled.addListener(listener);\n        for (BooleanProperty child : children) {\n            child.addListener(weakListener);\n        }\n    }\n\n    public static void setIcon(Stage stage) {\n        String icon;\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n            icon = \"/assets/img/icon.png\";\n        } else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n            icon = \"/assets/img/icon-mac.png\";\n        } else {\n            icon = \"/assets/img/icon@4x.png\";\n        }\n        stage.getIcons().add(newBuiltinImage(icon));\n    }\n\n    public static Image loadImage(Path path) throws Exception {\n        return loadImage(path, 0, 0, false, false);\n    }\n\n    public static Image loadImage(Path path,\n                                  int requestedWidth, int requestedHeight,\n                                  boolean preserveRatio, boolean smooth) throws Exception {\n        try (var input = new BufferedInputStream(Files.newInputStream(path))) {\n            String ext = FileUtils.getExtension(path).toLowerCase(Locale.ROOT);\n            ImageLoader loader = ImageUtils.EXT_TO_LOADER.get(ext);\n            if (loader == null && !ImageUtils.DEFAULT_EXTS.contains(ext)) {\n                input.mark(ImageUtils.HEADER_BUFFER_SIZE);\n                byte[] headerBuffer = input.readNBytes(ImageUtils.HEADER_BUFFER_SIZE);\n                input.reset();\n                loader = ImageUtils.guessLoader(headerBuffer);\n            }\n            if (loader == null)\n                loader = ImageUtils.DEFAULT;\n            return loader.load(input, requestedWidth, requestedHeight, preserveRatio, smooth);\n        }\n    }\n\n    public static Image loadImage(String url) throws Exception {\n        URI uri = NetworkUtils.toURI(url);\n\n        URLConnection connection = NetworkUtils.createConnection(uri);\n        if (connection instanceof HttpURLConnection)\n            connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);\n\n        try (BufferedInputStream input = new BufferedInputStream(connection.getInputStream())) {\n            String contentType = Objects.requireNonNull(connection.getContentType(), \"\");\n            Matcher matcher = ImageUtils.CONTENT_TYPE_PATTERN.matcher(contentType);\n            if (matcher.find())\n                contentType = matcher.group(\"type\");\n\n            ImageLoader loader = ImageUtils.CONTENT_TYPE_TO_LOADER.get(contentType);\n            if (loader == null && !ImageUtils.DEFAULT_CONTENT_TYPES.contains(contentType)) {\n                input.mark(ImageUtils.HEADER_BUFFER_SIZE);\n                byte[] headerBuffer = input.readNBytes(ImageUtils.HEADER_BUFFER_SIZE);\n                input.reset();\n                loader = ImageUtils.guessLoader(headerBuffer);\n            }\n\n            if (loader == null)\n                loader = ImageUtils.DEFAULT;\n\n            return loader.load(input, 0, 0, false, false);\n        }\n    }\n\n    /**\n     * Suppress IllegalArgumentException since the url is supposed to be correct definitely.\n     *\n     * @param url the url of image. The image resource should be a file within the jar.\n     * @return the image resource within the jar.\n     * @see org.jackhuang.hmcl.util.CrashReporter\n     * @see ResourceNotFoundError\n     */\n    public static Image newBuiltinImage(String url) {\n        try {\n            return builtinImageCache.computeIfAbsent(url, Image::new);\n        } catch (IllegalArgumentException e) {\n            throw new ResourceNotFoundError(\"Cannot access image: \" + url, e);\n        }\n    }\n\n    /**\n     * Suppress IllegalArgumentException since the url is supposed to be correct definitely.\n     *\n     * @param url             the url of image. The image resource should be a file within the jar.\n     * @param requestedWidth  the image's bounding box width\n     * @param requestedHeight the image's bounding box height\n     * @param preserveRatio   indicates whether to preserve the aspect ratio of\n     *                        the original image when scaling to fit the image within the\n     *                        specified bounding box\n     * @param smooth          indicates whether to use a better quality filtering\n     *                        algorithm or a faster one when scaling this image to fit within\n     *                        the specified bounding box\n     * @return the image resource within the jar.\n     * @see org.jackhuang.hmcl.util.CrashReporter\n     * @see ResourceNotFoundError\n     */\n    public static Image newBuiltinImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {\n        try {\n            return new Image(url, requestedWidth, requestedHeight, preserveRatio, smooth);\n        } catch (IllegalArgumentException e) {\n            throw new ResourceNotFoundError(\"Cannot access image: \" + url, e);\n        }\n    }\n\n    public static Task<Image> getRemoteImageTask(String url, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {\n        return new CacheFileTask(url)\n                .setSignificance(Task.TaskSignificance.MINOR)\n                .thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))\n                .setSignificance(Task.TaskSignificance.MINOR);\n    }\n\n    public static Task<Image> getRemoteImageTask(List<URI> uris, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {\n        return new CacheFileTask(uris)\n                .setSignificance(Task.TaskSignificance.MINOR)\n                .thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))\n                .setSignificance(Task.TaskSignificance.MINOR);\n    }\n\n    public static ObservableValue<Image> newRemoteImage(String url, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {\n        var image = new SimpleObjectProperty<Image>();\n        getRemoteImageTask(url, requestedWidth, requestedHeight, preserveRatio, smooth)\n                .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                    if (exception == null) {\n                        image.set(result);\n                    } else {\n                        LOG.warning(\"An exception encountered while loading remote image: \" + url, exception);\n                    }\n                })\n                .setSignificance(Task.TaskSignificance.MINOR)\n                .start();\n        return image;\n    }\n\n    public static JFXButton newRaisedButton(String text) {\n        JFXButton button = new JFXButton(text);\n        button.getStyleClass().add(\"jfx-button-raised\");\n        button.setButtonType(JFXButton.ButtonType.RAISED);\n        return button;\n    }\n\n    public static JFXButton newBorderButton(String text) {\n        JFXButton button = new JFXButton(text);\n        button.getStyleClass().add(\"jfx-button-border\");\n        return button;\n    }\n\n    public static JFXButton newToggleButton4(SVG icon) {\n        JFXButton button = new JFXButton();\n        button.getStyleClass().add(\"toggle-icon4\");\n        button.setGraphic(icon.createIcon());\n        return button;\n    }\n\n    public static JFXButton newToggleButton4(SVG icon, int size) {\n        JFXButton button = new JFXButton();\n        button.getStyleClass().add(\"toggle-icon4\");\n        button.setGraphic(icon.createIcon(size));\n        return button;\n    }\n\n    public static void setOnActionWithCooldown(ButtonBase button, Runnable action) {\n        setOnActionWithCooldown(button, action, Motion.SHORT4);\n    }\n\n    public static void setOnActionWithCooldown(ButtonBase button, Runnable action, Duration cooldown) {\n        button.setOnAction(e -> {\n            button.setDisable(true);\n\n            var pause = new PauseTransition(cooldown);\n            pause.setOnFinished(event -> button.setDisable(false));\n            pause.play();\n\n            action.run();\n            e.consume();\n        });\n    }\n\n    public static Label newSafeTruncatedLabel() {\n        Label label = new Label();\n        label.setTextOverrun(OverrunStyle.CENTER_WORD_ELLIPSIS);\n        showTooltipWhenTruncated(label);\n        return label;\n    }\n\n    private static final String LABEL_FULL_TEXT_PROP_KEY = FXUtils.class.getName() + \".LABEL_FULL_TEXT\";\n\n    public static void showTooltipWhenTruncated(Labeled labeled) {\n        ReadOnlyBooleanProperty textTruncatedProperty = textTruncatedProperty(labeled);\n        if (textTruncatedProperty != null) {\n            ChangeListener<Boolean> listener = (observable, oldValue, newValue) -> {\n                var label = (Labeled) ((ReadOnlyProperty<?>) observable).getBean();\n                var tooltip = (Tooltip) label.getProperties().get(LABEL_FULL_TEXT_PROP_KEY);\n\n                if (newValue) {\n                    if (tooltip == null) {\n                        tooltip = new Tooltip();\n                        tooltip.textProperty().bind(label.textProperty());\n                        label.getProperties().put(LABEL_FULL_TEXT_PROP_KEY, tooltip);\n                    }\n\n                    FXUtils.installFastTooltip(label, tooltip);\n                } else if (tooltip != null) {\n                    Tooltip.uninstall(label, tooltip);\n                }\n            };\n            listener.changed(textTruncatedProperty, false, textTruncatedProperty.get());\n            textTruncatedProperty.addListener(listener);\n        }\n    }\n\n    public static void applyDragListener(Node node, PathMatcher filter, Consumer<List<Path>> callback) {\n        applyDragListener(node, filter, callback, null);\n    }\n\n    public static void applyDragListener(Node node, PathMatcher filter, Consumer<List<Path>> callback, Runnable dragDropped) {\n        node.setOnDragOver(event -> {\n            if (event.getGestureSource() != node && event.getDragboard().hasFiles()) {\n                if (event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(filter::matches))\n                    event.acceptTransferModes(TransferMode.COPY_OR_MOVE);\n            }\n            event.consume();\n        });\n\n        node.setOnDragDropped(event -> {\n            List<File> files = event.getDragboard().getFiles();\n            if (files != null) {\n                List<Path> acceptFiles = files.stream().map(File::toPath).filter(filter::matches).toList();\n                if (!acceptFiles.isEmpty()) {\n                    callback.accept(acceptFiles);\n                    event.setDropCompleted(true);\n                }\n            }\n            if (dragDropped != null)\n                dragDropped.run();\n            event.consume();\n        });\n    }\n\n    public static <T> StringConverter<T> stringConverter(Function<T, String> func) {\n        return new StringConverter<T>() {\n\n            @Override\n            public String toString(T object) {\n                return object == null ? \"\" : func.apply(object);\n            }\n\n            @Override\n            public T fromString(String string) {\n                throw new UnsupportedOperationException();\n            }\n        };\n    }\n\n    public static <T> Callback<ListView<T>, ListCell<T>> jfxListCellFactory(Function<T, Node> graphicBuilder) {\n        return view -> new JFXListCell<T>() {\n            @Override\n            public void updateItem(T item, boolean empty) {\n                super.updateItem(item, empty);\n\n                if (!empty) {\n                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);\n                    setGraphic(graphicBuilder.apply(item));\n                }\n            }\n        };\n    }\n\n    public static ColumnConstraints getColumnFillingWidth() {\n        ColumnConstraints constraint = new ColumnConstraints();\n        constraint.setFillWidth(true);\n        return constraint;\n    }\n\n    public static ColumnConstraints getColumnHgrowing() {\n        ColumnConstraints constraint = new ColumnConstraints();\n        constraint.setFillWidth(true);\n        constraint.setHgrow(Priority.ALWAYS);\n        return constraint;\n    }\n\n    public static final Interpolator SINE = new Interpolator() {\n        @Override\n        protected double curve(double t) {\n            return Math.sin(t * Math.PI / 2);\n        }\n\n        @Override\n        public String toString() {\n            return \"Interpolator.SINE\";\n        }\n    };\n\n    public static void onEscPressed(Node node, Runnable action) {\n        node.addEventHandler(KeyEvent.KEY_PRESSED, e -> {\n            if (e.getCode() == KeyCode.ESCAPE) {\n                action.run();\n                e.consume();\n            }\n        });\n    }\n\n    public static void onClicked(Node node, Runnable action) {\n        node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {\n            if (e.getButton() == MouseButton.PRIMARY) {\n                action.run();\n                e.consume();\n            }\n        });\n    }\n\n    public static void onSecondaryButtonClicked(Node node, Runnable action) {\n        node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {\n            if (e.getButton() == MouseButton.SECONDARY) {\n                action.run();\n                e.consume();\n            }\n        });\n    }\n\n    public static <N extends Parent> N prepareNode(N node) {\n        Scene dummyScene = new Scene(node);\n        StyleSheets.init(dummyScene);\n        node.applyCss();\n        node.layout();\n        return node;\n    }\n\n    public static void prepareOnMouseEnter(Node node, Runnable action) {\n        node.addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<>() {\n            @Override\n            public void handle(MouseEvent e) {\n                node.removeEventFilter(MouseEvent.MOUSE_ENTERED, this);\n                action.run();\n            }\n        });\n    }\n\n    public static <T> void onScroll(Node node, List<T> list,\n                                    ToIntFunction<List<T>> finder,\n                                    Consumer<T> updater\n    ) {\n        node.addEventHandler(ScrollEvent.SCROLL, event -> {\n            double deltaY = event.getDeltaY();\n            if (deltaY == 0)\n                return;\n\n            int index = finder.applyAsInt(list);\n            if (index < 0) return;\n            if (deltaY > 0) // up\n                index--;\n            else // down\n                index++;\n\n            updater.accept(list.get((index + list.size()) % list.size()));\n            event.consume();\n        });\n    }\n\n    public static void copyOnDoubleClick(Labeled label) {\n        label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {\n            if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {\n                String text = label.getText();\n                if (text != null && !text.isEmpty()) {\n                    copyText(label.getText());\n                    e.consume();\n                }\n            }\n        });\n    }\n\n    public static void copyText(String text) {\n        copyText(text, i18n(\"message.copied\"));\n    }\n\n    public static void copyText(String text, @Nullable String toastMessage) {\n        ClipboardContent content = new ClipboardContent();\n        content.putString(text);\n        Clipboard.getSystemClipboard().setContent(content);\n\n        if (toastMessage != null && !Controllers.isStopped()) {\n            Controllers.showToast(toastMessage);\n        }\n    }\n\n    public static List<Node> parseSegment(String segment, Consumer<String> hyperlinkAction) {\n        if (segment.indexOf('<') < 0)\n            return Collections.singletonList(new Text(segment));\n\n        try {\n            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n            DocumentBuilder builder = factory.newDocumentBuilder();\n            Document doc = builder.parse(new InputSource(new StringReader(\"<body>\" + segment + \"</body>\")));\n            Element r = doc.getDocumentElement();\n\n            NodeList children = r.getChildNodes();\n            List<Node> texts = new ArrayList<>();\n            for (int i = 0; i < children.getLength(); i++) {\n                org.w3c.dom.Node node = children.item(i);\n\n                if (node instanceof Element element) {\n                    if (\"a\".equals(element.getTagName())) {\n                        String href = element.getAttribute(\"href\");\n                        Text text = new Text(element.getTextContent());\n                        text.getStyleClass().add(\"hyperlink\");\n                        onClicked(text, () -> {\n                            String link = href;\n                            try {\n                                link = new URI(href).toASCIIString();\n                            } catch (URISyntaxException ignored) {\n                            }\n                            hyperlinkAction.accept(link);\n                        });\n                        text.setCursor(Cursor.HAND);\n                        text.setUnderline(true);\n                        texts.add(text);\n                    } else if (\"b\".equals(element.getTagName())) {\n                        Text text = new Text(element.getTextContent());\n                        text.getStyleClass().add(\"bold\");\n                        texts.add(text);\n                    } else if (\"br\".equals(element.getTagName())) {\n                        texts.add(new Text(\"\\n\"));\n                    } else {\n                        throw new IllegalArgumentException(\"unsupported tag \" + element.getTagName());\n                    }\n                } else {\n                    texts.add(new Text(node.getTextContent()));\n                }\n            }\n            return texts;\n        } catch (SAXException | ParserConfigurationException | IOException e) {\n            LOG.warning(\"Failed to parse xml\", e);\n            return Collections.singletonList(new Text(segment));\n        }\n    }\n\n    public static TextFlow segmentToTextFlow(final String segment, Consumer<String> hyperlinkAction) {\n        TextFlow tf = new TextFlow();\n        tf.getChildren().setAll(parseSegment(segment, hyperlinkAction));\n        return tf;\n    }\n\n    public static String toWeb(Color color) {\n        int r = (int) Math.round(color.getRed() * 255.0);\n        int g = (int) Math.round(color.getGreen() * 255.0);\n        int b = (int) Math.round(color.getBlue() * 255.0);\n\n        return String.format(\"#%02x%02x%02x\", r, g, b);\n    }\n\n    public static FileChooser.ExtensionFilter getImageExtensionFilter() {\n        return new FileChooser.ExtensionFilter(i18n(\"extension.png\"),\n                IMAGE_EXTENSIONS.stream().map(ext -> \"*.\" + ext).toArray(String[]::new));\n    }\n\n    /**\n     * Intelligently determines the popup position to prevent the menu from exceeding screen boundaries.\n     * Supports multi-monitor setups by detecting the current screen where the component is located.\n     * Now handles first-time popup display by forcing layout measurement.\n     *\n     * @param root          the root node to calculate position relative to\n     * @param popupInstance the popup instance to position\n     * @return the optimal vertical position for the popup menu\n     */\n    public static JFXPopup.PopupVPosition determineOptimalPopupPosition(Node root, JFXPopup popupInstance) {\n        // Get the screen bounds in screen coordinates\n        Bounds screenBounds = root.localToScreen(root.getBoundsInLocal());\n\n        // Convert Bounds to Rectangle2D for getScreensForRectangle method\n        Rectangle2D boundsRect = new Rectangle2D(\n                screenBounds.getMinX(), screenBounds.getMinY(),\n                screenBounds.getWidth(), screenBounds.getHeight()\n        );\n\n        // Find the screen that contains this component (supports multi-monitor)\n        List<Screen> screens = Screen.getScreensForRectangle(boundsRect);\n        Screen currentScreen = screens.isEmpty() ? Screen.getPrimary() : screens.get(0);\n        Rectangle2D visualBounds = currentScreen.getVisualBounds();\n\n        double screenHeight = visualBounds.getHeight();\n        double screenMinY = visualBounds.getMinY();\n        double itemScreenY = screenBounds.getMinY();\n\n        // Calculate available space relative to the current screen\n        double availableSpaceAbove = itemScreenY - screenMinY;\n        double availableSpaceBelow = screenMinY + screenHeight - itemScreenY - root.getBoundsInLocal().getHeight();\n\n        // Get popup content and ensure it's properly measured\n        Region popupContent = popupInstance.getPopupContent();\n\n        double menuHeight;\n        if (popupContent.getHeight() <= 0) {\n            // Force layout measurement if height is not yet available\n            popupContent.autosize();\n            popupContent.applyCss();\n            popupContent.layout();\n\n            // Get the measured height, or use a reasonable fallback\n            menuHeight = popupContent.getHeight();\n            if (menuHeight <= 0) {\n                // Fallback: estimate based on number of menu items\n                // Each menu item is roughly 36px height + separators + padding\n                menuHeight = 300; // Conservative estimate for the current menu structure\n            }\n        } else {\n            menuHeight = popupContent.getHeight();\n        }\n\n        // Add some margin for safety\n        menuHeight += 20;\n\n        return (availableSpaceAbove > menuHeight && availableSpaceBelow < menuHeight)\n                ? JFXPopup.PopupVPosition.BOTTOM  // Show menu below the button, expanding downward\n                : JFXPopup.PopupVPosition.TOP;    // Show menu above the button, expanding upward\n    }\n\n    public static void useJFXContextMenu(TextInputControl control) {\n        control.setContextMenu(null);\n\n        PopupMenu menu = new PopupMenu();\n        JFXPopup popup = new JFXPopup(menu);\n        popup.setAutoHide(true);\n\n        control.setOnContextMenuRequested(e -> {\n            boolean hasNoSelection = control.getSelectedText().isEmpty();\n\n            IconedMenuItem undo = new IconedMenuItem(SVG.UNDO, i18n(\"menu.undo\"), control::undo, popup);\n            IconedMenuItem redo = new IconedMenuItem(SVG.REDO, i18n(\"menu.redo\"), control::redo, popup);\n            IconedMenuItem cut = new IconedMenuItem(SVG.CONTENT_CUT, i18n(\"menu.cut\"), control::cut, popup);\n            IconedMenuItem copy = new IconedMenuItem(SVG.CONTENT_COPY, i18n(\"menu.copy\"), control::copy, popup);\n            IconedMenuItem paste = new IconedMenuItem(SVG.CONTENT_PASTE, i18n(\"menu.paste\"), control::paste, popup);\n            IconedMenuItem delete = new IconedMenuItem(SVG.DELETE, i18n(\"menu.deleteselection\"), () -> control.replaceSelection(\"\"), popup);\n            IconedMenuItem selectall = new IconedMenuItem(SVG.SELECT_ALL, i18n(\"menu.selectall\"), control::selectAll, popup);\n\n            menu.getContent().setAll(undo, redo, new MenuSeparator(), cut, copy, paste, delete, new MenuSeparator(), selectall);\n\n            undo.setDisable(!control.isUndoable());\n            redo.setDisable(!control.isRedoable());\n            cut.setDisable(hasNoSelection);\n            delete.setDisable(hasNoSelection);\n            copy.setDisable(hasNoSelection);\n            paste.setDisable(!Clipboard.getSystemClipboard().hasString());\n            selectall.setDisable(control.getText() == null || control.getText().isEmpty());\n\n            JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(control, popup);\n            popup.show(control, vPosition, JFXPopup.PopupHPosition.LEFT, e.getX(), vPosition == JFXPopup.PopupVPosition.TOP ? e.getY() : e.getY() - control.getHeight());\n\n            e.consume();\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2023  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.Text;\nimport javafx.scene.text.TextFlow;\nimport javafx.stage.Stage;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.launch.ProcessListener;\nimport org.jackhuang.hmcl.setting.StyleSheets;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.TwoLineListItem;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.Log4jLevel;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.logging.Logger;\nimport org.jackhuang.hmcl.util.platform.*;\n\nimport java.io.IOException;\nimport java.lang.management.ManagementFactory;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.FileTime;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class GameCrashWindow extends Stage {\n    private final Version version;\n    private final String memory;\n    private final String total_memory;\n    private final String java;\n    private final LibraryAnalyzer analyzer;\n    private final TextFlow reasonTextFlow = new TextFlow(new Text(i18n(\"game.crash.reason.unknown\")));\n    private final BooleanProperty loading = new SimpleBooleanProperty();\n    private final TextFlow feedbackTextFlow = new TextFlow();\n\n    private final ManagedProcess managedProcess;\n    private final DefaultGameRepository repository;\n    private final ProcessListener.ExitType exitType;\n    private final LaunchOptions launchOptions;\n    private final View view;\n    private final StackPane stackPane;\n\n    private final List<Log> logs;\n\n    public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType exitType, DefaultGameRepository repository, Version version, LaunchOptions launchOptions, List<Log> logs) {\n        Themes.applyNativeDarkMode(this);\n\n        this.managedProcess = managedProcess;\n        this.exitType = exitType;\n        this.repository = repository;\n        this.version = version;\n        this.launchOptions = launchOptions;\n        this.logs = logs;\n        this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));\n\n        memory = Optional.ofNullable(launchOptions.getMaxMemory()).map(i -> i + \" \" + i18n(\"settings.memory.unit.mib\")).orElse(\"-\");\n\n        total_memory = MEGABYTES.formatBytes(SystemInfo.getTotalMemorySize());\n\n        this.java = launchOptions.getJava().getArchitecture() == Architecture.SYSTEM_ARCH\n                ? launchOptions.getJava().getVersion()\n                : launchOptions.getJava().getVersion() + \" (\" + launchOptions.getJava().getArchitecture().getDisplayName() + \")\";\n\n        this.view = new View();\n\n        this.stackPane = new StackPane(view);\n        this.feedbackTextFlow.getChildren().addAll(FXUtils.parseSegment(i18n(\"game.crash.feedback\"), Controllers::onHyperlinkAction));\n\n        setScene(new Scene(stackPane, 800, 480));\n        StyleSheets.init(getScene());\n        setTitle(i18n(\"game.crash.title\"));\n        FXUtils.setIcon(this);\n\n        analyzeCrashReport();\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private void analyzeCrashReport() {\n        loading.set(true);\n        Task.allOf(Task.supplyAsync(() -> {\n            String rawLog = logs.stream().map(Log::getLog).collect(Collectors.joining(\"\\n\"));\n\n            // Get the crash-report from the crash-reports/xxx, or the output of console.\n            String crashReport = null;\n            try {\n                crashReport = CrashReportAnalyzer.findCrashReport(rawLog);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to read crash report\", e);\n            }\n            if (crashReport == null) {\n                crashReport = CrashReportAnalyzer.extractCrashReport(rawLog);\n            }\n\n            return pair(CrashReportAnalyzer.analyze(rawLog), crashReport != null ? CrashReportAnalyzer.findKeywordsFromCrashReport(crashReport) : new HashSet<>());\n        }), Task.supplyAsync(() -> {\n            Path latestLog = repository.getRunDirectory(version.getId()).resolve(\"logs/latest.log\");\n            if (!Files.isReadable(latestLog)) {\n                return pair(new HashSet<CrashReportAnalyzer.Result>(), new HashSet<String>());\n            }\n\n            String log;\n            try {\n                log = FileUtils.readTextMaybeNativeEncoding(latestLog);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to read logs/latest.log\", e);\n                return pair(new HashSet<CrashReportAnalyzer.Result>(), new HashSet<String>());\n            }\n\n            return pair(CrashReportAnalyzer.analyze(log), CrashReportAnalyzer.findKeywordsFromCrashReport(log));\n        })).whenComplete(Schedulers.javafx(), (taskResult, exception) -> {\n            loading.set(false);\n\n            if (exception != null) {\n                LOG.warning(\"Failed to analyze crash report\", exception);\n                reasonTextFlow.getChildren().setAll(FXUtils.parseSegment(i18n(\"game.crash.reason.unknown\"), Controllers::onHyperlinkAction));\n            } else {\n                EnumMap<CrashReportAnalyzer.Rule, CrashReportAnalyzer.Result> results = new EnumMap<>(CrashReportAnalyzer.Rule.class);\n                Set<String> keywords = new HashSet<>();\n                for (Pair<Set<CrashReportAnalyzer.Result>, Set<String>> pair : (List<Pair<Set<CrashReportAnalyzer.Result>, Set<String>>>) (List<?>) taskResult) {\n                    for (CrashReportAnalyzer.Result result : pair.getKey()) {\n                        results.put(result.getRule(), result);\n                    }\n                    keywords.addAll(pair.getValue());\n                }\n\n                List<Node> segments = new ArrayList<>(FXUtils.parseSegment(i18n(\"game.crash.feedback\"), Controllers::onHyperlinkAction));\n\n                LOG.info(\"Number of reasons: \" + results.size());\n                if (results.size() > 1) {\n                    segments.add(new Text(\"\\n\"));\n                    segments.addAll(FXUtils.parseSegment(i18n(\"game.crash.reason.multiple\"), Controllers::onHyperlinkAction));\n                } else {\n                    segments.add(new Text(\"\\n\\n\"));\n                }\n\n                for (CrashReportAnalyzer.Result result : results.values()) {\n                    String message;\n                    switch (result.getRule()) {\n                        case TOO_OLD_JAVA:\n                            message = i18n(\"game.crash.reason.too_old_java\", CrashReportAnalyzer.getJavaVersionFromMajorVersion(Integer.parseInt(result.getMatcher().group(\"expected\"))));\n                            break;\n                        case MOD_RESOLUTION_CONFLICT:\n                        case MOD_RESOLUTION_MISSING:\n                        case MOD_RESOLUTION_COLLECTION:\n                            message = i18n(\"game.crash.reason.\" + result.getRule().name().toLowerCase(Locale.ROOT),\n                                    translateFabricModId(result.getMatcher().group(\"sourcemod\")),\n                                    parseFabricModId(result.getMatcher().group(\"destmod\")),\n                                    parseFabricModId(result.getMatcher().group(\"destmod\")));\n                            break;\n                        case MOD_RESOLUTION_MISSING_MINECRAFT:\n                            message = i18n(\"game.crash.reason.\" + result.getRule().name().toLowerCase(Locale.ROOT),\n                                    translateFabricModId(result.getMatcher().group(\"mod\")),\n                                    result.getMatcher().group(\"version\"));\n                            break;\n                        case MOD_FOREST_OPTIFINE:\n                        case TWILIGHT_FOREST_OPTIFINE:\n                        case PERFORMANT_FOREST_OPTIFINE:\n                        case JADE_FOREST_OPTIFINE:\n                        case NEOFORGE_FOREST_OPTIFINE:\n                            message = i18n(\"game.crash.reason.mod\", \"OptiFine\");\n                            LOG.info(\"Crash cause: \" + result.getRule() + \": \" + i18n(\"game.crash.reason.mod\", \"OptiFine\"));\n                            break;\n                        default:\n                            message = i18n(\"game.crash.reason.\" + result.getRule().name().toLowerCase(Locale.ROOT),\n                                    Arrays.stream(result.getRule().getGroupNames()).map(groupName -> result.getMatcher().group(groupName))\n                                            .toArray());\n                            break;\n                    }\n                    LOG.info(\"Crash cause: \" + result.getRule() + \": \" + message);\n                    segments.addAll(FXUtils.parseSegment(message, Controllers::onHyperlinkAction));\n                    segments.add(new Text(\"\\n\\n\"));\n                }\n                if (results.isEmpty()) {\n                    if (!keywords.isEmpty()) {\n                        reasonTextFlow.getChildren().setAll(new Text(i18n(\"game.crash.reason.stacktrace\", String.join(\", \", keywords))));\n                        LOG.info(\"Crash reason unknown, but some log keywords have been found: \" + String.join(\", \", keywords));\n                    } else {\n                        reasonTextFlow.getChildren().setAll(FXUtils.parseSegment(i18n(\"game.crash.reason.unknown\"), Controllers::onHyperlinkAction));\n                        LOG.info(\"Crash reason unknown\");\n                    }\n                } else {\n                    feedbackTextFlow.setVisible(false);\n                    reasonTextFlow.getChildren().setAll(segments);\n                }\n            }\n        }).start();\n    }\n\n    private static final Pattern FABRIC_MOD_ID = Pattern.compile(\"\\\\{(?<modid>.*?) @ (?<version>.*?)}\");\n\n    private String translateFabricModId(String modName) {\n        switch (modName) {\n            case \"fabricloader\":\n                return \"Fabric\";\n            case \"fabric\":\n                return \"Fabric API\";\n            case \"minecraft\":\n                return \"Minecraft\";\n            default:\n                return modName;\n        }\n    }\n\n    private String parseFabricModId(String modName) {\n        Matcher matcher = FABRIC_MOD_ID.matcher(modName);\n        if (matcher.find()) {\n            String modid = matcher.group(\"modid\");\n            String version = matcher.group(\"version\");\n            if (\"[*]\".equals(version)) {\n                return i18n(\"game.crash.reason.mod_resolution_mod_version.any\", translateFabricModId(modid));\n            } else {\n                return i18n(\"game.crash.reason.mod_resolution_mod_version\", translateFabricModId(modid), version);\n            }\n        }\n        return translateFabricModId(modName);\n    }\n\n    private void showLogWindow() {\n        LogWindow logWindow = new LogWindow(managedProcess);\n\n        logWindow.logLine(new Log(Logger.filterForbiddenToken(\"Command: \" + new CommandBuilder().addAll(managedProcess.getCommands())), Log4jLevel.INFO));\n        if (managedProcess.getClasspath() != null)\n            logWindow.logLine(new Log(\"ClassPath: \" + managedProcess.getClasspath(), Log4jLevel.INFO));\n        logWindow.logLines(logs);\n        logWindow.show();\n    }\n\n    private void exportGameCrashInfo() {\n        Path logFile = Paths.get(\"minecraft-exported-crash-info-\" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH-mm-ss\")) + \".zip\").toAbsolutePath();\n\n        CompletableFuture.supplyAsync(() ->\n                        logs.stream().map(Log::getLog).collect(Collectors.joining(\"\\n\")))\n                .thenComposeAsync(logs -> {\n                    long processStartTime = managedProcess.getProcess().info()\n                            .startInstant()\n                            .map(Instant::toEpochMilli).orElseGet(() -> {\n                                try {\n                                    return ManagementFactory.getRuntimeMXBean().getStartTime();\n                                } catch (Throwable e) {\n                                    LOG.warning(\"Failed to get process start time\", e);\n                                    return 0L;\n                                }\n                            });\n\n                    return LogExporter.exportLogs(logFile, repository, launchOptions.getVersionName(), logs,\n                            new CommandBuilder().addAll(managedProcess.getCommands()).toString(),\n                            path -> {\n                                try {\n                                    FileTime lastModifiedTime = Files.getLastModifiedTime(path);\n                                    return lastModifiedTime.toMillis() >= processStartTime;\n                                } catch (Throwable e) {\n                                    LOG.warning(\"Failed to read file attributes\", e);\n                                    return false;\n                                }\n                            });\n                })\n                .handleAsync((result, exception) -> {\n                    if (exception == null) {\n                        FXUtils.showFileInExplorer(logFile);\n                        var dialog = new MessageDialogPane.Builder(i18n(\"settings.launcher.launcher_log.export.success\", logFile), i18n(\"message.success\"), MessageDialogPane.MessageType.SUCCESS).ok(null).build();\n                        DialogUtils.show(stackPane, dialog);\n                    } else {\n                        LOG.warning(\"Failed to export game crash info\", exception);\n                        var dialog = new MessageDialogPane.Builder(i18n(\"settings.launcher.launcher_log.export.failed\") + \"\\n\" + StringUtils.getStackTrace(exception), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR).ok(null).build();\n                        DialogUtils.show(stackPane, dialog);\n                    }\n\n                    return null;\n                }, Schedulers.javafx());\n    }\n\n    private final class View extends VBox {\n\n        View() {\n            this.getStyleClass().add(\"game-crash-window\");\n\n            HBox titlePane = new HBox();\n            {\n                Label title = new Label();\n                HBox.setHgrow(title, Priority.ALWAYS);\n\n                switch (exitType) {\n                    case JVM_ERROR:\n                        title.setText(i18n(\"launch.failed.cannot_create_jvm\"));\n                        break;\n                    case APPLICATION_ERROR:\n                        title.setText(i18n(\"launch.failed.exited_abnormally\"));\n                        break;\n                    case SIGKILL:\n                        title.setText(i18n(\"launch.failed.sigkill\"));\n                        break;\n                }\n\n                titlePane.setAlignment(Pos.CENTER);\n                titlePane.getStyleClass().addAll(\"jfx-tool-bar-second\", \"depth-1\", \"padding-8\");\n                titlePane.getChildren().setAll(title);\n            }\n\n            HBox infoPane = new HBox(8);\n            {\n                infoPane.setPadding(new Insets(8));\n                infoPane.setAlignment(Pos.CENTER_LEFT);\n\n                TwoLineListItem launcher = new TwoLineListItem();\n                launcher.getStyleClass().setAll(\"two-line-item-second-large\");\n                launcher.setTitle(i18n(\"launcher\"));\n                launcher.setSubtitle(Metadata.VERSION);\n\n                TwoLineListItem version = new TwoLineListItem();\n                version.getStyleClass().setAll(\"two-line-item-second-large\");\n                version.setTitle(i18n(\"game.version\"));\n                version.setSubtitle(GameCrashWindow.this.version.getId());\n\n                TwoLineListItem total_memory = new TwoLineListItem();\n                total_memory.getStyleClass().setAll(\"two-line-item-second-large\");\n                total_memory.setTitle(i18n(\"settings.physical_memory\"));\n                total_memory.setSubtitle(GameCrashWindow.this.total_memory);\n\n                TwoLineListItem memory = new TwoLineListItem();\n                memory.getStyleClass().setAll(\"two-line-item-second-large\");\n                memory.setTitle(i18n(\"settings.memory\"));\n                memory.setSubtitle(GameCrashWindow.this.memory);\n\n                TwoLineListItem java = new TwoLineListItem();\n                java.getStyleClass().setAll(\"two-line-item-second-large\");\n                java.setTitle(\"Java\");\n                java.setSubtitle(GameCrashWindow.this.java);\n\n                TwoLineListItem os = new TwoLineListItem();\n                os.getStyleClass().setAll(\"two-line-item-second-large\");\n                os.setTitle(i18n(\"system.operating_system\"));\n                os.setSubtitle(Lang.requireNonNullElse(OperatingSystem.OS_RELEASE_NAME, OperatingSystem.SYSTEM_NAME));\n\n                TwoLineListItem arch = new TwoLineListItem();\n                arch.getStyleClass().setAll(\"two-line-item-second-large\");\n                arch.setTitle(i18n(\"system.architecture\"));\n                arch.setSubtitle(Architecture.SYSTEM_ARCH.getDisplayName());\n\n                infoPane.getChildren().setAll(launcher, version, total_memory, memory, java, os, arch);\n            }\n\n            HBox moddedPane = new HBox(8);\n            {\n                moddedPane.setPadding(new Insets(8));\n                moddedPane.setAlignment(Pos.CENTER_LEFT);\n\n                for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {\n                    if (!type.getPatchId().isEmpty()) {\n                        analyzer.getVersion(type).ifPresent(ver -> {\n                            TwoLineListItem item = new TwoLineListItem();\n                            item.getStyleClass().setAll(\"two-line-item-second-large\");\n                            item.setTitle(i18n(\"install.installer.\" + type.getPatchId()));\n                            item.setSubtitle(ver);\n                            moddedPane.getChildren().add(item);\n                        });\n                    }\n                }\n            }\n\n            VBox gameDirPane = new VBox(8);\n            {\n                TwoLineListItem gameDir = new TwoLineListItem();\n                gameDir.getStyleClass().setAll(\"two-line-item-second-large\");\n                gameDir.setTitle(i18n(\"game.directory\"));\n                gameDir.setSubtitle(launchOptions.getGameDir().toAbsolutePath().toString());\n                FXUtils.installFastTooltip(gameDir, i18n(\"game.directory\"));\n\n                TwoLineListItem javaDir = new TwoLineListItem();\n                javaDir.getStyleClass().setAll(\"two-line-item-second-large\");\n                javaDir.setTitle(i18n(\"settings.game.java_directory\"));\n                javaDir.setSubtitle(launchOptions.getJava().getBinary().toAbsolutePath().toString());\n                FXUtils.installFastTooltip(javaDir, i18n(\"settings.game.java_directory\"));\n\n                Label reasonTitle = new Label(i18n(\"game.crash.reason\"));\n                reasonTitle.getStyleClass().add(\"two-line-item-second-large-title\");\n\n                ScrollPane reasonPane = new ScrollPane(reasonTextFlow);\n                reasonTextFlow.getStyleClass().add(\"crash-reason-text-flow\");\n                reasonPane.setFitToWidth(true);\n                reasonPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);\n                reasonPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);\n\n                feedbackTextFlow.getStyleClass().add(\"crash-reason-text-flow\");\n\n                gameDirPane.setPadding(new Insets(8));\n                VBox.setVgrow(gameDirPane, Priority.ALWAYS);\n                FXUtils.onChangeAndOperate(feedbackTextFlow.visibleProperty(), visible -> {\n                    if (visible) {\n                        gameDirPane.getChildren().setAll(gameDir, javaDir, new VBox(reasonTitle, reasonPane, feedbackTextFlow));\n                    } else {\n                        gameDirPane.getChildren().setAll(gameDir, javaDir, new VBox(reasonTitle, reasonPane));\n                    }\n                });\n            }\n\n            HBox toolBar = new HBox();\n            VBox.setMargin(toolBar, new Insets(0, 0, 4, 0));\n            {\n                JFXButton exportGameCrashInfoButton = FXUtils.newRaisedButton(i18n(\"logwindow.export_game_crash_logs\"));\n                exportGameCrashInfoButton.setOnAction(e -> exportGameCrashInfo());\n\n                JFXButton logButton = FXUtils.newRaisedButton(i18n(\"logwindow.title\"));\n                logButton.setOnAction(e -> showLogWindow());\n\n                JFXButton helpButton = FXUtils.newRaisedButton(i18n(\"help\"));\n                helpButton.setOnAction(e -> FXUtils.openLink(Metadata.CONTACT_URL));\n                FXUtils.installFastTooltip(helpButton, i18n(\"logwindow.help\"));\n\n                toolBar.setPadding(new Insets(8));\n                toolBar.setSpacing(8);\n                toolBar.getStyleClass().add(\"jfx-tool-bar\");\n                toolBar.getChildren().setAll(exportGameCrashInfoButton, logButton, helpButton);\n            }\n\n            getChildren().setAll(titlePane, infoPane, moddedPane, gameDirPane, toolBar);\n        }\n\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.scene.Cursor;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.text.Text;\nimport javafx.scene.text.TextFlow;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\n\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class HTMLRenderer {\n    private static URI resolveLink(Node linkNode) {\n        String href = linkNode.absUrl(\"href\");\n        if (href.isEmpty())\n            return null;\n\n        try {\n            return new URI(href);\n        } catch (Throwable e) {\n            return null;\n        }\n    }\n\n    private final List<javafx.scene.Node> children = new ArrayList<>();\n    private final List<Node> stack = new ArrayList<>();\n\n    private boolean bold;\n    private boolean italic;\n    private boolean underline;\n    private boolean strike;\n    private boolean highlight;\n    private String headerLevel;\n    private Node hyperlink;\n\n    private final Consumer<URI> onClickHyperlink;\n\n    public HTMLRenderer(Consumer<URI> onClickHyperlink) {\n        this.onClickHyperlink = onClickHyperlink;\n    }\n\n    private void updateStyle() {\n        bold = false;\n        italic = false;\n        underline = false;\n        strike = false;\n        highlight = false;\n        headerLevel = null;\n        hyperlink = null;\n\n        for (Node node : stack) {\n            String nodeName = node.nodeName();\n            switch (nodeName) {\n                case \"b\":\n                case \"strong\":\n                    bold = true;\n                    break;\n                case \"i\":\n                case \"em\":\n                    italic = true;\n                    break;\n                case \"ins\":\n                    underline = true;\n                    break;\n                case \"del\":\n                    strike = true;\n                    break;\n                case \"mark\":\n                    highlight = true;\n                    break;\n                case \"a\":\n                    hyperlink = node;\n                    break;\n                case \"h1\":\n                case \"h2\":\n                case \"h3\":\n                case \"h4\":\n                case \"h5\":\n                case \"h6\":\n                    headerLevel = nodeName;\n                    break;\n            }\n        }\n    }\n\n    private void pushNode(Node node) {\n        stack.add(node);\n        updateStyle();\n    }\n\n    private void popNode() {\n        stack.remove(stack.size() - 1);\n        updateStyle();\n    }\n\n    private void applyStyle(Text text) {\n        if (hyperlink != null) {\n            URI target = resolveLink(hyperlink);\n            if (target != null) {\n                FXUtils.onClicked(text, () -> onClickHyperlink.accept(target));\n                text.setCursor(Cursor.HAND);\n            }\n            text.getStyleClass().add(\"html-hyperlink\");\n        }\n\n        if (hyperlink != null || underline)\n            text.setUnderline(true);\n\n        if (strike)\n            text.setStrikethrough(true);\n\n        if (bold || highlight)\n            text.getStyleClass().add(\"html-bold\");\n\n        if (italic)\n            text.getStyleClass().add(\"html-italic\");\n\n        if (headerLevel != null)\n            text.getStyleClass().add(\"html-\" + headerLevel);\n    }\n\n    private void appendText(String text) {\n        Text textNode = new Text(text);\n        applyStyle(textNode);\n        children.add(textNode);\n    }\n\n    private void appendAutoLineBreak(String text) {\n        AutoLineBreak textNode = new AutoLineBreak(text);\n        applyStyle(textNode);\n        children.add(textNode);\n    }\n\n    private void appendImage(Node node) {\n        String src = node.absUrl(\"src\");\n        String alt = node.attr(\"alt\");\n\n        if (StringUtils.isNotBlank(src)) {\n            String widthAttr = node.attr(\"width\");\n            String heightAttr = node.attr(\"height\");\n\n            int width = 0;\n            int height = 0;\n\n            if (!widthAttr.isEmpty() && !heightAttr.isEmpty()) {\n                try {\n                    width = (int) Double.parseDouble(widthAttr);\n                    height = (int) Double.parseDouble(heightAttr);\n                } catch (NumberFormatException ignored) {\n                }\n\n                if (width <= 0 || height <= 0) {\n                    width = 0;\n                    height = 0;\n                }\n            }\n\n            try {\n                Image image = FXUtils.getRemoteImageTask(src, width, height, true, true)\n                        .run();\n                if (image == null)\n                    throw new AssertionError(\"Image loading task returned null\");\n\n                ImageView imageView = new ImageView(image);\n                if (hyperlink != null) {\n                    URI target = resolveLink(hyperlink);\n                    if (target != null) {\n                        FXUtils.onClicked(imageView, () -> onClickHyperlink.accept(target));\n                        imageView.setCursor(Cursor.HAND);\n                    }\n                }\n                children.add(imageView);\n                return;\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to load image: \" + src, e);\n            }\n        }\n\n        if (!alt.isEmpty())\n            appendText(alt);\n    }\n\n    public void appendNode(Node node) {\n        if (node instanceof TextNode) {\n            appendText(((TextNode) node).text());\n        }\n\n        String name = node.nodeName();\n        switch (name) {\n            case \"img\":\n                appendImage(node);\n                break;\n            case \"li\":\n                appendText(\"\\n \\u2022 \");\n                break;\n            case \"dt\":\n                appendText(\" \");\n                break;\n            case \"p\":\n            case \"h1\":\n            case \"h2\":\n            case \"h3\":\n            case \"h4\":\n            case \"h5\":\n            case \"h6\":\n            case \"tr\":\n                if (!children.isEmpty())\n                    appendAutoLineBreak(\"\\n\\n\");\n                break;\n        }\n\n        if (node.childNodeSize() > 0) {\n            pushNode(node);\n            for (Node childNode : node.childNodes()) {\n                appendNode(childNode);\n            }\n            popNode();\n        }\n\n        switch (name) {\n            case \"br\":\n            case \"dd\":\n            case \"p\":\n            case \"h1\":\n            case \"h2\":\n            case \"h3\":\n            case \"h4\":\n            case \"h5\":\n            case \"h6\":\n                appendAutoLineBreak(\"\\n\");\n                break;\n        }\n    }\n\n    private static boolean isSpacing(String text) {\n        if (text == null)\n            return true;\n\n        for (int i = 0; i < text.length(); i++) {\n            char ch = text.charAt(i);\n            if (ch != ' ' && ch != '\\t')\n                return false;\n        }\n        return true;\n    }\n\n    public void mergeLineBreaks() {\n        for (int i = 0; i < this.children.size(); i++) {\n            javafx.scene.Node child = this.children.get(i);\n            if (child instanceof AutoLineBreak) {\n                int lastAutoLineBreak = -1;\n\n                for (int j = i + 1; j < this.children.size(); j++) {\n                    javafx.scene.Node otherChild = this.children.get(j);\n\n                    if (otherChild instanceof AutoLineBreak) {\n                        lastAutoLineBreak = j;\n                    } else if (otherChild instanceof Text && isSpacing(((Text) otherChild).getText())) {\n                        // do nothing\n                    } else {\n                        break;\n                    }\n                }\n\n                if (lastAutoLineBreak > 0) {\n                    this.children.subList(i + 1, lastAutoLineBreak + 1).clear();\n\n                    if (((Text) child).getText().length() == 1) {\n                        ((Text) child).setText(\"\\n\\n\");\n                    }\n                }\n            }\n        }\n    }\n\n    public TextFlow render() {\n        TextFlow textFlow = new TextFlow();\n        textFlow.getStyleClass().add(\"html\");\n        textFlow.getChildren().setAll(children);\n        return textFlow;\n    }\n\n    private static final class AutoLineBreak extends Text {\n        public AutoLineBreak(String text) {\n            super(text);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXRippler;\nimport javafx.beans.Observable;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.setting.VersionIconType;\nimport org.jackhuang.hmcl.ui.construct.ImageContainer;\nimport org.jackhuang.hmcl.ui.construct.RipplerContainer;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\n/**\n * @author huangyuhui\n */\npublic class InstallerItem extends Control {\n    private final String id;\n    private final VersionIconType iconType;\n    private final Style style;\n    private final ObjectProperty<InstalledState> versionProperty = new SimpleObjectProperty<>(this, \"version\", null);\n    private final ObjectProperty<State> resolvedStateProperty = new SimpleObjectProperty<>(this, \"resolvedState\", InstallableState.INSTANCE);\n\n    private final ObjectProperty<Runnable> onInstall = new SimpleObjectProperty<>(this, \"onInstall\");\n    private final ObjectProperty<Runnable> onRemove = new SimpleObjectProperty<>(this, \"onRemove\");\n\n    public sealed interface State {\n    }\n\n    public static final class InstallableState implements State {\n        public static final InstallableState INSTANCE = new InstallableState();\n\n        private InstallableState() {\n        }\n    }\n\n    public record IncompatibleState(String incompatibleItemName, String incompatibleItemVersion) implements State {\n    }\n\n    public record InstalledState(String version, boolean external, boolean incompatibleWithGame) implements State {\n    }\n\n    public enum Style {\n        LIST_ITEM,\n        CARD,\n    }\n\n    public InstallerItem(LibraryAnalyzer.LibraryType id, Style style) {\n        this(id.getPatchId(), style);\n    }\n\n    public InstallerItem(String id, Style style) {\n        this.id = id;\n        this.style = style;\n\n        iconType = switch (id) {\n            case \"game\" -> VersionIconType.GRASS;\n            case \"fabric\", \"fabric-api\" -> VersionIconType.FABRIC;\n            case \"legacyfabric\", \"legacyfabric-api\" -> VersionIconType.LEGACY_FABRIC;\n            case \"forge\" -> VersionIconType.FORGE;\n            case \"cleanroom\" -> VersionIconType.CLEANROOM;\n            case \"liteloader\" -> VersionIconType.CHICKEN;\n            case \"optifine\" -> VersionIconType.OPTIFINE;\n            case \"quilt\", \"quilt-api\" -> VersionIconType.QUILT;\n            case \"neoforge\" -> VersionIconType.NEO_FORGE;\n            default -> null;\n        };\n    }\n\n    public String getLibraryId() {\n        return id;\n    }\n\n    public ObjectProperty<InstalledState> versionProperty() {\n        return versionProperty;\n    }\n\n    public ObjectProperty<State> resolvedStateProperty() {\n        return resolvedStateProperty;\n    }\n\n    public ObjectProperty<Runnable> onInstallProperty() {\n        return onInstall;\n    }\n\n    public Runnable getOnInstall() {\n        return onInstall.get();\n    }\n\n    public void setOnInstall(Runnable onInstall) {\n        this.onInstall.set(onInstall);\n    }\n\n    public ObjectProperty<Runnable> onRemoveProperty() {\n        return onRemove;\n    }\n\n    public Runnable getOnRemove() {\n        return onRemove.get();\n    }\n\n    public void setOnRemove(Runnable onRemove) {\n        this.onRemove.set(onRemove);\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new InstallerItemSkin(this);\n    }\n\n    public final static class InstallerItemGroup {\n        private final InstallerItem game;\n\n        private final InstallerItem[] libraries;\n\n        private Set<InstallerItem> getIncompatibles(Map<InstallerItem, Set<InstallerItem>> incompatibleMap, InstallerItem item) {\n            return incompatibleMap.computeIfAbsent(item, it -> new HashSet<>());\n        }\n\n        private void addIncompatibles(Map<InstallerItem, Set<InstallerItem>> incompatibleMap, InstallerItem item, InstallerItem... others) {\n            Set<InstallerItem> set = getIncompatibles(incompatibleMap, item);\n            for (InstallerItem other : others) {\n                set.add(other);\n                getIncompatibles(incompatibleMap, other).add(item);\n            }\n        }\n\n        private void mutualIncompatible(Map<InstallerItem, Set<InstallerItem>> incompatibleMap, InstallerItem... items) {\n            for (InstallerItem item : items) {\n                Set<InstallerItem> set = getIncompatibles(incompatibleMap, item);\n\n                for (InstallerItem item2 : items) {\n                    if (item2 != item) {\n                        set.add(item2);\n                    }\n                }\n            }\n        }\n\n        public InstallerItemGroup(String gameVersion, Style style) {\n            game = new InstallerItem(MINECRAFT, style);\n            InstallerItem fabric = new InstallerItem(FABRIC, style);\n            InstallerItem fabricApi = new InstallerItem(FABRIC_API, style);\n            InstallerItem forge = new InstallerItem(FORGE, style);\n            InstallerItem cleanroom = new InstallerItem(CLEANROOM, style);\n            InstallerItem legacyfabric = new InstallerItem(LEGACY_FABRIC, style);\n            InstallerItem legacyfabricApi = new InstallerItem(LEGACY_FABRIC_API, style);\n            InstallerItem neoForge = new InstallerItem(NEO_FORGE, style);\n            InstallerItem liteLoader = new InstallerItem(LITELOADER, style);\n            InstallerItem optiFine = new InstallerItem(OPTIFINE, style);\n            InstallerItem quilt = new InstallerItem(QUILT, style);\n            InstallerItem quiltApi = new InstallerItem(QUILT_API, style);\n\n            Map<InstallerItem, Set<InstallerItem>> incompatibleMap = new HashMap<>();\n            mutualIncompatible(incompatibleMap, forge, fabric, quilt, neoForge, cleanroom, legacyfabric);\n            addIncompatibles(incompatibleMap, liteLoader, fabric, quilt, neoForge, cleanroom, legacyfabric);\n            addIncompatibles(incompatibleMap, optiFine, fabric, quilt, neoForge, cleanroom, liteLoader, legacyfabric);\n            addIncompatibles(incompatibleMap, fabricApi, forge, quiltApi, neoForge, liteLoader, optiFine, cleanroom, legacyfabric, legacyfabricApi);\n            addIncompatibles(incompatibleMap, quiltApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine, cleanroom, legacyfabric, legacyfabricApi);\n            addIncompatibles(incompatibleMap, legacyfabricApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine, cleanroom, quilt, quiltApi);\n\n            for (Map.Entry<InstallerItem, Set<InstallerItem>> entry : incompatibleMap.entrySet()) {\n                InstallerItem item = entry.getKey();\n                Set<InstallerItem> incompatibleItems = entry.getValue();\n\n                Observable[] bindings = new Observable[incompatibleItems.size() + 1];\n                bindings[0] = item.versionProperty;\n                int i = 1;\n                for (InstallerItem other : incompatibleItems) {\n                    bindings[i++] = other.versionProperty;\n                }\n\n                item.resolvedStateProperty.bind(Bindings.createObjectBinding(() -> {\n                    InstalledState itemVersion = item.versionProperty.get();\n                    if (itemVersion != null) {\n                        return itemVersion;\n                    }\n\n                    for (InstallerItem other : incompatibleItems) {\n                        InstalledState otherVersion = other.versionProperty.get();\n                        if (otherVersion != null) {\n                            return new IncompatibleState(other.id, otherVersion.version);\n                        }\n                    }\n\n                    return InstallableState.INSTANCE;\n                }, bindings));\n            }\n\n            if (gameVersion != null) {\n                game.versionProperty.set(new InstalledState(gameVersion, false, false));\n            }\n\n            InstallerItem[] all = {game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi, legacyfabric, legacyfabricApi, cleanroom};\n\n            for (InstallerItem item : all) {\n                if (!item.resolvedStateProperty.isBound()) {\n                    item.resolvedStateProperty.bind(Bindings.createObjectBinding(() -> {\n                        InstalledState itemVersion = item.versionProperty.get();\n                        if (itemVersion != null) {\n                            return itemVersion;\n                        }\n                        return InstallableState.INSTANCE;\n                    }, item.versionProperty));\n                }\n            }\n\n            if (gameVersion == null) {\n                this.libraries = all;\n            } else if (gameVersion.equals(\"1.12.2\")) {\n                this.libraries = new InstallerItem[]{game, forge, cleanroom, liteLoader, legacyfabric, legacyfabricApi, optiFine};\n            } else if (GameVersionNumber.compare(gameVersion, \"1.13.2\") <= 0) {\n                this.libraries = new InstallerItem[]{game, forge, liteLoader, optiFine, legacyfabric, legacyfabricApi};\n            } else {\n                this.libraries = new InstallerItem[]{game, forge, neoForge, optiFine, fabric, fabricApi, quilt, quiltApi};\n            }\n        }\n\n        public InstallerItem getGame() {\n            return game;\n        }\n\n        public InstallerItem[] getLibraries() {\n            return libraries;\n        }\n    }\n\n    private static final class InstallerItemSkin extends SkinBase<InstallerItem> {\n        private static final PseudoClass LIST_ITEM = PseudoClass.getPseudoClass(\"list-item\");\n        private static final PseudoClass CARD = PseudoClass.getPseudoClass(\"card\");\n\n        @SuppressWarnings({\"FieldCanBeLocal\", \"unused\"})\n        private final ChangeListener<Number> holder;\n\n        InstallerItemSkin(InstallerItem control) {\n            super(control);\n\n            Pane pane;\n            if (control.style == Style.CARD) {\n                pane = new VBox();\n                holder = FXUtils.onWeakChangeAndOperate(pane.widthProperty(), v -> FXUtils.setLimitHeight(pane, v.doubleValue() * 0.7));\n            } else {\n                pane = new HBox();\n                holder = null;\n            }\n            pane.getStyleClass().add(\"installer-item\");\n            RipplerContainer container = new RipplerContainer(pane);\n            container.setPosition(JFXRippler.RipplerPos.BACK);\n            StackPane paneWrapper = new StackPane();\n            paneWrapper.getStyleClass().add(\"installer-item-wrapper\");\n            paneWrapper.getChildren().setAll(container);\n            getChildren().setAll(paneWrapper);\n\n            pane.pseudoClassStateChanged(LIST_ITEM, control.style == Style.LIST_ITEM);\n            pane.pseudoClassStateChanged(CARD, control.style == Style.CARD);\n            paneWrapper.pseudoClassStateChanged(CARD, control.style == Style.CARD);\n\n            if (control.iconType != null) {\n                var imageContainer = new ImageContainer(32);\n                imageContainer.setImage(control.iconType.getIcon());\n                imageContainer.setMouseTransparent(true);\n                imageContainer.getStyleClass().add(\"installer-item-image\");\n                pane.getChildren().add(imageContainer);\n\n                if (control.style == Style.CARD) {\n                    VBox.setMargin(imageContainer, new Insets(8, 0, 16, 0));\n                }\n            }\n\n            Label nameLabel = new Label();\n            nameLabel.getStyleClass().add(\"installer-item-name\");\n            nameLabel.setMouseTransparent(true);\n            pane.getChildren().add(nameLabel);\n            nameLabel.textProperty().set(I18n.hasKey(\"install.installer.\" + control.id) ? i18n(\"install.installer.\" + control.id) : control.id);\n            HBox.setMargin(nameLabel, new Insets(0, 4, 0, 4));\n\n            Label statusLabel = new Label();\n            statusLabel.getStyleClass().add(\"installer-item-status\");\n            statusLabel.setMouseTransparent(true);\n            pane.getChildren().add(statusLabel);\n            HBox.setHgrow(statusLabel, Priority.ALWAYS);\n            statusLabel.textProperty().bind(Bindings.createStringBinding(() -> {\n                State state = control.resolvedStateProperty.get();\n\n                if (state instanceof InstalledState installedState) {\n                    if (installedState.incompatibleWithGame) {\n                        return i18n(\"install.installer.change_version\", installedState.version);\n                    }\n                    if (installedState.external) {\n                        return i18n(\"install.installer.external_version\", installedState.version);\n                    }\n                    return i18n(\"install.installer.version\", installedState.version);\n                } else if (state instanceof InstallableState) {\n                    return control.style == Style.CARD\n                            ? i18n(\"install.installer.do_not_install\")\n                            : i18n(\"install.installer.not_installed\");\n                } else if (state instanceof IncompatibleState incompatibleState) {\n                    return i18n(\"install.installer.incompatible\", i18n(\"install.installer.\" + incompatibleState.incompatibleItemName));\n                } else {\n                    throw new AssertionError(\"Unknown state type: \" + state.getClass());\n                }\n            }, control.resolvedStateProperty));\n            BorderPane.setMargin(statusLabel, new Insets(0, 0, 0, 8));\n            BorderPane.setAlignment(statusLabel, Pos.CENTER_LEFT);\n\n            HBox buttonsContainer = new HBox();\n            buttonsContainer.setPickOnBounds(false);\n            buttonsContainer.setSpacing(8);\n            buttonsContainer.setAlignment(Pos.CENTER);\n            pane.getChildren().add(buttonsContainer);\n\n            JFXButton removeButton = FXUtils.newToggleButton4(SVG.CLOSE);\n            if (control.id.equals(MINECRAFT.getPatchId())) {\n                removeButton.setVisible(false);\n            } else {\n                removeButton.visibleProperty().bind(Bindings.createBooleanBinding(() -> {\n                    State state = control.resolvedStateProperty.get();\n                    return state instanceof InstalledState installedState && !installedState.external;\n                }, control.resolvedStateProperty));\n            }\n            removeButton.managedProperty().bind(removeButton.visibleProperty());\n            removeButton.setOnAction(e -> {\n                Runnable onRemove = control.getOnRemove();\n                if (onRemove != null)\n                    onRemove.run();\n            });\n            buttonsContainer.getChildren().add(removeButton);\n\n            JFXButton installButton = new JFXButton();\n            installButton.graphicProperty().bind(Bindings.createObjectBinding(() ->\n                            control.resolvedStateProperty.get() instanceof InstallableState ?\n                                    SVG.ARROW_FORWARD.createIcon() :\n                                    SVG.UPDATE.createIcon(),\n                    control.resolvedStateProperty\n            ));\n            installButton.getStyleClass().add(\"toggle-icon4\");\n            installButton.visibleProperty().bind(Bindings.createBooleanBinding(() -> {\n                if (control.getOnInstall() == null) {\n                    return false;\n                }\n\n                State state = control.resolvedStateProperty.get();\n                if (state instanceof InstallableState) {\n                    return true;\n                }\n                if (state instanceof InstalledState) {\n                    return !((InstalledState) state).external;\n                }\n\n                return false;\n            }, control.resolvedStateProperty, control.onInstall));\n            installButton.managedProperty().bind(installButton.visibleProperty());\n            installButton.setOnAction(e -> {\n                Runnable onInstall = control.getOnInstall();\n                if (onInstall != null)\n                    onInstall.run();\n            });\n            buttonsContainer.getChildren().add(installButton);\n\n            FXUtils.onChangeAndOperate(installButton.visibleProperty(), clickable -> {\n                if (clickable) {\n                    container.setOnMouseClicked(event -> {\n                        Runnable onInstall = control.getOnInstall();\n                        if (onInstall != null && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {\n                            onInstall.run();\n                            event.consume();\n                        }\n                    });\n                    pane.setCursor(Cursor.HAND);\n                } else {\n                    container.setOnMouseClicked(null);\n                    pane.setCursor(Cursor.DEFAULT);\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageBase.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.Event;\nimport javafx.event.EventHandler;\nimport javafx.scene.control.Control;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\n\nimport static org.jackhuang.hmcl.ui.construct.SpinnerPane.FAILED_ACTION;\n\npublic class ListPageBase<T> extends Control implements TransitionPane.Cacheable {\n    private final ListProperty<T> items = new SimpleListProperty<>(this, \"items\", FXCollections.observableArrayList());\n    private final BooleanProperty loading = new SimpleBooleanProperty(this, \"loading\", false);\n    private final StringProperty failedReason = new SimpleStringProperty(this, \"failed\");\n\n    public ObservableList<T> getItems() {\n        return items.get();\n    }\n\n    public void setItems(ObservableList<T> items) {\n        this.items.set(items);\n    }\n\n    public ListProperty<T> itemsProperty() {\n        return items;\n    }\n\n    public boolean isLoading() {\n        return loading.get();\n    }\n\n    public void setLoading(boolean loading) {\n        this.loading.set(loading);\n    }\n\n    public BooleanProperty loadingProperty() {\n        return loading;\n    }\n\n    public String getFailedReason() {\n        return failedReason.get();\n    }\n\n    public StringProperty failedReasonProperty() {\n        return failedReason;\n    }\n\n    public void setFailedReason(String failedReason) {\n        this.failedReason.set(failedReason);\n    }\n\n    public final ObjectProperty<EventHandler<Event>> onFailedActionProperty() {\n        return onFailedAction;\n    }\n\n    public final void setOnFailedAction(EventHandler<Event> value) {\n        onFailedActionProperty().set(value);\n    }\n\n    public final EventHandler<Event> getOnFailedAction() {\n        return onFailedActionProperty().get();\n    }\n\n    private ObjectProperty<EventHandler<Event>> onFailedAction = new SimpleObjectProperty<EventHandler<Event>>(this, \"onFailedAction\") {\n        @Override\n        protected void invalidated() {\n            setEventHandler(FAILED_ACTION, get());\n        }\n    };\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport com.jfoenix.controls.*;\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Scene;\nimport javafx.scene.control.*;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.layout.*;\nimport javafx.stage.Stage;\nimport org.jackhuang.hmcl.game.GameDumpGenerator;\nimport org.jackhuang.hmcl.game.Log;\nimport org.jackhuang.hmcl.setting.StyleSheets;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\nimport org.jackhuang.hmcl.util.CircularArrayList;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.Log4jLevel;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.Lang.thread;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huangyuhui\n */\npublic final class LogWindow extends Stage {\n\n    private static final Log4jLevel[] LEVELS = {Log4jLevel.FATAL, Log4jLevel.ERROR, Log4jLevel.WARN, Log4jLevel.INFO, Log4jLevel.DEBUG};\n\n    private final CircularArrayList<Log> logs;\n    private final Map<Log4jLevel, SimpleIntegerProperty> levelCountMap = new EnumMap<>(Log4jLevel.class);\n    private final Map<Log4jLevel, SimpleBooleanProperty> levelShownMap = new EnumMap<>(Log4jLevel.class);\n\n    {\n        for (Log4jLevel level : Log4jLevel.values()) {\n            levelCountMap.put(level, new SimpleIntegerProperty());\n            levelShownMap.put(level, new SimpleBooleanProperty(true));\n        }\n    }\n\n    private final LogWindowImpl impl;\n    private final ManagedProcess gameProcess;\n\n    public LogWindow(ManagedProcess gameProcess) {\n        this(gameProcess, new CircularArrayList<>());\n    }\n\n    public LogWindow(ManagedProcess gameProcess, CircularArrayList<Log> logs) {\n        Themes.applyNativeDarkMode(this);\n\n        this.logs = logs;\n        this.impl = new LogWindowImpl();\n        setScene(new Scene(impl, 800, 480));\n        StyleSheets.init(getScene());\n        setTitle(i18n(\"logwindow.title\"));\n        FXUtils.setIcon(this);\n\n        for (SimpleBooleanProperty property : levelShownMap.values()) {\n            property.addListener(o -> shakeLogs());\n        }\n\n        this.gameProcess = gameProcess;\n    }\n\n    public void logLine(Log log) {\n        Log4jLevel level = log.getLevel();\n        logs.add(log);\n        if (levelShownMap.get(level).get())\n            impl.listView.getItems().add(log);\n\n        SimpleIntegerProperty property = levelCountMap.get(log.getLevel());\n        property.set(property.get() + 1);\n        checkLogCount();\n        autoScroll();\n    }\n\n    public void logLines(List<Log> logs) {\n        for (Log log : logs) {\n            Log4jLevel level = log.getLevel();\n            this.logs.add(log);\n            if (levelShownMap.get(level).get())\n                impl.listView.getItems().add(log);\n\n            SimpleIntegerProperty property = levelCountMap.get(log.getLevel());\n            property.set(property.get() + 1);\n        }\n        checkLogCount();\n        autoScroll();\n    }\n\n    private void shakeLogs() {\n        impl.listView.getItems().setAll(logs.stream().filter(log -> levelShownMap.get(log.getLevel()).get()).collect(Collectors.toList()));\n        autoScroll();\n    }\n\n    private void checkLogCount() {\n        int nRemove = logs.size() - Log.getLogLines();\n        if (nRemove <= 0)\n            return;\n\n        ObservableList<Log> items = impl.listView.getItems();\n        int itemsSize = items.size();\n        int count = 0;\n\n        for (int i = 0; i < nRemove; i++) {\n            Log removedLog = logs.removeFirst();\n            if (itemsSize > count && items.get(count) == removedLog)\n                count++;\n        }\n\n        items.remove(0, count);\n    }\n\n    private void autoScroll() {\n        if (!impl.listView.getItems().isEmpty() && impl.autoScroll.get())\n            impl.listView.scrollTo(impl.listView.getItems().size() - 1);\n    }\n\n    private final class LogWindowImpl extends Control {\n\n        private final ListView<Log> listView = new JFXListView<>();\n        private final BooleanProperty autoScroll = new SimpleBooleanProperty();\n        private final StringProperty[] buttonText = new StringProperty[LEVELS.length];\n        private final BooleanProperty[] showLevel = new BooleanProperty[LEVELS.length];\n        private final JFXComboBox<Integer> cboLines = new JFXComboBox<>();\n        private final StackPane stackPane = new StackPane();\n\n        LogWindowImpl() {\n            getStyleClass().add(\"log-window\");\n\n            listView.getProperties().put(\"no-smooth-scrolling\", true);\n            listView.setItems(FXCollections.observableList(new CircularArrayList<>(logs.size())));\n\n            for (int i = 0; i < LEVELS.length; i++) {\n                buttonText[i] = new SimpleStringProperty();\n                showLevel[i] = new SimpleBooleanProperty(true);\n            }\n\n            cboLines.getItems().setAll(500, 2000, 5000, 10000);\n            cboLines.setValue(Log.getLogLines());\n            cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> config().setLogLines(newValue));\n\n            for (int i = 0; i < LEVELS.length; ++i) {\n                buttonText[i].bind(Bindings.concat(levelCountMap.get(LEVELS[i]), \" \" + LEVELS[i].name().toLowerCase(Locale.ROOT) + \"s\"));\n                levelShownMap.get(LEVELS[i]).bind(showLevel[i]);\n            }\n        }\n\n        private void onTerminateGame() {\n            LogWindow.this.gameProcess.stop();\n        }\n\n        private void onClear() {\n            impl.listView.getItems().clear();\n            logs.clear();\n        }\n\n        private void onExportLogs() {\n            thread(() -> {\n                Path logFile = Paths.get(\"minecraft-exported-logs-\" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH-mm-ss\")) + \".log\").toAbsolutePath();\n                try {\n                    Files.write(logFile, logs.stream().map(Log::getLog).collect(Collectors.toList()));\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to export logs\", e);\n                    return;\n                }\n\n                Platform.runLater(() -> {\n                    var dialog = new MessageDialogPane.Builder(i18n(\"settings.launcher.launcher_log.export.success\", logFile), i18n(\"message.success\"), MessageDialogPane.MessageType.SUCCESS).ok(null).build();\n                    DialogUtils.show(stackPane, dialog);\n                });\n\n                FXUtils.showFileInExplorer(logFile);\n            });\n        }\n\n        private void onExportDump(SpinnerPane pane) {\n            assert SystemUtils.supportJVMAttachment();\n\n            pane.setLoading(true);\n\n            thread(() -> {\n                Path dumpFile = Paths.get(\"minecraft-exported-jstack-dump-\" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH-mm-ss\")) + \".log\").toAbsolutePath();\n\n                try {\n                    if (gameProcess.isRunning()) {\n                        GameDumpGenerator.writeDumpTo(gameProcess.getProcess().pid(), dumpFile);\n                        FXUtils.showFileInExplorer(dumpFile);\n                    }\n                } catch (Throwable e) {\n                    LOG.warning(\"Failed to create minecraft jstack dump\", e);\n\n                    Platform.runLater(() -> {\n                        var dialog = new MessageDialogPane.Builder(i18n(\"logwindow.export_dump\") + \"\\n\" + StringUtils.getStackTrace(e), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR).ok(null).build();\n                        DialogUtils.show(stackPane, dialog);\n                    });\n                }\n\n                Platform.runLater(() -> pane.setLoading(false));\n            });\n        }\n\n        @Override\n        protected Skin<?> createDefaultSkin() {\n            return new LogWindowSkin(this);\n        }\n    }\n\n    private static final class LogWindowSkin extends SkinBase<LogWindowImpl> {\n        private static final PseudoClass EMPTY = PseudoClass.getPseudoClass(\"empty\");\n        private static final PseudoClass FATAL = PseudoClass.getPseudoClass(\"fatal\");\n        private static final PseudoClass ERROR = PseudoClass.getPseudoClass(\"error\");\n        private static final PseudoClass WARN = PseudoClass.getPseudoClass(\"warn\");\n        private static final PseudoClass INFO = PseudoClass.getPseudoClass(\"info\");\n        private static final PseudoClass DEBUG = PseudoClass.getPseudoClass(\"debug\");\n        private static final PseudoClass TRACE = PseudoClass.getPseudoClass(\"trace\");\n        private static final PseudoClass SELECTED = PseudoClass.getPseudoClass(\"selected\");\n\n        private final Set<ListCell<Log>> selected = new HashSet<>();\n        private final JFXSnackbar snackbar = new JFXSnackbar();\n\n        LogWindowSkin(LogWindowImpl control) {\n            super(control);\n\n            VBox vbox = new VBox(3);\n            vbox.setPadding(new Insets(3, 0, 3, 0));\n            getSkinnable().stackPane.getChildren().setAll(vbox);\n            getChildren().setAll(getSkinnable().stackPane);\n            snackbar.registerSnackbarContainer(getSkinnable().stackPane);\n\n            {\n                BorderPane borderPane = new BorderPane();\n                borderPane.setPadding(new Insets(0, 3, 0, 3));\n\n                {\n                    HBox hBox = new HBox(3);\n                    hBox.setPadding(new Insets(0, 0, 0, 4));\n                    hBox.setAlignment(Pos.CENTER_LEFT);\n\n                    Label label = new Label(i18n(\"logwindow.show_lines\"));\n                    hBox.getChildren().setAll(label, control.cboLines);\n\n                    borderPane.setLeft(hBox);\n                }\n\n                {\n                    HBox hBox = new HBox(3);\n                    for (int i = 0; i < LEVELS.length; i++) {\n                        ToggleButton button = new ToggleButton();\n                        button.getStyleClass().addAll(\"log-toggle\", LEVELS[i].name().toLowerCase(Locale.ROOT));\n                        button.textProperty().bind(control.buttonText[i]);\n                        button.setSelected(true);\n                        control.showLevel[i].bind(button.selectedProperty());\n                        hBox.getChildren().add(button);\n                    }\n\n                    borderPane.setRight(hBox);\n                }\n\n                vbox.getChildren().add(borderPane);\n            }\n\n            {\n                ListView<Log> listView = control.listView;\n                listView.getItems().addListener((InvalidationListener) observable -> {\n                    if (!listView.getItems().isEmpty() && control.autoScroll.get())\n                        listView.scrollTo(listView.getItems().size() - 1);\n                });\n\n                listView.setStyle(\"-fx-font-family: \\\"\" + Lang.requireNonNullElse(config().getFontFamily(), FXUtils.DEFAULT_MONOSPACE_FONT)\n                        + \"\\\"; -fx-font-size: \" + config().getFontSize() + \"px;\");\n                listView.setCellFactory(x -> new ListCell<>() {\n                    {\n                        x.setSelectionModel(new NoneMultipleSelectionModel<>());\n                        getStyleClass().add(\"log-window-list-cell\");\n                        Region clippedContainer = (Region) listView.lookup(\".clipped-container\");\n                        if (clippedContainer != null) {\n                            maxWidthProperty().bind(clippedContainer.widthProperty());\n                            prefWidthProperty().bind(clippedContainer.widthProperty());\n                        }\n                        setPadding(new Insets(2));\n                        setWrapText(true);\n                        setGraphic(null);\n\n                        setOnMouseClicked(event -> {\n                            if (event.getButton() != MouseButton.PRIMARY)\n                                return;\n\n                            if (!event.isControlDown()) {\n                                for (ListCell<Log> logListCell : selected) {\n                                    if (logListCell != this) {\n                                        logListCell.pseudoClassStateChanged(SELECTED, false);\n                                        if (logListCell.getItem() != null) {\n                                            logListCell.getItem().setSelected(false);\n                                        }\n                                    }\n                                }\n\n                                selected.clear();\n                            }\n\n                            selected.add(this);\n                            pseudoClassStateChanged(SELECTED, true);\n                            if (getItem() != null) {\n                                getItem().setSelected(true);\n                            }\n\n                            event.consume();\n                        });\n                    }\n\n                    @Override\n                    protected void updateItem(Log item, boolean empty) {\n                        super.updateItem(item, empty);\n\n                        pseudoClassStateChanged(EMPTY, empty);\n                        pseudoClassStateChanged(FATAL, !empty && item.getLevel() == Log4jLevel.FATAL);\n                        pseudoClassStateChanged(ERROR, !empty && item.getLevel() == Log4jLevel.ERROR);\n                        pseudoClassStateChanged(WARN, !empty && item.getLevel() == Log4jLevel.WARN);\n                        pseudoClassStateChanged(INFO, !empty && item.getLevel() == Log4jLevel.INFO);\n                        pseudoClassStateChanged(DEBUG, !empty && item.getLevel() == Log4jLevel.DEBUG);\n                        pseudoClassStateChanged(TRACE, !empty && item.getLevel() == Log4jLevel.TRACE);\n                        pseudoClassStateChanged(SELECTED, !empty && item.isSelected());\n\n                        if (empty) {\n                            setText(null);\n                        } else {\n                            setText(item.getLog());\n                        }\n                    }\n                });\n\n                listView.setOnKeyPressed(event -> {\n                    if (event.isControlDown() && event.getCode() == KeyCode.C) {\n                        StringBuilder stringBuilder = new StringBuilder();\n\n                        for (Log item : listView.getItems()) {\n                            if (item != null && item.isSelected()) {\n                                if (item.getLog() != null)\n                                    stringBuilder.append(item.getLog());\n                                stringBuilder.append('\\n');\n                            }\n                        }\n\n                        FXUtils.copyText(stringBuilder.toString(), null);\n                        snackbar.fireEvent(new JFXSnackbar.SnackbarEvent(new JFXSnackbarLayout(i18n(\"message.copied\"))));\n                    }\n                });\n\n                VBox.setVgrow(listView, Priority.ALWAYS);\n                vbox.getChildren().add(listView);\n            }\n\n            {\n                BorderPane bottom = new BorderPane();\n\n                HBox hBox = new HBox(3);\n                bottom.setRight(hBox);\n                hBox.setAlignment(Pos.CENTER_RIGHT);\n                hBox.setPadding(new Insets(0, 3, 0, 3));\n\n                JFXCheckBox autoScrollCheckBox = new JFXCheckBox(i18n(\"logwindow.autoscroll\"));\n                autoScrollCheckBox.setSelected(true);\n                control.autoScroll.bind(autoScrollCheckBox.selectedProperty());\n\n                JFXButton exportLogsButton = new JFXButton(i18n(\"button.export\"));\n                exportLogsButton.setOnAction(e -> getSkinnable().onExportLogs());\n\n                JFXButton terminateButton = new JFXButton(i18n(\"logwindow.terminate_game\"));\n                terminateButton.setOnAction(e -> getSkinnable().onTerminateGame());\n\n                SpinnerPane exportDumpPane = new SpinnerPane();\n                exportDumpPane.getStyleClass().add(\"small-spinner-pane\");\n                JFXButton exportDumpButton = new JFXButton(i18n(\"logwindow.export_dump\"));\n                if (SystemUtils.supportJVMAttachment()) {\n                    exportDumpButton.setOnAction(e -> getSkinnable().onExportDump(exportDumpPane));\n                } else {\n                    FXUtils.installFastTooltip(exportDumpPane, i18n(\"logwindow.export_dump.no_dependency\"));\n                    exportDumpButton.setDisable(true);\n                }\n                exportDumpPane.setContent(exportDumpButton);\n\n                JFXButton clearButton = new JFXButton(i18n(\"button.clear\"));\n                clearButton.setOnAction(e -> getSkinnable().onClear());\n                hBox.getChildren().setAll(autoScrollCheckBox, exportLogsButton, terminateButton, exportDumpPane, clearButton);\n\n                vbox.getChildren().add(bottom);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.paint.Paint;\nimport javafx.scene.shape.SVGPath;\n\n/// All vector icons used in the launcher.\n///\n/// Unless otherwise stated,\n/// these icons are from <a href=\"https://fonts.google.com/icons?icon.size=24&icon.color=%235f6368&icon.query=list&icon.set=Material+Symbols\">Material Symbols</a>,\n/// with a style of outlined, a weight of 400, a grade of 0, and an optical size of 24 px.\n/// The view boxes of all icons are normalized to `0 0 24 24`.\npublic enum SVG {\n    NONE(\"\"), // Empty Icon\n    ADD(\"M11 13H5V11H11V5H13V11H19V13H13V19H11V13Z\"),\n    ADD_CIRCLE(\"M11 17H13V13H17V11H13V7H11V11H7V13H11V17ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z\"),\n    ALPHA_CIRCLE(\"M11,7H13A2,2 0 0,1 15,9V17H13V13H11V17H9V9A2,2 0 0,1 11,7M11,9V11H13V9H11M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z\"), // Not Material\n    ARCHIVE(\"M12 18 16 14 14.6 12.6 13 14.2V10H11V14.2L9.4 12.6 8 14 12 18ZM5 8V19H19V8H5ZM5 21Q4.175 21 3.5875 20.4125T3 19V6.525Q3 6.175 3.1125 5.85T3.45 5.25L4.7 3.725Q4.975 3.375 5.3875 3.1875T6.25 3H17.75Q18.2 3 18.6125 3.1875T19.3 3.725L20.55 5.25Q20.775 5.525 20.8875 5.85T21 6.525V19Q21 19.825 20.4125 20.4125T19 21H5ZM5.4 6H18.6L17.75 5H6.25L5.4 6ZM12 13.5Z\"),\n    ARCHIVE_FILL(\"M12 18l4-4-1.4-1.4L13 14.2V10H11v4.2L9.4 12.6 8 14l4 4ZM5 21q-.825 0-1.4125-.5875T3 19V6.525q0-.35.1125-.675t.3375-.6L4.7 3.725q.275-.35.6875-.5375T6.25 3h11.5q.45 0 .8625.1875T19.3 3.725L20.55 5.25q.225.275.3375.6T21 6.525V19q0 .825-.5875 1.4125T19 21H5ZM5.4 6H18.6l-.85-1H6.25L5.4 6Z\"),\n    ARROW_BACK(\"M7.825 13 13.425 18.6 12 20 4 12 12 4 13.425 5.4 7.825 11H20V13H7.825Z\"),\n    ARROW_DROP_DOWN(\"M12 15 7 10H17L12 15Z\"),\n    ARROW_DROP_UP(\"M7 14 12 9 17 14H7Z\"),\n    ARROW_FORWARD(\"M16.175 13H4V11H16.175L10.575 5.4 12 4 20 12 12 20 10.575 18.6 16.175 13Z\"),\n    BETA_CIRCLE(\"M15,10.5C15,11.3 14.3,12 13.5,12C14.3,12 15,12.7 15,13.5V15A2,2 0 0,1 13,17H9V7H13A2,2 0 0,1 15,9V10.5M13,15V13H11V15H13M13,11V9H11V11H13M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z\"), // Not Material\n    CANCEL(\"M8.4 17 12 13.4 15.6 17 17 15.6 13.4 12 17 8.4 15.6 7 12 10.6 8.4 7 7 8.4 10.6 12 7 15.6 8.4 17ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z\"),\n    CHAT(\"M6 14H14V12H6V14ZM6 11H18V9H6V11ZM6 8H18V6H6V8ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z\"),\n    CHECK(\"M9.55 18 3.85 12.3 5.275 10.875 9.55 15.15 18.725 5.975 20.15 7.4 9.55 18Z\"),\n    CHECKROOM(\"M3 20Q2.575 20 2.2875 19.7125T2 19Q2 18.75 2.1 18.5375T2.4 18.2L11 11.75V10Q11 9.575 11.3 9.2875T12.025 9Q12.65 9 13.075 8.55T13.5 7.475Q13.5 6.85 13.0625 6.425T12 6Q11.375 6 10.9375 6.4375T10.5 7.5H8.5Q8.5 6.05 9.525 5.025T12 4Q13.45 4 14.475 5.0125T15.5 7.475Q15.5 8.65 14.8125 9.575T13 10.85V11.75L21.6 18.2Q21.8 18.325 21.9 18.5375T22 19Q22 19.425 21.7125 19.7125T21 20H3ZM6 18H18L12 13.5 6 18Z\"),\n    CHECK_CIRCLE(\"M10.6 16.6 17.65 9.55 16.25 8.15 10.6 13.8 7.75 10.95 6.35 12.35 10.6 16.6ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z\"),\n    CLOSE(\"M6.4 19 5 17.6 10.6 12 5 6.4 6.4 5 12 10.6 17.6 5 19 6.4 13.4 12 19 17.6 17.6 19 12 13.4 6.4 19Z\"),\n    CONTENT_CUT(\"M19 21l-7-7-2.35 2.35q.2.375.275.8T10 18q0 1.65-1.175 2.825T6 22q-1.65 0-2.825-1.175T2 18t1.175-2.825T6 14q.425 0 .85.075t.8.275L10 12 7.65 9.65q-.375.2-.8.275T6 10q-1.65 0-2.825-1.175T2 6q0-1.65 1.175-2.825T6 2q1.65 0 2.825 1.175T10 6q0 .425-.075.85t-.275.8L22 20v1H19Zm-4-10-2-2 6-6h3v1l-7 7ZM7.4125 7.4125Q8 6.825 8 6t-.5875-1.4125Q6.825 4 6 4t-1.4125.5875Q4 5.175 4 6t.5875 1.4125T6 8t1.4125-.5875ZM12.35 12.35q.15-.15.15-.35t-.15-.35-.35-.15-.35.15-.15.35.15.35.35.15.35-.15ZM7.4125 19.4125Q8 18.825 8 18t-.5875-1.4125Q6.825 16 6 16t-1.4125.5875Q4 17.175 4 18t.5875 1.4125Q5.175 20 6 20t1.4125-.5875Z\"),\n    CONTENT_COPY(\"M9 18Q8.175 18 7.5875 17.4125T7 16V4Q7 3.175 7.5875 2.5875T9 2H18Q18.825 2 19.4125 2.5875T20 4V16Q20 16.825 19.4125 17.4125T18 18H9ZM9 16H18V4H9V16ZM5 22Q4.175 22 3.5875 21.4125T3 20V6H5V20H16V22H5ZM9 16V4 16Z\"),\n    CONTENT_PASTE(\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h4.175q.275-.875 1.075-1.437T12 1q1 0 1.788.563T14.85 3H19q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5h-2v3H7V5H5zm7-14q.425 0 .713-.288T13 4t-.288-.712T12 3t-.712.288T11 4t.288.713T12 5\"),\n    CREATE_NEW_FOLDER(\"M14 16h2V14h2V12H16V10H14v2H12v2h2v2ZM4 20q-.825 0-1.4125-.5875T2 18V6q0-.825.5875-1.4125T4 4h6l2 2h8q.825 0 1.4125.5875T22 8V18q0 .825-.5875 1.4125T20 20H4Zm0-2H20V8H11.175l-2-2H4V18ZV6 18Z\"),\n    DELETE(\"M7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM9 17H11V8H9V17ZM13 17H15V8H13V17ZM7 6V19 6Z\"),\n    DELETE_FOREVER(\"M9.4 16.5 12 13.9 14.6 16.5 16 15.1 13.4 12.5 16 9.9 14.6 8.5 12 11.1 9.4 8.5 8 9.9 10.6 12.5 8 15.1 9.4 16.5ZM7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM7 6V19 6Z\"),\n    DEPLOYED_CODE(\"M11 19.425V12.575L5 9.1V15.95L11 19.425ZM13 19.425 19 15.95V9.1L13 12.575V19.425ZM12 10.85 17.925 7.425 12 4 6.075 7.425 12 10.85ZM4 17.7Q3.525 17.425 3.2625 16.975T3 15.975V8.025Q3 7.475 3.2625 7.025T4 6.3L11 2.275Q11.475 2 12 2T13 2.275L20 6.3Q20.475 6.575 20.7375 7.025T21 8.025V15.975Q21 16.525 20.7375 16.975T20 17.7L13 21.725Q12.525 22 12 22T11 21.725L4 17.7ZM12 12Z\"),\n    DEPLOYED_CODE_FILL(\"M11 21.725 4 17.7q-.475-.275-.7375-.725T3 15.975V8.025q0-.55.2625-1T4 6.3l7-4.025Q11.475 2 12 2t1 .275L20 6.3q.475.275.7375.725t.2625 1v7.95q0 .55-.2625 1T20 17.7l-7 4.025Q12.525 22 12 22t-1-.275Zm0-9.15v6.85L12 20l1-.575v-6.85L19 9.1V8.05l-1.075-.625L12 10.85 6.075 7.425 5 8.05V9.1l6 3.475Z\"),\n    DOWNLOAD(\"M12 16 7 11 8.4 9.55 11 12.15V4H13V12.15L15.6 9.55 17 11 12 16ZM6 20Q5.175 20 4.5875 19.4125T4 18V15H6V18H18V15H20V18Q20 18.825 19.4125 19.4125T18 20H6Z\"),\n    DRESSER(\"M4 21V5Q4 4.175 4.5875 3.5875T6 3H18Q18.825 3 19.4125 3.5875T20 5V21H18V19H6V21H4ZM6 11H11V5H6V11ZM13 7H18V5H13V7ZM13 11H18V9H13V11ZM10 16H14V14H10V16ZM6 13V17H18V13H6ZM6 13V17 13Z\"),\n    EDIT(\"M5 19H6.425L16.2 9.225 14.775 7.8 5 17.575V19ZM3 21V16.75L16.2 3.575Q16.5 3.3 16.8625 3.15T17.625 3Q18.025 3 18.4 3.15T19.05 3.6L20.425 5Q20.725 5.275 20.8625 5.65T21 6.4Q21 6.8 20.8625 7.1625T20.425 7.825L7.25 21H3ZM19 6.4 17.6 5 19 6.4ZM15.475 8.525 14.775 7.8 16.2 9.225 15.475 8.525Z\"),\n    ERROR(\"M12 17Q12.425 17 12.7125 16.7125T13 16Q13 15.575 12.7125 15.2875T12 15Q11.575 15 11.2875 15.2875T11 16Q11 16.425 11.2875 16.7125T12 17ZM11 13H13V7H11V13ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z\"),\n    EXPLORE(\"M12 12Zm0 8q-3.325 0-5.6625-2.3375T4 12Q4 8.675 6.3375 6.3375T12 4q3.325-0 5.6625 2.3375T20 12q0 3.325-2.3375 5.6625T12 20Zm0 2q2.075-0 3.9-.7875t3.175-2.1375q1.35-1.35 2.1375-3.175T22 12q-0-2.075-.7875-3.9T19.075 4.925q-1.35-1.35-3.175-2.1375T12 2q-2.075 0-3.9.7875T4.925 4.925Q3.575 6.275 2.7875 8.1T2 12q0 2.075.7875 3.9T4.925 19.075q1.35 1.35 3.175 2.1375T12 22Zm0-8.5q.625 0 1.0625-.4375T13.5 12t-.4375-1.0625T12 10.5t-1.0625.4375T10.5 12t.4375 1.0625T12 13.5Zm-4.5 3 2-7 7-2-2 7-7 2Z\"),\n    EXTENSION(\"M8.8 21H5Q4.175 21 3.5875 20.4125T3 19V15.2Q4.2 15.2 5.1 14.4375T6 12.5Q6 11.325 5.1 10.5625T3 9.8V6Q3 5.175 3.5875 4.5875T5 4H9Q9 2.95 9.725 2.225T11.5 1.5Q12.55 1.5 13.275 2.225T14 4H18Q18.825 4 19.4125 4.5875T20 6V10Q21.05 10 21.775 10.725T22.5 12.5Q22.5 13.55 21.775 14.275T20 15V19Q20 19.825 19.4125 20.4125T18 21H14.2Q14.2 19.75 13.4125 18.875T11.5 18Q10.375 18 9.5875 18.875T8.8 21ZM5 19H7.125Q7.725 17.35 9.05 16.675T11.5 16Q12.625 16 13.95 16.675T15.875 19H18V13H20Q20.2 13 20.35 12.85T20.5 12.5Q20.5 12.3 20.35 12.15T20 12H18V6H12V4Q12 3.8 11.85 3.65T11.5 3.5Q11.3 3.5 11.15 3.65T11 4V6H5V8.2Q6.35 8.7 7.175 9.875T8 12.5Q8 13.925 7.175 15.1T5 16.8V19ZM11.5 12.5Z\"),\n    EXTENSION_FILL(\"M8.8 21H5q-.825 0-1.4125-.5875T3 19V15.2q1.2 0 2.1-.7625T6 12.5q0-1.175-.9-1.9375T3 9.8V6q0-.825.5875-1.4125T5 4H9q0-1.05.725-1.775T11.5 1.5q1.05 0 1.775.725T14 4h4q.825 0 1.4125.5875T20 6v4q1.05 0 1.775.725T22.5 12.5q0 1.05-.725 1.775T20 15v4q0 .825-.5875 1.4125T18 21H14.2q0-1.25-.7875-2.125T11.5 18q-1.125 0-1.9125.875T8.8 21Z\"),\n    FEEDBACK(\"M12 15Q12.425 15 12.7125 14.7125T13 14Q13 13.575 12.7125 13.2875T12 13Q11.575 13 11.2875 13.2875T11 14Q11 14.425 11.2875 14.7125T12 15ZM11 11H13V5H11V11ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z\"),\n    FEEDBACK_FILL(\"M2 22V4q0-.825.5875-1.4125T4 2H20q.825 0 1.4125.5875T22 4V16q0 .825-.5875 1.4125T20 18H6L2 22Zm10-7q.425 0 .7125-.2875T13 14t-.2875-.7125T12 13t-.7125.2875T11 14t.2875.7125T12 15Zm-1-4h2V5H11v6Z\"),\n    FOLDER(\"M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8V18Q22 18.825 21.4125 19.4125T20 20H4ZM4 18H20V8H11.175L9.175 6H4V18ZM4 18V6 18Z\"),\n    FOLDER_COPY(\"M3 21Q2.175 21 1.5875 20.4125T1 19V6H3V19H20V21H3ZM7 17Q6.175 17 5.5875 16.4125T5 15V4Q5 3.175 5.5875 2.5875T7 2H12L14 4H21Q21.825 4 22.4125 4.5875T23 6V15Q23 15.825 22.4125 16.4125T21 17H7ZM7 15H21V6H13.175L11.175 4H7V15ZM7 15V4 15Z\"),\n    FOLDER_OPEN(\"M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8H11.175L9.175 6H4V18L6.4 10H23.5L20.925 18.575Q20.725 19.225 20.1875 19.6125T19 20H4ZM6.1 18H19L20.8 12H7.9L6.1 18ZM6.1 18 7.9 12 6.1 18ZM4 8V6 8Z\"),\n    FORMAT_LIST_BULLETED(\"M9 19V17H21V19H9ZM9 13V11H21V13H9ZM9 7V5H21V7H9ZM5 20Q4.175 20 3.5875 19.4125T3 18Q3 17.175 3.5875 16.5875T5 16Q5.825 16 6.4125 16.5875T7 18Q7 18.825 6.4125 19.4125T5 20ZM5 14Q4.175 14 3.5875 13.4125T3 12Q3 11.175 3.5875 10.5875T5 10Q5.825 10 6.4125 10.5875T7 12Q7 12.825 6.4125 13.4125T5 14ZM5 8Q4.175 8 3.5875 7.4125T3 6Q3 5.175 3.5875 4.5875T5 4Q5.825 4 6.4125 4.5875T7 6Q7 6.825 6.4125 7.4125T5 8Z\"),\n    FORT(\"M1 21V17l2-2V9L1 7V3H3V5H5V3H7V5H9V3h2V7L9 9v1h6V9L13 7V3h2V5h2V3h2V5h2V3h2V7L21 9v6l2 2v4H14V18q0-.825-.5875-1.4125T12 16q-.825 0-1.4125.5875T10 18v3H1Zm2-2H8V18q0-1.65 1.175-2.825T12 14q1.65 0 2.825 1.175T16 18v1h5V17.825l-2-2V8.175L20.175 7h-4.35L17 8.175V12H7V8.175L8.175 7H3.825L5 8.175v7.65l-2 2V19Zm9-6Z\"),\n    FOR_YOU(\"M12 12Q14.025 12 16.225 11.5875T20 10.5V20.5Q18.5 21.175 16.35 21.5875T12 22Q9.8 22 7.65 21.5875T4 20.5V10.5Q5.575 11.175 7.775 11.5875T12 12ZM18 19V13.25Q16.75 13.6 15.1125 13.8T12 14Q10.525 14 8.8875 13.8T6 13.25V19Q7.25 19.45 8.875 19.725T12 20Q13.5 20 15.125 19.725T18 19ZM12 2Q13.65 2 14.825 3.175T16 6Q16 7.65 14.825 8.825T12 10Q10.35 10 9.175 8.825T8 6Q8 4.35 9.175 3.175T12 2ZM12 8Q12.825 8 13.4125 7.4125T14 6Q14 5.175 13.4125 4.5875T12 4Q11.175 4 10.5875 4.5875T10 6Q10 6.825 10.5875 7.4125T12 8ZM12 6ZM12 16.625Z\"),\n    GAMEPAD(\"M12 7.65ZM16.35 12ZM7.65 12ZM12 16.35ZM12 10.5 9 7.5V2H15V7.5L12 10.5ZM16.5 15 13.5 12 16.5 9H22V15H16.5ZM2 15V9H7.5L10.5 12 7.5 15H2ZM9 22V16.5L12 13.5 15 16.5V22H9ZM12 7.65 13 6.65V4H11V6.65L12 7.65ZM4 13H6.65L7.65 12 6.65 11H4V13ZM11 20H13V17.35L12 16.35 11 17.35V20ZM17.35 13H20V11H17.35L16.35 12 17.35 13Z\"),\n    GLOBE_BOOK(\"M3.075 13Q3.05 12.75 3.0375 12.5T3.025 12Q3.025 10.125 3.725 8.4875T5.65 5.6375Q6.875 4.425 8.5 3.7125T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 12.25 20.9875 12.5T20.95 13H18.925Q18.975 12.75 18.9875 12.5T19 12Q19 11.75 18.9875 11.5T18.925 11H15.975Q16 11.25 16 11.5V12.5Q16 12.75 15.975 13H14V12.175Q14 11.875 13.9875 11.575T13.95 11H10.075Q10.05 11.275 10.0375 11.575T10.025 12.175V13H8.05Q8.025 12.75 8.025 12.5V11.5Q8.025 11.25 8.05 11H5.1Q5.05 11.25 5.0375 11.5T5.025 12Q5.025 12.25 5.0375 12.5T5.1 13H3.075ZM5.7 9H8.275Q8.475 7.925 8.775 7.0625T9.425 5.5Q8.225 5.95 7.25 6.8625T5.7 9ZM10.35 9H13.65Q13.4 7.925 13.025 6.9T12 5Q11.35 5.875 10.9625 6.9T10.35 9ZM15.75 9H18.325Q17.75 7.775 16.7625 6.8625T14.575 5.5Q14.925 6.25 15.2375 7.0875T15.75 9ZM11 21V20Q11 18.75 10.125 17.875T8 17H2V15H8Q9.2 15 10.2375 15.525T12 17Q12.725 16.05 13.7625 15.525T16 15H22V17H16Q14.75 17 13.875 17.875T13 20V21H11Z\"),\n    GRAPH2(\"M5 22q-1.25 0-2.125-.875T2 19q0-.975.5625-1.75T4 16.175V14q0-1.25.875-2.125T7 11h4V7.825q-.875-.3-1.4375-1.075T9 5q0-1.25.875-2.125T12 2t2.125.875T15 5q0 .975-.5625 1.75T13 7.825V11h4q1.25 0 2.125.875T20 14v2.175q.875.3 1.4375 1.075T22 19q0 1.25-.875 2.125T19 22t-2.125-.875T16 19q0-.975.5625-1.75T18 16.175V14q0-.425-.2875-.7125T17 13H13v3.175q.875.3 1.4375 1.075T15 19q0 1.25-.875 2.125T12 22t-2.125-.875T9 19q0-.975.5625-1.75T11 16.175V13H7q-.425 0-.7125.2875T6 14v2.175q.875.3 1.4375 1.075T8 19q0 1.25-.875 2.125T5 22Zm0-2q.425 0 .7125-.2875T6 19q0-.425-.2875-.7125T5 18q-.425 0-.7125.2875T4 19q0 .425.2875.7125T5 20Zm7 0q.425 0 .7125-.2875T13 19q0-.425-.2875-.7125T12 18q-.425 0-.7125.2875T11 19q0 .425.2875.7125T12 20Zm7 0q.425 0 .7125-.2875T20 19q0-.425-.2875-.7125T19 18q-.425 0-.7125.2875T18 19q0 .425.2875.7125T19 20ZM12 6q.425 0 .7125-.2875T13 5q0-.425-.2875-.7125T12 4q-.425 0-.7125.2875T11 5q0 .425.2875.7125T12 6Z\"),\n    HELP(\"M11.95 18Q12.475 18 12.8375 17.6375T13.2 16.75Q13.2 16.225 12.8375 15.8625T11.95 15.5Q11.425 15.5 11.0625 15.8625T10.7 16.75Q10.7 17.275 11.0625 17.6375T11.95 18ZM11.05 14.15H12.9Q12.9 13.325 13.0875 12.85T14.15 11.55Q14.8 10.9 15.175 10.3125T15.55 8.9Q15.55 7.5 14.525 6.75T12.1 6Q10.675 6 9.7875 6.75T8.55 8.55L10.2 9.2Q10.325 8.75 10.7625 8.225T12.1 7.7Q12.9 7.7 13.3 8.1375T13.7 9.1Q13.7 9.6 13.4 10.0375T12.65 10.85Q11.55 11.825 11.3 12.325T11.05 14.15ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z\"),\n    HELP_FILL(\"M11.95 18q.525 0 .8875-.3625T13.2 16.75q0-.525-.3625-.8875T11.95 15.5q-.525 0-.8875.3625T10.7 16.75q0 .525.3625.8875T11.95 18Zm-.9-3.85H12.9q0-.825.1875-1.3t1.0625-1.3q.65-.65 1.025-1.2375T15.55 8.9q0-1.4-1.025-2.15T12.1 6q-1.425 0-2.3125.75T8.55 8.55l1.65.65q.125-.45.5625-.975T12.1 7.7q.8 0 1.2.4375t.4.9625q0 .5-.3.9375t-.75.8125q-1.1.975-1.35 1.475t-.25 1.825ZM12 22q-2.075 0-3.9-.7875T4.925 19.075q-1.35-1.35-2.1375-3.175T2 12q0-2.075.7875-3.9T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2q2.075 0 3.9.7875T19.075 4.925q1.35 1.35 2.1375 3.175T22 12q0 2.075-.7875 3.9T19.075 19.075q-1.35 1.35-3.175 2.1375T12 22Z\"),\n    HOME(\"M6 19H9V13H15V19H18V10L12 5.5 6 10V19ZM4 21V9L12 3 20 9V21H13V15H11V21H4ZM12 12.25Z\"),\n    HOST(\"M4 21Q3.175 21 2.5875 20.4125T2 19V5Q2 4.175 2.5875 3.5875T4 3H9Q9.825 3 10.4125 3.5875T11 5V19Q11 19.825 10.4125 20.4125T9 21H4ZM15 21Q14.175 21 13.5875 20.4125T13 19V5Q13 4.175 13.5875 3.5875T15 3H20Q20.825 3 21.4125 3.5875T22 5V19Q22 19.825 21.4125 20.4125T20 21H15ZM4 19H9V5H4V19ZM15 19H20V5H15V19ZM5 15H8V13H5V15ZM16 15H19V13H16V15ZM5 12H8V10H5V12ZM16 12H19V10H16V12ZM5 9H8V7H5V9ZM16 9H19V7H16V9ZM4 19H9 4ZM15 19H20 15Z\"),\n    INFO(\"M11 17H13V11H11V17ZM12 9Q12.425 9 12.7125 8.7125T13 8Q13 7.575 12.7125 7.2875T12 7Q11.575 7 11.2875 7.2875T11 8Q11 8.425 11.2875 8.7125T12 9ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z\"),\n    INFO_FILL(\"M12 22q2.075-0 3.9-.7875t3.175-2.1375q1.35-1.35 2.1375-3.175T22 12q-0-2.075-.7875-3.9T19.075 4.925q-1.35-1.35-3.175-2.1375T12 2q-2.075 0-3.9.7875T4.925 4.925Q3.575 6.275 2.7875 8.1T2 12q0 2.075.7875 3.9T4.925 19.075q1.35 1.35 3.175 2.1375T12 22ZM12 9q-.425 0-.7125-.2875T11 8t.2875-.7125T12 7q.425-0 .7125.2875T13 8t-.2875.7125T12 9Zm-1 8V11h2v6H11Z\"),\n    KEYBOARD_ARROW_DOWN(\"M12 15.4 6 9.4 7.4 8 12 12.6 16.6 8 18 9.4 12 15.4Z\"),\n    KEYBOARD_ARROW_UP(\"M12 10.8 7.4 15.4 6 14 12 8 18 14 16.6 15.4 12 10.8Z\"),\n    LANDSCAPE(\"M1 18l6-8 4.5 6H19L14 9.35l-2.5 3.3L10.25 11 14 6l9 12H1Zm13.025-2ZM5 16H9L7 13.325 5 16ZH9 5Z\"),\n    LIST(\"M7 9V7H21V9H7ZM7 13V11H21V13H7ZM7 17V15H21V17H7ZM4 9Q3.575 9 3.2875 8.7125T3 8Q3 7.575 3.2875 7.2875T4 7Q4.425 7 4.7125 7.2875T5 8Q5 8.425 4.7125 8.7125T4 9ZM4 13Q3.575 13 3.2875 12.7125T3 12Q3 11.575 3.2875 11.2875T4 11Q4.425 11 4.7125 11.2875T5 12Q5 12.425 4.7125 12.7125T4 13ZM4 17Q3.575 17 3.2875 16.7125T3 16Q3 15.575 3.2875 15.2875T4 15Q4.425 15 4.7125 15.2875T5 16Q5 16.425 4.7125 16.7125T4 17Z\"),\n    LISTS(\"M2 20V16H6V20H2ZM8 20V16H22V20H8ZM2 14V10H6V14H2ZM8 14V10H22V14H8ZM2 8V4H6V8H2ZM8 8V4H22V8H8Z\"),\n    LOCAL_CAFE(\"M4 21V19H20V21H4ZM8 17Q6.35 17 5.175 15.825T4 13V3H20Q20.825 3 21.4125 3.5875T22 5V8Q22 8.825 21.4125 9.4125T20 10H18V13Q18 14.65 16.825 15.825T14 17H8ZM8 15H14Q14.825 15 15.4125 14.4125T16 13V5H6V13Q6 13.825 6.5875 14.4125T8 15ZM18 8H20V5H18V8ZM8 15H6 16 8Z\"),\n    LOCAL_CAFE_FILL(\"M4 21V19H20v2H4Zm4-4q-1.65 0-2.825-1.175T4 13V3H20q.825 0 1.4125.5875T22 5V8q0 .825-.5875 1.4125T20 10H18v3q0 1.65-1.175 2.825T14 17H8ZM18 8h2V5H18V8Z\"),\n    LOCATION_CITY(\"M3 21V7H9V5l3-3 3 3v6h6V21H3Zm2-2H7V17H5v2Zm0-4H7V13H5v2Zm0-4H7V9H5v2Zm6 8h2V17H11v2Zm0-4h2V13H11v2Zm0-4h2V9H11v2Zm0-4h2V5H11V7Zm6 12h2V17H17v2Zm0-4h2V13H17v2Z\"),\n    MENU(\"M3 18V16H21V18H3ZM3 13V11H21V13H3ZM3 8V6H21V8H3Z\"),\n    MICROSOFT(\"M4 20H22v2H4V13H20v7h2V4H20v7H4V4h7V20h2V4h9V2H2V22H4\"), // Not Material\n    MINIMIZE_CENTER(\"M6 13v-2h12v2H6Z\"), // Not Material\n    MORE_HORIZ(\"M6 14Q5.175 14 4.5875 13.4125T4 12Q4 11.175 4.5875 10.5875T6 10Q6.825 10 7.4125 10.5875T8 12Q8 12.825 7.4125 13.4125T6 14ZM12 14Q11.175 14 10.5875 13.4125T10 12Q10 11.175 10.5875 10.5875T12 10Q12.825 10 13.4125 10.5875T14 12Q14 12.825 13.4125 13.4125T12 14ZM18 14Q17.175 14 16.5875 13.4125T16 12Q16 11.175 16.5875 10.5875T18 10Q18.825 10 19.4125 10.5875T20 12Q20 12.825 19.4125 13.4125T18 14Z\"),\n    MORE_VERT(\"M12 20Q11.175 20 10.5875 19.4125T10 18Q10 17.175 10.5875 16.5875T12 16Q12.825 16 13.4125 16.5875T14 18Q14 18.825 13.4125 19.4125T12 20ZM12 14Q11.175 14 10.5875 13.4125T10 12Q10 11.175 10.5875 10.5875T12 10Q12.825 10 13.4125 10.5875T14 12Q14 12.825 13.4125 13.4125T12 14ZM12 8Q11.175 8 10.5875 7.4125T10 6Q10 5.175 10.5875 4.5875T12 4Q12.825 4 13.4125 4.5875T14 6Q14 6.825 13.4125 7.4125T12 8Z\"),\n    OPEN_IN_NEW(\"M5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H12V5H5V19H19V12H21V19Q21 19.825 20.4125 20.4125T19 21H5ZM9.7 15.7 8.3 14.3 17.6 5H14V3H21V10H19V6.4L9.7 15.7Z\"),\n    OUTPUT(\"M5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H19Q19.825 3 20.4125 3.5875T21 5V7H19V5H5V19H19V17H21V19Q21 19.825 20.4125 20.4125T19 21H5ZM17 17 15.6 15.6 18.175 13H9V11H18.175L15.6 8.4 17 7 22 12 17 17Z\"),\n    PACKAGE(\"M10 9.75 12 8.75 14 9.75V5H10V9.75ZM7 17V15H12V17H7ZM5 21Q4.175 21 3.5875 20.4125T3 19V5Q3 4.175 3.5875 3.5875T5 3H19Q19.825 3 20.4125 3.5875T21 5V19Q21 19.825 20.4125 20.4125T19 21H5ZM5 5V19 5ZM5 19H19V5H16V13L12 11 8 13V5H5V19Z\"),\n    PACKAGE2(\"M11 19.425V12.575L5 9.1V15.95L11 19.425ZM13 19.425 19 15.95V9.1L13 12.575V19.425ZM11 21.725 4 17.7Q3.525 17.425 3.2625 16.975T3 15.975V8.025Q3 7.475 3.2625 7.025T4 6.3L11 2.275Q11.475 2 12 2T13 2.275L20 6.3Q20.475 6.575 20.7375 7.025T21 8.025V15.975Q21 16.525 20.7375 16.975T20 17.7L13 21.725Q12.525 22 12 22T11 21.725ZM16 8.525 17.925 7.425 12 4 10.05 5.125 16 8.525ZM12 10.85 13.95 9.725 8.025 6.3 6.075 7.425 12 10.85Z\"),\n    PACKAGE2_FILL(\"M11 21.725v-9.15L3 7.95v8.025q0 .55.2625 1T4 17.7l7 4.025Zm2 0L20 17.7q.475-.275.7375-.725t.2625-1V7.95l-8 4.625v9.15Zm3.975-13.75 2.95-1.725L13 2.275Q12.525 2 12 2t-1 .275L9.025 3.4l7.95 4.575ZM12 10.85l2.975-1.7L7.05 4.55l-3 1.725L12 10.85Z\"),\n    PERSON(\"M12 12Q10.35 12 9.175 10.825T8 8Q8 6.35 9.175 5.175T12 4Q13.65 4 14.825 5.175T16 8Q16 9.65 14.825 10.825T12 12ZM4 20V17.2Q4 16.35 4.4375 15.6375T5.6 14.55Q7.15 13.775 8.75 13.3875T12 13Q13.65 13 15.25 13.3875T18.4 14.55Q19.125 14.925 19.5625 15.6375T20 17.2V20H4ZM6 18H18V17.2Q18 16.925 17.8625 16.7T17.5 16.35Q16.15 15.675 14.775 15.3375T12 15Q10.6 15 9.225 15.3375T6.5 16.35Q6.275 16.475 6.1375 16.7T6 17.2V18ZM12 10Q12.825 10 13.4125 9.4125T14 8Q14 7.175 13.4125 6.5875T12 6Q11.175 6 10.5875 6.5875T10 8Q10 8.825 10.5875 9.4125T12 10ZM12 8ZM12 18Z\"),\n    PUBLIC(\"M12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM11 19.95V18Q10.175 18 9.5875 17.4125T9 16V15L4.2 10.2Q4.125 10.65 4.0625 11.1T4 12Q4 15.025 5.9875 17.3T11 19.95ZM17.9 17.4Q18.925 16.275 19.4625 14.8875T20 12Q20 9.55 18.6375 7.525T15 4.6V5Q15 5.825 14.4125 6.4125T13 7H11V9Q11 9.425 10.7125 9.7125T10 10H8V12H14Q14.425 12 14.7125 12.2875T15 13V16H16Q16.65 16 17.175 16.3875T17.9 17.4Z\"),\n    REFRESH(\"M12 20Q8.65 20 6.325 17.675T4 12Q4 8.65 6.325 6.325T12 4Q13.725 4 15.3 4.7125T18 6.75V4H20V11H13V9H17.2Q16.4 7.6 15.0125 6.8T12 6Q9.5 6 7.75 7.75T6 12Q6 14.5 7.75 16.25T12 18Q13.925 18 15.475 16.9T17.65 14H19.75Q19.05 16.65 16.9 18.325T12 20Z\"),\n    REDO(\"M9.9 19q-2.425 0-4.163-1.575T4 13.5t1.738-3.925T9.9 8h6.3l-2.6-2.6L15 4l5 5l-5 5l-1.4-1.4l2.6-2.6H9.9q-1.575 0-2.738 1T6 13.5T7.163 16T9.9 17H17v2z\"),\n    RELEASE_CIRCLE(\"M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z\"), // Not Material\n    RESTORE(\"M12 21Q8.55 21 5.9875 18.7125T3.05 13H5.1Q5.45 15.6 7.4125 17.3T12 19Q14.925 19 16.9625 16.9625T19 12Q19 9.075 16.9625 7.0375T12 5Q10.275 5 8.775 5.8T6.25 8H9V10H3V4H5V6.35Q6.275 4.75 8.1125 3.875T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 13.875 20.2875 15.5125T18.3625 18.3625Q17.15 19.575 15.5125 20.2875T12 21Z\"), // Not Material\n    ROCKET_LAUNCH(\"M5.65 10.025 7.6 10.85Q7.95 10.15 8.325 9.5T9.15 8.2L7.75 7.925 5.65 10.025ZM9.2 12.1 12.05 14.925Q13.1 14.525 14.3 13.7T16.55 11.825Q18.3 10.075 19.2875 7.9375T20.15 4Q18.35 3.875 16.2 4.8625T12.3 7.6Q11.25 8.65 10.425 9.85T9.2 12.1ZM13.65 10.475Q13.075 9.9 13.075 9.0625T13.65 7.65Q14.225 7.075 15.075 7.075T16.5 7.65Q17.075 8.225 17.075 9.0625T16.5 10.475Q15.925 11.05 15.075 11.05T13.65 10.475ZM14.125 18.5 16.225 16.4 15.95 15Q15.3 15.45 14.65 15.8125T13.3 16.525L14.125 18.5ZM21.95 2.175Q22.425 5.2 21.3625 8.0625T17.7 13.525L18.2 16Q18.3 16.5 18.15 16.975T17.65 17.8L13.45 22 11.35 17.075 7.075 12.8 2.15 10.7 6.325 6.5Q6.675 6.15 7.1625 6T8.15 5.95L10.625 6.45Q13.225 3.85 16.075 2.775T21.95 2.175ZM3.925 15.975Q4.8 15.1 6.0625 15.0875T8.2 15.95Q9.075 16.825 9.0625 18.0875T8.175 20.225Q7.55 20.85 6.0875 21.3T2.05 22.1Q2.4 19.525 2.85 18.0625T3.925 15.975ZM5.35 17.375Q5.1 17.625 4.85 18.2875T4.5 19.625Q5.175 19.525 5.8375 19.2875T6.75 18.8Q7.05 18.5 7.075 18.075T6.8 17.35Q6.5 17.05 6.075 17.0625T5.35 17.375Z\"),\n    SCHEMA(\"M4 23V17H6.5V15H4V9H6.5V7H4V1h7V7H8.5V9H11v2h3V9h7v6H14V13H11v2H8.5v2H11v6H4Zm2-2H9V19H6v2Zm0-8H9V11H6v2Zm10 0h3V11H16v2ZM6 5H9V3H6V5ZM7.5 4Zm0 8Zm10 0Zm-10 8Z\"),\n    SCHEMA_FILL(\"M4 23V17H6.5V15H4V9H6.5V7H4V1h7V7H8.5V9H11v2h3V9h7v6H14V13H11v2H8.5v2H11v6H4Z\"),\n    SCREENSHOT_MONITOR(\"M15 16H19V12H17.5V14.5H15V16ZM5 10H6.5V7.5H9V6H5V10ZM8 21V19H4Q3.175 19 2.5875 18.4125T2 17V5Q2 4.175 2.5875 3.5875T4 3H20Q20.825 3 21.4125 3.5875T22 5V17Q22 17.825 21.4125 18.4125T20 19H16V21H8ZM4 17H20V5H4V17ZM4 17V5 17Z\"),\n    SCRIPT(\"M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z\"), // Not Material\n    SEARCH(\"M19.6 21 13.3 14.7Q12.55 15.3 11.575 15.65T9.5 16Q6.775 16 4.8875 14.1125T3 9.5Q3 6.775 4.8875 4.8875T9.5 3Q12.225 3 14.1125 4.8875T16 9.5Q16 10.6 15.65 11.575T14.7 13.3L21 19.6 19.6 21ZM9.5 14Q11.375 14 12.6875 12.6875T14 9.5Q14 7.625 12.6875 6.3125T9.5 5Q7.625 5 6.3125 6.3125T5 9.5Q5 11.375 6.3125 12.6875T9.5 14Z\"),\n    SELECT_ALL(\"M7 17V7H17V17H7ZM9 15H15V9H9V15ZM5 19V21Q4.175 21 3.5875 20.4125T3 19H5ZM3 17V15H5V17H3ZM3 13V11H5V13H3ZM3 9V7H5V9H3ZM5 5H3Q3 4.175 3.5875 3.5875T5 3V5ZM7 21V19H9V21H7ZM7 5V3H9V5H7ZM11 21V19H13V21H11ZM11 5V3H13V5H11ZM15 21V19H17V21H15ZM15 5V3H17V5H15ZM19 21V19H21Q21 19.825 20.4125 20.4125T19 21ZM19 17V15H21V17H19ZM19 13V11H21V13H19ZM19 9V7H21V9H19ZM19 5V3Q19.825 3 20.4125 3.5875T21 5H19Z\"),\n    SETTINGS(\"M19.43 12.98C19.47 12.66 19.5 12.34 19.5 12 19.5 11.66 19.47 11.34 19.43 11.02L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.57 5.11 19.4 5.02 19.22 5.02 19.16 5.02 19.1 5.03 19.05 5.05L16.56 6.05C16.04 5.65 15.48 5.32 14.87 5.07L14.49 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.89 5.03 4.83 5.02 4.77 5.02 4.6 5.02 4.43 5.11 4.34 5.27L2.34 8.73C2.21 8.95 2.27 9.22 2.46 9.37L4.57 11.02C4.53 11.34 4.5 11.67 4.5 12 4.5 12.33 4.53 12.66 4.57 12.98L2.46 14.63C2.27 14.78 2.22 15.05 2.34 15.27L4.34 18.73C4.43 18.89 4.6 18.98 4.78 18.98 4.84 18.98 4.9 18.97 4.95 18.95L7.44 17.95C7.96 18.35 8.52 18.68 9.13 18.93L9.51 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.49 21.58L14.87 18.93C15.48 18.68 16.04 18.34 16.56 17.95L19.05 18.95C19.11 18.97 19.17 18.98 19.23 18.98 19.4 18.98 19.57 18.89 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.98ZM17.45 11.27C17.49 11.58 17.5 11.79 17.5 12 17.5 12.21 17.48 12.43 17.45 12.73L17.31 13.86 18.2 14.56 19.28 15.4 18.58 16.61 17.31 16.1 16.27 15.68 15.37 16.36C14.94 16.68 14.53 16.92 14.12 17.09L13.06 17.52 12.9 18.65 12.7 20H11.3L11.11 18.65 10.95 17.52 9.89 17.09C9.46 16.91 9.06 16.68 8.66 16.38L7.75 15.68 6.69 16.11 5.42 16.62 4.72 15.41 5.8 14.57 6.69 13.87 6.55 12.74C6.52 12.43 6.5 12.2 6.5 12S6.52 11.57 6.55 11.27L6.69 10.14 5.8 9.44 4.72 8.6 5.42 7.39 6.69 7.9 7.73 8.32 8.63 7.64C9.06 7.32 9.47 7.08 9.88 6.91L10.94 6.48 11.1 5.35 11.3 4H12.69L12.88 5.35 13.04 6.48 14.1 6.91C14.53 7.09 14.93 7.32 15.33 7.62L16.24 8.32 17.3 7.89 18.57 7.38 19.27 8.59 18.2 9.44 17.31 10.14 17.45 11.27ZM12 8C9.79 8 8 9.79 8 12S9.79 16 12 16 16 14.21 16 12 14.21 8 12 8ZM12 14C10.9 14 10 13.1 10 12S10.9 10 12 10 14 10.9 14 12 13.1 14 12 14Z\"), // Material Icons\n    SETTINGS_FILL(\"M9.25 22l-.4-3.2q-.325-.125-.6125-.3t-.5625-.375L4.7 19.375l-2.75-4.75 2.575-1.95Q4.5 12.5 4.5 12.3375v-.675q0-.1625.025-.3375L1.95 9.375 4.7 4.625l2.975 1.25q.275-.2.575-.375t.6-.3L9.25 2h5.5l.4 3.2q.325.125.6125.3t.5625.375L19.3 4.625l2.75 4.75-2.575 1.95q.025.175.025.3375v.675q0 .1625-.05.3375l2.575 1.95-2.75 4.75-2.95-1.25q-.275.2-.575.375t-.6.3l-.4 3.2H9.25Zm2.8-6.5q1.45 0 2.475-1.025T15.55 12 14.525 9.525 12.05 8.5q-1.475 0-2.4875 1.025T8.55 12q0 1.45 1.0125 2.475T12.05 15.5Z\"), // Material Icons\n    STADIA_CONTROLLER(\"M4.725 20Q3.225 20 2.1625 18.925T1.05 16.325Q1.05 16.1 1.075 15.875T1.15 15.425L3.25 7.025Q3.6 5.675 4.675 4.8375T7.125 4H16.875Q18.25 4 19.325 4.8375T20.75 7.025L22.85 15.425Q22.9 15.65 22.9375 15.8875T22.975 16.35Q22.975 17.875 21.8875 18.9375T19.275 20Q18.225 20 17.325 19.45T15.975 17.95L15.275 16.5Q15.15 16.25 14.9 16.125T14.375 16H9.625Q9.35 16 9.1 16.125T8.725 16.5L8.025 17.95Q7.575 18.9 6.675 19.45T4.725 20ZM4.8 18Q5.275 18 5.6625 17.75T6.25 17.075L6.95 15.65Q7.325 14.875 8.05 14.4375T9.625 14H14.375Q15.225 14 15.95 14.45T17.075 15.65L17.775 17.075Q17.975 17.5 18.3625 17.75T19.225 18Q19.925 18 20.425 17.5375T20.95 16.375Q20.95 16.4 20.9 15.9L18.8 7.525Q18.625 6.85 18.1 6.425T16.875 6H7.125Q6.425 6 5.8875 6.425T5.2 7.525L3.1 15.9Q3.05 16.05 3.05 16.35 3.05 17.05 3.5625 17.525T4.8 18ZM13.5 11Q13.925 11 14.2125 10.7125T14.5 10Q14.5 9.575 14.2125 9.2875T13.5 9Q13.075 9 12.7875 9.2875T12.5 10Q12.5 10.425 12.7875 10.7125T13.5 11ZM15.5 9Q15.925 9 16.2125 8.7125T16.5 8Q16.5 7.575 16.2125 7.2875T15.5 7Q15.075 7 14.7875 7.2875T14.5 8Q14.5 8.425 14.7875 8.7125T15.5 9ZM15.5 13Q15.925 13 16.2125 12.7125T16.5 12Q16.5 11.575 16.2125 11.2875T15.5 11Q15.075 11 14.7875 11.2875T14.5 12Q14.5 12.425 14.7875 12.7125T15.5 13ZM17.5 11Q17.925 11 18.2125 10.7125T18.5 10Q18.5 9.575 18.2125 9.2875T17.5 9Q17.075 9 16.7875 9.2875T16.5 10Q16.5 10.425 16.7875 10.7125T17.5 11ZM8.5 12.5Q8.825 12.5 9.0375 12.2875T9.25 11.75V10.75H10.25Q10.575 10.75 10.7875 10.5375T11 10Q11 9.675 10.7875 9.4625T10.25 9.25H9.25V8.25Q9.25 7.925 9.0375 7.7125T8.5 7.5Q8.175 7.5 7.9625 7.7125T7.75 8.25V9.25H6.75Q6.425 9.25 6.2125 9.4625T6 10Q6 10.325 6.2125 10.5375T6.75 10.75H7.75V11.75Q7.75 12.075 7.9625 12.2875T8.5 12.5ZM12 12Z\"),\n    STADIA_CONTROLLER_FILL(\"M4.725 20q-1.5 0-2.5625-1.075T1.05 16.325q0-.225.025-.45t.075-.45l2.1-8.4q.35-1.35 1.425-2.1875T7.125 4h9.75q1.375 0 2.45.8375T20.75 7.025l2.1 8.4q.05.225.0875.4625t.0375.4625q0 1.525-1.0875 2.5875T19.275 20q-1.05 0-1.95-.55t-1.35-1.5l-.7-1.45q-.125-.25-.375-.375T14.375 16H9.625q-.275 0-.525.125t-.375.375l-.7 1.45q-.45.95-1.35 1.5T4.725 20ZM13.5 11q.425 0 .7125-.2875T14.5 10t-.2875-.7125T13.5 9t-.7125.2875T12.5 10t.2875.7125T13.5 11Zm2-2q.425 0 .7125-.2875T16.5 8q0-.425-.2875-.7125T15.5 7q-.425 0-.7125.2875T14.5 8t.2875.7125T15.5 9Zm0 4q.425 0 .7125-.2875T16.5 12q0-.425-.2875-.7125T15.5 11q-.425 0-.7125.2875T14.5 12t.2875.7125T15.5 13Zm2-2q.425 0 .7125-.2875T18.5 10q0-.425-.2875-.7125T17.5 9q-.425 0-.7125.2875T16.5 10q0 .425.2875.7125T17.5 11Zm-9 1.5q.325 0 .5375-.2125T9.25 11.75v-1h1q.325 0 .5375-.2125T11 10t-.2125-.5375T10.25 9.25h-1v-1q0-.325-.2125-.5375T8.5 7.5q-.325 0-.5375.2125T7.75 8.25v1h-1q-.325 0-.5375.2125T6 10q0 .325.2125.5375T6.75 10.75h1v1q0 .325.2125.5375T8.5 12.5Z\"),\n    STYLE(\"M3.975 19.8 3.125 19.45Q2.35 19.125 2.0875 18.325T2.175 16.75L3.975 12.85V19.8ZM7.975 22Q7.15 22 6.5625 21.4125T5.975 20V14L8.625 21.35Q8.7 21.525 8.775 21.6875T8.975 22H7.975ZM13.125 21.9Q12.325 22.2 11.575 21.825T10.525 20.65L6.075 8.45Q5.775 7.65 6.125 6.8875T7.275 5.85L14.825 3.1Q15.625 2.8 16.375 3.175T17.425 4.35L21.875 16.55Q22.175 17.35 21.825 18.1125T20.675 19.15L13.125 21.9ZM10.975 10Q11.4 10 11.6875 9.7125T11.975 9Q11.975 8.575 11.6875 8.2875T10.975 8Q10.55 8 10.2625 8.2875T9.975 9Q9.975 9.425 10.2625 9.7125T10.975 10ZM12.425 20 19.975 17.25 15.525 5 7.975 7.75 12.425 20ZM7.975 7.75 15.525 5 7.975 7.75Z\"),\n    STYLE_FILL(\"M3.975 19.8l-.85-.35q-.775-.325-1.0375-1.125T2.175 16.75l1.8-3.9V19.8Zm4 2.2q-.825 0-1.4125-.5875T5.975 20V14l2.65 7.35q.075.175.15.3375t.2.3125h-1Zm5.15-.1q-.8.3-1.55-.075t-1.05-1.175L6.075 8.45q-.3-.8.05-1.5625T7.275 5.85l7.55-2.75q.8-.3 1.55.075t1.05 1.175l4.45 12.2q.3.8-.05 1.5625T20.675 19.15l-7.55 2.75ZM10.975 10q.425 0 .7125-.2875T11.975 9q0-.425-.2875-.7125T10.975 8t-.7125.2875T9.975 9t.2875.7125T10.975 10Z\"),\n    TEXTURE(\"M4.4 21q-.475-.1-.8875-.5125T3 19.6L19.6 3q.525.125.9.5125t.525.8875L4.4 21ZM3 14.7v-2.8L11.9 3h2.8L3 14.7ZM3 7V5q0-.825.5875-1.4125T5 3h2L3 7Zm14 14 4-4v2q0 .825-.5875 1.4125T19 21h-2Zm-7.7 0L21 9.3v2.8L12.1 21H9.3Z\"),\n    TRIP(\"M4 21Q3.175 21 2.5875 20.4125T2 19V8Q2 7.175 2.5875 6.5875T4 6H8V4Q8 3.175 8.5875 2.5875T10 2H14Q14.825 2 15.4125 2.5875T16 4V6H20Q20.825 6 21.4125 6.5875T22 8V19Q22 19.825 21.4125 20.4125T20 21H4ZM10 6H14V4H10V6ZM6 8H4V19H6V8ZM16 19V8H8V19H16ZM18 8V19H20V8H18ZM12 13.5Z\"),\n    TUNE(\"M11 21V15H13V17H21V19H13V21H11ZM3 19V17H9V19H3ZM7 15V13H3V11H7V9H9V15H7ZM11 13V11H21V13H11ZM15 9V3H17V5H21V7H17V9H15ZM3 7V5H13V7H3Z\"),\n    UNFOLD_MORE(\"M12 21 7.5 16.5l1.45-1.45L12 18.1l3.05-3.05 1.45 1.45L12 21ZM8.95 9.05 7.5 7.6 12 3.1l4.5 4.5-1.45 1.45L12 6 8.95 9.05Z\"),\n    UPDATE(\"M12 21Q10.125 21 8.4875 20.2875T5.6375 18.3625Q4.425 17.15 3.7125 15.5125T3 12Q3 10.125 3.7125 8.4875T5.6375 5.6375Q6.85 4.425 8.4875 3.7125T12 3Q14.05 3 15.8875 3.875T19 6.35V4H21V10H15V8H17.75Q16.725 6.6 15.225 5.8T12 5Q9.075 5 7.0375 7.0375T5 12Q5 14.925 7.0375 16.9625T12 19Q14.625 19 16.5875 17.3T18.9 13H20.95Q20.575 16.425 18.0125 18.7125T12 21ZM14.8 16.2 11 12.4V7H13V11.6L16.2 14.8 14.8 16.2Z\"),\n    UNDO(\"M7 19v-2h7.1q1.575 0 2.738-1T18 13.5T16.838 11T14.1 10H7.8l2.6 2.6L9 14L4 9l5-5l1.4 1.4L7.8 8h6.3q2.425 0 4.163 1.575T20 13.5t-1.737 3.925T14.1 19z\"),\n    VISIBILITY(\"M12 16q1.875 0 3.1875-1.3125T16.5 11.5 15.1875 8.3125 12 7 8.8125 8.3125 7.5 11.5t1.3125 3.1875T12 16Zm0-1.8q-1.125 0-1.9125-.7875T9.3 11.5t.7875-1.9125T12 8.8q1.125 0 1.9125.7875T14.7 11.5q0 1.125-.7875 1.9125T12 14.2ZM12 19q-3.65 0-6.65-2.0375T1 11.5Q2.35 8.075 5.35 6.0375T12 4q3.65 0 6.65 2.0375T23 11.5q-1.35 3.425-4.35 5.4625T12 19Zm0-7.5ZM12 17q2.825 0 5.1875-1.4875T20.8 11.5q-1.25-2.525-3.6125-4.0125T12 6 6.8125 7.4875 3.2 11.5q1.25 2.525 3.6125 4.0125T12 17Z\"),\n    VISIBILITY_OFF(\"M16.1 13.3l-1.45-1.45q.225-1.175-.675-2.2t-2.325-.8L10.2 7.4q.425-.2.8625-.3T12 7q1.875 0 3.1875 1.3125T16.5 11.5q0 .5-.1.9375t-.3.8625Zm3.2 3.15-1.45-1.4q.95-.725 1.6875-1.5875T20.8 11.5q-1.25-2.525-3.5875-4.0125T12 6q-.725 0-1.425.1T9.2 6.4L7.65 4.85q1.025-.425 2.1-.6375T12 4q3.775 0 6.725 2.0875T23 11.5q-.575 1.475-1.5125 2.7375T19.3 16.45Zm.5 6.15-4.2-4.15q-.875.275-1.7625.4125T12 19q-3.775 0-6.725-2.0875T1 11.5q.525-1.325 1.325-2.4625T4.15 7L1.4 4.2 2.8 2.8 21.2 21.2l-1.4 1.4ZM5.55 8.4q-.725.65-1.325 1.425T3.2 11.5q1.25 2.525 3.5875 4.0125T12 17q.5 0 .975-.0625T13.95 16.8l-.9-.95q-.275.075-.525.1125T12 16q-1.875 0-3.1875-1.3125T7.5 11.5q0-.275.0375-.525T7.65 10.45L5.55 8.4Zm7.975 2.325ZM9.75 12.6Z\"),\n    WARNING(\"M1 21 12 2 23 21H1ZM4.45 19H19.55L12 6 4.45 19ZM12 18Q12.425 18 12.7125 17.7125T13 17Q13 16.575 12.7125 16.2875T12 16Q11.575 16 11.2875 16.2875T11 17Q11 17.425 11.2875 17.7125T12 18ZM11 15H13V10H11V15ZM12 12.5Z\"),\n    WB_SUNNY(\"M11 4V1H13V4H11ZM11 23V20H13V23H11ZM20 13V11H23V13H20ZM1 13V11H4V13H1ZM18.7 6.7 17.3 5.3 19.05 3.5 20.5 4.95 18.7 6.7ZM4.95 20.5 3.5 19.05 5.3 17.3 6.7 18.7 4.95 20.5ZM19.05 20.5 17.3 18.7 18.7 17.3 20.5 19.05 19.05 20.5ZM5.3 6.7 3.5 4.95 4.95 3.5 6.7 5.3 5.3 6.7ZM12 18Q9.5 18 7.75 16.25T6 12Q6 9.5 7.75 7.75T12 6Q14.5 6 16.25 7.75T18 12Q18 14.5 16.25 16.25T12 18ZM12 16Q13.675 16 14.8375 14.8375T16 12Q16 10.325 14.8375 9.1625T12 8Q10.325 8 9.1625 9.1625T8 12Q8 13.675 9.1625 14.8375T12 16ZM12 12Z\"),\n    WB_SUNNY_FILL(\"M11 4V1h2V4H11Zm0 19V20h2v3H11Zm9-10V11h3v2H20ZM1 13V11H4v2H1ZM18.7 6.7 17.3 5.3l1.75-1.8L20.5 4.95 18.7 6.7ZM4.95 20.5 3.5 19.05 5.3 17.3l1.4 1.4-1.75 1.8Zm14.1 0-1.75-1.8 1.4-1.4 1.8 1.75-1.45 1.45ZM5.3 6.7 3.5 4.95 4.95 3.5 6.7 5.3 5.3 6.7ZM12 18q-2.5 0-4.25-1.75T6 12 7.75 7.75 12 6t4.25 1.75T18 12t-1.75 4.25T12 18Z\"),\n    ;\n\n    public static final double DEFAULT_SIZE = 24;\n\n    static void setSize(SVGPath path, double size) {\n        double scale = size / DEFAULT_SIZE;\n        path.setScaleX(scale);\n        path.setScaleY(scale);\n    }\n\n    private final String rawPath;\n    private String path;\n\n    SVG(String rawPath) {\n        this.rawPath = rawPath;\n    }\n\n    public String getPath() {\n        if (path == null)\n            // We move the current point so that SVGPath will treat 0 0 24 24 as the layout bounds\n            path = \"M24 24ZM0 0Z\" + rawPath;\n        return path;\n    }\n\n    public SVGPath createIcon() {\n        var path = new SVGPath();\n        path.getStyleClass().add(\"svg\");\n        path.setContent(getPath());\n        return path;\n    }\n\n    public SVGContainer createIcon(double size) {\n        return new SVGContainer(this, size);\n    }\n\n    public SVGPath createIcon(ObservableValue<? extends Paint> color) {\n        SVGPath path = createIcon();\n        path.fillProperty().bind(color);\n        return path;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/SVGContainer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.scene.Parent;\nimport javafx.scene.shape.SVGPath;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.animation.Motion;\n\n/// A lightweight wrapper for displaying [SVG] icons.\n///\n/// @author Glavo\npublic final class SVGContainer extends Parent {\n\n    private static final String DEFAULT_STYLE_CLASS = \"svg-container\";\n\n    private final SVGPath path = new SVGPath();\n    private SVG icon = SVG.NONE;\n    private double iconSize = SVG.DEFAULT_SIZE;\n    private SVGPath tempPath;\n    private Timeline timeline;\n\n    {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n        this.path.getStyleClass().add(\"svg\");\n    }\n\n    /// Creates an SVGContainer with the default icon and the default icon size.\n    public SVGContainer() {\n        this(SVG.NONE, SVG.DEFAULT_SIZE);\n    }\n\n    /// Creates an SVGContainer showing the given icon using the default icon size.\n    ///\n    /// @param icon the [SVG] icon to display\n    public SVGContainer(SVG icon) {\n        this(icon, SVG.DEFAULT_SIZE);\n    }\n\n    /// Creates an SVGContainer with a custom icon size. The initial icon is\n    /// [SVG#NONE].\n    ///\n    /// @param iconSize the icon size\n    public SVGContainer(double iconSize) {\n        this(SVG.NONE, iconSize);\n    }\n\n    /// Creates an SVGContainer with the specified icon and size.\n    ///\n    /// @param icon     the [SVG] icon to display\n    /// @param iconSize the icon size\n    public SVGContainer(SVG icon, double iconSize) {\n        setIconSizeImpl(iconSize);\n        setIcon(icon);\n    }\n\n    public double getIconSize() {\n        return iconSize;\n    }\n\n    private void setIconSizeImpl(double newSize) {\n        this.iconSize = newSize;\n        SVG.setSize(path, newSize);\n        if (tempPath != null)\n            SVG.setSize(tempPath, newSize);\n    }\n\n    public void setIconSize(double newSize) {\n        setIconSizeImpl(newSize);\n        requestLayout();\n    }\n\n    /// Gets the currently displayed icon.\n    public SVG getIcon() {\n        return icon;\n    }\n\n    /// Sets the icon to display without animation.\n    public void setIcon(SVG newIcon) {\n        setIcon(newIcon, Duration.ZERO);\n    }\n\n    /// Sets the icon to display with a cross-fade animation.\n    public void setIcon(SVG newIcon, Duration animationDuration) {\n        if (timeline != null) {\n            timeline.stop();\n            timeline = null;\n        }\n\n        SVG oldIcon = this.icon;\n        this.icon = newIcon;\n\n        if (animationDuration.equals(Duration.ZERO)) {\n            path.setContent(newIcon.getPath());\n            path.setOpacity(1);\n            if (getChildren().size() != 1)\n                getChildren().setAll(path);\n        } else {\n            if (tempPath == null) {\n                tempPath = new SVGPath();\n                tempPath.getStyleClass().add(\"svg\");\n                SVG.setSize(tempPath, iconSize);\n            } else\n                tempPath.setOpacity(1);\n\n            tempPath.setContent(oldIcon.getPath());\n            getChildren().setAll(path, tempPath);\n\n            path.setOpacity(0);\n            path.setContent(newIcon.getPath());\n\n            timeline = new Timeline(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(path.opacityProperty(), 0, Motion.LINEAR),\n                            new KeyValue(tempPath.opacityProperty(), 1, Motion.LINEAR)\n                    ),\n                    new KeyFrame(animationDuration,\n                            new KeyValue(path.opacityProperty(), 1, Motion.LINEAR),\n                            new KeyValue(tempPath.opacityProperty(), 0, Motion.LINEAR)\n                    )\n            );\n            timeline.setOnFinished(e -> {\n                getChildren().setAll(path);\n                timeline = null;\n            });\n            timeline.play();\n        }\n    }\n\n    // Parent\n\n    @Override\n    public double prefWidth(double height) {\n        return iconSize;\n    }\n\n    @Override\n    public double prefHeight(double width) {\n        return iconSize;\n    }\n\n    @Override\n    public double minHeight(double width) {\n        return iconSize;\n    }\n\n    @Override\n    public double minWidth(double height) {\n        return iconSize;\n    }\n\n    @Override\n    protected void layoutChildren() {\n        // Do nothing\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/ScrollUtils.java",
    "content": "// Copy from https://github.com/palexdev/MaterialFX/blob/c8038ce2090f5cddf923a19d79cc601db86a4d17/materialfx/src/main/java/io/github/palexdev/materialfx/utils/ScrollUtils.java\n\n/*\n * Copyright (C) 2022 Parisi Alessandro\n * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX).\n *\n * MaterialFX is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * MaterialFX is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.animation.Animation;\nimport javafx.animation.Animation.Status;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.Timeline;\nimport javafx.event.EventHandler;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.skin.VirtualFlow;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.input.ScrollEvent;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.util.Holder;\n\n/**\n * Utility class for ScrollPanes.\n */\nfinal class ScrollUtils {\n\n    public enum ScrollDirection {\n        UP(-1), RIGHT(-1), DOWN(1), LEFT(1);\n\n        final int intDirection;\n\n        ScrollDirection(int intDirection) {\n            this.intDirection = intDirection;\n        }\n\n        public int intDirection() {\n            return intDirection;\n        }\n    }\n\n    private static final double DEFAULT_SPEED = 1.0;\n    private static final double DEFAULT_TRACK_PAD_ADJUSTMENT = 7.0;\n\n    private static final double CUTOFF_DELTA = 0.01;\n\n    /**\n     * Determines if the given ScrollEvent comes from a trackpad.\n     * <p></p>\n     * Although this method works in most cases, it is not very accurate.\n     * Since in JavaFX there's no way to tell if a ScrollEvent comes from a trackpad or a mouse\n     * we use this trick: I noticed that a mouse scroll has a delta of 32 (don't know if it changes depending on the device or OS)\n     * and trackpad scrolls have a way smaller delta. So depending on the scroll direction we check if the delta is lesser than 10\n     * (trackpad event) or greater(mouse event).\n     *\n     * @see ScrollEvent#getDeltaX()\n     * @see ScrollEvent#getDeltaY()\n     */\n    public static boolean isTrackPad(ScrollEvent event, ScrollDirection scrollDirection) {\n        return switch (scrollDirection) {\n            case UP, DOWN -> Math.abs(event.getDeltaY()) < 10;\n            case LEFT, RIGHT -> Math.abs(event.getDeltaX()) < 10;\n        };\n    }\n\n    /**\n     * Determines the scroll direction of the given ScrollEvent.\n     * <p></p>\n     * Although this method works fine, it is not very accurate.\n     * In JavaFX there's no concept of scroll direction, if you try to scroll with a trackpad\n     * you'll notice that you can scroll in both directions at the same time, both deltaX and deltaY won't be 0.\n     * <p></p>\n     * For this method to work we assume that this behavior is not possible.\n     * <p></p>\n     * If deltaY is 0 we return LEFT or RIGHT depending on deltaX (respectively if lesser or greater than 0).\n     * <p>\n     * Else we return DOWN or UP depending on deltaY (respectively if lesser or greater than 0).\n     *\n     * @see ScrollEvent#getDeltaX()\n     * @see ScrollEvent#getDeltaY()\n     */\n    public static ScrollDirection determineScrollDirection(ScrollEvent event) {\n        double deltaX = event.getDeltaX();\n        double deltaY = event.getDeltaY();\n\n        if (deltaY == 0.0) {\n            return deltaX < 0 ? ScrollDirection.LEFT : ScrollDirection.RIGHT;\n        } else {\n            return deltaY < 0 ? ScrollDirection.DOWN : ScrollDirection.UP;\n        }\n    }\n\n    //================================================================================\n    // ScrollPanes\n    //================================================================================\n\n    /**\n     * Adds a smooth scrolling effect to the given scroll pane,\n     * calls {@link #addSmoothScrolling(ScrollPane, double)} with a\n     * default speed value of 1.\n     */\n    public static void addSmoothScrolling(ScrollPane scrollPane) {\n        addSmoothScrolling(scrollPane, DEFAULT_SPEED);\n    }\n\n    /**\n     * Adds a smooth scrolling effect to the given scroll pane with the given scroll speed.\n     * Calls {@link #addSmoothScrolling(ScrollPane, double, double)}\n     * with a default trackPadAdjustment of 7.\n     */\n    public static void addSmoothScrolling(ScrollPane scrollPane, double speed) {\n        addSmoothScrolling(scrollPane, speed, DEFAULT_TRACK_PAD_ADJUSTMENT);\n    }\n\n    /**\n     * Adds a smooth scrolling effect to the given scroll pane with the given\n     * scroll speed and the given trackPadAdjustment.\n     * <p></p>\n     * The trackPadAdjustment is a value used to slow down the scrolling if a trackpad is used.\n     * This is kind of a workaround and it's not perfect, but at least it's way better than before.\n     * The default value is 7, tested up to 10, further values can cause scrolling misbehavior.\n     */\n    public static void addSmoothScrolling(ScrollPane scrollPane, double speed, double trackPadAdjustment) {\n        smoothScroll(scrollPane, speed, trackPadAdjustment);\n    }\n\n    /// @author Glavo\n    public static void addSmoothScrolling(VirtualFlow<?> virtualFlow) {\n        addSmoothScrolling(virtualFlow, DEFAULT_SPEED);\n    }\n\n    /// @author Glavo\n    public static void addSmoothScrolling(VirtualFlow<?> virtualFlow, double speed) {\n        addSmoothScrolling(virtualFlow, speed, DEFAULT_TRACK_PAD_ADJUSTMENT);\n    }\n\n    /// @author Glavo\n    public static void addSmoothScrolling(VirtualFlow<?> virtualFlow, double speed, double trackPadAdjustment) {\n        smoothScroll(virtualFlow, speed, trackPadAdjustment);\n    }\n\n    private static final double[] FRICTIONS = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};\n    private static final Duration DURATION = Duration.millis(3);\n\n    private static void smoothScroll(ScrollPane scrollPane, double speed, double trackPadAdjustment) {\n        final double[] derivatives = new double[FRICTIONS.length];\n\n        Timeline timeline = new Timeline();\n        Holder<ScrollDirection> scrollDirectionHolder = new Holder<>();\n        final EventHandler<MouseEvent> mouseHandler = event -> timeline.stop();\n        final EventHandler<ScrollEvent> scrollHandler = event -> {\n            if (event.getEventType() == ScrollEvent.SCROLL) {\n                ScrollDirection scrollDirection = determineScrollDirection(event);\n                scrollDirectionHolder.value = scrollDirection;\n\n                double currentSpeed = isTrackPad(event, scrollDirection) ? speed / trackPadAdjustment : speed;\n\n                derivatives[0] += scrollDirection.intDirection * currentSpeed;\n                if (timeline.getStatus() == Status.STOPPED) {\n                    timeline.play();\n                }\n                event.consume();\n            }\n        };\n        if (scrollPane.getContent().getParent() != null) {\n            scrollPane.getContent().getParent().addEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);\n            scrollPane.getContent().getParent().addEventHandler(ScrollEvent.ANY, scrollHandler);\n        }\n        scrollPane.getContent().parentProperty().addListener((observable, oldValue, newValue) -> {\n            if (oldValue != null) {\n                oldValue.removeEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);\n                oldValue.removeEventHandler(ScrollEvent.ANY, scrollHandler);\n            }\n            if (newValue != null) {\n                newValue.addEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);\n                newValue.addEventHandler(ScrollEvent.ANY, scrollHandler);\n            }\n        });\n\n        timeline.getKeyFrames().add(new KeyFrame(DURATION, event -> {\n            for (int i = 0; i < derivatives.length; i++) {\n                derivatives[i] *= FRICTIONS[i];\n            }\n            for (int i = 1; i < derivatives.length; i++) {\n                derivatives[i] += derivatives[i - 1];\n            }\n\n            double dy = derivatives[derivatives.length - 1];\n            double size;\n            switch (scrollDirectionHolder.value) {\n                case LEFT:\n                case RIGHT:\n                    size = scrollPane.getContent().getLayoutBounds().getWidth();\n                    scrollPane.setHvalue(Math.min(Math.max(scrollPane.getHvalue() + dy / size, 0), 1));\n                    break;\n                case UP:\n                case DOWN:\n                    size = scrollPane.getContent().getLayoutBounds().getHeight();\n                    scrollPane.setVvalue(Math.min(Math.max(scrollPane.getVvalue() + dy / size, 0), 1));\n                    break;\n            }\n\n            if (Math.abs(dy) < CUTOFF_DELTA) {\n                timeline.stop();\n            }\n        }));\n        timeline.setCycleCount(Animation.INDEFINITE);\n    }\n\n    /// @author Glavo\n    private static void smoothScroll(VirtualFlow<?> virtualFlow, double speed, double trackPadAdjustment) {\n        if (!virtualFlow.isVertical())\n            return;\n\n        final double[] derivatives = new double[FRICTIONS.length];\n\n        Timeline timeline = new Timeline();\n        final EventHandler<MouseEvent> mouseHandler = event -> timeline.stop();\n        final EventHandler<ScrollEvent> scrollHandler = event -> {\n            if (event.getEventType() == ScrollEvent.SCROLL) {\n                ScrollDirection scrollDirection = determineScrollDirection(event);\n                if (scrollDirection == ScrollDirection.LEFT || scrollDirection == ScrollDirection.RIGHT) {\n                    return;\n                }\n                double currentSpeed = isTrackPad(event, scrollDirection) ? speed / trackPadAdjustment : speed;\n\n                derivatives[0] += scrollDirection.intDirection * currentSpeed;\n                if (timeline.getStatus() == Status.STOPPED) {\n                    timeline.play();\n                }\n                event.consume();\n            }\n        };\n        virtualFlow.addEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);\n        virtualFlow.addEventFilter(ScrollEvent.ANY, scrollHandler);\n\n        timeline.getKeyFrames().add(new KeyFrame(DURATION, event -> {\n            for (int i = 0; i < derivatives.length; i++) {\n                derivatives[i] *= FRICTIONS[i];\n            }\n            for (int i = 1; i < derivatives.length; i++) {\n                derivatives[i] += derivatives[i - 1];\n            }\n\n            double dy = derivatives[derivatives.length - 1];\n            virtualFlow.scrollPixels(dy);\n\n            if (Math.abs(dy) < CUTOFF_DELTA) {\n                timeline.stop();\n            }\n        }));\n        timeline.setCycleCount(Animation.INDEFINITE);\n    }\n\n    private ScrollUtils() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXListView;\nimport javafx.beans.binding.Bindings;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.StackPane;\nimport org.jackhuang.hmcl.ui.construct.ComponentList;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\n\nimport java.util.List;\n\npublic abstract class ToolbarListPageSkin<E, P extends ListPageBase<E>> extends SkinBase<P> {\n\n    protected final JFXListView<E> listView;\n\n    public ToolbarListPageSkin(P skinnable) {\n        super(skinnable);\n\n        ComponentList root = new ComponentList();\n        root.getStyleClass().add(\"no-padding\");\n\n        StackPane container = new StackPane();\n        container.getChildren().add(root);\n        StackPane.setMargin(root, new Insets(10));\n\n        List<Node> toolbarButtons = initializeToolbar(skinnable);\n        if (!toolbarButtons.isEmpty()) {\n            HBox toolbar = new HBox();\n            toolbar.setAlignment(Pos.CENTER_LEFT);\n            toolbar.setPickOnBounds(false);\n            toolbar.getChildren().setAll(toolbarButtons);\n            root.getContent().add(toolbar);\n        }\n\n        SpinnerPane spinnerPane = new SpinnerPane();\n        spinnerPane.loadingProperty().bind(skinnable.loadingProperty());\n        spinnerPane.failedReasonProperty().bind(skinnable.failedReasonProperty());\n        spinnerPane.onFailedActionProperty().bind(skinnable.onFailedActionProperty());\n\n        ComponentList.setVgrow(spinnerPane, Priority.ALWAYS);\n\n        {\n            this.listView = new JFXListView<>();\n            this.listView.setPadding(Insets.EMPTY);\n            this.listView.setCellFactory(listView -> createListCell((JFXListView<E>) listView));\n            this.listView.getStyleClass().add(\"no-horizontal-scrollbar\");\n            Bindings.bindContent(this.listView.getItems(), skinnable.itemsProperty());\n            FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);\n\n            spinnerPane.setContent(listView);\n        }\n\n        root.getContent().add(spinnerPane);\n\n        getChildren().setAll(container);\n    }\n\n    public static JFXButton createToolbarButton2(String text, SVG svg, Runnable onClick) {\n        JFXButton ret = new JFXButton();\n        ret.getStyleClass().add(\"jfx-tool-bar-button\");\n        ret.setGraphic(svg.createIcon(20));\n        ret.setText(text);\n        ret.setOnAction(e -> onClick.run());\n        return ret;\n    }\n\n    public static JFXButton createDecoratorButton(String tooltip, SVG svg, Runnable onClick) {\n        JFXButton ret = new JFXButton();\n        ret.getStyleClass().add(\"jfx-decorator-button\");\n        ret.setGraphic(svg.createIcon(20));\n        FXUtils.installFastTooltip(ret, tooltip);\n        ret.setOnAction(e -> onClick.run());\n        return ret;\n    }\n\n    protected abstract List<Node> initializeToolbar(P skinnable);\n\n    protected ListCell<E> createListCell(JFXListView<E> listView) {\n        return new ListCell<>() {\n            @Override\n            protected void updateItem(E item, boolean empty) {\n                super.updateItem(item, empty);\n                if (!empty && item instanceof Node node) {\n                    setGraphic(node);\n                    setText(null);\n                } else {\n                    setGraphic(null);\n                    setText(null);\n                }\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport com.jfoenix.controls.JFXSpinner;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.construct.DialogCloseEvent;\nimport org.jackhuang.hmcl.ui.construct.JFXHyperlink;\nimport org.jackhuang.hmcl.upgrade.RemoteVersion;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Node;\n\nimport java.net.URL;\n\nimport static org.jackhuang.hmcl.Metadata.CHANGELOG_URL;\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class UpgradeDialog extends JFXDialogLayout {\n\n    public UpgradeDialog(RemoteVersion remoteVersion, Runnable updateRunnable) {\n        maxWidthProperty().bind(Controllers.getScene().widthProperty().multiply(0.7));\n        maxHeightProperty().bind(Controllers.getScene().heightProperty().multiply(0.7));\n\n        setHeading(new Label(i18n(\"update.changelog\")));\n        setBody(new JFXSpinner());\n\n        String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + \".html\";\n\n        Task.supplyAsync(Schedulers.io(), () -> {\n            VersionNumber targetVersion = VersionNumber.asVersion(remoteVersion.getVersion());\n            VersionNumber currentVersion = VersionNumber.asVersion(Metadata.VERSION);\n            if (targetVersion.compareTo(currentVersion) <= 0)\n                // Downgrade update, no need to display changelog\n                return null;\n\n            Document document = Jsoup.parse(new URL(url), 30 * 1000);\n            Node node = document.selectFirst(\"h1[data-version=\\\"%s\\\"]\".formatted(targetVersion));\n\n            if (node == null || !\"h1\".equals(node.nodeName())) {\n                LOG.warning(\"Changelog not found\");\n                return null;\n            }\n\n            HTMLRenderer renderer = new HTMLRenderer(uri -> {\n                LOG.info(\"Open link: \" + uri);\n                FXUtils.openLink(uri.toString());\n            });\n\n            do {\n                if (\"h1\".equals(node.nodeName())) {\n                    String changelogVersion = node.attr(\"data-version\");\n                    if (StringUtils.isBlank(changelogVersion) || currentVersion.compareTo(changelogVersion) >= 0) {\n                        break;\n                    }\n                }\n                renderer.appendNode(node);\n                node = node.nextSibling();\n            } while (node != null);\n\n            renderer.mergeLineBreaks();\n            return renderer.render();\n        }).whenComplete(Schedulers.javafx(), (result, exception) -> {\n            if (exception == null) {\n                if (result != null) {\n                    ScrollPane scrollPane = new ScrollPane(result);\n                    scrollPane.setFitToWidth(true);\n                    FXUtils.smoothScrolling(scrollPane);\n                    setBody(scrollPane);\n                } else {\n                    setBody();\n                }\n            } else {\n                LOG.warning(\"Failed to load update log, trying to open it in browser\");\n                FXUtils.openLink(url);\n                setBody();\n            }\n        }).start();\n\n        JFXHyperlink openInBrowser = new JFXHyperlink(i18n(\"web.view_in_browser\"));\n        openInBrowser.setExternalLink(url);\n\n        JFXButton updateButton = new JFXButton(i18n(\"update.accept\"));\n        updateButton.getStyleClass().add(\"dialog-accept\");\n        updateButton.setOnAction(e -> updateRunnable.run());\n\n        JFXButton cancelButton = new JFXButton(i18n(\"button.cancel\"));\n        cancelButton.getStyleClass().add(\"dialog-cancel\");\n        cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));\n\n        setActions(openInBrowser, updateButton, cancelButton);\n        onEscPressed(this, cancelButton::fire);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/WeakListenerHolder.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.beans.value.WeakChangeListener;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.WeakListChangeListener;\nimport org.jackhuang.hmcl.event.Event;\nimport org.jackhuang.hmcl.event.EventManager;\nimport org.jackhuang.hmcl.event.EventPriority;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Consumer;\n\npublic final class WeakListenerHolder {\n    private final List<Object> refs = new ArrayList<>(0);\n\n    public WeakListenerHolder() {\n    }\n\n    public WeakInvalidationListener weak(InvalidationListener listener) {\n        refs.add(listener);\n        return new WeakInvalidationListener(listener);\n    }\n\n    public <T> WeakChangeListener<T> weak(ChangeListener<T> listener) {\n        refs.add(listener);\n        return new WeakChangeListener<>(listener);\n    }\n\n    public <T> WeakListChangeListener<T> weak(ListChangeListener<T> listener) {\n        refs.add(listener);\n        return new WeakListChangeListener<>(listener);\n    }\n\n    public <T extends Event> void registerWeak(EventManager<T> manager, Consumer<T> consumer) {\n        refs.add(manager.registerWeak(consumer));\n    }\n\n    public <T extends Event> void registerWeak(EventManager<T> manager, Consumer<T> consumer, EventPriority priority) {\n        refs.add(manager.registerWeak(consumer, priority));\n    }\n\n    public <T> void onWeakChange(ObservableValue<T> value, Consumer<T> consumer) {\n        refs.add(FXUtils.onWeakChange(value, consumer));\n    }\n\n    public <T> void onWeakChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {\n        refs.add(FXUtils.onWeakChangeAndOperate(value, consumer));\n    }\n\n    public void add(Object obj) {\n        refs.add(obj);\n    }\n\n    public boolean remove(Object obj) {\n        return refs.remove(obj);\n    }\n\n    public void clear() {\n        refs.clear();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundFill;\nimport javafx.scene.paint.Color;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class WebPage extends SpinnerPane implements DecoratorPage {\n\n    private final ObjectProperty<DecoratorPage.State> stateProperty;\n\n    public WebPage(String title, String content) {\n        this.stateProperty = new SimpleObjectProperty<>(DecoratorPage.State.fromTitle(title));\n        this.setBackground(new Background(new BackgroundFill(Color.WHITE, null, null)));\n\n        Task.supplyAsync(() -> {\n            Document document = Jsoup.parseBodyFragment(content);\n            HTMLRenderer renderer = new HTMLRenderer(uri -> {\n                Controllers.confirm(i18n(\"web.open_in_browser\", uri), i18n(\"message.confirm\"), () -> {\n                    FXUtils.openLink(uri.toString());\n                }, null);\n            });\n            renderer.appendNode(document);\n            renderer.mergeLineBreaks();\n            return renderer.render();\n        }).whenComplete(Schedulers.javafx(), ((result, exception) -> {\n            if (exception == null) {\n                ScrollPane scrollPane = new ScrollPane();\n                scrollPane.setFitToWidth(true);\n                scrollPane.setContent(result);\n                setContent(scrollPane);\n                setFailedReason(null);\n            } else {\n                LOG.warning(\"Failed to load content\", exception);\n                setFailedReason(i18n(\"web.failed\"));\n            }\n        })).start();\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<DecoratorPage.State> stateProperty() {\n        return stateProperty;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/WindowsNativeUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.stage.Stage;\nimport javafx.stage.Window;\n\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.util.OptionalLong;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author Glavo\npublic final class WindowsNativeUtils {\n\n    public static OptionalLong getWindowHandle(Stage stage) {\n        try {\n            Class<?> windowStageClass = Class.forName(\"com.sun.javafx.tk.quantum.WindowStage\");\n            Class<?> glassWindowClass = Class.forName(\"com.sun.glass.ui.Window\");\n            Class<?> tkStageClass = Class.forName(\"com.sun.javafx.tk.TKStage\");\n\n            Object tkStage = MethodHandles.privateLookupIn(Window.class, MethodHandles.lookup())\n                    .findVirtual(Window.class, \"getPeer\", MethodType.methodType(tkStageClass))\n                    .invoke(stage);\n\n            MethodHandles.Lookup windowStageLookup = MethodHandles.privateLookupIn(windowStageClass, MethodHandles.lookup());\n            MethodHandle getPlatformWindow = windowStageLookup.findVirtual(windowStageClass, \"getPlatformWindow\", MethodType.methodType(glassWindowClass));\n            Object platformWindow = getPlatformWindow.invoke(tkStage);\n\n            long handle = (long) MethodHandles.privateLookupIn(glassWindowClass, MethodHandles.lookup())\n                    .findVirtual(glassWindowClass, \"getNativeWindow\", MethodType.methodType(long.class))\n                    .invoke(platformWindow);\n\n            return OptionalLong.of(handle);\n        } catch (Throwable ex) {\n            LOG.warning(\"Failed to get window handle\", ex);\n            return OptionalLong.empty();\n        }\n    }\n\n    private WindowsNativeUtils() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Pos;\nimport javafx.scene.canvas.Canvas;\nimport javafx.scene.control.Tooltip;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;\nimport org.jackhuang.hmcl.game.TexturesLoader;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.AdvancedListItem;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\n\nimport static javafx.beans.binding.Bindings.createStringBinding;\nimport static org.jackhuang.hmcl.setting.Accounts.getAccountFactory;\nimport static org.jackhuang.hmcl.setting.Accounts.getLocalizedLoginTypeName;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class AccountAdvancedListItem extends AdvancedListItem {\n    private final Tooltip tooltip;\n    private final Canvas canvas;\n\n    private final ObjectProperty<Account> account = new SimpleObjectProperty<Account>() {\n\n        @Override\n        protected void invalidated() {\n            Account account = get();\n            if (account == null) {\n                titleProperty().unbind();\n                subtitleProperty().unbind();\n                tooltip.textProperty().unbind();\n                setTitle(i18n(\"account.missing\"));\n                setSubtitle(i18n(\"account.missing.add\"));\n                tooltip.setText(i18n(\"account.create\"));\n\n                TexturesLoader.unbindAvatar(canvas);\n                TexturesLoader.drawAvatar(canvas, TexturesLoader.getDefaultSkinImage());\n\n            } else {\n                titleProperty().bind(BindingMapping.of(account, Account::getCharacter));\n                subtitleProperty().bind(accountSubtitle(account));\n                tooltip.textProperty().bind(accountTooltip(account));\n                TexturesLoader.bindAvatar(canvas, account);\n            }\n        }\n    };\n\n    public AccountAdvancedListItem() {\n        this(null);\n    }\n\n    public AccountAdvancedListItem(Account account) {\n        tooltip = new Tooltip();\n        FXUtils.installFastTooltip(this, tooltip);\n\n        canvas = new Canvas(32, 32);\n        canvas.setMouseTransparent(true);\n        AdvancedListItem.setAlignment(canvas, Pos.CENTER);\n\n        setLeftGraphic(canvas);\n\n        if (account != null) {\n            this.accountProperty().set(account);\n        } else {\n            FXUtils.onScroll(this, Accounts.getAccounts(),\n                    accounts -> accounts.indexOf(accountProperty().get()),\n                    Accounts::setSelectedAccount);\n        }\n    }\n\n    public ObjectProperty<Account> accountProperty() {\n        return account;\n    }\n\n    private static ObservableValue<String> accountSubtitle(Account account) {\n        if (account instanceof AuthlibInjectorAccount) {\n            return BindingMapping.of(((AuthlibInjectorAccount) account).getServer(), AuthlibInjectorServer::getName);\n        } else {\n            return createStringBinding(() -> getLocalizedLoginTypeName(getAccountFactory(account)));\n        }\n    }\n\n    private static ObservableValue<String> accountTooltip(Account account) {\n        if (account instanceof AuthlibInjectorAccount) {\n            AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();\n            return Bindings.format(\"%s (%s) (%s)\",\n                    BindingMapping.of(account, Account::getCharacter),\n                    account.getUsername(),\n                    BindingMapping.of(server, AuthlibInjectorServer::getName));\n        } else if (account instanceof YggdrasilAccount) {\n            return Bindings.format(\"%s (%s)\",\n                    BindingMapping.of(account, Account::getCharacter),\n                    account.getUsername());\n        } else {\n            return BindingMapping.of(account, Account::getCharacter);\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.ObjectBinding;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.beans.value.ObservableBooleanValue;\nimport javafx.scene.control.RadioButton;\nimport javafx.scene.control.Skin;\nimport javafx.scene.image.Image;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.CredentialExpiredException;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;\nimport org.jackhuang.hmcl.auth.offline.OfflineAccount;\nimport org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;\nimport org.jackhuang.hmcl.auth.yggdrasil.TextureType;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.DialogController;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.skin.InvalidSkinException;\nimport org.jackhuang.hmcl.util.skin.NormalizedSkin;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.CancellationException;\n\nimport static java.util.Collections.emptySet;\nimport static javafx.beans.binding.Bindings.createBooleanBinding;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class AccountListItem extends RadioButton {\n\n    private final Account account;\n    private final StringProperty title = new SimpleStringProperty();\n    private final StringProperty subtitle = new SimpleStringProperty();\n\n    public AccountListItem(Account account) {\n        this.account = account;\n        getStyleClass().clear();\n        setUserData(account);\n\n        String loginTypeName = Accounts.getLocalizedLoginTypeName(Accounts.getAccountFactory(account));\n        String portableSuffix = account.isPortable() ? \", \" + i18n(\"account.portable\") : \"\";\n        if (account instanceof AuthlibInjectorAccount) {\n            AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();\n            subtitle.bind(Bindings.concat(\n                    loginTypeName, \", \", i18n(\"account.injector.server\"), \": \",\n                    Bindings.createStringBinding(server::getName, server), portableSuffix));\n        } else {\n            subtitle.set(loginTypeName + portableSuffix);\n        }\n\n        StringBinding characterName = Bindings.createStringBinding(account::getCharacter, account);\n        if (account instanceof OfflineAccount) {\n            title.bind(characterName);\n        } else {\n            title.bind(\n                    account.getUsername().isEmpty() ? characterName :\n                            Bindings.concat(account.getUsername(), \" - \", characterName));\n        }\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new AccountListItemSkin(this);\n    }\n\n    public Task<?> refreshAsync() {\n        return Task.runAsync(() -> {\n            account.clearCache();\n            try {\n                account.logIn();\n            } catch (CredentialExpiredException e) {\n                try {\n                    DialogController.logIn(account);\n                } catch (CancellationException e1) {\n                    // ignore cancellation\n                } catch (Exception e1) {\n                    LOG.warning(\"Failed to refresh \" + account + \" with password\", e1);\n                    throw e1;\n                }\n            } catch (AuthenticationException e) {\n                LOG.warning(\"Failed to refresh \" + account + \" with token\", e);\n                throw e;\n            }\n        });\n    }\n\n    public ObservableBooleanValue canUploadSkin() {\n        if (account instanceof AuthlibInjectorAccount aiAccount) {\n            ObjectBinding<Optional<CompleteGameProfile>> profile = aiAccount.getYggdrasilService().getProfileRepository().binding(aiAccount.getUUID());\n            return createBooleanBinding(() -> {\n                Set<TextureType> uploadableTextures = profile.get()\n                        .map(AuthlibInjectorAccount::getUploadableTextures)\n                        .orElse(emptySet());\n                return uploadableTextures.contains(TextureType.SKIN);\n            }, profile);\n        } else if (account instanceof OfflineAccount || account.canUploadSkin()) {\n            return createBooleanBinding(() -> true);\n        } else {\n            return createBooleanBinding(() -> false);\n        }\n    }\n\n    /**\n     * @return the skin upload task, null if no file is selected\n     */\n    @Nullable\n    public Task<?> uploadSkin() {\n        if (account instanceof OfflineAccount) {\n            Controllers.dialog(new OfflineAccountSkinPane((OfflineAccount) account));\n            return null;\n        }\n        if (!account.canUploadSkin()) {\n            return null;\n        }\n\n        FileChooser chooser = new FileChooser();\n        chooser.setTitle(i18n(\"account.skin.upload\"));\n        chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"account.skin.file\"), \"*.png\"));\n        Path selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));\n        if (selectedFile == null) {\n            return null;\n        }\n\n        return refreshAsync()\n                .thenRunAsync(() -> {\n                    Image skinImg;\n                    try (var input = Files.newInputStream(selectedFile)) {\n                        skinImg = new Image(input);\n                    } catch (IOException e) {\n                        throw new InvalidSkinException(\"Failed to read skin image\", e);\n                    }\n                    if (skinImg.isError()) {\n                        throw new InvalidSkinException(\"Failed to read skin image\", skinImg.getException());\n                    }\n                    NormalizedSkin skin = new NormalizedSkin(skinImg);\n                    String model = skin.isSlim() ? \"slim\" : \"\";\n                    LOG.info(\"Uploading skin [\" + selectedFile + \"], model [\" + model + \"]\");\n                    account.uploadSkin(skin.isSlim(), selectedFile);\n                })\n                .thenComposeAsync(refreshAsync())\n                .whenComplete(Schedulers.javafx(), e -> {\n                    if (e != null) {\n                        Controllers.dialog(Accounts.localizeErrorMessage(e), i18n(\"account.skin.upload.failed\"), MessageType.ERROR);\n                    }\n                });\n    }\n\n    public void remove() {\n        Accounts.getAccounts().remove(account);\n    }\n\n    public Account getAccount() {\n        return account;\n    }\n\n    public String getTitle() {\n        return title.get();\n    }\n\n    public void setTitle(String title) {\n        this.title.set(title);\n    }\n\n    public StringProperty titleProperty() {\n        return title;\n    }\n\n    public String getSubtitle() {\n        return subtitle.get();\n    }\n\n    public void setSubtitle(String subtitle) {\n        this.subtitle.set(subtitle);\n    }\n\n    public StringProperty subtitleProperty() {\n        return subtitle;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXRadioButton;\nimport com.jfoenix.effects.JFXDepthManager;\nimport javafx.beans.binding.Bindings;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.canvas.Canvas;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;\nimport org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;\nimport org.jackhuang.hmcl.game.TexturesLoader;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class AccountListItemSkin extends SkinBase<AccountListItem> {\n\n    public AccountListItemSkin(AccountListItem skinnable) {\n        super(skinnable);\n\n        BorderPane root = new BorderPane();\n        root.setCursor(Cursor.HAND);\n        FXUtils.onClicked(root, skinnable::fire);\n\n        JFXRadioButton chkSelected = new JFXRadioButton();\n        chkSelected.setMouseTransparent(true);\n        BorderPane.setAlignment(chkSelected, Pos.CENTER);\n        chkSelected.selectedProperty().bind(skinnable.selectedProperty());\n        root.setLeft(chkSelected);\n\n        HBox center = new HBox();\n        center.setSpacing(8);\n        center.setAlignment(Pos.CENTER_LEFT);\n\n        Canvas canvas = new Canvas(32, 32);\n        TexturesLoader.bindAvatar(canvas, skinnable.getAccount());\n\n        Label title = new Label();\n        title.getStyleClass().add(\"title\");\n        title.textProperty().bind(skinnable.titleProperty());\n        Label subtitle = new Label();\n        subtitle.getStyleClass().add(\"subtitle\");\n        subtitle.textProperty().bind(skinnable.subtitleProperty());\n        if (skinnable.getAccount() instanceof AuthlibInjectorAccount) {\n            Tooltip tooltip = new Tooltip();\n            AuthlibInjectorServer server = ((AuthlibInjectorAccount) skinnable.getAccount()).getServer();\n            tooltip.textProperty().bind(BindingMapping.of(server, AuthlibInjectorServer::toString));\n            FXUtils.installSlowTooltip(subtitle, tooltip);\n        }\n        VBox item = new VBox(title, subtitle);\n        item.getStyleClass().add(\"two-line-list-item\");\n        BorderPane.setAlignment(item, Pos.CENTER);\n\n        center.getChildren().setAll(canvas, item);\n        root.setCenter(center);\n\n        HBox right = new HBox();\n        right.setAlignment(Pos.CENTER_RIGHT);\n\n        JFXButton btnMove = new JFXButton();\n        SpinnerPane spinnerMove = new SpinnerPane();\n        spinnerMove.getStyleClass().add(\"small-spinner-pane\");\n        btnMove.setOnAction(e -> {\n            Account account = skinnable.getAccount();\n            Accounts.getAccounts().remove(account);\n            if (account.isPortable()) {\n                account.setPortable(false);\n                if (!Accounts.getAccounts().contains(account))\n                    Accounts.getAccounts().add(account);\n            } else {\n                account.setPortable(true);\n                if (!Accounts.getAccounts().contains(account)) {\n                    int idx = 0;\n                    for (int i = Accounts.getAccounts().size() - 1; i >= 0; i--) {\n                        if (Accounts.getAccounts().get(i).isPortable()) {\n                            idx = i + 1;\n                            break;\n                        }\n                    }\n                    Accounts.getAccounts().add(idx, account);\n                }\n            }\n        });\n        btnMove.getStyleClass().add(\"toggle-icon4\");\n        if (skinnable.getAccount().isPortable()) {\n            btnMove.setGraphic(SVG.PUBLIC.createIcon());\n            FXUtils.installFastTooltip(btnMove, i18n(\"account.move_to_global\"));\n        } else {\n            btnMove.setGraphic(SVG.OUTPUT.createIcon());\n            FXUtils.installFastTooltip(btnMove, i18n(\"account.move_to_portable\"));\n        }\n        spinnerMove.setContent(btnMove);\n        right.getChildren().add(spinnerMove);\n\n        JFXButton btnRefresh = FXUtils.newToggleButton4(SVG.REFRESH);\n        SpinnerPane spinnerRefresh = new SpinnerPane();\n        spinnerRefresh.getStyleClass().setAll(\"small-spinner-pane\");\n        if (skinnable.getAccount() instanceof MicrosoftAccount && Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {\n            btnRefresh.setDisable(true);\n            FXUtils.installFastTooltip(spinnerRefresh, i18n(\"account.methods.microsoft.snapshot.tooltip\"));\n        }\n        btnRefresh.setOnAction(e -> {\n            spinnerRefresh.showSpinner();\n            skinnable.refreshAsync()\n                    .whenComplete(Schedulers.javafx(), ex -> {\n                        spinnerRefresh.hideSpinner();\n\n                        if (ex != null) {\n                            Controllers.showToast(Accounts.localizeErrorMessage(ex));\n                        }\n                    })\n                    .start();\n        });\n        FXUtils.installFastTooltip(btnRefresh, i18n(\"button.refresh\"));\n        spinnerRefresh.setContent(btnRefresh);\n        right.getChildren().add(spinnerRefresh);\n\n        JFXButton btnUpload = FXUtils.newToggleButton4(SVG.CHECKROOM);\n        SpinnerPane spinnerUpload = new SpinnerPane();\n        btnUpload.setOnAction(e -> {\n            Task<?> uploadTask = skinnable.uploadSkin();\n            if (uploadTask != null) {\n                spinnerUpload.showSpinner();\n                uploadTask\n                        .whenComplete(Schedulers.javafx(), ex -> spinnerUpload.hideSpinner())\n                        .start();\n            }\n        });\n        FXUtils.installFastTooltip(btnUpload, i18n(\"account.skin.upload\"));\n        btnUpload.disableProperty().bind(Bindings.not(skinnable.canUploadSkin()));\n        spinnerUpload.setContent(btnUpload);\n        spinnerUpload.getStyleClass().add(\"small-spinner-pane\");\n        right.getChildren().add(spinnerUpload);\n\n        JFXButton btnCopyUUID = FXUtils.newToggleButton4(SVG.CONTENT_COPY);\n        SpinnerPane spinnerCopyUUID = new SpinnerPane();\n        spinnerCopyUUID.getStyleClass().add(\"small-spinner-pane\");\n        btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));\n        FXUtils.installFastTooltip(btnCopyUUID, i18n(\"account.copy_uuid\"));\n        spinnerCopyUUID.setContent(btnCopyUUID);\n        right.getChildren().add(spinnerCopyUUID);\n\n        JFXButton btnRemove = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);\n        btnRemove.setOnAction(e -> Controllers.confirm(i18n(\"button.remove.confirm\"), i18n(\"button.remove\"), skinnable::remove, null));\n        BorderPane.setAlignment(btnRemove, Pos.CENTER);\n        FXUtils.installFastTooltip(btnRemove, i18n(\"button.delete\"));\n        right.getChildren().add(btnRemove);\n        root.setRight(right);\n\n        root.getStyleClass().add(\"card\");\n        root.setStyle(\"-fx-padding: 8 8 8 0;\");\n        JFXDepthManager.setDepth(root, 1);\n\n        getChildren().setAll(root);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ListProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleListProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.AdvancedListItem;\nimport org.jackhuang.hmcl.ui.construct.ClassTitle;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.i18n.LocaleUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\nimport org.jackhuang.hmcl.util.javafx.MappedObservableList;\n\nimport java.util.Locale;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {\n    static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(true);\n\n    static {\n        String property = System.getProperty(\"hmcl.offline.auth.restricted\", \"auto\");\n\n        if (\"false\".equals(property)\n                || \"auto\".equals(property) && LocaleUtils.IS_CHINA_MAINLAND\n                || globalConfig().isEnableOfflineAccount())\n            RESTRICTED.set(false);\n        else\n            globalConfig().enableOfflineAccountProperty().addListener(new ChangeListener<Boolean>() {\n                @Override\n                public void changed(ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) {\n                    if (newValue) {\n                        globalConfig().enableOfflineAccountProperty().removeListener(this);\n                        RESTRICTED.set(false);\n                    }\n                }\n            });\n    }\n\n    private final ObservableList<AccountListItem> items;\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n(\"account.manage\")));\n    private final ListProperty<Account> accounts = new SimpleListProperty<>(this, \"accounts\", FXCollections.observableArrayList());\n    private final ListProperty<AuthlibInjectorServer> authServers = new SimpleListProperty<>(this, \"authServers\", FXCollections.observableArrayList());\n    private final ObjectProperty<Account> selectedAccount;\n\n    public AccountListPage() {\n        items = MappedObservableList.create(accounts, AccountListItem::new);\n        selectedAccount = createSelectedItemPropertyFor(items, Account.class);\n    }\n\n    public ObjectProperty<Account> selectedAccountProperty() {\n        return selectedAccount;\n    }\n\n    public ListProperty<Account> accountsProperty() {\n        return accounts;\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n\n    public ListProperty<AuthlibInjectorServer> authServersProperty() {\n        return authServers;\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new AccountListPageSkin(this);\n    }\n\n    private static class AccountListPageSkin extends DecoratorAnimatedPageSkin<AccountListPage> {\n\n        private final ObservableList<AdvancedListItem> authServerItems;\n        private ChangeListener<Boolean> holder;\n\n        public AccountListPageSkin(AccountListPage skinnable) {\n            super(skinnable);\n\n            {\n                VBox boxMethods = new VBox();\n                {\n                    boxMethods.getStyleClass().add(\"advanced-list-box-content\");\n                    FXUtils.setLimitWidth(boxMethods, 200);\n\n                    AdvancedListItem microsoftItem = new AdvancedListItem();\n                    microsoftItem.getStyleClass().add(\"navigation-drawer-item\");\n                    microsoftItem.setTitle(i18n(\"account.methods.microsoft\"));\n                    microsoftItem.setLeftIcon(SVG.MICROSOFT);\n                    microsoftItem.setOnAction(e -> Controllers.dialog(new MicrosoftAccountLoginPane()));\n\n                    AdvancedListItem offlineItem = new AdvancedListItem();\n                    offlineItem.getStyleClass().add(\"navigation-drawer-item\");\n                    offlineItem.setTitle(i18n(\"account.methods.offline\"));\n                    offlineItem.setLeftIcon(SVG.PERSON);\n                    offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));\n\n                    VBox boxAuthServers = new VBox();\n                    authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {\n                        AdvancedListItem item = new AdvancedListItem();\n                        item.getStyleClass().add(\"navigation-drawer-item\");\n                        item.setLeftIcon(SVG.DRESSER);\n                        item.setOnAction(e -> Controllers.dialog(new CreateAccountPane(server)));\n                        item.setRightAction(SVG.CLOSE, () -> Controllers.confirm(i18n(\"button.remove.confirm\"), i18n(\"button.remove\"), () -> {\n                            skinnable.authServersProperty().remove(server);\n                        }, null));\n\n                        ObservableValue<String> title = BindingMapping.of(server, AuthlibInjectorServer::getName);\n                        item.titleProperty().bind(title);\n                        String host = \"\";\n                        try {\n                            host = NetworkUtils.toURI(server.getUrl()).getHost();\n                        } catch (IllegalArgumentException e) {\n                            LOG.warning(\"Unparsable authlib-injector server url \" + server.getUrl(), e);\n                        }\n                        item.subtitleProperty().set(host);\n                        Tooltip tooltip = new Tooltip();\n                        tooltip.textProperty().bind(Bindings.format(\"%s (%s)\", title, server.getUrl()));\n                        FXUtils.installFastTooltip(item, tooltip);\n\n                        return item;\n                    });\n                    Bindings.bindContent(boxAuthServers.getChildren(), authServerItems);\n\n                    ClassTitle title = new ClassTitle(i18n(\"account.create\").toUpperCase(Locale.ROOT));\n                    if (RESTRICTED.get()) {\n                        VBox wrapper = new VBox(offlineItem, boxAuthServers);\n                        wrapper.setPadding(Insets.EMPTY);\n                        FXUtils.installFastTooltip(wrapper, i18n(\"account.login.restricted\"));\n\n                        offlineItem.setDisable(true);\n                        boxAuthServers.setDisable(true);\n\n                        boxMethods.getChildren().setAll(title, microsoftItem, wrapper);\n\n                        holder = FXUtils.onWeakChange(RESTRICTED, value -> {\n                            if (!value) {\n                                holder = null;\n                                offlineItem.setDisable(false);\n                                boxAuthServers.setDisable(false);\n                                boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers);\n                            }\n                        });\n                    } else {\n                        boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers);\n                    }\n                }\n\n                AdvancedListItem addAuthServerItem = new AdvancedListItem();\n                {\n                    addAuthServerItem.getStyleClass().add(\"navigation-drawer-item\");\n                    addAuthServerItem.setTitle(i18n(\"account.injector.add\"));\n                    addAuthServerItem.setSubtitle(i18n(\"account.methods.authlib_injector\"));\n                    addAuthServerItem.setLeftIcon(SVG.ADD_CIRCLE);\n                    addAuthServerItem.setOnAction(e -> Controllers.dialog(new AddAuthlibInjectorServerPane()));\n                    VBox.setMargin(addAuthServerItem, new Insets(0, 0, 12, 0));\n                }\n\n                ScrollPane scrollPane = new ScrollPane(boxMethods);\n                VBox.setVgrow(scrollPane, Priority.ALWAYS);\n                setLeft(scrollPane, addAuthServerItem);\n            }\n\n            ScrollPane scrollPane = new ScrollPane();\n            VBox list = new VBox();\n            {\n                scrollPane.setFitToWidth(true);\n\n                list.maxWidthProperty().bind(scrollPane.widthProperty());\n                list.setSpacing(10);\n                list.getStyleClass().add(\"card-list\");\n\n                Bindings.bindContent(list.getChildren(), skinnable.items);\n\n                scrollPane.setContent(list);\n                FXUtils.smoothScrolling(scrollPane);\n\n                setCenter(scrollPane);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPopupMenu.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport com.jfoenix.controls.JFXPopup;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.StackPane;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.AdvancedListBox;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class AccountListPopupMenu extends StackPane {\n    public static void show(Node owner, JFXPopup.PopupVPosition vAlign, JFXPopup.PopupHPosition hAlign,\n                            double initOffsetX, double initOffsetY) {\n        var menu = new AccountListPopupMenu();\n        JFXPopup popup = new JFXPopup(menu);\n        popup.show(owner, vAlign, hAlign, initOffsetX, initOffsetY);\n    }\n\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final BooleanBinding isEmpty = Bindings.isEmpty(Accounts.getAccounts());\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final InvalidationListener listener;\n\n    public AccountListPopupMenu() {\n        AdvancedListBox box = new AdvancedListBox();\n        box.getStyleClass().add(\"no-padding\");\n        box.setPrefWidth(220);\n        box.setPrefHeight(-1);\n        box.setMaxHeight(260);\n\n        listener = o -> {\n            box.clear();\n\n            for (Account account : Accounts.getAccounts()) {\n                AccountAdvancedListItem item = new AccountAdvancedListItem(account);\n                item.setOnAction(e -> {\n                    Accounts.setSelectedAccount(account);\n                    if (getScene().getWindow() instanceof JFXPopup popup)\n                        popup.hide();\n                });\n                box.add(item);\n            }\n        };\n        listener.invalidated(null);\n        Accounts.getAccounts().addListener(new WeakInvalidationListener(listener));\n\n        Label placeholder = new Label(i18n(\"account.empty\"));\n        placeholder.setStyle(\"-fx-padding: 10px; -fx-text-fill: -monet-on-surface-variant; -fx-font-style: italic;\");\n\n        FXUtils.onChangeAndOperate(isEmpty, empty -> {\n            getChildren().setAll(empty ? placeholder : box);\n        });\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.Lang;\n\nimport javax.net.ssl.SSLException;\nimport java.io.IOException;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class AddAuthlibInjectorServerPane extends TransitionPane implements DialogAware {\n\n    private final Label lblServerUrl;\n    private final Label lblServerName;\n    private final Label lblCreationWarning;\n    private final Label lblServerWarning;\n    private final JFXTextField txtServerUrl;\n    private final JFXDialogLayout addServerPane;\n    private final JFXDialogLayout confirmServerPane;\n    private final SpinnerPane nextPane;\n    private final JFXButton btnAddNext;\n\n    private AuthlibInjectorServer serverBeingAdded;\n\n    public AddAuthlibInjectorServerPane(String url) {\n        this();\n        txtServerUrl.setText(url);\n        onAddNext();\n    }\n\n    public AddAuthlibInjectorServerPane() {\n        addServerPane = new JFXDialogLayout();\n        addServerPane.setHeading(new Label(i18n(\"account.injector.add\")));\n        {\n            txtServerUrl = new JFXTextField();\n            txtServerUrl.setPromptText(i18n(\"account.injector.server_url\"));\n            txtServerUrl.setOnAction(e -> onAddNext());\n\n            lblCreationWarning = new Label();\n            lblCreationWarning.setWrapText(true);\n            HBox actions = new HBox();\n            {\n                JFXButton cancel = new JFXButton(i18n(\"button.cancel\"));\n                cancel.getStyleClass().add(\"dialog-accept\");\n                cancel.setOnAction(e -> onAddCancel());\n\n                nextPane = new SpinnerPane();\n                nextPane.getStyleClass().add(\"small-spinner-pane\");\n                btnAddNext = new JFXButton(i18n(\"wizard.next\"));\n                btnAddNext.getStyleClass().add(\"dialog-accept\");\n                btnAddNext.setOnAction(e -> onAddNext());\n                nextPane.setContent(btnAddNext);\n\n                actions.getChildren().setAll(cancel, nextPane);\n            }\n\n            addServerPane.setBody(txtServerUrl);\n            addServerPane.setActions(lblCreationWarning, actions);\n\n            txtServerUrl.getValidators().addAll(new RequiredValidator(), new URLValidator());\n            FXUtils.setValidateWhileTextChanged(txtServerUrl, true);\n            btnAddNext.disableProperty().bind(txtServerUrl.activeValidatorProperty().isNotNull());\n        }\n\n        confirmServerPane = new JFXDialogLayout();\n        confirmServerPane.setHeading(new Label(i18n(\"account.injector.add\")));\n        {\n            GridPane body = new GridPane();\n            body.setStyle(\"-fx-padding: 15 0 0 0;\");\n            body.setVgap(15);\n            body.setHgap(15);\n            {\n                body.getColumnConstraints().setAll(\n                        Lang.apply(new ColumnConstraints(), c -> c.setMaxWidth(100)),\n                        new ColumnConstraints()\n                );\n\n                lblServerUrl = new Label();\n                GridPane.setColumnIndex(lblServerUrl, 1);\n                GridPane.setRowIndex(lblServerUrl, 0);\n\n                lblServerName = new Label();\n                GridPane.setColumnIndex(lblServerName, 1);\n                GridPane.setRowIndex(lblServerName, 1);\n\n                lblServerWarning = new Label(i18n(\"account.injector.http\"));\n                lblServerWarning.setStyle(\"-fx-text-fill: red;\");\n                GridPane.setColumnIndex(lblServerWarning, 0);\n                GridPane.setRowIndex(lblServerWarning, 2);\n                lblServerWarning.managedProperty().bind(lblServerWarning.visibleProperty());\n                GridPane.setColumnSpan(lblServerWarning, 2);\n\n                body.getChildren().setAll(\n                        Lang.apply(new Label(i18n(\"account.injector.server_url\")), l -> {\n                            GridPane.setColumnIndex(l, 0);\n                            GridPane.setRowIndex(l, 0);\n                        }),\n                        Lang.apply(new Label(i18n(\"account.injector.server_name\")), l -> {\n                            GridPane.setColumnIndex(l, 0);\n                            GridPane.setRowIndex(l, 1);\n                        }),\n                        lblServerUrl, lblServerName, lblServerWarning\n                );\n            }\n\n            JFXButton prevButton = new JFXButton(i18n(\"wizard.prev\"));\n            prevButton.getStyleClass().add(\"dialog-cancel\");\n            prevButton.setOnAction(e -> onAddPrev());\n\n            JFXButton cancelButton = new JFXButton(i18n(\"button.cancel\"));\n            cancelButton.getStyleClass().add(\"dialog-cancel\");\n            cancelButton.setOnAction(e -> onAddCancel());\n\n            JFXButton finishButton = new JFXButton(i18n(\"wizard.finish\"));\n            finishButton.getStyleClass().add(\"dialog-accept\");\n            finishButton.setOnAction(e -> onAddFinish());\n\n            confirmServerPane.setBody(body);\n            confirmServerPane.setActions(prevButton, cancelButton, finishButton);\n        }\n\n        this.setContent(addServerPane, ContainerAnimations.NONE);\n\n        lblCreationWarning.maxWidthProperty().bind(((FlowPane) lblCreationWarning.getParent()).widthProperty());\n        nextPane.hideSpinner();\n\n        onEscPressed(this, this::onAddCancel);\n    }\n\n    @Override\n    public void onDialogShown() {\n        txtServerUrl.requestFocus();\n    }\n\n    private String resolveFetchExceptionMessage(Throwable exception) {\n        if (exception instanceof SSLException) {\n            if (exception.getMessage() != null && exception.getMessage().contains(\"Remote host terminated\")) {\n                return i18n(\"account.failed.connect_injector_server\");\n            }\n            if (exception.getMessage() != null && (exception.getMessage().contains(\"No name matching\") || exception.getMessage().contains(\"No subject alternative DNS name matching\"))) {\n                return i18n(\"account.failed.dns\");\n            }\n            return i18n(\"account.failed.ssl\");\n        } else if (exception instanceof IOException) {\n            return i18n(\"account.failed.connect_injector_server\");\n        } else {\n            return exception.getClass().getName() + \": \" + exception.getLocalizedMessage();\n        }\n    }\n\n    private void onAddCancel() {\n        fireEvent(new DialogCloseEvent());\n    }\n\n    private void onAddNext() {\n        if (btnAddNext.isDisabled())\n            return;\n\n        lblCreationWarning.setText(\"\");\n\n        String url = txtServerUrl.getText();\n\n        nextPane.showSpinner();\n        addServerPane.setDisable(true);\n\n        Task.runAsync(() -> {\n            serverBeingAdded = AuthlibInjectorServer.locateServer(url);\n        }).whenComplete(Schedulers.javafx(), exception -> {\n            addServerPane.setDisable(false);\n            nextPane.hideSpinner();\n\n            if (exception == null) {\n                lblServerName.setText(serverBeingAdded.getName());\n                lblServerUrl.setText(serverBeingAdded.getUrl());\n\n                //noinspection HttpUrlsUsage\n                lblServerWarning.setVisible(serverBeingAdded.getUrl().startsWith(\"http://\"));\n\n                this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT);\n            } else {\n                LOG.warning(\"Failed to resolve auth server: \" + url, exception);\n                lblCreationWarning.setText(resolveFetchExceptionMessage(exception));\n            }\n        }).start();\n\n    }\n\n    private void onAddPrev() {\n        this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT);\n    }\n\n    private void onAddFinish() {\n        if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) {\n            config().getAuthlibInjectorServers().add(serverBeingAdded);\n        }\n        fireEvent(new DialogCloseEvent());\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/ClassicAccountLoginDialog.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport com.jfoenix.controls.JFXPasswordField;\nimport com.jfoenix.controls.JFXProgressBar;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.auth.AuthInfo;\nimport org.jackhuang.hmcl.auth.ClassicAccount;\nimport org.jackhuang.hmcl.auth.NoSelectedCharacterException;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.construct.DialogCloseEvent;\nimport org.jackhuang.hmcl.ui.construct.RequiredValidator;\n\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class ClassicAccountLoginDialog extends StackPane {\n    private final ClassicAccount oldAccount;\n    private final Consumer<AuthInfo> success;\n    private final Runnable failed;\n\n    private final JFXPasswordField txtPassword;\n    private final Label lblCreationWarning = new Label();\n    private final JFXProgressBar progressBar;\n\n    public ClassicAccountLoginDialog(ClassicAccount oldAccount, Consumer<AuthInfo> success, Runnable failed) {\n        this.oldAccount = oldAccount;\n        this.success = success;\n        this.failed = failed;\n\n        progressBar = new JFXProgressBar();\n        StackPane.setAlignment(progressBar, Pos.TOP_CENTER);\n        progressBar.setVisible(false);\n\n        JFXDialogLayout dialogLayout = new JFXDialogLayout();\n\n        {\n            dialogLayout.setHeading(new Label(i18n(\"login.enter_password\")));\n        }\n\n        {\n            VBox body = new VBox(15);\n            body.setPadding(new Insets(15, 0, 0, 0));\n\n            Label usernameLabel = new Label(oldAccount.getUsername());\n\n            txtPassword = new JFXPasswordField();\n            txtPassword.setOnAction(e -> onAccept());\n            txtPassword.getValidators().add(new RequiredValidator());\n            txtPassword.setLabelFloat(true);\n            txtPassword.setPromptText(i18n(\"account.password\"));\n\n            body.getChildren().setAll(usernameLabel, txtPassword);\n            dialogLayout.setBody(body);\n        }\n\n        {\n            JFXButton acceptButton = new JFXButton(i18n(\"button.ok\"));\n            acceptButton.setOnAction(e -> onAccept());\n            acceptButton.getStyleClass().add(\"dialog-accept\");\n\n            JFXButton cancelButton = new JFXButton(i18n(\"button.cancel\"));\n            cancelButton.setOnAction(e -> onCancel());\n            cancelButton.getStyleClass().add(\"dialog-cancel\");\n\n            dialogLayout.setActions(lblCreationWarning, acceptButton, cancelButton);\n        }\n\n        getChildren().setAll(dialogLayout);\n\n        onEscPressed(this, this::onCancel);\n    }\n\n    private void onAccept() {\n        String password = txtPassword.getText();\n        progressBar.setVisible(true);\n        lblCreationWarning.setText(\"\");\n        Task.supplyAsync(() -> oldAccount.logInWithPassword(password))\n                .whenComplete(Schedulers.javafx(), authInfo -> {\n                    success.accept(authInfo);\n                    fireEvent(new DialogCloseEvent());\n                    progressBar.setVisible(false);\n                }, e -> {\n                    LOG.info(\"Failed to login with password: \" + oldAccount, e);\n                    if (e instanceof NoSelectedCharacterException) {\n                        fireEvent(new DialogCloseEvent());\n                    } else {\n                        lblCreationWarning.setText(Accounts.localizeErrorMessage(e));\n                    }\n                    progressBar.setVisible(false);\n                }).start();\n    }\n\n    private void onCancel() {\n        failed.run();\n        fireEvent(new DialogCloseEvent());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport com.jfoenix.controls.*;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.application.Platform;\nimport javafx.beans.NamedArg;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.geometry.HPos;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.canvas.Canvas;\nimport javafx.scene.control.Hyperlink;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextInputControl;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.auth.AccountFactory;\nimport org.jackhuang.hmcl.auth.CharacterSelector;\nimport org.jackhuang.hmcl.auth.NoSelectedCharacterException;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;\nimport org.jackhuang.hmcl.auth.authlibinjector.BoundAuthlibInjectorAccountFactory;\nimport org.jackhuang.hmcl.auth.microsoft.MicrosoftAccountFactory;\nimport org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;\nimport org.jackhuang.hmcl.auth.yggdrasil.GameProfile;\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;\nimport org.jackhuang.hmcl.game.TexturesLoader;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.upgrade.IntegrityChecker;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.regex.Pattern;\n\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.unmodifiableList;\nimport static javafx.beans.binding.Bindings.bindContent;\nimport static javafx.beans.binding.Bindings.createBooleanBinding;\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.ui.FXUtils.*;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.javafx.ExtendedProperties.classPropertyFor;\n\npublic class CreateAccountPane extends JFXDialogLayout implements DialogAware {\n    private static final Pattern USERNAME_CHECKER_PATTERN = Pattern.compile(\"^[A-Za-z0-9_]+$\");\n\n    private boolean showMethodSwitcher;\n    private AccountFactory<?> factory;\n\n    private final Label lblErrorMessage;\n    private final JFXButton btnAccept;\n    private final SpinnerPane spinner;\n    private final Node body;\n    private final HBox actions;\n    private Node detailsPane; // AccountDetailsInputPane for Offline / Mojang / authlib-injector, Label for Microsoft\n    private final Pane detailsContainer;\n\n    private final BooleanProperty logging = new SimpleBooleanProperty();\n\n    private TaskExecutor loginTask;\n\n    public CreateAccountPane() {\n        this((AccountFactory<?>) null);\n    }\n\n    public CreateAccountPane(AccountFactory<?> factory) {\n        if (factory == null) {\n            if (AccountListPage.RESTRICTED.get()) {\n                showMethodSwitcher = false;\n                factory = Accounts.FACTORY_MICROSOFT;\n            } else {\n                showMethodSwitcher = true;\n                String preferred = config().getPreferredLoginType();\n                try {\n                    factory = Accounts.getAccountFactory(preferred);\n                } catch (IllegalArgumentException e) {\n                    factory = Accounts.FACTORY_OFFLINE;\n                }\n            }\n        } else {\n            showMethodSwitcher = false;\n        }\n        this.factory = factory;\n\n        {\n            String title;\n            if (showMethodSwitcher) {\n                title = \"account.create\";\n            } else {\n                title = \"account.create.\" + Accounts.getLoginType(factory);\n            }\n            setHeading(new Label(i18n(title)));\n        }\n\n        {\n            lblErrorMessage = new Label();\n            lblErrorMessage.setWrapText(true);\n            lblErrorMessage.setMaxWidth(400);\n\n            btnAccept = new JFXButton(i18n(\"account.login\"));\n            btnAccept.getStyleClass().add(\"dialog-accept\");\n            btnAccept.setOnAction(e -> onAccept());\n\n            spinner = new SpinnerPane();\n            spinner.getStyleClass().add(\"small-spinner-pane\");\n            spinner.setContent(btnAccept);\n\n            JFXButton btnCancel = new JFXButton(i18n(\"button.cancel\"));\n            btnCancel.getStyleClass().add(\"dialog-cancel\");\n            btnCancel.setOnAction(e -> onCancel());\n            onEscPressed(this, btnCancel::fire);\n\n            actions = new HBox(spinner, btnCancel);\n            actions.setAlignment(Pos.CENTER_RIGHT);\n\n            setActions(lblErrorMessage, actions);\n        }\n\n        if (showMethodSwitcher) {\n            TabControl.Tab<?>[] tabs = new TabControl.Tab[Accounts.FACTORIES.size()];\n            TabControl.Tab<?> selected = null;\n            for (int i = 0; i < tabs.length; i++) {\n                AccountFactory<?> f = Accounts.FACTORIES.get(i);\n                tabs[i] = new TabControl.Tab<>(Accounts.getLoginType(f), Accounts.getLocalizedLoginTypeName(f));\n                tabs[i].setUserData(f);\n                if (factory == f) {\n                    selected = tabs[i];\n                }\n            }\n\n            TabHeader tabHeader = new TabHeader(tabs);\n            tabHeader.getStyleClass().add(\"add-account-tab-header\");\n            tabHeader.setMinWidth(USE_PREF_SIZE);\n            tabHeader.setMaxWidth(USE_PREF_SIZE);\n            tabHeader.getSelectionModel().select(selected);\n            onChange(tabHeader.getSelectionModel().selectedItemProperty(),\n                    newItem -> {\n                        if (newItem == null)\n                            return;\n                        AccountFactory<?> newMethod = (AccountFactory<?>) newItem.getUserData();\n                        config().setPreferredLoginType(Accounts.getLoginType(newMethod));\n                        this.factory = newMethod;\n                        initDetailsPane();\n                    });\n\n            detailsContainer = new StackPane();\n            detailsContainer.setPadding(new Insets(15, 0, 0, 0));\n\n            VBox boxBody = new VBox(tabHeader, detailsContainer);\n            boxBody.setAlignment(Pos.CENTER);\n            body = boxBody;\n            setBody(body);\n\n        } else {\n            detailsContainer = new StackPane();\n            detailsContainer.setPadding(new Insets(10, 0, 0, 0));\n            body = detailsContainer;\n            setBody(body);\n        }\n        initDetailsPane();\n\n        setPrefWidth(560);\n    }\n\n    public CreateAccountPane(AuthlibInjectorServer authServer) {\n        this(Accounts.getAccountFactoryByAuthlibInjectorServer(authServer));\n    }\n\n    private void onAccept() {\n        spinner.showSpinner();\n        lblErrorMessage.setText(\"\");\n\n        if (!(factory instanceof MicrosoftAccountFactory)) {\n            body.setDisable(true);\n        }\n\n        String username;\n        String password;\n        Object additionalData;\n        if (detailsPane instanceof AccountDetailsInputPane) {\n            AccountDetailsInputPane details = (AccountDetailsInputPane) detailsPane;\n            username = details.getUsername();\n            password = details.getPassword();\n            additionalData = details.getAdditionalData();\n        } else {\n            username = null;\n            password = null;\n            additionalData = null;\n        }\n\n        Runnable doCreate = () -> {\n            logging.set(true);\n\n            loginTask = Task.supplyAsync(() -> factory.create(new DialogCharacterSelector(), username, password, null, additionalData))\n                    .whenComplete(Schedulers.javafx(), account -> {\n                        int oldIndex = Accounts.getAccounts().indexOf(account);\n                        if (oldIndex == -1) {\n                            Accounts.getAccounts().add(account);\n                        } else {\n                            // adding an already-added account\n                            // instead of discarding the new account, we first remove the existing one then add the new one\n                            Accounts.getAccounts().remove(oldIndex);\n                            Accounts.getAccounts().add(oldIndex, account);\n                        }\n\n                        // select the new account\n                        Accounts.setSelectedAccount(account);\n\n                        spinner.hideSpinner();\n                        fireEvent(new DialogCloseEvent());\n                    }, exception -> {\n                        if (exception instanceof NoSelectedCharacterException) {\n                            fireEvent(new DialogCloseEvent());\n                        } else {\n                            lblErrorMessage.setText(Accounts.localizeErrorMessage(exception));\n                        }\n                        body.setDisable(false);\n                        spinner.hideSpinner();\n                    }).executor(true);\n        };\n\n        if (factory instanceof OfflineAccountFactory && username != null && (!USERNAME_CHECKER_PATTERN.matcher(username).matches() || username.length() > 16)) {\n            Controllers.confirmWithCountdown(i18n(\"account.methods.offline.name.invalid\"), i18n(\"message.warning\"), 10,\n                    MessageDialogPane.MessageType.WARNING,\n                    doCreate, () -> {\n                        body.setDisable(false);\n                        spinner.hideSpinner();\n                    });\n        } else {\n            doCreate.run();\n        }\n    }\n\n    private void onCancel() {\n        if (loginTask != null) {\n            loginTask.cancel();\n        }\n        fireEvent(new DialogCloseEvent());\n    }\n\n    private void initDetailsPane() {\n        if (detailsPane != null) {\n            btnAccept.disableProperty().unbind();\n            detailsContainer.getChildren().remove(detailsPane);\n            lblErrorMessage.setText(\"\");\n            setActions(lblErrorMessage, actions);\n        }\n\n        if (factory == Accounts.FACTORY_MICROSOFT) {\n            detailsPane = new MicrosoftAccountLoginPane(true);\n            setActions();\n        } else {\n            detailsPane = new AccountDetailsInputPane(factory, btnAccept::fire);\n            btnAccept.disableProperty().bind(((AccountDetailsInputPane) detailsPane).validProperty().not());\n            setActions(lblErrorMessage, actions);\n        }\n\n        detailsContainer.getChildren().add(detailsPane);\n    }\n\n    private static class AccountDetailsInputPane extends GridPane {\n\n        // ==== authlib-injector hyperlinks ====\n        private static final String[] ALLOWED_LINKS = {\"homepage\", \"register\"};\n\n        private static List<Hyperlink> createHyperlinks(AuthlibInjectorServer server) {\n            if (server == null) {\n                return emptyList();\n            }\n\n            Map<String, String> links = server.getLinks();\n            List<Hyperlink> result = new ArrayList<>();\n            for (String key : ALLOWED_LINKS) {\n                String value = links.get(key);\n                if (value != null) {\n                    Hyperlink link = new Hyperlink(i18n(\"account.injector.link.\" + key));\n                    FXUtils.installSlowTooltip(link, value);\n                    link.setOnAction(e -> FXUtils.openLink(value));\n                    result.add(link);\n                }\n            }\n            return unmodifiableList(result);\n        }\n        // =====\n\n        private final AccountFactory<?> factory;\n        private @Nullable AuthlibInjectorServer server;\n        private @Nullable JFXComboBox<AuthlibInjectorServer> cboServers;\n        private @Nullable JFXTextField txtUsername;\n        private @Nullable JFXPasswordField txtPassword;\n        private @Nullable JFXTextField txtUUID;\n        private final BooleanBinding valid;\n\n        public AccountDetailsInputPane(AccountFactory<?> factory, Runnable onAction) {\n            this.factory = factory;\n\n            setVgap(22);\n            setHgap(15);\n            setAlignment(Pos.CENTER);\n\n            ColumnConstraints col0 = new ColumnConstraints();\n            col0.setMinWidth(USE_PREF_SIZE);\n            getColumnConstraints().add(col0);\n            ColumnConstraints col1 = new ColumnConstraints();\n            col1.setHgrow(Priority.ALWAYS);\n            getColumnConstraints().add(col1);\n\n            int rowIndex = 0;\n\n            if (!IntegrityChecker.isOfficial() && !(factory instanceof OfflineAccountFactory)) {\n                HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);\n                hintPane.setSegment(i18n(\"unofficial.hint\"));\n                GridPane.setColumnSpan(hintPane, 2);\n                add(hintPane, 0, rowIndex);\n\n                rowIndex++;\n            }\n\n            if (factory instanceof BoundAuthlibInjectorAccountFactory) {\n                this.server = ((BoundAuthlibInjectorAccountFactory) factory).getServer();\n\n                Label lblServers = new Label(i18n(\"account.injector.server\"));\n                setHalignment(lblServers, HPos.LEFT);\n                add(lblServers, 0, rowIndex);\n\n                Label lblServerName = new Label(this.server.getName());\n                lblServerName.setMaxWidth(Double.MAX_VALUE);\n                HBox.setHgrow(lblServerName, Priority.ALWAYS);\n\n                HBox linksContainer = new HBox();\n                linksContainer.setAlignment(Pos.CENTER);\n                linksContainer.getChildren().setAll(createHyperlinks(this.server));\n                linksContainer.setMinWidth(USE_PREF_SIZE);\n\n                HBox boxServers = new HBox(lblServerName, linksContainer);\n                boxServers.setAlignment(Pos.CENTER_LEFT);\n                add(boxServers, 1, rowIndex);\n\n                rowIndex++;\n            } else if (factory instanceof AuthlibInjectorAccountFactory) {\n                Label lblServers = new Label(i18n(\"account.injector.server\"));\n                setHalignment(lblServers, HPos.LEFT);\n                add(lblServers, 0, rowIndex);\n\n                cboServers = new JFXComboBox<>();\n                cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));\n                cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));\n                bindContent(cboServers.getItems(), config().getAuthlibInjectorServers());\n                cboServers.getItems().addListener(onInvalidating(\n                        () -> Platform.runLater( // the selection will not be updated as expected if we call it immediately\n                                cboServers.getSelectionModel()::selectFirst)));\n                cboServers.getSelectionModel().selectFirst();\n                cboServers.setPromptText(i18n(\"account.injector.empty\"));\n                BooleanBinding noServers = createBooleanBinding(cboServers.getItems()::isEmpty, cboServers.getItems());\n                classPropertyFor(cboServers, \"jfx-combo-box-warning\").bind(noServers);\n                classPropertyFor(cboServers, \"jfx-combo-box\").bind(noServers.not());\n                HBox.setHgrow(cboServers, Priority.ALWAYS);\n                HBox.setMargin(cboServers, new Insets(0, 10, 0, 0));\n                cboServers.setMaxWidth(Double.MAX_VALUE);\n\n                HBox linksContainer = new HBox();\n                linksContainer.setAlignment(Pos.CENTER);\n                onChangeAndOperate(cboServers.valueProperty(), server -> {\n                    this.server = server;\n                    linksContainer.getChildren().setAll(createHyperlinks(server));\n\n                    if (txtUsername != null)\n                        txtUsername.validate();\n                });\n                linksContainer.setMinWidth(USE_PREF_SIZE);\n\n                JFXButton btnAddServer = FXUtils.newToggleButton4(SVG.ADD, 20);\n                btnAddServer.setOnAction(e -> {\n                    Controllers.dialog(new AddAuthlibInjectorServerPane());\n                });\n\n                HBox boxServers = new HBox(cboServers, linksContainer, btnAddServer);\n                add(boxServers, 1, rowIndex);\n\n                rowIndex++;\n            }\n\n            if (factory.getLoginType().requiresUsername) {\n                Label lblUsername = new Label(i18n(\"account.username\"));\n                setHalignment(lblUsername, HPos.LEFT);\n                add(lblUsername, 0, rowIndex);\n\n                txtUsername = new JFXTextField();\n                txtUsername.setValidators(\n                        new RequiredValidator(),\n                        new Validator(i18n(\"input.email\"), username -> {\n                            if (requiresEmailAsUsername()) {\n                                return username.contains(\"@\");\n                            } else {\n                                return true;\n                            }\n                        }));\n                setValidateWhileTextChanged(txtUsername, true);\n                txtUsername.setOnAction(e -> onAction.run());\n                add(txtUsername, 1, rowIndex);\n\n                rowIndex++;\n            }\n\n            if (factory.getLoginType().requiresPassword) {\n                Label lblPassword = new Label(i18n(\"account.password\"));\n                setHalignment(lblPassword, HPos.LEFT);\n                add(lblPassword, 0, rowIndex);\n\n                txtPassword = new JFXPasswordField();\n                txtPassword.setValidators(new RequiredValidator());\n                setValidateWhileTextChanged(txtPassword, true);\n                txtPassword.setOnAction(e -> onAction.run());\n                add(txtPassword, 1, rowIndex);\n\n                rowIndex++;\n            }\n\n            if (factory instanceof OfflineAccountFactory) {\n                txtUsername.setPromptText(i18n(\"account.methods.offline.name.special_characters\"));\n                FXUtils.installFastTooltip(txtUsername, i18n(\"account.methods.offline.name.special_characters\"));\n\n                JFXHyperlink purchaseLink = new JFXHyperlink(i18n(\"account.methods.microsoft.purchase\"));\n                purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);\n                HBox linkPane = new HBox(purchaseLink);\n                GridPane.setColumnSpan(linkPane, 2);\n                add(linkPane, 0, rowIndex);\n\n                rowIndex++;\n\n                HBox box = new HBox();\n                MenuUpDownButton advancedButton = new MenuUpDownButton();\n                box.getChildren().setAll(advancedButton);\n                advancedButton.setText(i18n(\"settings.advanced\"));\n                GridPane.setColumnSpan(box, 2);\n                add(box, 0, rowIndex);\n\n                rowIndex++;\n\n                Label lblUUID = new Label(i18n(\"account.methods.offline.uuid\"));\n                lblUUID.managedProperty().bind(advancedButton.selectedProperty());\n                lblUUID.visibleProperty().bind(advancedButton.selectedProperty());\n                setHalignment(lblUUID, HPos.LEFT);\n                add(lblUUID, 0, rowIndex);\n\n                txtUUID = new JFXTextField();\n                txtUUID.managedProperty().bind(advancedButton.selectedProperty());\n                txtUUID.visibleProperty().bind(advancedButton.selectedProperty());\n                txtUUID.setValidators(new UUIDValidator());\n                txtUUID.promptTextProperty().bind(BindingMapping.of(txtUsername.textProperty()).map(name -> OfflineAccountFactory.getUUIDFromUserName(name).toString()));\n                txtUUID.setOnAction(e -> onAction.run());\n                add(txtUUID, 1, rowIndex);\n\n                rowIndex++;\n\n                HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);\n                hintPane.managedProperty().bind(advancedButton.selectedProperty());\n                hintPane.visibleProperty().bind(advancedButton.selectedProperty());\n                hintPane.setText(i18n(\"account.methods.offline.uuid.hint\"));\n                GridPane.setColumnSpan(hintPane, 2);\n                add(hintPane, 0, rowIndex);\n\n                rowIndex++;\n            }\n\n            valid = new BooleanBinding() {\n                {\n                    if (cboServers != null)\n                        bind(cboServers.valueProperty());\n                    if (txtUsername != null)\n                        bind(txtUsername.textProperty());\n                    if (txtPassword != null)\n                        bind(txtPassword.textProperty());\n                    if (txtUUID != null)\n                        bind(txtUUID.textProperty());\n                }\n\n                @Override\n                protected boolean computeValue() {\n                    if (cboServers != null && cboServers.getValue() == null)\n                        return false;\n                    if (txtUsername != null && !txtUsername.validate())\n                        return false;\n                    if (txtPassword != null && !txtPassword.validate())\n                        return false;\n                    if (txtUUID != null && !txtUUID.validate())\n                        return false;\n                    return true;\n                }\n            };\n        }\n\n        private boolean requiresEmailAsUsername() {\n            if ((factory instanceof AuthlibInjectorAccountFactory) && this.server != null) {\n                return !server.isNonEmailLogin();\n            }\n            if (factory instanceof BoundAuthlibInjectorAccountFactory bound) {\n                return !bound.getServer().isNonEmailLogin();\n            }\n            return false;\n        }\n\n        public Object getAdditionalData() {\n            if (factory instanceof AuthlibInjectorAccountFactory) {\n                return getAuthServer();\n            } else if (factory instanceof OfflineAccountFactory) {\n                UUID uuid = txtUUID == null ? null : StringUtils.isBlank(txtUUID.getText()) ? null : UUIDTypeAdapter.fromString(txtUUID.getText());\n                return new OfflineAccountFactory.AdditionalData(uuid, null);\n            } else {\n                return null;\n            }\n        }\n\n        public @Nullable AuthlibInjectorServer getAuthServer() {\n            return this.server;\n        }\n\n        public @Nullable String getUsername() {\n            return txtUsername == null ? null : txtUsername.getText();\n        }\n\n        public @Nullable String getPassword() {\n            return txtPassword == null ? null : txtPassword.getText();\n        }\n\n        public BooleanBinding validProperty() {\n            return valid;\n        }\n\n        public void focus() {\n            if (txtUsername != null) {\n                txtUsername.requestFocus();\n            }\n        }\n    }\n\n    public static class DialogCharacterSelector extends JFXDialogLayout implements CharacterSelector {\n\n        private final AdvancedListBox listBox = new AdvancedListBox();\n        private final JFXButton cancel = new JFXButton();\n\n        private final CountDownLatch latch = new CountDownLatch(1);\n        private GameProfile selectedProfile = null;\n\n        public DialogCharacterSelector() {\n            setStyle(\"-fx-padding: 8px;\");\n\n            cancel.setText(i18n(\"button.cancel\"));\n            cancel.setOnAction(e -> latch.countDown());\n            cancel.getStyleClass().add(\"dialog-cancel\");\n\n            listBox.startCategory(i18n(\"account.choose\").toUpperCase(Locale.ROOT));\n\n            setBody(listBox);\n\n            HBox hbox = new HBox();\n            hbox.setAlignment(Pos.CENTER_RIGHT);\n            hbox.getChildren().add(cancel);\n            setActions(hbox);\n\n            onEscPressed(this, cancel::fire);\n        }\n\n        @Override\n        public GameProfile select(YggdrasilService service, List<GameProfile> profiles) throws NoSelectedCharacterException {\n            Platform.runLater(() -> {\n                for (GameProfile profile : profiles) {\n                    Canvas portraitCanvas = new Canvas(32, 32);\n                    TexturesLoader.bindAvatar(portraitCanvas, service, profile.getId());\n\n                    IconedItem accountItem = new IconedItem(portraitCanvas, profile.getName());\n                    FXUtils.onClicked(accountItem, () -> {\n                        selectedProfile = profile;\n                        latch.countDown();\n                    });\n                    listBox.add(accountItem);\n                }\n                Controllers.dialog(this);\n            });\n\n            try {\n                latch.await();\n\n                if (selectedProfile == null)\n                    throw new NoSelectedCharacterException();\n\n                return selectedProfile;\n            } catch (InterruptedException ignored) {\n                throw new NoSelectedCharacterException();\n            } finally {\n                Platform.runLater(() -> fireEvent(new DialogCloseEvent()));\n            }\n        }\n    }\n\n    @Override\n    public void onDialogShown() {\n        if (detailsPane instanceof AccountDetailsInputPane) {\n            ((AccountDetailsInputPane) detailsPane).focus();\n        }\n    }\n\n    private static class UUIDValidator extends ValidatorBase {\n\n        public UUIDValidator() {\n            this(i18n(\"account.methods.offline.uuid.malformed\"));\n        }\n\n        public UUIDValidator(@NamedArg(\"message\") String message) {\n            super(message);\n        }\n\n        @Override\n        protected void eval() {\n            if (srcControl.get() instanceof TextInputControl) {\n                evalTextInputField();\n            }\n        }\n\n        private void evalTextInputField() {\n            TextInputControl textField = ((TextInputControl) srcControl.get());\n            if (StringUtils.isBlank(textField.getText())) {\n                hasErrors.set(false);\n                return;\n            }\n\n            try {\n                UUIDTypeAdapter.fromString(textField.getText());\n                hasErrors.set(false);\n            } catch (IllegalArgumentException ignored) {\n                hasErrors.set(true);\n            }\n        }\n    }\n\n    private static final String MICROSOFT_ACCOUNT_EDIT_PROFILE_URL = \"https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a\";\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport com.jfoenix.controls.JFXSpinner;\nimport io.nayuki.qrcodegen.QrCode;\nimport javafx.application.Platform;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.Group;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.FlowPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.shape.SVGPath;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.auth.AuthInfo;\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.OAuth;\nimport org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.upgrade.IntegrityChecker;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.QrCodeUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.util.concurrent.CancellationException;\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class MicrosoftAccountLoginPane extends JFXDialogLayout implements DialogAware {\n    private final Account accountToRelogin;\n    private final Consumer<AuthInfo> loginCallback;\n    private final Runnable cancelCallback;\n\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final WeakListenerHolder holder = new WeakListenerHolder();\n\n    private final ObjectProperty<Step> step = new SimpleObjectProperty<>();\n\n    private TaskExecutor browserTaskExecutor;\n    private TaskExecutor deviceTaskExecutor;\n\n    private final JFXButton btnLogin;\n    private final SpinnerPane loginButtonSpinner;\n\n    public MicrosoftAccountLoginPane() {\n        this(false);\n    }\n\n    public MicrosoftAccountLoginPane(boolean bodyonly) {\n        this(null, null, null, bodyonly);\n    }\n\n    public MicrosoftAccountLoginPane(Account account, Consumer<AuthInfo> callback, Runnable onCancel, boolean bodyonly) {\n        this.accountToRelogin = account;\n        this.loginCallback = callback;\n        this.cancelCallback = onCancel;\n\n        getStyleClass().add(\"microsoft-login-dialog\");\n        if (bodyonly) {\n            this.pseudoClassStateChanged(PseudoClass.getPseudoClass(\"bodyonly\"), true);\n        } else {\n            Label heading = new Label(accountToRelogin != null ? i18n(\"account.login.refresh\") : i18n(\"account.create.microsoft\"));\n            heading.getStyleClass().add(\"header-label\");\n            setHeading(heading);\n        }\n\n        this.setMaxWidth(650);\n\n        onEscPressed(this, this::onCancel);\n\n        btnLogin = new JFXButton(i18n(\"account.login\"));\n        btnLogin.getStyleClass().add(\"dialog-accept\");\n\n        loginButtonSpinner = new SpinnerPane();\n        loginButtonSpinner.getStyleClass().add(\"small-spinner-pane\");\n        loginButtonSpinner.setContent(btnLogin);\n\n        JFXButton btnCancel = new JFXButton(i18n(\"button.cancel\"));\n        btnCancel.getStyleClass().add(\"dialog-cancel\");\n        btnCancel.setOnAction(e -> onCancel());\n\n        setActions(loginButtonSpinner, btnCancel);\n\n        holder.registerWeak(Accounts.OAUTH_CALLBACK.onOpenBrowserAuthorizationCode, event -> Platform.runLater(() -> {\n            if (step.get() instanceof Step.StartAuthorizationCodeLogin)\n                step.set(new Step.WaitForOpenBrowser(event.getUrl()));\n        }));\n\n        holder.registerWeak(Accounts.OAUTH_CALLBACK.onGrantDeviceCode, event -> Platform.runLater(() -> {\n            if (step.get() instanceof Step.StartDeviceCodeLogin)\n                step.set(new Step.WaitForScanQrCode(event.getUserCode(), event.getVerificationUri()));\n        }));\n\n        this.step.set(Accounts.OAUTH_CALLBACK.getClientId().isEmpty()\n                ? new Step.Init()\n                : new Step.StartAuthorizationCodeLogin());\n        FXUtils.onChangeAndOperate(step, this::onStep);\n    }\n\n    private void onStep(Step currentStep) {\n        VBox rootContainer = new VBox(10);\n        setBody(rootContainer);\n        rootContainer.setAlignment(Pos.TOP_CENTER);\n\n        if (Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {\n            var snapshotHint = new HintPane(MessageDialogPane.MessageType.WARNING);\n            snapshotHint.setSegment(i18n(\"account.methods.microsoft.snapshot\"));\n            rootContainer.getChildren().add(snapshotHint);\n            btnLogin.setDisable(true);\n            loginButtonSpinner.setLoading(false);\n            return;\n        }\n\n        if (!IntegrityChecker.isOfficial()) {\n            var unofficialHintPane = new HintPane(MessageDialogPane.MessageType.WARNING);\n            unofficialHintPane.setSegment(i18n(\"unofficial.hint\"));\n            rootContainer.getChildren().add(unofficialHintPane);\n        }\n\n        if (currentStep instanceof Step.Init) {\n            btnLogin.setOnAction(e -> this.step.set(new Step.StartAuthorizationCodeLogin()));\n            loginButtonSpinner.setLoading(false);\n\n            var hintPane = new HintPane(MessageDialogPane.MessageType.INFO);\n            hintPane.setText(i18n(\"account.methods.microsoft.hint\"));\n            rootContainer.getChildren().add(hintPane);\n        } else if (currentStep instanceof Step.StartAuthorizationCodeLogin) {\n            loginButtonSpinner.setLoading(false);\n            cancelAllTasks();\n\n            rootContainer.getChildren().add(new JFXSpinner());\n\n            browserTaskExecutor = Task.supplyAsync(() -> Accounts.FACTORY_MICROSOFT.create(null, null, null, null, OAuth.GrantFlow.AUTHORIZATION_CODE))\n                    .whenComplete(Schedulers.javafx(), this::onLoginCompleted)\n                    .executor(true);\n        } else if (currentStep instanceof Step.StartDeviceCodeLogin) {\n            loginButtonSpinner.setLoading(true);\n            cancelAllTasks();\n\n            rootContainer.getChildren().add(new JFXSpinner());\n\n            deviceTaskExecutor = Task.supplyAsync(() -> Accounts.FACTORY_MICROSOFT.create(null, null, null, null, OAuth.GrantFlow.DEVICE))\n                    .whenComplete(Schedulers.javafx(), this::onLoginCompleted)\n                    .executor(true);\n        } else if (currentStep instanceof Step.WaitForOpenBrowser wait) {\n            btnLogin.setOnAction(e -> {\n                FXUtils.openLink(wait.url());\n                loginButtonSpinner.setLoading(true);\n            });\n            loginButtonSpinner.setLoading(false);\n\n            HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);\n            hintPane.setSegment(\n                    i18n(\"account.methods.microsoft.methods.browser.hint\", StringUtils.escapeXmlAttribute(wait.url()), wait.url()),\n                    FXUtils::copyText\n            );\n\n            rootContainer.getChildren().add(hintPane);\n        } else if (currentStep instanceof Step.WaitForScanQrCode wait) {\n            loginButtonSpinner.setLoading(true);\n\n            String scanUri = \"https://www.microsoft.com/link\".equals(wait.verificationUri())\n                    ? \"https://www.microsoft.com/link?otc=\" + wait.userCode()\n                    : wait.verificationUri();\n\n            var deviceHint = new HintPane(MessageDialogPane.MessageType.INFO);\n            deviceHint.setSegment(i18n(\"account.methods.microsoft.methods.device.hint\",\n                    StringUtils.escapeXmlAttribute(scanUri),\n                    wait.verificationUri(),\n                    wait.userCode()\n            ));\n\n            var qrCode = new SVGPath();\n            qrCode.fillProperty().bind(Themes.colorSchemeProperty().getPrimary());\n            qrCode.setContent(QrCodeUtils.toSVGPath(QrCode.encodeText(scanUri, QrCode.Ecc.MEDIUM)));\n            qrCode.setScaleX(3);\n            qrCode.setScaleY(3);\n\n            var lblCode = new Label(wait.userCode());\n            lblCode.getStyleClass().add(\"code-label\");\n            lblCode.setStyle(\"-fx-font-family: \\\"\" + Lang.requireNonNullElse(config().getFontFamily(), FXUtils.DEFAULT_MONOSPACE_FONT) + \"\\\";\");\n\n            var codeBox = new StackPane(lblCode);\n            codeBox.getStyleClass().add(\"code-box\");\n            codeBox.setCursor(Cursor.HAND);\n            FXUtils.onClicked(codeBox, () -> FXUtils.copyText(wait.userCode()));\n            codeBox.setMaxWidth(USE_PREF_SIZE);\n\n            rootContainer.getChildren().addAll(deviceHint, new Group(qrCode), codeBox);\n        } else if (currentStep instanceof Step.LoginFailed failed) {\n            btnLogin.setOnAction(e -> this.step.set(new Step.StartAuthorizationCodeLogin()));\n            loginButtonSpinner.setLoading(false);\n            cancelAllTasks();\n\n            HintPane errHintPane = new HintPane(MessageDialogPane.MessageType.ERROR);\n            errHintPane.setSegment(failed.message());\n            rootContainer.getChildren().add(errHintPane);\n        }\n\n        var linkBox = new FlowPane(8, 8);\n        linkBox.setAlignment(Pos.CENTER_LEFT);\n        linkBox.setPrefWrapLength(500);\n\n        if (currentStep instanceof Step.Init || currentStep instanceof Step.StartAuthorizationCodeLogin || currentStep instanceof Step.WaitForOpenBrowser) {\n            JFXHyperlink useQrCode = new JFXHyperlink(i18n(\"account.methods.microsoft.methods.device\"));\n            useQrCode.setOnAction(e -> this.step.set(new Step.StartDeviceCodeLogin()));\n            linkBox.getChildren().add(useQrCode);\n        } else if (currentStep instanceof Step.StartDeviceCodeLogin || currentStep instanceof Step.WaitForScanQrCode) {\n            JFXHyperlink userBrowser = new JFXHyperlink(i18n(\"account.methods.microsoft.methods.browser\"));\n            userBrowser.setOnAction(e -> this.step.set(new Step.StartAuthorizationCodeLogin()));\n            linkBox.getChildren().add(userBrowser);\n        }\n\n        JFXHyperlink profileLink = new JFXHyperlink(i18n(\"account.methods.microsoft.profile\"));\n        profileLink.setExternalLink(\"https://account.live.com/editprof.aspx\");\n        JFXHyperlink purchaseLink = new JFXHyperlink(i18n(\"account.methods.microsoft.purchase\"));\n        purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);\n\n        linkBox.getChildren().addAll(profileLink, purchaseLink);\n        rootContainer.getChildren().add(linkBox);\n\n        setBody(rootContainer);\n    }\n\n    private void cancelAllTasks() {\n        if (browserTaskExecutor != null) browserTaskExecutor.cancel();\n        if (deviceTaskExecutor != null) deviceTaskExecutor.cancel();\n    }\n\n    private void onCancel() {\n        cancelAllTasks();\n        if (cancelCallback != null) cancelCallback.run();\n        fireEvent(new DialogCloseEvent());\n    }\n\n    private void onLoginCompleted(MicrosoftAccount account, Exception exception) {\n        if (exception == null) {\n            if (accountToRelogin != null) Accounts.getAccounts().remove(accountToRelogin);\n\n            int oldIndex = Accounts.getAccounts().indexOf(account);\n            if (oldIndex == -1) {\n                Accounts.getAccounts().add(account);\n            } else {\n                Accounts.getAccounts().remove(oldIndex);\n                Accounts.getAccounts().add(oldIndex, account);\n            }\n\n            Accounts.setSelectedAccount(account);\n\n            if (loginCallback != null) {\n                try {\n                    loginCallback.accept(account.logIn());\n                } catch (AuthenticationException e) {\n                    this.step.set(new Step.LoginFailed(Accounts.localizeErrorMessage(e)));\n                    return;\n                }\n            }\n            fireEvent(new DialogCloseEvent());\n        } else if (!(exception instanceof CancellationException)) {\n            this.step.set(new Step.LoginFailed(Accounts.localizeErrorMessage(exception)));\n        }\n    }\n\n    private sealed interface Step {\n        final class Init implements Step {\n        }\n\n        final class StartAuthorizationCodeLogin implements Step {\n        }\n\n        record WaitForOpenBrowser(String url) implements Step {\n\n        }\n\n        final class StartDeviceCodeLogin implements Step {\n        }\n\n        record WaitForScanQrCode(String userCode, String verificationUri) implements Step {\n\n        }\n\n        record LoginFailed(String message) implements Step {\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.account;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXComboBox;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.geometry.Insets;\nimport javafx.geometry.VPos;\nimport javafx.scene.control.Label;\nimport javafx.scene.input.DragEvent;\nimport javafx.scene.input.TransferMode;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.auth.offline.OfflineAccount;\nimport org.jackhuang.hmcl.auth.offline.Skin;\nimport org.jackhuang.hmcl.auth.yggdrasil.TextureModel;\nimport org.jackhuang.hmcl.game.TexturesLoader;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.skin.SkinCanvas;\nimport org.jackhuang.hmcl.ui.skin.animation.SkinAniRunning;\nimport org.jackhuang.hmcl.ui.skin.animation.SkinAniWavingArms;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.UUID;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.ui.FXUtils.stringConverter;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class OfflineAccountSkinPane extends StackPane {\n    private final OfflineAccount account;\n\n    private final MultiFileItem<Skin.Type> skinItem = new MultiFileItem<>();\n    private final JFXTextField cslApiField = new JFXTextField();\n    private final JFXComboBox<TextureModel> modelCombobox = new JFXComboBox<>();\n    private final FileSelector skinSelector = new FileSelector();\n    private final FileSelector capeSelector = new FileSelector();\n\n    private final InvalidationListener skinBinding;\n\n    public OfflineAccountSkinPane(OfflineAccount account) {\n        this.account = account;\n\n        getStyleClass().add(\"skin-pane\");\n\n        JFXDialogLayout layout = new JFXDialogLayout();\n        getChildren().setAll(layout);\n        layout.setHeading(new Label(i18n(\"account.skin\")));\n\n        BorderPane pane = new BorderPane();\n\n        SkinCanvas canvas = new SkinCanvas(TexturesLoader.getDefaultSkinImage(), 300, 300, true);\n        StackPane canvasPane = new StackPane(canvas);\n        canvasPane.setPrefWidth(300);\n        canvasPane.setPrefHeight(300);\n        pane.setCenter(canvas);\n        canvas.getAnimationPlayer().addSkinAnimation(new SkinAniWavingArms(100, 2000, 7.5, canvas), new SkinAniRunning(100, 100, 30, canvas));\n        canvas.enableRotation(.5);\n\n        canvas.addEventHandler(DragEvent.DRAG_OVER, e -> {\n            if (e.getDragboard().hasFiles()) {\n                Path file = e.getDragboard().getFiles().get(0).toPath();\n                if (FileUtils.getName(file).endsWith(\".png\"))\n                    e.acceptTransferModes(TransferMode.COPY);\n            }\n        });\n        canvas.addEventHandler(DragEvent.DRAG_DROPPED, e -> {\n            if (e.isAccepted()) {\n                Path skin = e.getDragboard().getFiles().get(0).toPath();\n                Platform.runLater(() -> {\n                    skinSelector.setValue(FileUtils.getAbsolutePath(skin));\n                    skinItem.setSelectedData(Skin.Type.LOCAL_FILE);\n                });\n            }\n        });\n\n        StackPane skinOptionPane = new StackPane();\n        skinOptionPane.setMaxWidth(300);\n        VBox optionPane = new VBox(skinItem, skinOptionPane);\n        pane.setRight(optionPane);\n\n        skinSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));\n        capeSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));\n\n        layout.setBody(pane);\n\n        cslApiField.setPromptText(i18n(\"account.skin.type.csl_api.location.hint\"));\n        cslApiField.setValidators(new URLValidator());\n        FXUtils.setValidateWhileTextChanged(cslApiField, true);\n\n        skinItem.loadChildren(Arrays.asList(\n                new MultiFileItem.Option<>(i18n(\"message.default\"), Skin.Type.DEFAULT),\n                new MultiFileItem.Option<>(i18n(\"account.skin.type.steve\"), Skin.Type.STEVE),\n                new MultiFileItem.Option<>(i18n(\"account.skin.type.alex\"), Skin.Type.ALEX),\n                new MultiFileItem.Option<>(i18n(\"account.skin.type.local_file\"), Skin.Type.LOCAL_FILE),\n                new MultiFileItem.Option<>(i18n(\"account.skin.type.little_skin\"), Skin.Type.LITTLE_SKIN),\n                new MultiFileItem.Option<>(i18n(\"account.skin.type.csl_api\"), Skin.Type.CUSTOM_SKIN_LOADER_API)\n        ));\n\n        modelCombobox.setConverter(stringConverter(model -> i18n(\"account.skin.model.\" + model.modelName)));\n        modelCombobox.getItems().setAll(TextureModel.WIDE, TextureModel.SLIM);\n\n        if (account.getSkin() == null) {\n            skinItem.setSelectedData(Skin.Type.DEFAULT);\n            modelCombobox.setValue(TextureModel.WIDE);\n        } else {\n            skinItem.setSelectedData(account.getSkin().getType());\n            cslApiField.setText(account.getSkin().getCslApi());\n            modelCombobox.setValue(account.getSkin().getTextureModel());\n            skinSelector.setValue(account.getSkin().getLocalSkinPath());\n            capeSelector.setValue(account.getSkin().getLocalCapePath());\n        }\n\n        skinBinding = FXUtils.observeWeak(() -> {\n            getSkin().load(account.getUsername())\n                    .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                        if (exception != null) {\n                            LOG.warning(\"Failed to load skin\", exception);\n                            Controllers.showToast(i18n(\"message.failed\"));\n                        } else {\n                            UUID uuid = this.account.getUUID();\n                            if (result == null || result.getSkin() == null && result.getCape() == null) {\n                                canvas.updateSkin(\n                                        TexturesLoader.getDefaultSkin(uuid).getImage(),\n                                        TexturesLoader.getDefaultModel(uuid) == TextureModel.SLIM,\n                                        null\n                                );\n                                return;\n                            }\n                            canvas.updateSkin(\n                                    result.getSkin() != null ? result.getSkin().getImage() : TexturesLoader.getDefaultSkin(uuid).getImage(),\n                                    result.getModel() == TextureModel.SLIM,\n                                    result.getCape() != null ? result.getCape().getImage() : null);\n                        }\n                    }).start();\n        }, skinItem.selectedDataProperty(), cslApiField.textProperty(), modelCombobox.valueProperty(), skinSelector.valueProperty(), capeSelector.valueProperty());\n\n        FXUtils.onChangeAndOperate(skinItem.selectedDataProperty(), selectedData -> {\n            GridPane gridPane = new GridPane();\n            // Increase bottom padding to prevent the prompt from overlapping with the dialog action area\n\n            gridPane.setPadding(new Insets(0, 0, 45, 10));\n            gridPane.setHgap(16);\n            gridPane.setVgap(8);\n            gridPane.getColumnConstraints().setAll(new ColumnConstraints(), FXUtils.getColumnHgrowing());\n\n            switch (selectedData) {\n                case DEFAULT:\n                case STEVE:\n                case ALEX:\n                    break;\n                case LITTLE_SKIN:\n                    HintPane hint = new HintPane(MessageDialogPane.MessageType.INFO);\n                    hint.setText(i18n(\"account.skin.type.little_skin.hint\"));\n\n                    // Spanning two columns and expanding horizontally\n                    GridPane.setColumnSpan(hint, 2);\n                    GridPane.setHgrow(hint, Priority.ALWAYS);\n                    hint.setMaxWidth(Double.MAX_VALUE);\n\n                    // Force top alignment within cells (to avoid vertical offset caused by the baseline)\n                    GridPane.setValignment(hint, VPos.TOP);\n\n                    // Set a fixed height as the preferred height to prevent the GridPane from stretching or leaving empty space.\n                    hint.setMaxHeight(Region.USE_PREF_SIZE);\n                    hint.setMinHeight(Region.USE_PREF_SIZE);\n\n                    gridPane.addRow(0, hint);\n                    break;\n                case LOCAL_FILE:\n                    gridPane.setPadding(new Insets(0, 0, 0, 10));\n                    gridPane.addRow(0, new Label(i18n(\"account.skin.model\")), modelCombobox);\n                    gridPane.addRow(1, new Label(i18n(\"account.skin\")), skinSelector);\n                    gridPane.addRow(2, new Label(i18n(\"account.cape\")), capeSelector);\n                    break;\n                case CUSTOM_SKIN_LOADER_API:\n                    gridPane.addRow(0, new Label(i18n(\"account.skin.type.csl_api.location\")), cslApiField);\n                    break;\n            }\n\n            skinOptionPane.getChildren().setAll(gridPane);\n        });\n\n        JFXButton acceptButton = new JFXButton(i18n(\"button.ok\"));\n        acceptButton.getStyleClass().add(\"dialog-accept\");\n        acceptButton.setOnAction(e -> {\n            account.setSkin(getSkin());\n            fireEvent(new DialogCloseEvent());\n        });\n\n        JFXHyperlink littleSkinLink = new JFXHyperlink(i18n(\"account.skin.type.little_skin\"));\n        littleSkinLink.setOnAction(e -> FXUtils.openLink(\"https://littleskin.cn/\"));\n        JFXButton cancelButton = new JFXButton(i18n(\"button.cancel\"));\n        cancelButton.getStyleClass().add(\"dialog-cancel\");\n        cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));\n        onEscPressed(this, cancelButton::fire);\n\n        acceptButton.disableProperty().bind(\n                skinItem.selectedDataProperty().isEqualTo(Skin.Type.CUSTOM_SKIN_LOADER_API)\n                        .and(cslApiField.activeValidatorProperty().isNotNull()));\n\n        layout.setActions(littleSkinLink, acceptButton, cancelButton);\n    }\n\n    private Skin getSkin() {\n        Skin.Type type = skinItem.getSelectedData();\n        if (type == Skin.Type.LOCAL_FILE) {\n            return new Skin(type, cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());\n        } else {\n            String cslApi = type == Skin.Type.CUSTOM_SKIN_LOADER_API ? cslApiField.getText() : null;\n            return new Skin(type, cslApi, null, null, null);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.animation;\n\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.setting.ConfigHolder;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\n/**\n * @author Glavo\n */\npublic final class AnimationUtils {\n\n    private AnimationUtils() {\n    }\n\n    /**\n     * Trigger initialization of this class.\n     * Should be called from {@link org.jackhuang.hmcl.setting.Settings#init()}.\n     */\n    @SuppressWarnings(\"JavadocReference\")\n    public static void init() {\n    }\n\n    private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled();\n    private static final boolean PLAY_WINDOW_ANIMATION = ENABLED && !OperatingSystem.CURRENT_OS.isLinuxOrBSD();\n\n    public static boolean isAnimationEnabled() {\n        return ENABLED;\n    }\n\n    public static boolean playWindowAnimation() {\n        return PLAY_WINDOW_ANIMATION;\n    }\n\n    public static void reset(Node node, boolean opaque) {\n        node.setTranslateX(0);\n        node.setTranslateY(0);\n        node.setScaleX(1);\n        node.setScaleY(1);\n        node.setOpacity(opaque ? 1 : 0);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.animation;\n\nimport javafx.animation.*;\nimport javafx.scene.Node;\nimport javafx.scene.layout.Pane;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;\n\npublic enum ContainerAnimations implements TransitionPane.AnimationProducer {\n    NONE {\n        @Override\n        public void init(TransitionPane container, Node previousNode, Node nextNode) {\n            AnimationUtils.reset(previousNode, false);\n            AnimationUtils.reset(nextNode, true);\n        }\n\n        @Override\n        public Timeline animate(\n                Pane container, Node previousNode, Node nextNode,\n                Duration duration, Interpolator interpolator) {\n            return new Timeline();\n        }\n\n        @Override\n        public TransitionPane.AnimationProducer opposite() {\n            return this;\n        }\n    },\n\n    /**\n     * A fade between the old and new view\n     */\n    FADE {\n        @Override\n        public Timeline animate(\n                Pane container, Node previousNode, Node nextNode,\n                Duration duration, Interpolator interpolator) {\n            return new Timeline(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(previousNode.opacityProperty(), 1, interpolator),\n                            new KeyValue(nextNode.opacityProperty(), 0, interpolator)),\n                    new KeyFrame(duration,\n                            new KeyValue(previousNode.opacityProperty(), 0, interpolator),\n                            new KeyValue(nextNode.opacityProperty(), 1, interpolator)));\n        }\n\n        @Override\n        public TransitionPane.AnimationProducer opposite() {\n            return this;\n        }\n    },\n\n    /**\n     * A swipe effect\n     */\n    SWIPE_LEFT {\n        @Override\n        public void init(TransitionPane container, Node previousNode, Node nextNode) {\n            AnimationUtils.reset(previousNode, true);\n            AnimationUtils.reset(nextNode, true);\n            nextNode.setTranslateX(container.getWidth());\n        }\n\n        @Override\n        public Timeline animate(\n                Pane container, Node previousNode, Node nextNode,\n                Duration duration, Interpolator interpolator) {\n            return new Timeline(new KeyFrame(Duration.ZERO,\n                    new KeyValue(nextNode.translateXProperty(), container.getWidth(), interpolator),\n                    new KeyValue(previousNode.translateXProperty(), 0, interpolator)),\n                    new KeyFrame(duration,\n                            new KeyValue(nextNode.translateXProperty(), 0, interpolator),\n                            new KeyValue(previousNode.translateXProperty(), -container.getWidth(), interpolator)));\n        }\n\n        @Override\n        public TransitionPane.AnimationProducer opposite() {\n            return SWIPE_RIGHT;\n        }\n    },\n\n    /**\n     * A swipe effect\n     */\n    SWIPE_RIGHT {\n        @Override\n        public void init(TransitionPane container, Node previousNode, Node nextNode) {\n            AnimationUtils.reset(previousNode, true);\n            AnimationUtils.reset(nextNode, true);\n            nextNode.setTranslateX(-container.getWidth());\n        }\n\n        @Override\n        public Timeline animate(\n                Pane container, Node previousNode, Node nextNode,\n                Duration duration, Interpolator interpolator) {\n            return new Timeline(new KeyFrame(Duration.ZERO,\n                    new KeyValue(nextNode.translateXProperty(), -container.getWidth(), interpolator),\n                    new KeyValue(previousNode.translateXProperty(), 0, interpolator)),\n                    new KeyFrame(duration,\n                            new KeyValue(nextNode.translateXProperty(), 0, interpolator),\n                            new KeyValue(previousNode.translateXProperty(), container.getWidth(), interpolator)));\n        }\n\n        @Override\n        public TransitionPane.AnimationProducer opposite() {\n            return SWIPE_LEFT;\n        }\n    },\n\n    /// @see <a href=\"https://m3.material.io/styles/motion/transitions/transition-patterns\">Transitions - Material Design 3</a>\n    FORWARD {\n        @Override\n        public Timeline animate(\n                Pane container, Node previousNode, Node nextNode,\n                Duration duration, Interpolator interpolator) {\n            double offset = container.getWidth() > 0 ? container.getWidth() * 0.2 : 50;\n            return new Timeline(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(previousNode.translateXProperty(), 0, interpolator),\n                            new KeyValue(previousNode.opacityProperty(), 1, interpolator),\n                            new KeyValue(nextNode.opacityProperty(), 0, interpolator)),\n                    new KeyFrame(duration.multiply(0.5),\n                            new KeyValue(previousNode.translateXProperty(), -offset, interpolator),\n                            new KeyValue(previousNode.opacityProperty(), 0, interpolator),\n\n                            new KeyValue(nextNode.opacityProperty(), 0, interpolator),\n                            new KeyValue(nextNode.translateXProperty(), offset, interpolator)),\n                    new KeyFrame(duration,\n                            new KeyValue(nextNode.opacityProperty(), 1, interpolator),\n                            new KeyValue(nextNode.translateXProperty(), 0, interpolator))\n            );\n        }\n\n        @Override\n        public TransitionPane.AnimationProducer opposite() {\n            return BACKWARD;\n        }\n    },\n\n    /// @see <a href=\"https://m3.material.io/styles/motion/transitions/transition-patterns\">Transitions - Material Design 3</a>\n    BACKWARD {\n        @Override\n        public Timeline animate(\n                Pane container, Node previousNode, Node nextNode,\n                Duration duration, Interpolator interpolator) {\n            double offset = container.getWidth() > 0 ? container.getWidth() * 0.2 : 50;\n            return new Timeline(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(previousNode.translateXProperty(), 0, interpolator),\n                            new KeyValue(previousNode.opacityProperty(), 1, interpolator),\n                            new KeyValue(nextNode.opacityProperty(), 0, interpolator)),\n                    new KeyFrame(duration.multiply(0.5),\n                            new KeyValue(previousNode.translateXProperty(), offset, interpolator),\n                            new KeyValue(previousNode.opacityProperty(), 0, interpolator),\n\n                            new KeyValue(nextNode.opacityProperty(), 0, interpolator),\n                            new KeyValue(nextNode.translateXProperty(), -offset, interpolator)),\n                    new KeyFrame(duration,\n                            new KeyValue(nextNode.opacityProperty(), 1, interpolator),\n                            new KeyValue(nextNode.translateXProperty(), 0, interpolator))\n            );\n        }\n\n        @Override\n        public TransitionPane.AnimationProducer opposite() {\n            return FORWARD;\n        }\n    },\n\n    /// Imitates the animation when switching tabs in the Windows 11 Settings interface\n    SLIDE_UP_FADE_IN {\n        @Override\n        public Timeline animate(\n                Pane container, Node previousNode, Node nextNode,\n                Duration duration, Interpolator interpolator) {\n            double offset = container.getHeight() > 0 ? container.getHeight() * 0.2 : 50;\n            return new Timeline(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(previousNode.translateYProperty(), 0, interpolator),\n                            new KeyValue(previousNode.opacityProperty(), 1, interpolator),\n                            new KeyValue(nextNode.opacityProperty(), 0, interpolator),\n                            new KeyValue(nextNode.translateYProperty(), offset, interpolator)),\n                    new KeyFrame(duration.multiply(0.5),\n                            new KeyValue(previousNode.opacityProperty(), 0, interpolator)),\n                    new KeyFrame(duration,\n                            new KeyValue(nextNode.opacityProperty(), 1, interpolator),\n                            new KeyValue(nextNode.translateYProperty(), 0, interpolator))\n            );\n        }\n    },\n\n    NAVIGATION {\n        @Override\n        public Animation animate(Pane container, Node previousNode, Node nextNode, Duration duration, Interpolator interpolator) {\n            Timeline timeline = new Timeline();\n            Duration halfDuration = duration.divide(2);\n\n            timeline.getKeyFrames().add(new KeyFrame(Duration.ZERO,\n                    new KeyValue(previousNode.opacityProperty(), 1, interpolator)));\n            timeline.getKeyFrames().add(new KeyFrame(halfDuration,\n                    new KeyValue(previousNode.opacityProperty(), 0, interpolator)));\n            if (previousNode instanceof DecoratorAnimatedPage prevPage) {\n                Node left = prevPage.getLeft();\n                Node center = prevPage.getCenter();\n\n                timeline.getKeyFrames().add(new KeyFrame(Duration.ZERO,\n                        new KeyValue(left.translateXProperty(), 0, interpolator),\n                        new KeyValue(center.translateXProperty(), 0, interpolator)));\n                timeline.getKeyFrames().add(new KeyFrame(halfDuration,\n                        new KeyValue(left.translateXProperty(), -30, interpolator),\n                        new KeyValue(center.translateXProperty(), 30, interpolator)));\n            }\n\n            timeline.getKeyFrames().add(new KeyFrame(Duration.ZERO,\n                    new KeyValue(nextNode.opacityProperty(), 0, interpolator)));\n            timeline.getKeyFrames().add(new KeyFrame(halfDuration,\n                    new KeyValue(nextNode.opacityProperty(), 0, interpolator)));\n            timeline.getKeyFrames().add(new KeyFrame(duration,\n                    new KeyValue(nextNode.opacityProperty(), 1, interpolator)));\n            if (nextNode instanceof DecoratorAnimatedPage nextPage) {\n                Node left = nextPage.getLeft();\n                Node center = nextPage.getCenter();\n\n                timeline.getKeyFrames().add(new KeyFrame(halfDuration,\n                        new KeyValue(left.translateXProperty(), -30, interpolator),\n                        new KeyValue(center.translateXProperty(), 30, interpolator)));\n                timeline.getKeyFrames().add(new KeyFrame(duration,\n                        new KeyValue(left.translateXProperty(), 0, interpolator),\n                        new KeyValue(center.translateXProperty(), 0, interpolator)));\n            }\n\n            return timeline;\n        }\n    },\n    ;\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/Motion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.animation;\n\nimport javafx.animation.Interpolator;\nimport javafx.util.Duration;\n\nimport java.util.Objects;\n\n/// @author Glavo\n/// @see <a href=\"https://api.flutter.dev/flutter/animation/Curves-class.html\">Flutter Curves</a>\npublic final class Motion {\n\n    //region Curves\n\n    /// A linear animation curve.\n    ///\n    /// This is the identity map over the unit interval: its [Interpolator#curve(double)]\n    /// method returns its input unmodified. This is useful as a default curve for\n    /// cases where a [Interpolator] is required but no actual curve is desired.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4\">curve_linear.mp4</a>\n    public static final Interpolator LINEAR = Interpolator.LINEAR;\n\n    /// The emphasizedAccelerate easing curve in the Material specification.\n    ///\n    /// See also:\n    ///\n    /// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)\n    /// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)\n    public static final Interpolator EMPHASIZED_ACCELERATE = new Cubic(0.3, 0.0, 0.8, 0.15);\n\n    /// The emphasizedDecelerate easing curve in the Material specification.\n    ///\n    /// See also:\n    ///\n    /// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)\n    /// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)\n    public static final Interpolator EMPHASIZED_DECELERATE = new Cubic(0.05, 0.7, 0.1, 1.0);\n\n    /// The standard easing curve in the Material specification.\n    ///\n    /// See also:\n    ///\n    /// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)\n    /// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)\n    public static final Interpolator STANDARD = new Cubic(0.2, 0.0, 0.0, 1.0);\n\n    /// The standardAccelerate easing curve in the Material specification.\n    ///\n    /// See also:\n    ///\n    /// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)\n    /// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)\n    public static final Interpolator STANDARD_ACCELERATE = new Cubic(0.3, 0.0, 1.0, 1.0);\n\n    /// The standardDecelerate easing curve in the Material specification.\n    ///\n    /// See also:\n    ///\n    /// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)\n    /// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)\n    public static Interpolator STANDARD_DECELERATE = new Cubic(0.0, 0.0, 0.0, 1.0);\n\n    /// The legacyDecelerate easing curve in the Material specification.\n    ///\n    /// See also:\n    ///\n    /// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)\n    /// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)\n    public static Interpolator LEGACY_DECELERATE = new Cubic(0.0, 0.0, 0.2, 1.0);\n\n    /// The legacyAccelerate easing curve in the Material specification.\n    ///\n    /// See also:\n    ///\n    /// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)\n    /// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)\n    public static Interpolator LEGACY_ACCELERATE = new Cubic(0.4, 0.0, 1.0, 1.0);\n\n    /// The legacy easing curve in the Material specification.\n    ///\n    /// See also:\n    ///\n    /// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)\n    /// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)\n    public static Interpolator LEGACY = new Cubic(0.4, 0.0, 0.2, 1.0);\n\n    /// A cubic animation curve that speeds up quickly and ends slowly.\n    ///\n    /// This is the same as the CSS easing function `ease`.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4\">curve_ease.mp4</a>\n    public static final Interpolator EASE = new Cubic(0.25, 0.1, 0.25, 1.0);\n\n    /// A cubic animation curve that starts slowly and ends quickly.\n    ///\n    /// This is the same as the CSS easing function `ease-in`.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4\">curve_ease_in.mp4</a>\n    public static final Interpolator EASE_IN = new Cubic(0.42, 0.0, 1.0, 1.0);\n\n    /// A cubic animation curve that starts slowly and ends linearly.\n    ///\n    /// The symmetric animation to [#LINEAR_TO_EASE_OUT].\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_to_linear.mp4\">curve_ease_in_to_linear.mp4</a>\n    public static final Interpolator EASE_IN_TO_LINEAR = new Cubic(0.67, 0.03, 0.65, 0.09);\n\n    /// A cubic animation curve that starts slowly and ends quickly. This is\n    /// similar to [#EASE_IN], but with sinusoidal easing for a slightly less\n    /// abrupt beginning and end. Nonetheless, the result is quite gentle and is\n    /// hard to distinguish from [#linear] at a glance.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4\">curve_ease_in_sine.mp4</a>\n    public static final Interpolator EASE_IN_SINE = new Cubic(0.47, 0.0, 0.745, 0.715);\n\n    /// A cubic animation curve that starts slowly and ends quickly. Based on a\n    /// quadratic equation where `f(t) = t²`, this is effectively the inverse of\n    /// [#decelerate].\n    ///\n    /// Compared to [#EASE_IN_SINE], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4\">curve_ease_in_quad.mp4</a>\n    public static final Interpolator EASE_IN_QUAD = new Cubic(0.55, 0.085, 0.68, 0.53);\n\n    /// A cubic animation curve that starts slowly and ends quickly. This curve is\n    /// based on a cubic equation where `f(t) = t³`. The result is a safe sweet\n    /// spot when choosing a curve for widgets animating off the viewport.\n    ///\n    /// Compared to [#EASE_IN_QUAD], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4\">curve_ease_in_cubic.mp4</a>\n    public static final Interpolator EASE_IN_CUBIC = new Cubic(0.55, 0.055, 0.675, 0.19);\n\n    /// A cubic animation curve that starts slowly and ends quickly. This curve is\n    /// based on a quartic equation where `f(t) = t⁴`.\n    ///\n    /// Animations using this curve or steeper curves will benefit from a longer\n    /// duration to avoid motion feeling unnatural.\n    ///\n    /// Compared to [#EASE_IN_CUBIC], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4\">curve_ease_in_quart.mp4</a>\n    public static final Interpolator EASE_IN_QUART = new Cubic(0.895, 0.03, 0.685, 0.22);\n\n    /// A cubic animation curve that starts slowly and ends quickly. This curve is\n    /// based on a quintic equation where `f(t) = t⁵`.\n    ///\n    /// Compared to [#EASE_IN_QUART], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4\">curve_ease_in_quint.mp4</a>\n    public static final Interpolator EASE_IN_QUINT = new Cubic(0.755, 0.05, 0.855, 0.06);\n\n    /// A cubic animation curve that starts slowly and ends quickly. This curve is\n    /// based on an exponential equation where `f(t) = 2¹⁰⁽ᵗ⁻¹⁾`.\n    ///\n    /// Using this curve can give your animations extra flare, but a longer\n    /// duration may need to be used to compensate for the steepness of the curve.\n    ///\n    /// Compared to [#EASE_IN_QUINT], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4\">curve_ease_in_expo.mp4</a>\n    public static final Interpolator EASE_IN_EXPO = new Cubic(0.95, 0.05, 0.795, 0.035);\n\n    /// A cubic animation curve that starts slowly and ends quickly. This curve is\n    /// effectively the bottom-right quarter of a circle.\n    ///\n    /// Like [#EASE_IN_EXPO], this curve is fairly dramatic and will reduce\n    /// the clarity of an animation if not given a longer duration.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4\">curve_ease_in_circ.mp4</a>\n    public static final Interpolator EASE_IN_CIRC = new Cubic(0.6, 0.04, 0.98, 0.335);\n\n    /// A cubic animation curve that starts slowly and ends quickly. This curve\n    /// is similar to [#elasticIn] in that it overshoots its bounds before\n    /// reaching its end. Instead of repeated swinging motions before ascending,\n    /// though, this curve overshoots once, then continues to ascend.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4\">curve_ease_in_back.mp4</a>\n    public static final Interpolator EASE_IN_BACK = new Cubic(0.6, -0.28, 0.735, 0.045);\n\n    /// A cubic animation curve that starts quickly and ends slowly.\n    ///\n    /// This is the same as the CSS easing function `ease-out`.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4\">curve_ease_out.mp4</a>\n    public static final Interpolator EASE_OUT = new Cubic(0.0, 0.0, 0.58, 1.0);\n\n    /// A cubic animation curve that starts linearly and ends slowly.\n    ///\n    /// A symmetric animation to [#EASE_IN_TO_LINEAR].\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear_to_ease_out.mp4\">curve_linear_to_ease_out.mp4</a>\n    public static final Interpolator LINEAR_TO_EASE_OUT = new Cubic(0.35, 0.91, 0.33, 0.97);\n\n    /// A cubic animation curve that starts quickly and ends slowly. This is\n    /// similar to [#EASE_OUT], but with sinusoidal easing for a slightly\n    /// less abrupt beginning and end. Nonetheless, the result is quite gentle and\n    /// is hard to distinguish from [#linear] at a glance.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4\">curve_ease_out_sine.mp4</a>\n    public static final Interpolator EASE_OUT_SINE = new Cubic(0.39, 0.575, 0.565, 1.0);\n\n    /// A cubic animation curve that starts quickly and ends slowly. This is\n    /// effectively the same as [#decelerate], only simulated using a cubic\n    /// bezier function.\n    ///\n    /// Compared to [#EASE_OUT_SINE], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4\">curve_ease_out_quad.mp4</a>\n    public static final Interpolator EASE_OUT_QUAD = new Cubic(0.25, 0.46, 0.45, 0.94);\n\n    /// A cubic animation curve that starts quickly and ends slowly. This curve is\n    /// a flipped version of [#EASE_IN_CUBIC].\n    ///\n    /// The result is a safe sweet spot when choosing a curve for animating a\n    /// widget's position entering or already inside the viewport.\n    ///\n    /// Compared to [#EASE_OUT_QUAD], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4\">curve_ease_out_cubic.mp4</a>\n    public static final Interpolator EASE_OUT_CUBIC = new Cubic(0.215, 0.61, 0.355, 1.0);\n\n    /// A cubic animation curve that starts quickly and ends slowly. This curve is\n    /// a flipped version of [#EASE_IN_QUART].\n    ///\n    /// Animations using this curve or steeper curves will benefit from a longer\n    /// duration to avoid motion feeling unnatural.\n    ///\n    /// Compared to [#EASE_OUT_CUBIC], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4\">curve_ease_out_quart.mp4</a>\n    public static final Interpolator EASE_OUT_QUART = new Cubic(0.165, 0.84, 0.44, 1.0);\n\n    /// A cubic animation curve that starts quickly and ends slowly. This curve is\n    /// a flipped version of [#EASE_IN_QUINT].\n    ///\n    /// Compared to [#EASE_OUT_QUART], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4\">curve_ease_out_quint.mp4</a>\n    public static final Interpolator EASE_OUT_QUINT = new Cubic(0.23, 1.0, 0.32, 1.0);\n\n    /// A cubic animation curve that starts quickly and ends slowly. This curve is\n    /// a flipped version of [#EASE_IN_EXPO]. Using this curve can give your\n    /// animations extra flare, but a longer duration may need to be used to\n    /// compensate for the steepness of the curve.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4\">curve_ease_out_expo.mp4</a>\n    public static final Interpolator EASE_OUT_EXPO = new Cubic(0.19, 1.0, 0.22, 1.0);\n\n    /// A cubic animation curve that starts quickly and ends slowly. This curve is\n    /// effectively the top-left quarter of a circle.\n    ///\n    /// Like [#EASE_OUT_EXPO], this curve is fairly dramatic and will reduce\n    /// the clarity of an animation if not given a longer duration.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4\">curve_ease_out_circ.mp4</a>\n    public static final Interpolator EASE_OUT_CIRC = new Cubic(0.075, 0.82, 0.165, 1.0);\n\n    /// A cubic animation curve that starts quickly and ends slowly. This curve is\n    /// similar to [#elasticOut] in that it overshoots its bounds before\n    /// reaching its end. Instead of repeated swinging motions after ascending,\n    /// though, this curve only overshoots once.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4\">curve_ease_out_back.mp4</a>\n    public static final Interpolator EASE_OUT_BACK = new Cubic(0.175, 0.885, 0.32, 1.275);\n\n    /// A cubic animation curve that starts slowly, speeds up, and then ends\n    /// slowly.\n    ///\n    /// This is the same as the CSS easing function `ease-in-out`.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4\">curve_ease_in_out.mp4</a>\n    public static final Interpolator EASE_IN_OUT = new Cubic(0.42, 0.0, 0.58, 1.0);\n\n    /// A cubic animation curve that starts slowly, speeds up, and then ends\n    /// slowly. This is similar to [#EASE_IN_OUT], but with sinusoidal easing\n    /// for a slightly less abrupt beginning and end.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4\">curve_ease_in_out_sine.mp4</a>\n    public static final Interpolator EASE_IN_OUT_SINE = new Cubic(0.445, 0.05, 0.55, 0.95);\n\n    /// A cubic animation curve that starts slowly, speeds up, and then ends\n    /// slowly. This curve can be imagined as [#EASE_IN_QUAD] as the first\n    /// half, and [#EASE_OUT_QUAD] as the second.\n    ///\n    /// Compared to [#EASE_IN_OUT_SINE], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4\">curve_ease_in_out_quad.mp4</a>\n    public static final Interpolator EASE_IN_OUT_QUAD = new Cubic(0.455, 0.03, 0.515, 0.955);\n\n    /// A cubic animation curve that starts slowly, speeds up, and then ends\n    /// slowly. This curve can be imagined as [#EASE_IN_CUBIC] as the first\n    /// half, and [#EASE_OUT_CUBIC] as the second.\n    ///\n    /// The result is a safe sweet spot when choosing a curve for a widget whose\n    /// initial and final positions are both within the viewport.\n    ///\n    /// Compared to [#EASE_IN_OUT_QUAD], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4\">curve_ease_in_out_cubic.mp4</a>\n    public static final Interpolator EASE_IN_OUT_CUBIC = new Cubic(0.645, 0.045, 0.355, 1.0);\n\n    /// A cubic animation curve that starts slowly, speeds up, and then ends\n    /// slowly. This curve can be imagined as [#EASE_IN_QUART] as the first\n    /// half, and [#EASE_OUT_QUART] as the second.\n    ///\n    /// Animations using this curve or steeper curves will benefit from a longer\n    /// duration to avoid motion feeling unnatural.\n    ///\n    /// Compared to [#EASE_IN_OUT_CUBIC], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4\">curve_ease_in_out_quart.mp4</a>\n    public static final Interpolator EASE_IN_OUT_QUART = new Cubic(0.77, 0.0, 0.175, 1.0);\n\n    /// A cubic animation curve that starts slowly, speeds up, and then ends\n    /// slowly. This curve can be imagined as [#EASE_IN_QUINT] as the first\n    /// half, and [#EASE_OUT_QUINT] as the second.\n    ///\n    /// Compared to [#EASE_IN_OUT_QUART], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4\">curve_ease_in_out_quint.mp4</a>\n    public static final Interpolator EASE_IN_OUT_QUINT = new Cubic(0.86, 0.0, 0.07, 1.0);\n\n    /// A cubic animation curve that starts slowly, speeds up, and then ends\n    /// slowly.\n    ///\n    /// Since this curve is arrived at with an exponential function, the midpoint\n    /// is exceptionally steep. Extra consideration should be taken when designing\n    /// an animation using this.\n    ///\n    /// Compared to [#EASE_IN_OUT_QUINT], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4\">curve_ease_in_out_expo.mp4</a>\n    public static final Interpolator EASE_IN_OUT_EXPO = new Cubic(1.0, 0.0, 0.0, 1.0);\n\n    /// A cubic animation curve that starts slowly, speeds up, and then ends\n    /// slowly. This curve can be imagined as [#EASE_IN_CIRC] as the first\n    /// half, and [#EASE_OUT_CIRC] as the second.\n    ///\n    /// Like [#EASE_IN_OUT_EXPO], this curve is fairly dramatic and will reduce\n    /// the clarity of an animation if not given a longer duration.\n    ///\n    /// Compared to [#EASE_IN_OUT_EXPO], this curve is slightly steeper.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4\">curve_ease_in_out_circ.mp4</a>\n    public static final Interpolator EASE_IN_OUT_CIRC = new Cubic(0.785, 0.135, 0.15, 0.86);\n\n    /// A cubic animation curve that starts slowly, speeds up shortly thereafter,\n    /// and then ends slowly. This curve can be imagined as a steeper version of\n    /// [#EASE_IN_OUT_CUBIC].\n    ///\n    /// The result is a more emphasized eased curve when choosing a curve for a\n    /// widget whose initial and final positions are both within the viewport.\n    ///\n    /// Compared to [#EASE_IN_OUT_CUBIC], this curve is slightly steeper.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic_emphasized.mp4\">curve_ease_in_out_cubic_emphasized.mp4</a>\n    public static final Interpolator EASE_IN_OUT_CUBIC_EMPHASIZED = new ThreePointCubic(\n            new Offset(0.05, 0),\n            new Offset(0.133333, 0.06),\n            new Offset(0.166666, 0.4),\n            new Offset(0.208333, 0.82),\n            new Offset(0.25, 1)\n    );\n\n    /// A cubic animation curve that starts slowly, speeds up, and then ends\n    /// slowly. This curve can be imagined as [#EASE_IN_BACK] as the first\n    /// half, and [#EASE_OUT_BACK] as the second.\n    ///\n    /// Since two curves are used as a basis for this curve, the resulting\n    /// animation will overshoot its bounds twice before reaching its end - first\n    /// by exceeding its lower bound, then exceeding its upper bound and finally\n    /// descending to its final position.\n    ///\n    /// Derived from Robert Penner’s easing functions.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4\">curve_ease_in_out_back.mp4</a>\n    public static final Interpolator EASE_IN_OUT_BACK = new Cubic(0.68, -0.55, 0.265, 1.55);\n\n    /// A curve that starts quickly and eases into its final position.\n    ///\n    /// Over the course of the animation, the object spends more time near its\n    /// final destination. As a result, the user isn’t left waiting for the\n    /// animation to finish, and the negative effects of motion are minimized.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4\">curve_fast_out_slow_in.mp4</a>\n    public static final Interpolator FAST_OUT_SLOW_IN = new Cubic(0.4, 0.0, 0.2, 1.0);\n\n    /// A cubic animation curve that starts quickly, slows down, and then ends\n    /// quickly.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4\">curve_slow_middle.mp4</a>\n    public static final Interpolator SLOW_MIDDLE = new Cubic(0.15, 0.85, 0.85, 0.15);\n\n    private static double bounce(double t) {\n        if (t < 1.0 / 2.75) {\n            return 7.5625 * t * t;\n        } else if (t < 2 / 2.75) {\n            t -= 1.5 / 2.75;\n            return 7.5625 * t * t + 0.75;\n        } else if (t < 2.5 / 2.75) {\n            t -= 2.25 / 2.75;\n            return 7.5625 * t * t + 0.9375;\n        }\n        t -= 2.625 / 2.75;\n        return 7.5625 * t * t + 0.984375;\n    }\n\n    /// An oscillating curve that grows in magnitude.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4\">curve_bounce_in.mp4</a>\n\n    public static final Interpolator BOUNCE_IN = new Interpolator() {\n        @Override\n        protected double curve(double t) {\n            return 1.0 - bounce(1.0 - t);\n        }\n    };\n\n    /// An oscillating curve that first grows and then shrink in magnitude.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4\">curve_bounce_out.mp4</a>\n    public static final Interpolator BOUNCE_OUT = new Interpolator() {\n        @Override\n        protected double curve(double t) {\n            return bounce(t);\n        }\n    };\n\n    /// An oscillating curve that first grows and then shrink in magnitude.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4\">curve_bounce_in_out.mp4</a>\n    public static final Interpolator BOUNCE_IN_OUT = new Interpolator() {\n        @Override\n        protected double curve(double t) {\n            if (t < 0.5) {\n                return (1.0 - bounce(1.0 - t * 2.0)) * 0.5;\n            } else {\n                return bounce(t * 2.0 - 1.0) * 0.5 + 0.5;\n            }\n        }\n    };\n\n    private static final double PERIOD = 0.4;\n\n    /// An oscillating curve that grows in magnitude while overshooting its bounds.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4\">curve_elastic_in.mp4</a>\n    public static final Interpolator ELASTIC_IN = new Interpolator() {\n        @Override\n        protected double curve(double t) {\n            final double s = PERIOD / 4.0;\n            t = t - 1.0;\n            return -Math.pow(2.0, 10.0 * t) * Math.sin((t - s) * (Math.PI * 2.0) / PERIOD);\n        }\n    };\n\n    /// An oscillating curve that shrinks in magnitude while overshooting its bounds.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4\">curve_elastic_out.mp4</a>\n    public static Interpolator ELASTIC_OUT = new Interpolator() {\n        @Override\n        protected double curve(double t) {\n            final double s = PERIOD / 4.0;\n            return Math.pow(2.0, -10 * t) * Math.sin((t - s) * (Math.PI * 2.0) / PERIOD) + 1.0;\n        }\n    };\n\n    /// An oscillating curve that grows and then shrinks in magnitude while overshooting its bounds.\n    ///\n    /// @see <a href=\"https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4\">curve_elastic_in_out.mp4</a>\n    public static Interpolator ELASTIC_IN_OUT = new Interpolator() {\n        @Override\n        @SuppressWarnings(\"DuplicateExpressions\")\n        protected double curve(double t) {\n            final double s = PERIOD / 4.0;\n            t = 2.0 * t - 1.0;\n            if (t < 0.0) {\n                return -0.5 * Math.pow(2.0, 10.0 * t) * Math.sin((t - s) * (Math.PI * 2.0) / PERIOD);\n            } else {\n                return Math.pow(2.0, -10.0 * t) * Math.sin((t - s) * (Math.PI * 2.0) / PERIOD) * 0.5 + 1.0;\n            }\n        }\n    };\n\n    /// A cubic polynomial mapping of the unit interval.\n    private static final class Cubic extends Interpolator {\n        private static final double CUBIC_ERROR_BOUND = 0.001;\n\n        /// The x coordinate of the first control point.\n        ///\n        /// The line through the point (0, 0) and the first control point is tangent\n        /// to the curve at the point (0, 0).\n        private final double a;\n\n        /// The y coordinate of the first control point.\n        ///\n        /// The line through the point (0, 0) and the first control point is tangent\n        /// to the curve at the point (0, 0).\n        private final double b;\n\n        /// The x coordinate of the second control point.\n        ///\n        /// The line through the point (1, 1) and the second control point is tangent\n        /// to the curve at the point (1, 1).\n        private final double c;\n\n        /// The y coordinate of the second control point.\n        ///\n        /// The line through the point (1, 1) and the second control point is tangent\n        /// to the curve at the point (1, 1).\n        private final double d;\n\n        private Cubic(double a, double b, double c, double d) {\n            this.a = a;\n            this.b = b;\n            this.c = c;\n            this.d = d;\n        }\n\n        double _evaluateCubic(double a, double b, double m) {\n            return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1 - m) * m * m + m * m * m;\n        }\n\n        @Override\n        protected double curve(double t) {\n            double start = 0.0;\n            double end = 1.0;\n            while (true) {\n                final double midpoint = (start + end) / 2;\n                final double estimate = _evaluateCubic(a, c, midpoint);\n                if (Math.abs(t - estimate) < CUBIC_ERROR_BOUND) {\n                    return _evaluateCubic(b, d, midpoint);\n                }\n                if (estimate < t) {\n                    start = midpoint;\n                } else {\n                    end = midpoint;\n                }\n            }\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            return o instanceof Cubic cubic\n                    && this.a == cubic.a\n                    && this.b == cubic.b\n                    && this.c == cubic.c\n                    && this.d == cubic.d;\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(a, b, c, d);\n        }\n\n        @Override\n        public String toString() {\n            return \"Cubic[a=%s, b=%s, c=%s, d=%s]\".formatted(a, b, c, d);\n        }\n    }\n\n    private record Offset(double dx, double dy) {\n    }\n\n    private static final class ThreePointCubic extends Interpolator {\n\n        /// The coordinates of the first control point of the first curve.\n        ///\n        /// The line through the point (0, 0) and this control point is tangent to the\n        /// curve at the point (0, 0).\n        private final Offset a1;\n\n        /// The coordinates of the second control point of the first curve.\n        ///\n        /// The line through the [#midpoint] and this control point is tangent to the\n        /// curve approaching the [#midpoint].\n        private final Offset b1;\n\n        /// The coordinates of the middle shared point.\n        ///\n        /// The curve will go through this point. If the control points surrounding\n        /// this middle point ([#b1], and [#a2]) are not colinear with this point, then\n        /// the curve's derivative will have a discontinuity (a cusp) at this point.\n        private final Offset midpoint;\n\n        /// The coordinates of the first control point of the second curve.\n        ///\n        /// The line through the [#midpoint] and this control point is tangent to the\n        /// curve approaching the [#midpoint].\n        private final Offset a2;\n\n        /// The coordinates of the second control point of the second curve.\n        ///\n        /// The line through the point (1, 1) and this control point is tangent to the\n        /// curve at (1, 1).\n        private final Offset b2;\n\n        /// Creates two cubic curves that share a common control point.\n        ///\n        /// Rather than creating a new instance, consider using one of the common\n        /// three-point cubic curves in [Interpolator].\n        ///\n        /// The arguments correspond to the control points for the two curves,\n        /// including the [#midpoint], but do not include the two implied end points at\n        /// (0,0) and (1,1), which are fixed.\n        private ThreePointCubic(Offset a1, Offset b1, Offset midpoint, Offset a2, Offset b2) {\n            this.a1 = a1;\n            this.b1 = b1;\n            this.midpoint = midpoint;\n            this.a2 = a2;\n            this.b2 = b2;\n        }\n\n        @Override\n        protected double curve(double t) {\n            final boolean firstCurve = t < midpoint.dx;\n            final double scaleX = firstCurve ? midpoint.dx : 1.0 - midpoint.dx;\n            final double scaleY = firstCurve ? midpoint.dy : 1.0 - midpoint.dy;\n            final double scaledT = (t - (firstCurve ? 0.0 : midpoint.dx)) / scaleX;\n            if (firstCurve) {\n                return new Cubic(\n                        a1.dx / scaleX,\n                        a1.dy / scaleY,\n                        b1.dx / scaleX,\n                        b1.dy / scaleY\n                ).curve(scaledT) *\n                        scaleY;\n            } else {\n                return new Cubic(\n                        (a2.dx - midpoint.dx) / scaleX,\n                        (a2.dy - midpoint.dy) / scaleY,\n                        (b2.dx - midpoint.dx) / scaleX,\n                        (b2.dy - midpoint.dy) / scaleY\n                ).curve(scaledT) *\n                        scaleY +\n                        midpoint.dy;\n            }\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            return o instanceof ThreePointCubic that\n                    && a1.equals(that.a1)\n                    && b1.equals(that.b1)\n                    && midpoint.equals(that.midpoint)\n                    && a2.equals(that.a2)\n                    && b2.equals(that.b2);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(a1, b1, midpoint, a2, b2);\n        }\n\n        @Override\n        public String toString() {\n            return \"ThreePointCubic[a1=%s, b1=%s, midpoint=%s, a2=%s, b2=%s]\".formatted(a1, b1, midpoint, a2, b2);\n        }\n    }\n\n    //endregion Curves\n\n    // region Durations\n\n    /// The short1 duration (50ms) in the Material specification.\n    public static final Duration SHORT1 = Duration.millis(50);\n\n    /// The short2 duration (100ms) in the Material specification.\n    public static final Duration SHORT2 = Duration.millis(100);\n\n    /// The short3 duration (150ms) in the Material specification.\n    public static final Duration SHORT3 = Duration.millis(150);\n\n    /// The short4 duration (200ms) in the Material specification.\n    public static final Duration SHORT4 = Duration.millis(200);\n\n    /// The medium1 duration (250ms) in the Material specification.\n    public static final Duration MEDIUM1 = Duration.millis(250);\n\n    /// The medium2 duration (300ms) in the Material specification.\n    public static final Duration MEDIUM2 = Duration.millis(300);\n\n    /// The medium3 duration (350ms) in the Material specification.\n    public static final Duration MEDIUM3 = Duration.millis(350);\n\n    /// The medium4 duration (400ms) in the Material specification.\n    public static final Duration MEDIUM4 = Duration.millis(400);\n\n    /// The long1 duration (450ms) in the Material specification.\n    public static final Duration LONG1 = Duration.millis(450);\n\n    /// The long2 duration (500ms) in the Material specification.\n    public static final Duration LONG2 = Duration.millis(500);\n\n    /// The long3 duration (550ms) in the Material specification.\n    public static final Duration LONG3 = Duration.millis(550);\n\n    /// The long4 duration (600ms) in the Material specification.\n    public static final Duration LONG4 = Duration.millis(600);\n\n    /// The extralong1 duration (700ms) in the Material specification.\n    public static final Duration EXTRA_LONG1 = Duration.millis(700);\n\n    /// The extralong2 duration (800ms) in the Material specification.\n    public static final Duration EXTRA_LONG2 = Duration.millis(800);\n\n    /// The extralong3 duration (900ms) in the Material specification.\n    public static final Duration EXTRA_LONG3 = Duration.millis(900);\n\n    /// The extralong4 duration (1000ms) in the Material specification.\n    public static final Duration EXTRA_LONG4 = Duration.millis(1000);\n\n    // endregion Durations\n\n    private Motion() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.animation;\n\nimport javafx.animation.Animation;\nimport javafx.animation.Interpolator;\nimport javafx.application.Platform;\nimport javafx.scene.CacheHint;\nimport javafx.scene.Node;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.layout.StackPane;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jetbrains.annotations.Nullable;\n\npublic class TransitionPane extends StackPane {\n\n    private Node currentNode;\n\n    public TransitionPane() {\n        FXUtils.setOverflowHidden(this);\n    }\n\n    public Node getCurrentNode() {\n        return currentNode;\n    }\n\n    public final void setContent(Node newView, AnimationProducer transition) {\n        setContent(newView, transition, Motion.SHORT4);\n    }\n\n    public final void setContent(Node newView, AnimationProducer transition, Duration duration) {\n        setContent(newView, transition, duration, Motion.EASE);\n    }\n\n    public void setContent(Node newView, AnimationProducer transition,\n                           Duration duration, Interpolator interpolator) {\n        Node previousNode = currentNode != newView && getWidth() > 0 && getHeight() > 0 ? currentNode : null;\n        currentNode = newView;\n\n        if (!AnimationUtils.isAnimationEnabled() || previousNode == null || transition == ContainerAnimations.NONE) {\n            AnimationUtils.reset(newView, true);\n            getChildren().setAll(newView);\n            return;\n        }\n\n        getChildren().setAll(previousNode, newView);\n\n        setMouseTransparent(true);\n        transition.init(this, previousNode, newView);\n\n        CacheHint cacheHint = newView instanceof Cacheable cacheable\n                ? cacheable.getCacheHint(transition)\n                : null;\n\n        if (cacheHint != null) {\n            newView.setCache(true);\n            newView.setCacheHint(cacheHint);\n        }\n\n        // runLater or \"init\" will not work\n        Platform.runLater(() -> {\n            Animation newAnimation = transition.animate(\n                    this,\n                    previousNode,\n                    newView,\n                    duration, interpolator);\n            newAnimation.setOnFinished(e -> {\n                setMouseTransparent(false);\n                if (previousNode != currentNode) {\n                    getChildren().remove(previousNode);\n                }\n\n                if (cacheHint != null) {\n                    newView.setCache(false);\n                }\n            });\n            FXUtils.playAnimation(this, \"transition_pane\", newAnimation);\n        });\n\n    }\n\n    public interface AnimationProducer {\n        default void init(TransitionPane container, Node previousNode, Node nextNode) {\n            AnimationUtils.reset(previousNode, true);\n            AnimationUtils.reset(nextNode, false);\n        }\n\n        Animation animate(Pane container, Node previousNode, Node nextNode,\n                          Duration duration, Interpolator interpolator);\n\n        default @Nullable TransitionPane.AnimationProducer opposite() {\n            return null;\n        }\n    }\n\n    /// Marks a node as cacheable as a bitmap during animation.\n    public interface Cacheable {\n        /// @return the [cache hint][CacheHint] to use when caching this node during the given animation,\n        ///         or `null` to not cache it.\n        default @Nullable CacheHint getCacheHint(AnimationProducer animationProducer) {\n            // https://github.com/HMCL-dev/HMCL/issues/4789\n            return animationProducer == ContainerAnimations.SLIDE_UP_FADE_IN ? CacheHint.SPEED : null;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.SVGContainer;\nimport org.jackhuang.hmcl.ui.animation.Motion;\n\nimport java.util.function.Consumer;\n\npublic class AdvancedListBox extends ScrollPane {\n    private final VBox container = new VBox();\n\n    {\n        setContent(container);\n\n        FXUtils.smoothScrolling(this);\n\n        setFitToHeight(true);\n        setFitToWidth(true);\n        setHbarPolicy(ScrollBarPolicy.NEVER);\n        setVbarPolicy(ScrollBarPolicy.NEVER);\n\n        container.getStyleClass().add(\"advanced-list-box-content\");\n\n        this.addEventFilter(MouseEvent.MOUSE_ENTERED, event -> {\n            if (container.getHeight() > getHeight())\n                setVbarPolicy(ScrollBarPolicy.AS_NEEDED);\n        });\n        this.addEventFilter(MouseEvent.MOUSE_EXITED,\n                event -> setVbarPolicy(ScrollBarPolicy.NEVER));\n    }\n\n    public AdvancedListBox add(Node child) {\n        if (child instanceof Pane || child instanceof AdvancedListItem)\n            container.getChildren().add(child);\n        else {\n            StackPane pane = new StackPane();\n            pane.getStyleClass().add(\"advanced-list-box-item\");\n            pane.getChildren().setAll(child);\n            container.getChildren().add(pane);\n        }\n        return this;\n    }\n\n    private AdvancedListItem createNavigationDrawerItem(String title, SVG leftGraphic) {\n        AdvancedListItem item = new AdvancedListItem();\n        item.getStyleClass().add(\"navigation-drawer-item\");\n        item.setTitle(title);\n        if (leftGraphic != null) {\n            item.setLeftIcon(leftGraphic);\n        }\n        return item;\n    }\n\n    public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction) {\n        return addNavigationDrawerItem(title, leftGraphic, onAction, null);\n    }\n\n    public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction, Consumer<AdvancedListItem> initializer) {\n        AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);\n        if (onAction != null) {\n            item.setOnAction(e -> onAction.run());\n        }\n        if (initializer != null) {\n            initializer.accept(item);\n        }\n        return add(item);\n    }\n\n    public AdvancedListBox addNavigationDrawerTab(TabHeader tabHeader, TabControl.Tab<?> tab, String title, SVG leftGraphic) {\n        AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);\n        item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));\n        item.setOnAction(e -> tabHeader.select(tab));\n        return add(item);\n    }\n\n    public AdvancedListBox addNavigationDrawerTab(TabHeader tabHeader, TabControl.Tab<?> tab, String title,\n                                                  SVG unselectedGraphic, SVG selectedGraphic) {\n        AdvancedListItem item = createNavigationDrawerItem(title, null);\n        item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));\n        item.setOnAction(e -> tabHeader.select(tab));\n\n        var leftGraphic = new SVGContainer(item.isActive() ? selectedGraphic : unselectedGraphic, AdvancedListItem.LEFT_ICON_SIZE);\n        leftGraphic.setMouseTransparent(true);\n        AdvancedListItem.setAlignment(leftGraphic, Pos.CENTER);\n        AdvancedListItem.setMargin(leftGraphic, AdvancedListItem.LEFT_ICON_MARGIN);\n        FXUtils.onChange(item.activeProperty(), active -> leftGraphic.setIcon(active ? selectedGraphic : unselectedGraphic, Motion.SHORT4));\n        item.setLeftGraphic(leftGraphic);\n        return add(item);\n    }\n\n    public AdvancedListBox add(int index, Node child) {\n        if (child instanceof Pane || child instanceof AdvancedListItem)\n            container.getChildren().add(index, child);\n        else {\n            StackPane pane = new StackPane();\n            pane.getStyleClass().add(\"advanced-list-box-item\");\n            pane.getChildren().setAll(child);\n            container.getChildren().add(index, pane);\n        }\n        return this;\n    }\n\n    public AdvancedListBox remove(Node child) {\n        container.getChildren().remove(indexOf(child));\n        return this;\n    }\n\n    public int indexOf(Node child) {\n        if (child instanceof Pane) {\n            return container.getChildren().indexOf(child);\n        } else {\n            for (int i = 0; i < container.getChildren().size(); ++i) {\n                Node node = container.getChildren().get(i);\n                if (node instanceof StackPane) {\n                    ObservableList<Node> list = ((StackPane) node).getChildren();\n                    if (list.size() == 1 && list.get(0) == child)\n                        return i;\n                }\n            }\n            return -1;\n        }\n    }\n\n    public AdvancedListBox startCategory(String category) {\n        return add(new ClassTitle(category));\n    }\n\n    public void setSpacing(double spacing) {\n        container.setSpacing(spacing);\n    }\n\n    public void clear() {\n        container.getChildren().clear();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.*;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Skin;\nimport javafx.scene.layout.BorderPane;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\n\npublic class AdvancedListItem extends Control {\n    public static final double LEFT_GRAPHIC_SIZE = 32;\n    public static final double LEFT_ICON_SIZE = 20;\n    public static final Insets LEFT_ICON_MARGIN = new Insets(0, 6, 0, 6);\n\n    public static void setMargin(Node graphic, Insets margin) {\n        BorderPane.setMargin(graphic, margin);\n    }\n\n    public static void setAlignment(Node graphic, Pos alignment) {\n        BorderPane.setAlignment(graphic, alignment);\n    }\n\n    public AdvancedListItem() {\n        getStyleClass().add(\"advanced-list-item\");\n        FXUtils.onClicked(this, () -> fireEvent(new ActionEvent()));\n    }\n\n    private final ObjectProperty<Node> leftGraphic = new SimpleObjectProperty<>(this, \"leftGraphic\");\n\n    public ObjectProperty<Node> leftGraphicProperty() {\n        return leftGraphic;\n    }\n\n    public Node getLeftGraphic() {\n        return leftGraphic.get();\n    }\n\n    public void setLeftGraphic(Node leftGraphic) {\n        this.leftGraphic.set(leftGraphic);\n    }\n\n    public void setLeftIcon(SVG svg) {\n        Node icon = svg.createIcon(LEFT_ICON_SIZE);\n        icon.setMouseTransparent(true);\n        BorderPane.setMargin(icon, LEFT_ICON_MARGIN);\n        BorderPane.setAlignment(icon, Pos.CENTER);\n        leftGraphicProperty().set(icon);\n    }\n\n    private final ObjectProperty<Node> rightGraphic = new SimpleObjectProperty<>(this, \"rightGraphic\");\n\n    public ObjectProperty<Node> rightGraphicProperty() {\n        return rightGraphic;\n    }\n\n    public Node getRightGraphic() {\n        return rightGraphic.get();\n    }\n\n    public void setRightGraphic(Node rightGraphic) {\n        this.rightGraphic.set(rightGraphic);\n    }\n\n    public void setRightAction(SVG icon, Runnable action) {\n        var button = FXUtils.newToggleButton4(icon, 14);\n        button.setOnAction(e -> {\n            action.run();\n            e.consume();\n        });\n        setAlignment(button, Pos.CENTER);\n        setRightGraphic(button);\n    }\n\n    private final StringProperty title = new SimpleStringProperty(this, \"title\");\n\n    public StringProperty titleProperty() {\n        return title;\n    }\n\n    public String getTitle() {\n        return title.get();\n    }\n\n    public void setTitle(String title) {\n        this.title.set(title);\n    }\n\n    private final StringProperty subtitle = new SimpleStringProperty(this, \"subtitle\");\n\n    public StringProperty subtitleProperty() {\n        return subtitle;\n    }\n\n    public String getSubtitle() {\n        return subtitle.get();\n    }\n\n    public void setSubtitle(String subtitle) {\n        this.subtitle.set(subtitle);\n    }\n\n    private final BooleanProperty active = new SimpleBooleanProperty(this, \"active\");\n\n    public BooleanProperty activeProperty() {\n        return active;\n    }\n\n    public boolean isActive() {\n        return active.get();\n    }\n\n    public void setActive(boolean active) {\n        this.active.set(active);\n    }\n\n    private final ObjectProperty<EventHandler<ActionEvent>> onAction = new SimpleObjectProperty<>(this, \"onAction\") {\n        @Override\n        protected void invalidated() {\n            setEventHandler(ActionEvent.ACTION, get());\n        }\n    };\n\n    public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {\n        return onAction;\n    }\n\n    public final void setOnAction(EventHandler<ActionEvent> value) {\n        onActionProperty().set(value);\n    }\n\n    public final EventHandler<ActionEvent> getOnAction() {\n        return onActionProperty().get();\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new AdvancedListItemSkin(this);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItemSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.css.PseudoClass;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.BorderPane;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {\n    private static final PseudoClass SELECTED = PseudoClass.getPseudoClass(\"selected\");\n\n    public AdvancedListItemSkin(AdvancedListItem skinnable) {\n        super(skinnable);\n\n        FXUtils.onChangeAndOperate(skinnable.activeProperty(), active -> {\n            skinnable.pseudoClassStateChanged(SELECTED, active);\n        });\n\n        BorderPane root = new BorderPane();\n        root.getStyleClass().add(\"container\");\n        root.setPickOnBounds(false);\n\n        RipplerContainer container = new RipplerContainer(root);\n\n        TwoLineListItem item = new TwoLineListItem();\n        root.setCenter(item);\n        item.setMouseTransparent(true);\n        item.titleProperty().bind(skinnable.titleProperty());\n        item.subtitleProperty().bind(skinnable.subtitleProperty());\n\n        root.leftProperty().bind(skinnable.leftGraphicProperty());\n        root.rightProperty().bind(skinnable.rightGraphicProperty());\n\n        getChildren().setAll(container);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.scene.Node;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.shape.Rectangle;\nimport javafx.scene.text.Text;\nimport org.jackhuang.hmcl.util.Lang;\n\n/**\n * @author huangyuhui\n */\npublic class ClassTitle extends StackPane {\n    private final Node content;\n\n    public ClassTitle(String text) {\n        this(new Text(text));\n    }\n\n    public ClassTitle(Node content) {\n        this.content = content;\n\n        VBox vbox = new VBox();\n        vbox.getChildren().addAll(content);\n        Rectangle rectangle = new Rectangle();\n        rectangle.widthProperty().bind(vbox.widthProperty());\n        rectangle.setHeight(1.0);\n        vbox.getChildren().add(rectangle);\n        getChildren().setAll(vbox);\n        getStyleClass().add(\"class-title\");\n    }\n\n    public ClassTitle(String text, Node rightNode) {\n        this(Lang.apply(new BorderPane(), borderPane -> {\n            borderPane.setLeft(Lang.apply(new VBox(), vBox -> vBox.getChildren().setAll(new Text(text))));\n            borderPane.setRight(rightNode);\n        }));\n    }\n\n    public Node getContent() {\n        return content;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Orientation;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.util.javafx.MappedObservableList;\n\npublic class ComponentList extends Control implements NoPaddingComponent {\n\n    public ComponentList() {\n        getStyleClass().add(\"options-list\");\n    }\n\n    private final ObservableList<Node> content = FXCollections.observableArrayList();\n\n    public ObservableList<Node> getContent() {\n        return content;\n    }\n\n    @Override\n    public Orientation getContentBias() {\n        return Orientation.HORIZONTAL;\n    }\n\n    @Override\n    protected javafx.scene.control.Skin<?> createDefaultSkin() {\n        return new Skin(this);\n    }\n\n    private static final class Skin extends ControlSkinBase<ComponentList> {\n        private static final PseudoClass PSEUDO_CLASS_FIRST = PseudoClass.getPseudoClass(\"first\");\n        private static final PseudoClass PSEUDO_CLASS_LAST = PseudoClass.getPseudoClass(\"last\");\n\n        private final ObservableList<Node> list;\n\n        Skin(ComponentList control) {\n            super(control);\n\n            list = MappedObservableList.create(control.getContent(), node -> {\n                Pane wrapper;\n                if (node instanceof ComponentSublist sublist) {\n                    sublist.getStyleClass().remove(\"options-list\");\n                    sublist.getStyleClass().add(\"options-sublist\");\n                    wrapper = new ComponentSublistWrapper(sublist);\n                } else {\n                    wrapper = new StackPane(node);\n                }\n\n                wrapper.getStyleClass().add(\"options-list-item\");\n\n                if (node.getProperties().get(\"ComponentList.vgrow\") instanceof Priority priority) {\n                    VBox.setVgrow(wrapper, priority);\n                }\n\n                if (node instanceof NoPaddingComponent || node.getProperties().containsKey(\"ComponentList.noPadding\")) {\n                    wrapper.getStyleClass().add(\"no-padding\");\n                }\n                return wrapper;\n            });\n\n            updateStyle();\n            list.addListener((InvalidationListener) o -> updateStyle());\n\n            VBox vbox = new VBox();\n            vbox.setFillWidth(true);\n            Bindings.bindContent(vbox.getChildren(), list);\n            node = vbox;\n        }\n\n        private Node prevFirstItem;\n        private Node prevLastItem;\n\n        private void updateStyle() {\n            Node firstItem;\n            Node lastItem;\n\n            if (list.isEmpty()) {\n                firstItem = null;\n                lastItem = null;\n            } else {\n                firstItem = list.get(0);\n                lastItem = list.get(list.size() - 1);\n            }\n\n            if (firstItem != prevFirstItem) {\n                if (prevFirstItem != null)\n                    prevFirstItem.pseudoClassStateChanged(PSEUDO_CLASS_FIRST, false);\n                if (firstItem != null)\n                    firstItem.pseudoClassStateChanged(PSEUDO_CLASS_FIRST, true);\n                prevFirstItem = firstItem;\n            }\n\n            if (lastItem != prevLastItem) {\n                if (prevLastItem != null)\n                    prevLastItem.pseudoClassStateChanged(PSEUDO_CLASS_LAST, false);\n                if (lastItem != null)\n                    lastItem.pseudoClassStateChanged(PSEUDO_CLASS_LAST, true);\n                prevLastItem = lastItem;\n            }\n        }\n    }\n\n    public static Node createComponentListTitle(String title) {\n        HBox node = new HBox();\n        node.setAlignment(Pos.CENTER_LEFT);\n        node.setPadding(new Insets(8, 0, 0, 0));\n        {\n            Label advanced = new Label(title);\n            node.getChildren().setAll(advanced);\n        }\n        return node;\n    }\n\n    public static void setVgrow(Node node, Priority priority) {\n        node.getProperties().put(\"ComponentList.vgrow\", priority);\n    }\n\n    public static void setNoPadding(Node node) {\n        node.getProperties().put(\"ComponentList.noPadding\", true);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentSublist.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.*;\nimport javafx.scene.Node;\n\nimport java.util.List;\nimport java.util.function.Supplier;\n\npublic class ComponentSublist extends ComponentList {\n\n    Supplier<List<? extends Node>> lazyInitializer;\n\n    public ComponentSublist() {\n        super();\n    }\n\n    public ComponentSublist(Supplier<List<? extends Node>> lazyInitializer) {\n        this.lazyInitializer = lazyInitializer;\n    }\n\n    void doLazyInit() {\n        if (lazyInitializer != null) {\n            this.getContent().setAll(lazyInitializer.get());\n            setNeedsLayout(true);\n            lazyInitializer = null;\n        }\n    }\n\n    private final StringProperty title = new SimpleStringProperty(this, \"title\", \"Group\");\n\n    public StringProperty titleProperty() {\n        return title;\n    }\n\n    public String getTitle() {\n        return titleProperty().get();\n    }\n\n    public void setTitle(String title) {\n        titleProperty().set(title);\n    }\n\n    private StringProperty subtitle;\n\n    public StringProperty subtitleProperty() {\n        if (subtitle == null)\n            subtitle = new SimpleStringProperty(this, \"subtitle\", \"\");\n\n        return subtitle;\n    }\n\n    public String getSubtitle() {\n        return subtitleProperty().get();\n    }\n\n    public void setSubtitle(String subtitle) {\n        subtitleProperty().set(subtitle);\n    }\n\n    private boolean hasSubtitle = false;\n\n    public boolean isHasSubtitle() {\n        return hasSubtitle;\n    }\n\n    public void setHasSubtitle(boolean hasSubtitle) {\n        this.hasSubtitle = hasSubtitle;\n    }\n\n    private Node headerLeft;\n\n    public Node getHeaderLeft() {\n        return headerLeft;\n    }\n\n    public void setHeaderLeft(Node headerLeft) {\n        this.headerLeft = headerLeft;\n    }\n\n    private Node headerRight;\n\n    public Node getHeaderRight() {\n        return headerRight;\n    }\n\n    public void setHeaderRight(Node headerRight) {\n        this.headerRight = headerRight;\n    }\n\n    private boolean componentPadding = true;\n\n    public boolean hasComponentPadding() {\n        return componentPadding;\n    }\n\n    public void setComponentPadding(boolean componentPadding) {\n        this.componentPadding = componentPadding;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentSublistWrapper.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.animation.*;\nimport javafx.application.Platform;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.ui.animation.Motion;\n\n/// @author Glavo\nfinal class ComponentSublistWrapper extends VBox implements NoPaddingComponent {\n    private VBox container;\n\n    private Animation expandAnimation;\n    private boolean expanded = false;\n\n    ComponentSublistWrapper(ComponentSublist sublist) {\n        boolean noPadding = !sublist.hasComponentPadding();\n\n        this.getStyleClass().add(\"options-sublist-wrapper\");\n\n        Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(20);\n        expandIcon.getStyleClass().add(\"expand-icon\");\n        expandIcon.setMouseTransparent(true);\n\n        VBox labelVBox = new VBox();\n        labelVBox.setMouseTransparent(true);\n        labelVBox.setAlignment(Pos.CENTER_LEFT);\n\n        Node leftNode = sublist.getHeaderLeft();\n        if (leftNode == null) {\n            Label label = new Label();\n            label.textProperty().bind(sublist.titleProperty());\n            label.getStyleClass().add(\"title-label\");\n            labelVBox.getChildren().add(label);\n\n            if (sublist.isHasSubtitle()) {\n                Label subtitleLabel = new Label();\n                subtitleLabel.textProperty().bind(sublist.subtitleProperty());\n                subtitleLabel.getStyleClass().add(\"subtitle-label\");\n                subtitleLabel.textFillProperty().bind(Themes.colorSchemeProperty().getOnSurfaceVariant());\n                labelVBox.getChildren().add(subtitleLabel);\n            }\n        } else {\n            labelVBox.getChildren().setAll(leftNode);\n        }\n\n        HBox header = new HBox();\n        header.setSpacing(12);\n        header.getChildren().add(labelVBox);\n        header.setPadding(new Insets(10, 16, 10, 16));\n        header.setAlignment(Pos.CENTER_LEFT);\n        HBox.setHgrow(labelVBox, Priority.ALWAYS);\n        Node rightNode = sublist.getHeaderRight();\n        if (rightNode != null)\n            header.getChildren().add(rightNode);\n        header.getChildren().add(expandIcon);\n\n        RipplerContainer headerRippler = new RipplerContainer(header);\n        this.getChildren().add(headerRippler);\n\n        headerRippler.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {\n            if (event.getButton() != MouseButton.PRIMARY)\n                return;\n\n            event.consume();\n\n            if (expandAnimation != null && expandAnimation.getStatus() == Animation.Status.RUNNING) {\n                expandAnimation.stop();\n            }\n\n            boolean expanded = !this.expanded;\n            this.expanded = expanded;\n            if (expanded) {\n                sublist.doLazyInit();\n\n                if (container == null) {\n                    this.container = new VBox();\n\n                    if (!noPadding) {\n                        container.setPadding(new Insets(8, 16, 10, 16));\n                    }\n                    FXUtils.setLimitHeight(container, 0);\n                    FXUtils.setOverflowHidden(container);\n                    container.getChildren().setAll(sublist);\n                    ComponentSublistWrapper.this.getChildren().add(container);\n\n                    this.applyCss();\n                }\n\n                this.layout();\n            }\n\n            Platform.runLater(() -> {\n                // FIXME: ComponentSubList without padding must have a 4 pixel padding for displaying a border radius.\n                double contentHeight = expanded ? (sublist.prefHeight(sublist.getWidth()) + (noPadding ? 4 : 8 + 10)) : 0;\n                double targetRotate = expanded ? -180 : 0;\n\n                if (AnimationUtils.isAnimationEnabled()) {\n                    double currentRotate = expandIcon.getRotate();\n                    Duration duration = Motion.LONG2.multiply(Math.abs(currentRotate - targetRotate) / 180.0);\n                    Interpolator interpolator = Motion.EASE_IN_OUT_CUBIC_EMPHASIZED;\n\n                    expandAnimation = new Timeline(\n                            new KeyFrame(duration,\n                                    new KeyValue(container.minHeightProperty(), contentHeight, interpolator),\n                                    new KeyValue(container.maxHeightProperty(), contentHeight, interpolator),\n                                    new KeyValue(expandIcon.rotateProperty(), targetRotate, interpolator))\n                    );\n\n                    expandAnimation.play();\n                } else {\n                    container.setMinHeight(contentHeight);\n                    container.setMaxHeight(contentHeight);\n                    expandIcon.setRotate(targetRotate);\n                }\n            });\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ControlSkinBase.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Skin;\n\nimport java.util.Objects;\n\npublic abstract class ControlSkinBase<C extends Control> implements Skin<C> {\n    private final C control;\n\n    protected Node node;\n\n    /**\n     * Constructor for all SkinBase instances.\n     *\n     * @param control The control for which this Skin should attach to.\n     */\n    protected ControlSkinBase(C control) {\n        this.control = control;\n    }\n\n    @Override\n    public C getSkinnable() {\n        return control;\n    }\n\n    @Override\n    public Node getNode() {\n        Objects.requireNonNull(node);\n        return node;\n    }\n\n    @Override\n    public void dispose() {\n\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/DialogAware.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport org.jackhuang.hmcl.ui.Controllers;\n\n/**\n * @author yushijinhun\n * @see Controllers#dialog(javafx.scene.layout.Region)\n */\npublic interface DialogAware {\n\n    default void onDialogShown() {\n    }\n\n    default void onDialogClosed() {\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/DialogCloseEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.event.Event;\nimport javafx.event.EventTarget;\nimport javafx.event.EventType;\nimport javafx.scene.layout.Region;\nimport org.jackhuang.hmcl.ui.Controllers;\n\n/**\n * Indicates a close operation on the dialog.\n *\n * @author yushijinhun\n * @see Controllers#dialog(Region)\n */\npublic class DialogCloseEvent extends Event {\n\n    public static final EventType<DialogCloseEvent> CLOSE = new EventType<>(\"DIALOG_CLOSE\");\n\n    public DialogCloseEvent() {\n        super(CLOSE);\n    }\n\n    public DialogCloseEvent(Object source, EventTarget target) {\n        super(source, target, CLOSE);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/DialogPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport com.jfoenix.controls.JFXProgressBar;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.StackPane;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class DialogPane extends JFXDialogLayout {\n    private final StringProperty title = new SimpleStringProperty();\n    private final BooleanProperty valid = new SimpleBooleanProperty(true);\n    protected final SpinnerPane acceptPane = new SpinnerPane();\n    protected final JFXButton cancelButton = new JFXButton();\n    protected final Label warningLabel = new Label();\n    private final JFXProgressBar progressBar = new JFXProgressBar();\n\n    public DialogPane() {\n        Label titleLabel = new Label();\n        titleLabel.textProperty().bind(title);\n        setHeading(titleLabel);\n        getChildren().add(progressBar);\n\n        progressBar.setVisible(false);\n        StackPane.setMargin(progressBar, new Insets(-24.0D, -24.0D, -16.0D, -24.0D));\n        StackPane.setAlignment(progressBar, Pos.TOP_CENTER);\n        progressBar.setMaxWidth(Double.MAX_VALUE);\n\n        JFXButton acceptButton = new JFXButton(i18n(\"button.ok\"));\n        acceptButton.setOnAction(e -> onAccept());\n        acceptButton.disableProperty().bind(valid.not());\n        acceptButton.getStyleClass().add(\"dialog-accept\");\n        acceptPane.getStyleClass().add(\"small-spinner-pane\");\n        acceptPane.setContent(acceptButton);\n\n        cancelButton.setText(i18n(\"button.cancel\"));\n        cancelButton.setOnAction(e -> onCancel());\n        cancelButton.getStyleClass().add(\"dialog-cancel\");\n        onEscPressed(this, cancelButton::fire);\n\n        setActions(warningLabel, acceptPane, cancelButton);\n    }\n\n    protected JFXProgressBar getProgressBar() {\n        return progressBar;\n    }\n\n    public String getTitle() {\n        return title.get();\n    }\n\n    public StringProperty titleProperty() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title.set(title);\n    }\n\n    public boolean isValid() {\n        return valid.get();\n    }\n\n    public BooleanProperty validProperty() {\n        return valid;\n    }\n\n    public void setValid(boolean valid) {\n        this.valid.set(valid);\n    }\n\n    protected void onCancel() {\n        fireEvent(new DialogCloseEvent());\n    }\n\n    protected void onAccept() {\n        fireEvent(new DialogCloseEvent());\n    }\n\n    protected void setLoading() {\n        acceptPane.showSpinner();\n        warningLabel.setText(\"\");\n    }\n\n    protected void onSuccess() {\n        acceptPane.hideSpinner();\n        fireEvent(new DialogCloseEvent());\n    }\n\n    protected void onFailure(String msg) {\n        acceptPane.hideSpinner();\n        warningLabel.setText(msg);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/DoubleValidator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.NamedArg;\nimport javafx.scene.control.TextInputControl;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\n\npublic class DoubleValidator extends ValidatorBase {\n    private final boolean nullable;\n\n    public DoubleValidator() {\n        this(false);\n    }\n\n    public DoubleValidator(@NamedArg(\"nullable\") boolean nullable) {\n        this.nullable = nullable;\n    }\n\n    public DoubleValidator(@NamedArg(\"message\") String message, @NamedArg(\"nullable\") boolean nullable) {\n        super(message);\n        this.nullable = nullable;\n    }\n\n    @Override\n    protected void eval() {\n        if (srcControl.get() instanceof TextInputControl) {\n            evalTextInputField();\n        }\n    }\n\n    private void evalTextInputField() {\n        TextInputControl textField = ((TextInputControl) srcControl.get());\n\n        if (StringUtils.isBlank(textField.getText()))\n            hasErrors.set(!nullable);\n        else\n            hasErrors.set(Lang.toDoubleOrNull(textField.getText()) == null);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileSelector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Pos;\nimport javafx.scene.layout.HBox;\nimport javafx.stage.DirectoryChooser;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class FileSelector extends HBox {\n    private final StringProperty value = new SimpleStringProperty();\n    private String chooserTitle = i18n(\"selector.choose_file\");\n    private boolean directory = false;\n    private final ObservableList<FileChooser.ExtensionFilter> extensionFilters = FXCollections.observableArrayList();\n\n    public String getValue() {\n        return value.get();\n    }\n\n    public StringProperty valueProperty() {\n        return value;\n    }\n\n    public void setValue(String value) {\n        this.value.set(value);\n    }\n\n    public String getChooserTitle() {\n        return chooserTitle;\n    }\n\n    public FileSelector setChooserTitle(String chooserTitle) {\n        this.chooserTitle = chooserTitle;\n        return this;\n    }\n\n    public boolean isDirectory() {\n        return directory;\n    }\n\n    public FileSelector setDirectory(boolean directory) {\n        this.directory = directory;\n        return this;\n    }\n\n    public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() {\n        return extensionFilters;\n    }\n\n    public FileSelector() {\n        JFXTextField customField = new JFXTextField();\n        FXUtils.bindString(customField, valueProperty());\n\n        JFXButton selectButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN, 15);\n        selectButton.setOnAction(e -> {\n            if (directory) {\n                DirectoryChooser chooser = new DirectoryChooser();\n                chooser.setTitle(chooserTitle);\n                Path dir = FileUtils.toPath(chooser.showDialog(Controllers.getStage()));\n                if (dir != null) {\n                    String path = FileUtils.getAbsolutePath(dir);\n                    customField.setText(path);\n                    value.setValue(path);\n                }\n            } else {\n                FileChooser chooser = new FileChooser();\n                chooser.getExtensionFilters().addAll(getExtensionFilters());\n                chooser.setTitle(chooserTitle);\n                Path file = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));\n                if (file != null) {\n                    String path = FileUtils.getAbsolutePath(file);\n                    customField.setText(path);\n                    value.setValue(path);\n                }\n            }\n        });\n\n        setAlignment(Pos.CENTER_LEFT);\n        setSpacing(3);\n        getChildren().addAll(customField, selectButton);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.NumberBinding;\nimport javafx.geometry.Orientation;\nimport javafx.geometry.Point2D;\nimport javafx.scene.Node;\nimport javafx.scene.control.ScrollBar;\nimport javafx.scene.control.Skin;\nimport javafx.scene.layout.Region;\nimport javafx.scene.shape.Rectangle;\nimport org.jackhuang.hmcl.util.Lang;\n\n// Referenced in root.css\n@SuppressWarnings(\"unused\")\npublic class FloatScrollBarSkin implements Skin<ScrollBar> {\n    private ScrollBar scrollBar;\n    private Region group;\n    private Rectangle track = new Rectangle();\n    private Rectangle thumb = new Rectangle();\n\n    public FloatScrollBarSkin(final ScrollBar scrollBar) {\n        this.scrollBar = scrollBar;\n        scrollBar.setPrefHeight(1e-18);\n        scrollBar.setPrefWidth(1e-18);\n\n        this.group = new Region() {\n            Point2D dragStart;\n            double preDragThumbPos;\n\n            NumberBinding range = Bindings.subtract(scrollBar.maxProperty(), scrollBar.minProperty());\n            NumberBinding position = Bindings.divide(Bindings.subtract(scrollBar.valueProperty(), scrollBar.minProperty()), range);\n\n            {\n                // Children are added unmanaged because for some reason the height of the bar keeps changing\n                // if they're managed in certain situations... not sure about the cause.\n                getChildren().addAll(track, thumb);\n\n                track.setManaged(false);\n                track.getStyleClass().add(\"track\");\n\n                thumb.setManaged(false);\n                thumb.getStyleClass().add(\"thumb\");\n\n                scrollBar.orientationProperty().addListener(obs -> setup());\n\n                setup();\n\n\n                thumb.setOnMousePressed(me -> {\n                    if (me.isSynthesized()) {\n                        // touch-screen events handled by Scroll handler\n                        me.consume();\n                        return;\n                    }\n                    /*\n                     ** if max isn't greater than min then there is nothing to do here\n                     */\n                    if (getSkinnable().getMax() > getSkinnable().getMin()) {\n                        dragStart = thumb.localToParent(me.getX(), me.getY());\n                        double clampedValue = Lang.clamp(getSkinnable().getMin(), getSkinnable().getValue(), getSkinnable().getMax());\n                        preDragThumbPos = (clampedValue - getSkinnable().getMin()) / (getSkinnable().getMax() - getSkinnable().getMin());\n                        me.consume();\n                    }\n                });\n\n\n                thumb.setOnMouseDragged(me -> {\n                    if (me.isSynthesized()) {\n                        // touch-screen events handled by Scroll handler\n                        me.consume();\n                        return;\n                    }\n                    /*\n                     ** if max isn't greater than min then there is nothing to do here\n                     */\n                    if (getSkinnable().getMax() > getSkinnable().getMin()) {\n                        /*\n                         ** if the tracklength isn't greater then do nothing....\n                         */\n                        if (trackLength() > thumbLength()) {\n                            Point2D cur = thumb.localToParent(me.getX(), me.getY());\n                            if (dragStart == null) {\n                                // we're getting dragged without getting a mouse press\n                                dragStart = thumb.localToParent(me.getX(), me.getY());\n                            }\n                            double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY() : cur.getX() - dragStart.getX();\n                            double position = preDragThumbPos + dragPos / (trackLength() - thumbLength());\n                            if (!getSkinnable().isFocused() && getSkinnable().isFocusTraversable())\n                                getSkinnable().requestFocus();\n                            double newValue = (position * (getSkinnable().getMax() - getSkinnable().getMin())) + getSkinnable().getMin();\n                            if (!Double.isNaN(newValue)) {\n                                getSkinnable().setValue(Lang.clamp(getSkinnable().getMin(), newValue, getSkinnable().getMax()));\n                            }\n                        }\n\n                        me.consume();\n                    }\n                });\n            }\n\n            private double trackLength() {\n                return getSkinnable().getOrientation() == Orientation.VERTICAL ? track.getHeight() : track.getWidth();\n            }\n\n            private double thumbLength() {\n                return getSkinnable().getOrientation() == Orientation.VERTICAL ? thumb.getHeight() : thumb.getWidth();\n            }\n\n            private void setup() {\n                track.widthProperty().unbind();\n                track.heightProperty().unbind();\n\n                if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {\n                    track.relocate(0, -8);\n                    track.widthProperty().bind(scrollBar.widthProperty());\n                    track.setHeight(8);\n                } else {\n                    track.relocate(-8, 0);\n                    track.setWidth(8);\n                    track.heightProperty().bind(scrollBar.heightProperty());\n                }\n\n                thumb.xProperty().unbind();\n                thumb.yProperty().unbind();\n                thumb.widthProperty().unbind();\n                thumb.heightProperty().unbind();\n\n                if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {\n                    thumb.relocate(0, -8);\n                    thumb.widthProperty().bind(Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.widthProperty())));\n                    thumb.setHeight(8);\n                    thumb.xProperty().bind(Bindings.subtract(scrollBar.widthProperty(), thumb.widthProperty()).multiply(position));\n                } else {\n                    thumb.relocate(-8, 0);\n                    thumb.setWidth(8);\n                    thumb.heightProperty().bind(Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.heightProperty())));\n                    thumb.yProperty().bind(Bindings.subtract(scrollBar.heightProperty(), thumb.heightProperty()).multiply(position));\n                }\n            }\n\n            @Override\n            protected double computeMaxWidth(double height) {\n                if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {\n                    return Double.MAX_VALUE;\n                }\n\n                return 8;\n            }\n\n            @Override\n            protected double computeMaxHeight(double width) {\n                if (scrollBar.getOrientation() == Orientation.VERTICAL) {\n                    return Double.MAX_VALUE;\n                }\n\n                return 8;\n            }\n        };\n    }\n\n    @Override\n    public void dispose() {\n        scrollBar = null;\n        group = null;\n    }\n\n    @Override\n    public Node getNode() {\n        return group;\n    }\n\n    @Override\n    public ScrollBar getSkinnable() {\n        return scrollBar;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport static javafx.collections.FXCollections.emptyObservableList;\nimport static javafx.collections.FXCollections.observableList;\nimport static javafx.collections.FXCollections.singletonObservableList;\n\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\n\nimport com.jfoenix.controls.JFXComboBox;\nimport com.jfoenix.controls.JFXListCell;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.scene.text.Font;\n\npublic final class FontComboBox extends JFXComboBox<String> {\n\n    private boolean loaded = false;\n\n    public FontComboBox() {\n        setMinWidth(260);\n\n        styleProperty().bind(Bindings.concat(\"-fx-font-family: \\\"\", valueProperty(), \"\\\"\"));\n\n        setCellFactory(listView -> new JFXListCell<String>() {\n            @Override\n            public void updateItem(String item, boolean empty) {\n                super.updateItem(item, empty);\n                if (!empty) {\n                    setText(item);\n                    setGraphic(null);\n                    setStyle(\"-fx-font-family: \\\"\" + item + \"\\\"\");\n                }\n            }\n        });\n\n        itemsProperty().bind(BindingMapping.of(valueProperty())\n                        .map(value -> value == null ? emptyObservableList() : singletonObservableList(value)));\n\n        FXUtils.onClicked(this, () -> {\n            if (loaded)\n                return;\n            itemsProperty().unbind();\n            setItems(observableList(Font.getFamilies()));\n            loaded = true;\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.Text;\nimport javafx.scene.text.TextFlow;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\nimport java.util.Locale;\nimport java.util.function.Consumer;\n\npublic class HintPane extends VBox {\n    private final Text label = new Text();\n    private final StringProperty text = new SimpleStringProperty(this, \"text\");\n    private final TextFlow flow = new TextFlow();\n\n    public HintPane() {\n        this(MessageDialogPane.MessageType.INFO);\n    }\n\n    public HintPane(MessageDialogPane.MessageType type) {\n        setFillWidth(true);\n        getStyleClass().addAll(\"hint\", type.name().toLowerCase(Locale.ROOT));\n\n        HBox hbox = new HBox(type.getIcon().createIcon(16), new Text(type.getDisplayName()));\n        hbox.setAlignment(Pos.CENTER_LEFT);\n        hbox.setSpacing(2);\n        flow.getChildren().setAll(label);\n        getChildren().setAll(hbox, flow);\n        label.textProperty().bind(text);\n        VBox.setMargin(flow, new Insets(2, 2, 0, 2));\n    }\n\n    public String getText() {\n        return text.get();\n    }\n\n    public StringProperty textProperty() {\n        return text;\n    }\n\n    public void setText(String text) {\n        this.text.set(text);\n    }\n\n    public void setSegment(String segment) {\n        this.setSegment(segment, Controllers::onHyperlinkAction);\n    }\n\n    public void setSegment(String segment, Consumer<String> hyperlinkAction) {\n        flow.getChildren().setAll(FXUtils.parseSegment(segment, hyperlinkAction));\n    }\n\n    public void setChildren(Node... children) {\n        flow.getChildren().setAll(children);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.HBox;\n\npublic class IconedItem extends RipplerContainer {\n\n    private Label label;\n\n    public IconedItem(Node icon, String text) {\n        this(icon);\n        label.setText(text);\n    }\n\n    public IconedItem(Node icon) {\n        super(createHBox(icon));\n        label = ((Label) lookup(\"#label\"));\n        getStyleClass().setAll(\"iconed-item\");\n    }\n\n    private static HBox createHBox(Node icon) {\n        HBox hBox = new HBox();\n\n        if (icon != null) {\n            icon.setMouseTransparent(true);\n            hBox.getChildren().add(icon);\n        }\n\n        hBox.getStyleClass().add(\"iconed-item-container\");\n        Label textLabel = new Label();\n        textLabel.setId(\"label\");\n        textLabel.setMouseTransparent(true);\n        hBox.getChildren().addAll(textLabel);\n        return hBox;\n    }\n\n    public Label getLabel() {\n        return label;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXPopup;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\n\npublic final class IconedMenuItem extends IconedItem {\n\n    public IconedMenuItem(SVG icon, String text, Runnable action, JFXPopup popup) {\n        super(icon != null ? icon.createIcon(14) : null, text);\n\n        getStyleClass().setAll(\"iconed-menu-item\");\n\n        if (popup == null) {\n            FXUtils.onClicked(this, action);\n        } else {\n            FXUtils.onClicked(this, () -> {\n                action.run();\n                popup.hide();\n            });\n        }\n    }\n\n    public IconedMenuItem addTooltip(String tooltip) {\n        FXUtils.installFastTooltip(this, tooltip);\n        return this;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImageContainer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.css.CssMetaData;\nimport javafx.css.Styleable;\nimport javafx.css.StyleableDoubleProperty;\nimport javafx.css.StyleableProperty;\nimport javafx.css.converter.SizeConverter;\nimport javafx.geometry.Pos;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.shape.Rectangle;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/// A custom ImageView with fixed size and corner radius support.\npublic class ImageContainer extends StackPane {\n\n    private static final String DEFAULT_STYLE_CLASS = \"image-container\";\n\n    private final ImageView imageView = new ImageView();\n    private final Rectangle clip = new Rectangle();\n\n    public ImageContainer(double size) {\n        this(size, size);\n    }\n\n    public ImageContainer(double width, double height) {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n\n        FXUtils.setLimitWidth(this, width);\n        FXUtils.setLimitHeight(this, height);\n\n        imageView.setPreserveRatio(true);\n        FXUtils.limitSize(imageView, width, height);\n        StackPane.setAlignment(imageView, Pos.CENTER);\n\n        clip.setWidth(width);\n        clip.setHeight(height);\n        updateCornerRadius(getCornerRadius());\n        this.setClip(clip);\n\n        this.getChildren().setAll(imageView);\n    }\n\n    private void updateCornerRadius(double radius) {\n        clip.setArcWidth(radius);\n        clip.setArcHeight(radius);\n    }\n\n    private static final double DEFAULT_CORNER_RADIUS = 6.0;\n\n    private StyleableDoubleProperty cornerRadius;\n\n    public StyleableDoubleProperty cornerRadiusProperty() {\n        if (this.cornerRadius == null) {\n            cornerRadius = new StyleableDoubleProperty() {\n                @Override\n                public Object getBean() {\n                    return ImageContainer.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"cornerRadius\";\n                }\n\n                @Override\n                public CssMetaData<? extends Styleable, Number> getCssMetaData() {\n                    return StyleableProperties.CORNER_RADIUS;\n                }\n\n                @Override\n                protected void invalidated() {\n                    updateCornerRadius(get());\n                }\n            };\n        }\n\n        return cornerRadius;\n    }\n\n    public double getCornerRadius() {\n        return cornerRadius == null ? DEFAULT_CORNER_RADIUS : cornerRadius.get();\n    }\n\n    public void setCornerRadius(double radius) {\n        cornerRadiusProperty().set(radius);\n    }\n\n    public ObjectProperty<Image> imageProperty() {\n        return imageView.imageProperty();\n    }\n\n    public Image getImage() {\n        return imageView.getImage();\n    }\n\n    public void setImage(Image image) {\n        imageView.setImage(image);\n    }\n\n    public BooleanProperty smoothProperty() {\n        return imageView.smoothProperty();\n    }\n\n    public boolean isSmooth() {\n        return imageView.isSmooth();\n    }\n\n    public void setSmooth(boolean smooth) {\n        imageView.setSmooth(smooth);\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {\n        return StyleableProperties.STYLEABLES;\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<ImageContainer, Number> CORNER_RADIUS =\n                new CssMetaData<>(\"-jfx-corner-radius\", SizeConverter.getInstance(), DEFAULT_CORNER_RADIUS) {\n                    @Override\n                    public boolean isSettable(ImageContainer control) {\n                        return control.cornerRadius == null || !control.cornerRadius.isBound();\n                    }\n\n                    @Override\n                    public StyleableProperty<Number> getStyleableProperty(ImageContainer control) {\n                        return control.cornerRadiusProperty();\n                    }\n                };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n        static {\n            var styleables = new ArrayList<>(StackPane.getClassCssMetaData());\n            styleables.add(CORNER_RADIUS);\n            STYLEABLES = List.copyOf(styleables);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.beans.DefaultProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.image.Image;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\n@DefaultProperty(\"image\")\npublic final class ImagePickerItem extends BorderPane {\n\n    private final ImageContainer imageContainer;\n\n    private final StringProperty title = new SimpleStringProperty(this, \"title\");\n    private final ObjectProperty<EventHandler<ActionEvent>> onSelectButtonClicked = new SimpleObjectProperty<>(this, \"onSelectButtonClicked\");\n    private final ObjectProperty<EventHandler<ActionEvent>> onDeleteButtonClicked = new SimpleObjectProperty<>(this, \"onDeleteButtonClicked\");\n    private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, \"image\");\n\n    public ImagePickerItem() {\n        imageContainer = new ImageContainer(32);\n        imageContainer.setSmooth(false);\n\n        JFXButton selectButton = FXUtils.newToggleButton4(SVG.EDIT, 20);\n        selectButton.onActionProperty().bind(onSelectButtonClicked);\n\n        JFXButton deleteButton = FXUtils.newToggleButton4(SVG.RESTORE, 20);\n        deleteButton.onActionProperty().bind(onDeleteButtonClicked);\n\n        FXUtils.installFastTooltip(selectButton, i18n(\"button.edit\"));\n        FXUtils.installFastTooltip(deleteButton, i18n(\"button.reset\"));\n\n        HBox hBox = new HBox();\n        hBox.getChildren().setAll(imageContainer, selectButton, deleteButton);\n        hBox.setAlignment(Pos.CENTER_RIGHT);\n        hBox.setSpacing(8);\n        setRight(hBox);\n\n        VBox vBox = new VBox();\n        Label label = new Label();\n        label.textProperty().bind(title);\n        vBox.getChildren().setAll(label);\n        vBox.setAlignment(Pos.CENTER_LEFT);\n        setLeft(vBox);\n\n        imageContainer.imageProperty().bind(image);\n    }\n\n    public String getTitle() {\n        return title.get();\n    }\n\n    public StringProperty titleProperty() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title.set(title);\n    }\n\n    public EventHandler<ActionEvent> getOnSelectButtonClicked() {\n        return onSelectButtonClicked.get();\n    }\n\n    public ObjectProperty<EventHandler<ActionEvent>> onSelectButtonClickedProperty() {\n        return onSelectButtonClicked;\n    }\n\n    public void setOnSelectButtonClicked(EventHandler<ActionEvent> onSelectButtonClicked) {\n        this.onSelectButtonClicked.set(onSelectButtonClicked);\n    }\n\n    public EventHandler<ActionEvent> getOnDeleteButtonClicked() {\n        return onDeleteButtonClicked.get();\n    }\n\n    public ObjectProperty<EventHandler<ActionEvent>> onDeleteButtonClickedProperty() {\n        return onDeleteButtonClicked;\n    }\n\n    public void setOnDeleteButtonClicked(EventHandler<ActionEvent> onDeleteButtonClicked) {\n        this.onDeleteButtonClicked.set(onDeleteButtonClicked);\n    }\n\n    public Image getImage() {\n        return image.get();\n    }\n\n    public ObjectProperty<Image> imageProperty() {\n        return image;\n    }\n\n    public void setImage(Image image) {\n        this.image.set(image);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport com.jfoenix.controls.JFXTextField;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.FutureCallback;\n\nimport java.util.concurrent.CompletableFuture;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class InputDialogPane extends JFXDialogLayout implements DialogAware {\n    private final CompletableFuture<String> future = new CompletableFuture<>();\n\n    private final JFXTextField textField;\n    private final Label lblCreationWarning;\n    private final SpinnerPane acceptPane;\n    private final JFXButton acceptButton;\n\n    public InputDialogPane(String text, String initialValue, FutureCallback<String> onResult, ValidatorBase... validators) {\n        this(text, initialValue, onResult);\n        if (validators != null && validators.length > 0) {\n            textField.getValidators().addAll(validators);\n            FXUtils.setValidateWhileTextChanged(textField, true);\n            acceptButton.disableProperty().bind(textField.activeValidatorProperty().isNotNull());\n        }\n    }\n\n    public InputDialogPane(String text, String initialValue, FutureCallback<String> onResult) {\n        textField = new JFXTextField(initialValue);\n\n        this.setHeading(new HBox(new Label(text)));\n        this.setBody(new VBox(textField));\n\n        lblCreationWarning = new Label();\n\n        acceptPane = new SpinnerPane();\n        acceptPane.getStyleClass().add(\"small-spinner-pane\");\n        acceptButton = new JFXButton(i18n(\"button.ok\"));\n        acceptButton.getStyleClass().add(\"dialog-accept\");\n        acceptPane.setContent(acceptButton);\n\n        JFXButton cancelButton = new JFXButton(i18n(\"button.cancel\"));\n        cancelButton.getStyleClass().add(\"dialog-cancel\");\n\n        this.setActions(lblCreationWarning, acceptPane, cancelButton);\n\n        cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));\n        acceptButton.setOnAction(e -> {\n            acceptPane.showSpinner();\n\n            onResult.call(textField.getText(), new FutureCallback.ResultHandler() {\n                @Override\n                public void resolve() {\n                    acceptPane.hideSpinner();\n                    future.complete(textField.getText());\n                    fireEvent(new DialogCloseEvent());\n                }\n\n                @Override\n                public void reject(String reason) {\n                    acceptPane.hideSpinner();\n                    lblCreationWarning.setText(reason);\n                }\n            });\n        });\n        textField.setOnAction(event -> acceptButton.fire());\n        onEscPressed(this, cancelButton::fire);\n    }\n\n    @Override\n    public void onDialogShown() {\n        textField.requestFocus();\n    }\n\n    public CompletableFuture<String> getCompletableFuture() {\n        return future;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTableCell.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXCheckBox;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.scene.control.TableCell;\nimport javafx.scene.control.TableColumn;\nimport javafx.util.Callback;\n\n/// @author Glavo\npublic final class JFXCheckBoxTableCell<S, T> extends TableCell<S, T> {\n    public static <S> Callback<TableColumn<S, Boolean>, TableCell<S, Boolean>> forTableColumn(\n            final TableColumn<S, Boolean> column) {\n        return list -> new JFXCheckBoxTableCell<>();\n    }\n\n    private final JFXCheckBox checkBox = new JFXCheckBox();\n    private BooleanProperty booleanProperty;\n\n    public JFXCheckBoxTableCell() {\n        this.getStyleClass().add(\"jfx-checkbox-table-cell\");\n    }\n\n    @Override\n    protected void updateItem(T item, boolean empty) {\n        super.updateItem(item, empty);\n\n        if (empty) {\n            setText(null);\n            setGraphic(null);\n            checkBox.disableProperty().unbind();\n        } else {\n            setGraphic(checkBox);\n\n            if (booleanProperty != null) {\n                checkBox.selectedProperty().unbindBidirectional(booleanProperty);\n            }\n            if (getTableColumn().getCellObservableValue(getIndex()) instanceof BooleanProperty obsValue) {\n                booleanProperty = obsValue;\n                checkBox.selectedProperty().bindBidirectional(booleanProperty);\n            }\n\n            checkBox.disableProperty().bind(Bindings.not(\n                    getTableView().editableProperty().and(\n                            getTableColumn().editableProperty()).and(\n                            editableProperty())\n            ));\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXDialogPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.scene.Node;\nimport javafx.scene.layout.StackPane;\n\nimport java.util.ArrayList;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class JFXDialogPane extends StackPane {\n    private final ArrayList<Node> stack = new ArrayList<>();\n\n    public int size() {\n        return stack.size();\n    }\n\n    public Optional<Node> peek() {\n        if (stack.isEmpty()) {\n            return Optional.empty();\n        } else {\n            return Optional.of(stack.get(stack.size() - 1));\n        }\n    }\n\n    public void push(Node node) {\n        stack.add(node);\n        getChildren().setAll(node);\n\n        LOG.info(this + \" \" + stack);\n    }\n\n    public void pop(Node node) {\n        boolean flag = stack.remove(node);\n        if (stack.isEmpty())\n            getChildren().setAll();\n        else\n            getChildren().setAll(stack.get(stack.size() - 1));\n\n        LOG.info(this + \" \" + stack + \", removed: \" + flag + \", object: \" + node);\n    }\n\n    public boolean isEmpty() {\n        return stack.isEmpty();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXHyperlink.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.scene.control.Hyperlink;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\n\npublic final class JFXHyperlink extends Hyperlink {\n\n    public JFXHyperlink(String text) {\n        super(text);\n\n        setGraphic(SVG.OPEN_IN_NEW.createIcon(16));\n    }\n\n    public void setExternalLink(String externalLink) {\n        this.setOnAction(e -> FXUtils.openLink(externalLink));\n    }\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButton.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.*;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.OverrunStyle;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\n\n/// @author Glavo\npublic class LineButton extends LineButtonBase {\n    private static final String DEFAULT_STYLE_CLASS = \"line-button\";\n\n    private static final int IDX_TRAILING_TEXT = IDX_TRAILING;\n    private static final int IDX_TRAILING_ICON = IDX_TRAILING + 1;\n\n    public static LineButton createNavigationButton() {\n        var button = new LineButton();\n        button.setTrailingIcon(SVG.ARROW_FORWARD);\n        return button;\n    }\n\n    public static LineButton createExternalLinkButton(String url) {\n        var button = new LineButton();\n        button.setTrailingIcon(SVG.OPEN_IN_NEW);\n        if (url != null) {\n            button.setOnAction(event -> FXUtils.openLink(url));\n        }\n        return button;\n    }\n\n    public LineButton() {\n        getStyleClass().add(DEFAULT_STYLE_CLASS);\n        container.setMouseTransparent(true);\n    }\n\n    private ObjectProperty<EventHandler<ActionEvent>> onAction;\n\n    public ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {\n        if (onAction == null) {\n            onAction = new ObjectPropertyBase<>() {\n                @Override\n                public Object getBean() {\n                    return LineButton.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"onAction\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    setEventHandler(ActionEvent.ACTION, get());\n                }\n            };\n        }\n        return onAction;\n    }\n\n    public EventHandler<ActionEvent> getOnAction() {\n        return onActionProperty().get();\n    }\n\n    public void setOnAction(EventHandler<ActionEvent> value) {\n        onActionProperty().set(value);\n    }\n\n    private StringProperty trailingText;\n\n    public StringProperty trailingTextProperty() {\n        if (trailingText == null) {\n            trailingText = new StringPropertyBase() {\n                private Label trailingTextLabel;\n\n                @Override\n                public Object getBean() {\n                    return LineButton.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"trailingText\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    String message = get();\n                    if (message != null && !message.isEmpty()) {\n                        if (trailingTextLabel == null) {\n                            trailingTextLabel = new Label();\n                            trailingTextLabel.getStyleClass().add(\"trailing-label\");\n                            trailingTextLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);\n                        }\n                        trailingTextLabel.setText(message);\n                        setNode(IDX_TRAILING_TEXT, trailingTextLabel);\n                    } else if (trailingTextLabel != null) {\n                        trailingTextLabel.setText(\"\");\n                        setNode(IDX_TRAILING_TEXT, null);\n                    }\n                }\n            };\n        }\n\n        return trailingText;\n    }\n\n    public String getTrailingText() {\n        return trailingText != null ? trailingText.get() : null;\n    }\n\n    public void setTrailingText(String trailingText) {\n        trailingTextProperty().set(trailingText);\n    }\n\n    private ObjectProperty<Node> trailingIcon;\n\n    public ObjectProperty<Node> trailingIconProperty() {\n        if (trailingIcon == null)\n            trailingIcon = new ObjectPropertyBase<>() {\n                @Override\n                public Object getBean() {\n                    return LineButton.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"trailingIcon\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    setNode(IDX_TRAILING_ICON, get());\n                }\n            };\n\n        return trailingIcon;\n    }\n\n    public Node getTrailingIcon() {\n        return trailingIcon != null ? trailingIcon.get() : null;\n    }\n\n    public void setTrailingIcon(Node trailingIcon) {\n        trailingIconProperty().set(trailingIcon);\n    }\n\n    public void setTrailingIcon(SVG rightIcon) {\n        setTrailingIcon(rightIcon, 20);\n    }\n\n    public void setTrailingIcon(SVG rightIcon, double size) {\n        Node rightIconNode = rightIcon.createIcon(size);\n        rightIconNode.getStyleClass().add(\"trailing-icon\");\n        setTrailingIcon(rightIconNode);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineButtonBase.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.event.ActionEvent;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\n/// @author Glavo\npublic abstract class LineButtonBase extends LineComponent {\n    private static final String DEFAULT_STYLE_CLASS = \"line-button-base\";\n\n    protected final RipplerContainer ripplerContainer;\n\n    public LineButtonBase() {\n        this.getStyleClass().addAll(LineButtonBase.DEFAULT_STYLE_CLASS);\n\n        this.ripplerContainer = new RipplerContainer(container);\n        FXUtils.onClicked(this, this::fire);\n\n        this.getChildren().setAll(ripplerContainer);\n    }\n\n    public void fire() {\n        fireEvent(new ActionEvent());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineComponent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.ObjectPropertyBase;\nimport javafx.beans.property.StringProperty;\nimport javafx.beans.property.StringPropertyBase;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.ui.SVG;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\n/// @author Glavo\npublic abstract class LineComponent extends StackPane implements NoPaddingComponent {\n    private static final String DEFAULT_STYLE_CLASS = \"line-component\";\n    private static final double MIN_HEIGHT = 48.0;\n    private static final PseudoClass PSEUDO_LARGER_TITLE = PseudoClass.getPseudoClass(\"large-title\");\n\n    protected static final int IDX_LEADING = 0;\n    protected static final int IDX_TITLE = 1;\n    protected static final int IDX_TRAILING = 2;\n\n    public static final double SPACING = 12;\n    public static final double DEFAULT_ICON_SIZE = 20;\n\n    public static void setMargin(Node child, Insets value) {\n        HBox.setMargin(child, value);\n    }\n\n    protected final HBox container;\n\n    private final Label titleLabel;\n    private final VBox titleContainer;\n\n    public LineComponent() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n\n        this.setMinHeight(MIN_HEIGHT);\n\n        this.container = new HBox(SPACING);\n        container.getStyleClass().add(\"line-component-container\");\n        container.setAlignment(Pos.CENTER_LEFT);\n\n        this.titleLabel = new Label();\n        titleLabel.getStyleClass().add(\"title-label\");\n        titleLabel.setMinWidth(Region.USE_PREF_SIZE);\n\n        this.titleContainer = new VBox(titleLabel);\n        titleContainer.getStyleClass().add(\"title-container\");\n        titleContainer.setMouseTransparent(true);\n        titleContainer.setAlignment(Pos.CENTER_LEFT);\n        titleContainer.minWidthProperty().bind(titleLabel.prefWidthProperty());\n        HBox.setHgrow(titleContainer, Priority.ALWAYS);\n\n        this.setNode(IDX_TITLE, titleContainer);\n\n        this.getChildren().setAll(container);\n    }\n\n    private Node[] nodes = new Node[2];\n\n    protected void setNode(int idx, Node node) {\n        if (nodes.length <= idx)\n            nodes = Arrays.copyOf(nodes, idx + 1);\n\n        if (nodes[idx] != node) {\n            nodes[idx] = node;\n            container.getChildren().setAll(Arrays.stream(nodes).filter(Objects::nonNull).toArray(Node[]::new));\n        }\n    }\n\n    public void setLargeTitle(boolean largeTitle) {\n        pseudoClassStateChanged(PSEUDO_LARGER_TITLE, largeTitle);\n    }\n\n    private final StringProperty title = new StringPropertyBase() {\n        @Override\n        public Object getBean() {\n            return LineComponent.this;\n        }\n\n        @Override\n        public String getName() {\n            return \"title\";\n        }\n\n        @Override\n        protected void invalidated() {\n            titleLabel.setText(get());\n        }\n    };\n\n    public final StringProperty titleProperty() {\n        return title;\n    }\n\n    public String getTitle() {\n        return titleProperty().get();\n    }\n\n    public void setTitle(String title) {\n        titleProperty().set(title);\n    }\n\n    private StringProperty subtitle;\n\n    public final StringProperty subtitleProperty() {\n        if (subtitle == null) {\n            subtitle = new StringPropertyBase() {\n                private Label subtitleLabel;\n\n                @Override\n                public String getName() {\n                    return \"subtitle\";\n                }\n\n                @Override\n                public Object getBean() {\n                    return LineComponent.this;\n                }\n\n                @Override\n                protected void invalidated() {\n                    String subtitle = get();\n                    if (subtitle != null && !subtitle.isEmpty()) {\n                        if (subtitleLabel == null) {\n                            subtitleLabel = new Label();\n                            subtitleLabel.setWrapText(true);\n                            subtitleLabel.setMinHeight(Region.USE_PREF_SIZE);\n                            subtitleLabel.getStyleClass().add(\"subtitle-label\");\n                        }\n                        subtitleLabel.setText(subtitle);\n                        if (titleContainer.getChildren().size() == 1)\n                            titleContainer.getChildren().add(subtitleLabel);\n                    } else if (subtitleLabel != null) {\n                        subtitleLabel.setText(null);\n                        if (titleContainer.getChildren().size() == 2)\n                            titleContainer.getChildren().remove(1);\n                    }\n                }\n            };\n        }\n\n        return subtitle;\n    }\n\n    public final String getSubtitle() {\n        return subtitle != null ? subtitle.get() : null;\n    }\n\n    public final void setSubtitle(String subtitle) {\n        subtitleProperty().set(subtitle);\n    }\n\n    private ObjectProperty<Node> leading;\n\n    public final ObjectProperty<Node> leadingProperty() {\n        if (leading == null) {\n            leading = new ObjectPropertyBase<>() {\n                @Override\n                public Object getBean() {\n                    return LineComponent.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"leading\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    setNode(IDX_LEADING, get());\n                }\n            };\n        }\n        return leading;\n    }\n\n    public final Node getLeading() {\n        return leadingProperty().get();\n    }\n\n    public final void setLeading(Node node) {\n        leadingProperty().set(node);\n    }\n\n    public void setLeading(Image icon) {\n        setLeading(icon, -1);\n    }\n\n    public void setLeading(Image icon, double size) {\n        var imageView = new ImageView(icon);\n        if (size > 0) {\n            imageView.setFitWidth(size);\n            imageView.setFitHeight(size);\n            imageView.setPreserveRatio(true);\n            imageView.setSmooth(true);\n        }\n        imageView.setMouseTransparent(true);\n\n        setNode(IDX_LEADING, imageView);\n    }\n\n    public void setLeading(SVG svg) {\n        setLeading(svg, DEFAULT_ICON_SIZE);\n    }\n\n    public void setLeading(SVG svg, double size) {\n        Node node = svg.createIcon(size);\n        node.setMouseTransparent(true);\n        setNode(IDX_LEADING, node);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineFileChooserButton.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.stage.DirectoryChooser;\nimport javafx.stage.FileChooser;\nimport javafx.stage.Stage;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class LineFileChooserButton extends LineButton {\n    private static final String DEFAULT_STYLE_CLASS = \"line-file-select-button\";\n\n    public LineFileChooserButton() {\n        getStyleClass().add(DEFAULT_STYLE_CLASS);\n        setTrailingIcon(SVG.EDIT);\n    }\n\n    /// Converts the given path to absolute/relative(if possible) path according to [#convertToRelativePathProperty()].\n    private String processPath(Path path) {\n        if (isConvertToRelativePath() && path.isAbsolute()) {\n            try {\n                return Metadata.CURRENT_DIRECTORY.relativize(path).normalize().toString();\n            } catch (IllegalArgumentException e) {\n                // the given path can't be relativized against current path\n            }\n        }\n        return path.normalize().toString();\n    }\n\n    @Override\n    public void fire() {\n        super.fire();\n\n        Stage owner = Controllers.getStage(); // TODO: Allow user to set owner stage\n        String windowTitle = getFileChooserTitle();\n\n        Path initialDirectory = null;\n        if (getLocation() != null) {\n            Path file;\n            try {\n                file = FileUtils.toAbsolute(Path.of(getLocation()));\n                if (Files.exists(file)) {\n                    if (Files.isRegularFile(file))\n                        initialDirectory = file.getParent();\n                    else if (Files.isDirectory(file))\n                        initialDirectory = file;\n                }\n            } catch (IllegalArgumentException e) {\n                LOG.warning(\"Failed to resolve path: \" + getLocation());\n            }\n        }\n\n        Path path;\n        Type type = getType();\n        if (type == Type.OPEN_DIRECTORY) {\n            var directoryChooser = new DirectoryChooser();\n            if (windowTitle != null)\n                directoryChooser.setTitle(windowTitle);\n            if (initialDirectory != null)\n                directoryChooser.setInitialDirectory(initialDirectory.toFile());\n\n            path = FileUtils.toPath(directoryChooser.showDialog(owner));\n        } else {\n            var fileChooser = new FileChooser();\n            if (windowTitle != null)\n                fileChooser.setTitle(windowTitle);\n            if (initialDirectory != null)\n                fileChooser.setInitialDirectory(initialDirectory.toFile());\n\n            if (extensionFilters != null)\n                fileChooser.getExtensionFilters().setAll(extensionFilters);\n\n            fileChooser.setInitialFileName(getInitialFileName());\n\n            path = FileUtils.toPath(switch (type) {\n                case OPEN_FILE -> fileChooser.showOpenDialog(owner);\n                case SAVE_FILE -> fileChooser.showSaveDialog(owner);\n                default -> throw new AssertionError(\"Unknown Type: \" + type);\n            });\n        }\n\n        if (path != null) {\n            setLocation(processPath(path));\n        }\n    }\n\n    private final StringProperty location = new StringPropertyBase() {\n        @Override\n        public Object getBean() {\n            return LineFileChooserButton.this;\n        }\n\n        @Override\n        public String getName() {\n            return \"location\";\n        }\n\n        @Override\n        protected void invalidated() {\n            setTrailingText(get());\n        }\n    };\n\n    public StringProperty locationProperty() {\n        return location;\n    }\n\n    public String getLocation() {\n        return locationProperty().get();\n    }\n\n    public void setLocation(String location) {\n        locationProperty().set(location);\n    }\n\n    private final StringProperty fileChooserTitle = new SimpleStringProperty(this, \"fileChooserTitle\");\n\n    public StringProperty fileChooserTitleProperty() {\n        return fileChooserTitle;\n    }\n\n    public String getFileChooserTitle() {\n        return fileChooserTitleProperty().get();\n    }\n\n    public void setFileChooserTitle(String fileChooserTitle) {\n        fileChooserTitleProperty().set(fileChooserTitle);\n    }\n\n    private ObjectProperty<Type> type;\n\n    public ObjectProperty<Type> typeProperty() {\n        if (type == null) {\n            type = new SimpleObjectProperty<>(this, \"type\", Type.OPEN_FILE);\n        }\n        return type;\n    }\n\n    public Type getType() {\n        return type != null ? type.get() : Type.OPEN_FILE;\n    }\n\n    public void setType(Type type) {\n        typeProperty().set(type);\n    }\n\n    private ObjectProperty<String> initialFileName;\n\n    public final ObjectProperty<String> initialFileNameProperty() {\n        if (initialFileName == null)\n            initialFileName = new SimpleObjectProperty<>(this, \"initialFileName\");\n\n        return initialFileName;\n    }\n\n    public final String getInitialFileName() {\n        return initialFileName != null ? initialFileName.get() : null;\n    }\n\n    public final void setInitialFileName(String value) {\n        initialFileNameProperty().set(value);\n    }\n\n    private ObservableList<FileChooser.ExtensionFilter> extensionFilters;\n\n    public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() {\n        if (extensionFilters == null)\n            extensionFilters = FXCollections.observableArrayList();\n        return extensionFilters;\n    }\n\n    private BooleanProperty convertToRelativePath;\n\n    public BooleanProperty convertToRelativePathProperty() {\n        if (convertToRelativePath == null)\n            convertToRelativePath = new BooleanPropertyBase(false) {\n                @Override\n                public Object getBean() {\n                    return LineFileChooserButton.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"convertToRelativePath\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    String location = getLocation();\n                    if (location == null)\n                        return;\n                    try {\n                        setLocation(processPath(FileUtils.toAbsolute(Path.of(getLocation()))));\n                    } catch (IllegalArgumentException ignored) {\n                    }\n                }\n            };\n        return convertToRelativePath;\n    }\n\n    public boolean isConvertToRelativePath() {\n        return convertToRelativePath != null && convertToRelativePath.get();\n    }\n\n    public void setConvertToRelativePath(boolean convertToRelativePath) {\n        convertToRelativePathProperty().set(convertToRelativePath);\n    }\n\n    public enum Type {\n        OPEN_FILE,\n        OPEN_DIRECTORY,\n        SAVE_FILE\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LinePane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.ObjectPropertyBase;\nimport javafx.scene.Node;\n\n/// @author Glavo\npublic class LinePane extends LineComponent {\n    private static final String DEFAULT_STYLE_CLASS = \"line-pane\";\n\n    public LinePane() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    private ObjectProperty<Node> right;\n\n    public ObjectProperty<Node> rightProperty() {\n        if (right == null) {\n            right = new ObjectPropertyBase<>() {\n                @Override\n                public Object getBean() {\n                    return LinePane.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"right\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    setNode(IDX_TRAILING, get());\n                }\n            };\n        }\n        return right;\n    }\n\n    public Node getRight() {\n        return rightProperty().get();\n    }\n\n    public void setRight(Node right) {\n        rightProperty().set(right);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineSelectButton.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXPopup;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ListProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleListProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.input.*;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.util.javafx.MappedObservableList;\n\nimport java.util.Collection;\nimport java.util.Objects;\nimport java.util.function.Function;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;\n\n/// @author Glavo\npublic final class LineSelectButton<T> extends LineButton {\n\n    private static final String DEFAULT_STYLE_CLASS = \"line-select-button\";\n    private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass(\"selected\");\n\n    private JFXPopup popup;\n\n    public LineSelectButton() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n\n        InvalidationListener updateTrailingText = observable -> {\n            T value = getValue();\n            if (value != null) {\n                Function<T, String> converter = getConverter();\n                setTrailingText(converter != null ? converter.apply(value) : value.toString());\n            } else {\n                setTrailingText(null);\n            }\n        };\n        converterProperty().addListener(updateTrailingText);\n        valueProperty().addListener(updateTrailingText);\n\n        setTrailingIcon(SVG.UNFOLD_MORE);\n\n        ripplerContainer.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {\n            if (event.getButton() == MouseButton.SECONDARY) {\n                if (popup != null)\n                    popup.hide();\n                event.consume();\n            }\n        });\n    }\n\n    @Override\n    public void fire() {\n        super.fire();\n        if (popup == null) {\n            PopupMenu popupMenu = new PopupMenu();\n            this.popup = new JFXPopup(popupMenu);\n\n            popupMenu.addEventHandler(KeyEvent.KEY_PRESSED, this::handleKeyEvent);\n\n            ripplerContainer.addEventFilter(ScrollEvent.ANY, ignored -> popup.hide());\n\n            Bindings.bindContent(popupMenu.getContent(), MappedObservableList.create(itemsProperty(), item -> {\n                VBox vbox = new VBox();\n\n                var itemTitleLabel = new Label();\n                itemTitleLabel.getStyleClass().add(\"title-label\");\n                itemTitleLabel.textProperty().bind(Bindings.createStringBinding(() -> {\n                    if (item == null)\n                        return \"\";\n\n                    Function<T, String> converter = getConverter();\n                    return converter != null ? converter.apply(item) : Objects.toString(item, \"\");\n                }, converterProperty()));\n\n                var itemSubtitleLabel = new Label();\n                itemSubtitleLabel.getStyleClass().add(\"subtitle-label\");\n                itemSubtitleLabel.textProperty().bind(Bindings.createStringBinding(() -> {\n                    Function<T, String> descriptionConverter = getDescriptionConverter();\n                    return descriptionConverter != null ? descriptionConverter.apply(item) : \"\";\n                }, descriptionConverterProperty()));\n\n                FXUtils.onChangeAndOperate(itemSubtitleLabel.textProperty(), text -> {\n                    if (text == null || text.isEmpty()) {\n                        vbox.getChildren().setAll(itemTitleLabel);\n                    } else {\n                        vbox.getChildren().setAll(itemTitleLabel, itemSubtitleLabel);\n                    }\n                });\n\n                var wrapper = new StackPane(vbox);\n                wrapper.setAlignment(Pos.CENTER_LEFT);\n                wrapper.getStyleClass().add(\"menu-container\");\n                wrapper.setMouseTransparent(true);\n                RipplerContainer ripplerContainer = new RipplerContainer(wrapper);\n                FXUtils.onClicked(ripplerContainer, () -> {\n                    setValue(item);\n                    popup.hide();\n                });\n\n                FXUtils.onChangeAndOperate(valueProperty(),\n                        value -> wrapper.pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, Objects.equals(value, item)));\n\n                return ripplerContainer;\n            }));\n\n            popup.showingProperty().addListener((observable, oldValue, newValue) ->\n                    ripplerContainer.getRippler().setRipplerDisabled(newValue));\n        }\n\n        if (popup.isShowing()) {\n            popup.hide();\n        } else {\n            JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(this, popup);\n            popup.show(this, vPosition, JFXPopup.PopupHPosition.RIGHT,\n                    0,\n                    vPosition == JFXPopup.PopupVPosition.TOP ? this.getHeight() : -this.getHeight(),\n                    true);\n        }\n    }\n\n    private final ObjectProperty<T> value = new SimpleObjectProperty<>(this, \"value\");\n\n    public ObjectProperty<T> valueProperty() {\n        return value;\n    }\n\n    public T getValue() {\n        return valueProperty().get();\n    }\n\n    public void setValue(T value) {\n        valueProperty().set(value);\n    }\n\n    private final ObjectProperty<Function<T, String>> converter = new SimpleObjectProperty<>(this, \"converter\");\n\n    public ObjectProperty<Function<T, String>> converterProperty() {\n        return converter;\n    }\n\n    public Function<T, String> getConverter() {\n        return converterProperty().get();\n    }\n\n    public void setConverter(Function<T, String> value) {\n        converterProperty().set(value);\n    }\n\n    private ObjectProperty<Function<T, String>> descriptionConverter;\n\n    public ObjectProperty<Function<T, String>> descriptionConverterProperty() {\n        if (descriptionConverter == null)\n            descriptionConverter = new SimpleObjectProperty<>(this, \"descriptionConverter\");\n        return descriptionConverter;\n    }\n\n    public Function<T, String> getDescriptionConverter() {\n        return descriptionConverterProperty().get();\n    }\n\n    public void setDescriptionConverter(Function<T, String> value) {\n        descriptionConverterProperty().set(value);\n    }\n\n    private final ListProperty<T> items = new SimpleListProperty<>(this, \"items\", FXCollections.emptyObservableList());\n\n    public ListProperty<T> itemsProperty() {\n        return items;\n    }\n\n    public void setItems(ObservableList<T> value) {\n        itemsProperty().set(value);\n    }\n\n    public void setItems(Collection<T> value) {\n        if (value instanceof ObservableList<T> observableList) {\n            this.setItems(observableList);\n        } else {\n            this.setItems(FXCollections.observableArrayList(value));\n        }\n    }\n\n    @SafeVarargs\n    public final void setItems(T... values) {\n        this.setItems(FXCollections.observableArrayList(values));\n    }\n\n    public ObservableList<T> getItems() {\n        return items.get();\n    }\n\n    private void handleKeyEvent(KeyEvent event) {\n        ObservableList<T> list = getItems();\n        if (list == null || list.isEmpty()) return;\n        int index = list.indexOf(getValue());\n\n        var code = event.getCode();\n\n        if (code == KeyCode.UP) {\n            if (index > 0) {\n                setValue(list.get(index - 1));\n            } else {\n                setValue(list.get(list.size() - 1));\n            }\n            event.consume();\n        } else if (code == KeyCode.DOWN) {\n            if (index < list.size() - 1) {\n                setValue(list.get(index + 1));\n            } else {\n                setValue(list.get(0));\n            }\n            event.consume();\n        } else if (code == KeyCode.ENTER || code == KeyCode.ESCAPE) {\n            if (popup != null && popup.isShowing()) {\n                popup.hide();\n                event.consume();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineTextPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.StringProperty;\nimport javafx.beans.property.StringPropertyBase;\nimport javafx.scene.control.Label;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\n/// @author Glavo\npublic final class LineTextPane extends LineComponent {\n\n    private static final String DEFAULT_STYLE_CLASS = \"line-text-pane\";\n\n    public LineTextPane() {\n        this.getStyleClass().addAll(DEFAULT_STYLE_CLASS);\n    }\n\n    private StringProperty text;\n\n    public StringProperty textProperty() {\n        if (text == null) {\n            text = new StringPropertyBase() {\n                private Label rightLabel;\n\n                @Override\n                public Object getBean() {\n                    return LineTextPane.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"text\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    String text = get();\n                    if (text != null && !text.isEmpty()) {\n                        if (rightLabel == null) {\n                            rightLabel = FXUtils.newSafeTruncatedLabel();\n                            FXUtils.copyOnDoubleClick(rightLabel);\n                        }\n                        rightLabel.setText(text);\n                        setNode(IDX_TRAILING, rightLabel);\n                    } else {\n                        if (rightLabel != null)\n                            rightLabel.setText(null);\n\n                        setNode(IDX_TRAILING, null);\n                    }\n                }\n            };\n        }\n        return text;\n    }\n\n    public String getText() {\n        return textProperty().get();\n    }\n\n    public void setText(String text) {\n        textProperty().set(text);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineToggleButton.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXToggleButton;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic final class LineToggleButton extends LineButtonBase {\n    private static final String DEFAULT_STYLE_CLASS = \"line-toggle-button\";\n\n    private final JFXToggleButton toggleButton;\n\n    public LineToggleButton() {\n        this.getStyleClass().add(DEFAULT_STYLE_CLASS);\n\n        this.toggleButton = new JFXToggleButton();\n        toggleButton.selectedProperty().bindBidirectional(selectedProperty());\n        toggleButton.setSize(8);\n        FXUtils.setLimitHeight(toggleButton, 30);\n        setNode(IDX_TRAILING, toggleButton);\n    }\n\n    @Override\n    public void fire() {\n        toggleButton.fire();\n        super.fire();\n    }\n\n    private final BooleanProperty selected = new SimpleBooleanProperty(this, \"selected\");\n\n    public BooleanProperty selectedProperty() {\n        return selected;\n    }\n\n    public boolean isSelected() {\n        return selectedProperty().get();\n    }\n\n    public void setSelected(boolean selected) {\n        selectedProperty().set(selected);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MDListCell.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXListView;\nimport javafx.beans.binding.DoubleBinding;\nimport javafx.css.PseudoClass;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.StackPane;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic abstract class MDListCell<T> extends ListCell<T> {\n    private static final PseudoClass SELECTED = PseudoClass.getPseudoClass(\"selected\");\n\n    private final StackPane container = new StackPane();\n    private final StackPane root = new StackPane();\n\n    public MDListCell(JFXListView<T> listView) {\n\n        setText(null);\n        setGraphic(null);\n\n        root.getStyleClass().add(\"md-list-cell\");\n        RipplerContainer ripplerContainer = new RipplerContainer(container);\n        root.getChildren().setAll(ripplerContainer);\n\n        Region clippedContainer = (Region) listView.lookup(\".clipped-container\");\n        setPrefWidth(0);\n        if (clippedContainer != null) {\n            DoubleBinding converted = clippedContainer.widthProperty().subtract(1);\n\n            maxWidthProperty().bind(converted);\n            prefWidthProperty().bind(converted);\n            minWidthProperty().bind(converted);\n        }\n    }\n\n    @Override\n    protected void updateItem(T item, boolean empty) {\n        super.updateItem(item, empty);\n\n        updateControl(item, empty);\n        if (empty) {\n            setGraphic(null);\n        } else {\n            setGraphic(root);\n        }\n    }\n\n    protected StackPane getContainer() {\n        return container;\n    }\n\n    protected void setSelectable() {\n        FXUtils.onChangeAndOperate(selectedProperty(), selected -> {\n            root.pseudoClassStateChanged(SELECTED, selected);\n        });\n    }\n\n    protected abstract void updateControl(T item, boolean empty);\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuSeparator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.geometry.Insets;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Rectangle;\n\npublic class MenuSeparator extends StackPane {\n\n    public MenuSeparator() {\n        Rectangle rect = new Rectangle();\n        rect.widthProperty().bind(widthProperty().add(-14));\n        rect.setHeight(1);\n        rect.setFill(Color.GRAY);\n        maxHeightProperty().set(10);\n        setPadding(new Insets(3));\n        getChildren().setAll(rect);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MenuUpDownButton.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.HBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\n\npublic class MenuUpDownButton extends Control {\n\n    private final BooleanProperty selected = new SimpleBooleanProperty(this, \"selected\");\n    private final StringProperty text = new SimpleStringProperty(this, \"text\");\n\n    public MenuUpDownButton() {\n        this.getStyleClass().add(\"menu-up-down-button\");\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new MenuUpDownButtonSkin(this);\n    }\n\n    public boolean isSelected() {\n        return selected.get();\n    }\n\n    public BooleanProperty selectedProperty() {\n        return selected;\n    }\n\n    public void setSelected(boolean selected) {\n        this.selected.set(selected);\n    }\n\n    public String getText() {\n        return text.get();\n    }\n\n    public StringProperty textProperty() {\n        return text;\n    }\n\n    public void setText(String text) {\n        this.text.set(text);\n    }\n\n    private static class MenuUpDownButtonSkin extends SkinBase<MenuUpDownButton> {\n\n        protected MenuUpDownButtonSkin(MenuUpDownButton control) {\n            super(control);\n\n            HBox content = new HBox(8);\n            content.setAlignment(Pos.CENTER);\n            Label label = new Label();\n            label.textProperty().bind(control.text);\n\n            Node up = SVG.ARROW_DROP_UP.createIcon(16);\n            Node down = SVG.ARROW_DROP_DOWN.createIcon(16);\n\n            JFXButton button = new JFXButton();\n            button.setGraphic(content);\n            button.setOnAction(e -> {\n                control.selected.set(!control.isSelected());\n            });\n\n            FXUtils.onChangeAndOperate(control.selected, selected -> {\n                if (selected) {\n                    content.getChildren().setAll(label, up);\n                } else {\n                    content.getChildren().setAll(label, down);\n                }\n            });\n\n            getChildren().setAll(button);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.event.ActionEvent;\nimport javafx.scene.Node;\nimport javafx.scene.control.ButtonBase;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.TextFlow;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Locale;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class MessageDialogPane extends HBox {\n\n    public enum MessageType {\n        ERROR(SVG.ERROR),\n        INFO(SVG.INFO),\n        WARNING(SVG.WARNING),\n        QUESTION(SVG.HELP),\n        SUCCESS(SVG.CHECK_CIRCLE);\n\n        private final SVG icon;\n\n        MessageType(SVG icon) {\n            this.icon = icon;\n        }\n\n        public SVG getIcon() {\n            return icon;\n        }\n\n        public String getDisplayName() {\n            return i18n(\"message.\" + name().toLowerCase(Locale.ROOT));\n        }\n    }\n\n    private final HBox actions;\n\n    private @Nullable ButtonBase cancelButton;\n\n    public MessageDialogPane(@NotNull String text, @Nullable String title, @NotNull MessageType type) {\n        this.setSpacing(16);\n        this.getStyleClass().add(\"jfx-dialog-layout\");\n\n        Label graphic = new Label();\n        graphic.setTranslateX(10);\n        graphic.setTranslateY(10);\n        graphic.setMinSize(40, 40);\n        graphic.setMaxSize(40, 40);\n        graphic.setGraphic(type.getIcon().createIcon(40));\n\n        VBox vbox = new VBox();\n        HBox.setHgrow(vbox, Priority.ALWAYS);\n        {\n            StackPane titlePane = new StackPane();\n            titlePane.getStyleClass().addAll(\"jfx-layout-heading\", \"title\");\n            titlePane.getChildren().setAll(new Label(title != null ? title : type.getDisplayName()));\n\n            StackPane content = new StackPane();\n            content.getStyleClass().add(\"jfx-layout-body\");\n            EnhancedTextFlow textFlow = new EnhancedTextFlow(text);\n            textFlow.setStyle(\"-fx-font-size: 14px;\");\n            if (textFlow.computePrefHeight(400.0) <= 350.0)\n                content.getChildren().setAll(textFlow);\n            else {\n                ScrollPane scrollPane = new ScrollPane(textFlow);\n                FXUtils.smoothScrolling(scrollPane);\n                scrollPane.setPrefHeight(350);\n                VBox.setVgrow(scrollPane, Priority.ALWAYS);\n                scrollPane.setFitToWidth(true);\n                content.getChildren().setAll(scrollPane);\n            }\n\n            actions = new HBox();\n            actions.getStyleClass().add(\"jfx-layout-actions\");\n\n            vbox.getChildren().setAll(titlePane, content, actions);\n        }\n\n        this.getChildren().setAll(graphic, vbox);\n\n        onEscPressed(this, () -> {\n            if (cancelButton != null) {\n                cancelButton.fire();\n            }\n        });\n    }\n\n    public void addButton(Node btn) {\n        btn.addEventHandler(ActionEvent.ACTION, e -> fireEvent(new DialogCloseEvent()));\n        actions.getChildren().add(btn);\n    }\n\n    public void setCancelButton(@Nullable ButtonBase btn) {\n        cancelButton = btn;\n    }\n\n    public ButtonBase getCancelButton() {\n        return cancelButton;\n    }\n\n    private static final class EnhancedTextFlow extends TextFlow {\n        EnhancedTextFlow(String text) {\n            this.getChildren().setAll(FXUtils.parseSegment(text, Controllers::onHyperlinkAction));\n        }\n\n        @Override\n        public double computePrefHeight(double width) {\n            return super.computePrefHeight(width);\n        }\n    }\n\n    public static class Builder {\n        private final MessageDialogPane dialog;\n\n        public Builder(String text, String title, MessageType type) {\n            this.dialog = new MessageDialogPane(text, title, type);\n        }\n\n        public Builder addHyperLink(String text, String externalLink) {\n            JFXHyperlink link = new JFXHyperlink(text);\n            link.setExternalLink(externalLink);\n            dialog.actions.getChildren().add(link);\n            return this;\n        }\n\n        public Builder addAction(Node actionNode) {\n            dialog.addButton(actionNode);\n            actionNode.getStyleClass().add(\"dialog-accept\");\n            return this;\n        }\n\n        public Builder addAction(String text, @Nullable Runnable action) {\n            JFXButton btnAction = new JFXButton(text);\n            btnAction.getStyleClass().add(\"dialog-accept\");\n            if (action != null) {\n                btnAction.setOnAction(e -> action.run());\n            }\n            dialog.addButton(btnAction);\n            return this;\n        }\n\n        public Builder ok(@Nullable Runnable ok) {\n            JFXButton btnOk = new JFXButton(i18n(\"button.ok\"));\n            btnOk.getStyleClass().add(\"dialog-accept\");\n            if (ok != null) {\n                btnOk.setOnAction(e -> ok.run());\n            }\n            dialog.addButton(btnOk);\n            dialog.setCancelButton(btnOk);\n            return this;\n        }\n\n        public Builder addCancel(@Nullable Runnable cancel) {\n            return addCancel(i18n(\"button.cancel\"), cancel);\n        }\n\n        public Builder addCancel(String cancelText, @Nullable Runnable cancel) {\n            JFXButton btnCancel = new JFXButton(cancelText);\n            btnCancel.setButtonType(JFXButton.ButtonType.FLAT);\n            btnCancel.getStyleClass().add(\"dialog-cancel\");\n            if (cancel != null) {\n                btnCancel.setOnAction(e -> cancel.run());\n            }\n            dialog.addButton(btnCancel);\n            dialog.setCancelButton(btnCancel);\n            return this;\n        }\n\n        public Builder yesOrNo(@Nullable Runnable yes, @Nullable Runnable no) {\n            JFXButton btnYes = new JFXButton(i18n(\"button.yes\"));\n            btnYes.getStyleClass().add(\"dialog-accept\");\n            if (yes != null) {\n                btnYes.setOnAction(e -> yes.run());\n            }\n            dialog.addButton(btnYes);\n\n            addCancel(i18n(\"button.no\"), no);\n            return this;\n        }\n\n        public Builder actionOrCancel(ButtonBase actionButton, Runnable cancel) {\n            dialog.addButton(actionButton);\n\n            addCancel(cancel);\n            return this;\n        }\n\n        public MessageDialogPane build() {\n            return dialog;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXColorPicker;\nimport com.jfoenix.controls.JFXRadioButton;\nimport com.jfoenix.controls.JFXTextField;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.property.*;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.ColorPicker;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Toggle;\nimport javafx.scene.control.ToggleGroup;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.theme.ThemeColor;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\npublic final class MultiFileItem<T> extends VBox {\n    private final ObjectProperty<T> selectedData = new SimpleObjectProperty<>(this, \"selectedData\");\n    private final ObjectProperty<T> fallbackData = new SimpleObjectProperty<>(this, \"fallbackData\");\n\n    private final ToggleGroup group = new ToggleGroup();\n\n    private Consumer<Toggle> toggleSelectedListener;\n\n    @SuppressWarnings(\"unchecked\")\n    public MultiFileItem() {\n        setPadding(new Insets(0, 0, 10, 0));\n        setSpacing(8);\n\n        group.selectedToggleProperty().addListener((a, b, newValue) -> {\n            selectedData.set(newValue != null ? (T) newValue.getUserData() : null);\n            if (toggleSelectedListener != null)\n                toggleSelectedListener.accept(newValue);\n        });\n        selectedData.addListener((a, b, newValue) -> {\n            Optional<Toggle> selecting = group.getToggles().stream()\n                    .filter(it -> Objects.equals(it.getUserData(), newValue))\n                    .findFirst();\n            if (!selecting.isPresent()) {\n                selecting = group.getToggles().stream()\n                        .filter(it -> it.getUserData() == getFallbackData())\n                        .findFirst();\n            }\n\n            selecting.ifPresent(toggle -> toggle.setSelected(true));\n        });\n    }\n\n    public void loadChildren(Collection<Option<T>> options) {\n        getChildren().setAll(options.stream()\n                .map(option -> option.createItem(group))\n                .collect(Collectors.toList()));\n    }\n\n    public ToggleGroup getGroup() {\n        return group;\n    }\n\n    public void setToggleSelectedListener(Consumer<Toggle> consumer) {\n        toggleSelectedListener = consumer;\n    }\n\n    public T getSelectedData() {\n        return selectedData.get();\n    }\n\n    public ObjectProperty<T> selectedDataProperty() {\n        return selectedData;\n    }\n\n    public void setSelectedData(T selectedData) {\n        this.selectedData.set(selectedData);\n    }\n\n    public T getFallbackData() {\n        return fallbackData.get();\n    }\n\n    public ObjectProperty<T> fallbackDataProperty() {\n        return fallbackData;\n    }\n\n    public void setFallbackData(T fallbackData) {\n        this.fallbackData.set(fallbackData);\n    }\n\n    public static class Option<T> {\n        protected final String title;\n        protected String subtitle;\n        protected String tooltip;\n        protected final T data;\n        protected final BooleanProperty selected = new SimpleBooleanProperty();\n        protected final JFXRadioButton left = new JFXRadioButton();\n\n        public Option(String title, T data) {\n            this.title = title;\n            this.data = data;\n        }\n\n        public T getData() {\n            return data;\n        }\n\n        public String getTitle() {\n            return title;\n        }\n\n        public String getSubtitle() {\n            return subtitle;\n        }\n\n        public Option<T> setSubtitle(String subtitle) {\n            this.subtitle = subtitle;\n            return this;\n        }\n\n        public Option<T> setTooltip(String tooltip) {\n            this.tooltip = tooltip;\n            return this;\n        }\n\n        public boolean isSelected() {\n            return left.isSelected();\n        }\n\n        public BooleanProperty selectedProperty() {\n            return left.selectedProperty();\n        }\n\n        public void setSelected(boolean selected) {\n            left.setSelected(selected);\n        }\n\n        protected Node createItem(ToggleGroup group) {\n            BorderPane pane = new BorderPane();\n            pane.setPadding(new Insets(3));\n            FXUtils.setLimitHeight(pane, 30);\n\n            left.setText(title);\n            BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n            left.setToggleGroup(group);\n            left.setUserData(data);\n            if (StringUtils.isNotBlank(tooltip))\n                FXUtils.installFastTooltip(left, tooltip);\n            pane.setLeft(left);\n\n            if (StringUtils.isNotBlank(subtitle)) {\n                Label center = new Label(subtitle);\n                BorderPane.setAlignment(center, Pos.CENTER_RIGHT);\n                center.setWrapText(true);\n                center.getStyleClass().add(\"subtitle-label\");\n                center.setStyle(\"-fx-font-size: 10;\");\n                center.setPadding(new Insets(0, 0, 0, 15));\n                pane.setCenter(center);\n            }\n\n            return pane;\n        }\n    }\n\n    public static final class StringOption<T> extends Option<T> {\n        private final JFXTextField customField = new JFXTextField();\n\n        public StringOption(String title, T data) {\n            super(title, data);\n        }\n\n        public JFXTextField getCustomField() {\n            return customField;\n        }\n\n        public String getValue() {\n            return customField.getText();\n        }\n\n        public StringProperty valueProperty() {\n            return customField.textProperty();\n        }\n\n        public void setValue(String value) {\n            customField.setText(value);\n        }\n\n        public StringOption<T> bindBidirectional(Property<String> property) {\n            FXUtils.bindString(customField, property);\n            return this;\n        }\n\n        public StringOption<T> setValidators(ValidatorBase... validators) {\n            customField.setValidators(validators);\n            return this;\n        }\n\n        @Override\n        protected Node createItem(ToggleGroup group) {\n            BorderPane pane = new BorderPane();\n            pane.setPadding(new Insets(3));\n            FXUtils.setLimitHeight(pane, 30);\n\n            left.setText(title);\n            BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n            left.setToggleGroup(group);\n            left.setUserData(data);\n            pane.setLeft(left);\n\n            BorderPane.setAlignment(customField, Pos.CENTER_RIGHT);\n            customField.disableProperty().bind(left.selectedProperty().not());\n\n            if (!customField.getValidators().isEmpty()) {\n                FXUtils.setValidateWhileTextChanged(customField, true);\n            }\n\n            pane.setRight(customField);\n\n            return pane;\n        }\n    }\n\n    public static final class FileOption<T> extends Option<T> {\n        private final FileSelector selector = new FileSelector();\n\n        public FileOption(String title, T data) {\n            super(title, data);\n        }\n\n        public String getValue() {\n            return selector.getValue();\n        }\n\n        public StringProperty valueProperty() {\n            return selector.valueProperty();\n        }\n\n        public void setValue(String value) {\n            selector.setValue(value);\n        }\n\n        public FileOption<T> setDirectory(boolean directory) {\n            selector.setDirectory(directory);\n            return this;\n        }\n\n        public FileOption<T> bindBidirectional(Property<String> property) {\n            selector.valueProperty().bindBidirectional(property);\n            return this;\n        }\n\n        public FileOption<T> setChooserTitle(String chooserTitle) {\n            selector.setChooserTitle(chooserTitle);\n            return this;\n        }\n\n        public FileOption<T> addExtensionFilter(FileChooser.ExtensionFilter filter) {\n            selector.getExtensionFilters().add(filter);\n            return this;\n        }\n\n        @Override\n        protected Node createItem(ToggleGroup group) {\n            BorderPane pane = new BorderPane();\n            pane.setPadding(new Insets(3));\n            FXUtils.setLimitHeight(pane, 30);\n\n            left.setText(title);\n            BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n            left.setToggleGroup(group);\n            left.setUserData(data);\n            pane.setLeft(left);\n\n            selector.disableProperty().bind(left.selectedProperty().not());\n            BorderPane.setAlignment(selector, Pos.CENTER_RIGHT);\n            pane.setRight(selector);\n            return pane;\n        }\n    }\n\n    public static final class PaintOption<T> extends Option<T> {\n        private final ColorPicker colorPicker = new JFXColorPicker();\n\n        public PaintOption(String title, T data) {\n            super(title, data);\n        }\n\n        public PaintOption<T> setCustomColors(List<Color> colors) {\n            colorPicker.getCustomColors().setAll(colors);\n            return this;\n        }\n\n        public PaintOption<T> bindBidirectional(Property<Paint> property) {\n            FXUtils.bindPaint(colorPicker, property);\n            return this;\n        }\n\n        public PaintOption<T> bindThemeColorBidirectional(Property<ThemeColor> property) {\n            ThemeColor.bindBidirectional(colorPicker, property);\n            return this;\n        }\n\n        @Override\n        protected Node createItem(ToggleGroup group) {\n            BorderPane pane = new BorderPane();\n            pane.setPadding(new Insets(3));\n            FXUtils.setLimitHeight(pane, 30);\n\n            left.setText(title);\n            BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n            left.setToggleGroup(group);\n            left.setUserData(data);\n            pane.setLeft(left);\n\n            colorPicker.disableProperty().bind(left.selectedProperty().not());\n            BorderPane.setAlignment(colorPicker, Pos.CENTER_RIGHT);\n            pane.setRight(colorPicker);\n            return pane;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.animation.Interpolator;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.event.Event;\nimport javafx.event.EventHandler;\nimport javafx.event.EventType;\nimport javafx.scene.Node;\nimport javafx.scene.layout.Region;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.Motion;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.wizard.Navigation;\n\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Stack;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class Navigator extends TransitionPane {\n    private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Navigator.class.getName() + \".closeListener\";\n\n    private final BooleanProperty backable = new SimpleBooleanProperty(this, \"backable\");\n    private final Stack<Node> stack = new Stack<>();\n    private boolean initialized = false;\n\n    public void init(Node init) {\n        stack.push(init);\n        backable.set(canGoBack());\n        setContent(init, ContainerAnimations.NONE);\n\n        fireEvent(new NavigationEvent(this, init, Navigation.NavigationDirection.START, NavigationEvent.NAVIGATED));\n        if (init instanceof PageAware) ((PageAware) init).onPageShown();\n\n        initialized = true;\n    }\n\n    public void navigate(Node node, AnimationProducer animationProducer) {\n        navigate(node, animationProducer, Motion.SHORT4, Motion.EASE);\n    }\n\n    public void navigate(Node node, AnimationProducer animationProducer, Duration duration, Interpolator interpolator) {\n        FXUtils.checkFxUserThread();\n\n        if (!initialized)\n            throw new IllegalStateException(\"Navigator must have a root page\");\n\n        Node from = stack.peek();\n        if (from == node)\n            return;\n\n        LOG.info(\"Navigate to \" + node);\n\n        stack.push(node);\n        backable.set(canGoBack());\n\n        NavigationEvent navigating = new NavigationEvent(this, from, Navigation.NavigationDirection.NEXT, NavigationEvent.NAVIGATING);\n        fireEvent(navigating);\n        node.fireEvent(navigating);\n\n        node.getProperties().put(\"hmcl.navigator.animation\", animationProducer);\n        setContent(node, animationProducer, duration, interpolator);\n\n        NavigationEvent navigated = new NavigationEvent(this, node, Navigation.NavigationDirection.NEXT, NavigationEvent.NAVIGATED);\n        node.fireEvent(navigated);\n        if (node instanceof PageAware) ((PageAware) node).onPageShown();\n\n        EventHandler<PageCloseEvent> handler = event -> close(node);\n        node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);\n        node.addEventHandler(PageCloseEvent.CLOSE, handler);\n    }\n\n    public void close() {\n        close(stack.peek());\n    }\n\n    public void clear() {\n        while (stack.size() > 1)\n            close(stack.peek());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public void close(Node from) {\n        FXUtils.checkFxUserThread();\n\n        if (!initialized)\n            throw new IllegalStateException(\"Navigator must have a root page\");\n\n        if (stack.peek() != from) {\n            // Allow page to be closed multiple times.\n            LOG.info(\"Closing already closed page: \" + from, new Throwable());\n            return;\n        }\n\n        LOG.info(\"Closed page \" + from);\n\n        Node poppedNode = stack.pop();\n        NavigationEvent exited = new NavigationEvent(this, poppedNode, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.EXITED);\n        poppedNode.fireEvent(exited);\n        if (poppedNode instanceof PageAware pageAware) pageAware.onPageHidden();\n\n        backable.set(canGoBack());\n        Node node = stack.peek();\n\n        NavigationEvent navigating = new NavigationEvent(this, from, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.NAVIGATING);\n        fireEvent(navigating);\n        node.fireEvent(navigating);\n\n        Object obj = from.getProperties().get(\"hmcl.navigator.animation\");\n        if (obj instanceof AnimationProducer animationProducer) {\n            setContent(node, Objects.requireNonNullElse(animationProducer.opposite(), animationProducer));\n        } else {\n            setContent(node, ContainerAnimations.NONE);\n        }\n\n        NavigationEvent navigated = new NavigationEvent(this, node, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.NAVIGATED);\n        node.fireEvent(navigated);\n\n        Optional.ofNullable(from.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))\n                .ifPresent(handler -> from.removeEventHandler(PageCloseEvent.CLOSE, (EventHandler<PageCloseEvent>) handler));\n    }\n\n    public Node getCurrentPage() {\n        return stack.peek();\n    }\n\n    public boolean canGoBack() {\n        return stack.size() > 1;\n    }\n\n    public boolean isBackable() {\n        return backable.get();\n    }\n\n    public BooleanProperty backableProperty() {\n        return backable;\n    }\n\n    public void setBackable(boolean backable) {\n        this.backable.set(backable);\n    }\n\n    public int size() {\n        return stack.size();\n    }\n\n    @Override\n    public void setContent(Node newView, AnimationProducer transition, Duration duration, Interpolator interpolator) {\n        super.setContent(newView, transition, duration, interpolator);\n\n        if (newView instanceof Region region) {\n            region.setMinSize(0, 0);\n            FXUtils.setOverflowHidden(region);\n        }\n    }\n\n    public EventHandler<NavigationEvent> getOnNavigated() {\n        return onNavigated.get();\n    }\n\n    public ObjectProperty<EventHandler<NavigationEvent>> onNavigatedProperty() {\n        return onNavigated;\n    }\n\n    public void setOnNavigated(EventHandler<NavigationEvent> onNavigated) {\n        this.onNavigated.set(onNavigated);\n    }\n\n    private final ObjectProperty<EventHandler<NavigationEvent>> onNavigated = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, \"onNavigated\") {\n        @Override\n        protected void invalidated() {\n            setEventHandler(NavigationEvent.NAVIGATED, get());\n        }\n    };\n\n    public EventHandler<NavigationEvent> getOnNavigating() {\n        return onNavigating.get();\n    }\n\n    public ObjectProperty<EventHandler<NavigationEvent>> onNavigatingProperty() {\n        return onNavigating;\n    }\n\n    public void setOnNavigating(EventHandler<NavigationEvent> onNavigating) {\n        this.onNavigating.set(onNavigating);\n    }\n\n    private final ObjectProperty<EventHandler<NavigationEvent>> onNavigating = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, \"onNavigating\") {\n        @Override\n        protected void invalidated() {\n            setEventHandler(NavigationEvent.NAVIGATING, get());\n        }\n    };\n\n    public static final class NavigationEvent extends Event {\n        public static final EventType<NavigationEvent> EXITED = new EventType<>(\"EXITED\");\n        public static final EventType<NavigationEvent> NAVIGATED = new EventType<>(\"NAVIGATED\");\n        public static final EventType<NavigationEvent> NAVIGATING = new EventType<>(\"NAVIGATING\");\n\n        private final Navigator source;\n        private final Node node;\n        private final Navigation.NavigationDirection direction;\n\n        public NavigationEvent(Navigator source, Node target, Navigation.NavigationDirection direction, EventType<? extends Event> eventType) {\n            super(source, target, eventType);\n\n            this.source = source;\n            this.node = target;\n            this.direction = direction;\n        }\n\n        @Override\n        public Navigator getSource() {\n            return source;\n        }\n\n        public Node getNode() {\n            return node;\n        }\n\n        public Navigation.NavigationDirection getDirection() {\n            return direction;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NoPaddingComponent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\n/// Marker interface for no padding in [ComponentList].\n///\n/// @author Glavo\ninterface NoPaddingComponent {\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NoneMultipleSelectionModel.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.MultipleSelectionModel;\n\npublic class NoneMultipleSelectionModel<T> extends MultipleSelectionModel<T> {\n\n    public NoneMultipleSelectionModel() {\n    }\n\n    @Override\n    public ObservableList<Integer> getSelectedIndices() {\n        return FXCollections.emptyObservableList();\n    }\n\n    @Override\n    public ObservableList<T> getSelectedItems() {\n        return FXCollections.emptyObservableList();\n    }\n\n    @Override\n    public void selectIndices(int index, int... indices) {\n    }\n\n    @Override\n    public void selectAll() {\n    }\n\n    @Override\n    public void clearAndSelect(int index) {\n    }\n\n    @Override\n    public void select(int index) {\n    }\n\n    @Override\n    public void select(T obj) {\n    }\n\n    @Override\n    public void clearSelection(int index) {\n    }\n\n    @Override\n    public void clearSelection() {\n    }\n\n    @Override\n    public boolean isSelected(int index) {\n        return false;\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return true;\n    }\n\n    @Override\n    public void selectPrevious() {\n    }\n\n    @Override\n    public void selectNext() {\n    }\n\n    @Override\n    public void selectFirst() {\n    }\n\n    @Override\n    public void selectLast() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberValidator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.NamedArg;\nimport javafx.scene.control.TextInputControl;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\n\npublic class NumberValidator extends ValidatorBase {\n    private final boolean nullable;\n\n    public NumberValidator() {\n        this(false);\n    }\n\n    public NumberValidator(@NamedArg(\"nullable\") boolean nullable) {\n        this.nullable = nullable;\n    }\n\n    public NumberValidator(@NamedArg(\"message\") String message, @NamedArg(\"nullable\") boolean nullable) {\n        super(message);\n        this.nullable = nullable;\n    }\n\n    @Override\n    protected void eval() {\n        if (srcControl.get() instanceof TextInputControl) {\n            evalTextInputField();\n        }\n    }\n\n    private void evalTextInputField() {\n        TextInputControl textField = ((TextInputControl) srcControl.get());\n\n        if (StringUtils.isBlank(textField.getText()))\n            hasErrors.set(!nullable);\n        else\n            hasErrors.set(Lang.toIntOrNull(textField.getText()) == null);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionsList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.CssMetaData;\nimport javafx.css.Styleable;\nimport javafx.css.StyleableObjectProperty;\nimport javafx.css.StyleableProperty;\nimport javafx.css.converter.InsetsConverter;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Skin;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n// TODO: We plan to replace ComponentList with this class, but we need to address some issues first\n\n/// @author Glavo\npublic final class OptionsList extends Control {\n    public OptionsList() {\n        this.getStyleClass().add(\"options-list\");\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new OptionsListSkin(this);\n    }\n\n    private final ObservableList<Element> elements = FXCollections.observableArrayList();\n\n    public ObservableList<Element> getElements() {\n        return elements;\n    }\n\n    public void addTitle(String title) {\n        elements.add(new Title(title));\n    }\n\n    public void addNode(Node node) {\n        elements.add(new NodeElement(node));\n    }\n\n    public void addListElement(@NotNull Node node) {\n        elements.add(new ListElement(node));\n    }\n\n    public void addListElements(@NotNull Node... nodes) {\n        for (Node node : nodes) {\n            elements.add(new ListElement(node));\n        }\n    }\n\n    private final StyleableObjectProperty<Insets> contentPadding = new StyleableObjectProperty<>() {\n        @Override\n        public Object getBean() {\n            return OptionsList.this;\n        }\n\n        @Override\n        public String getName() {\n            return \"contentPadding\";\n        }\n\n        @Override\n        public javafx.css.CssMetaData<OptionsList, Insets> getCssMetaData() {\n            return StyleableProperties.CONTENT_PADDING;\n        }\n    };\n\n    public StyleableObjectProperty<Insets> contentPaddingProperty() {\n        return contentPadding;\n    }\n\n    public Insets getContentPadding() {\n        return contentPaddingProperty().get();\n    }\n\n    public void setContentPadding(Insets padding) {\n        contentPaddingProperty().set(padding);\n    }\n\n    private static final class StyleableProperties {\n        private static final CssMetaData<OptionsList, Insets> CONTENT_PADDING = new CssMetaData<>(\"-jfx-content-padding\", InsetsConverter.getInstance()) {\n            @Override\n            public boolean isSettable(OptionsList styleable) {\n                return styleable.contentPadding == null || !styleable.contentPadding.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Insets> getStyleableProperty(OptionsList styleable) {\n                return styleable.contentPaddingProperty();\n            }\n        };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n        static {\n            List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData());\n            Collections.addAll(styleables, CONTENT_PADDING);\n            STYLEABLES = List.copyOf(styleables);\n        }\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.STYLEABLES;\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static abstract class Element {\n        protected Node node;\n\n        Node getNode() {\n            if (node == null)\n                node = createNode();\n            return node;\n        }\n\n        protected abstract Node createNode();\n    }\n\n    public static final class Title extends Element {\n        private final @NotNull String title;\n\n        public Title(@NotNull String title) {\n            this.title = title;\n        }\n\n        @Override\n        protected Node createNode() {\n            Label label = new Label(title);\n            label.setPadding(new Insets(8, 0, 8, 0));\n            return label;\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            return this == obj || obj instanceof Title that && Objects.equals(this.title, that.title);\n        }\n\n        @Override\n        public int hashCode() {\n            return title.hashCode();\n        }\n\n        @Override\n        public String toString() {\n            return \"Title[%s]\".formatted(title);\n        }\n\n    }\n\n    public static final class NodeElement extends Element {\n        public NodeElement(@NotNull Node node) {\n            this.node = node;\n        }\n\n        @Override\n        protected Node createNode() {\n            return node;\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            return this == obj || obj instanceof NodeElement that && this.node.equals(that.node);\n        }\n\n        @Override\n        public int hashCode() {\n            return node.hashCode();\n        }\n\n        @Override\n        public String toString() {\n            return \"NodeElement[node=%s]\".formatted(node);\n        }\n    }\n\n    public static final class ListElement extends Element {\n        private final Node original;\n\n        public ListElement(@NotNull Node node) {\n            this.original = node;\n        }\n\n        @Override\n        protected Node createNode() {\n            if (original instanceof ComponentSublist sublist) {\n                return new ComponentSublistWrapper(sublist);\n            } else {\n                return original;\n            }\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            return this == obj || obj instanceof ListElement that && this.original.equals(that.original);\n        }\n\n        @Override\n        public int hashCode() {\n            return original.hashCode();\n        }\n\n        @Override\n        public String toString() {\n            return \"ListElement[node=%s]\".formatted(original);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionsListSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXListView;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.ObjectBinding;\nimport javafx.collections.ObservableList;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.StackPane;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\n/// @author Glavo\npublic final class OptionsListSkin extends SkinBase<OptionsList> {\n\n    private final JFXListView<OptionsList.Element> listView;\n    private final ObjectBinding<ContentPaddings> contentPaddings;\n\n    OptionsListSkin(OptionsList control) {\n        super(control);\n\n        this.listView = new JFXListView<>();\n        listView.setItems(control.getElements());\n        listView.setCellFactory(listView1 -> new Cell());\n\n        this.contentPaddings = Bindings.createObjectBinding(() -> {\n            Insets padding = control.getContentPadding();\n            return padding == null ? ContentPaddings.EMPTY : new ContentPaddings(\n                    new Insets(padding.getTop(), padding.getRight(), 0, padding.getLeft()),\n                    new Insets(0, padding.getRight(), padding.getBottom(), padding.getLeft()),\n                    new Insets(0, padding.getRight(), 0, padding.getLeft())\n            );\n        }, control.contentPaddingProperty());\n\n        this.getChildren().setAll(listView);\n    }\n\n    private record ContentPaddings(Insets first, Insets last, Insets middle) {\n        static final ContentPaddings EMPTY = new ContentPaddings(Insets.EMPTY, Insets.EMPTY, Insets.EMPTY);\n    }\n\n    private final class Cell extends ListCell<OptionsList.Element> {\n        private static final PseudoClass PSEUDO_CLASS_FIRST = PseudoClass.getPseudoClass(\"first\");\n        private static final PseudoClass PSEUDO_CLASS_LAST = PseudoClass.getPseudoClass(\"last\");\n\n        @SuppressWarnings(\"FieldCanBeLocal\")\n        private final InvalidationListener updateStyleListener = o -> updateStyle();\n\n        private StackPane wrapper;\n\n        public Cell() {\n            FXUtils.limitCellWidth(listView, this);\n\n            WeakInvalidationListener weakListener = new WeakInvalidationListener(updateStyleListener);\n            listView.itemsProperty().addListener((o, oldValue, newValue) -> {\n                if (oldValue != null)\n                    oldValue.removeListener(weakListener);\n                if (newValue != null)\n                    newValue.addListener(weakListener);\n\n                weakListener.invalidated(o);\n            });\n            itemProperty().addListener(weakListener);\n            contentPaddings.addListener(weakListener);\n        }\n\n        @Override\n        protected void updateItem(OptionsList.Element item, boolean empty) {\n            super.updateItem(item, empty);\n\n            if (empty || item == null) {\n                setGraphic(null);\n            } else if (item instanceof OptionsList.ListElement element) {\n                if (wrapper == null)\n                    wrapper = createWrapper();\n                else\n                    wrapper.getStyleClass().remove(\"no-padding\");\n\n                Node node = element.getNode();\n                if (node instanceof NoPaddingComponent || node.getProperties().containsKey(\"ComponentList.noPadding\"))\n                    wrapper.getStyleClass().add(\"no-padding\");\n\n                wrapper.getChildren().setAll(node);\n\n                setGraphic(wrapper);\n            } else {\n                setGraphic(item.getNode());\n            }\n\n            updateStyle();\n        }\n\n        private StackPane createWrapper() {\n            var wrapper = new StackPane();\n            wrapper.setAlignment(Pos.CENTER_LEFT);\n            wrapper.getStyleClass().add(\"options-list-item\");\n            updateStyle();\n            return wrapper;\n        }\n\n        private void updateStyle() {\n            OptionsList.Element item = getItem();\n            int index = getIndex();\n            ObservableList<OptionsList.Element> items = getListView().getItems();\n\n            if (item == null || index < 0 || index >= items.size()) {\n                this.setPadding(Insets.EMPTY);\n                return;\n            }\n\n            boolean isFirst = index == 0;\n            boolean isLast = index == items.size() - 1;\n\n            ContentPaddings paddings = contentPaddings.get();\n            if (isFirst) {\n                this.setPadding(paddings.first);\n            } else if (isLast) {\n                this.setPadding(paddings.last);\n            } else {\n                this.setPadding(paddings.middle);\n            }\n\n            if (item instanceof OptionsList.ListElement && wrapper != null) {\n                wrapper.pseudoClassStateChanged(PSEUDO_CLASS_FIRST, isFirst || !(items.get(index - 1) instanceof OptionsList.ListElement));\n                wrapper.pseudoClassStateChanged(PSEUDO_CLASS_LAST, isLast || !(items.get(index + 1) instanceof OptionsList.ListElement));\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/PageAware.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\npublic interface PageAware {\n    default void onPageShown() {\n    }\n\n    default void onPageHidden() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/PageCloseEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.event.Event;\nimport javafx.event.EventTarget;\nimport javafx.event.EventType;\n\n/**\n * Indicates a close operation on the navigator page.\n *\n * @author huangyuhui\n */\npublic class PageCloseEvent extends Event {\n\n    public static final EventType<PageCloseEvent> CLOSE = new EventType<>(\"PAGE_CLOSE\");\n\n    public PageCloseEvent() {\n        super(CLOSE);\n    }\n\n    public PageCloseEvent(Object source, EventTarget target) {\n        super(source, target, CLOSE);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/PopupMenu.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.When;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic class PopupMenu extends Control {\n\n    private final ObservableList<Node> content = FXCollections.observableArrayList();\n    private final BooleanProperty alwaysShowingVBar = new SimpleBooleanProperty();\n\n    public PopupMenu() {\n        getStyleClass().add(\"popup-menu\");\n    }\n\n    public ObservableList<Node> getContent() {\n        return content;\n    }\n\n    public boolean isAlwaysShowingVBar() {\n        return alwaysShowingVBar.get();\n    }\n\n    public BooleanProperty alwaysShowingVBarProperty() {\n        return alwaysShowingVBar;\n    }\n\n    public void setAlwaysShowingVBar(boolean alwaysShowingVBar) {\n        this.alwaysShowingVBar.set(alwaysShowingVBar);\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new PopupMenuSkin();\n    }\n\n    public static Node wrapPopupMenuItem(Node node) {\n        StackPane pane = new StackPane();\n        pane.getChildren().setAll(node);\n        pane.getStyleClass().add(\"menu-container\");\n        node.setMouseTransparent(true);\n        return new RipplerContainer(pane);\n    }\n\n    private class PopupMenuSkin extends SkinBase<PopupMenu> {\n\n        protected PopupMenuSkin() {\n            super(PopupMenu.this);\n\n            ScrollPane scrollPane = new ScrollPane();\n            scrollPane.setFitToHeight(true);\n            scrollPane.setFitToWidth(true);\n            scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);\n            scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);\n            scrollPane.vbarPolicyProperty().bind(new When(alwaysShowingVBar)\n                    .then(ScrollPane.ScrollBarPolicy.ALWAYS)\n                    .otherwise(ScrollPane.ScrollBarPolicy.AS_NEEDED));\n\n            VBox content = new VBox();\n            content.getStyleClass().add(\"popup-menu-content\");\n            Bindings.bindContent(content.getChildren(), PopupMenu.this.getContent());\n            scrollPane.setContent(content);\n\n            FXUtils.smoothScrolling(scrollPane);\n\n            getChildren().setAll(scrollPane);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/PromptDialogPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXCheckBox;\nimport com.jfoenix.controls.JFXComboBox;\nimport com.jfoenix.controls.JFXTextField;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Insets;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.HBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.FutureCallback;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\n\npublic class PromptDialogPane extends DialogPane {\n    private final CompletableFuture<List<Builder.Question<?>>> future = new CompletableFuture<>();\n\n    private final Builder builder;\n\n    public PromptDialogPane(Builder builder) {\n        this.builder = builder;\n        setTitle(builder.title);\n        setPrefWidth(560);\n\n        GridPane body = new GridPane();\n        body.setVgap(8);\n        body.setHgap(16);\n        body.getColumnConstraints().setAll(new ColumnConstraints(), FXUtils.getColumnHgrowing());\n        setBody(body);\n        List<BooleanBinding> bindings = new ArrayList<>();\n        int rowIndex = 0;\n        for (Builder.Question<?> question : builder.questions) {\n            if (question instanceof Builder.StringQuestion) {\n                Builder.StringQuestion stringQuestion = (Builder.StringQuestion) question;\n                JFXTextField textField = new JFXTextField();\n                textField.textProperty().addListener((a, b, newValue) -> stringQuestion.value = textField.getText());\n                textField.setText(stringQuestion.value);\n                textField.setValidators(((Builder.StringQuestion) question).validators.toArray(new ValidatorBase[0]));\n                if (stringQuestion.promptText != null) {\n                    textField.setPromptText(stringQuestion.promptText);\n                }\n                bindings.add(Bindings.createBooleanBinding(textField::validate, textField.textProperty()));\n\n                if (StringUtils.isNotBlank(question.question.get())) {\n                    body.addRow(rowIndex++, new Label(question.question.get()), textField);\n                } else {\n                    GridPane.setColumnSpan(textField, 1);\n                    body.addRow(rowIndex++, textField);\n                }\n                GridPane.setMargin(textField, new Insets(0, 0, 20, 0));\n            } else if (question instanceof Builder.BooleanQuestion) {\n                HBox hBox = new HBox();\n                GridPane.setColumnSpan(hBox, 1);\n                JFXCheckBox checkBox = new JFXCheckBox();\n                hBox.getChildren().setAll(checkBox);\n                HBox.setMargin(checkBox, new Insets(0, 0, 0, -10));\n                checkBox.setSelected(((Builder.BooleanQuestion) question).value);\n                checkBox.selectedProperty().addListener((a, b, newValue) -> ((Builder.BooleanQuestion) question).value = newValue);\n                checkBox.setText(question.question.get());\n                body.addRow(rowIndex++, hBox);\n            } else if (question instanceof Builder.CandidatesQuestion) {\n                JFXComboBox<String> comboBox = new JFXComboBox<>();\n                comboBox.getItems().setAll(((Builder.CandidatesQuestion) question).candidates);\n                comboBox.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) ->\n                        ((Builder.CandidatesQuestion) question).value = newValue.intValue());\n                comboBox.getSelectionModel().select(0);\n                if (StringUtils.isNotBlank(question.question.get())) {\n                    body.addRow(rowIndex++, new Label(question.question.get()), comboBox);\n                } else {\n                    GridPane.setColumnSpan(comboBox, 1);\n                    body.addRow(rowIndex++, comboBox);\n                }\n            } else if (question instanceof Builder.HintQuestion) {\n                HintPane pane = new HintPane();\n                GridPane.setColumnSpan(pane, 1);\n                pane.textProperty().bind(question.question);\n                body.addRow(rowIndex++, pane);\n            }\n        }\n\n        validProperty().bind(Bindings.createBooleanBinding(\n                () -> bindings.stream().allMatch(BooleanBinding::get),\n                bindings.toArray(new BooleanBinding[0])\n        ));\n    }\n\n    @Override\n    protected void onAccept() {\n        setLoading();\n\n        builder.callback.call(builder.questions, new FutureCallback.ResultHandler() {\n            @Override\n            public void resolve() {\n                future.complete(builder.questions);\n                runInFX(() -> onSuccess());\n            }\n\n            @Override\n            public void reject(String reason) {\n                runInFX(() -> onFailure(reason));\n            }\n        });\n    }\n\n    public CompletableFuture<List<Builder.Question<?>>> getCompletableFuture() {\n        return future;\n    }\n\n    public static class Builder {\n        private final List<Question<?>> questions = new ArrayList<>();\n        private final String title;\n        private final FutureCallback<List<Question<?>>> callback;\n\n        public Builder(String title, FutureCallback<List<Question<?>>> callback) {\n            this.title = title;\n            this.callback = callback;\n        }\n\n        public <T> Builder addQuestion(Question<T> question) {\n            questions.add(question);\n            return this;\n        }\n\n        public static class Question<T> {\n            public final StringProperty question = new SimpleStringProperty();\n            protected T value;\n\n            public Question(String question) {\n                this.question.set(question);\n            }\n\n            public T getValue() {\n                return value;\n            }\n\n            public String getQuestion() {\n                return question.get();\n            }\n\n            public StringProperty questionProperty() {\n                return question;\n            }\n\n            public void setQuestion(String question) {\n                this.question.set(question);\n            }\n        }\n\n        public static class HintQuestion extends Question<Void> {\n            public HintQuestion(String hint) {\n                super(hint);\n            }\n        }\n\n        public static class StringQuestion extends Question<String> {\n            protected final List<ValidatorBase> validators;\n            protected String promptText;\n\n            public StringQuestion(String question, String defaultValue, ValidatorBase... validators) {\n                super(question);\n                this.value = defaultValue;\n                this.validators = Arrays.asList(validators);\n            }\n\n            public StringQuestion setPromptText(String promptText) {\n                this.promptText = promptText;\n                return this;\n            }\n        }\n\n        public static class CandidatesQuestion extends Question<Integer> {\n            protected final List<String> candidates;\n\n            public CandidatesQuestion(String question, String... candidates) {\n                super(question);\n                this.value = null;\n                if (candidates == null || candidates.length == 0) {\n                    throw new IllegalArgumentException(\"At least one candidate required\");\n                }\n                this.candidates = new ArrayList<>(Arrays.asList(candidates));\n            }\n        }\n\n        public static class BooleanQuestion extends Question<Boolean> {\n\n            public BooleanQuestion(String question, boolean defaultValue) {\n                super(question);\n                this.value = defaultValue;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RequiredValidator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.NamedArg;\nimport javafx.scene.control.TextInputControl;\n\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class RequiredValidator extends ValidatorBase {\n\n    public RequiredValidator() {\n        this(i18n(\"input.not_empty\"));\n    }\n\n    public RequiredValidator(@NamedArg(\"message\") String message) {\n        super(message);\n    }\n\n    @Override\n    protected void eval() {\n        if (srcControl.get() instanceof TextInputControl) {\n            evalTextInputField();\n        }\n    }\n\n    private void evalTextInputField() {\n        TextInputControl textField = ((TextInputControl) srcControl.get());\n\n        hasErrors.set(StringUtils.isBlank(textField.getText()));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/RipplerContainer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXRippler;\nimport javafx.animation.Transition;\nimport javafx.css.*;\nimport javafx.css.converter.PaintConverter;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundFill;\nimport javafx.scene.layout.CornerRadii;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\nimport javafx.scene.shape.Rectangle;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.ui.animation.Motion;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class RipplerContainer extends StackPane {\n    private static final String DEFAULT_STYLE_CLASS = \"rippler-container\";\n    private static final CornerRadii DEFAULT_RADII = new CornerRadii(3);\n    private static final Color DEFAULT_RIPPLER_FILL = Color.rgb(0, 200, 255);\n\n    private final Node container;\n\n    private final StackPane buttonContainer = new StackPane();\n    private final JFXRippler buttonRippler = new JFXRippler(new StackPane()) {\n        private static final Background DEFAULT_MASK_BACKGROUND = new Background(new BackgroundFill(Color.WHITE, DEFAULT_RADII, Insets.EMPTY));\n\n        @Override\n        protected Node getMask() {\n            StackPane mask = new StackPane();\n            mask.shapeProperty().bind(buttonContainer.shapeProperty());\n            mask.setBackground(DEFAULT_MASK_BACKGROUND);\n            mask.resize(\n                    buttonContainer.getWidth() - buttonContainer.snappedRightInset() - buttonContainer.snappedLeftInset(),\n                    buttonContainer.getHeight() - buttonContainer.snappedBottomInset() - buttonContainer.snappedTopInset()\n            );\n            return mask;\n        }\n    };\n\n    private Transition coverAnimation;\n\n    public RipplerContainer(Node container) {\n        this.container = container;\n\n        getStyleClass().add(DEFAULT_STYLE_CLASS);\n        buttonRippler.setPosition(JFXRippler.RipplerPos.BACK);\n        buttonContainer.getChildren().add(buttonRippler);\n        focusedProperty().addListener((a, b, newValue) -> {\n            if (newValue) {\n                if (!isPressed())\n                    buttonRippler.showOverlay();\n            } else {\n                buttonRippler.hideOverlay();\n            }\n        });\n        pressedProperty().addListener(o -> buttonRippler.hideOverlay());\n        setPickOnBounds(false);\n\n        buttonContainer.setPickOnBounds(false);\n\n        updateChildren();\n\n        var shape = new Rectangle();\n        shape.widthProperty().bind(widthProperty());\n        shape.heightProperty().bind(heightProperty());\n        setShape(shape);\n\n        EventHandler<MouseEvent> mouseEventHandler;\n        if (AnimationUtils.isAnimationEnabled()) {\n            mouseEventHandler = event -> {\n                if (coverAnimation != null) {\n                    coverAnimation.stop();\n                    coverAnimation = null;\n                }\n\n                if (event.getEventType() == MouseEvent.MOUSE_ENTERED) {\n                    coverAnimation = new Transition() {\n                        {\n                            setCycleDuration(Motion.SHORT4);\n                            setInterpolator(Motion.EASE_IN);\n                        }\n\n                        @Override\n                        protected void interpolate(double frac) {\n                            interpolateBackground(frac);\n                        }\n                    };\n                } else {\n                    coverAnimation = new Transition() {\n                        {\n                            setCycleDuration(Motion.SHORT4);\n                            setInterpolator(Motion.EASE_OUT);\n                        }\n\n                        @Override\n                        protected void interpolate(double frac) {\n                            interpolateBackground(1 - frac);\n                        }\n                    };\n                }\n\n                coverAnimation.play();\n            };\n        } else {\n            mouseEventHandler = event ->\n                    interpolateBackground(event.getEventType() == MouseEvent.MOUSE_ENTERED ? 1 : 0);\n        }\n\n        addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEventHandler);\n        addEventHandler(MouseEvent.MOUSE_EXITED, mouseEventHandler);\n    }\n\n    private void interpolateBackground(double frac) {\n        if (frac < 0.01) {\n            setBackground(null);\n        } else {\n            Color onSurface = Themes.getColorScheme().getOnSurface();\n            setBackground(new Background(new BackgroundFill(\n                    Color.color(onSurface.getRed(), onSurface.getGreen(), onSurface.getBlue(), frac * 0.04),\n                    CornerRadii.EMPTY, Insets.EMPTY)));\n        }\n    }\n\n    protected void updateChildren() {\n        Node container = getContainer();\n        if (buttonRippler.getPosition() == JFXRippler.RipplerPos.BACK) {\n            getChildren().setAll(buttonContainer, container);\n            container.setPickOnBounds(false);\n        } else {\n            getChildren().setAll(container, buttonContainer);\n            buttonContainer.setPickOnBounds(false);\n        }\n    }\n\n    public void setPosition(JFXRippler.RipplerPos pos) {\n        buttonRippler.setPosition(pos);\n        updateChildren();\n    }\n\n    public JFXRippler getRippler() {\n        return buttonRippler;\n    }\n\n    public Node getContainer() {\n        return container;\n    }\n\n    private final StyleableObjectProperty<Paint> ripplerFill = new StyleableObjectProperty<>(DEFAULT_RIPPLER_FILL) {\n        @Override\n        public Object getBean() {\n            return RipplerContainer.this;\n        }\n\n        @Override\n        public String getName() {\n            return \"ripplerFill\";\n        }\n\n        @Override\n        public CssMetaData<? extends Styleable, Paint> getCssMetaData() {\n            return StyleableProperties.RIPPLER_FILL;\n        }\n\n        @Override\n        protected void invalidated() {\n            buttonRippler.setRipplerFill(get());\n        }\n    };\n\n    public StyleableObjectProperty<Paint> ripplerFillProperty() {\n        return ripplerFill;\n    }\n\n    public Paint getRipplerFill() {\n        return ripplerFillProperty().get();\n    }\n\n    public void setRipplerFill(Paint ripplerFill) {\n        ripplerFillProperty().set(ripplerFill);\n    }\n\n    @Override\n    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {\n        return getClassCssMetaData();\n    }\n\n    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {\n        return StyleableProperties.STYLEABLES;\n    }\n\n    private final static class StyleableProperties {\n        private static final CssMetaData<RipplerContainer, Paint> RIPPLER_FILL = new CssMetaData<>(\"-jfx-rippler-fill\", PaintConverter.getInstance(), DEFAULT_RIPPLER_FILL) {\n            @Override\n            public boolean isSettable(RipplerContainer styleable) {\n                return styleable.ripplerFill == null || !styleable.ripplerFill.isBound();\n            }\n\n            @Override\n            public StyleableProperty<Paint> getStyleableProperty(RipplerContainer styleable) {\n                return styleable.ripplerFillProperty();\n            }\n        };\n\n        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;\n\n        static {\n            var styleables = new ArrayList<>(StackPane.getClassCssMetaData());\n            styleables.add(RIPPLER_FILL);\n            STYLEABLES = List.copyOf(styleables);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXSpinner;\nimport javafx.beans.property.*;\nimport javafx.event.Event;\nimport javafx.event.EventHandler;\nimport javafx.event.EventType;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.StackPane;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\n\n/// A spinner pane that can show spinner, failed reason, or content.\npublic class SpinnerPane extends Control {\n    private static final String DEFAULT_STYLE_CLASS = \"spinner-pane\";\n\n    public SpinnerPane() {\n        getStyleClass().add(DEFAULT_STYLE_CLASS);\n    }\n\n    public void showSpinner() {\n        setLoading(true);\n    }\n\n    public void hideSpinner() {\n        setFailedReason(null);\n        setLoading(false);\n    }\n\n    private void updateContent() {\n        if (getSkin() instanceof Skin skin) {\n            skin.updateContent();\n        }\n    }\n\n    private ObjectProperty<Node> content;\n\n    public ObjectProperty<Node> contentProperty() {\n        if (content == null)\n            content = new ObjectPropertyBase<>() {\n                @Override\n                public Object getBean() {\n                    return SpinnerPane.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"content\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    updateContent();\n                }\n            };\n        return content;\n    }\n\n    public Node getContent() {\n        return contentProperty().get();\n    }\n\n    public void setContent(Node content) {\n        contentProperty().set(content);\n    }\n\n    private BooleanProperty loading;\n\n    public BooleanProperty loadingProperty() {\n        if (loading == null)\n            loading = new BooleanPropertyBase() {\n                @Override\n                public Object getBean() {\n                    return SpinnerPane.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"loading\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    updateContent();\n                }\n            };\n        return loading;\n    }\n\n    public boolean isLoading() {\n        return loading != null && loading.get();\n    }\n\n    public void setLoading(boolean loading) {\n        loadingProperty().set(loading);\n    }\n\n    private StringProperty failedReason;\n\n    public StringProperty failedReasonProperty() {\n        if (failedReason == null)\n            failedReason = new StringPropertyBase() {\n                @Override\n                public Object getBean() {\n                    return SpinnerPane.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"failedReason\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    updateContent();\n                }\n            };\n        return failedReason;\n    }\n\n    public String getFailedReason() {\n        return failedReason != null ? failedReason.get() : null;\n    }\n\n    public void setFailedReason(String failedReason) {\n        failedReasonProperty().set(failedReason);\n    }\n\n    private ObjectProperty<EventHandler<Event>> onFailedAction;\n\n    public final ObjectProperty<EventHandler<Event>> onFailedActionProperty() {\n        if (onFailedAction == null) {\n            onFailedAction = new ObjectPropertyBase<>() {\n                @Override\n                public Object getBean() {\n                    return SpinnerPane.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"onFailedAction\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    setEventHandler(FAILED_ACTION, get());\n                }\n            };\n        }\n        return onFailedAction;\n    }\n\n    public final EventHandler<Event> getOnFailedAction() {\n        return onFailedAction != null ? onFailedAction.get() : null;\n    }\n\n    public final void setOnFailedAction(EventHandler<Event> value) {\n        onFailedActionProperty().set(value);\n    }\n\n    @Override\n    protected SkinBase<SpinnerPane> createDefaultSkin() {\n        return new Skin(this);\n    }\n\n    private static final class Skin extends SkinBase<SpinnerPane> {\n        private final TransitionPane root = new TransitionPane();\n\n        Skin(SpinnerPane control) {\n            super(control);\n            root.setClip(null);\n\n            updateContent();\n            this.getChildren().setAll(root);\n        }\n\n        private StackPane contentPane;\n        private StackPane spinnerPane;\n        private StackPane failedPane;\n        private Label failedReasonLabel;\n\n        void updateContent() {\n            SpinnerPane control = getSkinnable();\n\n            Node nextContent;\n            if (control.isLoading()) {\n                if (spinnerPane == null) {\n                    spinnerPane = new StackPane(new JFXSpinner());\n                    spinnerPane.getStyleClass().add(\"notice-pane\");\n                }\n                nextContent = spinnerPane;\n            } else if (control.getFailedReason() != null) {\n                if (failedPane == null) {\n                    failedReasonLabel = new Label();\n                    failedPane = new StackPane(failedReasonLabel);\n                    failedPane.getStyleClass().add(\"notice-pane\");\n                    FXUtils.onClicked(failedPane, () -> control.fireEvent(new Event(SpinnerPane.FAILED_ACTION)));\n                }\n                failedReasonLabel.setText(control.getFailedReason());\n                nextContent = failedPane;\n            } else {\n                if (contentPane == null) {\n                    contentPane = new StackPane();\n                }\n\n                Node content = control.getContent();\n                if (content != null)\n                    contentPane.getChildren().setAll(content);\n                else\n                    contentPane.getChildren().clear();\n\n                nextContent = contentPane;\n            }\n\n            if (nextContent != failedPane && failedReasonLabel != null) {\n                failedReasonLabel.setText(null);\n            }\n\n            root.setContent(nextContent, ContainerAnimations.FADE);\n        }\n    }\n\n    public static final EventType<Event> FAILED_ACTION = new EventType<>(Event.ANY, \"FAILED_ACTION\");\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabControl.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.property.*;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.scene.AccessibleAttribute;\nimport javafx.scene.Node;\nimport javafx.scene.control.SingleSelectionModel;\n\nimport java.util.function.Supplier;\n\npublic interface TabControl {\n    ObservableList<Tab<?>> getTabs();\n\n    class TabControlSelectionModel extends SingleSelectionModel<Tab<?>> {\n        private final TabControl tabHeader;\n\n        public TabControlSelectionModel(final TabControl t) {\n            if (t == null) {\n                throw new NullPointerException(\"TabPane can not be null\");\n            }\n            this.tabHeader = t;\n\n            // watching for changes to the items list content\n            final ListChangeListener<Tab<?>> itemsContentObserver = c -> {\n                while (c.next()) {\n                    for (Tab<?> tab : c.getRemoved()) {\n                        if (tab != null && !tabHeader.getTabs().contains(tab)) {\n                            if (tab.isSelected()) {\n                                tab.setSelected(false);\n                                final int tabIndex = c.getFrom();\n\n                                // we always try to select the nearest, non-disabled\n                                // tab from the position of the closed tab.\n                                findNearestAvailableTab(tabIndex, true);\n                            }\n                        }\n                    }\n                    if (c.wasAdded() || c.wasRemoved()) {\n                        // The selected tab index can be out of sync with the list of tab if\n                        // we add or remove tabs before the selected tab.\n                        if (getSelectedIndex() != tabHeader.getTabs().indexOf(getSelectedItem())) {\n                            clearAndSelect(tabHeader.getTabs().indexOf(getSelectedItem()));\n                        }\n                    }\n                }\n                if (getSelectedIndex() == -1 && getSelectedItem() == null && tabHeader.getTabs().size() > 0) {\n                    // we go looking for the first non-disabled tab, as opposed to\n                    // just selecting the first tab (fix for RT-36908)\n                    findNearestAvailableTab(0, true);\n                } else if (tabHeader.getTabs().isEmpty()) {\n                    clearSelection();\n                }\n            };\n            if (this.tabHeader.getTabs() != null) {\n                this.tabHeader.getTabs().addListener(itemsContentObserver);\n            }\n        }\n\n        // API Implementation\n        @Override public void select(int index) {\n            if (index < 0 || (getItemCount() > 0 && index >= getItemCount()) ||\n                    (index == getSelectedIndex() && getModelItem(index).isSelected())) {\n                return;\n            }\n\n            // Unselect the old tab\n            if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) {\n                tabHeader.getTabs().get(getSelectedIndex()).setSelected(false);\n            }\n\n            setSelectedIndex(index);\n\n            Tab tab = getModelItem(index);\n            if (tab != null) {\n                setSelectedItem(tab);\n            }\n\n            // Select the new tab\n            if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) {\n                tabHeader.getTabs().get(getSelectedIndex()).setSelected(true);\n            }\n\n            /* Does this get all the change events */\n            ((Node) tabHeader).notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM);\n        }\n\n        @Override public void select(Tab tab) {\n            final int itemCount = getItemCount();\n\n            for (int i = 0; i < itemCount; i++) {\n                final Tab value = getModelItem(i);\n                if (value != null && value.equals(tab)) {\n                    select(i);\n                    return;\n                }\n            }\n            if (tab != null) {\n                setSelectedItem(tab);\n            }\n        }\n\n        @Override protected Tab<?> getModelItem(int index) {\n            final ObservableList<Tab<?>> items = tabHeader.getTabs();\n            if (items == null) return null;\n            if (index < 0 || index >= items.size()) return null;\n            return items.get(index);\n        }\n\n        @Override protected int getItemCount() {\n            final ObservableList<Tab<?>> items = tabHeader.getTabs();\n            return items == null ? 0 : items.size();\n        }\n\n        private Tab<?> findNearestAvailableTab(int tabIndex, boolean doSelect) {\n            // we always try to select the nearest, non-disabled\n            // tab from the position of the closed tab.\n            final int tabCount = getItemCount();\n            int i = 1;\n            Tab<?> bestTab = null;\n            while (true) {\n                // look leftwards\n                int downPos = tabIndex - i;\n                if (downPos >= 0) {\n                    Tab<?> _tab = getModelItem(downPos);\n                    if (_tab != null) {\n                        bestTab = _tab;\n                        break;\n                    }\n                }\n\n                // look rightwards. We subtract one as we need\n                // to take into account that a tab has been removed\n                // and if we don't do this we'll miss the tab\n                // to the right of the tab (as it has moved into\n                // the removed tabs position).\n                int upPos = tabIndex + i - 1;\n                if (upPos < tabCount) {\n                    Tab<?> _tab = getModelItem(upPos);\n                    if (_tab != null) {\n                        bestTab = _tab;\n                        break;\n                    }\n                }\n\n                if (downPos < 0 && upPos >= tabCount) {\n                    break;\n                }\n                i++;\n            }\n\n            if (doSelect && bestTab != null) {\n                select(bestTab);\n            }\n\n            return bestTab;\n        }\n    }\n\n    final class Tab<T extends Node> {\n        private final StringProperty id = new SimpleStringProperty(this, \"id\");\n        private final StringProperty text = new SimpleStringProperty(this, \"text\");\n        private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(this, \"selected\");\n        private final ObjectProperty<T> node = new SimpleObjectProperty<>(this, \"node\");\n        private final ObjectProperty<Object> userData = new SimpleObjectProperty<>(this, \"userData\");\n        private Supplier<? extends T> nodeSupplier;\n\n        public Tab(String id) {\n            setId(id);\n        }\n\n        public Tab(String id, String text) {\n            setId(id);\n            setText(text);\n        }\n\n        public Supplier<? extends T> getNodeSupplier() {\n            return nodeSupplier;\n        }\n\n        public void setNodeSupplier(Supplier<? extends T> nodeSupplier) {\n            this.nodeSupplier = nodeSupplier;\n        }\n\n        public String getId() {\n            return id.get();\n        }\n\n        public StringProperty idProperty() {\n            return id;\n        }\n\n        public void setId(String id) {\n            this.id.set(id);\n        }\n\n        public String getText() {\n            return text.get();\n        }\n\n        public StringProperty textProperty() {\n            return text;\n        }\n\n        public void setText(String text) {\n            this.text.set(text);\n        }\n\n        public boolean isSelected() {\n            return selected.get();\n        }\n\n        public ReadOnlyBooleanProperty selectedProperty() {\n            return selected.getReadOnlyProperty();\n        }\n\n        private void setSelected(boolean selected) {\n            this.selected.set(selected);\n        }\n\n        public T getNode() {\n            return node.get();\n        }\n\n        public ObjectProperty<T> nodeProperty() {\n            return node;\n        }\n\n        public void setNode(T node) {\n            this.node.set(node);\n        }\n\n        public Object getUserData() {\n            return userData.get();\n        }\n\n        public ObjectProperty<?> userDataProperty() {\n            return userData;\n        }\n\n        public void setUserData(Object userData) {\n            this.userData.set(userData);\n        }\n\n        public boolean isInitialized() {\n            return getNode() != null;\n        }\n\n        public boolean initializeIfNeeded() {\n            if (getNode() == null) {\n                if (getNodeSupplier() == null) {\n                    return false;\n                }\n                setNode(getNodeSupplier().get());\n                return true;\n            }\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.animation.*;\nimport javafx.application.Platform;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Side;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.transform.Rotate;\nimport javafx.scene.transform.Scale;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.Motion;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.util.javafx.MappedObservableList;\nimport org.jetbrains.annotations.Nullable;\n\n@SuppressWarnings(\"deprecation\")\npublic class TabHeader extends Control implements TabControl, PageAware {\n\n    private final TransitionPane contentPane;\n\n    public TabHeader(Tab<?>... tabs) {\n        this(null, tabs);\n    }\n\n    public TabHeader(@Nullable TransitionPane contentPane, Tab<?>... tabs) {\n        this.contentPane = contentPane;\n\n        getStyleClass().setAll(\"tab-header\");\n        if (tabs != null) {\n            getTabs().addAll(tabs);\n        }\n    }\n\n    private final ObservableList<Tab<?>> tabs = FXCollections.observableArrayList();\n\n    @Override\n    public ObservableList<Tab<?>> getTabs() {\n        return tabs;\n    }\n\n    private final SingleSelectionModel<Tab<?>> selectionModel = new TabControlSelectionModel(this);\n\n    public SingleSelectionModel<Tab<?>> getSelectionModel() {\n        return selectionModel;\n    }\n\n    public void select(Tab<?> tab) {\n        select(tab, true);\n    }\n\n    public void select(Tab<?> tab, boolean playAnimation) {\n        Tab<?> oldTab = getSelectionModel().getSelectedItem();\n        if (oldTab != null) {\n            if (oldTab.getNode() instanceof PageAware pageAware) {\n                pageAware.onPageHidden();\n            }\n        }\n\n        tab.initializeIfNeeded();\n        if (tab.getNode() instanceof PageAware pageAware) {\n            pageAware.onPageShown();\n        }\n\n        getSelectionModel().select(tab);\n\n        if (contentPane != null) {\n            if (playAnimation && contentPane.getCurrentNode() != null) {\n                contentPane.setContent(tab.getNode(),\n                        ContainerAnimations.SLIDE_UP_FADE_IN,\n                        Motion.MEDIUM4,\n                        Motion.EASE_IN_OUT_CUBIC_EMPHASIZED\n                );\n            } else {\n                contentPane.setContent(tab.getNode(), ContainerAnimations.NONE);\n            }\n        }\n    }\n\n    @Override\n    public void onPageShown() {\n        Tab<?> tab = getSelectionModel().getSelectedItem();\n        if (tab != null) {\n            if (tab.getNode() instanceof PageAware) {\n                ((PageAware) tab.getNode()).onPageShown();\n            }\n        }\n    }\n\n    @Override\n    public void onPageHidden() {\n        Tab<?> tab = getSelectionModel().getSelectedItem();\n        if (tab != null) {\n            if (tab.getNode() instanceof PageAware) {\n                ((PageAware) tab.getNode()).onPageHidden();\n            }\n        }\n    }\n\n    private final ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);\n\n    /**\n     * The position of the tabs.\n     */\n    public ObjectProperty<Side> sideProperty() {\n        return side;\n    }\n\n    /**\n     * The position to place the tabs.\n     */\n    public Side getSide() {\n        return side.get();\n    }\n\n    /**\n     * The position the place the tabs in this TabHeader.\n     */\n    public void setSide(Side side) {\n        this.side.set(side);\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new TabHeaderSkin(this);\n    }\n\n    public static class TabHeaderSkin extends SkinBase<TabHeader> {\n\n        private static final PseudoClass SELECTED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass(\"selected\");\n\n        private final HeaderContainer header;\n        private boolean isSelectingTab = false;\n        private Tab<?> selectedTab;\n\n        protected TabHeaderSkin(TabHeader control) {\n            super(control);\n\n            header = new HeaderContainer();\n            getChildren().setAll(header);\n\n            FXUtils.onChangeAndOperate(control.getSelectionModel().selectedItemProperty(), item -> {\n                isSelectingTab = true;\n                selectedTab = item;\n                Platform.runLater(() -> {\n                    header.setNeedsLayout2(true);\n                    header.layout();\n                });\n            });\n\n            this.selectedTab = control.getSelectionModel().getSelectedItem();\n            if (this.selectedTab == null && control.getSelectionModel().getSelectedIndex() != -1) {\n                control.getSelectionModel().select(control.getSelectionModel().getSelectedIndex());\n                this.selectedTab = control.getSelectionModel().getSelectedItem();\n            }\n\n            if (this.selectedTab == null) {\n                control.getSelectionModel().selectFirst();\n            }\n\n            this.selectedTab = control.getSelectionModel().getSelectedItem();\n        }\n\n        protected final class HeaderContainer extends StackPane {\n            private Timeline timeline;\n            private final StackPane selectedTabLine;\n            private final HeadersRegion headersRegion;\n            private final Scale scale = new Scale(1, 1, 0, 0);\n            private final Rotate rotate = new Rotate(0, 0, 1);\n            @SuppressWarnings({\"FieldCanBeLocal\", \"unused\"})\n            private final ObservableList<Node> binding;\n\n            public HeaderContainer() {\n                getStyleClass().add(\"tab-header-area\");\n                setPickOnBounds(false);\n\n                headersRegion = new HeadersRegion();\n                headersRegion.sideProperty().bind(getSkinnable().sideProperty());\n\n                selectedTabLine = new StackPane();\n                selectedTabLine.setManaged(false);\n                selectedTabLine.getTransforms().addAll(scale, rotate);\n                selectedTabLine.setCache(true);\n                selectedTabLine.getStyleClass().addAll(\"tab-selected-line\");\n                selectedTabLine.setPrefHeight(2);\n                selectedTabLine.setPrefWidth(2);\n                getChildren().setAll(headersRegion, selectedTabLine);\n                headersRegion.setPickOnBounds(false);\n                headersRegion.prefHeightProperty().bind(heightProperty());\n                rotate.pivotXProperty().bind(Bindings.createDoubleBinding(() -> getSkinnable().getSide().isHorizontal() ? 0.0 : 1, getSkinnable().sideProperty()));\n                rotate.pivotYProperty().bind(Bindings.createDoubleBinding(() -> getSkinnable().getSide().isHorizontal() ? 1.0 : 0, getSkinnable().sideProperty()));\n\n                Bindings.bindContent(headersRegion.getChildren(), binding = MappedObservableList.create(getSkinnable().getTabs(), tab -> {\n                    TabHeaderContainer container = new TabHeaderContainer(tab);\n                    container.setVisible(true);\n                    return container;\n                }));\n            }\n\n            public void setNeedsLayout2(boolean value) {\n                setNeedsLayout(value);\n            }\n\n            private boolean isAnimating() {\n                return this.timeline != null && this.timeline.getStatus() == Animation.Status.RUNNING;\n            }\n\n            @Override\n            protected void layoutChildren() {\n                super.layoutChildren();\n\n                if (isSelectingTab) {\n                    headersRegion.animateSelectionLine();\n                    isSelectingTab = false;\n                }\n            }\n\n            private final class HeadersRegion extends StackPane {\n                private SideAction action;\n                private final ObjectProperty<Side> side = new SimpleObjectProperty<Side>() {\n                    @Override\n                    protected void invalidated() {\n                        super.invalidated();\n\n                        action = switch (get()) {\n                            case TOP -> new Top();\n                            case BOTTOM -> new Bottom();\n                            case LEFT -> new Left();\n                            case RIGHT -> new Right();\n                        };\n                    }\n                };\n\n                public Side getSide() {\n                    return side.get();\n                }\n\n                public ObjectProperty<Side> sideProperty() {\n                    return side;\n                }\n\n                public void setSide(Side side) {\n                    this.side.set(side);\n                }\n\n                @Override\n                protected double computePrefWidth(double height) {\n                    return action.computePrefWidth(height);\n                }\n\n                @Override\n                protected double computePrefHeight(double width) {\n                    return action.computePrefHeight(width);\n                }\n\n                @Override\n                protected void layoutChildren() {\n                    action.layoutChildren();\n                }\n\n                private void animateSelectionLine() {\n                    action.animateSelectionLine();\n                }\n\n                private abstract class SideAction {\n                    abstract double computePrefWidth(double height);\n\n                    abstract double computePrefHeight(double width);\n\n                    void layoutChildren() {\n                        if (isSelectingTab) {\n                            animateSelectionLine();\n                            isSelectingTab = false;\n                        }\n                    }\n\n                    abstract void animateSelectionLine();\n                }\n\n                private abstract class Horizontal extends SideAction {\n                    @Override\n                    public double computePrefWidth(double height) {\n                        double width = 0;\n                        for (Node child : getChildren()) {\n                            if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;\n                            width += child.prefWidth(height);\n                        }\n                        return snapSize(width) + snappedLeftInset() + snappedRightInset();\n                    }\n\n                    @Override\n                    public double computePrefHeight(double width) {\n                        double height = 0;\n                        for (Node child : getChildren()) {\n                            if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;\n                            height = Math.max(height, child.prefHeight(width));\n                        }\n                        return snapSize(height) + snappedTopInset() + snappedBottomInset();\n                    }\n\n                    private void runTimeline(double newTransX, double newWidth) {\n                        double lineWidth = selectedTabLine.prefWidth(-1.0D);\n                        if (isAnimating()) {\n                            timeline.stop();\n                            double tempScaleX = scale.getX();\n                            if (rotate.getAngle() != 0.0D) {\n                                rotate.setAngle(0.0D);\n                                double tempWidth = tempScaleX * lineWidth;\n                                selectedTabLine.setTranslateX(selectedTabLine.getTranslateX() - tempWidth);\n                            }\n                        }\n\n                        double oldScaleX = scale.getX();\n                        double oldWidth = lineWidth * oldScaleX;\n                        double oldTransX = selectedTabLine.getTranslateX();\n                        double newScaleX = newWidth * oldScaleX / oldWidth;\n                        // newTransX += offsetStart * (double)this.direction;\n                        double transDiff = newTransX - oldTransX;\n                        if (transDiff < 0.0D) {\n                            selectedTabLine.setTranslateX(selectedTabLine.getTranslateX() + oldWidth);\n                            newTransX += newWidth;\n                            rotate.setAngle(180.0D);\n                        }\n\n                        timeline = new Timeline(\n                                new KeyFrame(\n                                        Duration.ZERO,\n                                        new KeyValue(selectedTabLine.translateXProperty(), selectedTabLine.getTranslateX(), Interpolator.EASE_BOTH)\n                                ),\n                                new KeyFrame(\n                                        Duration.seconds(0.24D),\n                                        new KeyValue(scale.xProperty(), newScaleX, Interpolator.EASE_BOTH),\n                                        new KeyValue(selectedTabLine.translateXProperty(), newTransX, Interpolator.EASE_BOTH)\n                                )\n                        );\n                        timeline.setOnFinished((finish) -> {\n                            if (rotate.getAngle() != 0.0D) {\n                                rotate.setAngle(0.0D);\n                                selectedTabLine.setTranslateX(selectedTabLine.getTranslateX() - newWidth);\n                            }\n\n                        });\n                        timeline.play();\n                    }\n\n                    @Override\n                    public void animateSelectionLine() {\n                        double offset = 0.0D;\n                        double selectedTabOffset = 0.0D;\n                        double selectedTabWidth = 0.0D;\n\n                        for (Node node : headersRegion.getChildren()) {\n                            if (node instanceof TabHeaderContainer) {\n                                TabHeaderContainer tabHeader = (TabHeaderContainer) node;\n                                double tabHeaderPrefWidth = snapSize(tabHeader.prefWidth(-1.0D));\n                                if (selectedTab != null && selectedTab.equals(tabHeader.tab)) {\n                                    selectedTabOffset = offset;\n                                    selectedTabWidth = tabHeaderPrefWidth;\n                                    break;\n                                }\n\n                                offset += tabHeaderPrefWidth;\n                            }\n                        }\n\n                        this.runTimeline(selectedTabOffset, selectedTabWidth);\n                    }\n                }\n\n                private final class Top extends Horizontal {\n                    @Override\n                    public void layoutChildren() {\n                        super.layoutChildren();\n\n                        double headerHeight = snapSize(prefHeight(-1));\n                        double tabStartX = 0;\n                        for (Node node : getChildren()) {\n                            if (!(node instanceof TabHeaderContainer)) continue;\n                            TabHeaderContainer child = (TabHeaderContainer) node;\n                            double w = snapSize(child.prefWidth(-1));\n                            double h = snapSize(child.prefHeight(-1));\n                            child.resize(w, h);\n\n                            child.relocate(tabStartX, headerHeight - h - snappedBottomInset());\n                            tabStartX += w;\n                        }\n\n                        selectedTabLine.resizeRelocate(0,\n                                headerHeight - selectedTabLine.prefHeight(-1),\n                                snapSize(selectedTabLine.prefWidth(-1)),\n                                snapSize(selectedTabLine.prefHeight(-1)));\n                    }\n                }\n\n                private final class Bottom extends Horizontal {\n                    @Override\n                    public void layoutChildren() {\n                        super.layoutChildren();\n\n                        double headerHeight = snapSize(prefHeight(-1));\n                        double tabStartX = 0;\n                        for (Node node : getChildren()) {\n                            if (!(node instanceof TabHeaderContainer)) continue;\n                            TabHeaderContainer child = (TabHeaderContainer) node;\n                            double w = snapSize(child.prefWidth(-1));\n                            double h = snapSize(child.prefHeight(-1));\n                            child.resize(w, h);\n\n                            child.relocate(tabStartX, snappedTopInset());\n                            tabStartX += w;\n                        }\n\n                        selectedTabLine.resizeRelocate(0, 0,\n                                snapSize(selectedTabLine.prefWidth(-1)),\n                                snapSize(selectedTabLine.prefHeight(-1)));\n                    }\n                }\n\n                private abstract class Vertical extends SideAction {\n                    @Override\n                    public double computePrefWidth(double height) {\n                        double width = 0;\n                        for (Node child : getChildren()) {\n                            if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;\n                            width = Math.max(width, child.prefWidth(height));\n                        }\n                        return snapSize(width) + snappedLeftInset() + snappedRightInset();\n                    }\n\n                    @Override\n                    public double computePrefHeight(double width) {\n                        double height = 0;\n                        for (Node child : getChildren()) {\n                            if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;\n                            height += child.prefHeight(width);\n                        }\n                        return snapSize(height) + snappedTopInset() + snappedBottomInset();\n                    }\n\n                    private void runTimeline(double newTransY, double newHeight) {\n                        double lineHeight = selectedTabLine.prefHeight(-1.0D);\n                        if (isAnimating()) {\n                            timeline.stop();\n                            double tempScaleY = scale.getY();\n                            if (rotate.getAngle() != 0.0D) {\n                                rotate.setAngle(0.0D);\n                                double tempHeight = tempScaleY * lineHeight;\n                                selectedTabLine.setTranslateY(selectedTabLine.getTranslateY() - tempHeight);\n                            }\n                        }\n\n                        double oldScaleY = scale.getY();\n                        double oldHeight = lineHeight * oldScaleY;\n                        double oldTransY = selectedTabLine.getTranslateY();\n                        double newScaleY = newHeight * oldScaleY / oldHeight;\n                        // newTransY += offsetStart * (double)this.direction;\n                        double transDiff = newTransY - oldTransY;\n                        if (transDiff < 0.0D) {\n                            selectedTabLine.setTranslateY(selectedTabLine.getTranslateY() + oldHeight);\n                            newTransY += newHeight;\n                            rotate.setAngle(180.0D);\n                        }\n\n                        timeline = new Timeline(\n                                new KeyFrame(\n                                        Duration.ZERO,\n                                        new KeyValue(selectedTabLine.translateYProperty(), selectedTabLine.getTranslateY(), Interpolator.EASE_BOTH)\n                                ),\n                                new KeyFrame(\n                                        Duration.seconds(1.24D),\n                                        new KeyValue(scale.yProperty(), newScaleY, Interpolator.EASE_BOTH),\n                                        new KeyValue(selectedTabLine.translateYProperty(), newTransY, Interpolator.EASE_BOTH)\n                                )\n                        );\n                        timeline.setOnFinished((finish) -> {\n                            if (rotate.getAngle() != 0.0D) {\n                                rotate.setAngle(0.0D);\n                                selectedTabLine.setTranslateY(selectedTabLine.getTranslateY() - newHeight);\n                            }\n\n                        });\n                        timeline.play();\n                    }\n\n                    @Override\n                    public void animateSelectionLine() {\n                        double offset = 0.0D;\n                        double selectedTabOffset = 0.0D;\n                        double selectedTabHeight = 0.0D;\n\n                        for (Node node : headersRegion.getChildren()) {\n                            if (node instanceof TabHeaderContainer) {\n                                TabHeaderContainer tabHeader = (TabHeaderContainer) node;\n                                double tabHeaderPrefHeight = snapSize(tabHeader.prefHeight(-1.0D));\n                                if (selectedTab != null && selectedTab.equals(tabHeader.tab)) {\n                                    selectedTabOffset = offset;\n                                    selectedTabHeight = tabHeaderPrefHeight;\n                                    break;\n                                }\n\n                                offset += tabHeaderPrefHeight;\n                            }\n                        }\n\n                        this.runTimeline(selectedTabOffset, selectedTabHeight);\n                    }\n                }\n\n                private final class Left extends Vertical {\n                    @Override\n                    public void layoutChildren() {\n                        super.layoutChildren();\n\n                        double headerWidth = snapSize(prefWidth(-1));\n                        double tabStartY = 0;\n                        for (Node node : getChildren()) {\n                            if (!(node instanceof TabHeaderContainer)) continue;\n                            TabHeaderContainer child = (TabHeaderContainer) node;\n                            double w = snapSize(child.prefWidth(-1));\n                            double h = snapSize(child.prefHeight(-1));\n                            child.resize(w, h);\n\n                            child.relocate(headerWidth - w - snappedRightInset(), tabStartY);\n                            tabStartY += h;\n                        }\n\n                        selectedTabLine.resizeRelocate(headerWidth - selectedTabLine.prefWidth(-1), 0,\n                                snapSize(selectedTabLine.prefWidth(-1)),\n                                snapSize(selectedTabLine.prefHeight(-1)));\n                    }\n                }\n\n                private final class Right extends Vertical {\n                    @Override\n                    public void layoutChildren() {\n                        super.layoutChildren();\n\n                        double headerWidth = snapSize(prefWidth(-1));\n                        double tabStartY = 0;\n                        for (Node node : getChildren()) {\n                            if (!(node instanceof TabHeaderContainer)) continue;\n                            TabHeaderContainer child = (TabHeaderContainer) node;\n                            double w = snapSize(child.prefWidth(-1));\n                            double h = snapSize(child.prefHeight(-1));\n                            child.resize(w, h);\n\n                            child.relocate(snappedLeftInset(), tabStartY);\n                            tabStartY += h;\n                        }\n\n                        selectedTabLine.resizeRelocate(0, 0,\n                                snapSize(selectedTabLine.prefWidth(-1)),\n                                snapSize(selectedTabLine.prefHeight(-1)));\n                    }\n                }\n\n            }\n        }\n\n        protected class TabHeaderContainer extends StackPane {\n\n            private final Tab<?> tab;\n            private final Label tabText;\n            private final BorderPane inner;\n\n            public TabHeaderContainer(Tab<?> tab) {\n                this.tab = tab;\n\n                tabText = new Label();\n                tabText.textProperty().bind(tab.textProperty());\n                tabText.getStyleClass().add(\"tab-label\");\n                inner = new BorderPane();\n                inner.setCenter(tabText);\n                inner.getStyleClass().add(\"tab-container\");\n                inner.setMouseTransparent(true);\n                RipplerContainer rippler = new RipplerContainer(inner);\n                getChildren().setAll(rippler);\n\n                FXUtils.onChangeAndOperate(tab.selectedProperty(), selected -> inner.pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, selected));\n\n                FXUtils.onClicked(this, () -> {\n                    this.setOpacity(1);\n                    getSkinnable().getSelectionModel().select(tab);\n                });\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.application.Platform;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Insets;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.task.*;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class TaskExecutorDialogPane extends BorderPane {\n    private TaskExecutor executor;\n    private TaskCancellationAction onCancel;\n    @SuppressWarnings({\"unused\", \"FieldCanBeLocal\"})\n    private final Consumer<FetchTask.SpeedEvent> speedEventHandler;\n\n    private final Label lblTitle;\n    private final Label lblProgress;\n    private final JFXButton btnCancel;\n    private final TaskListPane taskListPane;\n\n    public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) {\n        this.getStyleClass().add(\"task-executor-dialog-layout\");\n\n        FXUtils.setLimitWidth(this, 500);\n        FXUtils.setLimitHeight(this, 300);\n\n        VBox center = new VBox();\n        this.setCenter(center);\n        center.setPadding(new Insets(16));\n        {\n            lblTitle = new Label();\n            lblTitle.setStyle(\"-fx-font-size: 14px; -fx-font-weight: BOLD;\");\n\n            taskListPane = new TaskListPane();\n            VBox.setVgrow(taskListPane, Priority.ALWAYS);\n\n            center.getChildren().setAll(lblTitle, taskListPane);\n        }\n\n        BorderPane bottom = new BorderPane();\n        this.setBottom(bottom);\n        bottom.setPadding(new Insets(0, 8, 8, 8));\n        {\n            lblProgress = new Label();\n            bottom.setLeft(lblProgress);\n\n            btnCancel = new JFXButton(i18n(\"button.cancel\"));\n            btnCancel.getStyleClass().add(\"dialog-cancel\");\n            bottom.setRight(btnCancel);\n        }\n\n        setCancel(cancel);\n\n        btnCancel.setOnAction(e -> {\n            if (onCancel.getCancellationAction() != null) {\n                if (executor != null)\n                    executor.cancel();\n                onCancel.getCancellationAction().accept(this);\n            }\n        });\n\n        speedEventHandler = FetchTask.SPEED_EVENT.registerWeak(speedEvent -> {\n            String message = I18n.formatSpeed(speedEvent.getSpeed());\n            Platform.runLater(() -> lblProgress.setText(message));\n        });\n\n        onEscPressed(this, btnCancel::fire);\n    }\n\n    public void setExecutor(TaskExecutor executor) {\n        setExecutor(executor, true);\n    }\n\n    public void setExecutor(TaskExecutor executor, boolean autoClose) {\n        this.executor = executor;\n\n        if (executor != null) {\n            taskListPane.setExecutor(executor);\n\n            if (autoClose)\n                executor.addTaskListener(new TaskListener() {\n                    @Override\n                    public void onStop(boolean success, TaskExecutor executor) {\n                        Platform.runLater(() -> fireEvent(new DialogCloseEvent()));\n                    }\n                });\n        }\n    }\n\n    public StringProperty titleProperty() {\n        return lblTitle.textProperty();\n    }\n\n    public String getTitle() {\n        return lblTitle.getText();\n    }\n\n    public void setTitle(String currentState) {\n        lblTitle.setText(currentState);\n    }\n\n    public void setCancel(TaskCancellationAction onCancel) {\n        this.onCancel = onCancel;\n\n        runInFX(() -> btnCancel.setDisable(onCancel == null));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXListView;\nimport com.jfoenix.controls.JFXProgressBar;\nimport javafx.application.Platform;\nimport javafx.beans.WeakListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.DoubleBinding;\nimport javafx.beans.property.*;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.ProgressIndicator;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.StackPane;\nimport org.jackhuang.hmcl.download.cleanroom.CleanroomInstallTask;\nimport org.jackhuang.hmcl.download.fabric.FabricAPIInstallTask;\nimport org.jackhuang.hmcl.download.fabric.FabricInstallTask;\nimport org.jackhuang.hmcl.download.forge.ForgeNewInstallTask;\nimport org.jackhuang.hmcl.download.forge.ForgeOldInstallTask;\nimport org.jackhuang.hmcl.download.game.GameAssetDownloadTask;\nimport org.jackhuang.hmcl.download.game.GameInstallTask;\nimport org.jackhuang.hmcl.download.java.mojang.MojangJavaDownloadTask;\nimport org.jackhuang.hmcl.download.legacyfabric.LegacyFabricInstallTask;\nimport org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;\nimport org.jackhuang.hmcl.download.neoforge.NeoForgeInstallTask;\nimport org.jackhuang.hmcl.download.neoforge.NeoForgeOldInstallTask;\nimport org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;\nimport org.jackhuang.hmcl.download.quilt.QuiltAPIInstallTask;\nimport org.jackhuang.hmcl.download.quilt.QuiltInstallTask;\nimport org.jackhuang.hmcl.game.HMCLModpackInstallTask;\nimport org.jackhuang.hmcl.java.JavaInstallTask;\nimport org.jackhuang.hmcl.mod.MinecraftInstanceTask;\nimport org.jackhuang.hmcl.mod.ModpackInstallTask;\nimport org.jackhuang.hmcl.mod.ModpackUpdateTask;\nimport org.jackhuang.hmcl.mod.curse.CurseCompletionTask;\nimport org.jackhuang.hmcl.mod.curse.CurseInstallTask;\nimport org.jackhuang.hmcl.mod.mcbbs.McbbsModpackCompletionTask;\nimport org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthCompletionTask;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthInstallTask;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthModpackExportTask;\nimport org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;\nimport org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;\nimport org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;\nimport org.jackhuang.hmcl.mod.server.ServerModpackExportTask;\nimport org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.jackhuang.hmcl.task.TaskListener;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.util.FXThread;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.lang.ref.WeakReference;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.Lang.tryCast;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class TaskListPane extends StackPane {\n    private static final Insets DEFAULT_PROGRESS_NODE_PADDING = new Insets(0, 0, 4, 0);\n    private static final Insets STAGED_PROGRESS_NODE_PADDING = new Insets(0, 0, 4, 26);\n\n    private TaskExecutor executor;\n    private final JFXListView<Node> listView = new JFXListView<>();\n    private final Map<Task<?>, ProgressListNode> nodes = new HashMap<>();\n    private final Map<String, StageNode> stageNodes = new HashMap<>();\n    private final ObjectProperty<Insets> progressNodePadding = new SimpleObjectProperty<>(Insets.EMPTY);\n    private final DoubleProperty cellWidth = new SimpleDoubleProperty();\n\n    public TaskListPane() {\n        listView.setPadding(new Insets(12, 0, 0, 0));\n        listView.setCellFactory(l -> new Cell());\n        listView.setSelectionModel(null);\n        FXUtils.onChangeAndOperate(listView.widthProperty(), width -> {\n            double w = width.doubleValue();\n            cellWidth.set(w <= 12.0 ? w : w - 12.0);\n        });\n\n        getChildren().setAll(listView);\n    }\n\n    @FXThread\n    private void addStagesHints(@NotNull Collection<Task.StagesHint> hints) {\n        for (Task.StagesHint hint : hints) {\n            StageNode node = stageNodes.get(hint.stage());\n\n            if (node == null) {\n                node = new StageNode(hint.stage());\n                stageNodes.put(hint.stage(), node);\n                listView.getItems().add(node);\n            }\n            for (String stage : hint.aliases()) {\n                stageNodes.put(stage, node);\n            }\n        }\n    }\n\n    @FXThread\n    private void updateProgressNodePadding() {\n        progressNodePadding.set(stageNodes.isEmpty() ? DEFAULT_PROGRESS_NODE_PADDING : STAGED_PROGRESS_NODE_PADDING);\n    }\n\n    public void setExecutor(TaskExecutor executor) {\n        this.executor = executor;\n        executor.addTaskListener(new TaskListener() {\n            @Override\n            public void onStart() {\n                Platform.runLater(() -> {\n                    stageNodes.clear();\n                    listView.getItems().clear();\n                    addStagesHints(executor.getHints());\n                    updateProgressNodePadding();\n                });\n            }\n\n            @Override\n            public void onReady(Task<?> task) {\n                if (task instanceof Task.StagesHintTask) {\n                    Platform.runLater(() -> {\n                        addStagesHints(((Task<?>.StagesHintTask) task).getHints());\n                        updateProgressNodePadding();\n                    });\n                }\n\n                if (task.getStage() != null) {\n                    Platform.runLater(() -> {\n                        StageNode node = stageNodes.get(task.getStage());\n                        if (node != null)\n                            node.begin();\n                    });\n                }\n            }\n\n            @Override\n            public void onRunning(Task<?> task) {\n                if (!task.getSignificance().shouldShow() || task.getName() == null)\n                    return;\n\n                if (task instanceof GameAssetDownloadTask) {\n                    task.setName(i18n(\"assets.download_all\"));\n                } else if (task instanceof GameInstallTask) {\n                    if (task.getInheritedStage() != null && task.getInheritedStage().startsWith(\"hmcl.install.game\"))\n                        return;\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.game\")));\n                } else if (task instanceof CleanroomInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.cleanroom\")));\n                } else if (task instanceof LegacyFabricInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.legacyfabric\")));\n                } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.forge\")));\n                } else if (task instanceof NeoForgeInstallTask || task instanceof NeoForgeOldInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.neoforge\")));\n                } else if (task instanceof LiteLoaderInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.liteloader\")));\n                } else if (task instanceof OptiFineInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.optifine\")));\n                } else if (task instanceof FabricInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.fabric\")));\n                } else if (task instanceof FabricAPIInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.fabric-api\")));\n                } else if (task instanceof QuiltInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.quilt\")));\n                } else if (task instanceof QuiltAPIInstallTask) {\n                    task.setName(i18n(\"install.installer.install\", i18n(\"install.installer.quilt-api\")));\n                } else if (task instanceof CurseCompletionTask || task instanceof ModrinthCompletionTask || task instanceof ServerModpackCompletionTask || task instanceof McbbsModpackCompletionTask) {\n                    task.setName(i18n(\"modpack.completion\"));\n                } else if (task instanceof ModpackInstallTask) {\n                    task.setName(i18n(\"modpack.installing\"));\n                } else if (task instanceof ModpackUpdateTask) {\n                    task.setName(i18n(\"modpack.update\"));\n                } else if (task instanceof CurseInstallTask) {\n                    task.setName(i18n(\"modpack.installing.given\", i18n(\"modpack.type.curse\")));\n                } else if (task instanceof MultiMCModpackInstallTask) {\n                    task.setName(i18n(\"modpack.installing.given\", i18n(\"modpack.type.multimc\")));\n                } else if (task instanceof ModrinthInstallTask) {\n                    task.setName(i18n(\"modpack.installing.given\", i18n(\"modpack.type.modrinth\")));\n                } else if (task instanceof ServerModpackLocalInstallTask) {\n                    task.setName(i18n(\"install.installing\") + \": \" + i18n(\"modpack.type.server\"));\n                } else if (task instanceof HMCLModpackInstallTask) {\n                    task.setName(i18n(\"modpack.installing.given\", i18n(\"modpack.type.hmcl\")));\n                } else if (task instanceof McbbsModpackExportTask || task instanceof MultiMCModpackExportTask || task instanceof ServerModpackExportTask || task instanceof ModrinthModpackExportTask) {\n                    task.setName(i18n(\"modpack.export\"));\n                } else if (task instanceof MinecraftInstanceTask) {\n                    task.setName(i18n(\"modpack.scan\"));\n                } else if (task instanceof MojangJavaDownloadTask) {\n                    task.setName(i18n(\"download.java\"));\n                } else if (task instanceof JavaInstallTask) {\n                    task.setName(i18n(\"java.installing\"));\n                }\n\n                Platform.runLater(() -> {\n                    ProgressListNode node = new ProgressListNode(task);\n                    nodes.put(task, node);\n                    StageNode stageNode = stageNodes.get(task.getInheritedStage());\n                    listView.getItems().add(listView.getItems().indexOf(stageNode) + 1, node);\n                });\n            }\n\n            @Override\n            public void onFinished(Task<?> task) {\n                Platform.runLater(() -> {\n                    if (task.getStage() != null) {\n                        StageNode stageNode = stageNodes.get(task.getStage());\n                        if (stageNode != null)\n                            stageNode.succeed();\n                    }\n\n                    ProgressListNode node = nodes.remove(task);\n                    if (node != null) {\n                        node.unbind();\n                        listView.getItems().remove(node);\n                    }\n                });\n            }\n\n            @Override\n            public void onFailed(Task<?> task, Throwable throwable) {\n                if (task.getStage() != null) {\n                    Platform.runLater(() -> {\n                        StageNode stageNode = stageNodes.get(task.getStage());\n                        if (stageNode != null)\n                            stageNode.fail();\n                    });\n                }\n                ProgressListNode node = nodes.remove(task);\n                if (node != null)\n                    Platform.runLater(() -> node.setThrowable(throwable));\n            }\n\n            @Override\n            public void onPropertiesUpdate(Task<?> task) {\n                if (task instanceof Task.CountTask) {\n                    runInFX(() -> {\n                        StageNode stageNode = stageNodes.get(((Task<?>.CountTask) task).getCountStage());\n                        if (stageNode != null)\n                            stageNode.count();\n                    });\n\n                    return;\n                }\n\n                if (task.getStage() != null) {\n                    int total = tryCast(task.getProperties().get(\"total\"), Integer.class).orElse(0);\n                    runInFX(() -> {\n                        StageNode stageNode = stageNodes.get(task.getStage());\n                        if (stageNode != null)\n                            stageNode.addTotal(total);\n                    });\n                }\n            }\n        });\n    }\n\n    private final class Cell extends ListCell<Node> {\n        private static final double STATUS_ICON_SIZE = 14;\n        private static final Insets PROGRESS_BAR_MARGIN = new Insets(2, 0, 0, 0);\n\n        private final BorderPane pane = new BorderPane();\n        private final StackPane left = new StackPane();\n        private final Label title = new Label();\n        private final Label message = new Label();\n        private final JFXProgressBar bar = new JFXProgressBar();\n\n        private WeakReference<StageNode> prevStageNodeRef;\n        private StatusChangeListener statusChangeListener;\n\n        private Cell() {\n            setPadding(new Insets(0, 0, 4, 0));\n\n            prefWidthProperty().bind(cellWidth);\n\n            FXUtils.setLimitHeight(left, STATUS_ICON_SIZE);\n            FXUtils.setLimitWidth(left, STATUS_ICON_SIZE);\n\n            BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n            BorderPane.setMargin(left, new Insets(0, 12, 0, 0));\n            BorderPane.setAlignment(title, Pos.CENTER_LEFT);\n            pane.setCenter(title);\n\n            DoubleBinding barWidth = Bindings.createDoubleBinding(() -> {\n                Insets padding = pane.getPadding();\n                Insets insets = pane.getInsets();\n                return pane.getWidth() - padding.getLeft() - padding.getRight() - insets.getLeft() - insets.getRight();\n            }, pane.paddingProperty(), pane.widthProperty());\n            bar.minWidthProperty().bind(barWidth);\n            bar.prefWidthProperty().bind(barWidth);\n            bar.maxWidthProperty().bind(barWidth);\n            BorderPane.setMargin(bar, PROGRESS_BAR_MARGIN);\n\n            setGraphic(pane);\n        }\n\n        private void updateLeftIcon(StageNode.Status status) {\n            left.getChildren().setAll(status.svg.createIcon(STATUS_ICON_SIZE));\n        }\n\n        @Override\n        protected void updateItem(Node item, boolean empty) {\n            super.updateItem(item, empty);\n\n            pane.paddingProperty().unbind();\n            title.textProperty().unbind();\n            message.textProperty().unbind();\n\n            bar.setSmoothProgress(false);\n            bar.progressProperty().unbind();\n            StageNode prevStageNode;\n            if (prevStageNodeRef != null && (prevStageNode = prevStageNodeRef.get()) != null)\n                prevStageNode.status.removeListener(statusChangeListener);\n\n            if (item instanceof ProgressListNode progressListNode) {\n                title.setText(progressListNode.title);\n                message.textProperty().bind(progressListNode.message);\n                bar.progressProperty().bind(progressListNode.progress);\n\n                pane.paddingProperty().bind(progressNodePadding);\n                pane.setLeft(null);\n                pane.setRight(message);\n                pane.setBottom(bar);\n            } else if (item instanceof StageNode stageNode) {\n                title.textProperty().bind(stageNode.title);\n                message.setText(\"\");\n                bar.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS);\n\n                pane.setPadding(Insets.EMPTY);\n                pane.setLeft(left);\n                pane.setRight(message);\n                pane.setBottom(null);\n\n                updateLeftIcon(stageNode.status.get());\n                if (statusChangeListener == null)\n                    statusChangeListener = new StatusChangeListener(this);\n                stageNode.status.addListener(statusChangeListener);\n                prevStageNodeRef = new WeakReference<>(stageNode);\n            } else { // item == null\n                title.setText(\"\");\n                message.setText(\"\");\n                bar.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS);\n                pane.setPadding(Insets.EMPTY);\n                pane.setLeft(null);\n                pane.setRight(null);\n                pane.setBottom(null);\n            }\n\n            bar.setSmoothProgress(true);\n        }\n    }\n\n    private static final class StatusChangeListener implements ChangeListener<StageNode.Status>, WeakListener {\n\n        private final WeakReference<Cell> cellRef;\n\n        private StatusChangeListener(Cell cell) {\n            this.cellRef = new WeakReference<>(cell);\n        }\n\n        @Override\n        public boolean wasGarbageCollected() {\n            return cellRef.get() == null;\n        }\n\n        @Override\n        public void changed(ObservableValue<? extends StageNode.Status> observable,\n                            StageNode.Status oldValue,\n                            StageNode.Status newValue) {\n            Cell cell = cellRef.get();\n            if (cell == null) {\n                if (observable != null)\n                    observable.removeListener(this);\n                return;\n            }\n            cell.updateLeftIcon(newValue);\n        }\n    }\n\n    private static abstract class Node {\n\n    }\n\n    private static final class StageNode extends Node {\n        private int runningTasksCount = 0;\n\n        private enum Status {\n            WAITING(SVG.MORE_HORIZ),\n            RUNNING(SVG.ARROW_FORWARD),\n            SUCCESS(SVG.CHECK),\n            FAILED(SVG.CLOSE);\n\n            private final SVG svg;\n\n            Status(SVG svg) {\n                this.svg = svg;\n            }\n        }\n\n        private final ObjectProperty<Status> status = new SimpleObjectProperty<>(Status.WAITING);\n        private final StringProperty title = new SimpleStringProperty();\n        private final String message;\n        private int count = 0;\n        private int total = 0;\n\n        private StageNode(String stage) {\n            String stageKey;\n            String stageValue;\n\n            int idx = stage.indexOf(':');\n            if (idx >= 0) {\n                stageKey = stage.substring(0, idx);\n                stageValue = stage.substring(idx + 1);\n            } else {\n                stageKey = stage;\n                stageValue = null;\n            }\n\n            // CHECKSTYLE:OFF\n            // @formatter:off\n            message = switch (stageKey) {\n                case \"hmcl.modpack\" ->                  i18n(\"install.modpack\");\n                case \"hmcl.modpack.download\" ->         i18n(\"launch.state.modpack\");\n                case \"hmcl.install.assets\" ->           i18n(\"assets.download\");\n                case \"hmcl.install.libraries\" ->        i18n(\"libraries.download\");\n                case \"hmcl.install.game\" ->             i18n(\"install.installer.install\", i18n(\"install.installer.game\") + \" \" + stageValue);\n                case \"hmcl.install.forge\" ->            i18n(\"install.installer.install\", i18n(\"install.installer.forge\") + \" \" + stageValue);\n                case \"hmcl.install.cleanroom\" ->        i18n(\"install.installer.install\", i18n(\"install.installer.cleanroom\") + \" \" + stageValue);\n                case \"hmcl.install.neoforge\" ->         i18n(\"install.installer.install\", i18n(\"install.installer.neoforge\") + \" \" + stageValue);\n                case \"hmcl.install.liteloader\" ->       i18n(\"install.installer.install\", i18n(\"install.installer.liteloader\") + \" \" + stageValue);\n                case \"hmcl.install.optifine\" ->         i18n(\"install.installer.install\", i18n(\"install.installer.optifine\") + \" \" + stageValue);\n                case \"hmcl.install.fabric\" ->           i18n(\"install.installer.install\", i18n(\"install.installer.fabric\") + \" \" + stageValue);\n                case \"hmcl.install.fabric-api\" ->       i18n(\"install.installer.install\", i18n(\"install.installer.fabric-api\") + \" \" + stageValue);\n                case \"hmcl.install.legacyfabric\" ->     i18n(\"install.installer.install\", i18n(\"install.installer.legacyfabric\") + \" \" + stageValue);\n                case \"hmcl.install.legacyfabric-api\" -> i18n(\"install.installer.install\", i18n(\"install.installer.legacyfabric-api\") + \" \" + stageValue);\n                case \"hmcl.install.quilt\" ->            i18n(\"install.installer.install\", i18n(\"install.installer.quilt\") + \" \" + stageValue);\n                case \"hmcl.install.quilt-api\" ->        i18n(\"install.installer.install\", i18n(\"install.installer.quilt-api\") + \" \" + stageValue);\n                default -> i18n(stageKey);\n            };\n            // @formatter:on\n            // CHECKSTYLE:ON\n\n            title.set(message);\n        }\n\n        private void begin() {\n            runningTasksCount++;\n            if (status.get() == Status.WAITING || status.get() == Status.SUCCESS) {\n                status.set(Status.RUNNING);\n            }\n        }\n\n        public void succeed() {\n            runningTasksCount = Math.max(0, runningTasksCount - 1);\n\n            if (runningTasksCount == 0) {\n                status.set(Status.SUCCESS);\n            }\n        }\n\n        public void fail() {\n            runningTasksCount = Math.max(0, runningTasksCount - 1);\n            status.set(Status.FAILED);\n        }\n\n        public void count() {\n            updateCounter(++count, total);\n        }\n\n        public void addTotal(int n) {\n            this.total += n;\n            updateCounter(count, total);\n        }\n\n        public void updateCounter(int count, int total) {\n            title.setValue(total > 0\n                    ? message + \" - \" + count + \"/\" + total\n                    : message\n            );\n        }\n    }\n\n    private static final class ProgressListNode extends Node {\n        private final String title;\n        private final StringProperty message = new SimpleStringProperty(\"\");\n        private final DoubleProperty progress = new SimpleDoubleProperty(0.0);\n\n        private ProgressListNode(Task<?> task) {\n            this.title = task.getName();\n            progress.bind(task.progressProperty());\n        }\n\n        public void unbind() {\n            progress.unbind();\n        }\n\n        public void setThrowable(Throwable throwable) {\n            unbind();\n            message.set(throwable.getLocalizedMessage());\n            progress.set(0.);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.StringProperty;\nimport javafx.beans.property.StringPropertyBase;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic class TwoLineListItem extends VBox {\n    private static final String DEFAULT_STYLE_CLASS = \"two-line-list-item\";\n\n    private final HBox firstLine;\n    private HBox secondLine;\n\n    private final Label lblTitle;\n    private Label lblSubtitle;\n\n    public TwoLineListItem() {\n        getStyleClass().add(DEFAULT_STYLE_CLASS);\n        setMouseTransparent(true);\n\n        lblTitle = new Label();\n        lblTitle.getStyleClass().add(\"title\");\n\n        this.firstLine = new HBox(lblTitle);\n        firstLine.getStyleClass().add(\"first-line\");\n        firstLine.setAlignment(Pos.CENTER_LEFT);\n\n        this.getChildren().setAll(firstLine);\n    }\n\n    public TwoLineListItem(String titleString, String subtitleString) {\n        this();\n\n        setTitle(titleString);\n        setSubtitle(subtitleString);\n    }\n\n    private void initSecondLine() {\n        if (secondLine == null) {\n            lblSubtitle = new Label();\n            lblSubtitle.getStyleClass().add(\"subtitle\");\n\n            secondLine = new HBox(lblSubtitle);\n        }\n    }\n\n    private final StringProperty title = new StringPropertyBase() {\n        @Override\n        public Object getBean() {\n            return TwoLineListItem.this;\n        }\n\n        @Override\n        public String getName() {\n            return \"title\";\n        }\n\n        @Override\n        protected void invalidated() {\n            lblTitle.setText(get());\n        }\n    };\n\n    public StringProperty titleProperty() {\n        return title;\n    }\n\n    public String getTitle() {\n        return title.get();\n    }\n\n    public void setTitle(String title) {\n        this.title.set(title);\n    }\n\n    private StringProperty subtitle;\n\n    public StringProperty subtitleProperty() {\n        if (subtitle == null) {\n            subtitle = new StringPropertyBase() {\n                @Override\n                public Object getBean() {\n                    return TwoLineListItem.this;\n                }\n\n                @Override\n                public String getName() {\n                    return \"subtitle\";\n                }\n\n                @Override\n                protected void invalidated() {\n                    String subtitle = get();\n\n                    if (subtitle != null) {\n                        initSecondLine();\n                        lblSubtitle.setText(subtitle);\n\n                        if (getChildren().size() == 1)\n                            getChildren().add(secondLine);\n                    } else if (secondLine != null) {\n                        lblSubtitle.setText(null);\n                        if (getChildren().size() > 1)\n                            getChildren().setAll(firstLine);\n                    }\n                }\n            };\n        }\n        return subtitle;\n    }\n\n    public String getSubtitle() {\n        return subtitle != null ? subtitleProperty().get() : null;\n    }\n\n    public void setSubtitle(String subtitle) {\n        if (this.subtitle == null && subtitle == null)\n            return;\n\n        subtitleProperty().set(subtitle);\n    }\n\n    public Label getTitleLabel() {\n        return lblTitle;\n    }\n\n    public Label getSubtitleLabel() {\n        initSecondLine();\n        return lblSubtitle;\n    }\n\n    private ObservableList<Label> tags;\n\n    public ObservableList<Label> getTags() {\n        if (tags == null) {\n            tags = FXCollections.observableArrayList();\n\n            var tagsBox = new HBox(8);\n            tagsBox.getStyleClass().add(\"tags\");\n            tagsBox.setAlignment(Pos.CENTER_LEFT);\n            Bindings.bindContent(tagsBox.getChildren(), tags);\n\n            var scrollPane = new ScrollPane(tagsBox);\n            HBox.setHgrow(scrollPane, Priority.ALWAYS);\n            scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);\n            scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);\n            FXUtils.onChangeAndOperate(tagsBox.heightProperty(), height -> FXUtils.setLimitHeight(scrollPane, height.doubleValue()));\n            scrollPane.setPrefWidth(50);\n            firstLine.getChildren().setAll(lblTitle, scrollPane);\n        }\n        return tags;\n    }\n\n    public void addTag(String tag, PseudoClass pseudoClass) {\n        var tagLabel = new Label(tag);\n        tagLabel.getStyleClass().add(\"tag\");\n        if (pseudoClass != null)\n            tagLabel.pseudoClassStateChanged(pseudoClass, true);\n        getTags().add(tagLabel);\n    }\n\n    public void addTag(String tag) {\n        addTag(tag, null);\n    }\n\n    private static final PseudoClass WARNING_PSEUDO_CLASS = PseudoClass.getPseudoClass(\"warning\");\n\n    public void addTagWarning(String tag) {\n        addTag(tag, WARNING_PSEUDO_CLASS);\n    }\n\n    @Override\n    public String toString() {\n        return \"TwoLineListItem[title=%s, subtitle=%s, tags=%s]\".formatted(getTitle(), getSubtitle(), tags);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/URLValidator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.NamedArg;\nimport javafx.scene.control.TextInputControl;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class URLValidator extends ValidatorBase {\n    private final boolean nullable;\n\n    public URLValidator() {\n        this(false);\n    }\n\n    public URLValidator(@NamedArg(\"nullable\") boolean nullable) {\n        this(i18n(\"input.url\"), nullable);\n    }\n\n    public URLValidator(@NamedArg(\"message\") String message, @NamedArg(\"nullable\") boolean nullable) {\n        super(message);\n        this.nullable = nullable;\n    }\n\n    @Override\n    protected void eval() {\n        if (srcControl.get() instanceof TextInputControl) {\n            evalTextInputField();\n        }\n    }\n\n    private void evalTextInputField() {\n        TextInputControl textField = ((TextInputControl) srcControl.get());\n\n        if (StringUtils.isBlank(textField.getText()))\n            hasErrors.set(!nullable);\n        else {\n            try {\n                NetworkUtils.toURI(textField.getText());\n                hasErrors.set(false);\n            } catch (IllegalArgumentException e) {\n                hasErrors.set(true);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Validator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.construct;\n\nimport com.jfoenix.controls.JFXTextField;\nimport com.jfoenix.validation.base.ValidatorBase;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.scene.control.TextInputControl;\n\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\nimport org.jackhuang.hmcl.util.javafx.SafeStringConverter;\n\npublic final class Validator extends ValidatorBase {\n\n    public static Consumer<Predicate<String>> addTo(JFXTextField control) {\n        return addTo(control, null);\n    }\n\n    /**\n     * @see SafeStringConverter#asPredicate(Consumer)\n     */\n    public static Consumer<Predicate<String>> addTo(JFXTextField control, String message) {\n        return predicate -> {\n            Validator validator = new Validator(message, predicate);\n            InvalidationListener listener = any -> control.validate();\n            validator.getProperties().put(validator, listener);\n            control.textProperty().addListener(new WeakInvalidationListener(listener));\n            control.getValidators().add(validator);\n        };\n    }\n\n    private final Predicate<String> validator;\n\n    /**\n     * @param validator return true if the input string is valid.\n     */\n    public Validator(Predicate<String> validator) {\n        this.validator = validator;\n    }\n\n    public Validator(String message, Predicate<String> validator) {\n        this(validator);\n\n        setMessage(message);\n    }\n\n    @Override\n    protected void eval() {\n        if (this.srcControl.get() instanceof TextInputControl) {\n            String text = ((TextInputControl) srcControl.get()).getText();\n            hasErrors.set(!validator.test(text));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/Decorator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.decorator;\n\nimport com.jfoenix.controls.JFXSnackbar;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Skin;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundFill;\nimport javafx.scene.layout.CornerRadii;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.stage.Stage;\nimport javafx.stage.StageStyle;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.Launcher;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.ui.animation.Motion;\nimport org.jackhuang.hmcl.ui.wizard.Navigation;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\npublic class Decorator extends Control {\n    private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());\n    private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());\n    private final ObjectProperty<Background> contentBackground = new SimpleObjectProperty<>();\n    private final ObjectProperty<DecoratorPage.State> state = new SimpleObjectProperty<>();\n    private final ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonAction = new SimpleObjectProperty<>();\n    private final ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonAction = new SimpleObjectProperty<>();\n    private final ObjectProperty<EventHandler<ActionEvent>> onRefreshNavButtonAction = new SimpleObjectProperty<>();\n    private final BooleanProperty canRefresh = new SimpleBooleanProperty(false);\n    private final BooleanProperty canBack = new SimpleBooleanProperty(false);\n    private final BooleanProperty canClose = new SimpleBooleanProperty(false);\n    private final BooleanProperty showCloseAsHome = new SimpleBooleanProperty(false);\n    private final BooleanProperty titleTransparent = new SimpleBooleanProperty(false);\n    private final Stage primaryStage;\n    private Navigation.NavigationDirection navigationDirection = Navigation.NavigationDirection.START;\n    private StackPane drawerWrapper;\n    private final JFXSnackbar snackbar = new JFXSnackbar();\n\n    private final ReadOnlyBooleanWrapper allowMove = new ReadOnlyBooleanWrapper();\n    private final ReadOnlyBooleanWrapper dragging = new ReadOnlyBooleanWrapper();\n\n    private boolean playRestoreMinimizeAnimation = false;\n\n    public Decorator(Stage primaryStage) {\n        this.primaryStage = primaryStage;\n\n        setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));\n\n        primaryStage.initStyle(StageStyle.UNDECORATED);\n\n        if (AnimationUtils.playWindowAnimation()) {\n            FXUtils.onChange(primaryStage.iconifiedProperty(), iconified -> {\n                if (playRestoreMinimizeAnimation && !iconified) {\n                    playRestoreMinimizeAnimation = false;\n                    Timeline timeline = new Timeline(\n                            new KeyFrame(Duration.ZERO,\n                                    new KeyValue(this.opacityProperty(), 0, Motion.EASE),\n                                    new KeyValue(this.translateYProperty(), 200, Motion.EASE),\n                                    new KeyValue(this.scaleXProperty(), 0.4, Motion.EASE),\n                                    new KeyValue(this.scaleYProperty(), 0.4, Motion.EASE),\n                                    new KeyValue(this.scaleZProperty(), 0.4, Motion.EASE)\n                            ),\n                            new KeyFrame(Motion.SHORT4,\n                                    new KeyValue(this.opacityProperty(), 1, Motion.EASE),\n                                    new KeyValue(this.translateYProperty(), 0, Motion.EASE),\n                                    new KeyValue(this.scaleXProperty(), 1, Motion.EASE),\n                                    new KeyValue(this.scaleYProperty(), 1, Motion.EASE),\n                                    new KeyValue(this.scaleZProperty(), 1, Motion.EASE)\n                            )\n                    );\n                    timeline.play();\n                }\n            });\n        }\n\n    }\n\n    public Stage getPrimaryStage() {\n        return primaryStage;\n    }\n\n    public StackPane getDrawerWrapper() {\n        return drawerWrapper;\n    }\n\n    public void setDrawerWrapper(StackPane drawerWrapper) {\n        this.drawerWrapper = drawerWrapper;\n    }\n\n    public ObservableList<Node> getContent() {\n        return content.get();\n    }\n\n    public ListProperty<Node> contentProperty() {\n        return content;\n    }\n\n    public void setContent(ObservableList<Node> content) {\n        this.content.set(content);\n    }\n\n    public DecoratorPage.State getState() {\n        return state.get();\n    }\n\n    public ObjectProperty<DecoratorPage.State> stateProperty() {\n        return state;\n    }\n\n    public void setState(DecoratorPage.State state) {\n        this.state.set(state);\n    }\n\n    public ObservableList<Node> getContainer() {\n        return container.get();\n    }\n\n    public ListProperty<Node> containerProperty() {\n        return container;\n    }\n\n    public void setContainer(ObservableList<Node> container) {\n        this.container.set(container);\n    }\n\n    public Background getContentBackground() {\n        return contentBackground.get();\n    }\n\n    public ObjectProperty<Background> contentBackgroundProperty() {\n        return contentBackground;\n    }\n\n    public void setContentBackground(Background contentBackground) {\n        this.contentBackground.set(contentBackground);\n    }\n\n    public BooleanProperty canRefreshProperty() {\n        return canRefresh;\n    }\n\n    public BooleanProperty canBackProperty() {\n        return canBack;\n    }\n\n    public BooleanProperty canCloseProperty() {\n        return canClose;\n    }\n\n    public BooleanProperty showCloseAsHomeProperty() {\n        return showCloseAsHome;\n    }\n\n    public boolean isAllowMove() {\n        return allowMove.get();\n    }\n\n    public ReadOnlyBooleanProperty allowMoveProperty() {\n        return allowMove.getReadOnlyProperty();\n    }\n\n    void setAllowMove(boolean allowMove) {\n        this.allowMove.set(allowMove);\n    }\n\n    public boolean isDragging() {\n        return dragging.get();\n    }\n\n    public ReadOnlyBooleanProperty draggingProperty() {\n        return dragging.getReadOnlyProperty();\n    }\n\n    void setDragging(boolean dragging) {\n        this.dragging.set(dragging);\n    }\n\n    public boolean isTitleTransparent() {\n        return titleTransparent.get();\n    }\n\n    public BooleanProperty titleTransparentProperty() {\n        return titleTransparent;\n    }\n\n    public void setTitleTransparent(boolean titleTransparent) {\n        this.titleTransparent.set(titleTransparent);\n    }\n\n    public ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonActionProperty() {\n        return onBackNavButtonAction;\n    }\n\n    public ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonActionProperty() {\n        return onCloseNavButtonAction;\n    }\n\n    public ObjectProperty<EventHandler<ActionEvent>> onRefreshNavButtonActionProperty() {\n        return onRefreshNavButtonAction;\n    }\n\n    public JFXSnackbar getSnackbar() {\n        return snackbar;\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new DecoratorSkin(this);\n    }\n\n    public void minimize() {\n        if (AnimationUtils.playWindowAnimation() && OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) {\n            playRestoreMinimizeAnimation = true;\n            Timeline timeline = new Timeline(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(this.opacityProperty(), 1, Motion.EASE),\n                            new KeyValue(this.translateYProperty(), 0, Motion.EASE),\n                            new KeyValue(this.scaleXProperty(), 1, Motion.EASE),\n                            new KeyValue(this.scaleYProperty(), 1, Motion.EASE),\n                            new KeyValue(this.scaleZProperty(), 1, Motion.EASE)\n                    ),\n                    new KeyFrame(Motion.SHORT4,\n                            new KeyValue(this.opacityProperty(), 0, Motion.EASE),\n                            new KeyValue(this.translateYProperty(), 200, Motion.EASE),\n                            new KeyValue(this.scaleXProperty(), 0.4, Motion.EASE),\n                            new KeyValue(this.scaleYProperty(), 0.4, Motion.EASE),\n                            new KeyValue(this.scaleZProperty(), 0.4, Motion.EASE)\n                    )\n            );\n            timeline.setOnFinished(event -> primaryStage.setIconified(true));\n            timeline.play();\n        } else {\n            primaryStage.setIconified(true);\n        }\n    }\n\n    public void close() {\n        if (AnimationUtils.playWindowAnimation()) {\n            Timeline timeline = new Timeline(\n                    new KeyFrame(Duration.millis(0),\n                            new KeyValue(opacityProperty(), 1, Motion.EASE),\n                            new KeyValue(scaleXProperty(), 1, Motion.EASE),\n                            new KeyValue(scaleYProperty(), 1, Motion.EASE),\n                            new KeyValue(scaleZProperty(), 0.3, Motion.EASE)\n                    ),\n                    new KeyFrame(Duration.millis(200),\n                            new KeyValue(opacityProperty(), 0, Motion.EASE),\n                            new KeyValue(scaleXProperty(), 0.8, Motion.EASE),\n                            new KeyValue(scaleYProperty(), 0.8, Motion.EASE),\n                            new KeyValue(scaleZProperty(), 0.8, Motion.EASE)\n                    )\n            );\n            timeline.setOnFinished(event -> Launcher.stopApplication());\n            timeline.play();\n        } else {\n            Launcher.stopApplication();\n        }\n    }\n\n    public void capableDraggingWindow(Node node) {\n        node.addEventHandler(MouseEvent.MOUSE_MOVED, e -> allowMove.set(true));\n        node.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {\n            if (!isDragging()) allowMove.set(false);\n        });\n    }\n\n    public void forbidDraggingWindow(Node node) {\n        node.addEventHandler(MouseEvent.MOUSE_MOVED, e -> {\n            allowMove.set(false);\n            e.consume();\n        });\n    }\n\n    // TODO: Dirty implementation.\n    public Navigation.NavigationDirection getNavigationDirection() {\n        return navigationDirection;\n    }\n\n    public void setNavigationDirection(Navigation.NavigationDirection navigationDirection) {\n        this.navigationDirection = navigationDirection;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorAnimatedPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.decorator;\n\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\npublic class DecoratorAnimatedPage extends Control {\n\n    protected final VBox left = new VBox();\n    protected final StackPane center = new StackPane();\n\n    {\n        getStyleClass().add(\"gray-background\");\n    }\n\n    protected void setLeft(Node... children) {\n        left.getChildren().setAll(children);\n    }\n\n    protected void setCenter(Node... children) {\n        center.getChildren().setAll(children);\n    }\n\n    public VBox getLeft() {\n        return left;\n    }\n\n    public StackPane getCenter() {\n        return center;\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new DecoratorAnimatedPageSkin<>(this);\n    }\n\n    public static class DecoratorAnimatedPageSkin<T extends DecoratorAnimatedPage> extends SkinBase<T> {\n\n        protected DecoratorAnimatedPageSkin(T control) {\n            super(control);\n\n            BorderPane pane = new BorderPane();\n            pane.setLeft(control.left);\n            FXUtils.setLimitWidth(control.left, 200);\n            pane.setCenter(control.center);\n            getChildren().setAll(pane);\n        }\n\n        protected void setLeft(Node... children) {\n            getSkinnable().setLeft(children);\n        }\n\n        protected void setCenter(Node... children) {\n            getSkinnable().setCenter(children);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.decorator;\n\nimport com.jfoenix.controls.JFXSnackbar;\nimport com.jfoenix.controls.JFXSnackbarLayout;\nimport javafx.animation.Interpolator;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.PixelReader;\nimport javafx.scene.image.PixelWriter;\nimport javafx.scene.image.WritableImage;\nimport javafx.scene.input.*;\nimport javafx.scene.layout.*;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Paint;\nimport javafx.stage.Stage;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;\nimport org.jackhuang.hmcl.setting.EnumBackgroundImage;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.DialogUtils;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.Motion;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane.AnimationProducer;\nimport org.jackhuang.hmcl.ui.construct.JFXDialogPane;\nimport org.jackhuang.hmcl.ui.construct.Navigator;\nimport org.jackhuang.hmcl.ui.wizard.Refreshable;\nimport org.jackhuang.hmcl.ui.wizard.WizardProvider;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Locale;\nimport java.util.Random;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.io.FileUtils.getExtension;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class DecoratorController {\n    private final Decorator decorator;\n    private final Navigator navigator;\n\n    public DecoratorController(Stage stage, Node mainPage) {\n        decorator = new Decorator(stage);\n        decorator.titleTransparentProperty().bind(config().titleTransparentProperty());\n\n        navigator = new Navigator();\n        navigator.setOnNavigated(this::onNavigated);\n        navigator.init(mainPage);\n\n        decorator.getContent().setAll(navigator);\n        decorator.onCloseNavButtonActionProperty().set(e -> close());\n        decorator.onBackNavButtonActionProperty().set(e -> back());\n        decorator.onRefreshNavButtonActionProperty().set(e -> refresh());\n\n        setupAuthlibInjectorDnD();\n\n        // Setup background\n        decorator.setContentBackground(getBackground());\n        changeBackgroundListener = o -> updateBackground();\n        WeakInvalidationListener weakListener = new WeakInvalidationListener(changeBackgroundListener);\n        config().backgroundImageTypeProperty().addListener(weakListener);\n        config().backgroundImageProperty().addListener(weakListener);\n        config().backgroundImageUrlProperty().addListener(weakListener);\n        config().backgroundPaintProperty().addListener(weakListener);\n        config().backgroundImageOpacityProperty().addListener(weakListener);\n\n        // pass key events to current dialog / current page\n        decorator.addEventFilter(KeyEvent.ANY, e -> {\n            if (!(e.getTarget() instanceof Node t)) {\n                return;\n            }\n\n            Node newTarget;\n\n            JFXDialogPane currentDialogPane = null;\n            if (decorator.getDrawerWrapper() != null) {\n                currentDialogPane = (JFXDialogPane) decorator.getDrawerWrapper().getProperties().get(DialogUtils.PROPERTY_DIALOG_PANE_INSTANCE);\n            }\n\n            if (currentDialogPane != null && currentDialogPane.peek().isPresent()) {\n                newTarget = currentDialogPane.peek().get();\n            } else {\n                newTarget = navigator.getCurrentPage();\n            }\n            boolean needsRedirect = true;\n\n            while (t != null) {\n                if (t == newTarget) {\n                    // current event target is in newTarget\n                    needsRedirect = false;\n                    break;\n                }\n                t = t.getParent();\n            }\n            if (!needsRedirect) {\n                return;\n            }\n\n            e.consume();\n            newTarget.fireEvent(e.copyFor(e.getSource(), newTarget));\n        });\n\n        // press ESC to go back\n        onEscPressed(navigator, this::back);\n\n        // https://github.com/HMCL-dev/HMCL/issues/4290\n        if (OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) {\n            // press F11 to toggle full screen\n            navigator.addEventHandler(KeyEvent.KEY_PRESSED, e -> {\n                if (e.getCode() == KeyCode.F11) {\n                    stage.setFullScreen(!stage.isFullScreen());\n                    e.consume();\n                }\n            });\n        }\n\n        navigator.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> {\n            if (e.getButton() == MouseButton.BACK) {\n                back();\n                e.consume();\n            }\n        });\n    }\n\n    public Decorator getDecorator() {\n        return decorator;\n    }\n\n    // ==== Background ====\n\n    //FXThread\n    private int changeBackgroundCount = 0;\n\n    @SuppressWarnings(\"FieldCanBeLocal\") // Strong reference\n    private final InvalidationListener changeBackgroundListener;\n\n    private void updateBackground() {\n        final int currentCount = ++this.changeBackgroundCount;\n        Task.supplyAsync(Schedulers.io(), this::getBackground)\n                .setName(\"Update background\")\n                .whenComplete(Schedulers.javafx(), (background, exception) -> {\n                    if (exception == null) {\n                        if (this.changeBackgroundCount == currentCount)\n                            decorator.setContentBackground(background);\n                    } else {\n                        LOG.warning(\"Failed to update background\", exception);\n                    }\n                }).start();\n    }\n\n    private Background getBackground() {\n        EnumBackgroundImage imageType = config().getBackgroundImageType();\n        if (imageType == null)\n            imageType = EnumBackgroundImage.DEFAULT;\n\n        Image image = null;\n        switch (imageType) {\n            case CUSTOM:\n                String backgroundImage = config().getBackgroundImage();\n                if (backgroundImage != null)\n                    try {\n                        Path path = Path.of(backgroundImage);\n                        image = Files.isDirectory(path)\n                                ? randomImageIn(path)\n                                : tryLoadImage(path);\n                    } catch (Exception e) {\n                        LOG.warning(\"Couldn't load background image\", e);\n                    }\n                break;\n            case NETWORK:\n                String backgroundImageUrl = config().getBackgroundImageUrl();\n                if (backgroundImageUrl != null) {\n                    try {\n                        image = FXUtils.loadImage(backgroundImageUrl);\n                    } catch (Exception e) {\n                        LOG.warning(\"Couldn't load background image\", e);\n                    }\n                }\n                break;\n            case CLASSIC:\n                image = newBuiltinImage(\"/assets/img/background-classic.jpg\");\n                break;\n            case TRANSLUCENT: // Deprecated\n                return new Background(new BackgroundFill(new Color(1, 1, 1, 0.5), CornerRadii.EMPTY, Insets.EMPTY));\n            case PAINT:\n                Paint paint = config().getBackgroundPaint();\n                double opacity = Lang.clamp(0, config().getBackgroundImageOpacity(), 100) / 100.;\n                if (paint instanceof Color || paint == null) {\n                    Color color = (Color) paint;\n                    if (color == null)\n                        color = Color.WHITE; // Default to white if no color is set\n                    if (opacity < 1.)\n                        color = new Color(color.getRed(), color.getGreen(), color.getBlue(), opacity);\n                    return new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY));\n                } else {\n                    // TODO: Support opacity for non-color paints\n                    return new Background(new BackgroundFill(paint, CornerRadii.EMPTY, Insets.EMPTY));\n                }\n        }\n        if (image == null) {\n            image = loadDefaultBackgroundImage();\n        }\n        return createBackgroundWithOpacity(image, config().getBackgroundImageOpacity());\n    }\n\n    private Background createBackgroundWithOpacity(Image image, int opacity) {\n        if (opacity <= 0) {\n            return new Background(new BackgroundFill(new Color(1, 1, 1, 0), CornerRadii.EMPTY, Insets.EMPTY));\n        } else if (opacity >= 100 || image.getPixelReader() == null) {\n            return new Background(new BackgroundImage(\n                    image,\n                    BackgroundRepeat.NO_REPEAT,\n                    BackgroundRepeat.NO_REPEAT,\n                    BackgroundPosition.DEFAULT,\n                    new BackgroundSize(800, 480, false, false, true, true)\n            ));\n        } else {\n            WritableImage tempImage = new WritableImage((int) image.getWidth(), (int) image.getHeight());\n            PixelReader pixelReader = image.getPixelReader();\n            PixelWriter pixelWriter = tempImage.getPixelWriter();\n            for (int y = 0; y < image.getHeight(); y++) {\n                for (int x = 0; x < image.getWidth(); x++) {\n                    Color color = pixelReader.getColor(x, y);\n                    Color newColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getOpacity() * opacity / 100);\n                    pixelWriter.setColor(x, y, newColor);\n                }\n            }\n\n            return new Background(new BackgroundImage(\n                    tempImage,\n                    BackgroundRepeat.NO_REPEAT,\n                    BackgroundRepeat.NO_REPEAT,\n                    BackgroundPosition.DEFAULT,\n                    new BackgroundSize(800, 480, false, false, true, true)\n            ));\n        }\n    }\n\n    /**\n     * Load background image from bg/, background.png, background.jpg, background.gif\n     */\n    private Image loadDefaultBackgroundImage() {\n        Image image = randomImageIn(Metadata.HMCL_CURRENT_DIRECTORY.resolve(\"background\"));\n        if (image != null)\n            return image;\n\n        for (String extension : FXUtils.IMAGE_EXTENSIONS) {\n            image = tryLoadImage(Metadata.HMCL_CURRENT_DIRECTORY.resolve(\"background.\" + extension));\n            if (image != null)\n                return image;\n        }\n\n        image = randomImageIn(Metadata.CURRENT_DIRECTORY.resolve(\"bg\"));\n        if (image != null)\n            return image;\n\n        for (String extension : FXUtils.IMAGE_EXTENSIONS) {\n            image = tryLoadImage(Metadata.CURRENT_DIRECTORY.resolve(\"background.\" + extension));\n            if (image != null)\n                return image;\n        }\n\n        return newBuiltinImage(\"/assets/img/background.jpg\");\n    }\n\n    private @Nullable Image randomImageIn(Path imageDir) {\n        if (!Files.isDirectory(imageDir)) {\n            return null;\n        }\n\n        ArrayList<Path> candidates;\n        try (Stream<Path> stream = Files.list(imageDir)) {\n            candidates = stream\n                    .filter(it -> FXUtils.IMAGE_EXTENSIONS.contains(getExtension(it).toLowerCase(Locale.ROOT)))\n                    .filter(Files::isReadable)\n                    .collect(Collectors.toCollection(ArrayList::new));\n        } catch (IOException e) {\n            LOG.warning(\"Failed to list files in \" + imageDir, e);\n            return null;\n        }\n\n        Random rnd = new Random();\n        while (!candidates.isEmpty()) {\n            int selected = rnd.nextInt(candidates.size());\n            Image loaded = tryLoadImage(candidates.get(selected));\n            if (loaded != null)\n                return loaded;\n            else\n                candidates.remove(selected);\n        }\n        return null;\n    }\n\n    private @Nullable Image tryLoadImage(Path path) {\n        if (!Files.isReadable(path))\n            return null;\n\n        try {\n            return FXUtils.loadImage(path);\n        } catch (Exception e) {\n            LOG.warning(\"Couldn't load background image\", e);\n            return null;\n        }\n    }\n\n    // ==== Navigation ====\n\n    public void navigate(Node node, AnimationProducer animationProducer, Duration duration, Interpolator interpolator) {\n        navigator.navigate(node, animationProducer, duration, interpolator);\n    }\n\n    private void close() {\n        if (navigator.getCurrentPage() instanceof DecoratorPage) {\n            DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();\n\n            if (page.isPageCloseable()) {\n                page.closePage();\n                return;\n            }\n        }\n        navigator.clear();\n    }\n\n    private void back() {\n        if (navigator.getCurrentPage() instanceof DecoratorPage) {\n            DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();\n\n            if (page.back()) {\n                if (navigator.canGoBack()) {\n                    navigator.close();\n                }\n            }\n        } else {\n            if (navigator.canGoBack()) {\n                navigator.close();\n            }\n        }\n    }\n\n    private void refresh() {\n        if (navigator.getCurrentPage() instanceof Refreshable) {\n            Refreshable refreshable = (Refreshable) navigator.getCurrentPage();\n\n            if (refreshable.refreshableProperty().get())\n                refreshable.refresh();\n        }\n    }\n\n    private void onNavigated(Navigator.NavigationEvent event) {\n        if (event.getSource() != this.navigator) return;\n        Node to = event.getNode();\n\n        if (to instanceof Refreshable) {\n            decorator.canRefreshProperty().bind(((Refreshable) to).refreshableProperty());\n        } else {\n            decorator.canRefreshProperty().unbind();\n            decorator.canRefreshProperty().set(false);\n        }\n\n        decorator.canCloseProperty().set(navigator.size() > 2);\n\n        if (to instanceof DecoratorPage) {\n            decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).isPageCloseable());\n        } else {\n            decorator.showCloseAsHomeProperty().set(true);\n        }\n\n        decorator.setNavigationDirection(event.getDirection());\n\n        // state property should be updated at last.\n        if (to instanceof DecoratorPage) {\n            decorator.stateProperty().bind(((DecoratorPage) to).stateProperty());\n        } else {\n            decorator.stateProperty().unbind();\n            decorator.stateProperty().set(new DecoratorPage.State(\"\", null, navigator.canGoBack(), false, true));\n        }\n\n        if (to instanceof Region) {\n            Region region = (Region) to;\n            // Let root pane fix window size.\n            StackPane parent = (StackPane) region.getParent();\n            region.prefWidthProperty().bind(parent.widthProperty());\n            region.prefHeightProperty().bind(parent.heightProperty());\n        }\n    }\n\n    // ==== Dialog ====\n    public void showDialog(Node node) {\n        DialogUtils.show(decorator, node);\n    }\n\n    private void closeDialog(Node node) {\n        DialogUtils.close(node);\n    }\n\n    // ==== Toast ====\n\n    public void showToast(String content) {\n        decorator.getSnackbar().fireEvent(new JFXSnackbar.SnackbarEvent(new JFXSnackbarLayout(content)));\n    }\n\n    // ==== Wizard ====\n\n    public void startWizard(WizardProvider wizardProvider) {\n        startWizard(wizardProvider, null);\n    }\n\n    public void startWizard(WizardProvider wizardProvider, String category) {\n        FXUtils.checkFxUserThread();\n\n        navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category),\n                ContainerAnimations.FORWARD, Motion.SHORT4, Motion.EASE);\n    }\n\n    // ==== Authlib Injector DnD ====\n\n    private void setupAuthlibInjectorDnD() {\n        decorator.addEventFilter(DragEvent.DRAG_OVER, AuthlibInjectorDnD.dragOverHandler());\n        decorator.addEventFilter(DragEvent.DRAG_DROPPED, AuthlibInjectorDnD.dragDroppedHandler(\n                url -> Controllers.dialog(new AddAuthlibInjectorServerPane(url))));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.decorator;\n\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.ui.construct.Navigator;\nimport org.jackhuang.hmcl.ui.wizard.Refreshable;\n\npublic interface DecoratorPage extends Refreshable {\n    ReadOnlyObjectProperty<State> stateProperty();\n\n    default boolean isPageCloseable() {\n        return false;\n    }\n\n    default boolean back() {\n        return true;\n    }\n\n    @Override\n    default void refresh() {\n    }\n\n    default void closePage() {\n    }\n\n    default void onDecoratorPageNavigating(Navigator.NavigationEvent event) {\n        ((Node) this).getStyleClass().add(\"content-background\");\n    }\n\n    class State {\n        private final String title;\n        private final Node titleNode;\n        private final boolean backable;\n        private final boolean refreshable;\n        private final boolean animate;\n        private final double leftPaneWidth;\n\n        public State(String title, Node titleNode, boolean backable, boolean refreshable, boolean animate) {\n            this(title, titleNode, backable, refreshable, animate, 0);\n        }\n\n        public State(String title, Node titleNode, boolean backable, boolean refreshable, boolean animate, double leftPaneWidth) {\n            this.title = title;\n            this.titleNode = titleNode;\n            this.backable = backable;\n            this.refreshable = refreshable;\n            this.animate = animate;\n            this.leftPaneWidth = leftPaneWidth;\n        }\n\n        public static State fromTitle(String title) {\n            return new State(title, null, true, false, true);\n        }\n\n        public static State fromTitle(String title, double leftPaneWidth) {\n            return new State(title, null, true, false, true, leftPaneWidth);\n        }\n\n        public static State fromTitleNode(Node titleNode) {\n            return new State(null, titleNode, true, false, true);\n        }\n\n        public String getTitle() {\n            return title;\n        }\n\n        public Node getTitleNode() {\n            return titleNode;\n        }\n\n        public boolean isBackable() {\n            return backable;\n        }\n\n        public boolean isRefreshable() {\n            return refreshable;\n        }\n\n        public boolean isAnimate() {\n            return animate;\n        }\n\n        public double getLeftPaneWidth() {\n            return leftPaneWidth;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.decorator;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.collections.ListChangeListener;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Bounds;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.effect.BlurType;\nimport javafx.scene.effect.DropShadow;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.*;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Rectangle;\nimport javafx.stage.Stage;\n\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.Motion;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.wizard.Navigation;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\npublic class DecoratorSkin extends SkinBase<Decorator> {\n    private final StackPane root, parent;\n    private final StackPane titleContainer;\n    private final Stage primaryStage;\n    private final TransitionPane navBarPane;\n\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final InvalidationListener onWindowsStatusChange;\n    private final EventHandler<MouseEvent> onTitleBarDoubleClick;\n\n    private double mouseInitX, mouseInitY, stageInitX, stageInitY, stageInitWidth, stageInitHeight;\n\n    /**\n     * Constructor for all SkinBase instances.\n     *\n     * @param control The control for which this Skin should attach to.\n     */\n    public DecoratorSkin(Decorator control) {\n        super(control);\n\n        primaryStage = control.getPrimaryStage();\n\n        Decorator skinnable = getSkinnable();\n        root = new StackPane();\n        root.getStyleClass().add(\"window\");\n\n        StackPane shadowContainer = new StackPane();\n        shadowContainer.getStyleClass().add(\"body\");\n        shadowContainer.setEffect(new DropShadow(BlurType.ONE_PASS_BOX, Color.rgb(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0));\n\n        parent = new StackPane();\n        Rectangle clip = new Rectangle();\n        clip.widthProperty().bind(parent.widthProperty());\n        clip.heightProperty().bind(parent.heightProperty());\n        clip.setArcWidth(8);\n        clip.setArcHeight(8);\n        parent.setClip(clip);\n\n        skinnable.getSnackbar().registerSnackbarContainer(parent);\n\n        EventHandler<MouseEvent> onMouseReleased = this::onMouseReleased;\n        EventHandler<MouseEvent> onMouseDragged = this::onMouseDragged;\n        EventHandler<MouseEvent> onMouseMoved = this::onMouseMoved;\n\n        // https://github.com/HMCL-dev/HMCL/issues/4290\n        if (OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) {\n            onWindowsStatusChange = observable -> {\n                if (primaryStage.isIconified() || primaryStage.isFullScreen() || primaryStage.isMaximized()) {\n                    root.removeEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleased);\n                    root.removeEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDragged);\n                    root.removeEventFilter(MouseEvent.MOUSE_MOVED, onMouseMoved);\n                } else {\n                    root.addEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleased);\n                    root.addEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDragged);\n                    root.addEventFilter(MouseEvent.MOUSE_MOVED, onMouseMoved);\n                }\n            };\n            onTitleBarDoubleClick = event -> {\n                if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {\n                    primaryStage.setMaximized(!primaryStage.isMaximized());\n                    event.consume();\n                }\n            };\n            WeakInvalidationListener weakOnWindowsStatusChange = new WeakInvalidationListener(onWindowsStatusChange);\n            primaryStage.iconifiedProperty().addListener(weakOnWindowsStatusChange);\n            primaryStage.maximizedProperty().addListener(weakOnWindowsStatusChange);\n            primaryStage.fullScreenProperty().addListener(weakOnWindowsStatusChange);\n            onWindowsStatusChange.invalidated(null);\n        } else {\n            onWindowsStatusChange = null;\n            onTitleBarDoubleClick = null;\n            root.addEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleased);\n            root.addEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDragged);\n            root.addEventFilter(MouseEvent.MOUSE_MOVED, onMouseMoved);\n        }\n\n        shadowContainer.getChildren().setAll(parent);\n        root.getChildren().setAll(shadowContainer);\n\n        StackPane wrapper = new StackPane();\n        BorderPane frame = new BorderPane();\n        frame.getStyleClass().addAll(\"jfx-decorator\");\n        wrapper.getChildren().setAll(frame);\n        skinnable.setDrawerWrapper(wrapper);\n\n        parent.getChildren().add(wrapper);\n\n        // center node with an animation layer at bottom, a container layer at middle and a \"welcome\" layer at top.\n        StackPane container = new StackPane();\n        FXUtils.setOverflowHidden(container);\n\n        // content layer at middle\n        {\n            StackPane contentPlaceHolder = new StackPane();\n            contentPlaceHolder.getStyleClass().add(\"jfx-decorator-content-container\");\n            Bindings.bindContent(contentPlaceHolder.getChildren(), skinnable.contentProperty());\n\n            container.getChildren().add(contentPlaceHolder);\n        }\n\n        // welcome and hint layer at top\n        {\n            StackPane floatLayer = new StackPane();\n            Bindings.bindContent(floatLayer.getChildren(), skinnable.containerProperty());\n            ListChangeListener<Node> listener = c -> {\n                if (skinnable.getContainer().isEmpty()) {\n                    floatLayer.setMouseTransparent(true);\n                    floatLayer.setVisible(false);\n                } else {\n                    floatLayer.setMouseTransparent(false);\n                    floatLayer.setVisible(true);\n                }\n            };\n            skinnable.containerProperty().addListener(listener);\n            listener.onChanged(null);\n\n            container.getChildren().add(floatLayer);\n        }\n\n        frame.setCenter(container);\n\n        titleContainer = new StackPane();\n        titleContainer.setPickOnBounds(false);\n        titleContainer.getStyleClass().addAll(\"jfx-tool-bar\");\n\n        // Maybe, we can automatically identify whether the top part of the picture is light-coloured or dark when the title is transparent,\n        // and decide whether the whole top bar should be rendered in white or black. TODO\n        FXUtils.onChangeAndOperate(skinnable.titleTransparentProperty(), titleTransparent -> {\n            if (titleTransparent) {\n                wrapper.backgroundProperty().bind(skinnable.contentBackgroundProperty());\n                container.backgroundProperty().unbind();\n                container.setBackground(null);\n                titleContainer.getStyleClass().remove(\"background\");\n                titleContainer.getStyleClass().add(\"gray-background\");\n            } else {\n                container.backgroundProperty().bind(skinnable.contentBackgroundProperty());\n                wrapper.backgroundProperty().unbind();\n                wrapper.setBackground(null);\n                titleContainer.getStyleClass().add(\"background\");\n                titleContainer.getStyleClass().remove(\"gray-background\");\n            }\n        });\n\n        control.capableDraggingWindow(titleContainer);\n\n        BorderPane titleBar = new BorderPane();\n        titleContainer.getChildren().add(titleBar);\n\n        Rectangle buttonsContainerPlaceHolder = new Rectangle();\n        {\n            navBarPane = new TransitionPane();\n            navBarPane.setId(\"decoratorTitleTransitionPane\");\n            FXUtils.onChangeAndOperate(skinnable.stateProperty(), s -> {\n                if (s == null) return;\n                Node node = createNavBar(skinnable, s.getLeftPaneWidth(), s.isBackable(), skinnable.canCloseProperty().get(), skinnable.showCloseAsHomeProperty().get(), s.isRefreshable(), s.getTitle(), s.getTitleNode());\n                if (s.isAnimate()) {\n                    TransitionPane.AnimationProducer animation = switch (skinnable.getNavigationDirection()) {\n                        case NEXT -> NavBarAnimations.NEXT;\n                        case PREVIOUS -> NavBarAnimations.PREVIOUS;\n                        default -> ContainerAnimations.FADE;\n                    };\n                    skinnable.setNavigationDirection(Navigation.NavigationDirection.START);\n                    navBarPane.setContent(node, animation, Motion.SHORT4);\n                } else {\n                    navBarPane.getChildren().setAll(node);\n                }\n            });\n            titleBar.setCenter(navBarPane);\n            titleBar.setRight(buttonsContainerPlaceHolder);\n        }\n        frame.setTop(titleContainer);\n\n        {\n            HBox buttonsContainer = new HBox();\n            buttonsContainer.setAlignment(Pos.TOP_RIGHT);\n            buttonsContainer.setMaxHeight(40);\n            {\n                JFXButton btnHelp = new JFXButton();\n                btnHelp.setFocusTraversable(false);\n                btnHelp.setGraphic(SVG.HELP.createIcon(Themes.titleFillProperty()));\n                btnHelp.getStyleClass().add(\"jfx-decorator-button\");\n                btnHelp.setOnAction(e -> FXUtils.openLink(Metadata.CONTACT_URL));\n\n                JFXButton btnMin = new JFXButton();\n                btnMin.setFocusTraversable(false);\n                btnMin.setGraphic(SVG.MINIMIZE_CENTER.createIcon(Themes.titleFillProperty()));\n                btnMin.getStyleClass().add(\"jfx-decorator-button\");\n                btnMin.setOnAction(e -> skinnable.minimize());\n\n                JFXButton btnClose = new JFXButton();\n                btnClose.setFocusTraversable(false);\n                btnClose.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));\n                btnClose.getStyleClass().add(\"jfx-decorator-button\");\n                btnClose.setOnAction(e -> skinnable.close());\n\n                buttonsContainer.getChildren().setAll(btnHelp, btnMin, btnClose);\n            }\n            AnchorPane layer = new AnchorPane();\n            layer.setPickOnBounds(false);\n            layer.getChildren().add(buttonsContainer);\n            AnchorPane.setTopAnchor(buttonsContainer, 0.0);\n            AnchorPane.setRightAnchor(buttonsContainer, 0.0);\n            buttonsContainerPlaceHolder.widthProperty().bind(buttonsContainer.widthProperty());\n            parent.getChildren().add(layer);\n        }\n\n        getChildren().add(root);\n    }\n\n    private Node createNavBar(Decorator skinnable, double leftPaneWidth, boolean canBack, boolean canClose, boolean showCloseAsHome, boolean canRefresh, String title, Node titleNode) {\n        BorderPane navBar = new BorderPane();\n        navBar.getStyleClass().add(\"navigation-bar\");\n\n        {\n            HBox navLeft = new HBox();\n            navLeft.setAlignment(Pos.CENTER_LEFT);\n            navLeft.setPadding(new Insets(0, 5, 0, 5));\n\n            if (canBack) {\n                JFXButton backNavButton = new JFXButton();\n                backNavButton.setFocusTraversable(false);\n                backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Themes.titleFillProperty()));\n                backNavButton.getStyleClass().add(\"jfx-decorator-button\");\n                backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());\n                backNavButton.visibleProperty().set(canBack);\n\n                navLeft.getChildren().add(backNavButton);\n            }\n\n            if (canClose) {\n                JFXButton closeNavButton = new JFXButton();\n                closeNavButton.setFocusTraversable(false);\n                closeNavButton.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));\n                closeNavButton.getStyleClass().add(\"jfx-decorator-button\");\n                closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());\n                if (showCloseAsHome)\n                    closeNavButton.setGraphic(SVG.HOME.createIcon(Themes.titleFillProperty()));\n                else\n                    closeNavButton.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));\n\n                navLeft.getChildren().add(closeNavButton);\n            }\n\n            if (canBack || canClose) {\n                navBar.setLeft(navLeft);\n            }\n\n            BorderPane center = new BorderPane();\n            if (title != null) {\n                Label titleLabel = new Label();\n                titleLabel.textFillProperty().bind(Themes.titleFillProperty());\n                BorderPane.setAlignment(titleLabel, Pos.CENTER_LEFT);\n                titleLabel.getStyleClass().add(\"jfx-decorator-title\");\n                if (titleNode == null) {\n                    titleLabel.maxWidthProperty().bind(Bindings.createDoubleBinding(\n                            () -> skinnable.getWidth() - 150 - navLeft.getWidth(),\n                            skinnable.widthProperty(), navLeft.widthProperty()));\n                } else {\n                    titleLabel.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {\n                        // 8 (margin-left)\n                        return leftPaneWidth - 8 - navLeft.getWidth();\n                    }, navLeft.widthProperty()));\n                }\n                titleLabel.setText(title);\n                center.setLeft(titleLabel);\n                BorderPane.setAlignment(titleLabel, Pos.CENTER_LEFT);\n            }\n            if (titleNode != null) {\n                center.setCenter(titleNode);\n                BorderPane.setAlignment(titleNode, Pos.CENTER_LEFT);\n                BorderPane.setMargin(titleNode, new Insets(0, 0, 0, 8));\n            }\n            if (onTitleBarDoubleClick != null)\n                center.setOnMouseClicked(onTitleBarDoubleClick);\n            center.setOnMouseDragged(mouseEvent -> {\n                if (!getSkinnable().isDragging() && primaryStage.isMaximized()) {\n                    getSkinnable().setDragging(true);\n                    mouseInitX = mouseEvent.getScreenX();\n                    mouseInitY = mouseEvent.getScreenY();\n                    primaryStage.setMaximized(false);\n                    stageInitWidth = primaryStage.getWidth();\n                    stageInitHeight = primaryStage.getHeight();\n                    primaryStage.setY(stageInitY = 0);\n                    primaryStage.setX(stageInitX = mouseInitX - stageInitWidth / 2);\n                }\n            });\n            navBar.setCenter(center);\n\n            if (canRefresh) {\n                HBox navRight = new HBox();\n                navRight.setAlignment(Pos.CENTER_RIGHT);\n                JFXButton refreshNavButton = new JFXButton();\n                refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Themes.titleFillProperty()));\n                refreshNavButton.getStyleClass().add(\"jfx-decorator-button\");\n                refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());\n\n                Rectangle separator = new Rectangle();\n                separator.visibleProperty().bind(refreshNavButton.visibleProperty());\n                separator.heightProperty().bind(navBar.heightProperty());\n                separator.setFill(Color.GRAY);\n\n                navRight.getChildren().setAll(refreshNavButton, separator);\n                navBar.setRight(navRight);\n            }\n        }\n        return navBar;\n    }\n\n    private boolean isRightEdge(double x, double y, Bounds boundsInParent) {\n        return x < root.getWidth() && x >= root.getWidth() - root.snappedLeftInset();\n    }\n\n    private boolean isTopEdge(double x, double y, Bounds boundsInParent) {\n        return y >= 0 && y <= root.snappedTopInset();\n    }\n\n    private boolean isBottomEdge(double x, double y, Bounds boundsInParent) {\n        return y < root.getHeight() && y >= root.getHeight() - root.snappedLeftInset();\n    }\n\n    private boolean isLeftEdge(double x, double y, Bounds boundsInParent) {\n        return x >= 0 && x <= root.snappedLeftInset();\n    }\n\n    private void resizeStage(double newWidth, double newHeight) {\n        if (newWidth < 0)\n            newWidth = primaryStage.getWidth();\n        if (newWidth < primaryStage.getMinWidth())\n            newWidth = primaryStage.getMinWidth();\n        if (newWidth < titleContainer.getMinWidth())\n            newWidth = titleContainer.getMinWidth();\n\n        if (newHeight < 0)\n            newHeight = primaryStage.getHeight();\n        if (newHeight < primaryStage.getMinHeight())\n            newHeight = primaryStage.getMinHeight();\n        if (newHeight < titleContainer.getMinHeight())\n            newHeight = titleContainer.getMinHeight();\n\n        // Width and height must be set simultaneously to avoid JDK-8344372 (https://github.com/openjdk/jfx/pull/1654)\n        primaryStage.setWidth(newWidth);\n        primaryStage.setHeight(newHeight);\n    }\n\n    private void onMouseMoved(MouseEvent mouseEvent) {\n        if (!primaryStage.isFullScreen() && primaryStage.isResizable()) {\n            double x = mouseEvent.getX(), y = mouseEvent.getY();\n            Bounds boundsInParent = root.getBoundsInParent();\n            double diagonalSize = root.snappedLeftInset() + 10;\n            if (this.isRightEdge(x, y, boundsInParent)) {\n                if (y < diagonalSize) {\n                    root.setCursor(Cursor.NE_RESIZE);\n                } else if (y > root.getHeight() - diagonalSize) {\n                    root.setCursor(Cursor.SE_RESIZE);\n                } else {\n                    root.setCursor(Cursor.E_RESIZE);\n                }\n            } else if (this.isLeftEdge(x, y, boundsInParent)) {\n                if (y < diagonalSize) {\n                    root.setCursor(Cursor.NW_RESIZE);\n                } else if (y > root.getHeight() - diagonalSize) {\n                    root.setCursor(Cursor.SW_RESIZE);\n                } else {\n                    root.setCursor(Cursor.W_RESIZE);\n                }\n            } else if (this.isTopEdge(x, y, boundsInParent)) {\n                if (x < diagonalSize) {\n                    root.setCursor(Cursor.NW_RESIZE);\n                } else if (x > root.getWidth() - diagonalSize) {\n                    root.setCursor(Cursor.NE_RESIZE);\n                } else {\n                    root.setCursor(Cursor.N_RESIZE);\n                }\n            } else if (this.isBottomEdge(x, y, boundsInParent)) {\n                if (x < diagonalSize) {\n                    root.setCursor(Cursor.SW_RESIZE);\n                } else if (x > root.getWidth() - diagonalSize) {\n                    root.setCursor(Cursor.SE_RESIZE);\n                } else {\n                    root.setCursor(Cursor.S_RESIZE);\n                }\n            } else {\n                root.setCursor(Cursor.DEFAULT);\n            }\n        } else {\n            root.setCursor(Cursor.DEFAULT);\n        }\n    }\n\n    private void onMouseReleased(MouseEvent mouseEvent) {\n        getSkinnable().setDragging(false);\n    }\n\n    private void onMouseDragged(MouseEvent mouseEvent) {\n        if (!getSkinnable().isDragging()) {\n            getSkinnable().setDragging(true);\n            mouseInitX = mouseEvent.getScreenX();\n            mouseInitY = mouseEvent.getScreenY();\n            stageInitX = primaryStage.getX();\n            stageInitY = primaryStage.getY();\n            stageInitWidth = primaryStage.getWidth();\n            stageInitHeight = primaryStage.getHeight();\n        }\n\n        if (primaryStage.isFullScreen() || !mouseEvent.isPrimaryButtonDown() || mouseEvent.isStillSincePress())\n            return;\n\n        double dx = mouseEvent.getScreenX() - mouseInitX;\n        double dy = mouseEvent.getScreenY() - mouseInitY;\n\n        Cursor cursor = root.getCursor();\n        if (getSkinnable().isAllowMove()) {\n            if (cursor == Cursor.DEFAULT) {\n                primaryStage.setX(stageInitX + dx);\n                primaryStage.setY(stageInitY + dy);\n                mouseEvent.consume();\n            }\n        }\n\n        if (getSkinnable().isResizable()) {\n            if (cursor == Cursor.E_RESIZE) {\n                resizeStage(stageInitWidth + dx, -1);\n                mouseEvent.consume();\n\n            } else if (cursor == Cursor.S_RESIZE) {\n                resizeStage(-1, stageInitHeight + dy);\n                mouseEvent.consume();\n\n            } else if (cursor == Cursor.W_RESIZE) {\n                resizeStage(stageInitWidth - dx, -1);\n                primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());\n                mouseEvent.consume();\n\n            } else if (cursor == Cursor.N_RESIZE) {\n                resizeStage(-1, stageInitHeight - dy);\n                primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());\n                mouseEvent.consume();\n\n            } else if (cursor == Cursor.SE_RESIZE) {\n                resizeStage(stageInitWidth + dx, stageInitHeight + dy);\n                mouseEvent.consume();\n\n            } else if (cursor == Cursor.SW_RESIZE) {\n                resizeStage(stageInitWidth - dx, stageInitHeight + dy);\n                primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());\n                mouseEvent.consume();\n\n            } else if (cursor == Cursor.NW_RESIZE) {\n                resizeStage(stageInitWidth - dx, stageInitHeight - dy);\n                primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());\n                primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());\n                mouseEvent.consume();\n\n            } else if (cursor == Cursor.NE_RESIZE) {\n                resizeStage(stageInitWidth + dx, stageInitHeight - dy);\n                primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());\n                mouseEvent.consume();\n            }\n        }\n    }\n\n    enum NavBarAnimations implements TransitionPane.AnimationProducer {\n        NEXT {\n            @Override\n            public void init(TransitionPane container, Node previousNode, Node nextNode) {\n                super.init(container, previousNode, nextNode);\n                nextNode.setTranslateX(container.getWidth());\n            }\n\n            @Override\n            public Timeline animate(\n                    Pane container, Node previousNode, Node nextNode,\n                    Duration duration, Interpolator interpolator) {\n                return new Timeline(\n                        new KeyFrame(Duration.ZERO,\n                                new KeyValue(nextNode.translateXProperty(), 50, interpolator),\n                                new KeyValue(previousNode.translateXProperty(), 0, interpolator),\n                                new KeyValue(nextNode.opacityProperty(), 0, interpolator),\n                                new KeyValue(previousNode.opacityProperty(), 1, interpolator)),\n                        new KeyFrame(duration,\n                                new KeyValue(nextNode.translateXProperty(), 0, interpolator),\n                                new KeyValue(previousNode.translateXProperty(), -50, interpolator),\n                                new KeyValue(nextNode.opacityProperty(), 1, interpolator),\n                                new KeyValue(previousNode.opacityProperty(), 0, interpolator))\n                );\n            }\n\n            @Override\n            public TransitionPane.AnimationProducer opposite() {\n                return NEXT;\n            }\n        },\n\n        PREVIOUS {\n            @Override\n            public void init(TransitionPane container, Node previousNode, Node nextNode) {\n                super.init(container, previousNode, nextNode);\n                nextNode.setTranslateX(container.getWidth());\n            }\n\n            @Override\n            public Timeline animate(Pane container, Node previousNode, Node nextNode, Duration duration, Interpolator interpolator) {\n                return new Timeline(\n                        new KeyFrame(Duration.ZERO,\n                                new KeyValue(nextNode.translateXProperty(), -50, interpolator),\n                                new KeyValue(previousNode.translateXProperty(), 0, interpolator),\n                                new KeyValue(nextNode.opacityProperty(), 0, interpolator),\n                                new KeyValue(previousNode.opacityProperty(), 1, interpolator)),\n                        new KeyFrame(duration,\n                                new KeyValue(nextNode.translateXProperty(), 0, interpolator),\n                                new KeyValue(previousNode.translateXProperty(), 50, interpolator),\n                                new KeyValue(nextNode.opacityProperty(), 1, interpolator),\n                                new KeyValue(previousNode.opacityProperty(), 0, interpolator))\n                );\n            }\n\n            @Override\n            public TransitionPane.AnimationProducer opposite() {\n                return PREVIOUS;\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTransitionPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.decorator;\n\nimport javafx.animation.Interpolator;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.*;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Skin;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.StackPane;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.wizard.Refreshable;\n\npublic abstract class DecoratorTransitionPage extends Control implements DecoratorPage {\n    protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(\"\"));\n    private final DoubleProperty leftPaneWidth = new SimpleDoubleProperty();\n    private final BooleanProperty backable = new SimpleBooleanProperty(false);\n    private final BooleanProperty refreshable = new SimpleBooleanProperty(false);\n    private Node currentPage;\n    protected final TransitionPane transitionPane = new TransitionPane();\n\n    protected void navigate(Node page, TransitionPane.AnimationProducer animation, Duration duration, Interpolator interpolator) {\n        transitionPane.setContent(currentPage = page, animation, duration, interpolator);\n    }\n\n    protected void onNavigating(Node from) {\n        if (from instanceof DecoratorPage)\n            ((DecoratorPage) from).back();\n    }\n\n    protected void onNavigated(Node to) {\n        if (to instanceof Refreshable) {\n            refreshableProperty().bind(((Refreshable) to).refreshableProperty());\n        } else {\n            refreshableProperty().unbind();\n            refreshableProperty().set(false);\n        }\n\n        if (to instanceof DecoratorPage) {\n            state.bind(Bindings.createObjectBinding(() -> {\n                State state = ((DecoratorPage) to).stateProperty().get();\n                return new State(state.getTitle(), state.getTitleNode(), backable.get(), state.isRefreshable(), true, leftPaneWidth.get());\n            }, ((DecoratorPage) to).stateProperty()));\n        } else {\n            state.unbind();\n            state.set(new State(\"\", null, backable.get(), false, true, leftPaneWidth.get()));\n        }\n\n        if (to instanceof Region) {\n            Region region = (Region) to;\n            // Let root pane fix window size.\n            StackPane parent = (StackPane) region.getParent();\n            region.prefWidthProperty().bind(parent.widthProperty());\n            region.prefHeightProperty().bind(parent.heightProperty());\n        }\n    }\n\n    @Override\n    protected abstract Skin<?> createDefaultSkin();\n\n    protected Node getCurrentPage() {\n        return currentPage;\n    }\n\n    public boolean isBackable() {\n        return backable.get();\n    }\n\n    public BooleanProperty backableProperty() {\n        return backable;\n    }\n\n    public void setBackable(boolean backable) {\n        this.backable.set(backable);\n    }\n\n    public boolean isRefreshable() {\n        return refreshable.get();\n    }\n\n    @Override\n    public BooleanProperty refreshableProperty() {\n        return refreshable;\n    }\n\n    public void setRefreshable(boolean refreshable) {\n        this.refreshable.set(refreshable);\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n\n    public double getLeftPaneWidth() {\n        return leftPaneWidth.get();\n    }\n\n    public DoubleProperty leftPaneWidthProperty() {\n        return leftPaneWidth;\n    }\n\n    public void setLeftPaneWidth(double leftPaneWidth) {\n        this.leftPaneWidth.set(leftPaneWidth);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.decorator;\n\nimport javafx.scene.Node;\nimport javafx.scene.control.SkinBase;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.animation.Motion;\nimport org.jackhuang.hmcl.ui.construct.Navigator;\nimport org.jackhuang.hmcl.ui.construct.PageCloseEvent;\nimport org.jackhuang.hmcl.ui.wizard.*;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\nimport java.util.concurrent.ConcurrentLinkedQueue;\n\npublic class DecoratorWizardDisplayer extends DecoratorTransitionPage implements WizardDisplayer {\n    private final WizardController wizardController = new WizardController(this);\n    private final WizardDisplayer displayer = new TaskExecutorDialogWizardDisplayer(new ConcurrentLinkedQueue<>()) {\n        @Override\n        public void onEnd() {\n            super.onEnd();\n            fireEvent(new PageCloseEvent());\n        }\n\n        @Override\n        public void navigateTo(Node page, Navigation.NavigationDirection nav) {\n        }\n    };\n\n    private final String category;\n\n    public DecoratorWizardDisplayer(WizardProvider provider) {\n        this(provider, null);\n    }\n\n    public DecoratorWizardDisplayer(WizardProvider provider, String category) {\n        this.category = category;\n\n        wizardController.setProvider(provider);\n        wizardController.onStart();\n\n        addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);\n    }\n\n    @Override\n    public void onStart() {\n        displayer.onStart();\n    }\n\n    @Override\n    public void onCancel() {\n        displayer.onCancel();\n    }\n\n    @Override\n    public void onEnd() {\n        displayer.onEnd();\n    }\n\n    @Override\n    public void navigateTo(Node page, Navigation.NavigationDirection nav) {\n        displayer.navigateTo(page, nav);\n        navigate(page, nav.getAnimation(), Motion.SHORT4, Motion.EASE);\n\n        String prefix = category == null ? \"\" : category + \" - \";\n\n        String title;\n        if (page instanceof WizardPage)\n            title = prefix + ((WizardPage) page).getTitle();\n        else\n            title = \"\";\n        state.set(new State(title, null, true, refreshableProperty().get(), true));\n\n        if (page instanceof Refreshable) {\n            refreshableProperty().bind(((Refreshable) page).refreshableProperty());\n        } else {\n            refreshableProperty().unbind();\n            refreshableProperty().set(false);\n        }\n    }\n\n    @Override\n    public void handleTask(SettingsMap settings, Task<?> task) {\n        displayer.handleTask(settings, task);\n    }\n\n    @Override\n    public boolean isPageCloseable() {\n        return true;\n    }\n\n    @Override\n    public void closePage() {\n        wizardController.onCancel();\n    }\n\n    @Override\n    public boolean back() {\n        if (wizardController.canPrev()) {\n            wizardController.onPrev(true);\n            return false;\n        } else\n            return true;\n    }\n\n    @Override\n    public void refresh() {\n        ((Refreshable) getCurrentPage()).refresh();\n    }\n\n    @Override\n    protected Skin createDefaultSkin() {\n        return new Skin(this);\n    }\n\n    private static class Skin extends SkinBase<DecoratorWizardDisplayer> {\n\n        protected Skin(DecoratorWizardDisplayer control) {\n            super(control);\n\n            getChildren().setAll(control.transitionPane);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AbstractInstallersPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.*;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.FlowPane;\nimport javafx.scene.layout.HBox;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.InstallerItem;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.wizard.Navigation;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardPage;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic abstract class AbstractInstallersPage extends Control implements WizardPage {\n    public static final String FABRIC_QUILT_API_TIP = \"fabricQuiltApi\";\n    protected final WizardController controller;\n\n    protected InstallerItem.InstallerItemGroup group;\n    protected JFXTextField txtName = new JFXTextField();\n\n    protected BooleanProperty installable = new SimpleBooleanProperty();\n\n    public AbstractInstallersPage(WizardController controller, String gameVersion, DownloadProvider downloadProvider) {\n        this.controller = controller;\n        this.group = new InstallerItem.InstallerItemGroup(gameVersion, getInstallerItemStyle());\n\n        for (InstallerItem library : group.getLibraries()) {\n            String libraryId = library.getLibraryId();\n            if (libraryId.equals(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())) continue;\n            library.setOnInstall(() -> {\n                if (!Boolean.TRUE.equals(config().getShownTips().get(FABRIC_QUILT_API_TIP))\n                        && (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)\n                        || LibraryAnalyzer.LibraryType.QUILT_API.getPatchId().equals(libraryId)\n                        || LibraryAnalyzer.LibraryType.LEGACY_FABRIC_API.getPatchId().equals(libraryId))) {\n                    Controllers.dialog(new MessageDialogPane.Builder(\n                            i18n(\"install.installer.fabric-quilt-api.warning\", i18n(\"install.installer.\" + libraryId)),\n                            i18n(\"message.warning\"),\n                            MessageDialogPane.MessageType.WARNING\n                    ).ok(null).addCancel(i18n(\"button.do_not_show_again\"), () -> config().getShownTips().put(FABRIC_QUILT_API_TIP, true)).build());\n                }\n\n                if (!(library.resolvedStateProperty().get() instanceof InstallerItem.IncompatibleState))\n                    controller.onNext(\n                            new VersionsPage(\n                                    controller,\n                                    i18n(\"install.installer.choose\", i18n(\"install.installer.\" + libraryId)),\n                                    gameVersion,\n                                    downloadProvider,\n                                    libraryId,\n                                    () -> controller.onPrev(false, Navigation.NavigationDirection.PREVIOUS)\n                            ), Navigation.NavigationDirection.NEXT\n                    );\n            });\n            library.setOnRemove(() -> {\n                controller.getSettings().remove(libraryId);\n                reload();\n            });\n        }\n    }\n\n    protected InstallerItem.Style getInstallerItemStyle() {\n        return InstallerItem.Style.CARD;\n    }\n\n    @Override\n    public abstract String getTitle();\n\n    protected abstract void reload();\n\n    @Override\n    public void onNavigate(SettingsMap settings) {\n        reload();\n    }\n\n    @Override\n    public abstract void cleanup(SettingsMap settings);\n\n    protected abstract void onInstall();\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new InstallersPageSkin(this);\n    }\n\n    protected static class InstallersPageSkin extends SkinBase<AbstractInstallersPage> {\n        /**\n         * Constructor for all SkinBase instances.\n         *\n         * @param control The control for which this Skin should attach to.\n         */\n        protected InstallersPageSkin(AbstractInstallersPage control) {\n            super(control);\n\n            BorderPane root = new BorderPane();\n            root.setPadding(new Insets(16));\n\n            {\n                HBox versionNamePane = new HBox(8);\n                versionNamePane.getStyleClass().add(\"card-non-transparent\");\n                versionNamePane.setStyle(\"-fx-padding: 20 8 20 16\");\n                versionNamePane.setAlignment(Pos.CENTER_LEFT);\n\n                control.txtName.setMaxWidth(300);\n                versionNamePane.getChildren().setAll(new Label(i18n(\"version.name\")), control.txtName);\n                root.setTop(versionNamePane);\n            }\n\n            {\n                InstallerItem[] libraries = control.group.getLibraries();\n\n                FlowPane libraryPane = new FlowPane(16, 16, libraries);\n                ScrollPane scrollPane = new ScrollPane(libraryPane);\n                scrollPane.setFitToWidth(true);\n                scrollPane.setFitToHeight(true);\n                BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0));\n                root.setCenter(scrollPane);\n\n                if (libraries.length <= 8)\n                    scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);\n            }\n\n            {\n                JFXButton installButton = FXUtils.newRaisedButton(i18n(\"button.install\"));\n                installButton.disableProperty().bind(control.installable.not());\n                installButton.setPrefWidth(100);\n                installButton.setPrefHeight(40);\n                installButton.setOnAction(e -> control.onInstall());\n                BorderPane.setAlignment(installButton, Pos.CENTER_RIGHT);\n                root.setBottom(installButton);\n            }\n\n            getChildren().setAll(root);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.GameRepository;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.ui.InstallerItem;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\nclass AdditionalInstallersPage extends AbstractInstallersPage {\n    protected final BooleanProperty compatible = new SimpleBooleanProperty();\n    protected final GameRepository repository;\n    protected final String gameVersion;\n    protected final Version version;\n\n    public AdditionalInstallersPage(String gameVersion, Version version, WizardController controller, HMCLGameRepository repository, DownloadProvider downloadProvider) {\n        super(controller, gameVersion, downloadProvider);\n        this.gameVersion = gameVersion;\n        this.version = version;\n        this.repository = repository;\n\n        txtName.setText(version.getId());\n        txtName.setEditable(false);\n\n        for (InstallerItem library : group.getLibraries()) {\n            String libraryId = library.getLibraryId();\n            if (libraryId.equals(\"game\")) continue;\n            library.setOnRemove(() -> {\n                controller.getSettings().put(libraryId, new UpdateInstallerWizardProvider.RemoveVersionAction(libraryId));\n                reload();\n            });\n        }\n\n        installable.bind(Bindings.createBooleanBinding(() -> compatible.get() && txtName.validate(), txtName.textProperty(), compatible));\n    }\n\n    @Override\n    protected void onInstall() {\n        controller.onFinish();\n    }\n\n    @Override\n    public String getTitle() {\n        return i18n(\"settings.tabs.installers\");\n    }\n\n    private String getVersion(String id) {\n        return Optional.ofNullable(controller.getSettings().get(id))\n                .flatMap(it -> Lang.tryCast(it, RemoteVersion.class))\n                .map(RemoteVersion::getSelfVersion).orElse(null);\n    }\n\n    @Override\n    protected void reload() {\n        Version resolved = version.resolvePreservingPatches(repository);\n        LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved, repository.getGameVersion(resolved).orElse(null));\n        String game = analyzer.getVersion(MINECRAFT).orElse(null);\n        String currentGameVersion = Lang.nonNull(getVersion(\"game\"), game);\n\n        boolean compatible = true;\n\n        for (InstallerItem library : group.getLibraries()) {\n            String libraryId = library.getLibraryId();\n            String version = analyzer.getVersion(libraryId).orElse(null);\n            String libraryVersion = Lang.requireNonNullElse(getVersion(libraryId), version);\n            boolean alreadyInstalled = version != null && !(controller.getSettings().get(libraryId) instanceof UpdateInstallerWizardProvider.RemoveVersionAction);\n            if (!\"game\".equals(libraryId) && currentGameVersion != null && !currentGameVersion.equals(game) && getVersion(libraryId) == null && alreadyInstalled) {\n                // For third-party libraries, if game version is being changed, and the library is not being reinstalled,\n                // warns the user that we should update the library.\n                library.versionProperty().set(new InstallerItem.InstalledState(libraryVersion, false, true));\n                compatible = false;\n            } else if (alreadyInstalled || getVersion(libraryId) != null) {\n                library.versionProperty().set(new InstallerItem.InstalledState(libraryVersion, false, false));\n            } else {\n                library.versionProperty().set(null);\n            }\n        }\n\n        this.compatible.set(compatible);\n    }\n\n    @Override\n    public void cleanup(SettingsMap settings) {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.download.*;\nimport org.jackhuang.hmcl.download.game.GameRemoteVersion;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.AdvancedListBox;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.TabHeader;\nimport org.jackhuang.hmcl.ui.construct.Validator;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.ui.versions.DownloadListPage;\nimport org.jackhuang.hmcl.ui.versions.HMCLLocalizedDownloadListPage;\nimport org.jackhuang.hmcl.ui.versions.VersionPage;\nimport org.jackhuang.hmcl.ui.versions.Versions;\nimport org.jackhuang.hmcl.ui.wizard.Navigation;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardProvider;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.nio.file.Path;\nimport java.util.Locale;\nimport java.util.concurrent.CancellationException;\nimport java.util.function.Supplier;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {\n    private final ReadOnlyObjectWrapper<DecoratorPage.State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n(\"download\"), -1));\n    private final TabHeader tab;\n    private final TabHeader.Tab<VersionsPage> newGameTab = new TabHeader.Tab<>(\"newGameTab\");\n    private final TabHeader.Tab<DownloadListPage> modTab = new TabHeader.Tab<>(\"modTab\");\n    private final TabHeader.Tab<DownloadListPage> modpackTab = new TabHeader.Tab<>(\"modpackTab\");\n    private final TabHeader.Tab<DownloadListPage> resourcePackTab = new TabHeader.Tab<>(\"resourcePackTab\");\n    private final TabHeader.Tab<DownloadListPage> shaderTab = new TabHeader.Tab<>(\"shaderTab\");\n    private final TabHeader.Tab<DownloadListPage> worldTab = new TabHeader.Tab<>(\"worldTab\");\n    private final TransitionPane transitionPane = new TransitionPane();\n    private final DownloadNavigator versionPageNavigator = new DownloadNavigator();\n\n    private WeakListenerHolder listenerHolder;\n\n    public DownloadPage() {\n        this(null);\n    }\n\n    public DownloadPage(String uploadVersion) {\n        newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n(\"install.installer.choose\", i18n(\"install.installer.game\")), \"\", DownloadProviders.getDownloadProvider(),\n                \"game\", versionPageNavigator::onGameSelected)));\n        modpackTab.setNodeSupplier(loadVersionFor(() -> {\n            DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((downloadProvider, profile, __, mod, file) -> {\n                Versions.downloadModpackImpl(downloadProvider, profile, uploadVersion, mod, file);\n            }, false);\n\n            JFXButton installLocalModpackButton = FXUtils.newRaisedButton(i18n(\"install.modpack\"));\n            installLocalModpackButton.setOnAction(e -> Versions.importModpack());\n\n            page.getActions().add(installLocalModpackButton);\n            return page;\n        }));\n        modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, \"mods\"), true)));\n        resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, \"resourcepacks\"), true)));\n        shaderTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofShaderPack((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, \"shaderpacks\"), true)));\n        worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS)));\n        tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);\n\n        Profiles.registerVersionsListener(this::loadVersions);\n\n        tab.select(newGameTab);\n\n        AdvancedListBox sideBar = new AdvancedListBox()\n                .startCategory(i18n(\"download.game\").toUpperCase(Locale.ROOT))\n                .addNavigationDrawerTab(tab, newGameTab, i18n(\"game\"), SVG.STADIA_CONTROLLER, SVG.STADIA_CONTROLLER_FILL)\n                .addNavigationDrawerTab(tab, modpackTab, i18n(\"modpack\"), SVG.PACKAGE2, SVG.PACKAGE2_FILL)\n                .startCategory(i18n(\"download.content\").toUpperCase(Locale.ROOT))\n                .addNavigationDrawerTab(tab, modTab, i18n(\"mods\"), SVG.EXTENSION, SVG.EXTENSION_FILL)\n                .addNavigationDrawerTab(tab, resourcePackTab, i18n(\"resourcepack\"), SVG.TEXTURE)\n                .addNavigationDrawerTab(tab, shaderTab, i18n(\"download.shader\"), SVG.WB_SUNNY, SVG.WB_SUNNY_FILL)\n                .addNavigationDrawerTab(tab, worldTab, i18n(\"world\"), SVG.PUBLIC);\n        FXUtils.setLimitWidth(sideBar, 200);\n        setLeft(sideBar);\n\n        setCenter(transitionPane);\n    }\n\n    private static <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) {\n        return () -> {\n            T node = nodeSupplier.get();\n            if (node instanceof VersionPage.VersionLoadable) {\n                ((VersionPage.VersionLoadable) node).loadVersion(Profiles.getSelectedProfile(), null);\n            }\n            return node;\n        };\n    }\n\n    public static void download(DownloadProvider downloadProvider, Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {\n        if (version == null) version = profile.getSelectedVersion();\n\n        Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory();\n\n        Controllers.prompt(i18n(\"archive.file.name\"), (result, handler) -> {\n            Path dest = runDirectory.resolve(subdirectoryName).resolve(result);\n\n            Controllers.taskDialog(Task.composeAsync(() -> {\n                var task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(file.getFile().getUrl()), dest);\n                task.setName(file.getName());\n                return task;\n            }).whenComplete(Schedulers.javafx(), exception -> {\n                if (exception != null) {\n                    if (exception instanceof CancellationException) {\n                        Controllers.showToast(i18n(\"message.cancelled\"));\n                    } else {\n                        Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n(\"install.failed.downloading\"), MessageDialogPane.MessageType.ERROR);\n                    }\n                } else {\n                    Controllers.showToast(i18n(\"install.success\"));\n                }\n            }), i18n(\"message.downloading\"), TaskCancellationAction.NORMAL);\n            handler.resolve();\n        }, file.getFile().getFilename(), new Validator(i18n(\"install.new_game.malformed\"), FileUtils::isNameValid));\n\n    }\n\n    private void loadVersions(Profile profile) {\n        listenerHolder = new WeakListenerHolder();\n        runInFX(() -> {\n            if (profile == Profiles.getSelectedProfile()) {\n                listenerHolder.add(FXUtils.onWeakChangeAndOperate(profile.selectedVersionProperty(), version -> {\n                    if (modTab.isInitialized()) {\n                        modTab.getNode().loadVersion(profile, null);\n                    }\n                    if (modpackTab.isInitialized()) {\n                        modpackTab.getNode().loadVersion(profile, null);\n                    }\n                    if (resourcePackTab.isInitialized()) {\n                        resourcePackTab.getNode().loadVersion(profile, null);\n                    }\n                    if (shaderTab.isInitialized()) {\n                        shaderTab.getNode().loadVersion(profile, null);\n                    }\n                    if (worldTab.isInitialized()) {\n                        worldTab.getNode().loadVersion(profile, null);\n                    }\n                }));\n            }\n        });\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n\n    public void showGameDownloads() {\n        tab.select(newGameTab, false);\n    }\n\n    public void showModpackDownloads() {\n        tab.select(modpackTab, false);\n    }\n\n    public void showResourcepackDownloads() {\n        tab.select(resourcePackTab, false);\n    }\n\n    public DownloadListPage showModDownloads() {\n        tab.select(modTab, false);\n        return modTab.getNode();\n    }\n\n    public void showWorldDownloads() {\n        tab.select(worldTab, false);\n    }\n\n    private static final class DownloadNavigator implements Navigation {\n        private final SettingsMap settings = new SettingsMap();\n\n        @Override\n        public void onStart() {\n\n        }\n\n        @Override\n        public void onNext() {\n\n        }\n\n        @Override\n        public void onPrev(boolean cleanUp) {\n        }\n\n        @Override\n        public boolean canPrev() {\n            return false;\n        }\n\n        @Override\n        public void onFinish() {\n\n        }\n\n        @Override\n        public void onEnd() {\n\n        }\n\n        @Override\n        public void onCancel() {\n\n        }\n\n        @Override\n        public SettingsMap getSettings() {\n            return settings;\n        }\n\n        public void onGameSelected() {\n            Profile profile = Profiles.getSelectedProfile();\n            if (profile.getRepository().isLoaded()) {\n                Controllers.getDecorator().startWizard(new VanillaInstallWizardProvider(profile, (GameRemoteVersion) settings.get(\"game\")), i18n(\"install.new_game\"));\n            }\n        }\n\n    }\n\n    private static class VanillaInstallWizardProvider implements WizardProvider {\n        private final Profile profile;\n        private final DefaultDependencyManager dependencyManager;\n        private final DownloadProvider downloadProvider;\n        private final GameRemoteVersion gameVersion;\n\n        public VanillaInstallWizardProvider(Profile profile, GameRemoteVersion gameVersion) {\n            this.profile = profile;\n            this.gameVersion = gameVersion;\n            this.downloadProvider = DownloadProviders.getDownloadProvider();\n            this.dependencyManager = profile.getDependency(downloadProvider);\n        }\n\n        @Override\n        public void start(SettingsMap settings) {\n            settings.put(ModpackPage.PROFILE, profile);\n            settings.put(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion);\n        }\n\n        private Task<Void> finishVersionDownloadingAsync(SettingsMap settings) {\n            GameBuilder builder = dependencyManager.gameBuilder();\n\n            String name = (String) settings.get(\"name\");\n            builder.name(name);\n            builder.gameVersion(((RemoteVersion) settings.get(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())).getGameVersion());\n\n            settings.asStringMap().forEach((key, value) -> {\n                if (!LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(key)\n                        && value instanceof RemoteVersion remoteVersion)\n                    builder.version(remoteVersion);\n            });\n\n            return builder.buildAsync().whenComplete(any -> profile.getRepository().refreshVersions())\n                    .thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));\n        }\n\n        @Override\n        public Object finish(SettingsMap settings) {\n            settings.put(\"title\", i18n(\"install.new_game.installation\"));\n            settings.put(\"success_message\", i18n(\"install.success\"));\n            settings.put(FailureCallback.KEY, (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next));\n\n            return finishVersionDownloadingAsync(settings);\n        }\n\n        @Override\n        public Node createPage(WizardController controller, int step, SettingsMap settings) {\n            switch (step) {\n                case 0:\n                    return new InstallersPage(controller, profile.getRepository(), ((RemoteVersion) controller.getSettings().get(\"game\")).getGameVersion(), downloadProvider);\n                default:\n                    throw new IllegalStateException(\"error step \" + step + \", settings: \" + settings + \", pages: \" + controller.getPages());\n            }\n        }\n\n        @Override\n        public boolean cancel() {\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.InstallerItem;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.RequiredValidator;\nimport org.jackhuang.hmcl.ui.construct.Validator;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.i18n.I18n;\n\nimport static javafx.beans.binding.Bindings.createBooleanBinding;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class InstallersPage extends AbstractInstallersPage {\n\n    private boolean isNameModifiedByUser = false;\n\n    public InstallersPage(WizardController controller, HMCLGameRepository repository, String gameVersion, DownloadProvider downloadProvider) {\n        super(controller, gameVersion, downloadProvider);\n\n        txtName.getValidators().addAll(\n                new RequiredValidator(),\n                new Validator(i18n(\"install.new_game.already_exists\"), str -> !repository.versionIdConflicts(str)),\n                new Validator(i18n(\"install.new_game.malformed\"), HMCLGameRepository::isValidVersionId));\n        installable.bind(createBooleanBinding(txtName::validate, txtName.textProperty()));\n\n        txtName.textProperty().addListener((obs, oldText, newText) -> isNameModifiedByUser = true);\n    }\n\n    @Override\n    public String getTitle() {\n        return ((RemoteVersion) controller.getSettings().get(\"game\")).getGameVersion();\n    }\n\n    private String getVersion(String id) {\n        return I18n.getDisplayVersion((RemoteVersion) controller.getSettings().get(id));\n    }\n\n    protected void reload() {\n        for (InstallerItem library : group.getLibraries()) {\n            String libraryId = library.getLibraryId();\n            if (controller.getSettings().containsKey(libraryId)) {\n                library.versionProperty().set(new InstallerItem.InstalledState(getVersion(libraryId), false, false));\n            } else {\n                library.versionProperty().set(null);\n            }\n        }\n        if (!isNameModifiedByUser) {\n            setTxtNameWithLoaders();\n        }\n    }\n\n    @Override\n    public void cleanup(SettingsMap settings) {\n    }\n\n    private static boolean checkName(String name) {\n        for (int i = 0; i < name.length(); i++) {\n            char c = name.charAt(i);\n            if (!(c >= '0' && c <= '9')\n                    && !(c >= 'a' && c <= 'z')\n                    && !(c >= 'A' && c <= 'Z')\n                    && c != '-' && c != '_' && c != '.'\n            )\n                return false;\n        }\n\n        return true;\n    }\n\n    protected void onInstall() {\n        String name = txtName.getText();\n\n        if (!checkName(name)) {\n            Controllers.dialog(new MessageDialogPane.Builder(\n                    i18n(\"install.name.invalid\"),\n                    i18n(\"message.warning\"),\n                    MessageDialogPane.MessageType.QUESTION)\n                    .yesOrNo(() -> {\n                        controller.getSettings().put(\"name\", name);\n                        controller.onFinish();\n                    }, () -> {\n                        // The user selects Cancel and does nothing.\n                    })\n                    .build());\n        } else {\n            controller.getSettings().put(\"name\", name);\n            controller.onFinish();\n        }\n    }\n\n    private void setTxtNameWithLoaders() {\n        StringBuilder nameBuilder = new StringBuilder(getTitle());\n\n        for (InstallerItem library : group.getLibraries()) {\n            String libraryId = library.getLibraryId().replace(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), \"\");\n            if (!controller.getSettings().containsKey(libraryId)) {\n                continue;\n            }\n\n            LibraryAnalyzer.LibraryType libraryType = LibraryAnalyzer.LibraryType.fromPatchId(libraryId);\n\n            if (libraryType != null) {\n                String loaderName = switch (libraryType) {\n                    case FORGE -> \"Forge\";\n                    case NEO_FORGE -> \"NeoForge\";\n                    case CLEANROOM -> \"Cleanroom\";\n                    case LEGACY_FABRIC -> \"LegacyFabric\";\n                    case FABRIC -> \"Fabric\";\n                    case LITELOADER -> \"LiteLoader\";\n                    case QUILT -> \"Quilt\";\n                    case OPTIFINE -> \"OptiFine\";\n                    default -> null;\n                };\n\n                if (loaderName != null)\n                    nameBuilder.append('-').append(loaderName);\n            }\n        }\n\n        txtName.setText(nameBuilder.toString());\n        isNameModifiedByUser = false;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport javafx.application.Platform;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.game.ManuallyCreatedModpackException;\nimport org.jackhuang.hmcl.game.ModpackHelper;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.WebPage;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.RequiredValidator;\nimport org.jackhuang.hmcl.ui.construct.Validator;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class LocalModpackPage extends ModpackPage {\n\n    private final BooleanProperty installAsVersion = new SimpleBooleanProperty(true);\n    private Modpack manifest = null;\n    private Charset charset;\n\n    public LocalModpackPage(WizardController controller) {\n        super(controller);\n\n        Profile profile = controller.getSettings().get(ModpackPage.PROFILE);\n\n        String name = controller.getSettings().get(MODPACK_NAME);\n        if (name != null) {\n            txtModpackName.setText(name);\n            txtModpackName.setDisable(true);\n        } else {\n            FXUtils.onChangeAndOperate(installAsVersion, installAsVersion -> {\n                if (installAsVersion) {\n                    txtModpackName.getValidators().setAll(\n                            new RequiredValidator(),\n                            new Validator(i18n(\"install.new_game.already_exists\"), str -> !profile.getRepository().versionIdConflicts(str)),\n                            new Validator(i18n(\"install.new_game.malformed\"), HMCLGameRepository::isValidVersionId));\n                } else {\n                    txtModpackName.getValidators().setAll(\n                            new RequiredValidator(),\n                            new Validator(i18n(\"install.new_game.already_exists\"), str -> !ModpackHelper.isExternalGameNameConflicts(str) && Profiles.getProfiles().stream().noneMatch(p -> p.getName().equals(str))),\n                            new Validator(i18n(\"install.new_game.malformed\"), HMCLGameRepository::isValidVersionId));\n                }\n            });\n        }\n\n        btnDescription.setVisible(false);\n\n        Path selectedFile;\n        Path filePath = controller.getSettings().get(MODPACK_FILE);\n        if (filePath != null) {\n            selectedFile = filePath;\n        } else {\n            FileChooser chooser = new FileChooser();\n            chooser.setTitle(i18n(\"modpack.choose\"));\n            chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"modpack\"), \"*.zip\"));\n            selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));\n            if (selectedFile == null) {\n                controller.onEnd();\n                return;\n            }\n\n            controller.getSettings().put(MODPACK_FILE, selectedFile);\n        }\n\n        showSpinner();\n        Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(selectedFile))\n                .thenApplyAsync(encoding -> {\n                    charset = encoding;\n                    manifest = ModpackHelper.readModpackManifest(selectedFile, encoding);\n                    return manifest;\n                })\n                .whenComplete(Schedulers.javafx(), (manifest, exception) -> {\n                    if (exception instanceof ManuallyCreatedModpackException) {\n                        hideSpinner();\n                        nameProperty.set(FileUtils.getName(selectedFile));\n                        installAsVersion.set(false);\n\n                        if (name == null) {\n                            // trim: https://github.com/HMCL-dev/HMCL/issues/962\n                            txtModpackName.setText(FileUtils.getNameWithoutExtension(selectedFile));\n                        }\n\n                        Controllers.confirm(i18n(\"modpack.type.manual.warning\"), i18n(\"install.modpack\"), MessageDialogPane.MessageType.WARNING,\n                                () -> {},\n                                controller::onEnd);\n\n                        controller.getSettings().put(MODPACK_MANUALLY_CREATED, true);\n                    } else if (exception != null) {\n                        LOG.warning(\"Failed to read modpack manifest\", exception);\n                        Controllers.dialog(i18n(\"modpack.task.install.error\"), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n                        Platform.runLater(controller::onEnd);\n                    } else {\n                        hideSpinner();\n                        controller.getSettings().put(MODPACK_MANIFEST, manifest);\n                        nameProperty.set(manifest.getName());\n                        versionProperty.set(manifest.getVersion());\n                        authorProperty.set(manifest.getAuthor());\n\n                        if (name == null) {\n                            // trim: https://github.com/HMCL-dev/HMCL/issues/962\n                            txtModpackName.setText(manifest.getName().trim());\n                        }\n\n                        btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription()));\n                    }\n                }).start();\n    }\n\n    @Override\n    public void cleanup(SettingsMap settings) {\n        settings.remove(MODPACK_FILE);\n    }\n\n    protected void onInstall() {\n        String name = txtModpackName.getText();\n\n        // Check for non-ASCII characters.\n        if (!StringUtils.isASCII(name)) {\n            Controllers.dialog(new MessageDialogPane.Builder(\n                    i18n(\"install.name.invalid\"),\n                    i18n(\"message.warning\"),\n                    MessageDialogPane.MessageType.QUESTION)\n                    .yesOrNo(() -> {\n                        controller.getSettings().put(MODPACK_NAME, name);\n                        controller.getSettings().put(MODPACK_CHARSET, charset);\n                        controller.onFinish();\n                    }, () -> {\n                        // The user selects Cancel and does nothing.\n                    })\n                    .build());\n        } else {\n            controller.getSettings().put(MODPACK_NAME, name);\n            controller.getSettings().put(MODPACK_CHARSET, charset);\n            controller.onFinish();\n        }\n    }\n\n    protected void onDescribe() {\n        if (manifest != null)\n            Controllers.navigate(new WebPage(i18n(\"modpack.description\"), manifest.getDescription()));\n    }\n\n    public static final SettingsMap.Key<Path> MODPACK_FILE = new SettingsMap.Key<>(\"MODPACK_FILE\");\n    public static final SettingsMap.Key<String> MODPACK_NAME = new SettingsMap.Key<>(\"MODPACK_NAME\");\n    public static final SettingsMap.Key<Modpack> MODPACK_MANIFEST = new SettingsMap.Key<>(\"MODPACK_MANIFEST\");\n    public static final SettingsMap.Key<Charset> MODPACK_CHARSET = new SettingsMap.Key<>(\"MODPACK_CHARSET\");\n    public static final SettingsMap.Key<Boolean> MODPACK_MANUALLY_CREATED = new SettingsMap.Key<>(\"MODPACK_MANUALLY_CREATED\");\n    public static final SettingsMap.Key<String> MODPACK_ICON_URL = new SettingsMap.Key<>(\"MODPACK_ICON_URL\");\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.game.ManuallyCreatedModpackException;\nimport org.jackhuang.hmcl.game.ModpackHelper;\nimport org.jackhuang.hmcl.mod.MismatchedModpackTypeException;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackCompletionException;\nimport org.jackhuang.hmcl.mod.UnsupportedModpackException;\nimport org.jackhuang.hmcl.mod.server.ServerModpackManifest;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardProvider;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class ModpackInstallWizardProvider implements WizardProvider {\n    private final Profile profile;\n    private final Path file;\n    private final String updateVersion;\n    private String iconUrl;\n    private boolean hasSource;\n\n    public ModpackInstallWizardProvider(Profile profile) {\n        this(profile, null, null);\n    }\n\n    public ModpackInstallWizardProvider(Profile profile, Path modpackFile) {\n        this(profile, modpackFile, null);\n    }\n\n    public ModpackInstallWizardProvider(Profile profile, String updateVersion) {\n        this(profile, null, updateVersion);\n    }\n\n    public ModpackInstallWizardProvider(Profile profile, Path modpackFile, String updateVersion) {\n        this.profile = profile;\n        this.file = modpackFile;\n        this.updateVersion = updateVersion;\n    }\n\n    public void setIconUrl(String iconUrl) {\n        this.iconUrl = iconUrl;\n    }\n\n    @Override\n    public void start(SettingsMap settings) {\n        if (file != null)\n            settings.put(LocalModpackPage.MODPACK_FILE, file);\n        if (updateVersion != null)\n            settings.put(LocalModpackPage.MODPACK_NAME, updateVersion);\n        if (StringUtils.isNotBlank(iconUrl))\n            settings.put(LocalModpackPage.MODPACK_ICON_URL, iconUrl);\n        settings.put(ModpackPage.PROFILE, profile);\n        hasSource = settings.containsKey(LocalModpackPage.MODPACK_FILE) || settings.containsKey(RemoteModpackPage.MODPACK_SERVER_MANIFEST);\n    }\n\n    private Task<?> finishModpackInstallingAsync(SettingsMap settings) {\n        Path selected = settings.get(LocalModpackPage.MODPACK_FILE);\n        ServerModpackManifest serverModpackManifest = settings.get(RemoteModpackPage.MODPACK_SERVER_MANIFEST);\n        Modpack modpack = settings.get(LocalModpackPage.MODPACK_MANIFEST);\n        String name = settings.get(LocalModpackPage.MODPACK_NAME);\n        String iconUrl = settings.get(LocalModpackPage.MODPACK_ICON_URL);\n        Charset charset = settings.get(LocalModpackPage.MODPACK_CHARSET);\n        boolean isManuallyCreated = settings.getOrDefault(LocalModpackPage.MODPACK_MANUALLY_CREATED, false);\n\n        if (isManuallyCreated) {\n            return ModpackHelper.getInstallManuallyCreatedModpackTask(profile, selected, name, charset);\n        }\n\n        if ((selected == null && serverModpackManifest == null) || modpack == null || name == null) return null;\n\n        if (updateVersion != null) {\n            if (selected == null) {\n                Controllers.dialog(i18n(\"modpack.unsupported\"), i18n(\"message.error\"), MessageType.ERROR);\n                return null;\n            }\n            try {\n                if (serverModpackManifest != null) {\n                    return ModpackHelper.getUpdateTask(profile, serverModpackManifest, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name)));\n                } else {\n                    return ModpackHelper.getUpdateTask(profile, selected, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name)));\n                }\n            } catch (UnsupportedModpackException | ManuallyCreatedModpackException e) {\n                Controllers.dialog(i18n(\"modpack.unsupported\"), i18n(\"message.error\"), MessageType.ERROR);\n            } catch (MismatchedModpackTypeException e) {\n                Controllers.dialog(i18n(\"modpack.mismatched_type\", e.getRequired(), e.getFound()), i18n(\"message.error\"), MessageType.ERROR);\n            } catch (IOException e) {\n                Controllers.dialog(i18n(\"modpack.invalid\"), i18n(\"message.error\"), MessageType.ERROR);\n            }\n            return null;\n        } else {\n            if (serverModpackManifest != null) {\n                return ModpackHelper.getInstallTask(profile, serverModpackManifest, name, modpack)\n                        .thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));\n            } else {\n                return ModpackHelper.getInstallTask(profile, selected, name, modpack, iconUrl)\n                        .thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));\n            }\n        }\n    }\n\n    @Override\n    public Object finish(SettingsMap settings) {\n        settings.put(\"title\", i18n(\"install.modpack.installation\"));\n        settings.put(\"success_message\", i18n(\"install.success\"));\n        settings.put(FailureCallback.KEY, (ignored, exception, next) -> {\n            if (exception instanceof ModpackCompletionException) {\n                if (exception.getCause() instanceof FileNotFoundException) {\n                    Controllers.dialog(i18n(\"modpack.type.curse.not_found\"), i18n(\"install.failed\"), MessageType.ERROR, next);\n                } else {\n                    Controllers.dialog(i18n(\"install.success\"), i18n(\"install.success\"), MessageType.SUCCESS, next);\n                }\n            } else {\n                UpdateInstallerWizardProvider.alertFailureMessage(exception, next);\n            }\n        });\n\n        return finishModpackInstallingAsync(settings);\n    }\n\n    private static Node createModpackInstallPage(WizardController controller) {\n        if (controller.getSettings().containsKey(LocalModpackPage.MODPACK_FILE))\n            return new LocalModpackPage(controller);\n        else if (controller.getSettings().containsKey(RemoteModpackPage.MODPACK_SERVER_MANIFEST))\n            return new RemoteModpackPage(controller);\n        else\n            throw new IllegalArgumentException();\n    }\n\n    @Override\n    public Node createPage(WizardController controller, int step, SettingsMap settings) {\n        if (hasSource) {\n            return switch (step) {\n                case 0 -> createModpackInstallPage(controller);\n                default -> throw new IllegalStateException(\n                        \"error step \" + step + \", settings: \" + settings + \", pages: \" + controller.getPages());\n            };\n        } else {\n            return switch (step) {\n                case 0 -> new ModpackSelectionPage(controller);\n                case 1 -> createModpackInstallPage(controller);\n                default -> throw new IllegalStateException(\n                        \"error step \" + step + \", settings: \" + settings + \", pages: \" + controller.getPages());\n            };\n        }\n    }\n\n    @Override\n    public boolean cancel() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java",
    "content": "package org.jackhuang.hmcl.ui.download;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Pos;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.ComponentList;\nimport org.jackhuang.hmcl.ui.construct.LinePane;\nimport org.jackhuang.hmcl.ui.construct.LineTextPane;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardPage;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\nimport static javafx.beans.binding.Bindings.createBooleanBinding;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic abstract class ModpackPage extends SpinnerPane implements WizardPage {\n    public static final SettingsMap.Key<Profile> PROFILE = new SettingsMap.Key<>(\"PROFILE\");\n\n    protected final WizardController controller;\n\n    protected final StringProperty nameProperty;\n    protected final StringProperty versionProperty;\n    protected final StringProperty authorProperty;\n    protected final JFXTextField txtModpackName;\n    protected final JFXButton btnInstall;\n    protected final JFXButton btnDescription;\n\n    protected ModpackPage(WizardController controller) {\n        this.controller = controller;\n\n        VBox borderPane = new VBox();\n        borderPane.setAlignment(Pos.CENTER);\n        FXUtils.setLimitWidth(borderPane, 500);\n\n        ComponentList componentList = new ComponentList();\n        {\n            var archiveNamePane = new LinePane();\n            {\n                archiveNamePane.setTitle(i18n(\"version.name\"));\n\n                txtModpackName = new JFXTextField();\n                txtModpackName.setPrefWidth(300);\n                FXUtils.setLimitHeight(archiveNamePane, 75);\n                // BorderPane.setMargin(txtModpackName, new Insets(0, 0, 8, 32));\n                BorderPane.setAlignment(txtModpackName, Pos.CENTER_RIGHT);\n                archiveNamePane.setRight(txtModpackName);\n            }\n\n            var modpackNamePane = new LineTextPane();\n            {\n                modpackNamePane.setTitle(i18n(\"modpack.name\"));\n                nameProperty = modpackNamePane.textProperty();\n            }\n\n            var versionPane = new LineTextPane();\n            {\n                versionPane.setTitle(i18n(\"archive.version\"));\n                versionProperty = versionPane.textProperty();\n            }\n\n            var authorPane = new LineTextPane();\n            {\n                authorPane.setTitle(i18n(\"archive.author\"));\n                authorProperty = authorPane.textProperty();\n            }\n\n            var descriptionPane = new BorderPane();\n            {\n                btnDescription = FXUtils.newBorderButton(i18n(\"modpack.description\"));\n                btnDescription.setOnAction(e -> onDescribe());\n                descriptionPane.setLeft(btnDescription);\n\n                btnInstall = FXUtils.newRaisedButton(i18n(\"button.install\"));\n                btnInstall.setOnAction(e -> onInstall());\n                btnInstall.disableProperty().bind(createBooleanBinding(() -> !txtModpackName.validate(), txtModpackName.textProperty()));\n                descriptionPane.setRight(btnInstall);\n            }\n\n            componentList.getContent().setAll(\n                    archiveNamePane, modpackNamePane, versionPane, authorPane, descriptionPane);\n        }\n\n        borderPane.getChildren().setAll(componentList);\n        this.setContent(borderPane);\n    }\n\n    protected abstract void onInstall();\n\n    protected abstract void onDescribe();\n\n    @Override\n    public String getTitle() {\n        return i18n(\"modpack.task.install\");\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.effects.JFXDepthManager;\nimport javafx.application.Platform;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.shape.SVGPath;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.game.ModpackHelper;\nimport org.jackhuang.hmcl.mod.server.ServerModpackManifest;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.TwoLineListItem;\nimport org.jackhuang.hmcl.ui.construct.URLValidator;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardPage;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.ui.download.LocalModpackPage.MODPACK_FILE;\nimport static org.jackhuang.hmcl.ui.download.LocalModpackPage.MODPACK_NAME;\nimport static org.jackhuang.hmcl.ui.download.RemoteModpackPage.MODPACK_SERVER_MANIFEST;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class ModpackSelectionPage extends VBox implements WizardPage {\n    private final WizardController controller;\n\n    public ModpackSelectionPage(WizardController controller) {\n        this.controller = controller;\n\n        Label title = new Label(i18n(\"install.modpack\"));\n        title.setPadding(new Insets(8));\n\n        this.getStyleClass().add(\"jfx-list-view\");\n        this.setMaxSize(400, 150);\n        this.setSpacing(8);\n        this.getChildren().setAll(\n                title,\n                createButton(\"local\", this::onChooseLocalFile),\n                createButton(\"remote\", this::onChooseRemoteFile),\n                createButton(\"repository\", this::onChooseRepository)\n        );\n\n        Path filePath = controller.getSettings().get(MODPACK_FILE);\n        if (filePath != null) {\n            controller.getSettings().put(MODPACK_FILE, filePath);\n            Platform.runLater(controller::onNext);\n        }\n\n        FXUtils.applyDragListener(this, ModpackHelper::isFileModpackByExtension, modpacks -> {\n            Path modpack = modpacks.get(0);\n            controller.getSettings().put(MODPACK_FILE, modpack);\n            controller.onNext();\n        });\n    }\n\n    private JFXButton createButton(String type, Runnable action) {\n        JFXButton button = new JFXButton();\n\n        button.getStyleClass().add(\"card\");\n        button.setStyle(\"-fx-cursor: HAND;\");\n        button.prefWidthProperty().bind(this.widthProperty());\n        button.setOnAction(e -> action.run());\n\n        BorderPane graphic = new BorderPane();\n        graphic.setMouseTransparent(true);\n        graphic.setLeft(new TwoLineListItem(i18n(\"modpack.choose.\" + type), i18n(\"modpack.choose.\" + type + \".detail\")));\n\n        SVGPath arrow = new SVGPath();\n        arrow.setContent(SVG.ARROW_FORWARD.getPath());\n        BorderPane.setAlignment(arrow, Pos.CENTER);\n        graphic.setRight(arrow);\n\n        button.setGraphic(graphic);\n\n        JFXDepthManager.setDepth(button, 1);\n\n        return button;\n    }\n\n    private void onChooseLocalFile() {\n        FileChooser chooser = new FileChooser();\n        chooser.setTitle(i18n(\"modpack.choose\"));\n        chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"modpack\"), \"*.zip\", \"*.mrpack\"));\n        Path selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));\n        if (selectedFile == null) {\n            Platform.runLater(controller::onEnd);\n            return;\n        }\n\n        controller.getSettings().put(MODPACK_FILE, selectedFile);\n        controller.onNext();\n    }\n\n    private void onChooseRemoteFile() {\n        Controllers.prompt(i18n(\"modpack.choose.remote.tooltip\"), (url, handler) -> {\n            try {\n                if (url.endsWith(\"server-manifest.json\")) {\n                    // if urlString ends with .json, we assume that the url is server-manifest.json\n                    Controllers.taskDialog(new GetTask(url).whenComplete(Schedulers.javafx(), (result, e) -> {\n                        ServerModpackManifest manifest = JsonUtils.fromMaybeMalformedJson(result, ServerModpackManifest.class);\n                        if (manifest == null) {\n                            handler.reject(i18n(\"modpack.type.server.malformed\"));\n                        } else if (e == null) {\n                            handler.resolve();\n                            controller.getSettings().put(MODPACK_SERVER_MANIFEST, manifest);\n                            controller.onNext();\n                        } else {\n                            handler.reject(e.getMessage());\n                        }\n                    }), i18n(\"message.downloading\"), TaskCancellationAction.NORMAL);\n                } else {\n                    // otherwise we still consider the file as modpack zip file\n                    // since casually the url may not ends with \".zip\"\n                    Path modpack = Files.createTempFile(\"modpack\", \".zip\");\n                    Controllers.taskDialog(\n                            new FileDownloadTask(url, modpack)\n                                    .whenComplete(Schedulers.javafx(), e -> {\n                                        if (e == null) {\n                                            handler.resolve();\n                                            controller.getSettings().put(MODPACK_FILE, modpack);\n                                            controller.onNext();\n                                        } else {\n                                            handler.reject(e.getMessage());\n                                        }\n                                    }),\n                            i18n(\"message.downloading\"),\n                            TaskCancellationAction.NORMAL\n                    );\n                }\n            } catch (IOException e) {\n                handler.reject(e.getMessage());\n            }\n        }, \"\", new URLValidator());\n    }\n\n    public void onChooseRepository() {\n        String modPackName = controller.getSettings().get(MODPACK_NAME);\n        DownloadPage downloadPage = new DownloadPage(modPackName);\n        downloadPage.showModpackDownloads();\n        Controllers.navigate(downloadPage);\n    }\n\n    @Override\n    public void cleanup(SettingsMap settings) {\n    }\n\n    @Override\n    public String getTitle() {\n        return i18n(\"modpack.task.install\");\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport javafx.application.Platform;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.server.ServerModpackManifest;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.WebPage;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.RequiredValidator;\nimport org.jackhuang.hmcl.ui.construct.Validator;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.io.IOException;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class RemoteModpackPage extends ModpackPage {\n    private final ServerModpackManifest manifest;\n\n    public RemoteModpackPage(WizardController controller) {\n        super(controller);\n\n        manifest = controller.getSettings().get(MODPACK_SERVER_MANIFEST);\n        if (manifest == null)\n            throw new IllegalStateException(\"MODPACK_SERVER_MANIFEST should exist\");\n\n        try {\n            controller.getSettings().put(MODPACK_MANIFEST, manifest.toModpack(null));\n        } catch (IOException e) {\n            Controllers.dialog(i18n(\"modpack.type.server.malformed\"), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n            Platform.runLater(controller::onEnd);\n            return;\n        }\n\n        nameProperty.set(manifest.getName());\n        versionProperty.set(manifest.getVersion());\n        authorProperty.set(manifest.getAuthor());\n\n        Profile profile = controller.getSettings().get(ModpackPage.PROFILE);\n        String name = controller.getSettings().get(MODPACK_NAME);\n        if (name != null) {\n            txtModpackName.setText(name);\n            txtModpackName.setDisable(true);\n        } else {\n            // trim: https://github.com/HMCL-dev/HMCL/issues/962\n            txtModpackName.setText(manifest.getName().trim());\n            txtModpackName.getValidators().addAll(\n                    new RequiredValidator(),\n                    new Validator(i18n(\"install.new_game.already_exists\"), str -> !profile.getRepository().versionIdConflicts(str)),\n                    new Validator(i18n(\"install.new_game.malformed\"), HMCLGameRepository::isValidVersionId));\n        }\n\n        btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription()));\n    }\n\n    @Override\n    public void cleanup(SettingsMap settings) {\n        settings.remove(MODPACK_SERVER_MANIFEST);\n    }\n\n    protected void onInstall() {\n        if (!txtModpackName.validate()) return;\n        controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());\n        controller.onFinish();\n    }\n\n    protected void onDescribe() {\n        Controllers.navigate(new WebPage(i18n(\"modpack.description\"), manifest.getDescription()));\n    }\n\n    public static final SettingsMap.Key<ServerModpackManifest> MODPACK_SERVER_MANIFEST = new SettingsMap.Key<>(\"MODPACK_SERVER_MANIFEST\");\n    public static final SettingsMap.Key<String> MODPACK_NAME = new SettingsMap.Key<>(\"MODPACK_NAME\");\n    public static final SettingsMap.Key<Modpack> MODPACK_MANIFEST = new SettingsMap.Key<>(\"MODPACK_MANIFEST\");\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.download.*;\nimport org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;\nimport org.jackhuang.hmcl.download.game.LibraryDownloadException;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.DownloadException;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardProvider;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.io.ResponseCodeException;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.net.SocketTimeoutException;\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.concurrent.CancellationException;\nimport java.util.zip.ZipException;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class UpdateInstallerWizardProvider implements WizardProvider {\n    private final Profile profile;\n    private final DefaultDependencyManager dependencyManager;\n    private final String gameVersion;\n    private final Version version;\n    private final String libraryId;\n    private final String oldLibraryVersion;\n    private final DownloadProvider downloadProvider;\n\n    public UpdateInstallerWizardProvider(@NotNull Profile profile, @NotNull String gameVersion, @NotNull Version version, @NotNull String libraryId, @Nullable String oldLibraryVersion) {\n        this.profile = profile;\n        this.gameVersion = gameVersion;\n        this.version = version;\n        this.libraryId = libraryId;\n        this.oldLibraryVersion = oldLibraryVersion;\n        this.downloadProvider = DownloadProviders.getDownloadProvider();\n        this.dependencyManager = profile.getDependency(downloadProvider);\n    }\n\n    @Override\n    public void start(SettingsMap settings) {\n    }\n\n    @Override\n    public Object finish(SettingsMap settings) {\n        settings.put(\"title\", i18n(\"install.change_version.process\"));\n        settings.put(\"success_message\", i18n(\"install.success\"));\n        settings.put(FailureCallback.KEY, (settings1, exception, next) -> alertFailureMessage(exception, next));\n\n        // We remove library but not save it,\n        // so if installation failed will not break down current version.\n        Task<Version> ret = Task.supplyAsync(() -> version);\n        var hints = new ArrayList<Task.StagesHint>();\n        for (Object value : settings.asStringMap().values()) {\n            if (value instanceof RemoteVersion remoteVersion) {\n                ret = ret.thenComposeAsync(version -> dependencyManager.installLibraryAsync(version, remoteVersion));\n                hints.add(new Task.StagesHint(String.format(\"hmcl.install.%s:%s\", remoteVersion.getLibraryId(), remoteVersion.getSelfVersion())));\n                if (\"game\".equals(remoteVersion.getLibraryId())) {\n                    hints.add(new Task.StagesHint(\"hmcl.install.libraries\"));\n                    hints.add(new Task.StagesHint(\"hmcl.install.assets\"));\n                }\n            } else if (value instanceof RemoveVersionAction removeVersionAction) {\n                ret = ret.thenComposeAsync(version -> dependencyManager.removeLibraryAsync(version, removeVersionAction.libraryId));\n            }\n        }\n\n        return ret.thenComposeAsync(profile.getRepository()::saveAsync).thenComposeAsync(profile.getRepository().refreshVersionsAsync()).withStagesHints(hints);\n    }\n\n    @Override\n    public Node createPage(WizardController controller, int step, SettingsMap settings) {\n        switch (step) {\n            case 0:\n                return new VersionsPage(controller, i18n(\"install.installer.choose\", i18n(\"install.installer.\" + libraryId)), gameVersion, downloadProvider, libraryId, () -> {\n                    if (oldLibraryVersion == null) {\n                        controller.onFinish();\n                    } else if (\"game\".equals(libraryId)) {\n                        String newGameVersion = ((RemoteVersion) settings.get(libraryId)).getSelfVersion();\n                        controller.onNext(new AdditionalInstallersPage(newGameVersion, version, controller, profile.getRepository(), downloadProvider));\n                    } else {\n                        Controllers.confirm(i18n(\"install.change_version.confirm\", i18n(\"install.installer.\" + libraryId), oldLibraryVersion, ((RemoteVersion) settings.get(libraryId)).getSelfVersion()),\n                                i18n(\"install.change_version\"), controller::onFinish, controller::onCancel);\n                    }\n                });\n            default:\n                throw new IllegalStateException();\n        }\n    }\n\n    @Override\n    public boolean cancel() {\n        return true;\n    }\n\n    @Override\n    public boolean cancelIfCannotGoBack() {\n        // VersionsPage will call wizardController.onPrev(cleanUp = true) when list is empty.\n        // So we cancel this wizard when VersionPage calls the method.\n        return true;\n    }\n\n    public static void alertFailureMessage(Exception exception, Runnable next) {\n        if (exception instanceof LibraryDownloadException) {\n            String message = i18n(\"launch.failed.download_library\", ((LibraryDownloadException) exception).getLibrary().getName()) + \"\\n\";\n            if (exception.getCause() instanceof ResponseCodeException) {\n                ResponseCodeException rce = (ResponseCodeException) exception.getCause();\n                int responseCode = rce.getResponseCode();\n                String uri = rce.getUri();\n                if (responseCode == 404)\n                    message += i18n(\"download.code.404\", uri);\n                else\n                    message += i18n(\"download.failed\", uri, responseCode);\n            } else {\n                message += StringUtils.getStackTrace(exception.getCause());\n            }\n            Controllers.dialog(message, i18n(\"install.failed.downloading\"), MessageDialogPane.MessageType.ERROR, next);\n        } else if (exception instanceof DownloadException) {\n            URI uri = ((DownloadException) exception).getUri();\n            if (exception.getCause() instanceof SocketTimeoutException) {\n                Controllers.dialog(i18n(\"install.failed.downloading.timeout\", uri), i18n(\"install.failed.downloading\"), MessageDialogPane.MessageType.ERROR, next);\n            } else if (exception.getCause() instanceof ResponseCodeException) {\n                ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause();\n                if (I18n.hasKey(\"download.code.\" + responseCodeException.getResponseCode())) {\n                    Controllers.dialog(i18n(\"download.code.\" + responseCodeException.getResponseCode(), uri), i18n(\"install.failed.downloading\"), MessageDialogPane.MessageType.ERROR, next);\n                } else {\n                    Controllers.dialog(i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + StringUtils.getStackTrace(exception.getCause()), i18n(\"install.failed.downloading\"), MessageDialogPane.MessageType.ERROR, next);\n                }\n            } else {\n                Controllers.dialog(i18n(\"install.failed.downloading.detail\", uri) + \"\\n\" + StringUtils.getStackTrace(exception.getCause()), i18n(\"install.failed.downloading\"), MessageDialogPane.MessageType.ERROR, next);\n            }\n        } else if (exception instanceof UnsupportedInstallationException) {\n            switch (((UnsupportedInstallationException) exception).getReason()) {\n                case UnsupportedInstallationException.FORGE_1_17_OPTIFINE_H1_PRE2:\n                    Controllers.dialog(i18n(\"install.failed.optifine_forge_1.17\"), i18n(\"install.failed\"), MessageDialogPane.MessageType.ERROR, next);\n                    break;\n                default:\n                    Controllers.dialog(i18n(\"install.failed.optifine_conflict\"), i18n(\"install.failed\"), MessageDialogPane.MessageType.ERROR, next);\n                    break;\n            }\n        } else if (exception instanceof DefaultDependencyManager.UnsupportedLibraryInstallerException) {\n            Controllers.dialog(i18n(\"install.failed.install_online\"), i18n(\"install.failed\"), MessageDialogPane.MessageType.ERROR, next);\n        } else if (exception instanceof ArtifactMalformedException || exception instanceof ZipException) {\n            Controllers.dialog(i18n(\"install.failed.malformed\"), i18n(\"install.failed\"), MessageDialogPane.MessageType.ERROR, next);\n        } else if (exception instanceof GameAssetIndexDownloadTask.GameAssetIndexMalformedException) {\n            Controllers.dialog(i18n(\"assets.index.malformed\"), i18n(\"install.failed\"), MessageDialogPane.MessageType.ERROR, next);\n        } else if (exception instanceof VersionMismatchException) {\n            VersionMismatchException e = ((VersionMismatchException) exception);\n            Controllers.dialog(i18n(\"install.failed.version_mismatch\", e.getExpect(), e.getActual()), i18n(\"install.failed\"), MessageDialogPane.MessageType.ERROR, next);\n        } else if (exception instanceof CancellationException) {\n            // Ignore cancel\n        } else {\n            Controllers.dialog(StringUtils.getStackTrace(exception), i18n(\"install.failed\"), MessageDialogPane.MessageType.ERROR, next);\n        }\n    }\n\n    public static class RemoveVersionAction {\n        private final String libraryId;\n\n        public RemoveVersionAction(String libraryId) {\n            this.libraryId = libraryId;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.GameBuilder;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardProvider;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class VanillaInstallWizardProvider implements WizardProvider {\n    private final Profile profile;\n    private final DefaultDependencyManager dependencyManager;\n    private final DownloadProvider downloadProvider;\n\n    public VanillaInstallWizardProvider(Profile profile) {\n        this.profile = profile;\n        this.downloadProvider = DownloadProviders.getDownloadProvider();\n        this.dependencyManager = profile.getDependency(downloadProvider);\n    }\n\n    @Override\n    public void start(SettingsMap settings) {\n        settings.put(ModpackPage.PROFILE, profile);\n    }\n\n    private Task<Void> finishVersionDownloadingAsync(SettingsMap settings) {\n        GameBuilder builder = dependencyManager.gameBuilder();\n\n        String name = (String) settings.get(\"name\");\n        builder.name(name);\n        builder.gameVersion(((RemoteVersion) settings.get(\"game\")).getGameVersion());\n\n        settings.asStringMap().forEach((key, value) -> {\n            if (!\"game\".equals(key) && value instanceof RemoteVersion remoteVersion)\n                builder.version(remoteVersion);\n        });\n\n        return builder.buildAsync().whenComplete(any -> profile.getRepository().refreshVersions())\n                .thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));\n    }\n\n    @Override\n    public Object finish(SettingsMap settings) {\n        settings.put(\"title\", i18n(\"install.new_game.installation\"));\n        settings.put(\"success_message\", i18n(\"install.success\"));\n        settings.put(FailureCallback.KEY, (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next));\n\n        return finishVersionDownloadingAsync(settings);\n    }\n\n    @Override\n    public Node createPage(WizardController controller, int step, SettingsMap settings) {\n        switch (step) {\n            case 0:\n                return new VersionsPage(controller, i18n(\"install.installer.choose\", i18n(\"install.installer.game\")), \"\", downloadProvider, \"game\",\n                        () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), ((RemoteVersion) controller.getSettings().get(\"game\")).getGameVersion(), downloadProvider)));\n            default:\n                throw new IllegalStateException(\"error step \" + step + \", settings: \" + settings + \", pages: \" + controller.getPages());\n        }\n    }\n\n    @Override\n    public boolean cancel() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.download;\n\nimport com.jfoenix.controls.*;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.*;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.download.cleanroom.CleanroomRemoteVersion;\nimport org.jackhuang.hmcl.download.fabric.FabricAPIRemoteVersion;\nimport org.jackhuang.hmcl.download.fabric.FabricRemoteVersion;\nimport org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;\nimport org.jackhuang.hmcl.download.game.GameRemoteVersion;\nimport org.jackhuang.hmcl.download.legacyfabric.LegacyFabricAPIRemoteVersion;\nimport org.jackhuang.hmcl.download.legacyfabric.LegacyFabricRemoteVersion;\nimport org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;\nimport org.jackhuang.hmcl.download.neoforge.NeoForgeRemoteVersion;\nimport org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;\nimport org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion;\nimport org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion;\nimport org.jackhuang.hmcl.setting.VersionIconType;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.ComponentList;\nimport org.jackhuang.hmcl.ui.construct.RipplerContainer;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\nimport org.jackhuang.hmcl.ui.construct.TwoLineListItem;\nimport org.jackhuang.hmcl.ui.wizard.Navigation;\nimport org.jackhuang.hmcl.ui.wizard.Refreshable;\nimport org.jackhuang.hmcl.ui.wizard.WizardPage;\nimport org.jackhuang.hmcl.util.NativePatcher;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.util.Locale;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.*;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class VersionsPage extends Control implements WizardPage, Refreshable {\n    private final String gameVersion;\n    private final String libraryId;\n    private final String title;\n    private final Navigation navigation;\n    private final DownloadProvider downloadProvider;\n    private final VersionList<?> versionList;\n    private final Runnable callback;\n\n    private final ObservableList<RemoteVersion> versions = FXCollections.observableArrayList();\n    private final ObjectProperty<Status> status = new SimpleObjectProperty<>(Status.LOADING);\n\n    public VersionsPage(Navigation navigation,\n                        String title, String gameVersion,\n                        DownloadProvider downloadProvider,\n                        String libraryId,\n                        Runnable callback) {\n        this.title = title;\n        this.gameVersion = gameVersion;\n        this.libraryId = libraryId;\n        this.navigation = navigation;\n        this.downloadProvider = downloadProvider;\n        this.versionList = downloadProvider.getVersionListById(libraryId);\n        this.callback = callback;\n\n        refresh();\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new VersionsPageSkin(this);\n    }\n\n    @Override\n    public void refresh() {\n        status.set(Status.LOADING);\n        Task<?> task = versionList.refreshAsync(gameVersion)\n                .thenSupplyAsync(() -> versionList.getVersions(gameVersion).stream().sorted().collect(Collectors.toList()))\n                .whenComplete(Schedulers.javafx(), (items, exception) -> {\n                    if (exception == null) {\n                        versions.setAll(items);\n                        status.set(Status.SUCCESS);\n                    } else {\n                        LOG.warning(\"Failed to fetch versions list\", exception);\n                        status.set(Status.FAILED);\n                    }\n                });\n        task.start();\n    }\n\n    @Override\n    public String getTitle() {\n        return title;\n    }\n\n    private void onRefresh() {\n        refresh();\n    }\n\n    private void onBack() {\n        navigation.onPrev(true);\n    }\n\n    private enum Status {\n        LOADING,\n        FAILED,\n        SUCCESS,\n    }\n\n    private enum VersionTypeFilter {\n        ALL,\n        RELEASE,\n        SNAPSHOTS,\n        APRIL_FOOLS,\n        OLD\n    }\n\n    private static class RemoteVersionListCell extends ListCell<RemoteVersion> {\n        private final VersionsPage control;\n\n        private final TwoLineListItem twoLineListItem = new TwoLineListItem();\n        private final ImageView imageView = new ImageView();\n        private final StackPane pane = new StackPane();\n\n        RemoteVersionListCell(VersionsPage control) {\n            this.control = control;\n\n            imageView.setMouseTransparent(true);\n\n            HBox hbox = new HBox(16);\n            HBox.setHgrow(twoLineListItem, Priority.ALWAYS);\n            hbox.setAlignment(Pos.CENTER);\n\n            HBox actions = new HBox(8);\n            actions.setAlignment(Pos.CENTER);\n            {\n                if (\"game\".equals(control.libraryId)) {\n                    JFXButton wikiButton = newToggleButton4(SVG.GLOBE_BOOK);\n                    wikiButton.setOnAction(event -> onOpenWiki());\n                    FXUtils.installFastTooltip(wikiButton, i18n(\"wiki.tooltip\"));\n                    actions.getChildren().add(wikiButton);\n                }\n\n                JFXButton actionButton = newToggleButton4(SVG.ARROW_FORWARD);\n                actionButton.setOnAction(e -> onAction());\n                actions.getChildren().add(actionButton);\n            }\n\n            hbox.getChildren().setAll(imageView, twoLineListItem, actions);\n\n            pane.getStyleClass().add(\"md-list-cell\");\n            StackPane.setMargin(hbox, new Insets(10, 16, 10, 16));\n            pane.getChildren().setAll(new RipplerContainer(hbox));\n\n            FXUtils.onClicked(this, this::onAction);\n        }\n\n        private void onAction() {\n            RemoteVersion item = getItem();\n            if (item == null)\n                return;\n\n            control.navigation.getSettings().put(control.libraryId, item);\n            control.callback.run();\n        }\n\n        private void onOpenWiki() {\n            RemoteVersion item = getItem();\n            if (!(item instanceof GameRemoteVersion))\n                return;\n\n            FXUtils.openLink(I18n.getWikiLink((GameRemoteVersion) item));\n        }\n\n        @Override\n        public void updateItem(RemoteVersion remoteVersion, boolean empty) {\n            super.updateItem(remoteVersion, empty);\n\n            if (empty) {\n                setGraphic(null);\n                return;\n            }\n            setGraphic(pane);\n\n            twoLineListItem.setTitle(I18n.getDisplayVersion(remoteVersion));\n            if (remoteVersion.getReleaseDate() != null) {\n                twoLineListItem.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));\n            } else {\n                twoLineListItem.setSubtitle(null);\n            }\n            twoLineListItem.getTags().clear();\n\n            if (remoteVersion instanceof GameRemoteVersion) {\n                RemoteVersion.Type versionType = remoteVersion.getVersionType();\n                GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(remoteVersion.getGameVersion());\n\n                switch (versionType) {\n                    case RELEASE -> {\n                        twoLineListItem.addTag(i18n(\"version.game.release\"));\n                        imageView.setImage(VersionIconType.GRASS.getIcon());\n                    }\n                    case SNAPSHOT, PENDING, UNOBFUSCATED -> {\n                        if (versionType == RemoteVersion.Type.SNAPSHOT\n                                && GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()).isAprilFools()) {\n                            twoLineListItem.addTag(i18n(\"version.game.april_fools\"));\n                            imageView.setImage(VersionIconType.APRIL_FOOLS.getIcon());\n                        } else {\n                            twoLineListItem.addTag(i18n(\"version.game.snapshot\"));\n                            imageView.setImage(VersionIconType.COMMAND.getIcon());\n                        }\n                    }\n                    default -> {\n                        twoLineListItem.addTag(i18n(\"version.game.old\"));\n                        imageView.setImage(VersionIconType.CRAFT_TABLE.getIcon());\n                    }\n                }\n\n                switch (NativePatcher.checkSupportedStatus(gameVersion, Platform.SYSTEM_PLATFORM, OperatingSystem.SYSTEM_VERSION)) {\n                    case UNTESTED -> twoLineListItem.addTagWarning(i18n(\"version.game.support_status.untested\"));\n                    case UNSUPPORTED -> twoLineListItem.addTagWarning(i18n(\"version.game.support_status.unsupported\"));\n                }\n            } else {\n                VersionIconType iconType;\n                if (remoteVersion instanceof LiteLoaderRemoteVersion)\n                    iconType = VersionIconType.CHICKEN;\n                else if (remoteVersion instanceof OptiFineRemoteVersion)\n                    iconType = VersionIconType.OPTIFINE;\n                else if (remoteVersion instanceof ForgeRemoteVersion)\n                    iconType = VersionIconType.FORGE;\n                else if (remoteVersion instanceof CleanroomRemoteVersion)\n                    iconType = VersionIconType.CLEANROOM;\n                else if (remoteVersion instanceof NeoForgeRemoteVersion)\n                    iconType = VersionIconType.NEO_FORGE;\n                else if (remoteVersion instanceof LegacyFabricRemoteVersion || remoteVersion instanceof LegacyFabricAPIRemoteVersion)\n                    iconType = VersionIconType.LEGACY_FABRIC;\n                else if (remoteVersion instanceof FabricRemoteVersion || remoteVersion instanceof FabricAPIRemoteVersion)\n                    iconType = VersionIconType.FABRIC;\n                else if (remoteVersion instanceof QuiltRemoteVersion || remoteVersion instanceof QuiltAPIRemoteVersion)\n                    iconType = VersionIconType.QUILT;\n                else\n                    iconType = VersionIconType.COMMAND;\n\n                imageView.setImage(iconType.getIcon());\n                String displayGameVersion = I18n.getDisplayVersion(GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()));\n\n                if (twoLineListItem.getSubtitle() == null)\n                    twoLineListItem.setSubtitle(displayGameVersion);\n                else\n                    twoLineListItem.addTag(displayGameVersion);\n            }\n        }\n    }\n\n    private static final class VersionsPageSkin extends SkinBase<VersionsPage> {\n        private final JFXListView<RemoteVersion> list;\n\n        private final TransitionPane transitionPane;\n        private final JFXSpinner spinner;\n\n        private final JFXTextField nameField;\n        private final JFXComboBox<VersionTypeFilter> categoryField = new JFXComboBox<>();\n\n        VersionsPageSkin(VersionsPage control) {\n            super(control);\n\n            BorderPane root = new BorderPane();\n\n            GridPane searchPane = new GridPane();\n            root.setTop(searchPane);\n            searchPane.getStyleClass().addAll(\"card\");\n            BorderPane.setMargin(searchPane, new Insets(10, 10, 0, 10));\n\n            ColumnConstraints nameColumn = new ColumnConstraints();\n            nameColumn.setMinWidth(USE_PREF_SIZE);\n            ColumnConstraints column1 = new ColumnConstraints();\n            column1.setHgrow(Priority.ALWAYS);\n            ColumnConstraints column2 = new ColumnConstraints();\n            column2.setMaxWidth(150);\n            ColumnConstraints column3 = new ColumnConstraints();\n\n            if (control.versionList.hasType())\n                searchPane.getColumnConstraints().setAll(nameColumn, column1, nameColumn, column2, column3);\n            else\n                searchPane.getColumnConstraints().setAll(nameColumn, column1, column3);\n\n            searchPane.setHgap(16);\n            searchPane.setVgap(10);\n\n            {\n                int rowIndex = 0;\n\n                {\n                    nameField = new JFXTextField();\n                    nameField.setPromptText(i18n(\"version.search.prompt\"));\n                    nameField.textProperty().addListener(o -> updateList());\n\n                    if (\"game\".equals(control.libraryId)) {\n                        categoryField.getItems().setAll(\n                                VersionTypeFilter.ALL,\n                                VersionTypeFilter.RELEASE,\n                                VersionTypeFilter.SNAPSHOTS,\n                                VersionTypeFilter.APRIL_FOOLS,\n                                VersionTypeFilter.OLD\n                        );\n                        categoryField.getSelectionModel().select(VersionTypeFilter.RELEASE);\n                    } else {\n                        categoryField.getItems().setAll(\n                                VersionTypeFilter.ALL,\n                                VersionTypeFilter.RELEASE,\n                                VersionTypeFilter.SNAPSHOTS\n                        );\n                        categoryField.getSelectionModel().select(VersionTypeFilter.ALL);\n                    }\n                    categoryField.setConverter(stringConverter(type -> i18n(\"version.game.\" + type.name().toLowerCase(Locale.ROOT))));\n                    categoryField.getSelectionModel().selectedItemProperty().addListener(o -> updateList());\n\n                    JFXButton refreshButton = FXUtils.newRaisedButton(i18n(\"button.refresh\"));\n                    refreshButton.setOnAction(event -> control.onRefresh());\n\n                    if (control.versionList.hasType()) {\n                        searchPane.addRow(rowIndex++,\n                                new Label(i18n(\"version.search\")), nameField,\n                                new Label(i18n(\"version.game.type\")), categoryField,\n                                refreshButton\n                        );\n                    } else {\n                        searchPane.addRow(rowIndex++,\n                                new Label(i18n(\"version.search\")), nameField,\n                                refreshButton\n                        );\n                    }\n                }\n            }\n\n            {\n                SpinnerPane spinnerPane = new SpinnerPane();\n                root.setCenter(spinnerPane);\n\n                transitionPane = new TransitionPane();\n                spinner = new JFXSpinner();\n\n                StackPane centerWrapper = new StackPane();\n                centerWrapper.setStyle(\"-fx-padding: 10;\");\n                {\n                    ComponentList centrePane = new ComponentList();\n                    centrePane.getStyleClass().add(\"no-padding\");\n                    {\n                        list = new JFXListView<>();\n                        list.getStyleClass().add(\"jfx-list-view-float\");\n                        VBox.setVgrow(list, Priority.ALWAYS);\n\n                        control.versions.addListener((InvalidationListener) o -> updateList());\n\n                        list.setCellFactory(listView -> new RemoteVersionListCell(control));\n\n                        ComponentList.setVgrow(list, Priority.ALWAYS);\n\n                        // ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here\n                        ignoreEvent(list, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);\n\n                        centrePane.getContent().setAll(list);\n                    }\n\n                    centerWrapper.getChildren().setAll(centrePane);\n                }\n\n                StackPane failedPane = new StackPane();\n                failedPane.getStyleClass().add(\"notice-pane\");\n                {\n                    Label label = new Label(i18n(\"download.failed.refresh\"));\n                    FXUtils.onClicked(label, control::onRefresh);\n\n                    failedPane.getChildren().setAll(label);\n                }\n\n                StackPane emptyPane = new StackPane();\n                emptyPane.getStyleClass().add(\"notice-pane\");\n                {\n                    Label label = new Label(i18n(\"download.failed.empty\"));\n                    FXUtils.onClicked(label, control::onBack);\n\n                    emptyPane.getChildren().setAll(label);\n                }\n\n                FXUtils.onChangeAndOperate(control.status, status -> {\n                    if (status == Status.LOADING)\n                        transitionPane.setContent(spinner, ContainerAnimations.FADE);\n                    else if (status == Status.SUCCESS)\n                        transitionPane.setContent(centerWrapper, ContainerAnimations.FADE);\n                    else // if (status == Status.FAILED)\n                        transitionPane.setContent(failedPane, ContainerAnimations.FADE);\n                });\n\n                root.setCenter(transitionPane);\n            }\n\n            this.getChildren().setAll(root);\n        }\n\n        private void updateList() {\n            Stream<RemoteVersion> versions = getSkinnable().versions.stream();\n\n            VersionTypeFilter filter = categoryField.getSelectionModel().getSelectedItem();\n            if (filter != null)\n                versions = versions.filter(it -> {\n                    RemoteVersion.Type versionType = it.getVersionType();\n                    return switch (filter) {\n                        case RELEASE -> versionType == RemoteVersion.Type.RELEASE;\n                        case SNAPSHOTS -> versionType == RemoteVersion.Type.SNAPSHOT\n                                || versionType == RemoteVersion.Type.PENDING\n                                || versionType == RemoteVersion.Type.UNOBFUSCATED;\n                        case APRIL_FOOLS -> versionType == RemoteVersion.Type.SNAPSHOT\n                                && GameVersionNumber.asGameVersion(it.getGameVersion()).isAprilFools();\n                        case OLD -> versionType == RemoteVersion.Type.OLD;\n                        // case ALL,\n                        default -> true;\n                    };\n                });\n\n            String nameQuery = nameField.getText();\n            if (!StringUtils.isBlank(nameQuery)) {\n                if (nameQuery.startsWith(\"regex:\")) {\n                    try {\n                        Pattern pattern = Pattern.compile(nameQuery.substring(\"regex:\".length()));\n                        versions = versions.filter(it -> pattern.matcher(it.getSelfVersion()).find());\n                    } catch (Throwable e) {\n                        LOG.warning(\"Illegal regular expression: \" + nameQuery, e);\n                    }\n                } else {\n                    String lowerQueryString = nameQuery.toLowerCase(Locale.ROOT);\n                    versions = versions.filter(it -> it.getSelfVersion().toLowerCase(Locale.ROOT).contains(lowerQueryString));\n                }\n            }\n\n            //noinspection DataFlowIssue\n            list.getItems().setAll(versions.collect(Collectors.toList()));\n            list.scrollTo(0);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.export;\n\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.mod.ModAdviser;\nimport org.jackhuang.hmcl.mod.ModpackExportInfo;\nimport org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthModpackExportTask;\nimport org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;\nimport org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;\nimport org.jackhuang.hmcl.mod.server.ServerModpackExportTask;\nimport org.jackhuang.hmcl.setting.Config;\nimport org.jackhuang.hmcl.setting.FontManager;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.VersionSetting;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardProvider;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.io.Zipper;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\n\npublic final class ExportWizardProvider implements WizardProvider {\n    private final Profile profile;\n    private final String version;\n\n    public ExportWizardProvider(Profile profile, String version) {\n        this.profile = profile;\n        this.version = version;\n    }\n\n    @Override\n    public void start(SettingsMap settings) {\n    }\n\n    @Override\n    public Object finish(SettingsMap settings) {\n        List<String> whitelist = settings.get(ModpackFileSelectionPage.MODPACK_FILE_SELECTION);\n        Path modpackFile = settings.get(ModpackInfoPage.MODPACK_FILE);\n        ModpackExportInfo exportInfo = settings.get(ModpackInfoPage.MODPACK_INFO);\n        exportInfo.setWhitelist(whitelist);\n        String modpackType = settings.get(ModpackTypeSelectionPage.MODPACK_TYPE);\n\n        return exportWithLauncher(modpackType, exportInfo, modpackFile);\n    }\n\n    private Task<?> exportWithLauncher(String modpackType, ModpackExportInfo exportInfo, Path modpackFile) {\n        Path launcherJar = JarUtils.thisJarPath();\n        boolean packWithLauncher = exportInfo.isPackWithLauncher() && launcherJar != null;\n        return new Task<>() {\n            Path tempModpack;\n            Task<?> exportTask;\n\n            {\n                setSignificance(TaskSignificance.MODERATE);\n            }\n\n            @Override\n            public boolean doPreExecute() {\n                return true;\n            }\n\n            @Override\n            public void preExecute() throws Exception {\n                Path dest;\n                if (packWithLauncher) {\n                    dest = tempModpack = Files.createTempFile(\"hmcl\", \".zip\");\n                } else {\n                    dest = modpackFile;\n                }\n\n                switch (modpackType) {\n                    case ModpackTypeSelectionPage.MODPACK_TYPE_MCBBS:\n                        exportTask = exportAsMcbbs(exportInfo, dest);\n                        break;\n                    case ModpackTypeSelectionPage.MODPACK_TYPE_MULTIMC:\n                        exportTask = exportAsMultiMC(exportInfo, dest);\n                        break;\n                    case ModpackTypeSelectionPage.MODPACK_TYPE_SERVER:\n                        exportTask = exportAsServer(exportInfo, dest);\n                        break;\n                    case ModpackTypeSelectionPage.MODPACK_TYPE_MODRINTH:\n                        exportTask = exportAsModrinth(exportInfo, dest);\n                        break;\n                    default:\n                        throw new IllegalStateException(\"Unrecognized modpack type \" + modpackType);\n                }\n\n            }\n\n            @Override\n            public Collection<Task<?>> getDependents() {\n                return Collections.singleton(exportTask);\n            }\n\n            @Override\n            public void execute() throws Exception {\n                if (!packWithLauncher) return;\n                try (Zipper zip = new Zipper(modpackFile)) {\n                    Config exported = new Config();\n\n                    exported.setBackgroundImageType(config().getBackgroundImageType());\n                    exported.setBackgroundImage(config().getBackgroundImage());\n                    exported.setThemeColor(config().getThemeColor());\n                    exported.setDownloadType(config().getDownloadType());\n                    exported.setPreferredLoginType(config().getPreferredLoginType());\n                    exported.getAuthlibInjectorServers().setAll(config().getAuthlibInjectorServers());\n\n                    zip.putTextFile(exported.toJson(), \".hmcl/hmcl.json\");\n                    zip.putFile(tempModpack, ModpackTypeSelectionPage.MODPACK_TYPE_MODRINTH.equals(modpackType)\n                            ? \"modpack.mrpack\"\n                            : \"modpack.zip\");\n\n                    Path bg = Metadata.HMCL_CURRENT_DIRECTORY.resolve(\"background\");\n                    if (!Files.isDirectory(bg))\n                        bg = Metadata.CURRENT_DIRECTORY.resolve(\"bg\");\n                    if (Files.isDirectory(bg))\n                        zip.putDirectory(bg, \".hmcl/bg\");\n\n                    for (String extension : FXUtils.IMAGE_EXTENSIONS) {\n                        String fileName = \"background.\" + extension;\n                        Path background = Metadata.HMCL_CURRENT_DIRECTORY.resolve(fileName);\n                        if (!Files.isRegularFile(background))\n                            background = Metadata.CURRENT_DIRECTORY.resolve(fileName);\n                        if (Files.isRegularFile(background))\n                            zip.putFile(background, \".hmcl/\" + fileName);\n                    }\n\n                    for (String extension : FontManager.FONT_EXTENSIONS) {\n                        String fileName = \"font.\" + extension;\n                        Path font = Metadata.HMCL_CURRENT_DIRECTORY.resolve(fileName);\n                        if (!Files.isRegularFile(font))\n                            font = Metadata.CURRENT_DIRECTORY.resolve(fileName);\n                        if (Files.isRegularFile(font))\n                            zip.putFile(font, \".hmcl/\" + fileName);\n                    }\n\n                    zip.putFile(launcherJar, launcherJar.getFileName().toString());\n                }\n            }\n        };\n    }\n\n    private Task<?> exportAsMcbbs(ModpackExportInfo exportInfo, Path modpackFile) {\n        return new Task<Void>() {\n            Task<?> dependency = null;\n\n            {\n                setSignificance(TaskSignificance.MODERATE);\n            }\n\n            @Override\n            public void execute() {\n                dependency = new McbbsModpackExportTask(profile.getRepository(), version, exportInfo, modpackFile);\n            }\n\n            @Override\n            public Collection<Task<?>> getDependencies() {\n                return Collections.singleton(dependency);\n            }\n        };\n    }\n\n    private Task<?> exportAsMultiMC(ModpackExportInfo exportInfo, Path modpackFile) {\n        return new Task<Void>() {\n            Task<?> dependency;\n\n            {\n                setSignificance(TaskSignificance.MODERATE);\n            }\n\n            @Override\n            public void execute() {\n                VersionSetting vs = profile.getVersionSetting(version);\n                dependency = new MultiMCModpackExportTask(profile.getRepository(), version, exportInfo.getWhitelist(),\n                        new MultiMCInstanceConfiguration(\n                                \"OneSix\",\n                                exportInfo.getName() + \"-\" + exportInfo.getVersion(),\n                                null,\n                                Lang.toIntOrNull(vs.getPermSize()),\n                                vs.getWrapper(),\n                                vs.getPreLaunchCommand(),\n                                null,\n                                exportInfo.getDescription(),\n                                null,\n                                exportInfo.getJavaArguments(),\n                                vs.isFullscreen(),\n                                vs.getWidth(),\n                                vs.getHeight(),\n                                vs.getMaxMemory(),\n                                exportInfo.getMinMemory(),\n                                vs.isShowLogs(),\n                                /* showConsoleOnError */ true,\n                                /* autoCloseConsole */ false,\n                                /* overrideMemory */ true,\n                                /* overrideJavaLocation */ false,\n                                /* overrideJavaArgs */ true,\n                                /* overrideConsole */ true,\n                                /* overrideCommands */ true,\n                                /* overrideWindow */ true,\n                                /* iconKey */ null // TODO\n                        ), modpackFile);\n            }\n\n            @Override\n            public Collection<Task<?>> getDependencies() {\n                return Collections.singleton(dependency);\n            }\n        };\n    }\n\n    private Task<?> exportAsServer(ModpackExportInfo exportInfo, Path modpackFile) {\n        return new Task<Void>() {\n            Task<?> dependency;\n\n            {\n                setSignificance(TaskSignificance.MODERATE);\n            }\n\n            @Override\n            public void execute() {\n                dependency = new ServerModpackExportTask(profile.getRepository(), version, exportInfo, modpackFile);\n            }\n\n            @Override\n            public Collection<Task<?>> getDependencies() {\n                return Collections.singleton(dependency);\n            }\n        };\n    }\n\n    private Task<?> exportAsModrinth(ModpackExportInfo exportInfo, Path modpackFile) {\n        return new Task<Void>() {\n            Task<?> dependency;\n\n            {\n                setSignificance(TaskSignificance.MODERATE);\n            }\n\n            @Override\n            public void execute() {\n                dependency = new ModrinthModpackExportTask(\n                        profile.getRepository(),\n                        version,\n                        exportInfo,\n                        modpackFile\n                );\n            }\n\n            @Override\n            public Collection<Task<?>> getDependencies() {\n                return Collections.singleton(dependency);\n            }\n        };\n    }\n\n    @Override\n    public Node createPage(WizardController controller, int step, SettingsMap settings) {\n        return switch (step) {\n            case 0 -> new ModpackTypeSelectionPage(controller);\n            case 1 -> new ModpackInfoPage(controller, profile.getRepository(), version);\n            case 2 -> new ModpackFileSelectionPage(controller, profile, version, ModAdviser::suggestMod);\n            default -> throw new IllegalArgumentException(\"step\");\n        };\n    }\n\n    @Override\n    public boolean cancel() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.export;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXCheckBox;\nimport com.jfoenix.controls.JFXTreeView;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.CheckBoxTreeItem;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport org.jackhuang.hmcl.mod.ModAdviser;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardPage;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huangyuhui\n */\npublic final class ModpackFileSelectionPage extends BorderPane implements WizardPage {\n    private final WizardController controller;\n    private final String version;\n    private final ModAdviser adviser;\n    private final CheckBoxTreeItem<String> rootNode;\n\n    public ModpackFileSelectionPage(WizardController controller, Profile profile, String version, ModAdviser adviser) {\n        this.controller = controller;\n        this.version = version;\n        this.adviser = adviser;\n\n        JFXTreeView<String> treeView = new JFXTreeView<>();\n        rootNode = getTreeItem(profile.getRepository().getRunDirectory(version), \"minecraft\");\n        treeView.setRoot(rootNode);\n        treeView.setSelectionModel(new NoneMultipleSelectionModel<>());\n        onEscPressed(treeView, () -> controller.onPrev(true));\n        setMargin(treeView, new Insets(10, 10, 5, 10));\n        this.setCenter(treeView);\n\n        HBox nextPane = new HBox();\n        nextPane.setPadding(new Insets(16, 16, 16, 0));\n        nextPane.setAlignment(Pos.CENTER_RIGHT);\n        {\n            JFXButton btnNext = FXUtils.newRaisedButton(i18n(\"wizard.next\"));\n            btnNext.setPrefSize(100, 40);\n            btnNext.setOnAction(e -> onNext());\n\n            nextPane.getChildren().setAll(btnNext);\n        }\n\n        this.setBottom(nextPane);\n    }\n\n    private CheckBoxTreeItem<String> getTreeItem(Path file, String basePath) {\n        if (Files.notExists(file))\n            return null;\n\n        boolean isDirectory = Files.isDirectory(file);\n\n        ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED;\n        if (basePath.length() > \"minecraft/\".length()) {\n            state = adviser.advise(StringUtils.substringAfter(basePath, \"minecraft/\") + (isDirectory ? \"/\" : \"\"), isDirectory);\n\n            String fileName = FileUtils.getName(file);\n\n            if (!isDirectory) {\n                switch (fileName) {\n                    case \".DS_Store\", // macOS system file\n                         \"desktop.ini\", \"Thumbs.db\" // Windows system files\n                            -> state = ModAdviser.ModSuggestion.HIDDEN;\n                }\n                if (fileName.startsWith(\"._\")) // macOS system file\n                    state = ModAdviser.ModSuggestion.HIDDEN;\n                if (FileUtils.getNameWithoutExtension(file).equals(version))\n                    state = ModAdviser.ModSuggestion.HIDDEN;\n            }\n\n            if (isDirectory && fileName.equals(version + \"-natives\")) // Ignore <version>-natives\n                state = ModAdviser.ModSuggestion.HIDDEN;\n            if (state == ModAdviser.ModSuggestion.HIDDEN)\n                return null;\n        }\n\n        CheckBoxTreeItem<String> node = new CheckBoxTreeItem<>(StringUtils.substringAfterLast(basePath, \"/\"));\n        if (state == ModAdviser.ModSuggestion.SUGGESTED)\n            node.setSelected(true);\n\n        if (isDirectory) {\n            try (var stream = Files.list(file)) {\n                stream.forEach(it -> {\n                    CheckBoxTreeItem<String> subNode = getTreeItem(it, basePath + \"/\" + FileUtils.getName(it));\n                    if (subNode != null) {\n                        node.setSelected(subNode.isSelected() || node.isSelected());\n                        if (!subNode.isSelected()) {\n                            node.setIndeterminate(true);\n                        }\n                        node.getChildren().add(subNode);\n                    }\n                });\n            } catch (IOException e) {\n                LOG.warning(\"Failed to list contents of \" + file, e);\n            }\n\n            if (!node.isSelected()) node.setIndeterminate(false);\n\n            // Empty folder need not to be displayed.\n            if (node.getChildren().isEmpty()) {\n                return null;\n            }\n        }\n\n        HBox graphic = new HBox();\n        JFXCheckBox checkBox = new JFXCheckBox();\n        checkBox.selectedProperty().bindBidirectional(node.selectedProperty());\n        checkBox.indeterminateProperty().bindBidirectional(node.indeterminateProperty());\n        graphic.getChildren().add(checkBox);\n\n        if (TRANSLATION.containsKey(basePath)) {\n            Label comment = new Label(TRANSLATION.get(basePath));\n            comment.setStyle(\"-fx-text-fill: -monet-on-surface-variant;\");\n            comment.setMouseTransparent(true);\n            graphic.getChildren().add(comment);\n        }\n        graphic.setPickOnBounds(false);\n        node.setExpanded(\"minecraft\".equals(basePath));\n        node.setGraphic(graphic);\n\n        return node;\n    }\n\n    private void getFilesNeeded(CheckBoxTreeItem<String> node, String basePath, List<String> list) {\n        if (node == null) return;\n        if (node.isSelected() || node.isIndeterminate()) {\n            if (basePath.length() > \"minecraft/\".length())\n                list.add(StringUtils.substringAfter(basePath, \"minecraft/\"));\n            for (TreeItem<String> child : node.getChildren()) {\n                if (child instanceof CheckBoxTreeItem) {\n                    getFilesNeeded(((CheckBoxTreeItem<String>) child), basePath + \"/\" + child.getValue(), list);\n                }\n            }\n        }\n    }\n\n    @Override\n    public void cleanup(SettingsMap settings) {\n        controller.getSettings().remove(MODPACK_FILE_SELECTION);\n    }\n\n    private void onNext() {\n        ArrayList<String> list = new ArrayList<>();\n        getFilesNeeded(rootNode, \"minecraft\", list);\n        controller.getSettings().put(MODPACK_FILE_SELECTION, list);\n        controller.onFinish();\n    }\n\n    @Override\n    public String getTitle() {\n        return i18n(\"modpack.wizard.step.2.title\");\n    }\n\n    public static final SettingsMap.Key<List<String>> MODPACK_FILE_SELECTION = new SettingsMap.Key<>(\"modpack.accepted\");\n    private static final Map<String, String> TRANSLATION = mapOf(\n            pair(\"minecraft/hmclversion.cfg\", i18n(\"modpack.files.hmclversion_cfg\")),\n            pair(\"minecraft/servers.dat\", i18n(\"modpack.files.servers_dat\")),\n            pair(\"minecraft/saves\", i18n(\"modpack.files.saves\")),\n            pair(\"minecraft/mods\", i18n(\"modpack.files.mods\")),\n            pair(\"minecraft/config\", i18n(\"modpack.files.config\")),\n            pair(\"minecraft/liteconfig\", i18n(\"modpack.files.liteconfig\")),\n            pair(\"minecraft/resourcepacks\", i18n(\"modpack.files.resourcepacks\")),\n            pair(\"minecraft/resources\", i18n(\"modpack.files.resourcepacks\")),\n            pair(\"minecraft/options.txt\", i18n(\"modpack.files.options_txt\")),\n            pair(\"minecraft/optionsshaders.txt\", i18n(\"modpack.files.optionsshaders_txt\")),\n            pair(\"minecraft/mods/VoxelMods\", i18n(\"modpack.files.mods.voxelmods\")),\n            pair(\"minecraft/dumps\", i18n(\"modpack.files.dumps\")),\n            pair(\"minecraft/blueprints\", i18n(\"modpack.files.blueprints\")),\n            pair(\"minecraft/scripts\", i18n(\"modpack.files.scripts\"))\n    );\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.export;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXSlider;\nimport com.jfoenix.controls.JFXTextArea;\nimport com.jfoenix.controls.JFXTextField;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.*;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.layout.*;\nimport javafx.stage.FileChooser;\n\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.mod.ModpackExportInfo;\nimport org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.setting.VersionSetting;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardPage;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemInfo;\n\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.ui.export.ModpackTypeSelectionPage.MODPACK_TYPE;\nimport static org.jackhuang.hmcl.ui.export.ModpackTypeSelectionPage.MODPACK_TYPE_MODRINTH;\nimport static org.jackhuang.hmcl.ui.export.ModpackTypeSelectionPage.MODPACK_TYPE_SERVER;\nimport static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class ModpackInfoPage extends Control implements WizardPage {\n    private final WizardController controller;\n    private final HMCLGameRepository gameRepository;\n    private final ModpackExportInfo.Options options;\n    private final String versionName;\n    private final boolean canIncludeLauncher;\n\n    private final ModpackExportInfo exportInfo = new ModpackExportInfo();\n\n    private final SimpleStringProperty name = new SimpleStringProperty(\"\");\n    private final SimpleStringProperty author = new SimpleStringProperty(\"\");\n    private final SimpleStringProperty version = new SimpleStringProperty(\"1.0\");\n    private final SimpleStringProperty description = new SimpleStringProperty(\"\");\n    private final SimpleStringProperty url = new SimpleStringProperty(\"\");\n    private final SimpleBooleanProperty forceUpdate = new SimpleBooleanProperty();\n    private final SimpleBooleanProperty packWithLauncher = new SimpleBooleanProperty();\n    private final SimpleStringProperty fileApi = new SimpleStringProperty(\"\");\n    private final SimpleIntegerProperty minMemory = new SimpleIntegerProperty(0);\n    private final SimpleStringProperty authlibInjectorServer = new SimpleStringProperty();\n    private final SimpleStringProperty launchArguments = new SimpleStringProperty(\"\");\n    private final SimpleStringProperty javaArguments = new SimpleStringProperty(\"\");\n    private final SimpleStringProperty mcbbsThreadId = new SimpleStringProperty(\"\");\n    private final SimpleBooleanProperty noCreateRemoteFiles = new SimpleBooleanProperty();\n    private final SimpleBooleanProperty skipCurseForgeRemoteFiles = new SimpleBooleanProperty();\n\n    public ModpackInfoPage(WizardController controller, HMCLGameRepository gameRepository, String version) {\n        this.controller = controller;\n        this.gameRepository = gameRepository;\n        this.options = controller.getSettings().get(MODPACK_INFO_OPTION);\n        this.versionName = version;\n\n        if (this.options == null)\n            throw new IllegalArgumentException(\"Settings.MODPACK_INFO_OPTION is required\");\n\n        name.set(version);\n        author.set(Optional.ofNullable(Accounts.getSelectedAccount()).map(Account::getUsername).orElse(\"\"));\n\n        VersionSetting versionSetting = gameRepository.getVersionSetting(versionName);\n        minMemory.set(Optional.ofNullable(versionSetting.getMinMemory()).orElse(0));\n        launchArguments.set(versionSetting.getMinecraftArgs());\n        javaArguments.set(versionSetting.getJavaArgs());\n\n        canIncludeLauncher = JarUtils.thisJarPath() != null;\n    }\n\n    private void onNext() {\n        FileChooser fileChooser = new FileChooser();\n        fileChooser.setTitle(i18n(\"modpack.wizard.step.initialization.save\"));\n        if (!packWithLauncher.get() && controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_MODRINTH) {\n            fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"modpack\"), \"*.mrpack\"));\n            fileChooser.setInitialFileName(name.get() + (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS ? \"\" : \".mrpack\"));\n        } else {\n            fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"modpack\"), \"*.zip\"));\n            fileChooser.setInitialFileName(name.get() + \".zip\");\n        }\n        Path file = FileUtils.toPath(fileChooser.showSaveDialog(Controllers.getStage()));\n        if (file == null) {\n            controller.onEnd();\n            return;\n        }\n\n        exportInfo.setName(name.get());\n        exportInfo.setFileApi(fileApi.get());\n        exportInfo.setVersion(version.get());\n        exportInfo.setAuthor(author.get());\n        exportInfo.setDescription(description.get());\n        exportInfo.setUrl(url.get());\n        exportInfo.setForceUpdate(forceUpdate.get());\n        exportInfo.setPackWithLauncher(packWithLauncher.get());\n        exportInfo.setMinMemory(minMemory.get());\n        exportInfo.setLaunchArguments(launchArguments.get());\n        exportInfo.setJavaArguments(javaArguments.get());\n        exportInfo.setAuthlibInjectorServer(authlibInjectorServer.get());\n        exportInfo.setNoCreateRemoteFiles(noCreateRemoteFiles.get());\n        exportInfo.setSkipCurseForgeRemoteFiles(skipCurseForgeRemoteFiles.get());\n\n        if (StringUtils.isNotBlank(mcbbsThreadId.get())) {\n            exportInfo.setOrigins(Collections.singletonList(new McbbsModpackManifest.Origin(\n                    \"mcbbs\", Integer.parseInt(mcbbsThreadId.get())\n            )));\n        }\n\n        controller.getSettings().put(MODPACK_INFO, exportInfo);\n        controller.getSettings().put(MODPACK_FILE, file);\n        controller.onNext();\n    }\n\n    @Override\n    public void cleanup(SettingsMap settings) {\n        controller.getSettings().remove(MODPACK_INFO);\n    }\n\n    @Override\n    public String getTitle() {\n        return i18n(\"modpack.wizard.step.1.title\");\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new ModpackInfoPageSkin(this);\n    }\n\n    public static final SettingsMap.Key<ModpackExportInfo> MODPACK_INFO = new SettingsMap.Key<>(\"modpack.info\");\n    public static final SettingsMap.Key<Path> MODPACK_FILE = new SettingsMap.Key<>(\"modpack.file\");\n    public static final SettingsMap.Key<ModpackExportInfo.Options> MODPACK_INFO_OPTION = new SettingsMap.Key<>(\"modpack.info.option\");\n\n    public static class ModpackInfoPageSkin extends SkinBase<ModpackInfoPage> {\n        private ObservableList<Node> originList;\n\n        private final List<JFXTextField> validatingFields = new ArrayList<>();\n\n        public ModpackInfoPageSkin(ModpackInfoPage skinnable) {\n            super(skinnable);\n\n            Insets componentListMargin = new Insets(16, 0, 16, 0);\n\n            ScrollPane scroll = new ScrollPane();\n            scroll.setFitToWidth(true);\n            scroll.setFitToHeight(true);\n            getChildren().setAll(scroll);\n\n\n            {\n                BorderPane borderPane = new BorderPane();\n                borderPane.setStyle(\"-fx-padding: 16;\");\n                scroll.setContent(borderPane);\n\n                if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_SERVER) {\n                    Hyperlink hyperlink = new Hyperlink(i18n(\"modpack.wizard.step.initialization.server\"));\n                    hyperlink.setOnAction(e -> FXUtils.openLink(Metadata.DOCS_URL + \"/modpack/serverpack.html\"));\n                    borderPane.setTop(hyperlink);\n                }\n                if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_MODRINTH) {\n                    HintPane pane = new HintPane(MessageDialogPane.MessageType.INFO);\n                    pane.setText(i18n(\"modpack.wizard.step.initialization.modrinth.info\"));\n                    borderPane.setTop(pane);\n                } else {\n                    HintPane pane = new HintPane(MessageDialogPane.MessageType.INFO);\n                    pane.setText(i18n(\"modpack.wizard.step.initialization.warning\"));\n                    borderPane.setTop(pane);\n                }\n\n                {\n                    ComponentList list = new ComponentList();\n                    BorderPane.setMargin(list, componentListMargin);\n                    borderPane.setCenter(list);\n\n                    var instanceNamePane = new LineTextPane();\n                    {\n                        instanceNamePane.setTitle(i18n(\"modpack.wizard.step.initialization.exported_version\"));\n                        instanceNamePane.setText(skinnable.versionName);\n\n                        list.getContent().add(instanceNamePane);\n                    }\n\n\n                    list.getContent().addAll(\n                            createTextFieldLinePane(i18n(\"modpack.name\"), skinnable.name, new RequiredValidator()),\n                            createTextFieldLinePane(i18n(\"archive.version\"), skinnable.version, new RequiredValidator())\n                    );\n\n                    if (skinnable.options.isRequireAuthor()) {\n                        list.getContent().add(\n                                createTextFieldLinePane(i18n(\"archive.author\"), skinnable.author, new RequiredValidator())\n                        );\n                    }\n\n                    if (skinnable.options.isRequireFileApi()) {\n                        list.getContent().add(createTextFieldLinePane(\n                                i18n(\"modpack.file_api\"), skinnable.fileApi,\n                                skinnable.options.isValidateFileApi() ? new RequiredValidator() : null,\n                                new URLValidator(true)\n                        ));\n                    }\n\n                    if (skinnable.options.isRequireLaunchArguments()) {\n                        list.getContent().add(createTextFieldLinePane(\n                                i18n(\"settings.advanced.minecraft_arguments\"), skinnable.launchArguments\n                        ));\n                    }\n\n                    if (skinnable.options.isRequireJavaArguments()) {\n                        list.getContent().add(createTextFieldLinePane(\n                                i18n(\"settings.advanced.jvm_args\"), skinnable.javaArguments\n                        ));\n                    }\n\n                    if (skinnable.options.isRequireUrl()) {\n                        list.getContent().add(createTextFieldLinePane(\n                                i18n(\"modpack.origin.url\"), skinnable.url\n                        ));\n                    }\n\n                    if (skinnable.options.isRequireOrigins()) {\n                        list.getContent().add(createTextFieldLinePane(\n                                i18n(\"modpack.origin.mcbbs\"), skinnable.mcbbsThreadId,\n                                new NumberValidator(i18n(\"input.number\"), true)\n                        ));\n                    }\n\n                    if (skinnable.options.isRequireMinMemory()) {\n                        VBox pane = new VBox();\n\n                        Label title = new Label(i18n(\"settings.memory\"));\n                        VBox.setMargin(title, new Insets(0, 0, 8, 0));\n\n                        HBox lowerBoundPane = new HBox(8);\n                        lowerBoundPane.setAlignment(Pos.CENTER);\n                        VBox.setMargin(lowerBoundPane, new Insets(0, 0, 0, 16));\n\n                        {\n                            Label label = new Label(i18n(\"settings.memory.lower_bound\"));\n\n                            JFXSlider slider = new JFXSlider(0, 1, 0);\n                            HBox.setMargin(slider, new Insets(0, 0, 0, 8));\n                            HBox.setHgrow(slider, Priority.ALWAYS);\n                            slider.setValueFactory(self -> Bindings.createStringBinding(() -> (int) (self.getValue() * 100) + \"%\", self.valueProperty()));\n                            AtomicBoolean changedByTextField = new AtomicBoolean(false);\n                            FXUtils.onChangeAndOperate(skinnable.minMemory, minMemory -> {\n                                changedByTextField.set(true);\n                                slider.setValue(minMemory.intValue() * 1.0 / MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize()));\n                                changedByTextField.set(false);\n                            });\n                            slider.valueProperty().addListener((value, oldVal, newVal) -> {\n                                if (changedByTextField.get()) return;\n                                skinnable.minMemory.set((int) (value.getValue().doubleValue() * MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize())));\n                            });\n\n                            JFXTextField txtMinMemory = new JFXTextField();\n                            FXUtils.bindInt(txtMinMemory, skinnable.minMemory);\n                            txtMinMemory.getValidators().add(new NumberValidator(i18n(\"input.number\"), false));\n                            FXUtils.setLimitWidth(txtMinMemory, 60);\n                            validatingFields.add(txtMinMemory);\n\n                            lowerBoundPane.getChildren().setAll(label, slider, txtMinMemory, new Label(\"MiB\"));\n                        }\n\n                        pane.getChildren().setAll(title, lowerBoundPane);\n                        list.getContent().add(pane);\n                    }\n\n                    {\n                        VBox pane = new VBox(8);\n                        JFXTextArea area = new JFXTextArea();\n                        area.textProperty().bindBidirectional(skinnable.description);\n                        area.setMinHeight(400);\n                        pane.getChildren().setAll(new Label(i18n(\"modpack.desc\")), area);\n                        list.getContent().add(pane);\n                    }\n\n                    if (skinnable.options.isRequireAuthlibInjectorServer()) {\n                        var serversSelectButton = new LineSelectButton<AuthlibInjectorServer>();\n                        serversSelectButton.setTitle(i18n(\"account.injector.server\"));\n                        serversSelectButton.setConverter(AuthlibInjectorServer::getName);\n                        serversSelectButton.setDescriptionConverter(AuthlibInjectorServer::getUrl);\n                        serversSelectButton.itemsProperty().set(config().getAuthlibInjectorServers());\n\n                        skinnable.authlibInjectorServer.bind(Bindings.createStringBinding(() -> {\n                            AuthlibInjectorServer selected = serversSelectButton.getValue();\n                            return selected != null ? selected.getUrl() : null;\n                        }, serversSelectButton.valueProperty()));\n\n                        list.getContent().add(serversSelectButton);\n                    }\n\n                    if (skinnable.options.isRequireForceUpdate()) {\n                        var requireForceUpdateButton = new LineToggleButton();\n                        requireForceUpdateButton.setTitle(i18n(\"modpack.wizard.step.initialization.force_update\"));\n                        requireForceUpdateButton.selectedProperty().bindBidirectional(skinnable.forceUpdate);\n\n                        list.getContent().add(requireForceUpdateButton);\n                    }\n\n                    {\n                        var canIncludeLauncherButton = new LineToggleButton();\n                        canIncludeLauncherButton.setTitle(i18n(\"modpack.wizard.step.initialization.include_launcher\"));\n                        canIncludeLauncherButton.setDisable(!skinnable.canIncludeLauncher);\n                        canIncludeLauncherButton.selectedProperty().bindBidirectional(skinnable.packWithLauncher);\n\n                        list.getContent().add(canIncludeLauncherButton);\n                    }\n\n                    if (skinnable.options.isRequireNoCreateRemoteFiles()) {\n                        var requireNoCreateRemoteFilesButton = new LineToggleButton();\n                        requireNoCreateRemoteFilesButton.setTitle(i18n(\"modpack.wizard.step.initialization.no_create_remote_files\"));\n                        requireNoCreateRemoteFilesButton.selectedProperty().bindBidirectional(skinnable.noCreateRemoteFiles);\n\n                        list.getContent().add(requireNoCreateRemoteFilesButton);\n                    }\n\n                    if (skinnable.options.isRequireSkipCurseForgeRemoteFiles()) {\n                        var skipCurseForgeRemoteFilesButton = new LineToggleButton();\n                        skipCurseForgeRemoteFilesButton.setTitle(i18n(\"modpack.wizard.step.initialization.skip_curseforge_remote_files\"));\n                        skipCurseForgeRemoteFilesButton.selectedProperty().bindBidirectional(skinnable.skipCurseForgeRemoteFiles);\n\n                        list.getContent().add(skipCurseForgeRemoteFilesButton);\n                    }\n                }\n\n                {\n                    HBox hbox = new HBox();\n                    hbox.setAlignment(Pos.CENTER_RIGHT);\n                    borderPane.setBottom(hbox);\n\n                    JFXButton nextButton = FXUtils.newRaisedButton(i18n(\"wizard.next\"));\n                    nextButton.setOnAction(e -> skinnable.onNext());\n                    nextButton.setPrefWidth(100);\n                    nextButton.setPrefHeight(40);\n                    nextButton.disableProperty().bind(\n                            // Disable nextButton if any text of JFXTextFields in validatingFields does not fulfill\n                            // our requirement.\n                            Bindings.createBooleanBinding(() -> validatingFields.stream()\n                                            .map(field -> !field.validate())\n                                            .reduce(false, (left, right) -> left || right),\n                                    validatingFields.stream().map(JFXTextField::textProperty).toArray(StringProperty[]::new)));\n                    hbox.getChildren().add(nextButton);\n                }\n            }\n\n            FXUtils.smoothScrolling(scroll);\n        }\n\n        private LinePane createTextFieldLinePane(String title, StringProperty property, ValidatorBase... validators) {\n            LinePane linePane = new LinePane();\n            JFXTextField textField = new JFXTextField();\n            textField.setMinWidth(500);\n\n            linePane.setTitle(title);\n            linePane.setRight(textField);\n            textField.textProperty().bindBidirectional(property);\n\n            boolean needValidation = false;\n            if (validators != null) {\n                for (ValidatorBase validator : validators) {\n                    if (validator != null) {\n                        needValidation = true;\n                        textField.getValidators().add(validator);\n                    }\n                }\n            }\n            if (needValidation) {\n                FXUtils.setValidateWhileTextChanged(textField, true);\n                validatingFields.add(textField);\n            }\n\n            return linePane;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackTypeSelectionPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.export;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.mod.ModpackExportInfo;\nimport org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;\nimport org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;\nimport org.jackhuang.hmcl.mod.server.ServerModpackExportTask;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthModpackExportTask;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.TwoLineListItem;\nimport org.jackhuang.hmcl.ui.wizard.WizardController;\nimport org.jackhuang.hmcl.ui.wizard.WizardPage;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\nimport static org.jackhuang.hmcl.ui.export.ModpackInfoPage.MODPACK_INFO_OPTION;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class ModpackTypeSelectionPage extends VBox implements WizardPage {\n    private final WizardController controller;\n\n    public ModpackTypeSelectionPage(WizardController controller) {\n        this.controller = controller;\n        this.setPadding(new Insets(10));\n\n        Label title = new Label(i18n(\"modpack.export.as\"));\n        VBox.setMargin(title, new Insets(8, 0, 8, 12));\n\n        this.getStyleClass().add(\"jfx-list-view\");\n        this.setMaxSize(400, 150);\n        this.setSpacing(8);\n\n        this.getChildren().setAll(\n                title,\n                createButton(MODPACK_TYPE_MCBBS, McbbsModpackExportTask.OPTION),\n                createButton(MODPACK_TYPE_MULTIMC, MultiMCModpackExportTask.OPTION),\n                createButton(MODPACK_TYPE_SERVER, ServerModpackExportTask.OPTION),\n                createButton(MODPACK_TYPE_MODRINTH, ModrinthModpackExportTask.OPTION)\n        );\n    }\n\n    private JFXButton createButton(String type, ModpackExportInfo.Options option) {\n        JFXButton button = new JFXButton();\n\n        button.getStyleClass().add(\"card\");\n        button.setOnAction(e -> {\n            controller.getSettings().put(MODPACK_TYPE, type);\n            controller.getSettings().put(MODPACK_INFO_OPTION, option);\n            controller.onNext();\n        });\n\n        button.prefWidthProperty().bind(this.widthProperty());\n\n        BorderPane graphic = new BorderPane();\n        graphic.setMouseTransparent(true);\n        graphic.setLeft(new TwoLineListItem(i18n(\"modpack.type.\" + type), i18n(\"modpack.type.\" + type + \".export\")));\n\n        Node arrow = SVG.ARROW_FORWARD.createIcon();\n        BorderPane.setAlignment(arrow, Pos.CENTER);\n        graphic.setRight(arrow);\n\n        button.setGraphic(graphic);\n\n        return button;\n    }\n\n    @Override\n    public void cleanup(SettingsMap settings) {\n    }\n\n    @Override\n    public String getTitle() {\n        return i18n(\"modpack.wizard.step.3.title\");\n    }\n\n    public static final SettingsMap.Key<String> MODPACK_TYPE = new SettingsMap.Key<>(\"modpack.type\");\n\n    public static final String MODPACK_TYPE_MCBBS = \"mcbbs\";\n    public static final String MODPACK_TYPE_MULTIMC = \"multimc\";\n    public static final String MODPACK_TYPE_SERVER = \"server\";\n    public static final String MODPACK_TYPE_MODRINTH = \"modrinth\";\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationImage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.image;\n\n/**\n * @author Glavo\n */\npublic interface AnimationImage {\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageLoader.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.image;\n\nimport javafx.scene.image.Image;\n\nimport java.io.InputStream;\n\npublic interface ImageLoader {\n    Image load(InputStream input,\n               int requestedWidth, int requestedHeight,\n               boolean preserveRatio, boolean smooth) throws Exception;\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.image;\n\nimport com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;\nimport javafx.animation.Timeline;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.PixelFormat;\nimport javafx.scene.image.WritableImage;\nimport org.jackhuang.hmcl.ui.image.apng.Png;\nimport org.jackhuang.hmcl.ui.image.apng.argb8888.Argb8888Bitmap;\nimport org.jackhuang.hmcl.ui.image.apng.argb8888.Argb8888BitmapSequence;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;\nimport org.jackhuang.hmcl.ui.image.internal.AnimationImageImpl;\nimport org.jackhuang.hmcl.util.SwingFXUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport javax.imageio.ImageIO;\nimport javax.imageio.ImageReader;\nimport javax.imageio.stream.ImageInputStream;\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.util.*;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class ImageUtils {\n\n    // ImageLoaders\n\n    public static final ImageLoader DEFAULT = (input, requestedWidth, requestedHeight, preserveRatio, smooth) -> {\n        Image image = new Image(input,\n                requestedWidth, requestedHeight,\n                preserveRatio, smooth);\n        if (image.isError())\n            throw image.getException();\n        return image;\n    };\n\n    public static final ImageLoader WEBP = (input, requestedWidth, requestedHeight, preserveRatio, smooth) -> {\n        WebPImageReaderSpi spi = new WebPImageReaderSpi();\n        ImageReader reader = spi.createReaderInstance(null);\n        BufferedImage bufferedImage;\n        try (ImageInputStream imageInput = ImageIO.createImageInputStream(input)) {\n            reader.setInput(imageInput, true, true);\n            bufferedImage = reader.read(0, reader.getDefaultReadParam());\n        } finally {\n            reader.dispose();\n        }\n        return SwingFXUtils.toFXImage(bufferedImage, requestedWidth, requestedHeight, preserveRatio, smooth);\n    };\n\n    public static final ImageLoader APNG = (input, requestedWidth, requestedHeight, preserveRatio, smooth) -> {\n        if (!\"true\".equals(System.getProperty(\"hmcl.experimental.apng\", \"true\")))\n            return DEFAULT.load(input, requestedWidth, requestedHeight, preserveRatio, smooth);\n\n        try {\n            var sequence = Png.readArgb8888BitmapSequence(input);\n\n            final int width = sequence.header.width;\n            final int height = sequence.header.height;\n\n            boolean doScale;\n            if (requestedWidth > 0 && requestedHeight > 0\n                    && (requestedWidth != width || requestedHeight != height)) {\n                doScale = true;\n\n                if (preserveRatio) {\n                    double scaleX = (double) requestedWidth / width;\n                    double scaleY = (double) requestedHeight / height;\n                    double scale = Math.min(scaleX, scaleY);\n\n                    requestedWidth = (int) (width * scale);\n                    requestedHeight = (int) (height * scale);\n                }\n            } else {\n                doScale = false;\n            }\n\n            if (sequence.isAnimated()) {\n                try {\n                    return toImage(sequence, doScale, requestedWidth, requestedHeight);\n                } catch (Throwable e) {\n                    LOG.warning(\"Failed to load animated image\", e);\n                }\n            }\n\n            Argb8888Bitmap defaultImage = sequence.defaultImage;\n            int targetWidth;\n            int targetHeight;\n            int[] pixels;\n            if (doScale) {\n                targetWidth = requestedWidth;\n                targetHeight = requestedHeight;\n                pixels = scale(defaultImage.array,\n                        defaultImage.width, defaultImage.height,\n                        targetWidth, targetHeight);\n            } else {\n                targetWidth = defaultImage.width;\n                targetHeight = defaultImage.height;\n                pixels = defaultImage.array;\n            }\n\n            WritableImage image = new WritableImage(targetWidth, targetHeight);\n            image.getPixelWriter().setPixels(0, 0, targetWidth, targetHeight,\n                    PixelFormat.getIntArgbInstance(), pixels,\n                    0, targetWidth);\n            return image;\n        } catch (PngException e) {\n            throw new IOException(e);\n        }\n    };\n\n    public static final Map<String, ImageLoader> EXT_TO_LOADER = Map.of(\n            \"webp\", WEBP,\n            \"apng\", APNG\n    );\n\n    public static final Map<String, ImageLoader> CONTENT_TYPE_TO_LOADER = Map.of(\n            \"image/webp\", WEBP,\n            \"image/apng\", APNG\n    );\n\n    public static final Set<String> DEFAULT_EXTS = Set.of(\n            \"jpg\", \"jpeg\", \"bmp\", \"gif\"\n    );\n\n    public static final Set<String> DEFAULT_CONTENT_TYPES = Set.of(\n            \"image/jpeg\", \"image/bmp\", \"image/gif\"\n    );\n\n    // ------\n\n    public static final int HEADER_BUFFER_SIZE = 1024;\n\n    private static final byte[] RIFF_HEADER = {'R', 'I', 'F', 'F'};\n    private static final byte[] WEBP_HEADER = {'W', 'E', 'B', 'P'};\n\n    public static boolean isWebP(byte[] headerBuffer) {\n        return headerBuffer.length > 12\n                && Arrays.equals(headerBuffer, 0, 4, RIFF_HEADER, 0, 4)\n                && Arrays.equals(headerBuffer, 8, 12, WEBP_HEADER, 0, 4);\n    }\n\n    private static final byte[] PNG_HEADER = {\n            (byte) 0x89, (byte) 0x50, (byte) 0x4e, (byte) 0x47,\n            (byte) 0x0d, (byte) 0x0a, (byte) 0x1a, (byte) 0x0a,\n    };\n\n    private static final class PngChunkHeader {\n        private static final int IDAT_HEADER = 0x49444154;\n        private static final int acTL_HEADER = 0x6163544c;\n\n        private final int length;\n        private final int chunkType;\n\n        private PngChunkHeader(int length, int chunkType) {\n            this.length = length;\n            this.chunkType = chunkType;\n        }\n\n        private static @Nullable PngChunkHeader readHeader(ByteBuffer headerBuffer) {\n            if (headerBuffer.remaining() < 8)\n                return null;\n\n            int length = headerBuffer.getInt();\n            int chunkType = headerBuffer.getInt();\n\n            return new PngChunkHeader(length, chunkType);\n        }\n    }\n\n    public static boolean isApng(byte[] headerBuffer) {\n        if (headerBuffer.length <= 20)\n            return false;\n\n        if (!Arrays.equals(\n                headerBuffer, 0, 8,\n                PNG_HEADER, 0, 8))\n            return false;\n\n\n        ByteBuffer buffer = ByteBuffer.wrap(headerBuffer, 8, headerBuffer.length - 8);\n\n        PngChunkHeader header;\n        while ((header = PngChunkHeader.readHeader(buffer)) != null) {\n            // https://wiki.mozilla.org/APNG_Specification#Structure\n            // To be recognized as an APNG, an `acTL` chunk must appear in the stream before any `IDAT` chunks.\n            // The `acTL` structure is described below.\n            if (header.chunkType == PngChunkHeader.IDAT_HEADER)\n                break;\n\n            if (header.chunkType == PngChunkHeader.acTL_HEADER)\n                return true;\n\n            final int numBytes = header.length + 4;\n\n            if (buffer.remaining() > numBytes)\n                buffer.position(buffer.position() + numBytes);\n            else\n                break;\n        }\n\n        return false;\n    }\n\n    public static @Nullable ImageLoader guessLoader(byte[] headerBuffer) {\n        if (isWebP(headerBuffer))\n            return WEBP;\n        if (isApng(headerBuffer))\n            return APNG;\n        return null;\n    }\n\n    public static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(\"^\\\\s(?<type>image/[\\\\w-])\");\n\n    // APNG\n\n    private static int[] scale(int[] pixels,\n                               int sourceWidth, int sourceHeight,\n                               int targetWidth, int targetHeight) {\n        assert pixels.length == sourceWidth * sourceHeight;\n\n        double xScale = ((double) sourceWidth) / targetWidth;\n        double yScale = ((double) sourceHeight) / targetHeight;\n\n        int[] result = new int[targetWidth * targetHeight];\n\n        for (int row = 0; row < targetHeight; row++) {\n            for (int col = 0; col < targetWidth; col++) {\n                int sourceX = (int) (col * xScale);\n                int sourceY = (int) (row * yScale);\n                int color = pixels[sourceY * sourceWidth + sourceX];\n\n                result[row * targetWidth + col] = color;\n            }\n        }\n\n        return result;\n    }\n\n    private static Image toImage(Argb8888BitmapSequence sequence,\n                                 boolean doScale,\n                                 int targetWidth, int targetHeight) throws PngException {\n        final int width = sequence.header.width;\n        final int height = sequence.header.height;\n\n        List<Argb8888BitmapSequence.Frame> frames = sequence.getAnimationFrames();\n\n        var framePixels = new int[frames.size()][];\n        var durations = new int[framePixels.length];\n\n        int[] buffer = new int[Math.multiplyExact(width, height)];\n        for (int frameIndex = 0; frameIndex < frames.size(); frameIndex++) {\n            var frame = frames.get(frameIndex);\n            PngFrameControl control = frame.control;\n\n            if (frameIndex == 0 && (\n                    control.xOffset != 0 || control.yOffset != 0\n                            || control.width != width || control.height != height)) {\n                throw new PngIntegrityException(\"Invalid first frame: \" + control);\n            }\n\n            if (control.xOffset < 0 || control.yOffset < 0\n                    || width < 0 || height < 0\n                    || control.xOffset + control.width > width\n                    || control.yOffset + control.height > height\n                    || control.delayNumerator < 0 || control.delayDenominator < 0\n            ) {\n                throw new PngIntegrityException(\"Invalid frame control: \" + control);\n            }\n\n            int[] currentFrameBuffer = buffer.clone();\n            if (control.blendOp == 0) {\n                for (int row = 0; row < control.height; row++) {\n                    System.arraycopy(frame.bitmap.array,\n                            row * control.width,\n                            currentFrameBuffer,\n                            (control.yOffset + row) * width + control.xOffset,\n                            control.width);\n                }\n            } else if (control.blendOp == 1) {\n                // APNG_BLEND_OP_OVER - Alpha blending\n                for (int row = 0; row < control.height; row++) {\n                    for (int col = 0; col < control.width; col++) {\n                        int srcIndex = row * control.width + col;\n                        int dstIndex = (control.yOffset + row) * width + control.xOffset + col;\n\n                        int srcPixel = frame.bitmap.array[srcIndex];\n                        int dstPixel = currentFrameBuffer[dstIndex];\n\n                        int srcAlpha = (srcPixel >>> 24) & 0xFF;\n                        if (srcAlpha == 0) {\n                            continue;\n                        } else if (srcAlpha == 255) {\n                            currentFrameBuffer[dstIndex] = srcPixel;\n                        } else {\n                            int srcR = (srcPixel >>> 16) & 0xFF;\n                            int srcG = (srcPixel >>> 8) & 0xFF;\n                            int srcB = srcPixel & 0xFF;\n\n                            int dstAlpha = (dstPixel >>> 24) & 0xFF;\n                            int dstR = (dstPixel >>> 16) & 0xFF;\n                            int dstG = (dstPixel >>> 8) & 0xFF;\n                            int dstB = dstPixel & 0xFF;\n\n                            int invSrcAlpha = 255 - srcAlpha;\n\n                            int outAlpha = srcAlpha + (dstAlpha * invSrcAlpha + 127) / 255;\n                            int outR, outG, outB;\n\n                            if (outAlpha == 0) {\n                                outR = outG = outB = 0;\n                            } else {\n                                outR = (srcR * srcAlpha + dstR * dstAlpha * invSrcAlpha / 255 + outAlpha / 2) / outAlpha;\n                                outG = (srcG * srcAlpha + dstG * dstAlpha * invSrcAlpha / 255 + outAlpha / 2) / outAlpha;\n                                outB = (srcB * srcAlpha + dstB * dstAlpha * invSrcAlpha / 255 + outAlpha / 2) / outAlpha;\n                            }\n\n                            outAlpha = Math.min(outAlpha, 255);\n                            outR = Math.min(outR, 255);\n                            outG = Math.min(outG, 255);\n                            outB = Math.min(outB, 255);\n\n                            currentFrameBuffer[dstIndex] = (outAlpha << 24) | (outR << 16) | (outG << 8) | outB;\n                        }\n                    }\n                }\n            } else {\n                throw new PngIntegrityException(\"Unsupported blendOp \" + control.blendOp + \" at frame \" + frameIndex);\n            }\n\n            if (doScale)\n                framePixels[frameIndex] = scale(currentFrameBuffer,\n                        width, height,\n                        targetWidth, targetHeight);\n            else\n                framePixels[frameIndex] = currentFrameBuffer;\n\n            if (control.delayNumerator == 0) {\n                durations[frameIndex] = 10;\n            } else {\n                int durationsMills = 1000 * control.delayNumerator;\n                if (control.delayDenominator == 0)\n                    durationsMills /= 100;\n                else\n                    durationsMills /= control.delayDenominator;\n\n                durations[frameIndex] = durationsMills;\n            }\n\n            switch (control.disposeOp) {\n                case 0:  // APNG_DISPOST_OP_NONE\n                    System.arraycopy(currentFrameBuffer, 0, buffer, 0, currentFrameBuffer.length);\n                    break;\n                case 1: // APNG_DISPOSE_OP_BACKGROUND\n                    for (int row = 0; row < control.height; row++) {\n                        int fromIndex = (control.yOffset + row) * width + control.xOffset;\n                        Arrays.fill(buffer, fromIndex, fromIndex + control.width, 0);\n                    }\n                    break;\n                case 2: // APNG_DISPOSE_OP_PREVIOUS\n                    // Do nothing, keep the previous frame.\n                    break;\n                default:\n                    throw new PngIntegrityException(\"Unsupported disposeOp \" + control.disposeOp + \" at frame \" + frameIndex);\n            }\n        }\n\n        PngAnimationControl animationControl = sequence.getAnimationControl();\n        int cycleCount;\n        if (animationControl != null) {\n            cycleCount = animationControl.numPlays;\n            if (cycleCount == 0)\n                cycleCount = Timeline.INDEFINITE;\n        } else {\n            cycleCount = Timeline.INDEFINITE;\n        }\n\n        if (doScale)\n            return new AnimationImageImpl(targetWidth, targetHeight, framePixels, durations, cycleCount);\n        else\n            return new AnimationImageImpl(width, height, framePixels, durations, cycleCount);\n    }\n\n    private ImageUtils() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/Png.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng;\n\nimport org.jackhuang.hmcl.ui.image.apng.argb8888.*;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.map.PngMap;\nimport org.jackhuang.hmcl.ui.image.apng.map.PngMapReader;\nimport org.jackhuang.hmcl.ui.image.apng.reader.DefaultPngChunkReader;\nimport org.jackhuang.hmcl.ui.image.apng.reader.PngChunkProcessor;\nimport org.jackhuang.hmcl.ui.image.apng.reader.PngReadHelper;\nimport org.jackhuang.hmcl.ui.image.apng.reader.PngReader;\nimport org.jackhuang.hmcl.ui.image.apng.util.PngContainer;\nimport org.jackhuang.hmcl.ui.image.apng.util.PngContainerBuilder;\n\nimport java.io.InputStream;\n\n/**\n * Convenient one liners for loading PNG images.\n */\npublic class Png {\n\n    /**\n     * Read the provided stream and produce a PngMap of the data.\n     *\n     * @param is         stream to read from\n     * @param sourceName optional name, mainly for debugging.\n     * @return PngMap of the data.\n     * @throws PngException\n     */\n    public static PngMap readMap(InputStream is, String sourceName) throws PngException {\n        return PngReadHelper.read(is, new PngMapReader(sourceName));\n    }\n\n    public static PngContainer readContainer(InputStream is) throws PngException {\n        return PngReadHelper.read(is, new DefaultPngChunkReader<>(new PngContainerBuilder()));\n    }\n\n    public static <ResultT> ResultT read(InputStream is, PngReader<ResultT> reader) throws PngException {\n        return PngReadHelper.read(is, reader);\n    }\n\n    public static <ResultT> ResultT read(InputStream is, PngChunkProcessor<ResultT> processor) throws PngException {\n        return PngReadHelper.read(is, new DefaultPngChunkReader<>(processor));\n    }\n\n    public static Argb8888Bitmap readArgb8888Bitmap(InputStream is) throws PngException {\n        Argb8888Processor<Argb8888Bitmap> processor = new Argb8888Processor<>(new DefaultImageArgb8888Director());\n        return PngReadHelper.read(is, new DefaultPngChunkReader<>(processor));\n    }\n\n    public static Argb8888BitmapSequence readArgb8888BitmapSequence(InputStream is) throws PngException {\n        Argb8888Processor<Argb8888BitmapSequence> processor = new Argb8888Processor<>(new Argb8888BitmapSequenceDirector());\n        return PngReadHelper.read(is, new DefaultPngChunkReader<>(processor));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/PngAnimationType.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng;\n\n/**\n * A PNG file has an animation type - most commonly, not animated.\n * <p>\n * This is primarily relevant to tracking whether the default image in a PNG\n * is to be\n * a) processed as normal (e.g. in a standard or \"non-animated\" PNG file);\n * b) processed and used as the first frame in an animated PNG; or\n * c) not processed and discarded (e.g. in an animated PNG which does not use the first frame).\n */\npublic enum PngAnimationType {\n    NOT_ANIMATED,\n    ANIMATED_KEEP_DEFAULT_IMAGE,\n    ANIMATED_DISCARD_DEFAULT_IMAGE;\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/PngChunkCode.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng;\n\n/**\n * One four-letter (32-byte) code identifying the type of a PNG chunk.\n * <p>\n * Common chunk codes are defined as constants that can be used in a switch statement.\n * Users can add their own chunk codes separately.\n */\npublic class PngChunkCode {\n    public static final PngChunkCode IHDR = new PngChunkCode(PngConstants.IHDR_VALUE, \"IHDR\");\n    public static final PngChunkCode PLTE = new PngChunkCode(PngConstants.PLTE_VALUE, \"PLTE\");\n    public static final PngChunkCode IDAT = new PngChunkCode(PngConstants.IDAT_VALUE, \"IDAT\");\n    public static final PngChunkCode IEND = new PngChunkCode(PngConstants.IEND_VALUE, \"IEND\");\n    public static final PngChunkCode gAMA = new PngChunkCode(PngConstants.gAMA_VALUE, \"gAMA\");\n    public static final PngChunkCode bKGD = new PngChunkCode(PngConstants.bKGD_VALUE, \"bKGD\");\n    public static final PngChunkCode tRNS = new PngChunkCode(PngConstants.tRNS_VALUE, \"tRNS\");\n    public static final PngChunkCode acTL = new PngChunkCode(PngConstants.acTL_VALUE, \"acTL\");\n    public static final PngChunkCode fcTL = new PngChunkCode(PngConstants.fcTL_VALUE, \"fcTL\");\n    public static final PngChunkCode fdAT = new PngChunkCode(PngConstants.fdAT_VALUE, \"fdAT\");\n\n    public final int numeric;\n    public final String letters;\n\n    PngChunkCode(int numeric, String letters) {\n        this.numeric = numeric;\n        this.letters = letters;\n    }\n\n    /**\n     * Find out if the chunk is \"critical\" as defined by http://www.w3.org/TR/PNG/#5Chunk-naming-conventions\n     *\n     * <pre>\n     *     cHNk  <-- 32 bit chunk type represented in text form\n     *     ||||\n     *     |||+- Safe-to-copy bit is 1 (lower case letter; bit 5 is 1)\n     *     ||+-- Reserved bit is 0     (upper case letter; bit 5 is 0)\n     *     |+--- Private bit is 0      (upper case letter; bit 5 is 0)\n     *     +---- Ancillary bit is 1    (lower case letter; bit 5 is 1)\n     * </pre>\n     *\n     * @return true if this chunk is considered critical according to the PNG spec.\n     */\n    public boolean isCritical() {\n        // Ancillary bit: bit 5 o first byte\n        return (numeric & PngConstants.BIT_CHUNK_IS_ANCILLARY) == 0;\n    }\n\n    public boolean isAncillary() {\n        // Ancillary bit: bit 5 of first byte\n        return (numeric & PngConstants.BIT_CHUNK_IS_ANCILLARY) > 0;\n    }\n\n    public boolean isPrivate() {\n        return (numeric & PngConstants.BIT_CHUNK_IS_PRIVATE) > 0;\n    }\n\n    public boolean isPublic() {\n        return (numeric & PngConstants.BIT_CHUNK_IS_PRIVATE) == 0;\n    }\n\n    public boolean isReserved() {\n        return (numeric & PngConstants.BIT_CHUNK_IS_RESERVED) > 0;\n    }\n\n    public boolean isSafeToCopy() {\n        return (numeric & PngConstants.BIT_CHUNK_IS_SAFE_TO_COPY) > 0;\n    }\n\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        PngChunkCode that = (PngChunkCode) o;\n\n        if (numeric != that.numeric) return false;\n        return !(letters != null ? !letters.equals(that.letters) : that.letters != null);\n    }\n\n    @Override\n    public int hashCode() {\n        int result = numeric;\n        result = 31 * result + (letters != null ? letters.hashCode() : 0);\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return letters + \"(\" + numeric + \")\";\n    }\n\n    public static PngChunkCode from(int code) {\n        switch (code) {\n            case PngConstants.IHDR_VALUE:\n                return IHDR;\n            case PngConstants.gAMA_VALUE:\n                return gAMA;\n            case PngConstants.bKGD_VALUE:\n                return bKGD;\n            case PngConstants.tRNS_VALUE:\n                return tRNS;\n            case PngConstants.IDAT_VALUE:\n                return IDAT;\n            case PngConstants.IEND_VALUE:\n                return IEND;\n            case PngConstants.acTL_VALUE:\n                return acTL;\n            case PngConstants.fdAT_VALUE:\n                return fdAT;\n            default:\n                byte[] s = new byte[4];\n                s[0] = (byte) ((code & 0xff000000) >> 24);\n                s[1] = (byte) ((code & 0x00ff0000) >> 16);\n                s[2] = (byte) ((code & 0x0000ff00) >> 8);\n                s[3] = (byte) ((code & 0x000000ff));\n                return new PngChunkCode(code, new String(s));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/PngColourType.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng;\n\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;\n\n/**\n * PNG images support 5 colour types as defined at http://www.w3.org/TR/PNG/#11IHDR\n */\npublic enum PngColourType {\n    PNG_GREYSCALE(0, 1, \"1, 2, 4, 8, 16\", \"Greyscale\", \"Each pixel is a greyscale sample\"),\n    PNG_TRUECOLOUR(2, 3, \"8, 16\", \"Truecolour\", \"Each pixel is an R,G,B triple\"),\n    PNG_INDEXED_COLOUR(3, 1, \"1, 2, 4, 8\", \"Indexed-colour\", \"Each pixel is a palette index; a PLTE chunk shall appear.\"),\n    PNG_GREYSCALE_WITH_ALPHA(4, 2, \"4, 8, 16\", \"Greyscale with alpha\", \"Each pixel is a greyscale sample followed by an alpha sample.\"),\n    PNG_TRUECOLOUR_WITH_ALPHA(6, 4, \"8, 16\", \"Truecolour with alpha\", \"Each pixel is an R,G,B triple followed by an alpha sample.\");\n\n    public final int code;\n    public final int componentsPerPixel;\n    public final String allowedBitDepths;\n    public final String name;\n    public final String descriptino;\n\n    PngColourType(int code, int componentsPerPixel, String allowedBitDepths, String name, String descriptino) {\n        this.code = code;\n        this.componentsPerPixel = componentsPerPixel;\n        this.allowedBitDepths = allowedBitDepths;\n        this.name = name;\n        this.descriptino = descriptino;\n    }\n\n    public boolean isIndexed() {\n        return (code & 0x01) > 0;\n    }\n\n    public boolean hasAlpha() {\n        return (code & 0x04) > 0;\n    }\n\n    public boolean supportsSubByteDepth() {\n        return code == 0 || code == 3;\n    }\n\n    public static PngColourType fromByte(byte b) throws PngException {\n        switch (b) {\n            case 0:\n                return PNG_GREYSCALE;\n            case 2:\n                return PNG_TRUECOLOUR;\n            case 3:\n                return PNG_INDEXED_COLOUR;\n            case 4:\n                return PNG_GREYSCALE_WITH_ALPHA;\n            case 6:\n                return PNG_TRUECOLOUR_WITH_ALPHA;\n            default:\n                throw new PngIntegrityException(String.format(\"Valid PNG colour types are 0, 2, 3, 4, 6. Type '%d' is invalid\", b));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/PngConstants.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng;\n\n\npublic class PngConstants {\n\n    public static final int LENGTH_SIGNATURE = 8;\n    public static final int LENGTH_fcTL_CHUNK = 4 + 4 + 4 + 4 + 4 + 2 + 2 + 1 + 1;\n    public static final int LENGTH_acTL_CHUNK = 4 + 4;\n\n    public static final byte[] BYTES_SIGNATURE = new byte[]{\n            (byte) 0x89, 'P', 'N', 'G', (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A\n    };\n    public static final int IHDR_VALUE = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R'; // 1229472850;\n    public static final int PLTE_VALUE = 'P' << 24 | 'L' << 16 | 'T' << 8 | 'E';\n    public static final int IDAT_VALUE = 'I' << 24 | 'D' << 16 | 'A' << 8 | 'T'; // 1229209940;\n    public static final int IEND_VALUE = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D'; // 1229278788;\n    public static final int gAMA_VALUE = 'g' << 24 | 'A' << 16 | 'M' << 8 | 'A'; // 1732332865;\n    public static final int bKGD_VALUE = 'b' << 24 | 'K' << 16 | 'G' << 8 | 'D'; // 1649100612;\n    public static final int tRNS_VALUE = 't' << 24 | 'R' << 16 | 'N' << 8 | 'S';\n    public static final int acTL_VALUE = 'a' << 24 | 'c' << 16 | 'T' << 8 | 'L';\n    public static final int fcTL_VALUE = 'f' << 24 | 'c' << 16 | 'T' << 8 | 'L';\n    public static final int fdAT_VALUE = 'f' << 24 | 'd' << 16 | 'A' << 8 | 'T';\n\n    public static final int ERROR_NOT_PNG = 1;\n    public static final int ERROR_EOF = 2;\n    public static final int ERROR_EOF_EXPECTED = 3;\n    public static final int ERROR_UNKNOWN_IO_FAILURE = 4;\n    public static final int ERROR_FEATURE_NOT_SUPPORTED = 5;\n    public static final int ERROR_INTEGRITY = 6;\n\n    public static final int ONE_K = 1 << 10;\n    public static final int DEFAULT_TARGET_BUFFER_SIZE = 32 * ONE_K;\n\n    public static final byte[] GREY_PALETTE_16 = new byte[]{\n            (byte) 0x00, //  0\n            (byte) 0x11, //  1\n            (byte) 0x22, //  2\n            (byte) 0x33, //  3\n            (byte) 0x44, //  4\n            (byte) 0x55, //  5\n            (byte) 0x66, //  6\n            (byte) 0x77, //  7\n            (byte) 0x88, //  8\n            (byte) 0x99, //  9\n            (byte) 0xaa, // 10\n            (byte) 0xbb, // 11\n            (byte) 0xcc, // 12\n            (byte) 0xdd, // 13\n            (byte) 0xee, // 14\n            (byte) 0xff, // 15\n    };\n\n//    public static final byte[] MASKS_1 = new byte[] {\n//            (byte)0x01, // bit 0\n//            (byte)0x02, // bit 1\n//            (byte)0x04, // bit 2\n//            (byte)0x08, // bit 3\n//            (byte)0x10, // bit 4\n//            (byte)0x20, // bit 5\n//            (byte)0x40, // bit 6\n//            (byte)0x80, // bit 7\n//    };\n//\n//    public static final byte[] MASKS_2 = new byte[] {\n//            (byte)0x03, // bit 0-1\n//            (byte)0x0C, // bit 2-3\n//            (byte)0x30, // bit 4-5\n//            (byte)0xC0, // bit 6-7\n//    };\n//\n//    public static final byte[] MASKS_4 = new byte[] {\n//            (byte)0x0F, // bit 0-3\n//            (byte)0xF0, // bit 4-7\n//    };\n\n    public static final byte[] SHIFTS_1 = new byte[]{\n            (byte) 0, // bit 0\n            (byte) 1, // bit 1\n            (byte) 2, // bit 2\n            (byte) 3, // bit 3\n            (byte) 4, // bit 4\n            (byte) 5, // bit 5\n            (byte) 6, // bit 6\n            (byte) 7, // bit 7\n    };\n\n    public static final byte[] SHIFTS_2 = new byte[]{\n            (byte) 0, // bit 0-1\n            (byte) 2, // bit 2-3\n            (byte) 4, // bit 4-5\n            (byte) 6 // bit 6-7\n    };\n\n    public static final byte[] SHIFTS_4 = new byte[]{\n            (byte) 0x00, // bit 0-3\n            (byte) 0x04, // bit 4-7\n    };\n\n    public static final int BIT_CHUNK_IS_ANCILLARY = 0x20000000;\n    public static final int BIT_CHUNK_IS_PRIVATE = 0x00200000;\n    public static final int BIT_CHUNK_IS_RESERVED = 0x00002000;\n    public static final int BIT_CHUNK_IS_SAFE_TO_COPY = 0x00000020;\n\n\n//    public static final byte[] SHIFTS_1 = new byte[] {\n//            (byte)0x01, // bit 0\n//            (byte)0x02, // bit 1\n//            (byte)0x04, // bit 2\n//            (byte)0x08, // bit 3\n//            (byte)0x10, // bit 4\n//            (byte)0x20, // bit 5\n//            (byte)0x40, // bit 6\n//            (byte)0x80, // bit 7\n//    };\n\n    //public static final PngChunkCodes Foo = new PngChunkCodes(123, \"Foo\");\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/PngFilter.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng;\n\n/**\n * Each \"undo*\" function reverses the effect of a filter on a given single scanline.\n */\npublic class PngFilter {\n\n    public static void undoSubFilter(byte[] bytes, int pixelStart, int pixelEnd, int filterUnit, byte[] previousRow) {\n//        int ai = 0;\n//        for (int i=pixelStart+filterUnit; i<pixelEnd; i++) {\n//\n//            final int x = bytes[i];\n//            final int a = bytes[pixelStart + ai];\n        int ai = pixelStart;\n        for (int i = pixelStart + filterUnit; i < pixelEnd; i++) {\n\n            final int x = bytes[i];\n            final int a = bytes[ai];\n\n            //bytes[rowPosition] = (byte)((bytes[rowPosition] + left) & 0xff); // TODO & 0xff\n            bytes[i] = (byte) ((x + a) & 0xff);\n\n            ai++;\n        }\n    }\n\n    public static void undoUpFilter(byte[] bytes, int pixelStart, int pixelEnd, int filterUnit, byte[] previousRow) {\n//        for (int i=1; i<bytesPerLine; i++) {\n//            rowPosition++; // before the first op to skip filter byte\n//            bytes[rowPosition] = (byte)((bytes[rowPosition] + previousRow[i]) & 0xff);\n//        }\n        int bi = 0;\n        for (int i = pixelStart; i < pixelEnd; i++) {\n            final int x = bytes[i];\n            final int b = previousRow[bi];\n            bytes[i] = (byte) ((x + b) & 0xff);\n            bi++;\n        }\n\n    }\n\n    public static void undoAverageFilter(byte[] bytes, int pixelStart, int pixelEnd, int filterUnit, byte[] previousRow) {\n        int ai = pixelStart - filterUnit;\n        int bi = 0;\n        for (int i = pixelStart; i < pixelEnd; i++) {\n            final int x = bytes[i];\n            final int a = (ai < pixelStart) ? 0 : (0xff & bytes[ai]);\n            final int b = (0xff & previousRow[bi]);\n//        int ai = pixelStart;\n//        int bi = 0;\n//        for (int i=pixelStart+filterUnit; i<pixelEnd; i++) {\n//            final int x = bytes[i];\n//            final int a = bytes[ai];\n//            final int b = previousRow[bi];\n\n            //bytes[i] = (byte)((x+((a+b)>>1))&0xff);\n            //int z = x+(a+b)/2;\n            int z = x + ((a + b) / 2);\n            bytes[i] = (byte) (0xff & z);\n            ai++;\n            bi++;\n        }\n\n        /*\n        int bi = 0;\n        for (int i=pixelStart; i<pixelStart+filterUnit; i++) {\n            final int x = bytes[i];\n            final int b = previousRow[bi];\n            bytes[i] = (byte)((x + (b >> 1)) & 0xff);\n            bi++;\n        }\n\n        for (int i=pixelStart+filterUnit; i<pixelEnd; i++) {\n            final int x = bytes[i];\n            final int a = bytes[pixelStart+bi-filterUnit];\n            final int b = previousRow[bi];\n            bytes[i] = (byte)((x + ((a+b) >> 1)) & 0xff);\n            bi++;\n        }*/\n    }\n\n    /**\n     * See http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html\n     *\n     */\n    public static void undoPaethFilter(byte[] bytes, int pixelStart, int pixelEnd, int filterUnit, byte[] previousRow) {\n        //int scratch;\n        //int previousLeft = 0;\n        //int left = 0;\n\n\n        int ai = pixelStart - filterUnit;\n        int bi = 0;\n        int ci = -filterUnit;\n        //for (int i=0; i<bytesPerLine-1; i++) {\n        //for (int i=filterUnit; i<bytesPerLine-1; i++) {\n        for (int i = pixelStart; i < pixelEnd; i++) {\n\n            final int a, b, c, x;\n            x = bytes[i];\n            if (ai < pixelStart) {\n                a = c = 0;\n            } else {\n                a = 0xff & bytes[ai];\n                c = 0xff & previousRow[ci];\n            }\n            b = 0xff & previousRow[bi];\n            final int p = a + b - c;\n            final int pa = p >= a ? p - a : a - p;\n            final int pb = p >= b ? p - b : b - p;\n            final int pc = p >= c ? p - c : c - p;\n            final int predicted = (pa <= pb && pa <= pc) ? a\n                    : (pb <= pc) ? b\n                    : c;\n\n            bytes[i] = (byte) ((x + predicted) & 0xff);\n            ai++;\n            bi++;\n            ci++;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/PngScanlineBuffer.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng;\n\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\nimport org.jackhuang.hmcl.ui.image.apng.reader.PngScanlineProcessor;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Arrays;\n\n/**\n * A PngScanlineBuffer allows up-front once-off memory allocation that can be used\n * across an entire PNG (no matter how many frames) to accumulate and process image data.\n */\npublic class PngScanlineBuffer {\n\n    protected final byte[] bytes;\n    protected final int filterUnit;\n\n    protected byte[] previousLine;\n    protected int readPosition;\n    protected int writePosition;\n    //protected int numBytesPerLine;\n\n    public PngScanlineBuffer(int size, int maxBytesPerLine, int filterUnit) {\n        this.bytes = new byte[size];\n        this.previousLine = new byte[maxBytesPerLine];\n        //this.scratchLine = new byte[maxBytesPerLine];\n        //this.numBytesPerLine = numBytesPerLine;\n        this.filterUnit = filterUnit;\n        reset();\n    }\n\n    public byte[] getBytes() {\n        return bytes;\n    }\n\n    public int getReadPosition() {\n        return readPosition;\n    }\n\n    public int getWritePosition() {\n        return writePosition;\n    }\n\n    public boolean canRead(int minNumBytes) {\n        return availableRead() >= minNumBytes;\n    }\n\n    public int availableRead() {\n        return writePosition - readPosition;\n    }\n\n    public int availableWrite() {\n        return bytes.length - writePosition;\n    }\n\n    public void skip(int numBytes) {\n        if (numBytes < 0 || numBytes > availableRead()) {\n            throw new IllegalArgumentException(String.format(\"Skip bytes must be 0 > n >= %d: %d\", availableRead(), numBytes));\n        }\n        readPosition += numBytes;\n    }\n\n    public void reset() {\n        this.readPosition = 0;\n        this.writePosition = 0;\n        Arrays.fill(previousLine, (byte) 0);\n    }\n\n    public void shift() {\n        // It is difficult to say whether there is a more efficient approach - e.g. a for loop -\n        // to shifting the bytes to the start of the array. For now, I consider System.arraycopy\n        // to be \"good enough\".\n        int len = availableRead();\n        System.arraycopy(bytes, readPosition, bytes, 0, len);\n        writePosition = len;\n        readPosition = 0;\n    }\n\n    /**\n     * Read the decompressed data into the buffer.\n     *\n     * @param is an InputStream yielding decompressed data.\n     * @return count of how many bytes read, or -1 if EOF hit.\n     * @throws IOException\n     */\n    public int read(InputStream is) throws IOException {\n        int r = is.read(bytes, writePosition, availableWrite());\n        if (r > 0) {\n            writePosition += r;\n        }\n        return r;\n    }\n\n    public boolean decompress(InputStream inputStream, PngScanlineProcessor processor) throws IOException, PngException {\n        int bytesPerLine = processor.getBytesPerLine();\n        try (InputStream iis = processor.makeInflaterInputStream(inputStream)) {\n            while (true) {\n// Ally says\n//     I LOVE Y.O.U.\n\n                int len = read(iis);\n                if (len <= 0) {\n                    return processor.isFinished();\n                }\n\n                // Or could do: len -= bytesPerLine until len < bytesPerLine\n                while (canRead(bytesPerLine)) {\n                    undoFilterScanline(bytesPerLine);\n                    processor.processScanline(bytes, readPosition + 1);//(), getReadPosition());\n                    skip(bytesPerLine);\n                }\n                shift();\n            }\n        }\n    }\n\n    public void undoFilterScanline(int bytesPerLine) {\n        int filterCode = bytes[readPosition];\n        switch (filterCode) {\n            case 0:\n                // NOP\n                break;\n            case 1:\n                PngFilter.undoSubFilter(bytes, readPosition + 1, readPosition + bytesPerLine, filterUnit, previousLine);\n                break;\n            case 2:\n                PngFilter.undoUpFilter(bytes, readPosition + 1, readPosition + bytesPerLine, filterUnit, previousLine);\n                break;\n            case 3:\n                PngFilter.undoAverageFilter(bytes, readPosition + 1, readPosition + bytesPerLine, filterUnit, previousLine);\n                break;\n            case 4:\n                PngFilter.undoPaethFilter(bytes, readPosition + 1, readPosition + bytesPerLine, filterUnit, previousLine);\n                break;\n            default: // I toyed with an exception here. But why? Just treat as if no filter.\n                //throw new IllegalArgumentException(String.format(\"Filter type %d invalid; valid is 0..4\", filterCode);\n                break;\n        }\n\n        // when un-filtering scanlines, the previous row is a copy of the *un-filtered* row (not the original).\n        System.arraycopy(bytes, readPosition + 1, previousLine, 0, bytesPerLine - 1); // keep a copy of the unmodified row\n    }\n\n    public static PngScanlineBuffer from(PngHeader header) {\n        return from(header, PngConstants.DEFAULT_TARGET_BUFFER_SIZE);\n    }\n\n    public static PngScanlineBuffer from(PngHeader header, int minBufferSize) {\n        return new PngScanlineBuffer(sizeFrom(header, minBufferSize), header.bytesPerRow, header.filterOffset);\n    }\n\n    public static int sizeFrom(PngHeader header, int minBufferSize) {\n        int bytesPerRow = header.bytesPerRow;\n        int bytesFullBitmap = bytesPerRow * header.height;\n        if (bytesFullBitmap < minBufferSize) {\n            return bytesFullBitmap;\n        }\n        int numRows = Math.max(1, minBufferSize / bytesPerRow);\n        return numRows * bytesPerRow;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Bitmap.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\n/**\n * A bitmap where each pixel is represented by 8-bits for alpha, red, green and blue\n * respectively, and each pixel stored in a single 32-bit integer. This is expressly designed\n * to be compatible with the input array used to build Android Bitmap objects, though of course\n * its use is not limited to that.\n */\npublic final class Argb8888Bitmap {\n    public final int[] array;\n    public final int width;\n    public final int height;\n\n    public Argb8888Bitmap(int width, int height) {\n        this(new int[width * height], width, height);\n    }\n\n    public Argb8888Bitmap(int[] array, int width, int height) {//}, int x, int y) {\n        this.array = array;\n        this.width = width;\n        this.height = height;\n//        this.x = y;\n    }\n\n    /**\n     * Create a new Bitmap that shares the byte array of this bitmap.\n     * This is useful when rendering a number of animation frames from an APNG file\n     * and wanting to minimise memory allocation, while still wanting a \"new\" bitmap\n     * to work on.\n     *\n     * @param width  in pixels of \"new\" bitmap (actual pixel array is the exact array\n     *               from the original bitmap).\n     * @param height in pixels of \"new\" bitmap (actual pixel array is the exact array\n     *               from the original bitmap).\n     * @return new bitmap object sharing the same data array.\n     */\n    public Argb8888Bitmap makeView(int width, int height) {//}, int x, int y) {\n        if ((width * height) > (this.width * this.height)) {\n            throw new IllegalArgumentException(String.format(\n                    \"Requested width and height (%d x %d) exceeds maximum pixels allowed by host bitmap (%d x %d\",\n                    width, height, this.width, this.height));\n        }\n        return new Argb8888Bitmap(array, width, height);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888BitmapSequence.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * An Argb8888BitmapSequence object represents all bitmaps in a single PNG file,\n * whether it has one and only one default image, any number of frames in an animation\n * or a default image and a separate set of animation frames.\n * <p>\n * Note that instances of this class will hold an individual bitmap for every frame\n * and does <em>not</em> do composition of images in any way. Composition is done in\n * the japng_android library using an Android Canvas. This class is not used for that,\n * as the intermediate Argb8888Bitmap objects are not required, only one bitmap and\n * the output buffer is required during composition.\n */\npublic final class Argb8888BitmapSequence {\n\n    public final PngHeader header;\n    public final Argb8888Bitmap defaultImage;\n\n    private boolean defaultImageIsSet = false;\n    private PngAnimationControl animationControl;\n    List<Frame> animationFrames;\n\n    public Argb8888BitmapSequence(PngHeader header) {\n        this.header = header;\n        this.defaultImage = new Argb8888Bitmap(header.width, header.height);\n    }\n\n    public void receiveAnimationControl(PngAnimationControl animationControl) {\n        this.animationControl = animationControl;\n        this.animationFrames = new ArrayList<>(animationControl.numFrames);\n    }\n\n    public void receiveDefaultImage(Argb8888Bitmap bitmap) {\n        defaultImageIsSet = true;\n    }\n\n    public boolean hasDefaultImage() {\n        return defaultImageIsSet;\n    }\n\n    public boolean isAnimated() {\n        return null != animationControl && animationControl.numFrames > 0;\n    }\n\n    public PngAnimationControl getAnimationControl() {\n        return animationControl;\n    }\n\n    public List<Frame> getAnimationFrames() {\n        return animationFrames;\n    }\n\n    public static final class Frame {\n        public final PngFrameControl control;\n        public final Argb8888Bitmap bitmap;\n\n        public Frame(PngFrameControl control, Argb8888Bitmap bitmap) {\n            this.control = control;\n            this.bitmap = bitmap;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888BitmapSequenceDirector.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngScanlineBuffer;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\n\n/**\n * Argb8888BitmapSequenceDirector instances direct an Argb8888Processor to build all frames\n * of an animation into an Argb8888BitmapSequence object.\n */\npublic class Argb8888BitmapSequenceDirector extends BasicArgb8888Director<Argb8888BitmapSequence> {\n    Argb8888BitmapSequence bitmapSequence = null;\n    PngFrameControl currentFrame = null;\n    private PngHeader header;\n\n    @Override\n    public void receiveHeader(PngHeader header, PngScanlineBuffer buffer) throws PngException {\n        this.header = header;\n        this.bitmapSequence = new Argb8888BitmapSequence(header);\n        //this.header = header;\n        //defaultImage = new Argb8888Bitmap(header.width, header.height);\n        this.scanlineProcessor = Argb8888Processors.from(header, buffer, this.bitmapSequence.defaultImage);\n    }\n\n    @Override\n    public boolean wantDefaultImage() {\n        return true;\n    }\n\n    @Override\n    public boolean wantAnimationFrames() {\n        return true;\n    }\n\n    @Override\n    public Argb8888ScanlineProcessor beforeDefaultImage() {\n        return scanlineProcessor;\n    }\n\n    @Override\n    public void receiveDefaultImage(Argb8888Bitmap bitmap) {\n        this.bitmapSequence.receiveDefaultImage(bitmap);\n    }\n\n    @Override\n    public void receiveAnimationControl(PngAnimationControl control) {\n        this.bitmapSequence.receiveAnimationControl(control);\n    }\n\n    @Override\n    public Argb8888ScanlineProcessor receiveFrameControl(PngFrameControl control) {\n        //throw new IllegalStateException(\"TODO up to here\");\n        //return null;\n        currentFrame = control;\n\n        //System.out.println(\"Frame: \"+control);\n\n        return scanlineProcessor.cloneWithNewBitmap(header.adjustFor(control)); // TODO: is this going to be a problem?\n    }\n\n    @Override\n    public void receiveFrameImage(Argb8888Bitmap bitmap) {\n        if (null == currentFrame) {\n            throw new IllegalStateException(\"Received a frame image with no frame control in place\");\n        }\n        if (null == bitmapSequence.animationFrames) {\n            throw new IllegalStateException(\"Received a frame image without animation control (or frame list?) in place\");\n        }\n        bitmapSequence.animationFrames.add(new Argb8888BitmapSequence.Frame(currentFrame, bitmap));\n        currentFrame = null;\n    }\n\n    @Override\n    public Argb8888BitmapSequence getResult() {\n        return bitmapSequence;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Director.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngScanlineBuffer;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\n\n/**\n * Argb8888Director implementations \"direct\" a given Argb8888Processor how to\n * control the output. This allows the Argb8888Processor to transform pixels into\n * ARGB8888 format while allowing for radically different final output objects,\n * e.g. a single bitmap, a sequence of bitamps, an Android View or Drawable, etc.\n * <p>\n * TODO: not sure if this will stay in this form. Needs refinement.\n */\npublic interface Argb8888Director<ResultT> {\n\n    void receiveHeader(PngHeader header, PngScanlineBuffer buffer) throws PngException;\n\n    void receivePalette(Argb8888Palette palette);\n\n    void processTransparentPalette(byte[] bytes, int position, int length) throws PngException;\n\n    void processTransparentGreyscale(byte k1, byte k0) throws PngException;\n\n    void processTransparentTruecolour(byte r1, byte r0, byte g1, byte g0, byte b1, byte b0) throws PngException;\n\n    boolean wantDefaultImage();\n\n    boolean wantAnimationFrames();\n\n    Argb8888ScanlineProcessor beforeDefaultImage();\n\n    void receiveDefaultImage(Argb8888Bitmap bitmap);\n\n    void receiveAnimationControl(PngAnimationControl control);\n\n    Argb8888ScanlineProcessor receiveFrameControl(PngFrameControl control);\n\n    void receiveFrameImage(Argb8888Bitmap bitmap);\n\n    ResultT getResult();\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Palette.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;\n\n/**\n * A palette implementation representing colours in ARGB8888 format as 32-bit integers.\n * <p>\n * Note that the transparency of individual entries in the palette is modified in-place\n * when the trNS chunk is processed.\n *\n * @see BasicArgb8888Director\n */\npublic class Argb8888Palette {\n    protected static Argb8888Palette monochromePalette = null;\n    protected static Argb8888Palette greyPalette2 = null;\n    protected static Argb8888Palette greyPalette4 = null;\n    protected static Argb8888Palette greyPalette8 = null;\n\n    /**\n     * The colour array is public and mutable by design.\n     *\n     * @see this.get()\n     */\n    public final int[] argbArray;\n\n    /**\n     * Construct a new palette using the specific array passed as the argument as-is (not copied).\n     *\n     * @param argbArray array of colours to use as-is (it is not copied).\n     */\n    public Argb8888Palette(int[] argbArray) {\n        this.argbArray = argbArray;\n    }\n\n    /**\n     * Retrieve a colour using a method. Users can also just use the argbArray directly.\n     *\n     * @param index of colour to retrieve\n     * @return integer representing the colour at that index.\n     */\n    public int get(int index) {\n        return this.argbArray[index];\n    }\n\n    /**\n     * @return the number of colours in this palette, which is the same thing as the size of the array.\n     */\n    public int size() {\n        return argbArray.length;\n    }\n\n    /**\n     * Build a new palette by reading the byte array (from the offset position and for length bytes)\n     * as provided in a PLTE chunk.\n     *\n     * @param bytes    array to read data from\n     * @param position offset into bytes array to begin reading from\n     * @param length   number of bytes to read from bytes array\n     * @return new palette formed by reading the bytes array.\n     * @throws PngException\n     */\n    public static Argb8888Palette fromPaletteBytes(byte[] bytes, int position, int length) throws PngException {\n        int numColours = length / 3; // guaranteed to be divisible by 3\n        int[] argbArray = new int[numColours];\n        int srcIndex = position;\n        int alpha = 0xff << 24;\n        for (int destIndex = 0; destIndex < numColours; destIndex++) {\n            final int r = bytes[srcIndex++] & 0xff;\n            final int g = bytes[srcIndex++] & 0xff;\n            final int b = bytes[srcIndex++] & 0xff;\n            argbArray[destIndex] = alpha | r << 16 | g << 8 | b;\n        }\n        return new Argb8888Palette(argbArray);\n    }\n\n    public static Argb8888Palette forGreyscale(int numEntries, int step) {\n        int[] array = new int[numEntries];\n        int alpha = 0xff << 24;\n        int grey = 0;\n        for (int i = 0; i < numEntries; i++) {\n            array[i] = alpha | grey << 16 | grey << 8 | grey;\n            grey = (grey + step) & 0xff;\n        }\n        return new Argb8888Palette(array);\n    }\n\n    public static Argb8888Palette forGreyscale(int bitDepth) throws PngException {\n        switch (bitDepth) {\n            case 1:\n                if (null == monochromePalette) {\n                    monochromePalette = forGreyscale(2, 0xff); // Worth it or not really?\n                }\n                return monochromePalette;\n            case 2:\n                if (null == greyPalette2) {\n                    greyPalette2 = forGreyscale(4, 0x55);\n                }\n                return greyPalette2;\n            case 4:\n                if (null == greyPalette4) {\n                    greyPalette4 = forGreyscale(16, 0x11);\n                }\n                return greyPalette4;\n            case 8: // TODO: need??\n                if (null == greyPalette8) {\n                    greyPalette8 = forGreyscale(256, 0x01);\n                }\n                return greyPalette8;\n            default:\n                throw new PngIntegrityException(String.format(\"Valid greyscale bit depths are 1, 2, 4, 8, not %d\", bitDepth));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Processor.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngChunkCode;\nimport org.jackhuang.hmcl.ui.image.apng.PngScanlineBuffer;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngGamma;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;\nimport org.jackhuang.hmcl.ui.image.apng.map.PngChunkMap;\nimport org.jackhuang.hmcl.ui.image.apng.reader.PngChunkProcessor;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Concrete implementation of a chunk processor designed to process pixels into\n * 32-bit ARGB8888 format.\n */\npublic class Argb8888Processor<ResultT> implements PngChunkProcessor<ResultT> { //PngChunkProcessor<Argb8888Bitmap> {\n\n    protected PngHeader header = null;\n    protected PngScanlineBuffer scanlineReader = null;\n    protected Argb8888Director<ResultT> builder = null;\n    protected Argb8888ScanlineProcessor scanlineProcessor = null;\n\n\n    public Argb8888Processor(Argb8888Director<ResultT> builder) {\n        this.builder = builder;\n    }\n\n    @Override\n    public void processHeader(PngHeader header) throws PngException {\n//        super.processHeader(header);\n////        if (header.bitDepth != 1 || header.colourType != PngColourType.PNG_GREYSCALE) {\n////            throw new PngFeatureException(\"ARGB888 only supports 1-bit greyscale\");\n////        }\n//        if (header.isGreyscale()) {\n//            palette = Argb8888Palette.forGreyscale(header.bitDepth);\n//        }\n//        scanlineReader = PngScanlineBuffer.from(header);\n        this.header = header;\n        this.scanlineReader = PngScanlineBuffer.from(header);\n        this.builder.receiveHeader(this.header, this.scanlineReader);\n    }\n\n    @Override\n    public void processGamma(PngGamma gamma) throws PngException {\n        // No gamma processing is done at the moment.\n    }\n\n    @Override\n    public void processPalette(byte[] bytes, int position, int length) throws PngException {\n        //palette = Argb8888Palette.fromPaletteBytes(bytes, position, length);\n        //scanlineProcessor = Argb8888Processors.fromPalette(this.header, palette);\n        builder.receivePalette(Argb8888Palette.fromPaletteBytes(bytes, position, length));\n    }\n\n    @Override\n    public void processTransparency(byte[] bytes, int position, int length) throws PngException {\n        switch (header.colourType) {\n            case PNG_GREYSCALE: // colour type 0\n                // grey sample value (2 bytes)\n                if (length != 2) {\n                    throw new PngIntegrityException(String.format(\"tRNS chunk for greyscale image must be exactly length=2, not %d\", length));\n                }\n                builder.processTransparentGreyscale(bytes[0], bytes[1]);\n                break;\n\n            case PNG_TRUECOLOUR: // colour type 2\n                // red, green, blue samples, EACH with two bytes (16-bits)\n                builder.processTransparentTruecolour(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]);\n                break;\n\n            case PNG_INDEXED_COLOUR: // colour type 3\n                // This is a sequence of one-byte alpha values to apply to each palette entry starting at zero.\n                // The number of entries may be less than the size of the palette, but not more.\n                builder.processTransparentPalette(bytes, position, length);\n                break;\n\n            case PNG_GREYSCALE_WITH_ALPHA:\n            case PNG_TRUECOLOUR_WITH_ALPHA:\n            default:\n                throw new PngIntegrityException(\"Illegal to have tRNS chunk with image type \" + header.colourType.name);\n        }\n    }\n\n    /**\n     * The only supported animation type is not \"NOT_ANIMATED\".\n     */\n//    @Override\n//    public PngAnimationType chooseApngImageType(PngAnimationType type, PngFrameControl currentFrame) throws PngException {\n//        scanlineProcessor = Argb8888ScanlineProcessor.from(header, scanlineReader, currentFrame);\n//        return PngAnimationType.NOT_ANIMATED;\n//    }\n    @Override\n    public void processDefaultImageData(InputStream inputStream, PngChunkCode code, int position, int length) throws IOException, PngException {\n        if (!builder.wantDefaultImage()) {\n            inputStream.skip(length); // important!\n            return;\n        }\n\n        if (null == scanlineProcessor) { // TODO: is that a good enough metric? Or could be numIdat==0?\n            scanlineProcessor = builder.beforeDefaultImage();\n            if (null == scanlineProcessor) throw new IllegalStateException(\"Builder must create scanline processor\");\n        }\n\n        if (scanlineReader.decompress(inputStream, scanlineProcessor)) {\n            // If here, the image is fully decompressed.\n            builder.receiveDefaultImage(scanlineProcessor.getBitmap());\n            scanlineProcessor = null;\n            scanlineReader.reset();\n        }\n    }\n\n    @Override\n    public void processAnimationControl(PngAnimationControl animationControl) throws PngException {\n        if (builder.wantAnimationFrames()) {\n            builder.receiveAnimationControl(animationControl);\n        }\n    }\n\n    @Override\n    public void processFrameControl(PngFrameControl frameControl) throws PngException {\n        if (!builder.wantAnimationFrames()) {\n            return;\n        }\n\n        if (null == scanlineProcessor) {\n            // If here, the data in this frame image has not started\n            scanlineProcessor = builder.receiveFrameControl(frameControl);\n            if (null == scanlineProcessor)\n                throw new IllegalStateException(\"Builder must create scanline processor for frame\");\n        } else {\n            throw new IllegalStateException(\"received animation frame control but image data was in progress\");\n        }\n    }\n\n    @Override\n    public void processFrameImageData(InputStream inputStream, PngChunkCode code, int position, int length) throws IOException, PngException {\n        //throw new PngFeatureException(\"PngArgb8888Processor does not support animation frames\");\n        if (!builder.wantAnimationFrames()) {\n            inputStream.skip(length);\n            return;\n        }\n\n        if (null == scanlineProcessor) {\n            throw new IllegalStateException(\"received animation frame image data before frame control or without processor in place\");\n        }\n\n        if (scanlineReader.decompress(inputStream, scanlineProcessor)) {\n            // If here, the image is fully decompressed.\n            builder.receiveFrameImage(scanlineProcessor.getBitmap());\n            scanlineReader.reset();\n            scanlineProcessor = null;\n        }\n    }\n\n    @Override\n    public void processChunkMapItem(PngChunkMap chunkMapItem) throws PngException {\n        // NOP\n    }\n\n    @Override\n    public ResultT getResult() {\n        //return scanlineProcessor.getBitmap();\n        return builder.getResult();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Processors.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngConstants;\nimport org.jackhuang.hmcl.ui.image.apng.PngScanlineBuffer;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngFeatureException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;\n\n/**\n * A series of scanline processor implementations for different input pixel formats,\n * each able to transform the input into ARGB8888 output pixels.\n */\npublic class Argb8888Processors {\n\n    /**\n     * Determine from the header the concrete Argb8888Processor implementation the\n     * caller needs to transform the incoming pixels into the ARGB8888 pixel format.\n     *\n     * @param header         of the image being loaded\n     * @param scanlineReader the scanline buffer being used\n     * @param bitmap         the destination bitmap\n     * @return a concrete Argb8888Processor to transform source pixels into the specific bitmap.\n     * @throws PngException if there is a feature not supported or some specification break with the file.\n     */\n    public static Argb8888ScanlineProcessor from(PngHeader header, PngScanlineBuffer scanlineReader, Argb8888Bitmap bitmap) throws PngException {\n\n        int bytesPerScanline = header.bytesPerRow;\n        switch (header.colourType) {\n            case PNG_GREYSCALE:\n                switch (header.bitDepth) {\n                    case 1:\n                        return new IndexedColourBits(bytesPerScanline, bitmap, 7, 0x01, PngConstants.SHIFTS_1, Argb8888Palette.forGreyscale(1));\n                    case 2:\n                        return new IndexedColourBits(bytesPerScanline, bitmap, 3, 0x03, PngConstants.SHIFTS_2, Argb8888Palette.forGreyscale(2));\n                    case 4:\n                        return new IndexedColourBits(bytesPerScanline, bitmap, 1, 0x0F, PngConstants.SHIFTS_4, Argb8888Palette.forGreyscale(4));\n                    case 8:\n                        return new Greyscale8(bytesPerScanline, bitmap);\n                    case 16:\n                        throw new PngFeatureException(\"Greyscale supports 1, 2, 4, 8 but not 16.\");\n                    default:\n                        throw new PngIntegrityException(String.format(\"Invalid greyscale bit-depth: %d\", header.bitDepth)); // TODO: should be in header parse.\n                }\n\n            case PNG_GREYSCALE_WITH_ALPHA:\n                switch (header.bitDepth) {\n                    case 4:\n                        return new Greyscale4Alpha(bytesPerScanline, bitmap);\n                    case 8:\n                        return new Greyscale8Alpha(bytesPerScanline, bitmap);\n                    case 16:\n                        return new Greyscale16Alpha(bytesPerScanline, bitmap);\n                    default:\n                        throw new PngIntegrityException(String.format(\"Invalid greyscale-with-alpha bit-depth: %d\", header.bitDepth)); // TODO: should be in header parse.\n                }\n\n            case PNG_INDEXED_COLOUR:\n                switch (header.bitDepth) {\n                    case 1:\n                        return new IndexedColourBits(bytesPerScanline, bitmap, 7, 0x01, PngConstants.SHIFTS_1);\n                    case 2:\n                        return new IndexedColourBits(bytesPerScanline, bitmap, 3, 0x03, PngConstants.SHIFTS_2);\n                    case 4:\n                        return new IndexedColourBits(bytesPerScanline, bitmap, 1, 0x0F, PngConstants.SHIFTS_4);\n                    case 8:\n                        return new IndexedColour8(bytesPerScanline, bitmap);\n                    default:\n                        throw new PngIntegrityException(String.format(\"Invalid indexed colour bit-depth: %d\", header.bitDepth)); // TODO: should be in header parse.\n                }\n\n            case PNG_TRUECOLOUR:\n                switch (header.bitDepth) {\n                    case 8:\n                        return new Truecolour8(bytesPerScanline, bitmap);\n                    case 16:\n                        return new Truecolour16(bytesPerScanline, bitmap);\n                    default:\n                        throw new PngIntegrityException(String.format(\"Invalid truecolour bit-depth: %d\", header.bitDepth)); // TODO: should be in header parse.\n\n                }\n\n            case PNG_TRUECOLOUR_WITH_ALPHA:\n                switch (header.bitDepth) {\n                    case 8:\n                        return new Truecolour8Alpha(bytesPerScanline, bitmap);\n                    case 16:\n                        return new Truecolour16Alpha(bytesPerScanline, bitmap);\n                    default:\n                        throw new PngIntegrityException(String.format(\"Invalid truecolour with alpha bit-depth: %d\", header.bitDepth)); // TODO: should be in header parse.\n\n                }\n\n            default:\n                throw new PngFeatureException(\"ARGB8888 doesn't support PNG mode \" + header.colourType.name());\n        }\n    }\n\n    /**\n     * Transforms 1-, 2-, 4-bit indexed colour source pixels to ARGB8888 pixels.\n     */\n    public static class IndexedColourBits extends Argb8888ScanlineProcessor {\n\n        private int highBit;\n        private int mask;\n        private byte[] shifts;\n\n        public IndexedColourBits(int bytesPerScanline, Argb8888Bitmap bitmap, int highBit, int mask, byte[] shifts) {\n            this(bytesPerScanline, bitmap, highBit, mask, shifts, null);\n        }\n\n        public IndexedColourBits(int bytesPerScanline, Argb8888Bitmap bitmap, int highBit, int mask, byte[] shifts, Argb8888Palette palette) {\n            super(bytesPerScanline, bitmap);\n            this.highBit = highBit;\n            this.mask = mask;\n            this.shifts = shifts;\n            this.palette = palette;\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            int writePosition = this.y * width;\n            int lastWritePosition = writePosition + width;\n            int bit = highBit;\n\n            while (writePosition < lastWritePosition) {\n                //final int index=(srcBytes[srcPosition+x] & masks[bit]) >> shifts[bit];\n                //final int index = (srcBytes[srcPosition])\n                //final int v = srcBytes[srcPosition];\n                final int index = mask & (srcBytes[srcPosition] >> shifts[bit]);\n\n                int dest;\n                if (palette != null) {\n                    dest = palette.argbArray[index];\n                } else {\n                    dest = 0;\n                }\n                destArray[writePosition++] = dest;\n                if (bit == 0) {\n                    srcPosition++;\n                    bit = highBit;\n                } else {\n                    bit--;\n                }\n            }\n            this.y++;\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new IndexedColourBits(bytesPerRow, bitmap, highBit, mask, shifts, palette);\n        }\n    }\n\n    /**\n     * Special case implementation to transform 8-bit indexed colour source pixels to ARGB8888 pixels.\n     */\n    public static class IndexedColour8 extends Argb8888ScanlineProcessor {\n\n        public IndexedColour8(int bytesPerScanline, Argb8888Bitmap bitmap) {\n            super(bytesPerScanline, bitmap);\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            int writePosition = this.y * width;\n            for (int x = 0; x < width; x++) {\n\n                final int index = 0xff & srcBytes[srcPosition++]; // TODO: need to use transparency and background chunks\n\n                int dest;\n                if (palette != null) {\n                    dest = palette.argbArray[index];\n                } else {\n                    dest = 0;\n                }\n                destArray[writePosition++] = dest;\n            }\n            this.y++;\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new IndexedColour8(bytesPerRow, bitmap);\n        }\n    }\n\n    /**\n     * Transforms 4-bit greyscale with alpha source pixels to ARGB8888 pixels.\n     */\n    public static class Greyscale4Alpha extends Argb8888ScanlineProcessor {\n\n        public Greyscale4Alpha(int bytesPerScanline, Argb8888Bitmap bitmap) {\n            super(bytesPerScanline, bitmap);\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            int writePosition = this.y * width;\n            for (int x = 0; x < width; x++) {\n                final int v = srcBytes[srcPosition++];\n                final int k = PngConstants.GREY_PALETTE_16[0x0f & (v >> 4)];\n                final int a = PngConstants.GREY_PALETTE_16[0x0f & v];\n                final int c = a << 24 | k << 16 | k << 8 | k;\n                destArray[writePosition++] = c;\n            }\n            this.y++;\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new Greyscale4Alpha(bytesPerRow, bitmap);\n        }\n    }\n\n    /**\n     * Transforms 8-bit greyscale source pixels to ARGB8888 pixels.\n     */\n    public static class Greyscale8 extends Argb8888ScanlineProcessor {\n\n        boolean haveTransparent = false;\n        int transparentSample = 0;\n\n        public Greyscale8(int bytesPerScanline, Argb8888Bitmap bitmap) {\n            super(bytesPerScanline, bitmap);\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            final int alpha = 0xff000000; // No alpha in the image means every pixel must be fully opaque\n            int writePosition = this.y * width;\n            for (int x = 0; x < width; x++) {\n                final int sample = 0xff & srcBytes[srcPosition++];\n                final int k = (haveTransparent && sample == transparentSample) ? transparentSample : sample;\n                final int c = alpha | k << 16 | k << 8 | k;\n                destArray[writePosition++] = c;\n            }\n            this.y++;\n        }\n\n        @Override\n        public void processTransparentGreyscale(byte k1, byte k0) {\n            // According to below, when image is less than 16-bits per pixel, use least significant byte\n            // http://www.w3.org/TR/PNG/#11transinfo\n            haveTransparent = true;\n            transparentSample = 0xff & k0; // NOT k1 according to http://www.w3.org/TR/PNG/#11transinfo\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new Greyscale8(bytesPerRow, bitmap);\n        }\n    }\n\n    /**\n     * Transforms 8-bit greyscale with alpha source pixels to ARGB8888 pixels.\n     */\n    public static class Greyscale8Alpha extends Argb8888ScanlineProcessor {\n\n        public Greyscale8Alpha(int bytesPerScanline, Argb8888Bitmap bitmap) {\n            super(bytesPerScanline, bitmap);\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            int writePosition = this.y * width;\n            for (int x = 0; x < width; x++) {\n                final int k = 0xff & srcBytes[srcPosition++];\n                final int a = 0xff & srcBytes[srcPosition++];\n                final int c = a << 24 | k << 16 | k << 8 | k;\n                destArray[writePosition++] = c;\n            }\n            this.y++;\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new Greyscale8Alpha(bytesPerRow, bitmap);\n        }\n    }\n\n    /**\n     * Transforms 16-bit greyscale with alpha source pixels to ARGB8888 pixels.\n     */\n    public static class Greyscale16Alpha extends Argb8888ScanlineProcessor {\n\n        public Greyscale16Alpha(int bytesPerScanline, Argb8888Bitmap bitmap) {\n            super(bytesPerScanline, bitmap);\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            int writePosition = this.y * width;\n            for (int x = 0; x < width; x++) {\n                final int k = 0xff & srcBytes[srcPosition++];\n                srcPosition++; // skip the least-significant byte of the grey\n                final int a = 0xff & srcBytes[srcPosition++];\n                srcPosition++; // skip the least-significant byte of the alpha\n                final int c = a << 24 | k << 16 | k << 8 | k;\n                destArray[writePosition++] = c;\n            }\n            this.y++;\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new Greyscale16Alpha(bytesPerRow, bitmap);\n        }\n    }\n\n    /**\n     * Transforms true-colour (RGB) 8-bit source pixels to ARGB8888 pixels.\n     */\n    public static class Truecolour8 extends Argb8888ScanlineProcessor {\n        public Truecolour8(int bytesPerScanline, Argb8888Bitmap bitmap) {\n            super(bytesPerScanline, bitmap);\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            final int alpha = 0xff000000; // No alpha in the image means every pixel must be fully opaque\n            int writePosition = this.y * width;\n            for (int x = 0; x < width; x++) {\n                final int r = 0xff & srcBytes[srcPosition++];\n                final int g = 0xff & srcBytes[srcPosition++];\n                final int b = 0xff & srcBytes[srcPosition++];\n                final int c = alpha | r << 16 | g << 8 | b;\n                destArray[writePosition++] = c;\n            }\n            this.y++;\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new Truecolour8(bytesPerRow, bitmap);\n        }\n    }\n\n    /**\n     * Transforms true-colour with alpha (RGBA) 8-bit source pixels to ARGB8888 pixels.\n     */\n    public static class Truecolour8Alpha extends Argb8888ScanlineProcessor {\n\n        public Truecolour8Alpha(int bytesPerScanline, Argb8888Bitmap bitmap) {\n            super(bytesPerScanline, bitmap);\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            int writePosition = this.y * width;\n            //srcPosition++; // skip filter byte\n            for (int x = 0; x < width; x++) {\n                final int r = 0xff & srcBytes[srcPosition++];\n                final int g = 0xff & srcBytes[srcPosition++];\n                final int b = 0xff & srcBytes[srcPosition++];\n                final int a = 0xff & srcBytes[srcPosition++];\n                final int c = a << 24 | r << 16 | g << 8 | b;\n                destArray[writePosition++] = c;\n            }\n            this.y++;\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new Truecolour8Alpha(bytesPerRow, bitmap);\n        }\n    }\n\n    /**\n     * Transforms true-colour (RGB) 16-bit source pixels to ARGB8888 pixels.\n     */\n    public static class Truecolour16 extends Argb8888ScanlineProcessor {\n        public Truecolour16(int bytesPerScanline, Argb8888Bitmap bitmap) {\n            super(bytesPerScanline, bitmap);\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            final int alpha = 0xff000000; // No alpha in the image means every pixel must be fully opaque\n            int writePosition = this.y * width;\n            //srcPosition++; // skip filter byte\n            for (int x = 0; x < width; x++) {\n                final int r = 0xff & srcBytes[srcPosition];\n                srcPosition += 2; // skip the byte just read and the least significant byte of the next\n                final int g = 0xff & srcBytes[srcPosition];\n                srcPosition += 2; // ditto\n                final int b = 0xff & srcBytes[srcPosition];\n                srcPosition += 2; // ditto again\n                final int c = alpha | r << 16 | g << 8 | b;\n                destArray[writePosition++] = c;\n            }\n            this.y++;\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new Truecolour16(bytesPerRow, bitmap);\n        }\n    }\n\n    /**\n     * Transforms true-colour with alpha (RGBA) 16-bit source pixels to ARGB8888 pixels.\n     * <p>\n     * Note that the simpler method of resampling the colour is done, namely discard the LSB.\n     */\n    public static class Truecolour16Alpha extends Argb8888ScanlineProcessor {\n        public Truecolour16Alpha(int bytesPerScanline, Argb8888Bitmap bitmap) {\n            super(bytesPerScanline, bitmap);\n        }\n\n        @Override\n        public void processScanline(byte[] srcBytes, int srcPosition) {\n            final int[] destArray = this.bitmap.array;\n            final int width = this.bitmap.width;\n            //final int alpha = 0xff000000; // No alpha in the image means every pixel must be fully opaque\n            int writePosition = this.y * width;\n            //srcPosition++; // skip filter byte\n            for (int x = 0; x < width; x++) {\n                final int r = 0xff & srcBytes[srcPosition];\n                srcPosition += 2; // skip the byte just read and the least significant byte of the next\n                final int g = 0xff & srcBytes[srcPosition];\n                srcPosition += 2; // ditto\n                final int b = 0xff & srcBytes[srcPosition];\n                srcPosition += 2; // ditto again\n                final int alpha = 0xff & srcBytes[srcPosition];\n                srcPosition += 2; // skip the byte just read and the least significant byte of the next\n                final int c = alpha << 24 | r << 16 | g << 8 | b;\n                destArray[writePosition++] = c;\n            }\n            this.y++;\n        }\n\n        @Override\n        public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n            return new Truecolour16Alpha(bytesPerRow, bitmap);\n        }\n    }\n\n    private Argb8888Processors() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888ScanlineProcessor.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\nimport org.jackhuang.hmcl.ui.image.apng.reader.BasicScanlineProcessor;\n\n/**\n * Base implementation that transforms scanlines of source pixels into scanlines of\n * ARGB8888 (32-bit integer) pixels in a destination Argb8888Bitmap object.\n * <p>\n * Note: I wonder if a better name is ScanlineConverter or PixelTransformer.\n *\n * @see Argb8888Processors\n */\npublic abstract class Argb8888ScanlineProcessor extends BasicScanlineProcessor {\n    protected Argb8888Bitmap bitmap;\n    protected Argb8888Palette palette;\n    protected int y = 0;\n\n//    public Argb8888ScanlineProcessor(PngHeader header) {\n//        this(header.bytesPerRow, new Argb8888Bitmap(header.width, header.height));\n//    }\n\n    public Argb8888ScanlineProcessor(int bytesPerScanline, Argb8888Bitmap bitmap) {\n        super(bytesPerScanline);\n        this.bitmap = bitmap;\n    }\n\n//    public Argb8888ScanlineProcessor(PngHeader header, PngScanlineBuffer scanlineReader, PngFrameControl currentFrame) {\n//        super(header, scanlineReader, currentFrame);\n//        bitmap = new Argb8888Bitmap(this.header.width, this.header.height);\n//    }\n\n    public Argb8888Bitmap getBitmap() {\n        return bitmap;\n    }\n\n    public Argb8888Palette getPalette() {\n        return palette;\n    }\n\n    public void setPalette(Argb8888Palette palette) {\n        this.palette = palette;\n    }\n\n    /**\n     * When processing a sequence of frames a caller may want to write to a completely\n     * new bitmap for every frame or re-use the bytes in an existing bitmap.\n     *\n     * @param header of image to use as basis for calculating image size.\n     * @return processor that will write to a new bitmap.\n     */\n    public Argb8888ScanlineProcessor cloneWithNewBitmap(PngHeader header) {\n        Argb8888ScanlineProcessor cloned = clone(header.bytesPerRow, new Argb8888Bitmap(header.width, header.height));\n        if (this.palette != null) {\n            cloned.setPalette(new Argb8888Palette(palette.argbArray));\n        }\n        return cloned;\n\n    }\n\n    /**\n     * When processing a sequence of frames a caller may want to write to a completely\n     * new bitmap for every frame or re-use the bytes in an existing bitmap.\n     *\n     * @param header of image to use as basis for calculating image size.\n     * @return processor that will write to the same bitmap as the current processor.\n     */\n    public Argb8888ScanlineProcessor cloneWithSharedBitmap(PngHeader header) {\n        Argb8888ScanlineProcessor cloned = clone(header.bytesPerRow, bitmap.makeView(header.width, header.height));\n        if (this.palette != null) {\n            cloned.setPalette(new Argb8888Palette(palette.argbArray));\n        }\n        return cloned;\n\n    }\n\n    //    public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {\n//        throw new IllegalStateException(\"TODO: override\");\n//    }\n    abstract Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap);\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BasicArgb8888Director.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;\n\n/**\n * Common functionality for Argb8888Director implementations.\n */\npublic abstract class BasicArgb8888Director<ResultT> implements Argb8888Director<ResultT> {\n    protected Argb8888ScanlineProcessor scanlineProcessor;\n\n    @Override\n    public void receivePalette(Argb8888Palette palette) {\n        scanlineProcessor.setPalette(palette);\n    }\n\n    @Override\n    public void processTransparentPalette(byte[] bytes, int position, int length) throws PngException {\n        Argb8888Palette palette = scanlineProcessor.getPalette();\n        if (null == palette) {\n            throw new PngIntegrityException(\"Received tRNS data but no palette is in place\");\n        }\n        if (length <= 0 || length > palette.size()) {\n            throw new PngIntegrityException(String.format(\"Received tRNS data length is invalid. Should be >1 && < %d but is %d\", palette.size(), length));\n        }\n        for (int i = 0; i < length; i++) {\n            final int alpha = 0xff & bytes[position + i];\n            palette.argbArray[i] = alpha << 24 | palette.argbArray[i] & 0x00FFFFFF;\n        }\n    }\n\n    @Override\n    public void processTransparentGreyscale(byte k1, byte k0) throws PngException {\n        scanlineProcessor.processTransparentGreyscale(k1, k0);\n    }\n\n    @Override\n    public void processTransparentTruecolour(byte r1, byte r0, byte g1, byte g0, byte b1, byte b0) throws PngException {\n        scanlineProcessor.processTransparentTruecolour(r1, r0, g1, g0, b1, b0);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/DefaultImageArgb8888Director.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.argb8888;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngScanlineBuffer;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\n\n/**\n * This will build a single bitmap: the default image within the PNG file.\n * Any animation data (or any other data) will not be processed.\n */\npublic class DefaultImageArgb8888Director extends BasicArgb8888Director<Argb8888Bitmap> {\n\n    protected Argb8888Bitmap defaultImage;\n\n    @Override\n    public void receiveHeader(PngHeader header, PngScanlineBuffer buffer) throws PngException {\n        defaultImage = new Argb8888Bitmap(header.width, header.height);\n        scanlineProcessor = Argb8888Processors.from(header, buffer, defaultImage);\n    }\n\n    @Override\n    public boolean wantDefaultImage() {\n        return true;\n    }\n\n    @Override\n    public boolean wantAnimationFrames() {\n        return false;\n    }\n\n    @Override\n    public Argb8888ScanlineProcessor beforeDefaultImage() {\n        return scanlineProcessor;\n    }\n\n    @Override\n    public void receiveDefaultImage(Argb8888Bitmap bitmap) {\n\n    }\n\n    @Override\n    public void receiveAnimationControl(PngAnimationControl control) {\n\n    }\n\n    @Override\n    public Argb8888ScanlineProcessor receiveFrameControl(PngFrameControl control) {\n        return null;\n    }\n\n    @Override\n    public void receiveFrameImage(Argb8888Bitmap bitmap) {\n\n    }\n\n    @Override\n    public Argb8888Bitmap getResult() {\n        return defaultImage;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/chunks/PngAnimationControl.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.chunks;\n\n/**\n * A PngAnimationControl object contains data parsed from the ``acTL``\n * animation control chunk of an animated PNG file.\n */\npublic class PngAnimationControl {\n    public final int numFrames;\n    public final int numPlays;\n\n    public PngAnimationControl(int numFrames, int numPlays) {\n        this.numFrames = numFrames;\n        this.numPlays = numPlays;\n    }\n\n    public boolean loopForever() {\n        return 0 == numPlays;\n    }\n\n    @Override\n    public String toString() {\n        return \"PngAnimationControl{\" +\n                \"numFrames=\" + numFrames +\n                \", numPlays=\" + numPlays +\n                '}';\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/chunks/PngFrameControl.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.chunks;\n\nimport org.jackhuang.hmcl.ui.image.apng.map.PngChunkMap;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A PngFrameControl object contains data parsed from the ``fcTL`` chunk data\n * in an animated PNG File.\n * <p>\n * See https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk\n * <pre>\n *     0    sequence_number       (unsigned int)   Sequence number of the animation chunk, starting from 0\n *     4    width                 (unsigned int)   Width of the following frame\n *     8    height                (unsigned int)   Height of the following frame\n *    12    x_offset              (unsigned int)   X position at which to render the following frame\n *    16    y_offset              (unsigned int)   Y position at which to render the following frame\n *    20    delay_num             (unsigned short) Frame delay fraction numerator\n *    22    delay_den             (unsigned short) Frame delay fraction denominator\n *    24    dispose_op            (byte)           Type of frame area disposal to be done after rendering this frame\n *    25    blend_op              (byte)           Type of frame area rendering for this frame\n * </pre>\n * <p>\n * Delay denominator: from spec, \"if denominator is zero it should be treated as 100ths of second\".\n * <pre>\n * dispose op:\n *    value\n *   0           APNG_DISPOSE_OP_NONE\n *   1           APNG_DISPOSE_OP_BACKGROUND\n *   2           APNG_DISPOSE_OP_PREVIOUS\n *\n * blend op:\n *  value\n *   0       APNG_BLEND_OP_SOURCE\n *   1       APNG_BLEND_OP_OVER\n * </pre>\n */\npublic class PngFrameControl {\n    public final int sequenceNumber;\n    public final int width;\n    public final int height;\n    public final int xOffset;\n    public final int yOffset;\n    public final short delayNumerator;\n    public final short delayDenominator;\n    public final byte disposeOp;\n    public final byte blendOp;\n    List<PngChunkMap> imageChunks = new ArrayList<>(1); // TODO: this may be removed\n\n    public PngFrameControl(int sequenceNumber, int width, int height, int xOffset, int yOffset, short delayNumerator, short delayDenominator, byte disposeOp, byte blendOp) {\n        this.sequenceNumber = sequenceNumber;\n        this.width = width;\n        this.height = height;\n        this.xOffset = xOffset;\n        this.yOffset = yOffset;\n        this.delayNumerator = delayNumerator;\n        this.delayDenominator = delayDenominator == 0 ? 100 : delayDenominator; // APNG spec says zero === 100.\n        this.disposeOp = disposeOp;\n        this.blendOp = blendOp;\n    }\n\n    /**\n     * @return number of milliseconds to show this frame for\n     */\n    public int getDelayMilliseconds() {\n        if (delayDenominator == 1000) {\n            return delayNumerator;\n        } else {\n            // if denom is 100 then need to multiple by 10\n            float f = 1000 / delayDenominator; // 1000/100 -> 10\n            return (int) (delayNumerator * f);\n        }\n    }\n\n    // TODO: can this be removed?\n    public void appendImageData(PngChunkMap chunkMap) {\n        imageChunks.add(chunkMap);\n    }\n\n    // TODO: this may be removed\n    public List<PngChunkMap> getImageChunks() {\n        return imageChunks;\n    }\n\n    @Override\n    public String toString() {\n        return \"PngFrameControl{\" +\n                \"sequenceNumber=\" + sequenceNumber +\n                \", width=\" + width +\n                \", height=\" + height +\n                \", xOffset=\" + xOffset +\n                \", yOffset=\" + yOffset +\n                \", delayNumerator=\" + delayNumerator +\n                \", delayDenominator=\" + delayDenominator +\n                \", disposeOp=\" + disposeOp +\n                \", blendOp=\" + blendOp +\n                '}';\n    }\n\n    // mainly for ease in unit testing\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        PngFrameControl that = (PngFrameControl) o;\n\n        if (sequenceNumber != that.sequenceNumber) return false;\n        if (width != that.width) return false;\n        if (height != that.height) return false;\n        if (xOffset != that.xOffset) return false;\n        if (yOffset != that.yOffset) return false;\n        if (delayNumerator != that.delayNumerator) return false;\n        if (delayDenominator != that.delayDenominator) return false;\n        if (disposeOp != that.disposeOp) return false;\n        return blendOp == that.blendOp;\n\n    }\n\n    @Override\n    public int hashCode() {\n        int result = sequenceNumber;\n        result = 31 * result + width;\n        result = 31 * result + height;\n        result = 31 * result + xOffset;\n        result = 31 * result + yOffset;\n        result = 31 * result + (int) delayNumerator;\n        result = 31 * result + (int) delayDenominator;\n        result = 31 * result + (int) disposeOp;\n        result = 31 * result + (int) blendOp;\n        return result;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/chunks/PngGamma.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.chunks;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\n\n/**\n * A PngGamma object represents data parsed from a ``gAMA`` chunk.\n */\npublic class PngGamma {\n    public final int imageGamma;\n\n    public PngGamma(int imageGamma) {\n        this.imageGamma = imageGamma;\n    }\n\n    public static PngGamma from(DataInputStream dis) throws IOException {\n        return new PngGamma(dis.readInt());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/chunks/PngHeader.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.chunks;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngColourType;\nimport org.jackhuang.hmcl.ui.image.apng.reader.PngReadHelper;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngFeatureException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;\n\nimport java.io.DataInput;\nimport java.io.IOException;\n\n/**\n * Created by aellerton on 10/05/2015.\n */\npublic class PngHeader {\n    /**\n     * Number of pixels (columns) wide.\n     */\n    public final int width;\n\n    /**\n     * Number of pixels (rows) high.\n     */\n    public final int height;\n\n    /**\n     * The bitDepth is the number of bits for each <em>channel</em> of a given pixel.\n     * A better name might be \"bitsPerPixelChannel\" but the name \"bitDepth\" is used\n     * throughout the PNG specification.\n     * <p>\n     * A truecolour image with a bitDepth of 8 means that the red channel of a pixel\n     * has 8 bits (so 256 levels of red), green has 8 bits (256 levels of green), and\n     * blue has 8 bits (so 256 levels of green). That means the total bitsPerPixel\n     * for that bitmap will be 8+8+8 = 24.\n     * <p>\n     * A truecolour <em>with alpha</em> image with bitDepth of 8 will be the same\n     * except every alpha element of every pixel will have 8 bits (so 256 levels of\n     * alpha transparency), meaning that the total bitsPerPixel for that bitmap will\n     * be 8+8+8+8=32.\n     * <p>\n     * A truecolour with alpha image with <em>bitDepth of 16</em> means that each of\n     * red, green blue and alpha have 16-bits respectively, meaning that the total\n     * bitsPerPixel will be 16+16+16+16 = 64.\n     * <p>\n     * A greyscale image (no alpha) with bitDepth of 16 has only a grey channel for\n     * each pixel, so the bitsPerPixel will also be 16.\n     * <p>\n     * But a greyscale image <em>with alpha</em> with a bitDepth of 16 has a grey\n     * channel and an alpha channel, each with 16 bits so the bitsPerPixel will be\n     * 16+16=32.\n     * <p>\n     * As for palette-based images...\n     * <ul>\n     * <li>A monochrome image or image with 2 colour palette has bitDepth=1.</li>\n     * <li>An image with 4 colour palette has bitDepth=2.</li>\n     * <li>An image with 8 colour palette has bitDepth=3.</li>\n     * <li>An image with 16 colour palette has bitDepth=4.</li>\n     * <li>A greyscale image with 16 levels of gray <em>and an alpha channel</em>\n     *   has bitDepth=4 and bitsPerPixel=8 because the gray and the alpha channel\n     *   each have 4 bits.</li>\n     * </ul>\n     *\n     * @see #bitsPerPixel\n     */\n    public final byte bitDepth;\n\n    /**\n     * Every PNG image must be exactly one of the standard types as defined by the\n     * PNG specification. Better names might have been \"imageType\" or \"imageFormat\"\n     * but the name \"colourType\" is used throughout the PNG spec.\n     */\n    public final PngColourType colourType;\n\n    /**\n     * Compression type of the file.\n     * In practice this is redundant: it may be zip and nothing else.\n     */\n    public final byte compressionMethod;\n\n    /**\n     * Filter method used by the file.\n     * In practice this is redundant because the filter types are set in the\n     * specification and have never been (and never will be) extended.\n     */\n    public final byte filterMethod;\n\n    /**\n     * An image is either interlaced or not interlaced.\n     * At the time of writing only non-interlaced is supported by this library.\n     */\n    public final byte interlaceMethod;\n\n    /**\n     * The number of bits that comprise a single pixel in this bitmap (or every\n     * frame if animated). This is distinct from bitDepth.\n     *\n     * @see #bitDepth\n     */\n    public final int bitsPerPixel;\n    public final int bytesPerRow;\n    public final int filterOffset;\n\n    public PngHeader(int width, int height, byte bitDepth, PngColourType colourType) {\n        this(width, height, bitDepth, colourType, (byte) 0, (byte) 0, (byte) 0);\n    }\n\n    public PngHeader(int width, int height, byte bitDepth, PngColourType colourType, byte compressionMethod, byte filterMethod, byte interlaceMethod) {\n        this.width = width;\n        this.height = height;\n        this.bitDepth = bitDepth;\n        this.colourType = colourType;\n        this.compressionMethod = compressionMethod;\n        this.filterMethod = filterMethod;\n        this.interlaceMethod = interlaceMethod;\n        this.bitsPerPixel = bitDepth * colourType.componentsPerPixel;\n        this.bytesPerRow = PngReadHelper.calculateBytesPerRow(width, bitDepth, colourType, interlaceMethod);\n        //this.filterOffset = this.bitsPerPixel < 8 ? 1 : this.bitsPerPixel>>3; // minimum of 1 byte. RGB888 will be 3 bytes. RGBAFFFF is 8 bytes.\n        this.filterOffset = (this.bitsPerPixel + 7) >> 3; // libpng\n\n        // from pypng\n//        # Derived values\n//        # http://www.w3.org/TR/PNG/#6Colour-values\n//        colormap =  bool(self.color_type & 1)\n//        greyscale = not (self.color_type & 2)\n//        alpha = bool(self.color_type & 4)\n//        color_planes = (3,1)[greyscale or colormap]\n//        planes = color_planes + alpha\n    }\n\n    @Override\n    public String toString() {\n        return \"PngHeader{\" +\n                \"width=\" + width +\n                \", height=\" + height +\n                \", bitDepth=\" + bitDepth +\n                \", colourType=\" + colourType +\n                \", compressionMethod=\" + compressionMethod +\n                \", filterMethod=\" + filterMethod +\n                \", interlaceMethod=\" + interlaceMethod +\n                '}';\n    }\n\n    public boolean isInterlaced() {\n        return interlaceMethod == 1;\n    }\n\n    public boolean isZipCompression() {\n        return compressionMethod == 0;\n    }\n\n    public boolean isGreyscale() {\n        return colourType == PngColourType.PNG_GREYSCALE | colourType == PngColourType.PNG_GREYSCALE_WITH_ALPHA;\n    }\n\n    /**\n     * @return true if the image type indicates there is an alpha value for every pixel.\n     * Note that this take into account any transparency or background chunk.\n     */\n    public boolean hasAlphaChannel() {\n        return colourType == PngColourType.PNG_GREYSCALE_WITH_ALPHA | colourType == PngColourType.PNG_TRUECOLOUR_WITH_ALPHA;\n    }\n\n    public static PngHeader makeTruecolour(int width, int height) {\n        return new PngHeader(width, height, (byte) 8, PngColourType.PNG_TRUECOLOUR);\n    }\n\n    public static PngHeader makeTruecolourAlpha(int width, int height) {\n        return new PngHeader(width, height, (byte) 8, PngColourType.PNG_TRUECOLOUR_WITH_ALPHA);\n    }\n\n    public PngHeader adjustFor(PngFrameControl frame) {\n        if (frame == null) {\n            return this;\n        } else {\n            return new PngHeader(frame.width, frame.height, this.bitDepth, this.colourType, this.compressionMethod, this.filterMethod, this.interlaceMethod);\n        }\n    }\n\n    public static void checkHeaderParameters(int width, int height, byte bitDepth, PngColourType colourType, byte compressionMethod, byte filterMethod, byte interlaceMethod) throws PngException {\n\n        switch (bitDepth) {\n            case 1:\n            case 2:\n            case 4:\n            case 8:\n            case 16:\n                break; // all fine\n            default:\n                throw new PngIntegrityException(\"Invalid bit depth \" + bitDepth);\n        }\n\n        // thanks to pypng\n        if (colourType.isIndexed() && bitDepth > 8) {\n            throw new PngIntegrityException(String.format(\n                    \"Indexed images (colour type %d) cannot have bitdepth > 8 (bit depth %d).\" +\n                            \" See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 .\", colourType.code, bitDepth));\n        }\n\n        if (bitDepth < 8 && !colourType.supportsSubByteDepth()) {\n            throw new PngIntegrityException(String.format(\n                    \"Illegal combination of bit depth (%d) and colour type (%d).\" +\n                            \" See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 .\", colourType.code, bitDepth));\n        }\n\n        if (interlaceMethod != 0) {\n            throw new PngFeatureException(\"Interlaced images are not yet supported\");\n        }\n    }\n\n    public static PngHeader from(DataInput dis) throws IOException, PngException {\n        int width = dis.readInt();\n        int height = dis.readInt();\n        byte bitDepth = dis.readByte();\n        PngColourType colourType = PngColourType.fromByte(dis.readByte());\n        byte compressionMethod = dis.readByte();\n        byte filterMethod = dis.readByte();\n        byte interlaceMethod = dis.readByte();\n        checkHeaderParameters(width, height, bitDepth, colourType, compressionMethod, filterMethod, interlaceMethod);\n        return new PngHeader(width, height, bitDepth, colourType, compressionMethod, filterMethod, interlaceMethod);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/chunks/PngPalette.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.chunks;\n\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;\nimport org.jackhuang.hmcl.ui.image.apng.argb8888.Argb8888Palette;\n\nimport java.util.Arrays;\n\n/**\n * A PngPalette object represents an ordered array of RGB (888) colour tuples\n * derived from a PLTE chunk.\n * <p>\n * WARNING: this class may not remain in the API.\n * When implementing the Argb8888 decoders it seems clear that every output\n * format will benefit from a specific palette implementation, so this attempt\n * at a generic palette may be removed.\n *\n * @see Argb8888Palette\n */\npublic class PngPalette {\n    // TODO: should include alpha here? Can then store as int32s?\n    public final byte[] rgb888;\n    public final int[] rgba8888; // Including this duplicate for now. Not sure if will keep it.\n    public final int numColours;\n\n    public static final int LENGTH_RGB_BYTES = 3;\n    public static final int BYTE_INITIAL_ALPHA = 0xff;\n\n    public PngPalette(byte[] rgb888, int[] rgba8888) {\n        this.rgb888 = rgb888;\n        this.rgba8888 = rgba8888;\n        this.numColours = rgb888.length / 3;\n    }\n\n    public static PngPalette from(byte[] source, int first, int length) throws PngException {\n        if (length % LENGTH_RGB_BYTES != 0) {\n            throw new PngIntegrityException(String.format(\"Invalid palette data length: %d (not a multiple of 3)\", length));\n        }\n\n        return new PngPalette(\n                Arrays.copyOfRange(source, first, first + length),\n                rgba8888From(source, first, length)\n        );\n    }\n\n    private static int[] rgba8888From(byte[] source, int first, int length) {\n        int last = first + length;\n        int numColours = length / 3;\n        int[] rgba8888 = new int[numColours];\n        int j = 0;\n        for (int i = first; i < last; i += LENGTH_RGB_BYTES) {\n            rgba8888[j] = source[i] << 24 | source[i + 1] << 16 | source[i + 2] << 8 | BYTE_INITIAL_ALPHA;\n            j++;\n        }\n        return rgba8888;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/error/PngException.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.error;\n\n/**\n * All exceptions in the library are a PngException or subclass of it.\n */\npublic class PngException extends Exception {\n    int code;\n\n    public PngException(int code, String message) {\n        super(message);\n        this.code = code;\n    }\n\n    public PngException(int code, String message, Throwable cause) {\n        super(message, cause);\n        this.code = code;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/error/PngFeatureException.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.error;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngConstants;\n\n/**\n * A PngFeatureException is thrown when a feature used in a specific file\n * is not supported by the pipeline being used. For example, at the time\n * of writing, interlaced image reading is not supported.\n */\npublic class PngFeatureException extends PngException {\n    public PngFeatureException(String message) {\n        super(PngConstants.ERROR_FEATURE_NOT_SUPPORTED, message);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/error/PngIntegrityException.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.error;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngConstants;\n\n/**\n * A PngIntegrityException is thrown when some aspect of the PNG file being\n * loaded is invalid or unacceptable according to the PNG Specification.\n * <p>\n * For example, requesting a 16-bit palette image is invalid, or a colour type\n * outside of the allowed set.\n */\npublic class PngIntegrityException extends PngException {\n    public PngIntegrityException(String message) {\n        super(PngConstants.ERROR_INTEGRITY, message);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/map/PngChunkMap.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.map;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngChunkCode;\n\n/**\n * A single chunk from a PNG file is represented by a PngChunkMap.\n * <p>\n * WARNING: not sure if this API will remain.\n * </p>\n */\npublic class PngChunkMap {\n    /**\n     * The code like IHDR, IPAL, IDAT. If the chunk code is non-standard,\n     * the UNKNOWN code will be used and the codeString will be set.\n     */\n    public PngChunkCode code;\n\n    /**\n     * Number of bytes containing the data portion of the chunk. Note that this excludes\n     * the last 4 bytes that are the CRC checksom.\n     */\n    public int dataLength;\n\n    /**\n     * Integer offset of the first byte of data in this chunk (the byte immediately\n     * following the chunk length 32-bits and the chunk type 32-bits) from byte zero\n     * of the source.\n     */\n    public int dataPosition;\n\n    public int checksum;\n\n    public PngChunkMap(PngChunkCode code, int dataPosition, int dataLength, int checksum) {\n        this.code = code;\n        this.dataLength = dataLength;\n        this.dataPosition = dataPosition;\n        this.checksum = checksum;\n    }\n\n    @Override\n    public String toString() {\n        return \"PngChunkMap{\" +\n                \"letters=\" + code +\n                \", dataLength=\" + dataLength +\n                \", dataPosition=\" + dataPosition +\n                \", checksum=\" + checksum +\n                '}';\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/map/PngMap.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.map;\n\nimport java.util.List;\n\n/**\n * A PngMap represents a map over an entire single PNG file.\n * <p>\n * WARNING: not sure if this API will remain.\n * </p>\n */\npublic class PngMap {\n    public String source;\n    public List<PngChunkMap> chunks;\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/map/PngMapReader.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.map;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngChunkCode;\nimport org.jackhuang.hmcl.ui.image.apng.PngConstants;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.reader.PngReader;\nimport org.jackhuang.hmcl.ui.image.apng.reader.PngSource;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\n\n/**\n * Simple processor that skips all chunk content and ignores checksums, with\n * sole objective of building a map of the contents of a PNG file.\n * <p>\n * WARNING: not sure if this API will remain.\n * </p>\n */\npublic class PngMapReader implements PngReader<PngMap> {\n    PngMap map;\n\n    public PngMapReader(String sourceName) {\n        map = new PngMap();\n        map.source = sourceName;\n        map.chunks = new ArrayList<>(4);\n    }\n\n    @Override\n    public boolean readChunk(PngSource source, int code, int dataLength) throws PngException, IOException {\n        int dataPosition = source.tell();\n        source.skip(dataLength);\n        int chunkChecksum = source.readInt();\n        map.chunks.add(new PngChunkMap(PngChunkCode.from(code), dataPosition, dataLength, chunkChecksum));\n\n        return code == PngConstants.IEND_VALUE;\n    }\n\n    @Override\n    public void finishedChunks(PngSource source) throws PngException, IOException {\n        // NOP\n    }\n\n    @Override\n    public PngMap getResult() {\n        return map;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/package-info.java",
    "content": "/**\n * @see <a href=\"https://github.com/aellerton/japng\">japng</a>\n */\npackage org.jackhuang.hmcl.ui.image.apng;"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/BasicScanlineProcessor.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport org.jackhuang.hmcl.ui.image.apng.util.PartialInflaterInputStream;\n\nimport java.io.FilterInputStream;\nimport java.io.InputStream;\nimport java.util.zip.Inflater;\n\n/**\n * A BasicScanlineProcessor manages a re-entrant java.util.zip.Inflater object and\n * basic bytesPerLine management.\n */\npublic abstract class BasicScanlineProcessor implements PngScanlineProcessor {\n    protected final int bytesPerLine;\n    protected Inflater inflater = new Inflater();\n    //protected PngHeader header;\n    //protected PngScanlineBuffer scanlineReader;\n    //protected PngFrameControl currentFrame;\n\n    //public DefaultScanlineProcessor(PngHeader header, PngScanlineBuffer scanlineReader, PngFrameControl currentFrame) {\n    public BasicScanlineProcessor(int bytesPerScanline) {\n//        this.header = header.adjustFor(currentFrame);\n//        this.currentFrame = currentFrame;\n//        this.scanlineReader = scanlineReader;\n//        this.bytesPerLine = this.header.bytesPerRow;\n        this.bytesPerLine = bytesPerScanline;\n    }\n\n    //@Override\n    //abstract public void processScanline(byte[] bytes, int position);\n\n    @Override\n    public FilterInputStream makeInflaterInputStream(InputStream inputStream) {\n        return new PartialInflaterInputStream(inputStream, inflater);\n    }\n\n    @Override\n    public int getBytesPerLine() {\n        return bytesPerLine;\n    }\n\n    @Override\n    public boolean isFinished() {\n        return inflater.finished();\n    }\n\n//    @Override\n//    public InflaterInputStream connect(InputStream inputStream) {\n//        multipartStream.add(inputStream);\n//        return iis;\n//    }\n\n    @Override\n    public void processTransparentGreyscale(byte k1, byte k0) {\n        // NOP by default\n    }\n\n    @Override\n    public void processTransparentTruecolour(byte r1, byte r0, byte g1, byte g0, byte b1, byte b0) {\n        // NOP by default\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngAnimationType;\nimport org.jackhuang.hmcl.ui.image.apng.PngChunkCode;\nimport org.jackhuang.hmcl.ui.image.apng.PngConstants;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngGamma;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;\nimport org.jackhuang.hmcl.ui.image.apng.map.PngChunkMap;\n\nimport java.io.IOException;\n\n/**\n * The DefaultPngChunkReader is the default PNG chunk-reading workhorse.\n * <p>\n * Note that any chunk types not recognised can be processed in the readOtherChunk()\n * method.\n */\npublic class DefaultPngChunkReader<ResultT> implements PngChunkReader<ResultT> {\n    protected PngChunkProcessor<ResultT> processor;\n    protected boolean seenHeader = false;\n    protected int idatCount = 0;\n    protected int apngSequenceExpect = 0;\n    protected PngAnimationType animationType = PngAnimationType.NOT_ANIMATED;\n    //private PngMainImageOp mainImageOp = PngMainImageOp.MAIN_IMAGE_KEEP;\n\n    public DefaultPngChunkReader(PngChunkProcessor<ResultT> processor) {\n        this.processor = processor;\n    }\n\n    @Override\n    public boolean readChunk(PngSource source, int code, int dataLength) throws PngException, IOException {\n        int dataPosition = source.tell(); // note the start position before any further reads are done.\n\n        if (dataLength < 0) {\n            throw new PngIntegrityException(String.format(\"Corrupted read (Data length %d)\", dataLength));\n        }\n\n        switch (code) {\n            case PngConstants.IHDR_VALUE:\n                readHeaderChunk(source, dataLength);\n                break;\n\n            case PngConstants.IEND_VALUE:\n                // NOP\n                break;\n\n            case PngConstants.gAMA_VALUE:\n                readGammaChunk(source, dataLength);\n                break;\n\n            case PngConstants.bKGD_VALUE:\n                readBackgroundChunk(source, dataLength);\n                break;\n\n            case PngConstants.tRNS_VALUE:\n                readTransparencyChunk(source, dataLength);\n                break;\n\n            case PngConstants.PLTE_VALUE:\n                readPaletteChunk(source, dataLength);\n                break;\n\n            case PngConstants.IDAT_VALUE:\n                readImageDataChunk(source, dataLength);\n                break;\n\n            case PngConstants.acTL_VALUE:\n                readAnimationControlChunk(source, dataLength);\n                break;\n\n            case PngConstants.fcTL_VALUE:\n                readFrameControlChunk(source, dataLength);\n                break;\n\n            case PngConstants.fdAT_VALUE:\n                readFrameImageDataChunk(source, dataLength);\n                break;\n\n            default:\n                readOtherChunk(code, source, dataPosition, dataLength);\n                break;\n        }\n\n        int chunkChecksum = source.readInt();\n        processChunkEnd(code, dataPosition, dataLength, chunkChecksum);\n        return code == PngConstants.IEND_VALUE;\n    }\n\n    @Override\n    public void processChunkEnd(int code, int dataPosition, int dataLength, int chunkChecksum) throws PngException {\n        processor.processChunkMapItem(new PngChunkMap(PngChunkCode.from(code), dataPosition, dataLength, chunkChecksum));\n        //container.chunks.add(new PngChunkMap(PngChunkCode.from(code), dataLength, dataPosition, chunkChecksum));\n    }\n\n    @Override\n    public void readHeaderChunk(PngSource source, int dataLength) throws IOException, PngException {\n        PngHeader header = PngHeader.from(source.getDis());\n        seenHeader = true;\n        processor.processHeader(header); // Hmm. Prefer header = PngHeader.from(source) ?\n    }\n\n    @Override\n    public void readGammaChunk(PngSource source, int dataLength) throws PngException, IOException {\n        processor.processGamma(PngGamma.from(source.getDis()));\n    }\n\n    @Override\n    public void readTransparencyChunk(PngSource source, int dataLength) throws IOException, PngException {\n        // TODO\n        //processor.processTransparency(PngTransparency.from(source, dataLength));\n        //throw new PngFeatureException(\"TODO UP TO HERE\");\n        //source.skip(dataLength);\n\n//        if (dataLength % 3 != 0) {\n//            throw new PngIntegrityException(String.format(\"png spec: palette chunk length must be divisible by 3: %d\", dataLength));\n//        }\n\n        if (source.supportsByteAccess()) {\n            processor.processTransparency(source.getBytes(), source.tell(), dataLength);\n            source.skip(dataLength);\n        } else {\n            byte[] paletteBytes = new byte[dataLength];\n            //ByteStreams.readFully(source.getBis(), paletteBytes);\n            source.getDis().readFully(paletteBytes);\n            processor.processTransparency(paletteBytes, 0, dataLength);\n        }\n    }\n\n    @Override\n    public void readBackgroundChunk(PngSource source, int dataLength) throws IOException, PngException {\n        if (!seenHeader) {\n            throw new PngIntegrityException(\"bKGD chunk received before IHDR chunk\");\n        }\n        // TODO\n        //processor.processBackground(PngBackground.from(source, dataLength);\n        source.skip(dataLength);\n    }\n\n    @Override\n    public void readPaletteChunk(PngSource source, int dataLength) throws IOException, PngException {\n\n        if (dataLength % 3 != 0) {\n            throw new PngIntegrityException(String.format(\"png spec: palette chunk length must be divisible by 3: %d\", dataLength));\n        }\n        // TODO: can check if colour type matches palette type, or if any palette received before (overkill?)\n\n        if (source.supportsByteAccess()) {\n            processor.processPalette(source.getBytes(), source.tell(), dataLength);\n            source.skip(dataLength);\n        } else {\n            byte[] paletteBytes = new byte[dataLength];\n            //ByteStreams.readFully(source.getBis(), paletteBytes);\n            source.getDis().readFully(paletteBytes);\n            processor.processPalette(paletteBytes, 0, dataLength);\n        }\n    }\n\n    @Override\n    public void readImageDataChunk(PngSource source, int dataLength) throws PngException, IOException {\n\n        if (idatCount == 0 && apngSequenceExpect == 0) {\n            // Processing a plain PNG (non animated) IDAT chunk\n            animationType = PngAnimationType.NOT_ANIMATED; // processor.chooseApngImageType(PngAnimationType.NOT_ANIMATED, null);\n        }\n        idatCount++;\n\n        switch (animationType) {\n            case ANIMATED_DISCARD_DEFAULT_IMAGE:\n                // do nothing\n                source.skip(dataLength);\n                break;\n            case ANIMATED_KEEP_DEFAULT_IMAGE:\n                processor.processFrameImageData(source.slice(dataLength), PngChunkCode.IDAT, source.tell(), dataLength);\n                break;\n\n            case NOT_ANIMATED:\n            default:\n                processor.processDefaultImageData(source.slice(dataLength), PngChunkCode.IDAT, source.tell(), dataLength);\n                break;\n        }\n//        source.skip(dataLength);\n    }\n\n    @Override\n    public void readAnimationControlChunk(PngSource source, int dataLength) throws IOException, PngException {\n        if (dataLength != PngConstants.LENGTH_acTL_CHUNK) {\n            throw new PngIntegrityException(String.format(\"acTL chunk length must be %d, not %d\", PngConstants.LENGTH_acTL_CHUNK, dataLength));\n        }\n        processor.processAnimationControl(new PngAnimationControl(source.readInt(), source.readInt()));\n    }\n\n    @Override\n    public void readFrameControlChunk(PngSource source, int dataLength) throws IOException, PngException {\n        if (dataLength != PngConstants.LENGTH_fcTL_CHUNK) {\n            throw new PngIntegrityException(String.format(\"fcTL chunk length must be %d, not %d\", PngConstants.LENGTH_fcTL_CHUNK, dataLength));\n        }\n        int sequence = source.readInt(); // TODO: check sequence # is correct or PngIntegrityException\n\n        if (sequence != apngSequenceExpect) {\n            throw new PngIntegrityException(String.format(\"fctl chunk expected sequence %d but received %d\", apngSequenceExpect, sequence));\n        }\n        apngSequenceExpect++; // ready for next time\n\n        PngFrameControl frame = new PngFrameControl(\n                sequence,\n                source.readInt(), // width\n                source.readInt(), // height\n                source.readInt(), // x offset\n                source.readInt(), // y offset\n                source.readUnsignedShort(), // delay numerator\n                source.readUnsignedShort(), // delay denominator\n                source.readByte(), // dispose op\n                source.readByte() // blend op\n        );\n\n        if (sequence == 0) { // We're at the first frame...\n            if (idatCount == 0) { // Not seen any IDAT chunks yet\n                // APNG Spec says that when the first fcTL chunk is received *before* the first IDAT chunk\n                // the main image of the PNG becomes the first frame in the animation.\n\n                animationType = PngAnimationType.ANIMATED_KEEP_DEFAULT_IMAGE; //processor.chooseApngImageType(PngAnimationType.ANIMATED_KEEP_DEFAULT_IMAGE, frame);\n\n                //mainImageOp = PngMainImageOp.MAIN_IMAGE_STARTS_ANIMATION;\n            } else {\n                //mainImageOp = PngMainImageOp.MAIN_IMAGE_DISCARD;\n                animationType = PngAnimationType.ANIMATED_DISCARD_DEFAULT_IMAGE; // processor.chooseApngImageType(PngAnimationType.ANIMATED_DISCARD_DEFAULT_IMAGE, frame);\n            }\n        } else {\n            // fall through\n        }\n\n        processor.processFrameControl(frame);\n    }\n\n    //public abstract void setMainImageOp(PngMainImageOp op);\n\n    @Override\n    public void readFrameImageDataChunk(PngSource source, int dataLength) throws IOException, PngException {\n        // Note that once the sequence number is confirmed as being correct that there\n        // is no need to retain it in subsequent data.\n        int position = source.tell();\n        int sequence = source.readInt();\n        dataLength -= 4; // After reading the sequence number the data is just like IDAT.\n\n        if (sequence != apngSequenceExpect) {\n            throw new PngIntegrityException(String.format(\"fdAT chunk expected sequence %d but received %d\", apngSequenceExpect, sequence));\n        }\n        apngSequenceExpect++; // for next time\n\n        //processFrameData(sequence, source, dataLength);\n        //processFrameImageData(source, dataLength);\n        processor.processFrameImageData(source.slice(dataLength), PngChunkCode.fdAT, source.tell(), dataLength);\n\n//        PngFrameControl current = container.getCurrentAnimationFrame();\n//\n//        //imageDecoder\n//        // TODO: send image bytes to digester\n//        current.appendImageData(new PngChunkMap(PngChunkCode.fdAT, dataLength, position, 0));\n//\n//        // TODO: skip everything except the frame sequence number\n\n\n//        source.skip(dataLength);\n    }\n\n    @Override\n    public void readOtherChunk(int code, PngSource source, int dataPosition, int dataLength) throws IOException {\n        // If we're not processing it, got to skip it.\n        source.skip(dataLength);\n    }\n\n    @Override\n    public void finishedChunks(PngSource source) throws PngException, IOException {\n    }\n\n    @Override\n    public ResultT getResult() {\n        return processor.getResult();\n    }\n\n    public boolean isSeenHeader() {\n        return seenHeader;\n    }\n\n    public int getIdatCount() {\n        return idatCount;\n    }\n\n    public int getApngSequenceExpect() {\n        return apngSequenceExpect;\n    }\n\n    public PngAnimationType getAnimationType() {\n        return animationType;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngAtOnceSource.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * A simple way to process a PNG file is to read the entire thing into memory.\n * <p>\n * Very bad for very large images (500Mb?)? Yes.\n * But simple for smaller (say <10Mb?) images? Also yes.\n * I'd certainly like to make a lean alternative that processes bytes without reading\n * the whole thing into memory, but it can come later. Patches welcome.\n * <p>\n * WARNING: I'm not sure I'll keep this. I may remove it and do everything with\n * a BufferedInputStream + DataInputStream.\n * </p>\n */\npublic class PngAtOnceSource implements PngSource {\n\n    final byte[] bytes;\n    //private String sourceDescription;\n    private final ByteArrayInputStream bis;\n    private final DataInputStream dis;\n\n    public PngAtOnceSource(byte[] bytes) { //}, String sourceDescription) {\n        this.bytes = bytes;\n        //this.sourceDescription = sourceDescription;\n        this.bis = new ByteArrayInputStream(this.bytes); // never closed because nothing to do for ByteArrayInputStream\n        this.dis = new DataInputStream(this.bis); // never closed because underlying stream doesn't need to be closed.\n    }\n\n    @Override\n    public int getLength() {\n        return bytes == null ? 0 : bytes.length;\n    }\n\n    @Override\n    public boolean supportsByteAccess() {\n        return true;\n    }\n\n    @Override\n    public byte[] getBytes() throws IOException {\n        return bytes;\n    }\n\n    @Override\n    public byte readByte() throws IOException {\n        return dis.readByte();\n    }\n\n    @Override\n    public short readUnsignedShort() throws IOException {\n        return (short) dis.readUnsignedShort();\n    }\n\n    @Override\n    public int readInt() throws IOException {\n        return dis.readInt();\n    }\n\n    @Override\n    public long skip(int chunkLength) throws IOException {\n        return dis.skip(chunkLength);\n    }\n\n    @Override\n    public int tell() {\n        return bytes.length - bis.available();\n    }\n\n    @Override\n    public int available() {\n        return bis.available();\n    }\n\n//    @Override\n//    public String getSourceDescription() {\n//        return sourceDescription;\n//    }\n\n//    @Override\n//    public ByteArrayInputStream getBis() {\n//        return bis;\n//    }\n\n    @Override\n    public DataInputStream getDis() {\n        return dis;\n    }\n\n    @Override\n    public InputStream slice(int dataLength) throws IOException {\n        // The below would be fine but in this case we have the full byte stream anyway...\n        //return ByteStreams.limit(bis, dataLength);\n        InputStream slice = new ByteArrayInputStream(bytes, tell(), dataLength);\n        this.skip(dataLength);\n        return slice;\n    }\n\n    public static PngAtOnceSource from(InputStream is) throws IOException {\n        return new PngAtOnceSource(is.readAllBytes());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngChunkProcessor.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngChunkCode;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngGamma;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.map.PngChunkMap;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * While a PngChunkReader does the low-level reading of a PNG file, it delegates <em>processing</em> of\n * the data to a PngChunkProcessor instance. The class is generic about a specific result type because\n * the intention of a chunk processor is to yield some result, which might be a bitmap, a sequence of\n * bitmaps, a map of chunks, etc.\n */\npublic interface PngChunkProcessor<ResultT> {\n\n    /**\n     * Subclasses can further process the header immediately after PngHeader has been read.\n     * A subclass might elect to bail out of subsequent processing, e.g. if the image is too big\n     * or has an invalid format.\n     *\n     * @param header of image\n     */\n    void processHeader(PngHeader header) throws PngException;\n\n    /**\n     * Process the PngGamma object parsed from a ``gaMA`` chunk.\n     *\n     * @param gamma parsed gamma data.\n     * @throws PngException\n     */\n    void processGamma(PngGamma gamma) throws PngException;\n\n    /**\n     * Process the raw trNS data from the PNG file.\n     * <p>\n     * Note that this data is passed raw (but in a complete byte array).\n     * It is not parsed into an object because it allows concrete implementations to define\n     * their own way to handle the data. And it is not provided as in InputStream because\n     * the length of the data is well defined based on the PNG colour type.\n     *\n     * @param bytes    array to read data from.\n     * @param position offset into the array to begin reading data from.\n     * @param length   number of bytes to read from the array.\n     * @throws PngException\n     */\n    void processTransparency(byte[] bytes, int position, int length) throws PngException;\n\n    /**\n     * A PLTE chunk has been read and needs to be processed.\n     * <p>\n     * A byte array is provided to process. A pre-defined palette class is not loaded because\n     * different rendering targets may elect to load the palette in different ways.\n     * For example, rendering to an ARGB palette may load the RGB data and organise as\n     * an array of 32-bit integer ARGB values.\n     * </p>\n     *\n     * @param bytes    representing the loaded palette data. Each colour is represented by three\n     *                 bytes, 1 each for red, green and blue.\n     * @param position that the colour values begin in bytes\n     * @param length   number of bytes that the palette bytes continue. Guaranteed to be a\n     *                 multiple of 3.\n     * @throws PngException\n     */\n    void processPalette(byte[] bytes, int position, int length) throws PngException;\n\n    /**\n     * Process one IDAT chunk the \"default image\" bitmap in a given file.\n     * <p>\n     * The \"default\", \"main\" or \"primary image\" of a file is what you'd generally\n     * think of as \"the\" image in file, namely the image constructed of IDAT chunks\n     * and displayed by any normal viewer or browser.\n     * <p>\n     * I theorise (without proof) that most PNG files in the wild have a single\n     * IDAT chunk representing all pixels in the main image, but the PNG specification\n     * requires loaders to handle the case where there are multiple IDAT chunks.\n     * In the case of multiple chunks the data continues precisely where the last\n     * IDAT chunk left off. The same applies to fdAT chunks.\n     * <p>\n     * The data is provided not in any pre-parsed object or even as a byte array because\n     * the bytes can be arbitrarily long. The InputStream provided is a slice of the\n     * containing source and will terminate at the end of the IDAT chunk.\n     *\n     * @param inputStream data source containing all bytes in the image data chunk.\n     * @param code        of the chunk which should always be IDAT in this case.\n     *                    Compare to this.processFrameImageData.\n     * @param position    absolute position within the file. hmmm may be removed\n     * @param length      length of bytes of the hunk. hmmm may be removed\n     * @throws IOException\n     * @throws PngException\n     */\n    void processDefaultImageData(InputStream inputStream, PngChunkCode code, int position, int length) throws IOException, PngException;\n\n    void processAnimationControl(PngAnimationControl animationControl) throws PngException;\n\n    void processFrameControl(PngFrameControl frameControl) throws PngException;\n\n    /**\n     * The reader has determined that bitmap data needs to be processed for an animation frame.\n     * The image data may be from an IDAT chunk or an fdAT chunk.\n     * <p>\n     * Whether the \"default image\" in a PNG file is to be part of the animation or discarded\n     * is determed by the placement of the first fcTL chunk in the file. See the APNG specification.\n     *\n     * @param inputStream data source containing all bytes in the frame image data chunk.\n     * @param code        either IDAT or fdAT. Designed to allow an implementation to detect whether the\n     *                    frame is from the \"main image\" or a subsequent image. May not be necessary.\n     * @param position    absolute position within the file. hmmm may be removed\n     * @param length      length of bytes of the hunk. hmmm may be removed\n     * @throws IOException\n     * @throws PngException\n     */\n    void processFrameImageData(InputStream inputStream, PngChunkCode code, int position, int length) throws IOException, PngException;\n\n    /**\n     * A subclass can record data about the chunk here.\n     * <p>\n     * WARNING: this API may be removed. I'm not sure if it is useful.\n     *\n     * @param chunkMapItem represents a \"map\" of a single chunk in the file.\n     * @throws PngException\n     */\n    void processChunkMapItem(PngChunkMap chunkMapItem) throws PngException;\n\n    /**\n     * @return the result of processing all chunks. Only ever called after the file is fully read.\n     */\n    ResultT getResult();\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngChunkReader.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\n\nimport java.io.IOException;\n\n/**\n * Created by aellerton on 15/05/2015.\n */\npublic interface PngChunkReader<ResultT> extends PngReader<ResultT> {\n    @Override\n    boolean readChunk(PngSource source, int code, int dataLength) throws PngException, IOException;\n\n    void processChunkEnd(int code, int dataPosition, int dataLength, int chunkChecksum) throws PngException;\n\n    void readHeaderChunk(PngSource source, int dataLength) throws IOException, PngException;\n\n    void readGammaChunk(PngSource source, int dataLength) throws PngException, IOException;\n\n    /**\n     * Process the tRNS chunk.\n     * <p>\n     * From http://www.w3.org/TR/PNG/#11tRNS:\n     * <p>\n     * The tRNS chunk specifies either alpha values that are associated with palette entries (for indexed-colour\n     * images) or a single transparent colour (for greyscale and truecolour images). The tRNS chunk contains:\n     * <p>\n     * Colour type 0:\n     * <p>\n     * Grey sample value 2 bytes\n     * <p>\n     * Colour type 2:\n     * <p>\n     * Red sample value\t2 bytes\n     * Blue sample value\t2 bytes\n     * Green sample value\t2 bytes\n     * <p>\n     * Colour type 3:\n     * <p>\n     * Alpha for palette index 0\t1 byte\n     * Alpha for palette index 1\t1 byte\n     * ...etc...\t1 byte\n     * <p>\n     * Note that for palette colour types the number of transparency entries may be less than the number of\n     * entries in the palette. In that case all missing entries are assumed to be fully opaque.\n     *\n     * @param source\n     * @param dataLength\n     */\n    void readTransparencyChunk(PngSource source, int dataLength) throws IOException, PngException;\n\n    /**\n     * Process the bKGD chunk.\n     * <p>\n     * From http://www.w3.org/TR/PNG/#11bKGD:\n     * <p>\n     * The bKGD chunk specifies a default background colour to present the image against.\n     * If there is any other preferred background, either user-specified or part of a larger\n     * page (as in a browser), the bKGD chunk should be ignored. The bKGD chunk contains:\n     * <p>\n     * Colour types 0 and 4\n     * Greyscale\t2 bytes\n     * Colour types 2 and 6\n     * Red\t2 bytes\n     * Green\t2 bytes\n     * Blue\t2 bytes\n     * Colour type 3\n     * Palette index\t1 byte\n     * <p>\n     * For colour type 3 (indexed-colour), the value is the palette index of the colour to be used as background.\n     * <p>\n     * For colour types 0 and 4 (greyscale, greyscale with alpha), the value is the grey level to be used as\n     * background in the range 0 to (2bitdepth)-1. For colour types 2 and 6 (truecolour, truecolour with alpha),\n     * the values are the colour to be used as background, given as RGB samples in the range 0 to (2bitdepth)-1.\n     * In each case, for consistency, two bytes per sample are used regardless of the image bit depth. If the image\n     * bit depth is less than 16, the least significant bits are used and the others are 0.\n     *\n     * @param source\n     * @param dataLength\n     * @throws IOException\n     */\n    void readBackgroundChunk(PngSource source, int dataLength) throws IOException, PngException;\n\n    void readPaletteChunk(PngSource source, int dataLength) throws IOException, PngException;\n\n    /**\n     * Read the IDAT chunk.\n     * <p>\n     * The default implementation skips the data, deferring to the finishedChunks() method\n     * to process the data. Key reasons to do this:\n     * <ul>\n     *     <li>There could be multiple IDAT chunks and the streams need to\n     *     be concatenated. This could be done in a single pass but it's more\n     *     complicated and is not the objective of this implementation.</li>\n     *     <li>This might be an APNG file and the IDAT chunk(s) are to be skipped.</li>\n     * </ul>\n     *\n     * @param source     to read from\n     * @param dataLength in bytes of the data\n     * @throws PngException\n     * @throws IOException\n     */\n    void readImageDataChunk(PngSource source, int dataLength) throws PngException, IOException;\n\n    void readAnimationControlChunk(PngSource source, int dataLength) throws IOException, PngException;\n\n    /**\n     * Read the fcTL chunk.\n     * <p>\n     * See https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk\n     *\n     * @param source\n     * @param dataLength\n     * @throws IOException\n     * @throws PngException\n     */\n    void readFrameControlChunk(PngSource source, int dataLength) throws IOException, PngException;\n\n    void readFrameImageDataChunk(PngSource source, int dataLength) throws IOException, PngException;\n\n    /**\n     * Give subclasses the opportunity to process a chunk code that was not recognised.\n     *\n     * @param code         chunk type as integer.\n     * @param source       of PNG data, positioned to read the data bytes.\n     * @param dataPosition offset from absolute start of bytes that data beings.\n     * @param dataLength   of this chunk\n     * @throws IOException\n     */\n    void readOtherChunk(int code, PngSource source, int dataPosition, int dataLength) throws IOException;\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngReadHelper.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngColourType;\nimport org.jackhuang.hmcl.ui.image.apng.PngConstants;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\n\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * PNG reading support functions.\n */\npublic class PngReadHelper {\n\n    /**\n     * Return true if the next 8 bytes of the InputStream match the\n     * standard 8 byte PNG Signature, and false if they do not match.\n     * <p>\n     * If the stream ends before the signature is read an EOFExceptoin is thrown.\n     * </p><p>\n     * Note that no temporary buffer is allocated.\n     * </p>\n     *\n     * @param is Stream to read the 8-byte PNG signature from\n     * @throws IOException in the case of any IO exception, including EOF.\n     */\n    public static boolean readSignature(InputStream is) throws IOException {\n        for (int i = 0; i < PngConstants.LENGTH_SIGNATURE; i++) {\n            int b = is.read();\n            if (b < 0) {\n                throw new EOFException();\n            }\n            if ((byte) b != PngConstants.BYTES_SIGNATURE[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Reads a given InputStream using the PngReader to process all chunks until the file is\n     * finished, then returning the result from the PngReader.\n     *\n     * @param is        stream to read\n     * @param reader    reads and delegates processing of all chunks\n     * @param <ResultT> result of the processing\n     * @return result of the processing of the InputStream.\n     * @throws PngException\n     */\n    public static <ResultT> ResultT read(InputStream is, PngReader<ResultT> reader) throws PngException {\n        try {\n            if (!PngReadHelper.readSignature(is)) {\n                throw new PngException(PngConstants.ERROR_NOT_PNG, \"Failed to read PNG signature\");\n            }\n\n//            PngAtOnceSource source = PngAtOnceSource.from(is);//, sourceName);\n            PngSource source = new PngStreamSource(is);\n            boolean finished = false;\n\n            while (!finished) {\n                int length = source.readInt();\n                int code = source.readInt();\n                finished = reader.readChunk(source, code, length);\n            }\n\n            if (source.available() > 0) { // Should trailing data after IEND always be error or can configure as warning?\n                throw new PngException(PngConstants.ERROR_EOF_EXPECTED, String.format(\"Completed IEND but %d byte(s) remain\", source.available()));\n            }\n\n            reader.finishedChunks(source);\n\n            return reader.getResult();\n\n        } catch (EOFException e) {\n            throw new PngException(PngConstants.ERROR_EOF, \"Unexpected EOF\", e);\n        } catch (IOException e) {\n            throw new PngException(PngConstants.ERROR_UNKNOWN_IO_FAILURE, e.getMessage(), e);\n        }\n\n    }\n\n    /**\n     * Number of bytes per row is key to processing scanlines.\n     * <p>\n     * TODO: should this by on the header?\n     */\n    public static int calculateBytesPerRow(int pixelsPerRow, byte bitDepth, PngColourType colourType, byte interlaceMethod) {\n        if (interlaceMethod != 0) {\n            throw new IllegalStateException(\"Interlaced images not yet supported\");\n\n        } else {\n            int numComponentsPerPixel = colourType.componentsPerPixel; // or \"channels\". e.g. \"gray and alpha\" means two components.\n            int bitsPerComponent = bitDepth; // e.g. \"4\" means 4 bits for gray, 4 bits for alpha\n            int bitsPerPixel = bitsPerComponent * numComponentsPerPixel;  // e.g. total of 8 bits per pixel\n            int bitsPerRow = bitsPerPixel * pixelsPerRow;\n\n            //?? use that (bitDepth+7)>>3 ... thing?\n            //   unsigned int bpp = (row_info->pixel_depth + 7) >> 3; // libpng\n\n            // If there are less than 8 bits per pixel, then ensure the last byte of the row is padded.\n            int bytesPerRow = bitsPerRow / 8 + ((0 == (bitsPerRow % 8)) ? 0 : (8 - bitsPerRow % 8));\n            return 1 + bytesPerRow; // need 1 byte for filter code\n        }\n    }\n\n    private PngReadHelper() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngReader.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\n\nimport java.io.IOException;\n\n/**\n * All PngReader implementations need to read a specific single chunk and to return\n * a result of some form.\n */\npublic interface PngReader<ResultT> {\n    boolean readChunk(PngSource source, int code, int dataLength) throws PngException, IOException;\n\n    void finishedChunks(PngSource source) throws PngException, IOException;\n\n    ResultT getResult();\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngScanlineProcessor.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport java.io.FilterInputStream;\nimport java.io.InputStream;\n\n/**\n * A PngScanlineProcessor receives decompressed, de-filtered scanlines, one by one, in order\n * from top to bottom. Interlaced scanlines are not yet supported. The scanline data is not\n * parsed or expanded in any form, so if the PNG is 1-bit monochrome or 16 bit RGBA, the bytes\n * are provided as-is. The job of the processor is to read and reformat each pixel as\n * appropriate for the destination bitmap.\n * <p>\n * Note that a single instance of a PngScanlineProcessor is intended to process a single\n * bitmap and that, when the bitmap is complete, the isFinished() method will return true.\n * In the case of an animated PNG, it is expected that a new PngScanlineProcessor will be\n * created to service the new bitmap.\n * <p>\n * Note: I wonder if this would be better named PngScanlineTransformer because its primary\n * purpose is to convert pixels from raw file format to destination format.\n */\npublic interface PngScanlineProcessor {\n\n    /**\n     * A PngScanlineProcessor is responsible for decompressing the raw (compressed) data.\n     * This is important because there can be more than one IDAT and fdAT chunks for a single\n     * image, and decompression must occur seamlessly across those chunks, so the decompression\n     * must be stateful across multiple invocations of makeInflaterInputStream. In practice, it\n     * seems to be rare that there are multiple image data chunks for a single image.\n     *\n     * @param inputStream stream over raw compressed PNG image data.\n     * @return an InflaterInputStream that decompresses the current stream.\n     */\n    FilterInputStream makeInflaterInputStream(InputStream inputStream);\n\n    /**\n     * The processScanline method is invoked when the raw image data has been first decompressed\n     * then de-filtered. The PngScanlineProcessor then must interpret each byte according to the\n     * specific image format and render it to the destination as appropriate.\n     *\n     * @param bytes    decompressed, de-filtered bytes, ready for processing.\n     * @param position the position that scanline pixels begin in the array. Note that it is\n     *                 the responsibility of the PngScanlineProcessor to know exactly how many\n     *                 bytes are in the row. This is because a single processor might process\n     *                 different frames in an APNG, and each frame can have a different width\n     *                 (up to the maximum set in the PNG header).\n     */\n    void processScanline(byte[] bytes, int position);\n\n    int getBytesPerLine();\n\n    void processTransparentGreyscale(byte k1, byte k0);\n\n    void processTransparentTruecolour(byte r1, byte r0, byte g1, byte g0, byte b1, byte b0);\n\n    /**\n     * A PngScanlineProcessor must be able to decompress more than one consecutive IDAT or fdAT\n     * chunks. This is because the PNG specification states that a valid PNG file can have more\n     * than one consecutive IDAT (and by extension, fdAT) chunks and the data therein must be\n     * treated as if concatenated.\n     *\n     * @return true when the data for the current bitmap is complete.\n     */\n    boolean isFinished();\n\n    //InflaterInputStream connect(InputStream inputStream);\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngSource.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * The API for reading any PNG source.\n *\n * @see PngAtOnceSource\n * <p>\n * WARNING: this may be removed in favour of direct use of InputStream objects.\n */\npublic interface PngSource {\n    int getLength();\n\n    boolean supportsByteAccess();\n\n    byte[] getBytes() throws IOException;\n\n    byte readByte() throws IOException;\n\n    short readUnsignedShort() throws IOException;\n\n    int readInt() throws IOException;\n\n    long skip(int chunkLength) throws IOException;\n\n    int tell();\n\n    int available();\n\n//    String getSourceDescription();\n\n//    ByteArrayInputStream getBis();\n\n    DataInputStream getDis();\n\n    InputStream slice(int dataLength) throws IOException;\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngStreamSource.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.reader;\n\nimport org.jackhuang.hmcl.ui.image.apng.util.InputStreamSlice;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Created by aellerton on 13/06/2015.\n */\npublic class PngStreamSource implements PngSource {\n    final InputStream src;\n    //private final ByteArrayInputStream bis;\n    private final DataInputStream dis;\n\n    public PngStreamSource(InputStream src) {\n        this.src = src;\n        //this.bis = new ByteArrayInputStream(this.bytes); // never closed because nothing to do for ByteArrayInputStream\n        this.dis = new DataInputStream(this.src); // never closed because underlying stream doesn't need to be closed.\n\n    }\n\n    @Override\n    public int getLength() {\n        return 0; // TODO?\n    }\n\n    @Override\n    public boolean supportsByteAccess() {\n        return false;\n    }\n\n    @Override\n    public byte[] getBytes() throws IOException {\n        return null;\n    }\n\n    @Override\n    public byte readByte() throws IOException {\n        return dis.readByte();\n    }\n\n    @Override\n    public short readUnsignedShort() throws IOException {\n        return (short) dis.readUnsignedShort();\n    }\n\n    @Override\n    public int readInt() throws IOException {\n        return dis.readInt();\n    }\n\n    @Override\n    public long skip(int chunkLength) throws IOException {\n        return dis.skip(chunkLength);\n    }\n\n    @Override\n    public int tell() {\n        return 0; // TODO\n    }\n\n    @Override\n    public int available() {\n        try {\n            return dis.available(); // TODO: adequate?\n        } catch (IOException e) {\n            return 0; // TODO\n        }\n    }\n\n//    @Override\n//    public ByteArrayInputStream getBis() {\n//        return null;\n//    }\n\n    @Override\n    public DataInputStream getDis() {\n        return dis;\n    }\n\n    @Override\n    public InputStream slice(int dataLength) {\n        return new InputStreamSlice(src, dataLength);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/util/InputStreamSlice.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.util;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * A specific-length slice of some other InputStream.\n */\npublic class InputStreamSlice extends InputStream {\n    protected final InputStream src;\n    protected final int length;\n    protected int position = 0;\n    protected boolean atEof = false;\n\n    public InputStreamSlice(InputStream src, int length) {\n        this.src = src;\n        this.length = length;\n    }\n\n    public int tell() {\n        return position;\n    }\n\n    @Override\n    public int read(byte[] b) throws IOException {\n        if (atEof || position >= length) {\n            atEof = true;\n            return -1;\n        }\n        int rv = src.read(b, 0, Math.min(b.length, length - position));\n        if (rv > 0) {\n            position += rv;\n        }\n        return rv;\n    }\n\n    @Override\n    public int read(byte[] b, int off, int len) throws IOException {\n        if (atEof || position >= length) {\n            atEof = true;\n            return -1;\n        }\n        int rv = src.read(b, off, Math.min(len, length - position));\n        if (rv > 0) {\n            position += rv;\n        }\n        return rv;\n    }\n\n    @Override\n    public long skip(long n) throws IOException {\n        if (atEof || position >= length) {\n            atEof = true;\n            return -1;\n        }\n        n = Math.min(available(), n); // calculate maximum skip\n        long remaining = n;\n        while (remaining > 0) {\n            long skipped = src.skip(remaining); // attempt to skip that much\n            if (skipped <= 0) {\n                throw new IOException(\"Failed to skip a total of \" + n + \" bytes in stream; \" + remaining + \" bytes remained but \" + skipped + \" returned from skip.\");\n            }\n            remaining -= skipped;\n        }\n        position += n; // adjust position by correct skip\n        return n;\n    }\n\n    @Override\n    public int available() throws IOException {\n        if (atEof || position >= length) {\n            return 0;\n        }\n        return length - position;\n    }\n\n    @Override\n    public int read() throws IOException {\n        if (atEof || position >= length) {\n            atEof = true;\n            return -1;\n        }\n\n        int rv = src.read();\n        if (rv < 0) {\n            atEof = true;\n        } else if (rv > 0) {\n            this.position += rv;\n        }\n        return rv;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/util/PartialInflaterInputStream.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.util;\n\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.zip.DataFormatException;\nimport java.util.zip.Inflater;\nimport java.util.zip.InflaterInputStream;\nimport java.util.zip.ZipException;\n\n/**\n * A hacked copy of the Java standard java.util.zip.InflaterInputStream that\n * is modified to work with files that have multiple IDAT (or fdAT) chunks for\n * a single bitmap.\n * <p>\n * The default java.util.zip.InflaterInputStream by David Connelly works a treat\n * except that if the java.util.zip.Inflater is expecting data when the stream\n * runs out of data then the stream will die with an exception.\n * <p>\n * The PartialInflaterInputStream, on the other hand, quietly allows the EOF\n * to be returned and leaves the Inflater in the \"waiting for more data\" data,\n * ready to pick up when the next IDAT (or fdAT) chunk starts.\n * <p>\n * If you're wondering why I didn't just subclass InflaterInputStream, there are\n * a few reasons. The main change is in the ``fill()`` method, which needs to\n * call the method ``ensureOpen()`` but can't because it is private. It is also\n * ideal to return an int but the original ``fill`` method is void. Then there\n * is a change to the ``read()`` method and that references a number of private\n * fields. In short, monkeying around in an attempt to reuse the original seemed\n * more expensive than just copying the original code.\n * <p>\n * The original class documentation states:\n * \"This class implements a stream filter for uncompressing data in the\n * \"deflate\" compression format. It is also used as the basis for other\n * decompression filters, such as GZIPInputStream.\"\n * </blockquote>\n *\n * @see InflaterInputStream\n */\npublic class PartialInflaterInputStream extends FilterInputStream {\n\n    /**\n     * Decompressor for this stream.\n     */\n    protected Inflater inf;\n\n    /**\n     * Input buffer for decompression.\n     */\n    protected byte[] buf;\n\n    /**\n     * Length of input buffer.\n     */\n    protected int len;\n\n    private boolean closed = false;\n    // this flag is set to true after EOF has reached\n    private boolean reachEOF = false;\n\n    /**\n     * Check to make sure that this stream has not been closed\n     */\n    private void ensureOpen() throws IOException {\n        if (closed) {\n            throw new IOException(\"Stream closed\");\n        }\n    }\n\n\n    /**\n     * Creates a new input stream with the specified decompressor and\n     * buffer size.\n     *\n     * @param in   the input stream\n     * @param inf  the decompressor (\"inflater\")\n     * @param size the input buffer size\n     * @throws IllegalArgumentException if size is <= 0\n     */\n    public PartialInflaterInputStream(InputStream in, Inflater inf, int size) {\n        super(in);\n        if (in == null || inf == null) {\n            throw new NullPointerException();\n        } else if (size <= 0) {\n            throw new IllegalArgumentException(\"buffer size <= 0\");\n        }\n        this.inf = inf;\n        buf = new byte[size];\n    }\n\n    /**\n     * Creates a new input stream with the specified decompressor and a\n     * default buffer size.\n     *\n     * @param in  the input stream\n     * @param inf the decompressor (\"inflater\")\n     */\n    public PartialInflaterInputStream(InputStream in, Inflater inf) {\n        this(in, inf, 512);\n    }\n\n    boolean usesDefaultInflater = false;\n\n    /**\n     * Creates a new input stream with a default decompressor and buffer size.\n     *\n     * @param in the input stream\n     */\n    public PartialInflaterInputStream(InputStream in) {\n        this(in, new Inflater());\n        usesDefaultInflater = true;\n    }\n\n    private byte[] singleByteBuf = new byte[1];\n\n    /**\n     * Reads a byte of uncompressed data. This method will block until\n     * enough input is available for decompression.\n     *\n     * @return the byte read, or -1 if end of compressed input is reached\n     * @throws IOException if an I/O error has occurred\n     */\n    public int read() throws IOException {\n        ensureOpen();\n        return read(singleByteBuf, 0, 1) == -1 ? -1 : singleByteBuf[0] & 0xff;\n    }\n\n    /**\n     * Reads uncompressed data into an array of bytes. If <code>len</code> is not\n     * zero, the method will block until some input can be decompressed; otherwise,\n     * no bytes are read and <code>0</code> is returned.\n     *\n     * @param b   the buffer into which the data is read\n     * @param off the start offset in the destination array <code>b</code>\n     * @param len the maximum number of bytes read\n     * @return the actual number of bytes read, or -1 if the end of the\n     * compressed input is reached or a preset dictionary is needed\n     * @throws NullPointerException      If <code>b</code> is <code>null</code>.\n     * @throws IndexOutOfBoundsException If <code>off</code> is negative,\n     *                                   <code>len</code> is negative, or <code>len</code> is greater than\n     *                                   <code>b.length - off</code>\n     * @throws ZipException              if a ZIP format error has occurred\n     * @throws IOException               if an I/O error has occurred\n     */\n    public int read(byte[] b, int off, int len) throws IOException {\n        ensureOpen();\n        if (b == null) {\n            throw new NullPointerException();\n        } else if (off < 0 || len < 0 || len > b.length - off) {\n            throw new IndexOutOfBoundsException();\n        } else if (len == 0) {\n            return 0;\n        }\n        try {\n            int n;\n            while ((n = inf.inflate(b, off, len)) == 0) {\n                if (inf.finished() || inf.needsDictionary()) {\n                    reachEOF = true;\n                    return -1;\n                }\n                if (inf.needsInput()) {\n                    n = fill();\n                    if (n <= 0) { // TODO: or only < 0?\n                        return -1;\n                    }\n                }\n            }\n            return n;\n        } catch (DataFormatException e) {\n            String s = e.getMessage();\n            throw new ZipException(s != null ? s : \"Invalid ZLIB data format\");\n        }\n    }\n\n    /**\n     * Returns 0 after EOF has been reached, otherwise always return 1.\n     * <p>\n     * Programs should not count on this method to return the actual number\n     * of bytes that could be read without blocking.\n     *\n     * @return 1 before EOF and 0 after EOF.\n     * @throws IOException if an I/O error occurs.\n     *\n     */\n    public int available() throws IOException {\n        ensureOpen();\n        if (reachEOF) {\n            return 0;\n        } else {\n            return 1;\n        }\n    }\n\n    private byte[] b = new byte[512];\n\n    /**\n     * Skips specified number of bytes of uncompressed data.\n     *\n     * @param n the number of bytes to skip\n     * @return the actual number of bytes skipped.\n     * @throws IOException              if an I/O error has occurred\n     * @throws IllegalArgumentException if n < 0\n     */\n    public long skip(long n) throws IOException {\n        if (n < 0) {\n            throw new IllegalArgumentException(\"negative skip length\");\n        }\n        ensureOpen();\n        int max = (int) Math.min(n, Integer.MAX_VALUE);\n        int total = 0;\n        while (total < max) {\n            int len = max - total;\n            if (len > b.length) {\n                len = b.length;\n            }\n            len = read(b, 0, len);\n            if (len == -1) {\n                reachEOF = true;\n                break;\n            }\n            total += len;\n        }\n        return total;\n    }\n\n    /**\n     * Closes this input stream and releases any system resources associated\n     * with the stream.\n     *\n     * @throws IOException if an I/O error has occurred\n     */\n    public void close() throws IOException {\n        if (!closed) {\n            if (usesDefaultInflater)\n                inf.end();\n            in.close();\n            closed = true;\n        }\n    }\n\n    /**\n     * Fills input buffer with more data to decompress.\n     *\n     * @throws IOException if an I/O error has occurred\n     */\n    protected int fill() throws IOException {\n        ensureOpen();\n        int n = in.read(buf, 0, buf.length);\n        /* A. Ellerton: remove the failure\n        if (len == -1) {\n            throw new EOFException(\"Unexpected end of ZLIB input stream\");\n        }\n        inf.setInput(buf, 0, len);\n        */\n        if (n > 0) {\n            len = n;\n            inf.setInput(buf, 0, len);\n        }\n        return n;\n    }\n\n    /**\n     * Tests if this input stream supports the <code>mark</code> and\n     * <code>reset</code> methods. The <code>markSupported</code>\n     * method of <code>InflaterInputStream</code> returns\n     * <code>false</code>.\n     *\n     * @return a <code>boolean</code> indicating if this stream type supports\n     * the <code>mark</code> and <code>reset</code> methods.\n     * @see InputStream#mark(int)\n     * @see InputStream#reset()\n     */\n    public boolean markSupported() {\n        return false;\n    }\n\n    /**\n     * Marks the current position in this input stream.\n     *\n     * <p> The <code>mark</code> method of <code>InflaterInputStream</code>\n     * does nothing.\n     *\n     * @param readlimit the maximum limit of bytes that can be read before\n     *                  the mark position becomes invalid.\n     * @see InputStream#reset()\n     */\n    public void mark(int readlimit) {\n    }\n\n    /**\n     * Repositions this stream to the position at the time the\n     * <code>mark</code> method was last called on this input stream.\n     *\n     * <p> The method <code>reset</code> for class\n     * <code>InflaterInputStream</code> does nothing except throw an\n     * <code>IOException</code>.\n     *\n     * @throws IOException if this method is invoked.\n     * @see InputStream#mark(int)\n     * @see IOException\n     */\n    public void reset() throws IOException {\n        throw new IOException(\"mark/reset not supported\");\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/util/PngContainer.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.util;\n\nimport org.jackhuang.hmcl.ui.image.apng.chunks.*;\nimport org.jackhuang.hmcl.ui.image.apng.map.PngChunkMap;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A PngContainer represents the parsed content of a PNG file.\n * <p>\n * The original idea was that all implementations can use this as a \"container\"\n * for representing the data, but I think it is too generic to be useful.\n * <p>\n * WARNING: not sure if this API will remain.\n * </p>\n */\npublic class PngContainer {\n    public List<PngChunkMap> chunks = new ArrayList<>(4);\n    public PngHeader header;\n    public PngGamma gamma;\n    public PngPalette palette;\n    //PngTransparency transarency;\n    //PngBackground background;\n\n    public PngAnimationControl animationControl;\n    public List<PngFrameControl> animationFrames;\n    public PngFrameControl currentFrame;\n    public boolean hasDefaultImage = false;\n\n    public PngHeader getHeader() {\n        return header;\n    }\n\n    public PngGamma getGamma() {\n        return gamma;\n    }\n\n    public boolean isAnimated() {\n        return animationControl != null;// && animationControl.numFrames > 1;\n    }\n\n    /* TODO need?\n    public PngAnimationControl getAnimationControl() {\n        return animationControl;\n    }\n\n    public List<PngFrameControl> getAnimationFrames() {\n        return animationFrames;\n    }\n\n    public PngFrameControl getCurrentAnimationFrame() throws PngException {\n        if (animationFrames.isEmpty()) {\n            throw new PngIntegrityException(\"No animation frames yet\");\n        }\n        return animationFrames.get(animationFrames.size()-1);\n    }\n    */\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/util/PngContainerBuilder.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.util;\n\n/**\n * Returns the PngContainer built by the PngContainerProcessor.\n * WARNING: this may be removed.\n */\npublic class PngContainerBuilder extends PngContainerProcessor<PngContainer> {\n\n//    @Override\n//    public PngAnimationType chooseApngImageType(PngAnimationType type, PngFrameControl currentFrame) throws PngException {\n//        return type;\n//    }\n\n    @Override\n    public PngContainer getResult() {\n        return getContainer();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/util/PngContainerProcessor.java",
    "content": "// Copy from https://github.com/aellerton/japng\n// Licensed under the Apache License, Version 2.0.\n\npackage org.jackhuang.hmcl.ui.image.apng.util;\n\nimport org.jackhuang.hmcl.ui.image.apng.PngChunkCode;\nimport org.jackhuang.hmcl.ui.image.apng.chunks.*;\nimport org.jackhuang.hmcl.ui.image.apng.reader.PngChunkProcessor;\nimport org.jackhuang.hmcl.ui.image.apng.error.PngException;\nimport org.jackhuang.hmcl.ui.image.apng.map.PngChunkMap;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\n\n\n/**\n * TODO: need to keep this?\n */\npublic abstract class PngContainerProcessor<ResultT> implements PngChunkProcessor<ResultT> {\n    protected final PngContainer container;\n\n    public PngContainerProcessor() {\n        this.container = new PngContainer();\n    }\n\n    @Override\n    public void processHeader(PngHeader header) throws PngException {\n        container.header = header;\n    }\n\n    @Override\n    public void processGamma(PngGamma pngGamma) {\n        container.gamma = pngGamma;\n    }\n\n\n    @Override\n    public void processPalette(byte[] bytes, int position, int length) throws PngException {\n        container.palette = PngPalette.from(bytes, position, length);\n    }\n\n\n    @Override\n    public void processTransparency(byte[] bytes, int position, int length) throws PngException {\n        // NOP\n    }\n\n    @Override\n    public void processAnimationControl(PngAnimationControl animationControl) {\n        container.animationControl = animationControl;\n        container.animationFrames = new ArrayList<PngFrameControl>(container.animationControl.numFrames);\n    }\n\n    @Override\n    public void processFrameControl(PngFrameControl pngFrameControl) throws PngException {\n        container.animationFrames.add(pngFrameControl);\n        container.currentFrame = pngFrameControl;\n//        imageDataProcessor = builder.makeFrameImageProcessor(container.currentFrame);\n    }\n\n    @Override\n    public void processDefaultImageData(InputStream inputStream, PngChunkCode code, int position, int length) throws IOException, PngException {\n        if (null != container.currentFrame) {\n            throw new IllegalStateException(\"Attempt to process main frame image data but an animation frame is in place\");\n        }\n        container.hasDefaultImage = true;\n        // TODO: keep this?\n        processImageDataStream(inputStream);\n    }\n\n    @Override\n    public void processFrameImageData(InputStream inputStream, PngChunkCode code, int position, int length) throws IOException, PngException {\n        if (null == container.currentFrame) {\n            throw new IllegalStateException(\"Attempt to process animation frame image data without a frame in place\");\n        }\n        container.currentFrame.appendImageData(new PngChunkMap(code, position, length, 0));\n        // TODO: keep this?\n        processImageDataStream(inputStream);\n    }\n\n    protected void processImageDataStream(InputStream inputStream) throws IOException, PngException {\n        inputStream.skip(inputStream.available());\n    }\n\n    @Override\n    public void processChunkMapItem(PngChunkMap pngChunkMap) {\n        container.chunks.add(pngChunkMap);\n    }\n\n    public PngContainer getContainer() {\n        return container;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/AnimationImageImpl.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.image.internal;\n\nimport javafx.animation.Interpolator;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.beans.value.WritableValue;\nimport javafx.scene.image.PixelFormat;\nimport javafx.scene.image.WritableImage;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.image.AnimationImage;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * @author Glavo\n */\npublic final class AnimationImageImpl extends WritableImage implements AnimationImage {\n\n    private Animation animation;\n    private final int[][] frames;\n    private final int[] durations;\n    private final int cycleCount;\n\n    public AnimationImageImpl(int width, int height,\n                              int[][] frames, int[] durations, int cycleCount) {\n        super(width, height);\n\n        if (frames.length != durations.length) {\n            throw new IllegalArgumentException(\"frames.length != durations.length\");\n        }\n\n        this.frames = frames;\n        this.durations = durations;\n        this.cycleCount = cycleCount;\n\n        play();\n    }\n\n    public void play() {\n        if (animation == null) {\n            animation = new Animation(this);\n            animation.timeline.play();\n        }\n    }\n\n    private void updateImage(int frameIndex) {\n        final int width = (int) getWidth();\n        final int height = (int) getHeight();\n        final int[] frame = frames[frameIndex];\n        this.getPixelWriter().setPixels(0, 0,\n                width, height,\n                PixelFormat.getIntArgbInstance(),\n                frame, 0, width\n        );\n    }\n\n    private static final class Animation implements WritableValue<Integer> {\n        private final Timeline timeline = new Timeline();\n        private final WeakReference<AnimationImageImpl> imageRef;\n\n        private Integer value;\n\n        private Animation(AnimationImageImpl image) {\n            this.imageRef = new WeakReference<>(image);\n            timeline.setCycleCount(image.cycleCount);\n\n            int duration = 0;\n\n            for (int i = 0; i < image.frames.length; ++i) {\n                timeline.getKeyFrames().add(\n                        new KeyFrame(Duration.millis(duration),\n                                new KeyValue(this, i, Interpolator.DISCRETE)));\n\n                duration = duration + image.durations[i];\n            }\n\n            timeline.getKeyFrames().add(new KeyFrame(Duration.millis(duration)));\n        }\n\n        @Override\n        public Integer getValue() {\n            return value;\n        }\n\n        @Override\n        public void setValue(Integer value) {\n            this.value = value;\n\n            AnimationImageImpl image = imageRef.get();\n            if (image == null) {\n                timeline.stop();\n                return;\n            }\n            image.updateImage(value);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.google.gson.*;\nimport javafx.geometry.Insets;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.image.Image;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.construct.ComponentList;\nimport org.jackhuang.hmcl.ui.construct.LineButton;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class AboutPage extends StackPane {\n\n    private final WeakListenerHolder holder = new WeakListenerHolder();\n\n    public AboutPage() {\n        ComponentList about = new ComponentList();\n        {\n            var launcher = LineButton.createExternalLinkButton(Metadata.PUBLISH_URL);\n            launcher.setLargeTitle(true);\n            launcher.setLeading(FXUtils.newBuiltinImage(\"/assets/img/icon.png\"));\n            launcher.setTitle(\"Hello Minecraft! Launcher\");\n            launcher.setSubtitle(Metadata.VERSION);\n\n            var author = LineButton.createExternalLinkButton(\"https://space.bilibili.com/1445341\");\n            author.setLargeTitle(true);\n            author.setLeading(FXUtils.newBuiltinImage(\"/assets/img/yellow_fish.png\"));\n            author.setTitle(\"huanghongxun\");\n            author.setSubtitle(i18n(\"about.author.statement\"));\n\n            about.getContent().setAll(launcher, author);\n        }\n\n        ComponentList thanks = loadIconedTwoLineList(\"/assets/about/thanks.json\");\n\n        ComponentList deps = loadIconedTwoLineList(\"/assets/about/deps.json\");\n\n        ComponentList legal = new ComponentList();\n        {\n            var copyright = LineButton.createExternalLinkButton(Metadata.ABOUT_URL);\n            copyright.setLargeTitle(true);\n            copyright.setTitle(i18n(\"about.copyright\"));\n            copyright.setSubtitle(i18n(\"about.copyright.statement\"));\n\n            var claim = LineButton.createExternalLinkButton(Metadata.EULA_URL);\n            claim.setLargeTitle(true);\n            claim.setTitle(i18n(\"about.claim\"));\n            claim.setSubtitle(i18n(\"about.claim.statement\"));\n\n            var openSource = LineButton.createExternalLinkButton(\"https://github.com/HMCL-dev/HMCL\");\n            openSource.setLargeTitle(true);\n            openSource.setTitle(i18n(\"about.open_source\"));\n            openSource.setSubtitle(i18n(\"about.open_source.statement\"));\n\n            legal.getContent().setAll(copyright, claim, openSource);\n        }\n\n        VBox content = new VBox(16);\n        content.setPadding(new Insets(10));\n        content.getChildren().setAll(\n                ComponentList.createComponentListTitle(i18n(\"about\")),\n                about,\n\n                ComponentList.createComponentListTitle(i18n(\"about.thanks_to\")),\n                thanks,\n\n                ComponentList.createComponentListTitle(i18n(\"about.dependency\")),\n                deps,\n\n                ComponentList.createComponentListTitle(i18n(\"about.legal\")),\n                legal\n        );\n\n\n        ScrollPane scrollPane = new ScrollPane(content);\n        scrollPane.setFitToWidth(true);\n        FXUtils.smoothScrolling(scrollPane);\n        getChildren().setAll(scrollPane);\n    }\n\n    private static Image loadImage(String url) {\n        return url.startsWith(\"/\")\n                ? FXUtils.newBuiltinImage(url)\n                : new Image(url);\n    }\n\n    private ComponentList loadIconedTwoLineList(String path) {\n        ComponentList componentList = new ComponentList();\n\n        InputStream input = FXUtils.class.getResourceAsStream(path);\n        if (input == null) {\n            LOG.warning(\"Resources not found: \" + path);\n            return componentList;\n        }\n\n        try {\n            JsonArray array = JsonUtils.fromJsonFully(input, JsonArray.class);\n\n            for (JsonElement element : array) {\n                JsonObject obj = element.getAsJsonObject();\n\n                var button = new LineButton();\n                button.setLargeTitle(true);\n\n                if (obj.get(\"externalLink\") instanceof JsonPrimitive externalLink) {\n                    button.setTrailingIcon(SVG.OPEN_IN_NEW);\n\n                    String link = externalLink.getAsString();\n                    button.setOnAction(event -> FXUtils.openLink(link));\n                }\n\n                if (obj.has(\"image\")) {\n                    JsonElement image = obj.get(\"image\");\n                    if (image.isJsonPrimitive()) {\n                        button.setLeading(loadImage(image.getAsString()));\n                    } else if (image.isJsonObject()) {\n                        holder.add(FXUtils.onWeakChangeAndOperate(Themes.darkModeProperty(), darkMode -> {\n                            button.setLeading(darkMode\n                                    ? loadImage(image.getAsJsonObject().get(\"dark\").getAsString())\n                                    : loadImage(image.getAsJsonObject().get(\"light\").getAsString())\n                            );\n                        }));\n                    }\n                }\n\n                if (obj.get(\"title\") instanceof JsonPrimitive title)\n                    button.setTitle(title.getAsString());\n                else if (obj.get(\"titleLocalized\") instanceof JsonPrimitive titleLocalized)\n                    button.setTitle(i18n(titleLocalized.getAsString()));\n\n                if (obj.get(\"subtitle\") instanceof JsonPrimitive subtitle)\n                    button.setSubtitle(subtitle.getAsString());\n                else if (obj.get(\"subtitleLocalized\") instanceof JsonPrimitive subtitleLocalized)\n                    button.setSubtitle(i18n(subtitleLocalized.getAsString()));\n\n                componentList.getContent().add(button);\n            }\n        } catch (IOException | JsonParseException e) {\n            LOG.warning(\"Failed to load list: \" + path, e);\n        }\n\n        return componentList;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.jfoenix.controls.*;\nimport javafx.beans.binding.Bindings;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\n\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.ToggleGroup;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.task.FetchTask;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.javafx.SafeStringConverter;\n\nimport java.net.Proxy;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Function;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class DownloadSettingsPage extends StackPane {\n\n    private final WeakListenerHolder holder = new WeakListenerHolder();\n\n    public DownloadSettingsPage() {\n        VBox content = new VBox(10);\n        content.setPadding(new Insets(10));\n        content.setFillWidth(true);\n        ScrollPane scrollPane = new ScrollPane(content);\n        FXUtils.smoothScrolling(scrollPane);\n        scrollPane.setFitToWidth(true);\n        getChildren().setAll(scrollPane);\n\n        {\n            var downloadSource = new ComponentList();\n            downloadSource.getStyleClass().add(\"card-non-transparent\");\n            {\n\n                var autoChooseDownloadSource = new LineToggleButton();\n                autoChooseDownloadSource.setTitle(i18n(\"settings.launcher.download_source.auto\"));\n                autoChooseDownloadSource.selectedProperty().bindBidirectional(config().autoChooseDownloadTypeProperty());\n\n                Function<String, String> converter = key -> i18n(\"download.provider.\" + key);\n                Function<String, String> descriptionConverter = key -> {\n                    String bundleKey = \"download.provider.\" + key + \".desc\";\n                    return I18n.hasKey(bundleKey) ? i18n(bundleKey) : null;\n                };\n\n                var versionListSourcePane = new LineSelectButton<String>();\n                versionListSourcePane.disableProperty().bind(autoChooseDownloadSource.selectedProperty().not());\n                versionListSourcePane.setTitle(i18n(\"settings.launcher.version_list_source\"));\n                versionListSourcePane.setConverter(converter);\n                versionListSourcePane.setDescriptionConverter(descriptionConverter);\n                versionListSourcePane.setItems(DownloadProviders.AUTO_PROVIDERS.keySet());\n                versionListSourcePane.valueProperty().bindBidirectional(config().versionListSourceProperty());\n\n                var downloadSourcePane = new LineSelectButton<String>();\n                downloadSourcePane.disableProperty().bind(autoChooseDownloadSource.selectedProperty());\n                downloadSourcePane.setTitle(i18n(\"settings.launcher.download_source\"));\n                downloadSourcePane.setConverter(converter);\n                downloadSourcePane.setDescriptionConverter(descriptionConverter);\n                downloadSourcePane.setItems(DownloadProviders.DIRECT_PROVIDERS.keySet());\n                downloadSourcePane.valueProperty().bindBidirectional(config().downloadTypeProperty());\n\n                downloadSource.getContent().setAll(autoChooseDownloadSource, versionListSourcePane, downloadSourcePane);\n            }\n\n            content.getChildren().addAll(ComponentList.createComponentListTitle(i18n(\"settings.launcher.download_source\")), downloadSource);\n        }\n\n        {\n            VBox downloadThreads = new VBox(16);\n            downloadThreads.getStyleClass().add(\"card-non-transparent\");\n            {\n                {\n                    JFXCheckBox chkAutoDownloadThreads = new JFXCheckBox(i18n(\"settings.launcher.download.threads.auto\"));\n                    VBox.setMargin(chkAutoDownloadThreads, new Insets(8, 0, 0, 0));\n                    chkAutoDownloadThreads.selectedProperty().bindBidirectional(config().autoDownloadThreadsProperty());\n                    downloadThreads.getChildren().add(chkAutoDownloadThreads);\n\n                    chkAutoDownloadThreads.selectedProperty().addListener((a, b, newValue) -> {\n                        if (newValue) {\n                            config().downloadThreadsProperty().set(FetchTask.DEFAULT_CONCURRENCY);\n                        }\n                    });\n                }\n\n                {\n                    HBox hbox = new HBox(8);\n                    hbox.setStyle(\"-fx-view-order: -1;\"); // prevent the indicator from being covered by the hint\n                    hbox.setAlignment(Pos.CENTER);\n                    hbox.setPadding(new Insets(0, 0, 0, 30));\n                    hbox.disableProperty().bind(config().autoDownloadThreadsProperty());\n                    Label label = new Label(i18n(\"settings.launcher.download.threads\"));\n\n                    JFXSlider slider = new JFXSlider(1, 256, 64);\n                    HBox.setHgrow(slider, Priority.ALWAYS);\n\n                    JFXTextField threadsField = new JFXTextField();\n                    FXUtils.setLimitWidth(threadsField, 60);\n                    FXUtils.bindInt(threadsField, config().downloadThreadsProperty());\n\n                    AtomicBoolean changedByTextField = new AtomicBoolean(false);\n                    FXUtils.onChangeAndOperate(config().downloadThreadsProperty(), value -> {\n                        changedByTextField.set(true);\n                        slider.setValue(value.intValue());\n                        changedByTextField.set(false);\n                    });\n                    slider.valueProperty().addListener((value, oldVal, newVal) -> {\n                        if (changedByTextField.get()) return;\n                        config().downloadThreadsProperty().set(value.getValue().intValue());\n                    });\n\n                    hbox.getChildren().setAll(label, slider, threadsField);\n                    downloadThreads.getChildren().add(hbox);\n                }\n\n                {\n                    HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);\n                    VBox.setMargin(hintPane, new Insets(0, 0, 0, 30));\n                    hintPane.disableProperty().bind(config().autoDownloadThreadsProperty());\n                    hintPane.setText(i18n(\"settings.launcher.download.threads.hint\"));\n                    downloadThreads.getChildren().add(hintPane);\n                }\n            }\n\n            content.getChildren().addAll(ComponentList.createComponentListTitle(i18n(\"download\")), downloadThreads);\n        }\n\n        {\n            VBox proxyList = new VBox(10);\n            proxyList.getStyleClass().add(\"card-non-transparent\");\n\n            HBox proxyTypePane = new HBox();\n            {\n                proxyTypePane.setPadding(new Insets(10, 0, 0, 0));\n\n                ToggleGroup proxyConfigurationGroup = new ToggleGroup();\n\n                JFXRadioButton chkProxyDefault = new JFXRadioButton(i18n(\"settings.launcher.proxy.default\"));\n                chkProxyDefault.setUserData(null);\n                chkProxyDefault.setToggleGroup(proxyConfigurationGroup);\n\n                JFXRadioButton chkProxyNone = new JFXRadioButton(i18n(\"settings.launcher.proxy.none\"));\n                chkProxyNone.setUserData(Proxy.Type.DIRECT);\n                chkProxyNone.setToggleGroup(proxyConfigurationGroup);\n\n                JFXRadioButton chkProxyHttp = new JFXRadioButton(i18n(\"settings.launcher.proxy.http\"));\n                chkProxyHttp.setUserData(Proxy.Type.HTTP);\n                chkProxyHttp.setToggleGroup(proxyConfigurationGroup);\n\n\n                JFXRadioButton chkProxySocks = new JFXRadioButton(i18n(\"settings.launcher.proxy.socks\"));\n                chkProxySocks.setUserData(Proxy.Type.SOCKS);\n                chkProxySocks.setToggleGroup(proxyConfigurationGroup);\n\n                if (config().hasProxy()) {\n                    Proxy.Type proxyType = config().getProxyType();\n                    if (proxyType == Proxy.Type.DIRECT) {\n                        chkProxyNone.setSelected(true);\n                    } else if (proxyType == Proxy.Type.HTTP) {\n                        chkProxyHttp.setSelected(true);\n                    } else if (proxyType == Proxy.Type.SOCKS) {\n                        chkProxySocks.setSelected(true);\n                    } else {\n                        chkProxyNone.setSelected(true);\n                    }\n                } else {\n                    chkProxyDefault.setSelected(true);\n                }\n\n                holder.add(FXUtils.onWeakChange(proxyConfigurationGroup.selectedToggleProperty(), toggle -> {\n                    Proxy.Type proxyType = toggle != null ? (Proxy.Type) toggle.getUserData() : null;\n\n                    if (proxyType == null) {\n                        config().setHasProxy(false);\n                        config().setProxyType(null);\n                    } else {\n                        config().setHasProxy(true);\n                        config().setProxyType(proxyType);\n                    }\n                }));\n\n                proxyTypePane.getChildren().setAll(chkProxyDefault, chkProxyNone, chkProxyHttp, chkProxySocks);\n                proxyList.getChildren().add(proxyTypePane);\n            }\n\n            VBox proxyPane = new VBox();\n            {\n                proxyPane.disableProperty().bind(\n                        Bindings.createBooleanBinding(() ->\n                                        !config().hasProxy() || config().getProxyType() == null || config().getProxyType() == Proxy.Type.DIRECT,\n                                config().hasProxyProperty(),\n                                config().proxyTypeProperty()));\n\n                ColumnConstraints colHgrow = new ColumnConstraints();\n                colHgrow.setHgrow(Priority.ALWAYS);\n\n                {\n                    GridPane gridPane = new GridPane();\n                    gridPane.setPadding(new Insets(0, 0, 0, 30));\n                    gridPane.setHgap(20);\n                    gridPane.setVgap(10);\n                    gridPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);\n                    gridPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());\n\n                    {\n                        Label host = new Label(i18n(\"settings.launcher.proxy.host\"));\n                        GridPane.setRowIndex(host, 1);\n                        GridPane.setColumnIndex(host, 0);\n                        gridPane.getChildren().add(host);\n                    }\n\n                    {\n                        JFXTextField txtProxyHost = new JFXTextField();\n                        GridPane.setRowIndex(txtProxyHost, 1);\n                        GridPane.setColumnIndex(txtProxyHost, 1);\n                        gridPane.getChildren().add(txtProxyHost);\n                        FXUtils.bindString(txtProxyHost, config().proxyHostProperty());\n                    }\n\n                    {\n                        Label port = new Label(i18n(\"settings.launcher.proxy.port\"));\n                        GridPane.setRowIndex(port, 2);\n                        GridPane.setColumnIndex(port, 0);\n                        gridPane.getChildren().add(port);\n                    }\n\n                    {\n                        JFXTextField txtProxyPort = new JFXTextField();\n                        GridPane.setFillWidth(txtProxyPort, false);\n                        txtProxyPort.setMaxWidth(200);\n                        GridPane.setRowIndex(txtProxyPort, 2);\n                        GridPane.setColumnIndex(txtProxyPort, 1);\n                        FXUtils.setValidateWhileTextChanged(txtProxyPort, true);\n                        gridPane.getChildren().add(txtProxyPort);\n\n                        FXUtils.bind(txtProxyPort, config().proxyPortProperty(), SafeStringConverter.fromInteger()\n                                .restrict(it -> it >= 0 && it <= 0xFFFF)\n                                .fallbackTo(0)\n                                .asPredicate(Validator.addTo(txtProxyPort)));\n                    }\n                    proxyPane.getChildren().add(gridPane);\n                }\n\n                VBox chkProxyAuthenticationPane = new VBox();\n                {\n                    chkProxyAuthenticationPane.setPadding(new Insets(20, 0, 20, 5));\n\n                    JFXCheckBox chkProxyAuthentication = new JFXCheckBox(i18n(\"settings.launcher.proxy.authentication\"));\n                    chkProxyAuthenticationPane.getChildren().add(chkProxyAuthentication);\n                    chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());\n\n                    proxyPane.getChildren().add(chkProxyAuthenticationPane);\n                }\n\n                GridPane authPane = new GridPane();\n                {\n                    authPane.setPadding(new Insets(0, 0, 0, 30));\n                    authPane.setHgap(20);\n                    authPane.setVgap(10);\n                    authPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);\n                    authPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());\n                    authPane.disableProperty().bind(config().hasProxyAuthProperty().not());\n\n                    {\n                        Label username = new Label(i18n(\"settings.launcher.proxy.username\"));\n                        GridPane.setRowIndex(username, 0);\n                        GridPane.setColumnIndex(username, 0);\n                        authPane.getChildren().add(username);\n                    }\n\n                    {\n                        JFXTextField txtProxyUsername = new JFXTextField();\n                        GridPane.setRowIndex(txtProxyUsername, 0);\n                        GridPane.setColumnIndex(txtProxyUsername, 1);\n                        authPane.getChildren().add(txtProxyUsername);\n                        FXUtils.bindString(txtProxyUsername, config().proxyUserProperty());\n                    }\n\n                    {\n                        Label password = new Label(i18n(\"settings.launcher.proxy.password\"));\n                        GridPane.setRowIndex(password, 1);\n                        GridPane.setColumnIndex(password, 0);\n                        authPane.getChildren().add(password);\n                    }\n\n                    {\n                        JFXPasswordField txtProxyPassword = new JFXPasswordField();\n                        GridPane.setRowIndex(txtProxyPassword, 1);\n                        GridPane.setColumnIndex(txtProxyPassword, 1);\n                        authPane.getChildren().add(txtProxyPassword);\n                        txtProxyPassword.textProperty().bindBidirectional(config().proxyPassProperty());\n                    }\n\n                    proxyPane.getChildren().add(authPane);\n                    proxyList.getChildren().add(proxyPane);\n                }\n            }\n            content.getChildren().addAll(ComponentList.createComponentListTitle(i18n(\"settings.launcher.proxy\")), proxyList);\n        }\n\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport javafx.geometry.Insets;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.construct.ComponentList;\nimport org.jackhuang.hmcl.ui.construct.LineButton;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\nimport org.jackhuang.hmcl.Metadata;\n\npublic class FeedbackPage extends SpinnerPane {\n\n    private final WeakListenerHolder holder = new WeakListenerHolder();\n\n    public FeedbackPage() {\n        VBox content = new VBox();\n        content.setPadding(new Insets(10));\n        content.setSpacing(10);\n        content.setFillWidth(true);\n        ScrollPane scrollPane = new ScrollPane(content);\n        scrollPane.setFitToWidth(true);\n        FXUtils.smoothScrolling(scrollPane);\n        setContent(scrollPane);\n\n        ComponentList groups = new ComponentList();\n        {\n            var users = LineButton.createExternalLinkButton(Metadata.GROUPS_URL);\n            users.setLargeTitle(true);\n            users.setLeading(FXUtils.newBuiltinImage(\"/assets/img/icon.png\"));\n            users.setTitle(i18n(\"contact.chat.qq_group\"));\n            users.setSubtitle(i18n(\"contact.chat.qq_group.statement\"));\n\n            var discord = LineButton.createExternalLinkButton(\"https://discord.gg/jVvC7HfM6U\");\n            discord.setLargeTitle(true);\n            discord.setLeading(FXUtils.newBuiltinImage(\"/assets/img/discord.png\"));\n            discord.setTitle(i18n(\"contact.chat.discord\"));\n            discord.setSubtitle(i18n(\"contact.chat.discord.statement\"));\n\n            groups.getContent().setAll(users, discord);\n        }\n\n        ComponentList feedback = new ComponentList();\n        {\n            var github = LineButton.createExternalLinkButton(\"https://github.com/HMCL-dev/HMCL/issues/new/choose\");\n            github.setLargeTitle(true);\n            github.setTitle(i18n(\"contact.feedback.github\"));\n            github.setSubtitle(i18n(\"contact.feedback.github.statement\"));\n\n            holder.add(FXUtils.onWeakChangeAndOperate(Themes.darkModeProperty(), darkMode -> {\n                github.setLeading(darkMode\n                        ? FXUtils.newBuiltinImage(\"/assets/img/github-white.png\")\n                        : FXUtils.newBuiltinImage(\"/assets/img/github.png\"));\n            }));\n\n            feedback.getContent().setAll(github);\n        }\n\n        content.getChildren().addAll(\n                ComponentList.createComponentListTitle(i18n(\"contact.chat\")),\n                groups,\n                ComponentList.createComponentListTitle(i18n(\"contact.feedback\")),\n                feedback\n        );\n\n        this.setContent(content);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/HelpPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.google.gson.annotations.SerializedName;\nimport javafx.geometry.Insets;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.ComponentList;\nimport org.jackhuang.hmcl.ui.construct.LineButton;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.io.HttpRequest;\n\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class HelpPage extends SpinnerPane {\n\n    private final VBox content;\n\n    public HelpPage() {\n        content = new VBox();\n        content.setPadding(new Insets(10));\n        content.setSpacing(10);\n        content.setFillWidth(true);\n        ScrollPane scrollPane = new ScrollPane(content);\n        scrollPane.setFitToWidth(true);\n        FXUtils.smoothScrolling(scrollPane);\n        setContent(scrollPane);\n\n        var docPane = LineButton.createExternalLinkButton(Metadata.DOCS_URL);\n        docPane.setLargeTitle(true);\n        docPane.setTitle(i18n(\"help.doc\"));\n        docPane.setSubtitle(i18n(\"help.detail\"));\n\n        ComponentList doc = new ComponentList();\n        doc.getContent().setAll(docPane);\n        content.getChildren().add(doc);\n\n        loadHelp();\n    }\n\n    private void loadHelp() {\n        showSpinner();\n        Task.supplyAsync(() -> HttpRequest.GET(Metadata.DOCS_URL + \"/index.json\").getJson(listTypeOf(HelpCategory.class)))\n                .thenAcceptAsync(Schedulers.javafx(), helpCategories -> {\n                    for (HelpCategory category : helpCategories) {\n                        ComponentList categoryPane = new ComponentList();\n\n                        for (Help help : category.items()) {\n                            var item = LineButton.createExternalLinkButton(help.url());\n                            item.setLargeTitle(true);\n                            item.setTitle(help.title());\n                            item.setSubtitle(help.subtitle());\n\n                            categoryPane.getContent().add(item);\n                        }\n\n                        content.getChildren().add(ComponentList.createComponentListTitle(category.title()));\n                        content.getChildren().add(categoryPane);\n                    }\n                    hideSpinner();\n                }).start();\n    }\n\n    @JsonSerializable\n    private record HelpCategory(\n            @SerializedName(\"title\") String title,\n            @SerializedName(\"items\") List<Help> items) {\n    }\n\n    @JsonSerializable\n    private record Help(\n            @SerializedName(\"title\") String title,\n            @SerializedName(\"subtitle\") String subtitle,\n            @SerializedName(\"url\") String url) {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.jfoenix.controls.*;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.java.JavaDistribution;\nimport org.jackhuang.hmcl.download.java.JavaPackageType;\nimport org.jackhuang.hmcl.download.java.JavaRemoteVersion;\nimport org.jackhuang.hmcl.download.java.disco.*;\nimport org.jackhuang.hmcl.game.GameJavaVersion;\nimport org.jackhuang.hmcl.java.JavaInfo;\nimport org.jackhuang.hmcl.java.JavaManager;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.DialogCloseEvent;\nimport org.jackhuang.hmcl.ui.construct.DialogPane;\nimport org.jackhuang.hmcl.ui.construct.JFXHyperlink;\nimport org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.Result;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.CancellationException;\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.Lang.resolveException;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class JavaDownloadDialog extends StackPane {\n\n    public static Runnable showDialogAction(DownloadProvider downloadProvider) {\n        Platform platform = Platform.SYSTEM_PLATFORM;\n\n        List<GameJavaVersion> supportedVersions = GameJavaVersion.getSupportedVersions(platform);\n\n        EnumSet<DiscoJavaDistribution> distributions = EnumSet.noneOf(DiscoJavaDistribution.class);\n        for (DiscoJavaDistribution distribution : DiscoJavaDistribution.values()) {\n            if (distribution.isSupport(platform)) {\n                distributions.add(distribution);\n            }\n        }\n\n        return supportedVersions.isEmpty() && distributions.isEmpty()\n                ? null\n                : () -> Controllers.dialog(new JavaDownloadDialog(downloadProvider, platform, supportedVersions, distributions));\n    }\n\n    private final DownloadProvider downloadProvider;\n    private final Platform platform;\n    private final List<GameJavaVersion> supportedGameJavaVersions;\n    private final EnumSet<DiscoJavaDistribution> distributions;\n\n    private JavaDownloadDialog(DownloadProvider downloadProvider, Platform platform, List<GameJavaVersion> supportedGameJavaVersions, EnumSet<DiscoJavaDistribution> distributions) {\n        this.downloadProvider = downloadProvider;\n        this.platform = platform;\n        this.supportedGameJavaVersions = supportedGameJavaVersions;\n        this.distributions = distributions;\n\n        if (!supportedGameJavaVersions.isEmpty()) {\n            this.getChildren().add(new DownloadMojangJava());\n        } else {\n            this.getChildren().add(new DownloadDiscoJava());\n        }\n    }\n\n    private final class DownloadMojangJava extends DialogPane {\n        private final List<JFXCheckBox> checkboxes = new ArrayList<>();\n\n        DownloadMojangJava() {\n            setTitle(i18n(\"java.download.title\"));\n\n            VBox vbox = new VBox(16);\n            Label prompt = new Label(i18n(\"java.download.prompt\"));\n            vbox.getChildren().add(prompt);\n\n            setValid(false);\n            InvalidationListener updateValidStatus = e -> {\n                for (JFXCheckBox box : checkboxes) {\n                    if (!box.isDisabled() && box.isSelected()) {\n                        setValid(true);\n                        return;\n                    }\n                }\n                setValid(false);\n            };\n\n            for (GameJavaVersion version : supportedGameJavaVersions) {\n                JFXCheckBox button = new JFXCheckBox(\"Java \" + version.majorVersion());\n                button.setUserData(version);\n\n                if (JavaManager.REPOSITORY.isInstalled(platform, version)) {\n                    button.setDisable(true);\n                    button.setSelected(true);\n                } else {\n                    button.selectedProperty().addListener(updateValidStatus);\n                }\n\n                vbox.getChildren().add(button);\n                checkboxes.add(button);\n            }\n\n            setBody(vbox);\n\n            if (!distributions.isEmpty()) {\n                JFXHyperlink more = new JFXHyperlink(i18n(\"java.download.more\"));\n                more.setOnAction(event -> JavaDownloadDialog.this.getChildren().setAll(new DownloadDiscoJava()));\n                setActions(warningLabel, more, acceptPane, cancelButton);\n            } else\n                setActions(warningLabel, acceptPane, cancelButton);\n        }\n\n        @Override\n        protected void onAccept() {\n            fireEvent(new DialogCloseEvent());\n\n            List<GameJavaVersion> selectedVersions = new ArrayList<>();\n            for (JFXCheckBox box : checkboxes) {\n                if (!box.isDisabled() && box.isSelected()) {\n                    selectedVersions.add((GameJavaVersion) box.getUserData());\n                }\n            }\n\n            if (selectedVersions.isEmpty())\n                return;\n\n            Task<Void> task = Task.allOf(selectedVersions.stream().map(javaVersion -> JavaManager.getDownloadJavaTask(downloadProvider, platform, javaVersion).wrapResult()).toList())\n                    .whenComplete(Schedulers.javafx(), (results, exception) -> {\n                        Throwable exceptionToDisplay;\n                        if (exception == null) {\n                            List<Throwable> exceptions = results.stream()\n                                    .filter(Result::isFailure)\n                                    .map(Result::getException)\n                                    .map(Lang::resolveException)\n                                    .filter(it -> !(it instanceof CancellationException))\n                                    .toList();\n                            if (!exceptions.isEmpty()) {\n                                if (exceptions.size() == 1) {\n                                    exceptionToDisplay = exceptions.get(0);\n                                } else {\n                                    exceptionToDisplay = new IOException(\"Failed to download Java\");\n                                    for (Throwable e : exceptions)\n                                        exceptionToDisplay.addSuppressed(e);\n                                }\n                            } else {\n                                exceptionToDisplay = null;\n                            }\n                        } else {\n                            exceptionToDisplay = exception;\n                        }\n\n                        if (exceptionToDisplay != null) {\n                            LOG.warning(\"Failed to download java\", exceptionToDisplay);\n                            Controllers.dialog(DownloadProviders.localizeErrorMessage(exceptionToDisplay), i18n(\"install.failed\"));\n                        }\n                    });\n\n            Controllers.taskDialog(task, i18n(\"download.java.process\"), TaskCancellationAction.NORMAL);\n        }\n    }\n\n    private final class DownloadDiscoJava extends JFXDialogLayout {\n        private final JFXComboBox<DiscoJavaDistribution> distributionBox;\n        private final JFXComboBox<DiscoJavaRemoteVersion> remoteVersionBox;\n        private final JFXComboBox<JavaPackageType> packageTypeBox;\n        private final Label warningLabel = new Label();\n\n        private final JFXButton downloadButton;\n        private final StackPane downloadButtonPane = new StackPane();\n\n        private final DownloadProvider downloadProvider = DownloadProviders.getDownloadProvider();\n\n        private final Map<DiscoJavaDistribution, DiscoJavaVersionList> javaVersionLists = new HashMap<>();\n        private final ObjectProperty<DiscoJavaVersionList> currentJavaVersionList = new SimpleObjectProperty<>();\n\n        DownloadDiscoJava() {\n            assert !distributions.isEmpty();\n\n            this.distributionBox = new JFXComboBox<>();\n            this.distributionBox.setConverter(FXUtils.stringConverter(JavaDistribution::getDisplayName));\n\n            this.remoteVersionBox = new JFXComboBox<>();\n            this.remoteVersionBox.setConverter(FXUtils.stringConverter(JavaRemoteVersion::getDistributionVersion));\n\n            this.packageTypeBox = new JFXComboBox<>(FXCollections.observableArrayList());\n\n            this.downloadButton = new JFXButton(i18n(\"download\"));\n            downloadButton.setOnAction(e -> onDownload());\n            downloadButton.getStyleClass().add(\"dialog-accept\");\n            downloadButton.disableProperty().bind(Bindings.isNull(remoteVersionBox.getSelectionModel().selectedItemProperty()));\n            downloadButtonPane.getChildren().setAll(downloadButton);\n\n            JFXButton cancelButton = new JFXButton(i18n(\"button.cancel\"));\n            cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));\n            cancelButton.getStyleClass().add(\"dialog-cancel\");\n            onEscPressed(this, cancelButton::fire);\n\n            GridPane body = new GridPane();\n            body.getColumnConstraints().setAll(new ColumnConstraints(), FXUtils.getColumnHgrowing());\n            body.setVgap(8);\n            body.setHgap(16);\n\n            body.addRow(0, new Label(i18n(\"java.download.distribution\")), distributionBox);\n            body.addRow(1, new Label(i18n(\"java.download.version\")), remoteVersionBox);\n            body.addRow(2, new Label(i18n(\"java.download.packageType\")), packageTypeBox);\n\n            distributionBox.setItems(FXCollections.observableList(new ArrayList<>(distributions)));\n\n            FXUtils.onChange(packageTypeBox.getSelectionModel().selectedItemProperty(), packageType -> {\n\n                ObservableList<DiscoJavaRemoteVersion> versions;\n                if (packageType == null\n                        || currentJavaVersionList.get() == null\n                        || (versions = currentJavaVersionList.get().versions.get(packageType)) == null) {\n                    remoteVersionBox.setItems(null);\n                    return;\n                }\n\n                DiscoJavaRemoteVersion oldVersion = remoteVersionBox.getSelectionModel().getSelectedItem();\n                remoteVersionBox.setItems(versions);\n\n                if (oldVersion != null) {\n                    for (int i = 0; i < versions.size(); i++) {\n                        DiscoJavaRemoteVersion version = versions.get(i);\n                        if (Objects.equals(version.getDistributionVersion(), oldVersion.getDistributionVersion())) {\n                            remoteVersionBox.getSelectionModel().select(i);\n                            return;\n                        }\n                    }\n                    for (int i = 0; i < versions.size(); i++) {\n                        DiscoJavaRemoteVersion version = versions.get(i);\n                        if (version.getJdkVersion() == oldVersion.getJdkVersion()) {\n                            remoteVersionBox.getSelectionModel().select(i);\n                            return;\n                        }\n                    }\n                }\n\n                for (int i = 0; i < versions.size(); i++) {\n                    DiscoJavaRemoteVersion version = versions.get(i);\n                    if (version.getJdkVersion() == GameJavaVersion.LATEST.majorVersion()) {\n                        remoteVersionBox.getSelectionModel().select(i);\n                        return;\n                    }\n                }\n\n                for (int i = 0; i < versions.size(); i++) {\n                    DiscoJavaRemoteVersion version = versions.get(i);\n                    if (version.isLTS()) {\n                        remoteVersionBox.getSelectionModel().select(i);\n                        return;\n                    }\n                }\n\n                remoteVersionBox.getSelectionModel().selectFirst();\n            });\n\n            Consumer<DiscoJavaVersionList> updateListStatus = list -> {\n                remoteVersionBox.setItems(null);\n                packageTypeBox.getItems().clear();\n                remoteVersionBox.setDisable(true);\n                packageTypeBox.setDisable(true);\n                warningLabel.setText(null);\n\n                if (list == null || (list.versions != null && list.versions.isEmpty()))\n                    downloadButtonPane.getChildren().setAll(downloadButton);\n                else if (list.status == DiscoJavaVersionList.Status.LOADING)\n                    downloadButtonPane.getChildren().setAll(new JFXSpinner());\n                else {\n                    downloadButtonPane.getChildren().setAll(downloadButton);\n\n                    if (list.status == DiscoJavaVersionList.Status.SUCCESS) {\n                        packageTypeBox.getItems().setAll(list.versions.keySet());\n                        packageTypeBox.getSelectionModel().selectFirst();\n\n                        remoteVersionBox.setDisable(false);\n                        packageTypeBox.setDisable(false);\n                    } else\n                        warningLabel.setText(i18n(\"java.download.load_list.failed\"));\n                }\n            };\n\n            currentJavaVersionList.addListener((observable, oldValue, newValue) -> {\n                if (oldValue != null)\n                    oldValue.listener = null;\n\n                if (newValue != null) {\n                    updateListStatus.accept(newValue);\n\n                    if (newValue.status == DiscoJavaVersionList.Status.LOADING)\n                        newValue.listener = updateListStatus;\n                } else {\n                    currentJavaVersionList.set(null);\n                    updateListStatus.accept(null);\n                }\n            });\n\n            FXUtils.onChange(distributionBox.getSelectionModel().selectedItemProperty(),\n                    it -> currentJavaVersionList.set(getJavaVersionList(it)));\n\n            setHeading(new Label(i18n(\"java.download.title\")));\n            setBody(body);\n            setActions(warningLabel, downloadButtonPane, cancelButton);\n            if (platform.getOperatingSystem() == OperatingSystem.LINUX && platform.getArchitecture() == Architecture.RISCV64) {\n                JFXHyperlink hyperlink = new JFXHyperlink(i18n(\"java.download.banshanjdk-8\"));\n                hyperlink.setExternalLink(\"https://www.zthread.cn/#product\");\n                getActions().add(0, hyperlink);\n            }\n        }\n\n        private void onDownload() {\n            fireEvent(new DialogCloseEvent());\n\n            DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem();\n            DiscoJavaRemoteVersion version = remoteVersionBox.getSelectionModel().getSelectedItem();\n            JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem();\n\n            if (version == null)\n                return;\n\n            Controllers.taskDialog(new GetTask(downloadProvider.injectURLWithCandidates(version.getLinks().getPkgInfoUri()))\n                    .setExecutor(Schedulers.io())\n                    .thenComposeAsync(json -> {\n                        DiscoResult<DiscoRemoteFileInfo> result = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoRemoteFileInfo.class));\n                        if (result.getResult().size() != 1)\n                            throw new IOException(\"Illegal result: \" + json);\n\n                        DiscoRemoteFileInfo fileInfo = result.getResult().get(0);\n                        if (!fileInfo.getChecksumType().equals(\"sha1\") && !fileInfo.getChecksumType().equals(\"sha256\"))\n                            throw new IOException(\"Unsupported checksum type: \" + fileInfo.getChecksumType());\n                        if (StringUtils.isBlank(fileInfo.getDirectDownloadUri()))\n                            throw new IOException(\"Missing download URI: \" + json);\n\n                        Path targetFile = Files.createTempFile(\"hmcl-java-\", \".\" + version.getArchiveType());\n                        targetFile.toFile().deleteOnExit();\n\n                        Task<FileDownloadTask.IntegrityCheck> getIntegrityCheck;\n                        if (StringUtils.isNotBlank(fileInfo.getChecksum()))\n                            getIntegrityCheck = Task.completed(new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), fileInfo.getChecksum()));\n                        else if (StringUtils.isNotBlank(fileInfo.getChecksumUri()))\n                            getIntegrityCheck = new GetTask(downloadProvider.injectURLWithCandidates(fileInfo.getChecksumUri()))\n                                    .thenApplyAsync(checksum -> {\n                                        checksum = checksum.trim();\n\n                                        int idx = checksum.indexOf(' ');\n                                        if (idx > 0)\n                                            checksum = checksum.substring(0, idx);\n\n                                        return new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), checksum);\n                                    });\n                        else\n                            throw new IOException(\"Unable to get checksum for file\");\n\n                        return getIntegrityCheck\n                                .thenComposeAsync(integrityCheck ->\n                                        new FileDownloadTask(downloadProvider.injectURLWithCandidates(fileInfo.getDirectDownloadUri()),\n                                                targetFile, integrityCheck).setName(fileInfo.getFileName()))\n                                .thenSupplyAsync(() -> targetFile);\n                    })\n                    .whenComplete(Schedulers.javafx(), ((result, exception) -> {\n                        if (exception == null) {\n                            String javaVersion = version.getJavaVersion();\n                            JavaInfo info = new JavaInfo(platform, javaVersion, distribution.getVendor());\n\n                            Map<String, Object> updateInfo = new LinkedHashMap<>();\n                            updateInfo.put(\"type\", \"disco\");\n                            updateInfo.put(\"info\", version);\n\n                            int idx = javaVersion.indexOf('+');\n                            if (idx > 0) {\n                                javaVersion = javaVersion.substring(0, idx);\n                            }\n                            String defaultName = distribution.getApiParameter() + \"-\" + javaVersion + \"-\" + packageType.name().toLowerCase(Locale.ROOT);\n                            Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller ->\n                                    new JavaInstallPage(controller::onFinish, info, version, updateInfo, defaultName, result)));\n                        } else {\n                            LOG.warning(\"Failed to download java\", exception);\n                            Throwable resolvedException = resolveException(exception);\n                            if (!(resolvedException instanceof CancellationException)) {\n                                Controllers.dialog(DownloadProviders.localizeErrorMessage(resolvedException), i18n(\"install.failed\"));\n                            }\n                        }\n                    })), i18n(\"java.download\"), TaskCancellationAction.NORMAL);\n\n        }\n\n        private DiscoJavaVersionList getJavaVersionList(DiscoJavaDistribution distribution) {\n            if (distribution == null)\n                return null;\n            return javaVersionLists.computeIfAbsent(distribution, it -> {\n                DiscoJavaVersionList versionList = new DiscoJavaVersionList(it);\n                new DiscoFetchJavaListTask(downloadProvider, it, platform).setExecutor(Schedulers.io()).thenApplyAsync(versions -> {\n                    EnumMap<JavaPackageType, ObservableList<DiscoJavaRemoteVersion>> result = new EnumMap<>(JavaPackageType.class);\n                    if (versions.isEmpty())\n                        return result;\n\n                    for (Map.Entry<JavaPackageType, TreeMap<Integer, DiscoJavaRemoteVersion>> entry : versions.entrySet())\n                        for (DiscoJavaRemoteVersion version : entry.getValue().values())\n                            if (version.isLTS()\n                                    || version.getJdkVersion() == entry.getValue().lastKey() // latest version\n                                    || version.getJdkVersion() == 16)\n                                result.computeIfAbsent(entry.getKey(), ignored -> FXCollections.observableArrayList())\n                                        .add(version);\n\n                    for (List<DiscoJavaRemoteVersion> l : result.values())\n                        Collections.reverse(l);\n                    return result;\n                }).whenComplete(Schedulers.javafx(), ((result, exception) -> {\n                    if (exception == null) {\n                        versionList.status = DiscoJavaVersionList.Status.SUCCESS;\n                        versionList.versions = result;\n                    } else {\n                        LOG.warning(\"Failed to load java list\", exception);\n                        versionList.status = DiscoJavaVersionList.Status.FAILED;\n                    }\n                    versionList.invalidate();\n                })).start();\n                return versionList;\n            });\n        }\n    }\n\n    private static final class DiscoJavaVersionList {\n        enum Status {\n            LOADING, SUCCESS, FAILED\n        }\n\n        final DiscoJavaDistribution distribution;\n\n        Status status = Status.LOADING;\n        EnumMap<JavaPackageType, ObservableList<DiscoJavaRemoteVersion>> versions;\n        Consumer<DiscoJavaVersionList> listener;\n\n        DiscoJavaVersionList(DiscoJavaDistribution distribution) {\n            this.distribution = distribution;\n        }\n\n        void invalidate() {\n            if (listener != null)\n                listener.accept(this);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaInstallPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.download.java.JavaRemoteVersion;\nimport org.jackhuang.hmcl.download.java.disco.DiscoJavaDistribution;\nimport org.jackhuang.hmcl.download.java.disco.DiscoJavaRemoteVersion;\nimport org.jackhuang.hmcl.java.HMCLJavaRepository;\nimport org.jackhuang.hmcl.java.JavaInfo;\nimport org.jackhuang.hmcl.java.JavaManager;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.wizard.WizardSinglePage;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class JavaInstallPage extends WizardSinglePage {\n\n    private static final Pattern NAME_PATTERN = Pattern.compile(\"[a-zA-Z0-9.\\\\-_]+\");\n\n    private final Path file;\n\n    private final JavaInfo info;\n    private final JavaRemoteVersion remoteVersion;\n    private final Map<String, Object> update;\n    private final StringProperty nameProperty = new SimpleStringProperty();\n\n    public JavaInstallPage(Runnable onFinish, JavaInfo info, JavaRemoteVersion remoteVersion, Map<String, Object> update, String defaultName, Path file) {\n        super(onFinish);\n        this.info = info;\n        this.remoteVersion = remoteVersion;\n        this.update = update;\n        this.file = file;\n        this.nameProperty.set(defaultName);\n    }\n\n    @Override\n    protected SkinBase<?> createDefaultSkin() {\n        return new Skin(this);\n    }\n\n    @Override\n    protected Object finish() {\n        Task<JavaRuntime> installTask = JavaManager.getInstallJavaTask(info.getPlatform(), nameProperty.get(), update, file);\n        return remoteVersion == null ? installTask : installTask.whenComplete(exception -> {\n            try {\n                Files.delete(file);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to delete file: \" + file, e);\n            }\n        });\n    }\n\n    @Override\n    public String getTitle() {\n        return i18n(\"java.install\");\n    }\n\n    private static final class Skin extends SkinBase<JavaInstallPage> {\n\n        private final ComponentList componentList = new ComponentList();\n\n        private final JFXTextField nameField;\n\n        private final Set<String> usedNames = new HashSet<>();\n\n        Skin(JavaInstallPage control) {\n            super(control);\n\n            VBox borderPane = new VBox();\n            borderPane.setAlignment(Pos.CENTER);\n            FXUtils.setLimitWidth(borderPane, 500);\n\n\n            {\n                var namePane = new LinePane();\n                {\n                    namePane.setTitle(i18n(\"java.install.name\"));\n\n                    nameField = new JFXTextField();\n                    nameField.textProperty().bindBidirectional(control.nameProperty);\n                    FXUtils.setLimitWidth(nameField, 200);\n                    namePane.setRight(nameField);\n                    nameField.setValidators(\n                            new RequiredValidator(),\n                            new Validator(i18n(\"java.install.warning.invalid_character\"),\n                                    text -> !text.startsWith(HMCLJavaRepository.MOJANG_JAVA_PREFIX) && NAME_PATTERN.matcher(text).matches()),\n                            new Validator(i18n(\"java.install.failed.exists\"), text -> !usedNames.contains(text))\n                    );\n                    String defaultName = control.nameProperty.get();\n                    if (JavaManager.REPOSITORY.isInstalled(control.info.getPlatform(), defaultName)) {\n                        usedNames.add(defaultName);\n                    }\n                    nameField.textProperty().addListener(o -> nameField.validate());\n                    nameField.validate();\n\n                    componentList.getContent().add(namePane);\n                }\n\n                String vendor = JavaInfo.normalizeVendor(control.info.getVendor());\n                if (vendor != null)\n                    addInfo(i18n(\"java.info.vendor\"), vendor);\n\n                if (control.remoteVersion instanceof DiscoJavaRemoteVersion) {\n                    String distributionName = ((DiscoJavaRemoteVersion) control.remoteVersion).getDistribution();\n                    DiscoJavaDistribution distribution = DiscoJavaDistribution.of(distributionName);\n                    addInfo(i18n(\"java.info.disco.distribution\"), distribution != null ? distribution.getDisplayName() : distributionName);\n                } else\n                    addInfo(i18n(\"java.install.archive\"), control.file.toAbsolutePath().toString());\n\n                addInfo(i18n(\"java.info.version\"), control.info.getVersion());\n                addInfo(i18n(\"java.info.architecture\"), control.info.getPlatform().getArchitecture().getDisplayName());\n\n                BorderPane installPane = new BorderPane();\n                {\n                    JFXButton installButton = FXUtils.newRaisedButton(i18n(\"button.install\"));\n                    installButton.setOnAction(e -> {\n                        String name = control.nameProperty.get();\n                        if (JavaManager.REPOSITORY.isInstalled(control.info.getPlatform(), name)) {\n                            Controllers.dialog(i18n(\"java.install.failed.exists\"), null, MessageDialogPane.MessageType.WARNING);\n                            usedNames.add(name);\n                            nameField.validate();\n                        } else\n                            control.onFinish.run();\n                    });\n                    installButton.disableProperty().bind(nameField.activeValidatorProperty().isNotNull());\n                    installPane.setRight(installButton);\n\n                    componentList.getContent().add(installPane);\n                }\n            }\n\n            borderPane.getChildren().setAll(componentList);\n            this.getChildren().setAll(borderPane);\n        }\n\n        private void addInfo(String name, String value) {\n            LineTextPane pane = new LineTextPane();\n            pane.setTitle(name);\n            pane.setText(value);\n            this.componentList.getContent().add(pane);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXListView;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.value.ChangeListener;\nimport javafx.collections.FXCollections;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.StackPane;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.java.JavaInfo;\nimport org.jackhuang.hmcl.java.JavaManager;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.setting.ConfigHolder;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.*;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.RipplerContainer;\nimport org.jackhuang.hmcl.ui.construct.TwoLineListItem;\nimport org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;\nimport org.jackhuang.hmcl.util.FXThread;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.UnsupportedPlatformException;\nimport org.jackhuang.hmcl.util.tree.ArchiveFileTree;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class JavaManagementPage extends ListPageBase<JavaRuntime> {\n\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final ChangeListener<Collection<JavaRuntime>> listener;\n\n    private final Runnable onInstallJava;\n\n    public JavaManagementPage() {\n        this.listener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), this::loadJava);\n\n        if (Platform.SYSTEM_PLATFORM.equals(OperatingSystem.LINUX, Architecture.LOONGARCH64_OW)) {\n            onInstallJava = () -> FXUtils.openLink(\"https://www.loongnix.cn/zh/api/java/\");\n        } else {\n            onInstallJava = JavaDownloadDialog.showDialogAction(DownloadProviders.getDownloadProvider());\n        }\n\n        FXUtils.applyDragListener(this, it -> {\n            String name = FileUtils.getName(it);\n            return Files.isDirectory(it) || name.endsWith(\".zip\") || name.endsWith(\".tar.gz\") || name.equals(OperatingSystem.CURRENT_OS.getJavaExecutable());\n        }, files -> {\n            for (Path file : files) {\n                if (Files.isDirectory(file)) {\n                    onAddJavaHome(file);\n                } else {\n                    String fileName = FileUtils.getName(file);\n\n                    if (fileName.equals(OperatingSystem.CURRENT_OS.getJavaExecutable())) {\n                        onAddJavaBinary(file);\n                    } else if (fileName.endsWith(\".zip\") || fileName.endsWith(\".tar.gz\")) {\n                        onInstallArchive(file);\n                    } else {\n                        throw new AssertionError(\"Unreachable code\");\n                    }\n                }\n            }\n        });\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JavaPageSkin(this);\n    }\n\n    void onAddJava() {\n        FileChooser chooser = new FileChooser();\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)\n            chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(\"Java\", \"java.exe\"));\n        chooser.setTitle(i18n(\"settings.game.java_directory.choose\"));\n        Path file = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));\n        if (file != null) {\n            JavaManager.getAddJavaTask(file).whenComplete(Schedulers.javafx(), exception -> {\n                if (exception != null) {\n                    LOG.warning(\"Failed to add java\", exception);\n                    Controllers.dialog(i18n(\"java.add.failed\"), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n                }\n            }).start();\n        }\n    }\n\n    void onShowRestoreJavaPage() {\n        Controllers.navigateForward(new JavaRestorePage(ConfigHolder.globalConfig().getDisabledJava()));\n    }\n\n    private void onAddJavaBinary(Path file) {\n        JavaManager.getAddJavaTask(file).whenComplete(Schedulers.javafx(), exception -> {\n            if (exception != null) {\n                LOG.warning(\"Failed to add java\", exception);\n                Controllers.dialog(i18n(\"java.add.failed\"), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n            }\n        }).start();\n    }\n\n    private void onAddJavaHome(Path file) {\n        Task.composeAsync(() -> {\n            Path releaseFile = file.resolve(\"release\");\n            if (Files.notExists(releaseFile))\n                throw new IOException(\"Missing release file \" + releaseFile);\n            return JavaManager.getAddJavaTask(file.resolve(\"bin\").resolve(OperatingSystem.CURRENT_OS.getJavaExecutable()));\n        }).whenComplete(Schedulers.javafx(), exception -> {\n            if (exception != null) {\n                LOG.warning(\"Failed to add java\", exception);\n                Controllers.dialog(i18n(\"java.add.failed\"), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n            }\n        }).start();\n    }\n\n    private void onInstallArchive(Path file) {\n        Task.supplyAsync(() -> {\n            try (ArchiveFileTree<?, ?> tree = ArchiveFileTree.open(file)) {\n                JavaInfo info = JavaInfo.fromArchive(tree);\n\n                if (!JavaManager.isCompatible(info.getPlatform()))\n                    throw new UnsupportedPlatformException(info.getPlatform().toString());\n\n                return Pair.pair(tree.getRoot().getSubDirs().keySet().iterator().next(), info);\n            }\n        }).whenComplete(Schedulers.javafx(), (result, exception) -> {\n            if (exception == null) {\n                Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller ->\n                        new JavaInstallPage(controller::onFinish, result.getValue(), null, null, result.getKey(), file)));\n            } else {\n                if (exception instanceof UnsupportedPlatformException) {\n                    Controllers.dialog(i18n(\"java.install.failed.unsupported_platform\"), null, MessageDialogPane.MessageType.WARNING);\n                } else {\n                    Controllers.dialog(i18n(\"java.install.failed.invalid\"), null, MessageDialogPane.MessageType.WARNING);\n                }\n            }\n        }).start();\n    }\n\n    @FXThread\n    private void loadJava(Collection<JavaRuntime> javaRuntimes) {\n        if (javaRuntimes != null) {\n            this.setItems(FXCollections.observableArrayList(javaRuntimes));\n            this.setLoading(false);\n        } else {\n            this.setLoading(true);\n        }\n    }\n\n    private static final class JavaPageSkin extends ToolbarListPageSkin<JavaRuntime, JavaManagementPage> {\n\n        JavaPageSkin(JavaManagementPage skinnable) {\n            super(skinnable);\n        }\n\n        @Override\n        protected List<Node> initializeToolbar(JavaManagementPage skinnable) {\n            ArrayList<Node> res = new ArrayList<>(4);\n\n            res.add(createToolbarButton2(i18n(\"button.refresh\"), SVG.REFRESH, JavaManager::refresh));\n            if (skinnable.onInstallJava != null) {\n                res.add(createToolbarButton2(i18n(\"java.download\"), SVG.DOWNLOAD, skinnable.onInstallJava));\n            }\n            res.add(createToolbarButton2(i18n(\"java.add\"), SVG.ADD, skinnable::onAddJava));\n\n            JFXButton disableJava = createToolbarButton2(i18n(\"java.disabled.management\"), SVG.FORMAT_LIST_BULLETED, skinnable::onShowRestoreJavaPage);\n            disableJava.disableProperty().bind(Bindings.isEmpty(ConfigHolder.globalConfig().getDisabledJava()));\n            res.add(disableJava);\n\n            return res;\n        }\n\n        @Override\n        protected ListCell<JavaRuntime> createListCell(JFXListView<JavaRuntime> listView) {\n            return new JavaItemCell(listView);\n        }\n    }\n\n    private static final class JavaItemCell extends ListCell<JavaRuntime> {\n        private final Node graphic;\n        private final TwoLineListItem content;\n\n        private SVG removeIcon;\n        private final StackPane removeIconPane;\n        private final Tooltip removeTooltip = new Tooltip();\n\n        JavaItemCell(JFXListView<JavaRuntime> listView) {\n            BorderPane root = new BorderPane();\n\n            HBox center = new HBox();\n            center.setMouseTransparent(true);\n            center.setSpacing(8);\n            center.setAlignment(Pos.CENTER_LEFT);\n\n            this.content = new TwoLineListItem();\n            HBox.setHgrow(content, Priority.ALWAYS);\n\n            BorderPane.setAlignment(content, Pos.CENTER);\n            center.getChildren().setAll(content);\n            root.setCenter(center);\n\n            HBox right = new HBox();\n            right.setAlignment(Pos.CENTER_RIGHT);\n            {\n                JFXButton revealButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);\n                revealButton.setOnAction(e -> {\n                    JavaRuntime java = getItem();\n                    if (java != null)\n                        onReveal(java);\n                });\n                FXUtils.installFastTooltip(revealButton, i18n(\"reveal.in_file_manager\"));\n\n                JFXButton removeButton = new JFXButton();\n                removeButton.getStyleClass().add(\"toggle-icon4\");\n                removeButton.setOnAction(e -> {\n                    JavaRuntime java = getItem();\n                    if (java != null)\n                        onRemove(java);\n                });\n                FXUtils.installFastTooltip(removeButton, removeTooltip);\n\n                this.removeIconPane = new StackPane();\n                removeIconPane.setAlignment(Pos.CENTER);\n                FXUtils.setLimitWidth(removeIconPane, 24);\n                FXUtils.setLimitHeight(removeIconPane, 24);\n                removeButton.setGraphic(removeIconPane);\n\n                right.getChildren().setAll(revealButton, removeButton);\n            }\n            root.setRight(right);\n\n            root.getStyleClass().add(\"md-list-cell\");\n            root.setPadding(new Insets(8));\n\n            this.graphic = new RipplerContainer(root);\n\n            FXUtils.limitCellWidth(listView, this);\n        }\n\n        @Override\n        protected void updateItem(JavaRuntime item, boolean empty) {\n            super.updateItem(item, empty);\n            if (empty || item == null) {\n                setGraphic(null);\n            } else {\n                content.setTitle((item.isJDK() ? \"JDK\" : \"JRE\") + \" \" + item.getVersion());\n                content.setSubtitle(item.getBinary().toString());\n\n                content.getTags().clear();\n                content.addTag(i18n(\"java.info.architecture\") + \": \" + item.getArchitecture().getDisplayName());\n                String vendor = JavaInfo.normalizeVendor(item.getVendor());\n                if (vendor != null)\n                    content.addTag(i18n(\"java.info.vendor\") + \": \" + vendor);\n\n                SVG newRemoveIcon = item.isManaged() ? SVG.DELETE_FOREVER : SVG.DELETE;\n                if (removeIcon != newRemoveIcon) {\n                    removeIcon = newRemoveIcon;\n                    removeIconPane.getChildren().setAll(removeIcon.createIcon(24));\n                    removeTooltip.setText(item.isManaged() ? i18n(\"java.uninstall\") : i18n(\"java.disable\"));\n                }\n\n                setGraphic(graphic);\n            }\n        }\n\n        private void onReveal(JavaRuntime java) {\n            Path target;\n            Path parent = java.getBinary().getParent();\n            if (parent != null\n                    && parent.getParent() != null\n                    && parent.getFileName() != null\n                    && parent.getFileName().toString().equals(\"bin\")\n                    && Files.exists(parent.getParent().resolve(\"release\"))) {\n                target = parent.getParent();\n            } else {\n                target = java.getBinary();\n            }\n\n            FXUtils.showFileInExplorer(target);\n        }\n\n        private void onRemove(JavaRuntime java) {\n            if (java.isManaged()) {\n                Controllers.confirm(\n                        i18n(\"java.uninstall.confirm\"),\n                        i18n(\"message.warning\"),\n                        () -> Controllers.taskDialog(JavaManager.getUninstallJavaTask(java), i18n(\"java.uninstall\"), TaskCancellationAction.NORMAL),\n                        null\n                );\n            } else {\n                Controllers.confirm(\n                        i18n(\"java.disable.confirm\"),\n                        i18n(\"message.warning\"),\n                        () -> {\n                            String path = java.getBinary().toString();\n                            ConfigHolder.globalConfig().getUserJava().remove(path);\n                            ConfigHolder.globalConfig().getDisabledJava().add(path);\n                            try {\n                                JavaManager.removeJava(java);\n                            } catch (InterruptedException ignored) {\n                            }\n                        },\n                        null\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaRestorePage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableSet;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport org.jackhuang.hmcl.java.JavaManager;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.ui.*;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.RipplerContainer;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class JavaRestorePage extends ListPageBase<JavaRestorePage.DisabledJavaItem> implements DecoratorPage {\n\n    private final ObjectProperty<State> state = new SimpleObjectProperty<>(State.fromTitle(i18n(\"java.disabled.management\")));\n\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final InvalidationListener listener;\n\n    public JavaRestorePage(ObservableSet<String> disabledJava) {\n        this.listener = o -> {\n            ArrayList<DisabledJavaItem> result = new ArrayList<>(disabledJava.size());\n            for (String path : disabledJava) {\n                Path realPath = null;\n\n                try {\n                    realPath = Paths.get(path).toRealPath();\n                } catch (IOException ignored) {\n                }\n\n                result.add(new DisabledJavaItem(disabledJava, path, realPath));\n            }\n            result.sort((a, b) -> {\n                if (a.realPath == null && b.realPath != null)\n                    return -1;\n                if (a.realPath != null && b.realPath == null)\n                    return 1;\n                return a.path.compareTo(b.path);\n            });\n            this.setItems(FXCollections.observableList(result));\n        };\n        disabledJava.addListener(new WeakInvalidationListener(listener));\n        listener.invalidated(disabledJava);\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new JavaRestorePageSkin(this);\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state;\n    }\n\n    static final class DisabledJavaItem extends Control {\n        final ObservableSet<String> disabledJava;\n        final String path;\n        final Path realPath;\n\n        DisabledJavaItem(ObservableSet<String> disabledJava, String path, Path realPath) {\n            this.disabledJava = disabledJava;\n            this.path = path;\n            this.realPath = realPath;\n        }\n\n        @Override\n        protected Skin<?> createDefaultSkin() {\n            return new DisabledJavaItemSkin(this);\n        }\n\n        void onReveal() {\n            if (realPath != null) {\n                Path target;\n                Path parent = realPath.getParent();\n                if (parent != null\n                        && parent.getParent() != null\n                        && parent.getFileName() != null\n                        && parent.getFileName().toString().equals(\"bin\")\n                        && Files.exists(parent.getParent().resolve(\"release\"))) {\n                    target = parent.getParent();\n                } else {\n                    target = realPath;\n                }\n\n                FXUtils.showFileInExplorer(target);\n            }\n        }\n\n        void onRestore() {\n            disabledJava.remove(path);\n            JavaManager.getAddJavaTask(realPath).whenComplete(Schedulers.javafx(), exception -> {\n                if (exception != null) {\n                    LOG.warning(\"Failed to add java\", exception);\n                    Controllers.dialog(i18n(\"java.add.failed\"), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n                }\n            }).start();\n        }\n\n        void onRemove() {\n            disabledJava.remove(path);\n        }\n    }\n\n    private static final class DisabledJavaItemSkin extends SkinBase<DisabledJavaItem> {\n        DisabledJavaItemSkin(DisabledJavaItem skinnable) {\n            super(skinnable);\n\n            BorderPane root = new BorderPane();\n\n            Label label = new Label(skinnable.path);\n            BorderPane.setAlignment(label, Pos.CENTER_LEFT);\n            root.setCenter(label);\n\n            HBox right = new HBox();\n            right.setAlignment(Pos.CENTER_RIGHT);\n            {\n                JFXButton revealButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);\n                revealButton.setOnAction(e -> skinnable.onReveal());\n                FXUtils.installFastTooltip(revealButton, i18n(\"reveal.in_file_manager\"));\n\n                if (skinnable.realPath == null) {\n                    revealButton.setDisable(true);\n\n                    JFXButton removeButton = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);\n                    removeButton.setOnAction(e -> skinnable.onRemove());\n                    FXUtils.installFastTooltip(removeButton, i18n(\"java.disabled.management.remove\"));\n\n                    right.getChildren().setAll(revealButton, removeButton);\n                } else {\n                    JFXButton restoreButton = FXUtils.newToggleButton4(SVG.RESTORE);\n                    restoreButton.setOnAction(e -> skinnable.onRestore());\n                    FXUtils.installFastTooltip(restoreButton, i18n(\"java.disabled.management.restore\"));\n\n                    right.getChildren().setAll(revealButton, restoreButton);\n                }\n            }\n            root.setRight(right);\n\n            root.getStyleClass().add(\"md-list-cell\");\n            root.setPadding(new Insets(8));\n\n            this.getChildren().setAll(new RipplerContainer(root));\n        }\n    }\n\n    private static final class JavaRestorePageSkin extends ToolbarListPageSkin<DisabledJavaItem, JavaRestorePage> {\n        JavaRestorePageSkin(JavaRestorePage skinnable) {\n            super(skinnable);\n        }\n\n        @Override\n        protected List<Node> initializeToolbar(JavaRestorePage skinnable) {\n            return Collections.emptyList();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.ui.versions.VersionSettingsPage;\n\nimport java.util.Locale;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n(\"settings\")));\n    private final TabHeader tab;\n    private final TabHeader.Tab<VersionSettingsPage> gameTab = new TabHeader.Tab<>(\"versionSettingsPage\");\n    private final TabControl.Tab<JavaManagementPage> javaManagementTab = new TabControl.Tab<>(\"javaManagementPage\");\n    private final TabHeader.Tab<SettingsPage> settingsTab = new TabHeader.Tab<>(\"settingsPage\");\n    private final TabHeader.Tab<PersonalizationPage> personalizationTab = new TabHeader.Tab<>(\"personalizationPage\");\n    private final TabHeader.Tab<DownloadSettingsPage> downloadTab = new TabHeader.Tab<>(\"downloadSettingsPage\");\n    private final TabHeader.Tab<HelpPage> helpTab = new TabHeader.Tab<>(\"helpPage\");\n    private final TabHeader.Tab<AboutPage> aboutTab = new TabHeader.Tab<>(\"aboutPage\");\n    private final TabHeader.Tab<FeedbackPage> feedbackTab = new TabHeader.Tab<>(\"feedbackPage\");\n    private final TransitionPane transitionPane = new TransitionPane();\n\n    public LauncherSettingsPage() {\n        gameTab.setNodeSupplier(() -> new VersionSettingsPage(true));\n        javaManagementTab.setNodeSupplier(JavaManagementPage::new);\n        settingsTab.setNodeSupplier(SettingsPage::new);\n        personalizationTab.setNodeSupplier(PersonalizationPage::new);\n        downloadTab.setNodeSupplier(DownloadSettingsPage::new);\n        helpTab.setNodeSupplier(HelpPage::new);\n        feedbackTab.setNodeSupplier(FeedbackPage::new);\n        aboutTab.setNodeSupplier(AboutPage::new);\n        tab = new TabHeader(transitionPane, gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);\n\n        tab.select(gameTab);\n        addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null));\n\n        AdvancedListBox sideBar = new AdvancedListBox()\n                .addNavigationDrawerTab(tab, gameTab, i18n(\"settings.type.global.manage\"), SVG.STADIA_CONTROLLER, SVG.STADIA_CONTROLLER_FILL)\n                .addNavigationDrawerTab(tab, javaManagementTab, i18n(\"java.management\"), SVG.LOCAL_CAFE, SVG.LOCAL_CAFE_FILL)\n                .startCategory(i18n(\"launcher\").toUpperCase(Locale.ROOT))\n                .addNavigationDrawerTab(tab, settingsTab, i18n(\"settings.launcher.general\"), SVG.TUNE)\n                .addNavigationDrawerTab(tab, personalizationTab, i18n(\"settings.launcher.appearance\"), SVG.STYLE, SVG.STYLE_FILL)\n                .addNavigationDrawerTab(tab, downloadTab, i18n(\"download\"), SVG.DOWNLOAD)\n                .startCategory(i18n(\"help\").toUpperCase(Locale.ROOT))\n                .addNavigationDrawerTab(tab, helpTab, i18n(\"help\"), SVG.HELP, SVG.HELP_FILL)\n                .addNavigationDrawerTab(tab, feedbackTab, i18n(\"contact\"), SVG.FEEDBACK, SVG.FEEDBACK_FILL)\n                .addNavigationDrawerTab(tab, aboutTab, i18n(\"about\"), SVG.INFO, SVG.INFO_FILL);\n        FXUtils.setLimitWidth(sideBar, 200);\n        setLeft(sideBar);\n\n        setCenter(transitionPane);\n    }\n\n    @Override\n    public void onPageShown() {\n        tab.onPageShown();\n    }\n\n    @Override\n    public void onPageHidden() {\n        tab.onPageHidden();\n    }\n\n    public void showGameSettings(Profile profile) {\n        gameTab.getNode().loadVersion(profile, null);\n        tab.select(gameTab, false);\n    }\n\n    public void showFeedback() {\n        tab.select(feedbackTab, false);\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXPopup;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.TextFlow;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.theme.Themes;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.TwoLineListItem;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.ui.versions.GameListPopupMenu;\nimport org.jackhuang.hmcl.ui.versions.Versions;\nimport org.jackhuang.hmcl.upgrade.RemoteVersion;\nimport org.jackhuang.hmcl.upgrade.UpdateChecker;\nimport org.jackhuang.hmcl.upgrade.UpdateHandler;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.CancellationException;\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.download.RemoteVersion.Type.RELEASE;\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.ui.FXUtils.SINE;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class MainPage extends StackPane implements DecoratorPage {\n    private static final String ANNOUNCEMENT = \"announcement\";\n\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();\n\n    private final StringProperty currentGame = new SimpleStringProperty(this, \"currentGame\");\n    private final BooleanProperty showUpdate = new SimpleBooleanProperty(this, \"showUpdate\");\n    private final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>(this, \"latestVersion\");\n    private final ObservableList<Version> versions = FXCollections.observableArrayList();\n    private Profile profile;\n\n    private TransitionPane announcementPane;\n    private final StackPane updatePane;\n    private final JFXButton menuButton;\n\n    {\n        HBox titleNode = new HBox(8);\n        titleNode.setPadding(new Insets(0, 0, 0, 2));\n        titleNode.setAlignment(Pos.CENTER_LEFT);\n\n        ImageView titleIcon = new ImageView(FXUtils.newBuiltinImage(\"/assets/img/icon-title.png\"));\n        Label titleLabel = new Label(Metadata.FULL_TITLE);\n        if (I18n.isUpsideDown()) {\n            titleIcon.setRotate(180);\n            titleLabel.setRotate(180);\n        }\n        titleLabel.getStyleClass().add(\"jfx-decorator-title\");\n        titleLabel.textFillProperty().bind(Themes.titleFillProperty());\n        titleNode.getChildren().setAll(titleIcon, titleLabel);\n\n        state.setValue(new State(null, titleNode, false, false, true));\n\n        setPadding(new Insets(20));\n\n        if (Metadata.isNightly() || (Metadata.isDev() && !Objects.equals(Metadata.VERSION, config().getShownTips().get(ANNOUNCEMENT)))) {\n            String title;\n            String content;\n            if (Metadata.isNightly()) {\n                title = i18n(\"update.channel.nightly.title\");\n                content = i18n(\"update.channel.nightly.hint\");\n            } else {\n                title = i18n(\"update.channel.dev.title\");\n                content = i18n(\"update.channel.dev.hint\");\n            }\n\n            VBox announcementCard = new VBox();\n\n            BorderPane titleBar = new BorderPane();\n            titleBar.getStyleClass().add(\"title\");\n            titleBar.setLeft(new Label(title));\n\n            JFXButton btnHide = new JFXButton();\n            btnHide.setOnAction(e -> {\n                announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);\n                if (Metadata.isDev()) {\n                    config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);\n                }\n            });\n            btnHide.getStyleClass().add(\"announcement-close-button\");\n            btnHide.setGraphic(SVG.CLOSE.createIcon(20));\n            titleBar.setRight(btnHide);\n\n            TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);\n            body.setLineSpacing(4);\n\n            announcementCard.getChildren().setAll(titleBar, body);\n            announcementCard.setSpacing(16);\n            announcementCard.getStyleClass().addAll(\"card\", \"announcement\");\n\n            VBox announcementBox = new VBox(16);\n            announcementBox.setPadding(new Insets(15));\n            announcementBox.getChildren().add(announcementCard);\n\n            announcementPane = new TransitionPane();\n            announcementPane.setContent(announcementBox, ContainerAnimations.NONE);\n\n            StackPane.setMargin(announcementPane, new Insets(-15));\n            getChildren().add(announcementPane);\n        }\n\n        updatePane = new StackPane();\n        updatePane.setVisible(false);\n        updatePane.getStyleClass().add(\"bubble\");\n        FXUtils.setLimitWidth(updatePane, 230);\n        FXUtils.setLimitHeight(updatePane, 55);\n        StackPane.setAlignment(updatePane, Pos.TOP_RIGHT);\n        FXUtils.onClicked(updatePane, this::onUpgrade);\n        FXUtils.onChange(showUpdateProperty(), this::showUpdate);\n\n        {\n            HBox hBox = new HBox();\n            hBox.setSpacing(12);\n            hBox.setAlignment(Pos.CENTER_LEFT);\n            StackPane.setAlignment(hBox, Pos.CENTER_LEFT);\n            StackPane.setMargin(hBox, new Insets(9, 12, 9, 16));\n            {\n                TwoLineListItem prompt = new TwoLineListItem();\n                prompt.setSubtitle(i18n(\"update.bubble.subtitle\"));\n                prompt.setPickOnBounds(false);\n                prompt.titleProperty().bind(BindingMapping.of(latestVersionProperty()).map(latestVersion ->\n                        latestVersion == null ? \"\" : i18n(\"update.bubble.title\", latestVersion.getVersion())));\n\n                hBox.getChildren().setAll(SVG.UPDATE.createIcon(20), prompt);\n            }\n\n            JFXButton closeUpdateButton = new JFXButton();\n            closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(10));\n            StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT);\n            closeUpdateButton.getStyleClass().add(\"toggle-icon-tiny\");\n            StackPane.setMargin(closeUpdateButton, new Insets(5));\n            closeUpdateButton.setOnAction(e -> closeUpdateBubble());\n\n            updatePane.getChildren().setAll(hBox, closeUpdateButton);\n        }\n\n        HBox launchPane = new HBox();\n        launchPane.getStyleClass().add(\"launch-pane\");\n        FXUtils.onScroll(launchPane, versions, list -> {\n            String currentId = getCurrentGame();\n            return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));\n        }, it -> profile.setSelectedVersion(it.getId()));\n\n        StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);\n        {\n            JFXButton launchButton = new JFXButton();\n            launchButton.getStyleClass().add(\"launch-button\");\n            launchButton.setDefaultButton(true);\n            {\n                VBox graphic = new VBox();\n                graphic.setAlignment(Pos.CENTER);\n                Label launchLabel = new Label();\n                launchLabel.setStyle(\"-fx-font-size: 16px;\");\n                Label currentLabel = new Label();\n                currentLabel.setStyle(\"-fx-font-size: 12px;\");\n\n                FXUtils.onChangeAndOperate(currentGameProperty(), new Consumer<>() {\n                    private Tooltip tooltip;\n\n                    @Override\n                    public void accept(String currentGame) {\n                        if (currentGame == null) {\n                            launchLabel.setText(i18n(\"version.launch.empty\"));\n                            currentLabel.setText(null);\n                            graphic.getChildren().setAll(launchLabel);\n                            FXUtils.setOnActionWithCooldown(launchButton, MainPage.this::launchNoGame);\n                            if (tooltip == null)\n                                tooltip = new Tooltip(i18n(\"version.launch.empty.tooltip\"));\n                            FXUtils.installFastTooltip(launchButton, tooltip);\n                        } else {\n                            launchLabel.setText(i18n(\"version.launch\"));\n                            currentLabel.setText(currentGame);\n                            graphic.getChildren().setAll(launchLabel, currentLabel);\n                            FXUtils.setOnActionWithCooldown(launchButton, MainPage.this::launch);\n                            if (tooltip != null)\n                                Tooltip.uninstall(launchButton, tooltip);\n                        }\n                    }\n                });\n\n                launchButton.setGraphic(graphic);\n            }\n\n            menuButton = new JFXButton();\n            menuButton.getStyleClass().add(\"menu-button\");\n            menuButton.setOnAction(e -> GameListPopupMenu.show(\n                    menuButton,\n                    JFXPopup.PopupVPosition.BOTTOM,\n                    JFXPopup.PopupHPosition.RIGHT,\n                    0,\n                    -menuButton.getHeight(),\n                    profile, versions\n            ));\n            FXUtils.installFastTooltip(menuButton, i18n(\"version.switch\"));\n            menuButton.setGraphic(SVG.ARROW_DROP_UP.createIcon(30));\n\n            EventHandler<MouseEvent> secondaryClickHandle = event -> {\n                if (event.getButton() == MouseButton.SECONDARY && event.getClickCount() == 1) {\n                    menuButton.fire();\n                    event.consume();\n                }\n            };\n            launchButton.addEventHandler(MouseEvent.MOUSE_CLICKED, secondaryClickHandle);\n            menuButton.addEventHandler(MouseEvent.MOUSE_CLICKED, secondaryClickHandle);\n\n            launchPane.getChildren().setAll(launchButton, menuButton);\n        }\n\n        getChildren().addAll(updatePane, launchPane);\n\n    }\n\n    private void showUpdate(boolean show) {\n        doAnimation(show);\n\n        if (show && !config().isDisableAutoShowUpdateDialog()\n                && getLatestVersion() != null\n                && !Objects.equals(config().getPromptedVersion(), getLatestVersion().getVersion())) {\n            Controllers.dialog(new MessageDialogPane.Builder(\"\", i18n(\"update.bubble.title\", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO)\n                    .addAction(i18n(\"button.view\"), () -> {\n                        config().setPromptedVersion(getLatestVersion().getVersion());\n                        onUpgrade();\n                    })\n                    .addCancel(null)\n                    .build());\n        }\n    }\n\n    private void doAnimation(boolean show) {\n        if (AnimationUtils.isAnimationEnabled()) {\n            Duration duration = Duration.millis(320);\n            Timeline nowAnimation = new Timeline();\n            nowAnimation.getKeyFrames().addAll(\n                    new KeyFrame(Duration.ZERO,\n                            new KeyValue(updatePane.translateXProperty(), show ? 260 : 0, SINE)),\n                    new KeyFrame(duration,\n                            new KeyValue(updatePane.translateXProperty(), show ? 0 : 260, SINE)));\n            if (show) nowAnimation.getKeyFrames().add(\n                    new KeyFrame(Duration.ZERO, e -> updatePane.setVisible(true)));\n            else nowAnimation.getKeyFrames().add(\n                    new KeyFrame(duration, e -> updatePane.setVisible(false)));\n            nowAnimation.play();\n        } else {\n            updatePane.setVisible(show);\n        }\n    }\n\n    private void launch() {\n        Profile profile = Profiles.getSelectedProfile();\n        Versions.launch(profile, profile.getSelectedVersion());\n    }\n\n    private void launchNoGame() {\n        DownloadProvider downloadProvider = DownloadProviders.getDownloadProvider();\n        VersionList<?> versionList = downloadProvider.getVersionListById(\"game\");\n\n        Holder<String> gameVersionHolder = new Holder<>();\n        Task<?> task = versionList.refreshAsync(\"\")\n                .thenSupplyAsync(() -> versionList.getVersions(\"\").stream()\n                        .filter(it -> it.getVersionType() == RELEASE)\n                        .filter(it -> NativePatcher.checkSupportedStatus(GameVersionNumber.asGameVersion(it.getGameVersion()), Platform.SYSTEM_PLATFORM, OperatingSystem.SYSTEM_VERSION) != NativePatcher.SupportStatus.UNSUPPORTED)\n                        .sorted()\n                        .findFirst()\n                        .orElseThrow(() -> new IOException(\"No versions found\")))\n                .thenComposeAsync(version -> {\n                    Profile profile = Profiles.getSelectedProfile();\n                    DefaultDependencyManager dependency = profile.getDependency();\n                    String gameVersion = gameVersionHolder.value = version.getGameVersion();\n\n                    return dependency.gameBuilder()\n                            .name(gameVersion)\n                            .gameVersion(gameVersion)\n                            .buildAsync();\n                })\n                .whenComplete(any -> profile.getRepository().refreshVersions())\n                .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                    if (exception == null) {\n                        profile.setSelectedVersion(gameVersionHolder.value);\n                        launch();\n                    } else if (exception instanceof CancellationException) {\n                        Controllers.showToast(i18n(\"message.cancelled\"));\n                    } else {\n                        LOG.warning(\"Failed to install game\", exception);\n                        Controllers.dialog(StringUtils.getStackTrace(exception),\n                                i18n(\"install.failed\"),\n                                MessageDialogPane.MessageType.WARNING);\n                    }\n                });\n        Controllers.taskDialog(task, i18n(\"version.launch.empty.installing\"), TaskCancellationAction.NORMAL);\n    }\n\n    private void onUpgrade() {\n        RemoteVersion target = UpdateChecker.getLatestVersion();\n        if (target == null) {\n            return;\n        }\n        UpdateHandler.updateFrom(target);\n    }\n\n    private void closeUpdateBubble() {\n        showUpdate.unbind();\n        showUpdate.set(false);\n    }\n\n    @Override\n    public ReadOnlyObjectWrapper<State> stateProperty() {\n        return state;\n    }\n\n    public Profile getProfile() {\n        return profile;\n    }\n\n    public String getCurrentGame() {\n        return currentGame.get();\n    }\n\n    public StringProperty currentGameProperty() {\n        return currentGame;\n    }\n\n    public void setCurrentGame(String currentGame) {\n        this.currentGame.set(currentGame);\n    }\n\n    public ObservableList<Version> getVersions() {\n        return versions;\n    }\n\n    public boolean isShowUpdate() {\n        return showUpdate.get();\n    }\n\n    public BooleanProperty showUpdateProperty() {\n        return showUpdate;\n    }\n\n    public void setShowUpdate(boolean showUpdate) {\n        this.showUpdate.set(showUpdate);\n    }\n\n    public RemoteVersion getLatestVersion() {\n        return latestVersion.get();\n    }\n\n    public ObjectProperty<RemoteVersion> latestVersionProperty() {\n        return latestVersion;\n    }\n\n    public void setLatestVersion(RemoteVersion latestVersion) {\n        this.latestVersion.set(latestVersion);\n    }\n\n    public void initVersions(Profile profile, List<Version> versions) {\n        FXUtils.checkFxUserThread();\n        this.profile = profile;\n        this.versions.setAll(versions);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.jfoenix.controls.*;\nimport com.jfoenix.effects.JFXDepthManager;\nimport javafx.application.Platform;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.binding.When;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.ColorPicker;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.*;\nimport javafx.scene.text.Font;\nimport javafx.scene.text.FontSmoothingType;\nimport org.jackhuang.hmcl.setting.EnumBackgroundImage;\nimport org.jackhuang.hmcl.setting.FontManager;\nimport org.jackhuang.hmcl.theme.ThemeColor;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.javafx.SafeStringConverter;\n\nimport java.util.Arrays;\nimport java.util.Locale;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class PersonalizationPage extends StackPane {\n\n    private static int snapOpacity(double val) {\n        if (val <= 0) {\n            return 0;\n        } else if (Double.isNaN(val) || val >= 100.) {\n            return 100;\n        }\n\n        int prevTick = (int) (val / 5);\n        int prevTickValue = prevTick * 5;\n        int nextTickValue = (prevTick + 1) * 5;\n\n        return (val - prevTickValue) > (nextTickValue - val) ? nextTickValue : prevTickValue;\n    }\n\n    public PersonalizationPage() {\n        VBox content = new VBox(10);\n        content.setPadding(new Insets(10));\n        content.setFillWidth(true);\n        ScrollPane scrollPane = new ScrollPane(content);\n        FXUtils.smoothScrolling(scrollPane);\n        scrollPane.setFitToWidth(true);\n        getChildren().setAll(scrollPane);\n\n        ComponentList themeList = new ComponentList();\n        {\n            var brightnessPane = new LineSelectButton<String>();\n            brightnessPane.setTitle(i18n(\"settings.launcher.brightness\"));\n            brightnessPane.setConverter(name -> i18n(\"settings.launcher.brightness.\" + name));\n            brightnessPane.setItems(\"auto\", \"light\", \"dark\");\n            brightnessPane.valueProperty().bindBidirectional(config().themeBrightnessProperty());\n\n            themeList.getContent().add(brightnessPane);\n        }\n\n        {\n            BorderPane themePane = new BorderPane();\n            themeList.getContent().add(themePane);\n\n            Label left = new Label(i18n(\"settings.launcher.theme\"));\n            BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n            themePane.setLeft(left);\n\n            StackPane themeColorPickerContainer = new StackPane();\n            themeColorPickerContainer.setMinHeight(30);\n            themePane.setRight(themeColorPickerContainer);\n\n            ColorPicker picker = new JFXColorPicker();\n            picker.getCustomColors().setAll(ThemeColor.STANDARD_COLORS.stream().map(ThemeColor::color).toList());\n            ThemeColor.bindBidirectional(picker, config().themeColorProperty());\n            themeColorPickerContainer.getChildren().setAll(picker);\n            Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));\n        }\n        {\n            LineToggleButton titleTransparentButton = new LineToggleButton();\n            themeList.getContent().add(titleTransparentButton);\n            titleTransparentButton.selectedProperty().bindBidirectional(config().titleTransparentProperty());\n            titleTransparentButton.setTitle(i18n(\"settings.launcher.title_transparent\"));\n        }\n        {\n            LineToggleButton animationButton = new LineToggleButton();\n            themeList.getContent().add(animationButton);\n            animationButton.selectedProperty().bindBidirectional(config().animationDisabledProperty());\n            animationButton.setTitle(i18n(\"settings.launcher.turn_off_animations\"));\n            animationButton.setSubtitle(i18n(\"settings.take_effect_after_restart\"));\n        }\n        content.getChildren().addAll(ComponentList.createComponentListTitle(i18n(\"settings.launcher.appearance\")), themeList);\n\n        {\n            ComponentList componentList = new ComponentList();\n\n            MultiFileItem<EnumBackgroundImage> backgroundItem = new MultiFileItem<>();\n            ComponentSublist backgroundSublist = new ComponentSublist();\n            backgroundSublist.getContent().add(backgroundItem);\n            backgroundSublist.setTitle(i18n(\"launcher.background\"));\n            backgroundSublist.setHasSubtitle(true);\n\n            backgroundItem.loadChildren(Arrays.asList(\n                    new MultiFileItem.Option<>(i18n(\"launcher.background.default\"), EnumBackgroundImage.DEFAULT)\n                            .setTooltip(i18n(\"launcher.background.default.tooltip\")),\n                    new MultiFileItem.Option<>(i18n(\"launcher.background.classic\"), EnumBackgroundImage.CLASSIC),\n                    new MultiFileItem.FileOption<>(i18n(\"settings.custom\"), EnumBackgroundImage.CUSTOM)\n                            .setChooserTitle(i18n(\"launcher.background.choose\"))\n                            .addExtensionFilter(FXUtils.getImageExtensionFilter())\n                            .bindBidirectional(config().backgroundImageProperty()),\n                    new MultiFileItem.StringOption<>(i18n(\"launcher.background.network\"), EnumBackgroundImage.NETWORK)\n                            .setValidators(new URLValidator(true))\n                            .bindBidirectional(config().backgroundImageUrlProperty()),\n                    new MultiFileItem.PaintOption<>(i18n(\"launcher.background.paint\"), EnumBackgroundImage.PAINT)\n                            .bindBidirectional(config().backgroundPaintProperty())\n            ));\n            backgroundItem.selectedDataProperty().bindBidirectional(config().backgroundImageTypeProperty());\n            backgroundSublist.subtitleProperty().bind(\n                    new When(backgroundItem.selectedDataProperty().isEqualTo(EnumBackgroundImage.DEFAULT))\n                            .then(i18n(\"launcher.background.default\"))\n                            .otherwise(config().backgroundImageProperty()));\n\n            HBox opacityItem = new HBox(8);\n            {\n                opacityItem.setAlignment(Pos.CENTER);\n\n                Label label = new Label(i18n(\"settings.launcher.background.settings.opacity\"));\n\n                JFXSlider slider = new JFXSlider(0, 100,\n                        config().getBackgroundImageType() != EnumBackgroundImage.TRANSLUCENT\n                                ? config().getBackgroundImageOpacity() : 50);\n                slider.setShowTickMarks(true);\n                slider.setMajorTickUnit(10);\n                slider.setMinorTickCount(1);\n                slider.setBlockIncrement(5);\n                slider.setSnapToTicks(true);\n                slider.setPadding(new Insets(9, 0, 0, 0));\n                HBox.setHgrow(slider, Priority.ALWAYS);\n\n                if (config().getBackgroundImageType() == EnumBackgroundImage.TRANSLUCENT) {\n                    slider.setDisable(true);\n                    config().backgroundImageTypeProperty().addListener(new ChangeListener<>() {\n                        @Override\n                        public void changed(ObservableValue<? extends EnumBackgroundImage> observable, EnumBackgroundImage oldValue, EnumBackgroundImage newValue) {\n                            if (newValue != EnumBackgroundImage.TRANSLUCENT) {\n                                config().backgroundImageTypeProperty().removeListener(this);\n                                slider.setDisable(false);\n                                slider.setValue(100);\n                            }\n                        }\n                    });\n                }\n\n                Label textOpacity = new Label();\n                FXUtils.setLimitWidth(textOpacity, 50);\n\n                StringBinding valueBinding = Bindings.createStringBinding(() -> ((int) slider.getValue()) + \"%\", slider.valueProperty());\n                textOpacity.textProperty().bind(valueBinding);\n                slider.setValueFactory(s -> valueBinding);\n\n                slider.valueProperty().addListener((observable, oldValue, newValue) ->\n                        config().setBackgroundImageOpacity(snapOpacity(newValue.doubleValue())));\n\n                opacityItem.getChildren().setAll(label, slider, textOpacity);\n            }\n\n            componentList.getContent().setAll(backgroundItem, opacityItem);\n            content.getChildren().addAll(ComponentList.createComponentListTitle(i18n(\"launcher.background\")), componentList);\n        }\n\n        {\n            ComponentList logPane = new ComponentList();\n\n            {\n                VBox fontPane = new VBox();\n                fontPane.setSpacing(5);\n\n                {\n                    BorderPane borderPane = new BorderPane();\n                    fontPane.getChildren().add(borderPane);\n                    {\n                        Label left = new Label(i18n(\"settings.launcher.log.font\"));\n                        BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n                        borderPane.setLeft(left);\n                    }\n\n                    {\n                        HBox hBox = new HBox();\n                        hBox.setSpacing(3);\n\n                        FontComboBox cboLogFont = new FontComboBox();\n                        cboLogFont.valueProperty().bindBidirectional(config().fontFamilyProperty());\n\n                        JFXTextField txtLogFontSize = new JFXTextField();\n                        FXUtils.setLimitWidth(txtLogFontSize, 50);\n                        FXUtils.bind(txtLogFontSize, config().fontSizeProperty(), SafeStringConverter.fromFiniteDouble()\n                                .restrict(it -> it > 0)\n                                .fallbackTo(12.0)\n                                .asPredicate(Validator.addTo(txtLogFontSize)));\n\n                        JFXButton clearButton = FXUtils.newToggleButton4(SVG.RESTORE);\n                        clearButton.setOnAction(e -> cboLogFont.setValue(null));\n\n                        FXUtils.installFastTooltip(clearButton, i18n(\"button.reset\"));\n\n                        hBox.getChildren().setAll(cboLogFont, txtLogFontSize, clearButton);\n\n                        borderPane.setRight(hBox);\n                    }\n                }\n\n                Label lblLogFontDisplay = new Label(\"[23:33:33] [Client Thread/INFO] [WaterPower]: Loaded mod WaterPower.\");\n                lblLogFontDisplay.fontProperty().bind(Bindings.createObjectBinding(\n                        () -> Font.font(Lang.requireNonNullElse(config().getFontFamily(), FXUtils.DEFAULT_MONOSPACE_FONT), config().getFontSize()),\n                        config().fontFamilyProperty(), config().fontSizeProperty()));\n\n                fontPane.getChildren().add(lblLogFontDisplay);\n\n                logPane.getContent().add(fontPane);\n            }\n\n            content.getChildren().addAll(ComponentList.createComponentListTitle(i18n(\"settings.launcher.log\")), logPane);\n        }\n\n        {\n            ComponentList fontPane = new ComponentList();\n\n            {\n                VBox vbox = new VBox();\n                vbox.setSpacing(5);\n\n                {\n                    BorderPane borderPane = new BorderPane();\n                    vbox.getChildren().add(borderPane);\n                    {\n                        Label left = new Label(i18n(\"settings.launcher.font\"));\n                        BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n                        borderPane.setLeft(left);\n                    }\n\n                    {\n                        HBox hBox = new HBox();\n                        hBox.setSpacing(8);\n\n                        FontComboBox cboFont = new FontComboBox();\n                        cboFont.setValue(config().getLauncherFontFamily());\n                        FXUtils.onChange(cboFont.valueProperty(), FontManager::setFontFamily);\n\n                        JFXButton clearButton = FXUtils.newToggleButton4(SVG.RESTORE);\n                        clearButton.setOnAction(e -> cboFont.setValue(null));\n\n                        FXUtils.installFastTooltip(clearButton, i18n(\"button.reset\"));\n\n                        hBox.getChildren().setAll(cboFont, clearButton);\n\n                        borderPane.setRight(hBox);\n                    }\n                }\n\n                vbox.getChildren().add(new Label(\"Hello Minecraft! Launcher\"));\n\n                fontPane.getContent().add(vbox);\n            }\n\n            {\n                var fontAntiAliasingPane = new LineSelectButton<Optional<FontSmoothingType>>();\n                fontAntiAliasingPane.setTitle(i18n(\"settings.launcher.font.anti_aliasing\"));\n                fontAntiAliasingPane.setSubtitle(i18n(\"settings.take_effect_after_restart\"));\n                fontAntiAliasingPane.setConverter(value ->\n                        value.isPresent()\n                                ? i18n(\"settings.launcher.font.anti_aliasing.\" + value.get().name().toLowerCase(Locale.ROOT))\n                                : i18n(\"settings.launcher.font.anti_aliasing.auto\")\n                );\n                fontAntiAliasingPane.setItems(\n                        Optional.empty(),\n                        Optional.of(FontSmoothingType.LCD),\n                        Optional.of(FontSmoothingType.GRAY)\n                );\n\n                String fontAntiAliasing = globalConfig().getFontAntiAliasing();\n                if (\"lcd\".equalsIgnoreCase(fontAntiAliasing)) {\n                    fontAntiAliasingPane.setValue(Optional.of(FontSmoothingType.LCD));\n                } else if (\"gray\".equalsIgnoreCase(fontAntiAliasing)) {\n                    fontAntiAliasingPane.setValue(Optional.of(FontSmoothingType.GRAY));\n                } else {\n                    fontAntiAliasingPane.setValue(Optional.empty());\n                }\n\n                FXUtils.onChange(fontAntiAliasingPane.valueProperty(), value ->\n                        globalConfig().setFontAntiAliasing(value.map(it -> it.name().toLowerCase(Locale.ROOT))\n                                .orElse(null)));\n\n                fontPane.getContent().add(fontAntiAliasingPane);\n            }\n\n            content.getChildren().addAll(ComponentList.createComponentListTitle(i18n(\"settings.launcher.font\")), fontPane);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.jfoenix.controls.JFXPopup;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.scene.layout.Region;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.event.EventBus;\nimport org.jackhuang.hmcl.event.RefreshedVersionsEvent;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.game.ModpackHelper;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.terracotta.TerracottaMetadata;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;\nimport org.jackhuang.hmcl.ui.account.AccountListPopupMenu;\nimport org.jackhuang.hmcl.ui.animation.AnimationUtils;\nimport org.jackhuang.hmcl.ui.construct.AdvancedListBox;\nimport org.jackhuang.hmcl.ui.construct.AdvancedListItem;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;\nimport org.jackhuang.hmcl.ui.nbt.NBTEditorPage;\nimport org.jackhuang.hmcl.ui.nbt.NBTFileType;\nimport org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;\nimport org.jackhuang.hmcl.ui.versions.GameListPopupMenu;\nimport org.jackhuang.hmcl.ui.versions.Versions;\nimport org.jackhuang.hmcl.upgrade.UpdateChecker;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.platform.*;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class RootPage extends DecoratorAnimatedPage implements DecoratorPage {\n    private MainPage mainPage = null;\n\n    public RootPage() {\n        EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class)\n                .register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));\n\n        Profile profile = Profiles.getSelectedProfile();\n        if (profile != null && profile.getRepository().isLoaded())\n            onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository());\n\n        getStyleClass().remove(\"gray-background\");\n        getLeft().getStyleClass().add(\"gray-background\");\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return getMainPage().stateProperty();\n    }\n\n    @Override\n    protected Skin createDefaultSkin() {\n        return new Skin(this);\n    }\n\n    public MainPage getMainPage() {\n        if (mainPage == null) {\n            MainPage mainPage = new MainPage();\n            FXUtils.applyDragListener(mainPage,\n                    file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file),\n                    modpacks -> {\n                        Path file = modpacks.get(0);\n                        if (ModpackHelper.isFileModpackByExtension(file)) {\n                            Controllers.getDecorator().startWizard(\n                                    new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),\n                                    i18n(\"install.modpack\"));\n                        } else if (NBTFileType.isNBTFileByExtension(file)) {\n                            try {\n                                Controllers.navigate(new NBTEditorPage(file));\n                            } catch (Throwable e) {\n                                LOG.warning(\"Fail to open nbt file\", e);\n                                Controllers.dialog(i18n(\"nbt.open.failed\") + \"\\n\\n\" + StringUtils.getStackTrace(e),\n                                        i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n                            }\n                        }\n                    });\n\n            FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), mainPage::setCurrentGame);\n            mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());\n            mainPage.latestVersionProperty().bind(UpdateChecker.latestVersionProperty());\n\n            Profiles.registerVersionsListener(profile -> {\n                HMCLGameRepository repository = profile.getRepository();\n                List<Version> children = repository.getVersions().parallelStream()\n                        .filter(version -> !version.isHidden())\n                        .sorted(Comparator\n                                .comparing((Version version) -> Lang.requireNonNullElse(version.getReleaseTime(), Instant.EPOCH))\n                                .thenComparing(version -> VersionNumber.asVersion(repository.getGameVersion(version).orElse(version.getId()))))\n                        .collect(Collectors.toList());\n                runInFX(() -> {\n                    if (profile == Profiles.getSelectedProfile())\n                        mainPage.initVersions(profile, children);\n                });\n            });\n            this.mainPage = mainPage;\n        }\n        return mainPage;\n    }\n\n    private static class Skin extends DecoratorAnimatedPageSkin<RootPage> {\n\n        protected Skin(RootPage control) {\n            super(control);\n\n            // first item in left sidebar\n            AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();\n            accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));\n            FXUtils.onSecondaryButtonClicked(accountListItem, () -> AccountListPopupMenu.show(accountListItem, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, accountListItem.getWidth(), 0));\n            accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());\n\n            // second item in left sidebar\n            GameAdvancedListItem gameListItem = new GameAdvancedListItem();\n            gameListItem.setOnAction(e -> {\n                Profile profile = Profiles.getSelectedProfile();\n                String version = Profiles.getSelectedVersion();\n                if (version == null) {\n                    Controllers.navigate(Controllers.getGameListPage());\n                } else {\n                    Versions.modifyGameSettings(profile, version);\n                }\n            });\n            FXUtils.onScroll(gameListItem, getSkinnable().getMainPage().getVersions(), list -> {\n                String currentId = getSkinnable().getMainPage().getCurrentGame();\n                return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));\n            }, it -> getSkinnable().getMainPage().getProfile().setSelectedVersion(it.getId()));\n            if (AnimationUtils.isAnimationEnabled()) {\n                FXUtils.prepareOnMouseEnter(gameListItem, Controllers::prepareVersionPage);\n            }\n            FXUtils.onSecondaryButtonClicked(gameListItem, () -> showGameListPopupMenu(gameListItem));\n\n            // third item in left sidebar\n            AdvancedListItem gameItem = new AdvancedListItem();\n            gameItem.setLeftIcon(SVG.FORMAT_LIST_BULLETED);\n            gameItem.setTitle(i18n(\"version.manage\"));\n            gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));\n            FXUtils.onSecondaryButtonClicked(gameItem, () -> showGameListPopupMenu(gameItem));\n\n            // forth item in left sidebar\n            AdvancedListItem downloadItem = new AdvancedListItem();\n            downloadItem.setLeftIcon(SVG.DOWNLOAD);\n            downloadItem.setTitle(i18n(\"download\"));\n            downloadItem.setOnAction(e -> {\n                Controllers.getDownloadPage().showGameDownloads();\n                Controllers.navigate(Controllers.getDownloadPage());\n            });\n            FXUtils.installFastTooltip(downloadItem, i18n(\"download.hint\"));\n            if (AnimationUtils.isAnimationEnabled()) {\n                FXUtils.prepareOnMouseEnter(downloadItem, Controllers::prepareDownloadPage);\n            }\n\n            // fifth item in left sidebar\n            AdvancedListItem launcherSettingsItem = new AdvancedListItem();\n            launcherSettingsItem.setLeftIcon(SVG.SETTINGS);\n            launcherSettingsItem.setTitle(i18n(\"settings\"));\n            launcherSettingsItem.setOnAction(e -> {\n                Controllers.getSettingsPage().showGameSettings(Profiles.getSelectedProfile());\n                Controllers.navigate(Controllers.getSettingsPage());\n            });\n            if (AnimationUtils.isAnimationEnabled()) {\n                FXUtils.prepareOnMouseEnter(launcherSettingsItem, Controllers::prepareSettingsPage);\n            }\n\n            // sixth item in left sidebar\n            AdvancedListItem terracottaItem = new AdvancedListItem();\n            terracottaItem.setLeftIcon(SVG.GRAPH2);\n            terracottaItem.setTitle(i18n(\"terracotta\"));\n            terracottaItem.setOnAction(e -> {\n                if (TerracottaMetadata.PROVIDER != null) {\n                    Controllers.navigate(Controllers.getTerracottaPage());\n                } else {\n                    String message;\n                    if (Architecture.SYSTEM_ARCH.getBits() == Bits.BIT_32)\n                        message = i18n(\"terracotta.unsupported.arch.32bit\");\n                    else if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS\n                            && !OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_10))\n                        message = i18n(\"terracotta.unsupported.os.windows.old\");\n                    else if (Platform.SYSTEM_PLATFORM.equals(OperatingSystem.LINUX, Architecture.LOONGARCH64_OW))\n                        message = i18n(\"terracotta.unsupported.arch.loongarch64_ow\");\n                    else\n                        message = i18n(\"terracotta.unsupported\");\n\n                    Controllers.dialog(message, null, MessageDialogPane.MessageType.WARNING);\n                }\n            });\n\n            // the left sidebar\n            AdvancedListBox sideBar = new AdvancedListBox()\n                    .startCategory(i18n(\"account\").toUpperCase(Locale.ROOT))\n                    .add(accountListItem)\n                    .startCategory(i18n(\"version\").toUpperCase(Locale.ROOT))\n                    .add(gameListItem)\n                    .add(gameItem)\n                    .add(downloadItem)\n                    .startCategory(i18n(\"settings.launcher.general\").toUpperCase(Locale.ROOT))\n                    .add(launcherSettingsItem)\n                    .add(terracottaItem)\n                    .addNavigationDrawerItem(i18n(\"contact.chat\"), SVG.CHAT, () -> {\n                        Controllers.getSettingsPage().showFeedback();\n                        Controllers.navigate(Controllers.getSettingsPage());\n                    });\n\n            // the root page, with the sidebar in left, navigator in center.\n            setLeft(sideBar);\n            setCenter(getSkinnable().getMainPage());\n        }\n\n        public void showGameListPopupMenu(Region gameListItem) {\n            GameListPopupMenu.show(gameListItem,\n                    JFXPopup.PopupVPosition.TOP,\n                    JFXPopup.PopupHPosition.LEFT,\n                    gameListItem.getWidth(),\n                    0,\n                    getSkinnable().getMainPage().getProfile(),\n                    getSkinnable().getMainPage().getVersions());\n        }\n    }\n\n    private boolean checkedModpack = false;\n\n    private void onRefreshedVersions(HMCLGameRepository repository) {\n        runInFX(() -> {\n            if (!checkedModpack) {\n                checkedModpack = true;\n\n                if (repository.getVersionCount() == 0) {\n                    Path zipModpack = Metadata.CURRENT_DIRECTORY.resolve(\"modpack.zip\");\n                    Path mrpackModpack = Metadata.CURRENT_DIRECTORY.resolve(\"modpack.mrpack\");\n\n                    Path modpackFile;\n                    if (Files.exists(zipModpack)) {\n                        modpackFile = zipModpack;\n                    } else if (Files.exists(mrpackModpack)) {\n                        modpackFile = mrpackModpack;\n                    } else {\n                        modpackFile = null;\n                    }\n\n                    if (modpackFile != null) {\n                        Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile))\n                                .thenApplyAsync(encoding -> ModpackHelper.readModpackManifest(modpackFile, encoding))\n                                .thenApplyAsync(modpack -> ModpackHelper\n                                        .getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack, null)\n                                        .executor())\n                                .thenAcceptAsync(Schedulers.javafx(), executor -> {\n                                    Controllers.taskDialog(executor, i18n(\"modpack.installing\"), TaskCancellationAction.NO_CANCEL);\n                                    executor.start();\n                                }).start();\n                    }\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.main;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXRadioButton;\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.geometry.VPos;\nimport javafx.scene.Cursor;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.ToggleGroup;\nimport javafx.scene.layout.*;\nimport javafx.scene.text.TextAlignment;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.setting.EnumCommonDirectory;\nimport org.jackhuang.hmcl.setting.Settings;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;\nimport org.jackhuang.hmcl.upgrade.RemoteVersion;\nimport org.jackhuang.hmcl.upgrade.UpdateChannel;\nimport org.jackhuang.hmcl.upgrade.UpdateChecker;\nimport org.jackhuang.hmcl.upgrade.UpdateHandler;\nimport org.jackhuang.hmcl.util.AprilFools;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.i18n.SupportedLocale;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.tukaani.xz.XZInputStream;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.*;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.Lang.thread;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class SettingsPage extends ScrollPane {\n\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final ToggleGroup updateChannelGroup;\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final InvalidationListener updateListener;\n\n    public SettingsPage() {\n        this.setFitToWidth(true);\n\n        VBox rootPane = new VBox();\n        rootPane.setPadding(new Insets(10));\n        this.setContent(rootPane);\n        FXUtils.smoothScrolling(this);\n\n        ComponentList settingsPane = new ComponentList();\n        {\n            {\n                StackPane sponsorPane = new StackPane();\n                sponsorPane.setCursor(Cursor.HAND);\n                FXUtils.onClicked(sponsorPane, this::onSponsor);\n                sponsorPane.setPadding(new Insets(8, 0, 8, 0));\n\n                GridPane gridPane = new GridPane();\n\n                ColumnConstraints col = new ColumnConstraints();\n                col.setHgrow(Priority.SOMETIMES);\n                col.setMaxWidth(Double.POSITIVE_INFINITY);\n\n                gridPane.getColumnConstraints().setAll(col);\n\n                RowConstraints row = new RowConstraints();\n                row.setMinHeight(Double.NEGATIVE_INFINITY);\n                row.setValignment(VPos.TOP);\n                row.setVgrow(Priority.SOMETIMES);\n                gridPane.getRowConstraints().setAll(row);\n\n                {\n                    Label label = new Label(i18n(\"sponsor.hmcl\"));\n                    label.setWrapText(true);\n                    label.setTextAlignment(TextAlignment.JUSTIFY);\n                    GridPane.setRowIndex(label, 0);\n                    GridPane.setColumnIndex(label, 0);\n                    gridPane.getChildren().add(label);\n                }\n\n                sponsorPane.getChildren().setAll(gridPane);\n                settingsPane.getContent().add(sponsorPane);\n            }\n\n            {\n                ComponentSublist updatePane = new ComponentSublist();\n                updatePane.setTitle(i18n(\"update\"));\n                updatePane.setHasSubtitle(true);\n\n                final Label lblUpdate;\n                final Label lblUpdateSub;\n                {\n                    VBox headerLeft = new VBox();\n\n                    lblUpdate = new Label(i18n(\"update\"));\n                    lblUpdate.getStyleClass().add(\"title-label\");\n                    lblUpdateSub = new Label();\n                    lblUpdateSub.getStyleClass().add(\"subtitle-label\");\n\n                    headerLeft.getChildren().setAll(lblUpdate, lblUpdateSub);\n                    updatePane.setHeaderLeft(headerLeft);\n                }\n\n                {\n                    JFXButton btnUpdate = FXUtils.newToggleButton4(SVG.UPDATE, 20);\n                    btnUpdate.setOnAction(e -> onUpdate());\n                    FXUtils.installFastTooltip(btnUpdate, i18n(\"update.tooltip\"));\n\n                    updateListener = any -> {\n                        btnUpdate.setVisible(UpdateChecker.isOutdated());\n\n                        if (UpdateChecker.isOutdated()) {\n                            lblUpdateSub.setText(i18n(\"update.newest_version\", UpdateChecker.getLatestVersion().getVersion()));\n                            lblUpdateSub.getStyleClass().setAll(\"update-label\");\n\n                            lblUpdate.setText(i18n(\"update.found\"));\n                            lblUpdate.getStyleClass().setAll(\"update-label\");\n                        } else if (UpdateChecker.isCheckingUpdate()) {\n                            lblUpdateSub.setText(i18n(\"update.checking\"));\n                            lblUpdateSub.getStyleClass().setAll(\"subtitle-label\");\n\n                            lblUpdate.setText(i18n(\"update\"));\n                            lblUpdate.getStyleClass().setAll(\"title-label\");\n                        } else {\n                            lblUpdateSub.setText(i18n(\"update.latest\"));\n                            lblUpdateSub.getStyleClass().setAll(\"subtitle-label\");\n\n                            lblUpdate.setText(i18n(\"update\"));\n                            lblUpdate.getStyleClass().setAll(\"title-label\");\n                        }\n                    };\n                    UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));\n                    UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));\n                    UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));\n                    updateListener.invalidated(null);\n\n                    updatePane.setHeaderRight(btnUpdate);\n                }\n\n                {\n                    VBox content = new VBox(12);\n                    content.setPadding(new Insets(8, 0, 0, 0));\n\n                    updateChannelGroup = new ToggleGroup();\n\n                    JFXRadioButton chkUpdateStable = new JFXRadioButton(i18n(\"update.channel.stable\"));\n                    chkUpdateStable.setUserData(UpdateChannel.STABLE);\n                    chkUpdateStable.setToggleGroup(updateChannelGroup);\n\n                    JFXRadioButton chkUpdateDev = new JFXRadioButton(i18n(\"update.channel.dev\"));\n                    chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);\n                    chkUpdateDev.setToggleGroup(updateChannelGroup);\n\n                    Label noteWrapper = new Label(i18n(\"update.note\"));\n                    VBox.setMargin(noteWrapper, new Insets(8, 0, 0, 0));\n\n                    content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);\n\n                    updatePane.getContent().add(content);\n                }\n                settingsPane.getContent().add(updatePane);\n            }\n\n            {\n                LineToggleButton previewPane = new LineToggleButton();\n                previewPane.setTitle(i18n(\"update.preview\"));\n                previewPane.setSubtitle(i18n(\"update.preview.subtitle\"));\n                previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());\n\n                ObjectProperty<UpdateChannel> updateChannel = selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class);\n                updateChannel.set(UpdateChannel.getChannel());\n                InvalidationListener checkUpdateListener = e -> {\n                    UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());\n                };\n                updateChannel.addListener(checkUpdateListener);\n                previewPane.selectedProperty().addListener(checkUpdateListener);\n\n                settingsPane.getContent().add(previewPane);\n            }\n\n            {\n                LineToggleButton disableAutoShowUpdateDialogPane = new LineToggleButton();\n                disableAutoShowUpdateDialogPane.setTitle(i18n(\"update.disable_auto_show_update_dialog\"));\n                disableAutoShowUpdateDialogPane.setSubtitle(i18n(\"update.disable_auto_show_update_dialog.subtitle\"));\n                disableAutoShowUpdateDialogPane.selectedProperty().bindBidirectional(config().disableAutoShowUpdateDialogProperty());\n                settingsPane.getContent().add(disableAutoShowUpdateDialogPane);\n            }\n\n            if (AprilFools.isShowAprilFoolsSettings()) {\n                LineToggleButton disableAprilFools = new LineToggleButton();\n                disableAprilFools.setTitle(i18n(\"settings.launcher.disable_april_fools\"));\n                disableAprilFools.setSubtitle(i18n(\"settings.take_effect_after_restart\"));\n                disableAprilFools.selectedProperty().bindBidirectional(config().disableAprilFoolsProperty());\n                settingsPane.getContent().add(disableAprilFools);\n            }\n\n            {\n                MultiFileItem<EnumCommonDirectory> fileCommonLocation = new MultiFileItem<>();\n                fileCommonLocation.loadChildren(Arrays.asList(\n                        new MultiFileItem.Option<>(i18n(\"launcher.cache_directory.default\"), EnumCommonDirectory.DEFAULT),\n                        new MultiFileItem.FileOption<>(i18n(\"settings.custom\"), EnumCommonDirectory.CUSTOM)\n                                .setChooserTitle(i18n(\"launcher.cache_directory.choose\"))\n                                .setDirectory(true)\n                                .bindBidirectional(config().commonDirectoryProperty())\n                ));\n                fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());\n\n                ComponentSublist fileCommonLocationSublist = new ComponentSublist();\n                fileCommonLocationSublist.getContent().add(fileCommonLocation);\n                fileCommonLocationSublist.setTitle(i18n(\"launcher.cache_directory\"));\n                fileCommonLocationSublist.setHasSubtitle(true);\n                fileCommonLocationSublist.subtitleProperty().bind(\n                        Bindings.createObjectBinding(() -> Optional.ofNullable(Settings.instance().getCommonDirectory())\n                                        .orElse(i18n(\"launcher.cache_directory.disabled\")),\n                                config().commonDirectoryProperty(), config().commonDirTypeProperty()));\n\n                JFXButton cleanButton = FXUtils.newBorderButton(i18n(\"launcher.cache_directory.clean\"));\n                cleanButton.setOnAction(e -> clearCacheDirectory());\n                fileCommonLocationSublist.setHeaderRight(cleanButton);\n\n                settingsPane.getContent().add(fileCommonLocationSublist);\n            }\n\n            {\n                var chooseLanguagePane = new LineSelectButton<SupportedLocale>();\n                chooseLanguagePane.setTitle(i18n(\"settings.launcher.language\"));\n                chooseLanguagePane.setSubtitle(i18n(\"settings.take_effect_after_restart\"));\n\n                SupportedLocale currentLocale = I18n.getLocale();\n                chooseLanguagePane.setConverter(locale -> {\n                    if (locale.isDefault())\n                        return locale.getDisplayName(currentLocale);\n                    else if (locale.isSameLanguage(currentLocale))\n                        return locale.getDisplayName(locale);\n                    else\n                        return locale.getDisplayName(currentLocale) + \" - \" + locale.getDisplayName(locale);\n                });\n                chooseLanguagePane.setItems(SupportedLocale.getSupportedLocales());\n                chooseLanguagePane.valueProperty().bindBidirectional(config().localizationProperty());\n\n                settingsPane.getContent().add(chooseLanguagePane);\n            }\n\n            {\n                LineToggleButton disableAutoGameOptionsPane = new LineToggleButton();\n                disableAutoGameOptionsPane.setTitle(i18n(\"settings.launcher.disable_auto_game_options\"));\n                disableAutoGameOptionsPane.selectedProperty().bindBidirectional(config().disableAutoGameOptionsProperty());\n\n                settingsPane.getContent().add(disableAutoGameOptionsPane);\n            }\n\n            {\n                BorderPane debugPane = new BorderPane();\n\n                Label left = new Label(i18n(\"settings.launcher.debug\"));\n                BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n                debugPane.setLeft(left);\n\n                JFXButton openLogFolderButton = new JFXButton(i18n(\"settings.launcher.launcher_log.reveal\"));\n                openLogFolderButton.setOnAction(e -> openLogFolder());\n                openLogFolderButton.getStyleClass().add(\"jfx-button-border\");\n                if (LOG.getLogFile() == null)\n                    openLogFolderButton.setDisable(true);\n\n                JFXButton logButton = FXUtils.newBorderButton(i18n(\"settings.launcher.launcher_log.export\"));\n                logButton.setOnAction(e -> onExportLogs());\n\n                HBox buttonBox = new HBox();\n                buttonBox.setSpacing(10);\n                buttonBox.getChildren().addAll(openLogFolderButton, logButton);\n                BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT);\n                debugPane.setRight(buttonBox);\n\n                settingsPane.getContent().add(debugPane);\n            }\n\n            rootPane.getChildren().add(settingsPane);\n        }\n    }\n\n    private void openLogFolder() {\n        FXUtils.openFolder(LOG.getLogFile().getParent());\n    }\n\n    private void onUpdate() {\n        RemoteVersion target = UpdateChecker.getLatestVersion();\n        if (target == null) {\n            return;\n        }\n        UpdateHandler.updateFrom(target);\n    }\n\n    private static String getEntryName(Set<String> entryNames, String name) {\n        if (entryNames.add(name)) {\n            return name;\n        }\n\n        for (long i = 1; ; i++) {\n            String newName = name + \".\" + i;\n            if (entryNames.add(newName)) {\n                return newName;\n            }\n        }\n    }\n\n    /// This method guarantees to close both `input` and the current zip entry.\n    ///\n    /// If no exception occurs, this method returns `true`;\n    /// If an exception occurs while reading from `input`, this method returns `false`;\n    /// If an exception occurs while writing to `output`, this method will throw it as is.\n    private static boolean exportLogFile(ZipOutputStream output,\n                                         Path file, // For logging\n                                         String entryName,\n                                         InputStream input,\n                                         byte[] buffer) throws IOException {\n        //noinspection TryFinallyCanBeTryWithResources\n        try {\n            output.putNextEntry(new ZipEntry(entryName));\n            int read;\n            while (true) {\n                try {\n                    read = input.read(buffer);\n                    if (read <= 0)\n                        return true;\n                } catch (Throwable ex) {\n                    LOG.warning(\"Failed to decompress log file \" + file, ex);\n                    return false;\n                }\n\n                output.write(buffer, 0, read);\n            }\n        } finally {\n            try {\n                input.close();\n            } catch (Throwable ex) {\n                LOG.warning(\"Failed to close log file \" + file, ex);\n            }\n            output.closeEntry();\n        }\n    }\n\n    private void onExportLogs() {\n        thread(() -> {\n            String nameBase = \"hmcl-exported-logs-\" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH-mm-ss\"));\n            List<Path> recentLogFiles = LOG.findRecentLogFiles(5);\n\n            Path outputFile;\n            try {\n                if (recentLogFiles.isEmpty()) {\n                    outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + \".log\");\n\n                    LOG.info(\"Exporting latest logs to \" + outputFile);\n                    try (OutputStream output = Files.newOutputStream(outputFile)) {\n                        LOG.exportLogs(output);\n                    }\n                } else {\n                    outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + \".zip\");\n\n                    LOG.info(\"Exporting latest logs to \" + outputFile);\n\n                    byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];\n                    try (var os = Files.newOutputStream(outputFile);\n                         var zos = new ZipOutputStream(os)) {\n\n                        Set<String> entryNames = new HashSet<>();\n\n                        for (Path path : recentLogFiles) {\n                            String fileName = FileUtils.getName(path);\n                            String extension = StringUtils.substringAfterLast(fileName, '.');\n\n                            if (\"gz\".equals(extension) || \"xz\".equals(extension)) {\n                                // If an exception occurs while decompressing the input file, we should\n                                // ensure the input file and the current zip entry are closed,\n                                // then copy the compressed file content as-is into a new entry in the zip file.\n\n                                InputStream input = null;\n                                try {\n                                    input = Files.newInputStream(path);\n                                    input = \"gz\".equals(extension)\n                                            ? new GZIPInputStream(input)\n                                            : new XZInputStream(input);\n                                } catch (Throwable ex) {\n                                    LOG.warning(\"Failed to open log file \" + path, ex);\n                                    IOUtils.closeQuietly(input, ex);\n                                    input = null;\n                                }\n\n                                String entryName = getEntryName(entryNames, StringUtils.substringBeforeLast(fileName, \".\"));\n                                if (input != null && exportLogFile(zos, path, entryName, input, buffer))\n                                    continue;\n                            }\n\n                            // Copy the log file content as-is into a new entry in the zip file.\n                            // If an exception occurs while decompressing the input file, we should\n                            // ensure the input file and the current zip entry are closed.\n\n                            InputStream input;\n                            try {\n                                input = Files.newInputStream(path);\n                            } catch (Throwable ex) {\n                                LOG.warning(\"Failed to open log file \" + path, ex);\n                                continue;\n                            }\n\n                            exportLogFile(zos, path, getEntryName(entryNames, fileName), input, buffer);\n                        }\n\n                        zos.putNextEntry(new ZipEntry(getEntryName(entryNames, \"hmcl-latest.log\")));\n                        LOG.exportLogs(zos);\n                        zos.closeEntry();\n                    }\n                }\n            } catch (IOException e) {\n                LOG.warning(\"Failed to export logs\", e);\n                Platform.runLater(() -> Controllers.dialog(i18n(\"settings.launcher.launcher_log.export.failed\") + \"\\n\" + StringUtils.getStackTrace(e), null, MessageType.ERROR));\n                return;\n            }\n\n            Platform.runLater(() -> Controllers.dialog(i18n(\"settings.launcher.launcher_log.export.success\", outputFile)));\n            FXUtils.showFileInExplorer(outputFile);\n        });\n    }\n\n    private void onSponsor() {\n        FXUtils.openLink(\"https://github.com/HMCL-dev/HMCL\");\n    }\n\n    private void clearCacheDirectory() {\n        String commonDirectory = Settings.instance().getCommonDirectory();\n        if (commonDirectory != null) {\n            FileUtils.cleanDirectoryQuietly(Path.of(commonDirectory, \"cache\"));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/nbt/NBTEditorPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.nbt;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.PageCloseEvent;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\n/**\n * @author Glavo\n */\npublic final class NBTEditorPage extends SpinnerPane implements DecoratorPage {\n    private final ReadOnlyObjectWrapper<State> state;\n    private final Path file;\n    private final NBTFileType type;\n\n    private final BorderPane root = new BorderPane();\n\n    public NBTEditorPage(Path file) throws IOException {\n        getStyleClass().add(\"gray-background\");\n\n        this.state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n(\"nbt.title\", file.toString())));\n        this.file = file;\n        this.type = NBTFileType.ofFile(file);\n\n        if (type == null) {\n            throw new IOException(\"Unknown type of file \" + file);\n        }\n\n        setContent(root);\n        setLoading(true);\n\n        HBox actions = new HBox(8);\n        actions.setPadding(new Insets(8));\n        actions.setAlignment(Pos.CENTER_RIGHT);\n\n        JFXButton saveButton = FXUtils.newRaisedButton(i18n(\"button.save\"));\n        saveButton.setOnAction(e -> {\n            try {\n                save();\n            } catch (IOException ex) {\n                LOG.warning(\"Failed to save NBT file\", ex);\n                Controllers.dialog(i18n(\"nbt.save.failed\") + \"\\n\\n\" + StringUtils.getStackTrace(ex));\n            }\n        });\n\n        JFXButton cancelButton = FXUtils.newRaisedButton(i18n(\"button.cancel\"));\n        cancelButton.setOnAction(e -> fireEvent(new PageCloseEvent()));\n        onEscPressed(this, cancelButton::fire);\n\n        actions.getChildren().setAll(saveButton, cancelButton);\n\n        Task.supplyAsync(() -> type.readAsTree(file))\n                .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                    if (exception == null) {\n                        setLoading(false);\n                        NBTTreeView view = new NBTTreeView(result);\n                        BorderPane.setMargin(view, new Insets(10));\n                        onEscPressed(view, cancelButton::fire);\n                        root.setCenter(view);\n                    } else {\n                        LOG.warning(\"Fail to open nbt file\", exception);\n                        Controllers.dialog(i18n(\"nbt.open.failed\") + \"\\n\\n\" + StringUtils.getStackTrace(exception), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);\n                    }\n                }).start();\n    }\n\n    public void save() throws IOException {\n        // TODO\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/nbt/NBTFileType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.nbt;\n\nimport com.github.steveice10.opennbt.NBTIO;\nimport com.github.steveice10.opennbt.tag.builtin.CompoundTag;\nimport com.github.steveice10.opennbt.tag.builtin.IntTag;\nimport com.github.steveice10.opennbt.tag.builtin.ListTag;\nimport com.github.steveice10.opennbt.tag.builtin.Tag;\nimport kala.compress.utils.BoundedInputStream;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.BufferedInputStream;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.RandomAccessFile;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.Inflater;\nimport java.util.zip.InflaterInputStream;\n\n/**\n * @author Glavo\n */\npublic enum NBTFileType {\n    COMPRESSED(\"dat\", \"dat_old\") {\n        @Override\n        public Tag read(Path file) throws IOException {\n            try (BufferedInputStream fileInputStream = new BufferedInputStream(Files.newInputStream(file))) {\n                fileInputStream.mark(3);\n                byte[] header = new byte[3];\n                if (fileInputStream.read(header) < 3) {\n                    throw new IOException(\"File is too small\");\n                }\n                fileInputStream.reset();\n\n                InputStream input;\n                if (Arrays.equals(header, new byte[]{0x1f, (byte) 0x8b, 0x08})) {\n                    input = new GZIPInputStream(fileInputStream);\n                } else {\n                    input = fileInputStream;\n                }\n\n                Tag tag = NBTIO.readTag(input);\n                if (!(tag instanceof CompoundTag))\n                    throw new IOException(\"Unexpected tag: \" + tag);\n                return tag;\n            }\n        }\n    },\n    ANVIL(\"mca\") {\n        @Override\n        public Tag read(Path file) throws IOException {\n            return REGION.read(file);\n        }\n\n        @Override\n        public NBTTreeView.Item readAsTree(Path file) throws IOException {\n            return REGION.readAsTree(file);\n        }\n    },\n    REGION(\"mcr\") {\n        @Override\n        public Tag read(Path file) throws IOException {\n            try (RandomAccessFile r = new RandomAccessFile(file.toFile(), \"r\")) {\n                ListTag tag = new ListTag(file.getFileName().toString(), CompoundTag.class);\n                if (r.length() == 0) {\n                    return tag;\n                }\n\n                byte[] header = new byte[4096];\n                byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB\n                Inflater inflater = new Inflater();\n\n                r.readFully(header);\n                for (int i = 0; i < 4096; i += 4) {\n                    int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff);\n                    int length = header[i + 3] & 0xff;\n\n                    if (offset == 0 || length == 0) {\n                        continue;\n                    }\n\n                    r.seek(offset * 4096L);\n                    r.readFully(buffer, 0, length * 4096);\n\n                    int chunkLength = ((buffer[0] & 0xff) << 24) + ((buffer[1] & 0xff) << 16) + ((buffer[2] & 0xff) << 8) + (buffer[3] & 0xff);\n\n                    InputStream input = new ByteArrayInputStream(buffer);\n                    input.skip(5);\n                    input = new BoundedInputStream(input, chunkLength - 1);\n\n                    switch (buffer[4]) {\n                        case 0x01:\n                            // GZip\n                            input = new GZIPInputStream(input);\n                            break;\n                        case 0x02:\n                            // Zlib\n                            inflater.reset();\n                            input = new InflaterInputStream(input, inflater);\n                            break;\n                        case 0x03:\n                            // Uncompressed\n                            break;\n                        default:\n                            throw new IOException(\"Unsupported compression method: \" + Integer.toHexString(buffer[4] & 0xff));\n                    }\n\n                    try (InputStream in = input) {\n                        Tag chunk = NBTIO.readTag(in);\n                        if (!(chunk instanceof CompoundTag))\n                            throw new IOException(\"Unexpected tag: \" + chunk);\n\n                        tag.add(chunk);\n                    }\n                }\n                return tag;\n            }\n        }\n\n        @Override\n        public NBTTreeView.Item readAsTree(Path file) throws IOException {\n            NBTTreeView.Item item = new NBTTreeView.Item(read(file));\n\n            for (Tag tag : ((ListTag) item.getValue())) {\n                CompoundTag chunk = (CompoundTag) tag;\n\n                NBTTreeView.Item tree = NBTTreeView.buildTree(chunk);\n\n                Tag xPos = chunk.get(\"xPos\");\n                Tag zPos = chunk.get(\"zPos\");\n\n                if (xPos instanceof IntTag && zPos instanceof IntTag) {\n                    tree.setText(String.format(\"Chunk: %d  %d\", xPos.getValue(), zPos.getValue()));\n                } else {\n                    tree.setText(\"Chunk: Unknown\");\n                }\n\n                item.getChildren().add(tree);\n            }\n\n            return item;\n        }\n    };\n\n    static final NBTFileType[] types = values();\n\n    public static boolean isNBTFileByExtension(Path file) {\n        return NBTFileType.ofFile(file) != null;\n    }\n\n    public static NBTFileType ofFile(Path file) {\n        String ext = FileUtils.getExtension(file);\n        for (NBTFileType type : types) {\n            for (String extension : type.extensions) {\n                if (extension.equals(ext))\n                    return type;\n            }\n        }\n\n        return null;\n    }\n\n    private final String[] extensions;\n\n    NBTFileType(String... extensions) {\n        this.extensions = extensions;\n    }\n\n    public abstract Tag read(Path file) throws IOException;\n\n    public NBTTreeView.Item readAsTree(Path file) throws IOException {\n        NBTTreeView.Item root = NBTTreeView.buildTree(read(file));\n        root.setName(file.getFileName().toString());\n        return root;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/nbt/NBTTagType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.nbt;\n\nimport com.github.steveice10.opennbt.tag.builtin.Tag;\n\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\n\n/**\n * @author Glavo\n */\npublic enum NBTTagType {\n    BYTE, SHORT, INT, LONG, FLOAT, DOUBLE,\n    BYTE_ARRAY, INT_ARRAY, LONG_ARRAY,\n    STRING,\n    LIST, COMPOUND;\n\n    private static final Map<String, NBTTagType> lookupTable = new HashMap<>();\n\n    static {\n        for (NBTTagType type : values()) {\n            lookupTable.put(type.getTagClassName(), type);\n        }\n    }\n\n    public static NBTTagType typeOf(Tag tag) {\n        NBTTagType type = lookupTable.get(tag.getClass().getSimpleName());\n        if (type == null) {\n            throw new IllegalArgumentException(\"Unknown tag: \" + type);\n        }\n        return type;\n    }\n\n    private final String iconUrl;\n    private final String tagClassName;\n\n    NBTTagType() {\n        String tagName;\n        String className;\n\n        int idx = name().indexOf('_');\n        if (idx < 0) {\n            tagName = name().charAt(0) + name().substring(1).toLowerCase(Locale.ROOT);\n            className = tagName + \"Tag\";\n        } else {\n            tagName = name().charAt(0) + name().substring(1, idx + 1).toLowerCase(Locale.ROOT)\n                    + name().charAt(idx + 1) + name().substring(idx + 2).toLowerCase(Locale.ROOT);\n            className = tagName.substring(0, idx) + tagName.substring(idx + 1) + \"Tag\";\n        }\n\n        this.iconUrl = \"/assets/img/nbt/TAG_\" + tagName + \".png\";\n        this.tagClassName = className;\n    }\n\n    public String getIconUrl() {\n        return iconUrl;\n    }\n\n    public String getTagClassName() {\n        return tagClassName;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/nbt/NBTTreeView.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.nbt;\n\nimport com.github.steveice10.opennbt.tag.builtin.CompoundTag;\nimport com.github.steveice10.opennbt.tag.builtin.ListTag;\nimport com.github.steveice10.opennbt.tag.builtin.Tag;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.control.skin.TreeViewSkin;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.util.Callback;\nimport org.jackhuang.hmcl.ui.FXUtils;\n\nimport java.lang.reflect.Array;\nimport java.util.EnumMap;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\n/**\n * @author Glavo\n */\npublic final class NBTTreeView extends TreeView<Tag> {\n\n    public NBTTreeView(NBTTreeView.Item tree) {\n        this.setRoot(tree);\n        if (tree != null) tree.setExpanded(true);\n        this.setCellFactory(cellFactory());\n    }\n\n    @Override\n    protected javafx.scene.control.Skin<?> createDefaultSkin() {\n        return new TreeViewSkin<Tag>(this) {\n            {\n                FXUtils.smoothScrolling(getVirtualFlow());\n            }\n        };\n    }\n\n    private static Callback<TreeView<Tag>, TreeCell<Tag>> cellFactory() {\n        EnumMap<NBTTagType, Image> icons = new EnumMap<>(NBTTagType.class);\n\n        return view -> new TreeCell<>() {\n            private void setTagText(String text) {\n                String name = ((Item) getTreeItem()).getName();\n\n                if (name == null) {\n                    setText(text);\n                } else if (text == null) {\n                    setText(name);\n                } else {\n                    setText(name + \": \" + text);\n                }\n            }\n\n            private void setTagText(int nEntries) {\n                setTagText(i18n(\"nbt.entries\", nEntries));\n            }\n\n            @Override\n            public void updateItem(Tag item, boolean empty) {\n                super.updateItem(item, empty);\n\n                ImageView imageView = (ImageView) this.getGraphic();\n                if (imageView == null) {\n                    imageView = new ImageView();\n                    this.setGraphic(imageView);\n                }\n\n                if (item == null) {\n                    imageView.setImage(null);\n                    setText(null);\n                    return;\n                }\n\n                NBTTagType tagType = NBTTagType.typeOf(item);\n                imageView.setImage(icons.computeIfAbsent(tagType, type -> new Image(type.getIconUrl())));\n                imageView.setFitHeight(16);\n                imageView.setFitWidth(16);\n\n                if (((Item) getTreeItem()).getText() != null) {\n                    setText(((Item) getTreeItem()).getText());\n                } else {\n                    switch (tagType) {\n                        case BYTE:\n                        case SHORT:\n                        case INT:\n                        case LONG:\n                        case FLOAT:\n                        case DOUBLE:\n                        case STRING:\n                            setTagText(item.getValue().toString());\n                            break;\n                        case BYTE_ARRAY:\n                        case INT_ARRAY:\n                        case LONG_ARRAY:\n                            setTagText(Array.getLength(item.getValue()));\n                            break;\n                        case LIST:\n                            setTagText(((ListTag) item).size());\n                            break;\n                        case COMPOUND:\n                            setTagText(((CompoundTag) item).size());\n                            break;\n                        default:\n                            setTagText(null);\n                    }\n                }\n            }\n        };\n    }\n\n    public static Item buildTree(Tag tag) {\n        Item item = new Item(tag);\n\n        if (tag instanceof CompoundTag) {\n            for (Tag subTag : ((CompoundTag) tag)) {\n                item.getChildren().add(buildTree(subTag));\n            }\n        } else if (tag instanceof ListTag) {\n            int idx = 0;\n            for (Tag subTag : ((ListTag) tag)) {\n                Item subTree = buildTree(subTag);\n                subTree.setName(String.valueOf(idx++));\n                item.getChildren().add(subTree);\n            }\n        }\n        FXUtils.onChangeAndOperate(item.expandedProperty(), expanded -> {\n            if (expanded && item.getChildren().size() == 1) item.getChildren().get(0).setExpanded(true);\n        });\n\n        return item;\n    }\n\n    public CompoundTag getRootTag() {\n        return ((CompoundTag) getRoot().getValue());\n    }\n\n    public static class Item extends TreeItem<Tag> {\n\n        private String text;\n        private String name;\n\n        public Item() {\n        }\n\n        public Item(Tag value) {\n            super(value);\n        }\n\n        public String getText() {\n            return text;\n        }\n\n        public void setText(String text) {\n            this.text = text;\n        }\n\n        public void setName(String name) {\n            this.name = name;\n        }\n\n        public String getName() {\n            return name == null ? getValue().getName() : name;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.profile;\n\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.scene.control.RadioButton;\nimport javafx.scene.control.Skin;\n\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\n\npublic class ProfileListItem extends RadioButton {\n    private final Profile profile;\n    private final StringProperty title = new SimpleStringProperty();\n    private final StringProperty subtitle = new SimpleStringProperty();\n\n    public ProfileListItem(Profile profile) {\n        this.profile = profile;\n        getStyleClass().setAll(\"profile-list-item\", \"navigation-drawer-item\");\n        setUserData(profile);\n\n        title.set(Profiles.getProfileDisplayName(profile));\n        subtitle.set(profile.getGameDir().toString());\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new ProfileListItemSkin(this);\n    }\n\n    public void remove() {\n        Profiles.getProfiles().remove(profile);\n    }\n\n    public Profile getProfile() {\n        return profile;\n    }\n\n    public String getTitle() {\n        return title.get();\n    }\n\n    public void setTitle(String title) {\n        this.title.set(title);\n    }\n\n    public StringProperty titleProperty() {\n        return title;\n    }\n\n    public String getSubtitle() {\n        return subtitle.get();\n    }\n\n    public void setSubtitle(String subtitle) {\n        this.subtitle.set(subtitle);\n    }\n\n    public StringProperty subtitleProperty() {\n        return subtitle;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.profile;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.RipplerContainer;\nimport org.jackhuang.hmcl.ui.construct.TwoLineListItem;\n\npublic class ProfileListItemSkin extends SkinBase<ProfileListItem> {\n    private static final PseudoClass SELECTED = PseudoClass.getPseudoClass(\"selected\");\n\n    public ProfileListItemSkin(ProfileListItem skinnable) {\n        super(skinnable);\n\n        BorderPane root = new BorderPane();\n        root.setPickOnBounds(false);\n        RipplerContainer container = new RipplerContainer(root);\n\n        FXUtils.onChangeAndOperate(skinnable.selectedProperty(), active -> {\n            skinnable.pseudoClassStateChanged(SELECTED, active);\n        });\n\n        FXUtils.onClicked(getSkinnable(), () -> getSkinnable().setSelected(true));\n\n        Node left = SVG.FOLDER.createIcon(20);\n        left.setMouseTransparent(true);\n        BorderPane.setMargin(left, new Insets(0, 6, 0, 6));\n        root.setLeft(left);\n        BorderPane.setAlignment(left, Pos.CENTER_LEFT);\n\n        TwoLineListItem item = new TwoLineListItem();\n        item.setPickOnBounds(false);\n        BorderPane.setAlignment(item, Pos.CENTER);\n        root.setCenter(item);\n\n        HBox right = new HBox();\n        right.setAlignment(Pos.CENTER_RIGHT);\n\n        JFXButton btnRemove = FXUtils.newToggleButton4(SVG.CLOSE, 14);\n        btnRemove.setOnAction(e -> skinnable.remove());\n        BorderPane.setAlignment(btnRemove, Pos.CENTER);\n        right.getChildren().add(btnRemove);\n        root.setRight(right);\n\n        item.titleProperty().bind(skinnable.titleProperty());\n        item.subtitleProperty().bind(skinnable.subtitleProperty());\n\n        getChildren().setAll(container);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfilePage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.profile;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXTextField;\nimport com.jfoenix.validation.RequiredFieldValidator;\nimport com.jfoenix.validation.base.ValidatorBase;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.*;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Path;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class ProfilePage extends BorderPane implements DecoratorPage {\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();\n    private final StringProperty location;\n    private final Profile profile;\n    private final JFXTextField txtProfileName;\n    private final LineFileChooserButton gameDir;\n    private final LineToggleButton toggleUseRelativePath;\n\n    /**\n     * @param profile null if creating a new profile.\n     */\n    public ProfilePage(Profile profile) {\n        getStyleClass().add(\"gray-background\");\n\n        this.profile = profile;\n        String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse(\"\");\n\n        state.set(State.fromTitle(profile == null ? i18n(\"profile.new\") : i18n(\"profile\") + \" - \" + profileDisplayName));\n        location = new SimpleStringProperty(this, \"location\",\n                Optional.ofNullable(profile).map(Profile::getGameDir).map(FileUtils::getAbsolutePath).orElse(\".minecraft\"));\n\n        ScrollPane scroll = new ScrollPane();\n        this.setCenter(scroll);\n        scroll.setFitToHeight(true);\n        scroll.setFitToWidth(true);\n        {\n            VBox rootPane = new VBox();\n            rootPane.setStyle(\"-fx-padding: 20;\");\n            {\n                ComponentList componentList = new ComponentList();\n                {\n                    BorderPane profileNamePane = new BorderPane();\n                    {\n                        Label label = new Label(i18n(\"profile.name\"));\n                        profileNamePane.setLeft(label);\n                        BorderPane.setAlignment(label, Pos.CENTER_LEFT);\n\n                        txtProfileName = new JFXTextField();\n                        profileNamePane.setRight(txtProfileName);\n                        RequiredFieldValidator validator = new RequiredFieldValidator();\n                        validator.setMessage(i18n(\"input.not_empty\"));\n                        txtProfileName.getValidators().add(validator);\n                        BorderPane.setMargin(txtProfileName, new Insets(8, 0, 8, 0));\n\n                        txtProfileName.setText(profileDisplayName);\n                        txtProfileName.getValidators().add(new ValidatorBase() {\n                            {\n                                setMessage(i18n(\"profile.already_exists\"));\n                            }\n\n                            @Override\n                            protected void eval() {\n                                JFXTextField control = (JFXTextField) this.getSrcControl();\n                                hasErrors.set(Profiles.getProfiles().stream().anyMatch(profile -> profile.getName().equals(control.getText())));\n                            }\n                        });\n                    }\n\n                    gameDir = new LineFileChooserButton();\n                    gameDir.setTitle(i18n(\"profile.instance_directory\"));\n                    gameDir.setFileChooserTitle(i18n(\"profile.instance_directory.choose\"));\n                    gameDir.setType(LineFileChooserButton.Type.OPEN_DIRECTORY);\n                    gameDir.locationProperty().bindBidirectional(location);\n\n                    toggleUseRelativePath = new LineToggleButton();\n                    toggleUseRelativePath.setTitle(i18n(\"profile.use_relative_path\"));\n\n                    gameDir.convertToRelativePathProperty().bind(toggleUseRelativePath.selectedProperty());\n                    if (profile != null) {\n                        toggleUseRelativePath.setSelected(profile.isUseRelativePath());\n                    }\n\n                    componentList.getContent().setAll(profileNamePane, gameDir, toggleUseRelativePath);\n                }\n\n                rootPane.getChildren().setAll(componentList);\n            }\n\n            scroll.setContent(rootPane);\n        }\n\n        BorderPane savePane = new BorderPane();\n        this.setBottom(savePane);\n        savePane.setPickOnBounds(false);\n        savePane.setStyle(\"-fx-padding: 20;\");\n        StackPane.setAlignment(savePane, Pos.BOTTOM_RIGHT);\n        {\n            JFXButton saveButton = FXUtils.newRaisedButton(i18n(\"button.save\"));\n            savePane.setRight(saveButton);\n            BorderPane.setAlignment(savePane, Pos.BOTTOM_RIGHT);\n            StackPane.setAlignment(saveButton, Pos.BOTTOM_RIGHT);\n            saveButton.setPrefSize(100, 40);\n            saveButton.setOnAction(e -> onSave());\n            saveButton.disableProperty().bind(Bindings.createBooleanBinding(\n                    () -> !txtProfileName.validate() || StringUtils.isBlank(getLocation()),\n                    txtProfileName.textProperty(), location));\n        }\n\n        ChangeListener<String> locationChangeListener = (observable, oldValue, newValue) -> {\n            Path newPath;\n            try {\n                newPath = FileUtils.toAbsolute(Path.of(newValue));\n            } catch (InvalidPathException ignored) {\n                return;\n            }\n\n            if (!\".minecraft\".equals(FileUtils.getName(newPath)))\n                return;\n\n            Path parent = newPath.getParent();\n            if (parent == null)\n                return;\n\n            String suggestedName = FileUtils.getName(parent);\n            if (!suggestedName.isBlank()) {\n                txtProfileName.setText(suggestedName);\n            }\n        };\n        locationProperty().addListener(locationChangeListener);\n\n        txtProfileName.textProperty().addListener(new ChangeListener<>() {\n            @Override\n            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {\n                if (txtProfileName.isFocused()) {\n                    txtProfileName.textProperty().removeListener(this);\n                    locationProperty().removeListener(locationChangeListener);\n                }\n            }\n        });\n    }\n\n    private void onSave() {\n        if (profile != null) {\n            profile.setName(txtProfileName.getText());\n            profile.setUseRelativePath(toggleUseRelativePath.isSelected());\n            if (StringUtils.isNotBlank(getLocation())) {\n                profile.setGameDir(Path.of(getLocation()));\n            }\n        } else {\n            if (StringUtils.isBlank(getLocation())) {\n                gameDir.fire();\n            }\n            Profile newProfile = new Profile(txtProfileName.getText(), Path.of(getLocation()));\n            newProfile.setUseRelativePath(toggleUseRelativePath.isSelected());\n            Profiles.getProfiles().add(newProfile);\n        }\n\n        fireEvent(new PageCloseEvent());\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n\n    public String getLocation() {\n        return location.get();\n    }\n\n    public StringProperty locationProperty() {\n        return location;\n    }\n\n    public void setLocation(String location) {\n        this.location.set(location);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/FunctionHelper.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.event.Event;\nimport javafx.event.EventHandler;\n\nimport java.util.Arrays;\nimport java.util.function.BiConsumer;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\npublic final class FunctionHelper {\n\n    private FunctionHelper() {\n    }\n\n    @SafeVarargs\n    public static <T> void always(Consumer<T> consumer, T... ts) {\n        Arrays.asList(ts).forEach(consumer);\n    }\n\n    @SafeVarargs\n    public static <A, B> void alwaysA(BiConsumer<A, B> consumer, A a, B... bs) {\n        Arrays.asList(bs).forEach(b -> consumer.accept(a, b));\n    }\n\n    @SafeVarargs\n    public static <A, B> void alwaysB(BiConsumer<A, B> consumer, B b, A... as) {\n        Arrays.asList(as).forEach(a -> consumer.accept(a, b));\n    }\n\n    public static <A, B> BiConsumer<B, A> exchange(BiConsumer<A, B> consumer) {\n        return (b, a) -> consumer.accept(a, b);\n    }\n\n    @SafeVarargs\n    public static <T> Consumer<T> link(Consumer<T>... consumers) {\n        return t -> {\n            for (Consumer<T> consumer : consumers)\n                consumer.accept(t);\n        };\n    }\n\n    @SafeVarargs\n    public static <T extends Event> EventHandler<T> link(EventHandler<T>... handlers) {\n        return t -> {\n            for (EventHandler<T> handler : handlers)\n                handler.handle(t);\n        };\n    }\n\n    public static <A, B> Consumer<A> link1(Function<A, B> function, Consumer<B> consumer) {\n        return a -> consumer.accept(function.apply(a));\n    }\n\n    public static <A, B, C> BiConsumer<A, C> link2(Function<A, B> function, BiConsumer<B, C> consumer) {\n        return (a, c) -> consumer.accept(function.apply(a), c);\n    }\n\n    public static <A, B> Consumer<B> link2(Supplier<A> supplier, BiConsumer<A, B> consumer) {\n        return b -> consumer.accept(supplier.get(), b);\n    }\n\n    public static <A, B> Supplier<B> link1(Supplier<A> supplier, Function<A, B> function) {\n        return () -> function.apply(supplier.get());\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinAnimation.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class SkinAnimation {\n\n    protected int weight, left;\n    protected List<SkinTransition> transitions;\n\n    public SkinAnimation() {\n        this.transitions = new ArrayList<>();\n    }\n\n    public SkinAnimation(int weight, SkinTransition... transitions) {\n        this.weight = weight;\n        this.transitions = Arrays.asList(transitions);\n        init();\n    }\n\n    protected void init() {\n        transitions.forEach(t -> {\n            EventHandler<ActionEvent> oldHandler = t.getOnFinished();\n            EventHandler<ActionEvent> newHandler = e -> left--;\n            newHandler = oldHandler == null ? newHandler : FunctionHelper.link(oldHandler, newHandler);\n            t.setOnFinished(newHandler);\n        });\n    }\n\n    public int getWeight() {\n        return weight;\n    }\n\n    public boolean isPlaying() {\n        return left > 0;\n    }\n\n    public void play() {\n        transitions.forEach(SkinTransition::play);\n        left = transitions.size();\n    }\n\n    public void playFromStart() {\n        transitions.forEach(SkinTransition::playFromStart);\n        left = transitions.size();\n    }\n\n    public void stop() {\n        transitions.forEach(SkinTransition::stop);\n        left = 0;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinAnimationPlayer.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.animation.AnimationTimer;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Random;\n\npublic class SkinAnimationPlayer {\n\n    protected final Random random = new Random();\n    protected List<SkinAnimation> animations = new ArrayList<>();\n    protected SkinAnimation playing;\n    protected boolean running;\n    protected int weightedSum = 0;\n    protected long lastPlayTime = -1L, interval = 10_000_000_000L;\n    protected AnimationTimer animationTimer = new AnimationTimer() {\n        @Override\n        public void handle(long now) {\n            if (playing == null || !playing.isPlaying() && now - lastPlayTime > interval) {\n                int nextAni = random.nextInt(weightedSum);\n                SkinAnimation tmp = null;\n                for (SkinAnimation animation : animations) {\n                    nextAni -= animation.getWeight();\n                    tmp = animation;\n                    if (nextAni <= 0)\n                        break;\n                }\n                playing = tmp;\n                if (playing == null && animations.size() > 0)\n                    playing = animations.get(animations.size() - 1);\n                if (playing != null) {\n                    playing.playFromStart();\n                    lastPlayTime = now;\n                }\n            }\n        }\n    };\n\n    public int getWeightedSum() {\n        return weightedSum;\n    }\n\n    public void setInterval(long interval) {\n        this.interval = interval;\n        if (interval < 1)\n            animationTimer.stop();\n        else\n            start();\n    }\n\n    public long getInterval() {\n        return interval;\n    }\n\n    public long getLastPlayTime() {\n        return lastPlayTime;\n    }\n\n    public boolean isRunning() {\n        return running;\n    }\n\n    public boolean isPlaying() {\n        return playing != null;\n    }\n\n    public SkinAnimation getPlaying() {\n        return playing;\n    }\n\n    public void addSkinAnimation(SkinAnimation... animations) {\n        this.animations.addAll(Arrays.asList(animations));\n        this.weightedSum = this.animations.stream().mapToInt(SkinAnimation::getWeight).sum();\n        start();\n    }\n\n    public void start() {\n        if (!running && weightedSum > 0 && interval > 0) {\n            animationTimer.start();\n            running = true;\n        }\n    }\n\n    public void stop() {\n        if (running)\n            animationTimer.stop();\n        if (playing != null)\n            playing.stop();\n        running = false;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinCanvas.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.scene.*;\nimport javafx.scene.image.Image;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.input.ScrollEvent;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Material;\nimport javafx.scene.paint.PhongMaterial;\nimport javafx.scene.shape.Shape3D;\nimport javafx.scene.transform.Rotate;\nimport javafx.scene.transform.Scale;\nimport javafx.scene.transform.Translate;\n\nimport org.jetbrains.annotations.Nullable;\n\npublic class SkinCanvas extends Group {\n\n    public static final SkinCube ALEX_LARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, true);\n    public static final SkinCube ALEX_RARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, true);\n\n    public static final SkinCube STEVEN_LARM = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, false);\n    public static final SkinCube STEVEN_RARM = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, false);\n\n    protected Image srcSkin, skin, srcCape, cape;\n    protected boolean isSlim;\n\n    protected double preW, preH;\n    protected boolean msaa;\n\n    protected SubScene subScene;\n    protected Group root = new Group();\n\n    public final SkinMultipleCubes headOuter = new SkinMultipleCubes(8, 8, 8, 32F / 64F, 0F, 1.125, 0.2);\n    public final SkinMultipleCubes bodyOuter = new SkinMultipleCubes(8, 12, 4, 16F / 64F, 32F / 64F, 1, 0.2);\n    public final SkinMultipleCubes larmOuter = new SkinMultipleCubes(4, 12, 4, 48F / 64F, 48F / 64F, 1.0625, 0.2);\n    public final SkinMultipleCubes rarmOuter = new SkinMultipleCubes(4, 12, 4, 40F / 64F, 32F / 64F, 1.0625, 0.2);\n    public final SkinMultipleCubes llegOuter = new SkinMultipleCubes(4, 12, 4, 0F / 64F, 48F / 64F, 1.0625, 0.2);\n    public final SkinMultipleCubes rlegOuter = new SkinMultipleCubes(4, 12, 4, 0F / 64F, 32F / 64F, 1.0625, 0.2);\n\n    public final SkinCube headInside = new SkinCube(8, 8, 8, 32F / 64F, 16F / 64F, 0F, 0F, 0F, false);\n    public final SkinCube bodyInside = new SkinCube(8, 12, 4, 24F / 64F, 16F / 64F, 16F / 64F, 16F / 64F, 0.03F, false);\n    public final SkinCube larmInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, false);\n    public final SkinCube rarmInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, false);\n    public final SkinCube llegInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 16F / 64F, 48F / 64F, 0F, false);\n    public final SkinCube rlegInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 0F, 16F / 64F, 0F, false);\n\n    public final SkinCube capeCube = new SkinCube(10, 16, 1, 22F / 64F, 17F / 32F, 0F, 0F, 0F, false);\n\n    public final SkinGroup head = new SkinGroup(\n            new Rotate(0, 0, headInside.getHeight() / 2, 0, Rotate.X_AXIS),\n            new Rotate(0, Rotate.Y_AXIS),\n            new Rotate(0, 0, headInside.getHeight() / 2, 0, Rotate.Z_AXIS),\n            headOuter, headInside\n    );\n    public final SkinGroup body = new SkinGroup(\n            new Rotate(0, Rotate.X_AXIS),\n            new Rotate(0, Rotate.Y_AXIS),\n            new Rotate(0, Rotate.Z_AXIS),\n            bodyOuter, bodyInside\n    );\n    public final SkinGroup larm = new SkinGroup(\n            new Rotate(0, 0, -larmInside.getHeight() / 2, 0, Rotate.X_AXIS),\n            new Rotate(0, Rotate.Y_AXIS),\n            new Rotate(0, +larmInside.getWidth() / 2, -larmInside.getHeight() / 2, 0, Rotate.Z_AXIS),\n            larmOuter, larmInside\n    );\n    public final SkinGroup rarm = new SkinGroup(\n            new Rotate(0, 0, -rarmInside.getHeight() / 2, 0, Rotate.X_AXIS),\n            new Rotate(0, Rotate.Y_AXIS),\n            new Rotate(0, -rarmInside.getWidth() / 2, -rarmInside.getHeight() / 2, 0, Rotate.Z_AXIS),\n            rarmOuter, rarmInside\n    );\n    public final SkinGroup lleg = new SkinGroup(\n            new Rotate(0, 0, -llegInside.getHeight() / 2, 0, Rotate.X_AXIS),\n            new Rotate(0, Rotate.Y_AXIS),\n            new Rotate(0, 0, -llegInside.getHeight() / 2, 0, Rotate.Z_AXIS),\n            llegOuter, llegInside\n    );\n    public final SkinGroup rleg = new SkinGroup(\n            new Rotate(0, 0, -rlegInside.getHeight() / 2, 0, Rotate.X_AXIS),\n            new Rotate(0, Rotate.Y_AXIS),\n            new Rotate(0, 0, -rlegInside.getHeight() / 2, 0, Rotate.Z_AXIS),\n            rlegOuter, rlegInside\n    );\n\n    public final SkinGroup capeGroup = new SkinGroup(\n            new Rotate(0, 0, -capeCube.getHeight() / 2, 0, Rotate.X_AXIS),\n            new Rotate(0, Rotate.Y_AXIS),\n            new Rotate(0, Rotate.Z_AXIS),\n            capeCube\n    );\n\n    protected PerspectiveCamera camera = new PerspectiveCamera(true);\n\n    protected Rotate xRotate = new Rotate(0, Rotate.X_AXIS);\n    protected Rotate yRotate = new Rotate(180, Rotate.Y_AXIS);\n    protected Rotate zRotate = new Rotate(0, Rotate.Z_AXIS);\n    protected Translate translate = new Translate(0, 0, -80);\n    protected Scale scale = new Scale(1, 1);\n\n    protected SkinAnimationPlayer animationPlayer = new SkinAnimationPlayer();\n\n    public SkinAnimationPlayer getAnimationPlayer() {\n        return animationPlayer;\n    }\n\n    public Image getSrcSkin() {\n        return srcSkin;\n    }\n\n    public Image getSkin() {\n        return skin;\n    }\n\n    public void updateSkin(Image skin, boolean isSlim, final @Nullable Image cape) {\n        if (SkinHelper.isNoRequest(skin) && SkinHelper.isSkin(skin)) {\n            this.srcSkin = skin;\n            this.skin = SkinHelper.x32Tox64(skin);\n            this.srcCape = cape;\n            this.cape = cape == null ? null : cape.getWidth() < 256 ? SkinHelper.enlarge(cape, 4, 8) : cape;\n            int multiple = Math.max((int) (1024 / skin.getWidth()), 1);\n            if (multiple > 1)\n                this.skin = SkinHelper.enlarge(this.skin, multiple, multiple);\n            updateSkinModel(isSlim, cape != null);\n            bindMaterial(root);\n        }\n    }\n\n    protected void updateSkinModel(boolean isSlim, boolean hasCape) {\n        this.isSlim = isSlim;\n        FunctionHelper.alwaysB(SkinMultipleCubes::setWidth, isSlim ? 3 : 4, larmOuter, rarmOuter);\n        FunctionHelper.alwaysB(SkinCube::setWidth, isSlim ? 3D : 4D, larmInside, rarmInside);\n\n        FunctionHelper.alwaysB(Node::setTranslateX, -(bodyInside.getWidth() + larmInside.getWidth()) / 2, larm);\n        FunctionHelper.alwaysB(Node::setTranslateX, +(bodyInside.getWidth() + rarmInside.getWidth()) / 2, rarm);\n        if (isSlim) {\n            larmInside.setModel(ALEX_LARM.getModel());\n            rarmInside.setModel(ALEX_RARM.getModel());\n        } else {\n            larmInside.setModel(STEVEN_LARM.getModel());\n            rarmInside.setModel(STEVEN_RARM.getModel());\n        }\n\n        larm.getZRotate().setPivotX(-larmInside.getWidth() / 2);\n        rarm.getZRotate().setPivotX(+rarmInside.getWidth() / 2);\n\n        capeGroup.setVisible(hasCape);\n    }\n\n    public SkinCanvas(Image skin, double preW, double preH, boolean msaa) {\n        this.skin = skin;\n        this.preW = preW;\n        this.preH = preH;\n        this.msaa = msaa;\n\n        init();\n    }\n\n    protected Material createMaterial(final Image image) {\n        PhongMaterial material = new PhongMaterial();\n        material.setDiffuseMap(image);\n        return material;\n    }\n\n    protected void bindMaterial(Group group) {\n        Material material = createMaterial(skin);\n        for (Node node : group.getChildren())\n            if (node instanceof Shape3D)\n                ((Shape3D) node).setMaterial(node == capeCube ? createMaterial(cape) : material);\n            else if (node instanceof SkinMultipleCubes)\n                ((SkinMultipleCubes) node).updateSkin(skin);\n            else if (node instanceof Group)\n                bindMaterial((Group) node);\n    }\n\n    protected Group createPlayerModel() {\n        head.setTranslateY(-(bodyInside.getHeight() + headInside.getHeight()) / 2);\n\n        larm.setTranslateX(-(bodyInside.getWidth() + larmInside.getWidth()) / 2);\n        rarm.setTranslateX(+(bodyInside.getWidth() + rarmInside.getWidth()) / 2);\n\n        lleg.setTranslateX(-(bodyInside.getWidth() - llegInside.getWidth()) / 2);\n        rleg.setTranslateX(+(bodyInside.getWidth() - rlegInside.getWidth()) / 2);\n\n        lleg.setTranslateY(+(bodyInside.getHeight() + llegInside.getHeight()) / 2);\n        rleg.setTranslateY(+(bodyInside.getHeight() + rlegInside.getHeight()) / 2);\n\n        capeGroup.setTranslateY(+(capeCube.getHeight() - bodyOuter.getHeight()) / 2);\n        capeGroup.setTranslateZ(-(bodyInside.getDepth() + bodyOuter.getDepth()) / 2);\n\n        capeGroup.getTransforms().addAll(new Rotate(180, Rotate.Y_AXIS), new Rotate(10, Rotate.X_AXIS));\n\n        root.getTransforms().addAll(xRotate);\n\n        root.getChildren().addAll(\n                head,\n                body,\n                larm,\n                rarm,\n                lleg,\n                rleg,\n                capeGroup\n        );\n        updateSkin(skin, false, null);\n\n        return root;\n    }\n\n    protected SubScene createSubScene() {\n        Group group = new Group();\n\n        AmbientLight light = new AmbientLight(Color.WHITE);\n        group.getChildren().add(light);\n\n        group.getChildren().add(createPlayerModel());\n        group.getTransforms().add(zRotate);\n\n        camera.getTransforms().addAll(yRotate, translate, scale);\n\n        subScene = new SubScene(group, preW, preH, true,\n                msaa ? SceneAntialiasing.BALANCED : SceneAntialiasing.DISABLED);\n        subScene.setCamera(camera);\n\n        return subScene;\n    }\n\n    protected void init() {\n        getChildren().add(createSubScene());\n    }\n\n    private double lastX, lastY;\n\n    public void enableRotation(double sensitivity) {\n        addEventHandler(MouseEvent.MOUSE_PRESSED, e -> {\n            lastX = -1;\n            lastY = -1;\n        });\n        addEventHandler(MouseEvent.MOUSE_DRAGGED, e -> {\n            if (!(lastX == -1 || lastY == -1)) {\n                if (e.isAltDown() || e.isControlDown() || e.isShiftDown()) {\n                    if (e.isShiftDown())\n                        zRotate.setAngle(zRotate.getAngle() - (e.getSceneY() - lastY) * sensitivity);\n                    if (e.isAltDown())\n                        yRotate.setAngle(yRotate.getAngle() + (e.getSceneX() - lastX) * sensitivity);\n                    if (e.isControlDown())\n                        xRotate.setAngle(xRotate.getAngle() + (e.getSceneY() - lastY) * sensitivity);\n                } else {\n                    double yaw = yRotate.getAngle() + (e.getSceneX() - lastX) * sensitivity;\n                    yaw %= 360;\n                    if (yaw < 0)\n                        yaw += 360;\n\n                    int flagX = yaw < 90 || yaw > 270 ? 1 : -1;\n                    int flagZ = yaw < 180 ? -1 : 1;\n                    double kx = Math.abs(90 - yaw % 180) / 90 * flagX, kz = Math.abs(90 - (yaw + 90) % 180) / 90 * flagZ;\n\n                    xRotate.setAngle(xRotate.getAngle() + (e.getSceneY() - lastY) * sensitivity * kx);\n                    yRotate.setAngle(yaw);\n                    zRotate.setAngle(zRotate.getAngle() + (e.getSceneY() - lastY) * sensitivity * kz);\n                }\n            }\n            lastX = e.getSceneX();\n            lastY = e.getSceneY();\n        });\n        addEventHandler(ScrollEvent.SCROLL, e -> {\n            double delta = (e.getDeltaY() > 0 ? 1 : e.getDeltaY() == 0 ? 0 : -1) / 10D * sensitivity;\n            scale.setX(Math.min(Math.max(scale.getX() - delta, 0.1), 10));\n            scale.setY(Math.min(Math.max(scale.getY() - delta, 0.1), 10));\n        });\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinCube.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.scene.shape.Mesh;\nimport javafx.scene.shape.MeshView;\nimport javafx.scene.shape.TriangleMesh;\n\npublic class SkinCube extends MeshView {\n\n    public static class Model extends TriangleMesh {\n\n        public Model(float width, float height, float depth, float scaleX, float scaleY, float startX, float startY, boolean isSlim) {\n            getPoints().addAll(createPoints(width, height, depth));\n            getTexCoords().addAll(createTexCoords(width, height, depth, scaleX, scaleY, startX, startY, isSlim));\n            getFaces().addAll(createFaces());\n        }\n\n        public static float[] createPoints(float width, float height, float depth) {\n            width /= 2F;\n            height /= 2F;\n            depth /= 2F;\n            return new float[]{\n                    -width, -height, +depth, // P0\n                    +width, -height, +depth, // P1\n                    -width, +height, +depth, // P2\n                    +width, +height, +depth, // P3\n                    -width, -height, -depth, // P4\n                    +width, -height, -depth, // P5\n                    -width, +height, -depth, // P6\n                    +width, +height, -depth  // P7\n            };\n        }\n\n        public static float[] createTexCoords(float width, float height, float depth, float scaleX, float scaleY,\n                float startX, float startY, boolean isSlim) {\n            float x = (width + depth) * 2, y = height + depth, half_width = width / x * scaleX, half_depth = depth / x * scaleX,\n                    top_x = depth / x * scaleX + startX, top_y = startY, arm4 = isSlim ? half_depth : half_width,\n                    bottom_x = startX, middle_y = depth / y * scaleY + top_y, bottom_y = scaleY + top_y;\n            return new float[]{\n                    top_x, top_y,                                    // T0  ---\n                    top_x + half_width, top_y,                       // T1   |\n                    top_x + half_width * 2, top_y,                   // T2  ---\n                    bottom_x, middle_y,                              // T3  ---\n                    bottom_x + half_depth, middle_y,                 // T4   |\n                    bottom_x + half_depth + half_width, middle_y,    // T5   |\n                    bottom_x + scaleX - arm4, middle_y,              // T6   |\n                    bottom_x + scaleX, middle_y,                     // T7  ---\n                    bottom_x, bottom_y,                              // T8  ---\n                    bottom_x + half_depth, bottom_y,                 // T9   |\n                    bottom_x + half_depth + half_width, bottom_y,    // T10  |\n                    bottom_x + scaleX - arm4, bottom_y,              // T11  |\n                    bottom_x + scaleX, bottom_y                      // T12 ---\n            };\n        }\n\n        public static int[] createFaces() {\n            int[] faces = {\n                    // TOP\n                    5, 0, 4, 1, 0, 5,    //P5,T0, P4,T1, P0,T5\n                    5, 0, 0, 5, 1, 4,    //P5,T0, P0,T5, P1,T4\n                    // RIGHT\n                    0, 5, 4, 6, 6, 11,   //P0,T4 ,P4,T3, P6,T8\n                    0, 5, 6, 11, 2, 10,  //P0,T4 ,P6,T8, P2,T9\n                    // FRONT\n                    1, 4, 0, 5, 2, 10,   //P1,T5, P0,T4, P2,T9\n                    1, 4, 2, 10, 3, 9,   //P1,T5, P2,T9, P3,T10\n                    // LEFT\n                    5, 3, 1, 4, 3, 9,    //P5,T6, P1,T5, P3,T10\n                    5, 3, 3, 9, 7, 8,    //P5,T6, P3,T10,P7,T11\n                    // BACK\n                    4, 6, 5, 7, 7, 12,   //P4,T6, P5,T7, P7,T12\n                    4, 6, 7, 12, 6, 11,  //P4,T6, P7,T12,P6,T11\n                    // BOTTOM\n                    3, 5, 2, 6, 6, 2,    //P3,T2, P2,T1, P6,T5\n                    3, 5, 6, 2, 7, 1     //P3,T2, P6,T5, P7,T6\n            };\n\n            int[] copy = faces.clone();\n\n            for (int i = 0, mid = copy.length >> 1, j = copy.length - 1; i < mid; i++, j--) {\n                int tmp = copy[i];\n                copy[i] = copy[j];\n                copy[j] = tmp;\n            }\n\n            for (int i = 0; i < copy.length; i += 2) {\n                int tmp = copy[i];\n                copy[i] = copy[i + 1];\n                copy[i + 1] = tmp;\n            }\n\n            int[] result = new int[faces.length + copy.length];\n            System.arraycopy(faces, 0, result, 0, faces.length);\n            System.arraycopy(copy, 0, result, faces.length, copy.length);\n            return result;\n        }\n\n    }\n\n    private double width, height, depth;\n    private boolean isSlim;\n    private Mesh model;\n\n    public SkinCube(float width, float height, float depth, float scaleX, float scaleY, float startX, float startY, float enlarge, boolean isSlim) {\n        this.width = width;\n        this.height = height;\n        this.depth = depth;\n        this.isSlim = isSlim;\n        setMesh(model = new Model(width + enlarge, height + enlarge, depth + enlarge, scaleX, scaleY, startX, startY, isSlim));\n    }\n\n    public void setWidth(double width) {\n        this.width = width;\n    }\n\n    public double getWidth() {\n        return width;\n    }\n\n    public void setHeight(double height) {\n        this.height = height;\n    }\n\n    public double getHeight() {\n        return height;\n    }\n\n    public void setDepth(double depth) {\n        this.depth = depth;\n    }\n\n    public double getDepth() {\n        return depth;\n    }\n\n    public boolean isSlim() {\n        return isSlim;\n    }\n\n    public Mesh getModel() {\n        return model;\n    }\n\n    public void setModel(Mesh model) {\n        this.model = model;\n        setMesh(model);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinGroup.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.transform.Rotate;\n\npublic class SkinGroup extends Group {\n\n    protected Rotate xRotate, yRotate, zRotate;\n\n    public SkinGroup(Rotate xRotate, Rotate yRotate, Rotate zRotate, Node... nodes) {\n        this.xRotate = xRotate;\n        this.yRotate = yRotate;\n        this.zRotate = zRotate;\n        Group group = new Group();\n        group.getChildren().addAll(nodes);\n        getChildren().add(addRotate(group, xRotate, yRotate, zRotate));\n    }\n\n    protected Group addRotate(Group group, Rotate... rotates) {\n        for (Rotate rotate : rotates)\n            group = addRotate(group, rotate);\n        return group;\n    }\n\n    protected Group addRotate(Group group, Rotate rotate) {\n        Group newGroup = new Group();\n        group.getTransforms().add(rotate);\n        newGroup.getChildren().add(group);\n        return newGroup;\n    }\n\n    public Rotate getXRotate() {\n        return xRotate;\n    }\n\n    public Rotate getYRotate() {\n        return yRotate;\n    }\n\n    public Rotate getZRotate() {\n        return zRotate;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinHelper.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.scene.image.Image;\nimport javafx.scene.image.PixelReader;\nimport javafx.scene.image.PixelWriter;\nimport javafx.scene.image.WritableImage;\n\npublic final class SkinHelper {\n\n    private SkinHelper() {\n    }\n\n    public static class PixelCopier {\n\n        protected Image srcImage;\n        protected WritableImage newImage;\n\n        public PixelCopier(Image srcImage, WritableImage newImage) {\n            this.srcImage = srcImage;\n            this.newImage = newImage;\n        }\n\n        public void copy(int srcX, int srcY, int width, int height) {\n            copy(srcX, srcY, srcX, srcY, width, height);\n        }\n\n        public void copy(int srcX, int srcY, int toX, int toY, int width, int height) {\n            copy(srcX, srcY, toX, toY, width, height, false, false);\n        }\n\n        public void copy(int srcX, int srcY, int toX, int toY, int width, int height, boolean reversalX, boolean reversalY) {\n            PixelReader reader = srcImage.getPixelReader();\n            PixelWriter writer = newImage.getPixelWriter();\n            for (int x = 0; x < width; x++)\n                for (int y = 0; y < height; y++)\n                    writer.setArgb(toX + x, toY + y,\n                            reader.getArgb(srcX + (reversalX ? width - x - 1 : x), srcY + (reversalY ? height - y - 1 : y)));\n        }\n\n        public void copy(float srcX, float srcY, float toX, float toY, float width, float height) {\n            copy(srcX, srcY, toX, toY, width, height, false, false);\n        }\n\n        public void copy(float srcX, float srcY, float toX, float toY, float width, float height, boolean reversalX, boolean reversalY) {\n            PixelReader reader = srcImage.getPixelReader();\n            PixelWriter writer = newImage.getPixelWriter();\n            int srcScaleX = (int) srcImage.getWidth();\n            int srcScaleY = (int) srcImage.getHeight();\n            int newScaleX = (int) newImage.getWidth();\n            int newScaleY = (int) newImage.getHeight();\n            int srcWidth = (int) (width * srcScaleX);\n            int srcHeight = (int) (height * srcScaleY);\n\n            for (int x = 0; x < srcWidth; x++)\n                for (int y = 0; y < srcHeight; y++)\n                    writer.setArgb((int) (toX * newScaleX + x), (int) (toY * newScaleY + y),\n                            reader.getArgb((int) (srcX * srcScaleX + (reversalX ? srcWidth - x - 1 : x)),\n                                    (int) (srcY * srcScaleY + (reversalY ? srcHeight - y - 1 : y))));\n        }\n\n    }\n\n    public static boolean isNoRequest(Image image) {\n        return image.getRequestedWidth() == 0 && image.getRequestedHeight() == 0;\n    }\n\n    public static boolean isSkin(Image image) {\n        return image.getWidth() % 64 == 0 && image.getWidth() / 64 > 0 &&\n                (image.getHeight() == image.getWidth() / 2 || image.getHeight() == image.getWidth());\n    }\n\n    public static Image x32Tox64(Image srcSkin) {\n        if (srcSkin.getHeight() == srcSkin.getWidth())\n            return srcSkin;\n\n        WritableImage newSkin = new WritableImage((int) srcSkin.getWidth(), (int) srcSkin.getHeight() * 2);\n        PixelCopier copier = new PixelCopier(srcSkin, newSkin);\n        // HEAD & HAT\n        copier.copy(0 / 64F, 0 / 32F, 0 / 64F, 0 / 64F, 64 / 64F, 16 / 32F);\n        // LEFT-LEG\n        x32Tox64(copier, 0 / 64F, 16 / 32F, 16 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F);\n        // RIGHT-LEG\n        copier.copy(0 / 64F, 16 / 32F, 0 / 64F, 16 / 64F, 16 / 64F, 16 / 32F);\n        // BODY\n        copier.copy(16 / 64F, 16 / 32F, 16 / 64F, 16 / 64F, 24 / 64F, 16 / 32F);\n        // LEFT-ARM\n        x32Tox64(copier, 40 / 64F, 16 / 32F, 32 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F);\n        // RIGHT-ARM\n        copier.copy(40 / 64F, 16 / 32F, 40 / 64F, 16 / 64F, 16 / 64F, 16 / 32F);\n\n        return newSkin;\n    }\n\n    static void x32Tox64(PixelCopier copier, float srcX, float srcY, float toX, float toY, float width, float height, float depth) {\n        // TOP\n        copier.copy(srcX + depth, srcY, toX + depth, toY, width, depth * 2, true, false);\n        // BOTTOM\n        copier.copy(srcX + depth + width, srcY, toX + depth + width, toY, width, depth * 2, true, false);\n        // INS\n        copier.copy(srcX, srcY + depth * 2, toX + width + depth, toY + depth, depth, height, true, false);\n        // OUTS\n        copier.copy(srcX + width + depth, srcY + depth * 2, toX, toY + depth, depth, height, true, false);\n        // FRONT\n        copier.copy(srcX + depth, srcY + depth * 2, toX + depth, toY + depth, width, height, true, false);\n        // BACK\n        copier.copy(srcX + width + depth * 2, srcY + depth * 2, toX + width + depth * 2, toY + depth, width, height, true, false);\n    }\n\n    public static Image enlarge(Image srcSkin, int multipleX, int multipleY) {\n        WritableImage newSkin = new WritableImage((int) srcSkin.getWidth() * multipleX, (int) srcSkin.getHeight() * multipleY);\n        PixelReader reader = srcSkin.getPixelReader();\n        PixelWriter writer = newSkin.getPixelWriter();\n\n        for (int x = 0, lenX = (int) srcSkin.getWidth(); x < lenX; x++)\n            for (int y = 0, lenY = (int) srcSkin.getHeight(); y < lenY; y++)\n                for (int mx = 0; mx < multipleX; mx++)\n                    for (int my = 0; my < multipleY; my++) {\n                        int argb = reader.getArgb(x, y);\n                        writer.setArgb(x * multipleX + mx, y * multipleY + my, argb);\n                    }\n\n        return newSkin;\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinMultipleCubes.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.geometry.Point2D;\nimport javafx.scene.Group;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.PixelReader;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.Material;\nimport javafx.scene.paint.PhongMaterial;\nimport javafx.scene.shape.Box;\n\nimport java.util.function.BiConsumer;\nimport java.util.function.Supplier;\n\npublic class SkinMultipleCubes extends Group {\n\n    public static class Face extends Group {\n\n        public Face(Image image, int startX, int startY, int width, int height, int interval, boolean reverseX, boolean reverseY,\n                    Supplier<Box> supplier, BiConsumer<Box, Point2D> consumer) {\n            PixelReader reader = image.getPixelReader();\n            for (int x = 0; x < width; x++)\n                for (int y = 0; y < height; y++) {\n                    int argb;\n                    if ((argb = reader.getArgb(startX + (reverseX ? width - x - 1 : x) * interval,\n                            startY + (reverseY ? height - y - 1 : y) * interval)) != 0) {\n                        Box pixel = supplier.get();\n                        consumer.accept(pixel, new Point2D(x, y));\n                        pixel.setMaterial(createMaterial(Color.rgb(\n                                (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, (argb >> 0) & 0xFF)));\n                        getChildren().add(pixel);\n                    }\n                }\n        }\n\n        protected Material createMaterial(Color color) {\n            return new PhongMaterial(color);\n        }\n\n    }\n\n    protected int width, height, depth;\n    protected float startX, startY;\n    protected double length, thick;\n\n    public SkinMultipleCubes(int width, int height, int depth, float startX, float startY, double length, double thick) {\n        this.width = width;\n        this.height = height;\n        this.depth = depth;\n        this.startX = startX;\n        this.startY = startY;\n        this.length = length;\n        this.thick = thick;\n    }\n\n    public void setWidth(int width) {\n        this.width = width;\n    }\n\n    public int getWidth() {\n        return width;\n    }\n\n    public void setHeight(int height) {\n        this.height = height;\n    }\n\n    public int getHeight() {\n        return height;\n    }\n\n    public void setDepth(int depth) {\n        this.depth = depth;\n    }\n\n    public int getDepth() {\n        return depth;\n    }\n\n    public void setStartX(float startX) {\n        this.startX = startX;\n    }\n\n    public float getStartX() {\n        return startX;\n    }\n\n    public void setStartY(float startY) {\n        this.startY = startY;\n    }\n\n    public float getStartY() {\n        return startY;\n    }\n\n    public void setLength(double length) {\n        this.length = length;\n    }\n\n    public double getLength() {\n        return length;\n    }\n\n    public void setThick(double thick) {\n        this.thick = thick;\n    }\n\n    public double getThick() {\n        return thick;\n    }\n\n    public void updateSkin(Image skin) {\n        getChildren().clear();\n        int start_x = (int) (startX * skin.getWidth()), start_y = (int) (startY * skin.getHeight()),\n                interval = (int) Math.max(skin.getWidth() / 64, 1),\n                width_interval = width * interval, height_interval = height * interval, depth_interval = depth * interval;\n        // FRONT\n        getChildren().add(new Face(skin, start_x + depth_interval, start_y + depth_interval, width, height, interval, false, false,\n                () -> new Box(length, length, thick), (b, p) -> {\n            b.setTranslateX(((width - 1) / 2.0 - p.getX()) * b.getWidth());\n            b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight());\n            b.setTranslateZ((depth * length + thick) / 2.0);\n        }));\n        // BACK\n        getChildren().add(new Face(skin, start_x + width_interval + depth_interval * 2, start_y + depth_interval, width, height, interval, true, false,\n                () -> new Box(length, length, thick), (b, p) -> {\n            b.setTranslateX(((width - 1) / 2.0 - p.getX()) * b.getWidth());\n            b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight());\n            b.setTranslateZ(-(depth * length + thick) / 2.0);\n        }));\n        // LEFT\n        getChildren().add(new Face(skin, start_x + width_interval + depth_interval, start_y + depth_interval, depth, height, interval, false, false,\n                () -> new Box(thick, length, length), (b, p) -> {\n            b.setTranslateX((width * length + thick) / 2.0);\n            b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight());\n            b.setTranslateZ(((depth - 1) / 2.0 - p.getX()) * b.getDepth());\n        }));\n        // RIGHT\n        getChildren().add(new Face(skin, start_x, start_y + depth_interval, depth, height, interval, true, false,\n                () -> new Box(thick, length, length), (b, p) -> {\n            b.setTranslateX(-(width * length + thick) / 2.0);\n            b.setTranslateY(-((height - 1) / 2.0 - p.getY()) * b.getHeight());\n            b.setTranslateZ(((depth - 1) / 2.0 - p.getX()) * b.getDepth());\n        }));\n        // TOP\n        getChildren().add(new Face(skin, start_x + depth_interval, start_y, width, depth, interval, false, false,\n                () -> new Box(length, thick, length), (b, p) -> {\n            b.setTranslateX(((width - 1) / 2.0 - p.getX()) * b.getWidth());\n            b.setTranslateY(-(height * length + thick) / 2.0);\n            b.setTranslateZ(-((depth - 1) / 2.0 - p.getY()) * b.getDepth());\n        }));\n        // BOTTOM\n        getChildren().add(new Face(skin, start_x + width_interval + depth_interval, start_y, width, depth, interval, false, false,\n                () -> new Box(length, thick, length), (b, p) -> {\n            b.setTranslateX(((width - 1) / 2.0 - p.getX()) * b.getWidth());\n            b.setTranslateY((height * length + thick) / 2.0);\n            b.setTranslateZ(-((depth - 1) / 2.0 - p.getY()) * b.getDepth());\n        }));\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinTransition.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.animation.Transition;\nimport javafx.beans.value.WritableValue;\nimport javafx.util.Duration;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Function;\n\npublic class SkinTransition extends Transition {\n\n    protected Function<Double, Double> expression;\n    protected List<WritableValue<Number>> observables;\n    protected boolean fix;\n    protected int count;\n\n    public int getCount() {\n        return count;\n    }\n\n    @SafeVarargs\n    public SkinTransition(Duration duration, Function<Double, Double> expression, WritableValue<Number>... observables) {\n        setCycleDuration(duration);\n        this.expression = expression;\n        this.observables = Arrays.asList(observables);\n    }\n\n    @Override\n    protected void interpolate(double frac) {\n        if (frac == 0 || frac == 1)\n            count++;\n        double val = expression.apply(frac);\n        observables.forEach(w -> w.setValue(val));\n    }\n\n    @Override\n    public void play() {\n        count = 0;\n        super.play();\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/animation/SkinAniRunning.java",
    "content": "package org.jackhuang.hmcl.ui.skin.animation;\n\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.skin.FunctionHelper;\nimport org.jackhuang.hmcl.ui.skin.SkinAnimation;\nimport org.jackhuang.hmcl.ui.skin.SkinCanvas;\nimport org.jackhuang.hmcl.ui.skin.SkinTransition;\n\npublic final class SkinAniRunning extends SkinAnimation {\n\n    private SkinTransition larmTransition, rarmTransition;\n\n    public SkinAniRunning(int weight, int time, double angle, SkinCanvas canvas) {\n        larmTransition = new SkinTransition(Duration.millis(time),\n                v -> v * (larmTransition.getCount() % 4 < 2 ? 1 : -1) * angle,\n                canvas.larm.getXRotate().angleProperty(), canvas.rleg.getXRotate().angleProperty());\n\n        rarmTransition = new SkinTransition(Duration.millis(time),\n                v -> v * (rarmTransition.getCount() % 4 < 2 ? 1 : -1) * -angle,\n                canvas.rarm.getXRotate().angleProperty(), canvas.lleg.getXRotate().angleProperty());\n\n        FunctionHelper.alwaysB(SkinTransition::setAutoReverse, true, larmTransition, rarmTransition);\n        FunctionHelper.alwaysB(SkinTransition::setCycleCount, 16, larmTransition, rarmTransition);\n        FunctionHelper.always(transitions::add, larmTransition, rarmTransition);\n        this.weight = weight;\n        init();\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/animation/SkinAniWavingArms.java",
    "content": "package org.jackhuang.hmcl.ui.skin.animation;\n\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.ui.skin.FunctionHelper;\nimport org.jackhuang.hmcl.ui.skin.SkinAnimation;\nimport org.jackhuang.hmcl.ui.skin.SkinCanvas;\nimport org.jackhuang.hmcl.ui.skin.SkinTransition;\n\npublic final class SkinAniWavingArms extends SkinAnimation {\n\n    public SkinAniWavingArms(int weight, int time, double angle, SkinCanvas canvas) {\n        SkinTransition larmTransition = new SkinTransition(Duration.millis(time), v -> v * angle,\n                canvas.larm.getZRotate().angleProperty());\n\n        SkinTransition rarmTransition = new SkinTransition(Duration.millis(time), v -> v * -angle,\n                canvas.rarm.getZRotate().angleProperty());\n\n        FunctionHelper.alwaysB(SkinTransition::setAutoReverse, true, larmTransition, rarmTransition);\n        FunctionHelper.alwaysB(SkinTransition::setCycleCount, 2, larmTransition, rarmTransition);\n        FunctionHelper.always(transitions::add, larmTransition, rarmTransition);\n        this.weight = weight;\n        init();\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.terracotta;\n\nimport com.jfoenix.controls.JFXProgressBar;\nimport javafx.beans.property.DoubleProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleDoubleProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.WeakChangeListener;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.TextFlow;\nimport org.jackhuang.hmcl.game.LauncherHelper;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.terracotta.TerracottaManager;\nimport org.jackhuang.hmcl.terracotta.TerracottaMetadata;\nimport org.jackhuang.hmcl.terracotta.TerracottaState;\nimport org.jackhuang.hmcl.terracotta.profile.TerracottaProfile;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.versions.Versions;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.i18n.LocaleUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.Zipper;\nimport org.jackhuang.hmcl.util.logging.Logger;\n\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class TerracottaControllerPage extends StackPane {\n    private static final String FEEDBACK_TIP = \"terracotta-feedback\";\n    private static final ObjectProperty<TerracottaState> UI_STATE = new SimpleObjectProperty<>();\n\n    static {\n        FXUtils.onChangeAndOperate(TerracottaManager.stateProperty(), state -> {\n            if (state != null) {\n                UI_STATE.set(state);\n            }\n        });\n    }\n\n    private final WeakListenerHolder holder = new WeakListenerHolder();\n\n    /* FIXME: It's sucked to have such a long logic, containing UI for all states defined in TerracottaState, with unclear control flows.\n         Consider moving UI into multiple files for each state respectively. */\n    public TerracottaControllerPage() {\n        holder.add(FXUtils.observeWeak(() -> {\n            // Run daemon process only if HMCL is focused and is displaying current node.\n            TerracottaManager.switchDaemon(getScene() != null && Controllers.getStage().isFocused());\n        }, this.sceneProperty(), Controllers.getStage().focusedProperty()));\n\n        TransitionPane transition = new TransitionPane();\n\n        ObjectProperty<String> statusProperty = new SimpleObjectProperty<>();\n        DoubleProperty progressProperty = new SimpleDoubleProperty();\n        ObservableList<Node> nodesProperty = FXCollections.observableList(new ArrayList<>());\n\n        FXUtils.applyDragListener(this, path -> {\n            TerracottaState state = UI_STATE.get();\n\n            if (state instanceof TerracottaState.Uninitialized ||\n                    state instanceof TerracottaState.Preparing preparing && preparing.hasInstallFence() ||\n                    state instanceof TerracottaState.Fatal fatal && fatal.isRecoverable()\n            ) {\n                return Files.isReadable(path) && FileUtils.getName(path).toLowerCase(Locale.ROOT).endsWith(\".tar.gz\");\n            } else {\n                return false;\n            }\n        }, files -> {\n            Path path = files.get(0);\n\n            if (TerracottaManager.isInvalidBundle(path)) {\n                Controllers.dialog(\n                        i18n(\"terracotta.from_local.file_name_mismatch\", TerracottaMetadata.PACKAGE_NAME, FileUtils.getName(path)),\n                        i18n(\"message.error\"),\n                        MessageDialogPane.MessageType.ERROR\n                );\n                return;\n            }\n\n            TerracottaState.Preparing next = TerracottaManager.install(path);\n            if (next != null) {\n                UI_STATE.set(next);\n            }\n        });\n\n        ChangeListener<TerracottaState> listener = (_uiState, legacyState, state) -> {\n            if (legacyState != null && legacyState.isUIFakeState() && !state.isUIFakeState() && legacyState.getClass() == state.getClass()) {\n                return;\n            }\n\n            progressProperty.unbind();\n\n            if (state instanceof TerracottaState.Bootstrap) {\n                statusProperty.set(i18n(\"terracotta.status.bootstrap\"));\n                progressProperty.set(-1);\n                nodesProperty.setAll();\n            } else if (state instanceof TerracottaState.Uninitialized uninitialized) {\n                String fork = uninitialized.hasLegacy() ? \"update\" : \"not_exist\";\n\n                statusProperty.set(i18n(\"terracotta.status.uninitialized.\" + fork));\n                progressProperty.set(0);\n\n                TextFlow body = FXUtils.segmentToTextFlow(i18n(\"terracotta.confirm.desc\"), Controllers::onHyperlinkAction);\n                body.getStyleClass().add(\"terracotta-hint\");\n                body.setLineSpacing(4);\n\n                var download = createLargeTitleLineButton();\n                download.setLeading(FXUtils.newBuiltinImage(\"/assets/img/terracotta.png\"));\n                download.setTitle(i18n(String.format(\"terracotta.status.uninitialized.%s.title\", fork)));\n                download.setSubtitle(i18n(\"terracotta.status.uninitialized.desc\"));\n                download.setTrailingIcon(SVG.ARROW_FORWARD, ICON_SIZE);\n                download.setOnAction(event -> {\n                    TerracottaState.Preparing s = TerracottaManager.download();\n                    if (s != null) {\n                        UI_STATE.set(s);\n                    }\n\n                    if (uninitialized.hasLegacy() && I18n.isUseChinese()) {\n                        Object feedback = config().getShownTips().get(FEEDBACK_TIP);\n                        if (!(feedback instanceof Number number) || number.intValue() < 1) {\n                            Controllers.confirm(i18n(\"terracotta.feedback.desc\"), i18n(\"terracotta.feedback.title\"), () -> {\n                                FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK);\n                                config().getShownTips().put(FEEDBACK_TIP, 1);\n                            }, () -> {\n                            });\n                        }\n                    }\n                });\n\n                nodesProperty.setAll(body, download, getThirdPartyDownloadNodes());\n            } else if (state instanceof TerracottaState.Preparing) {\n                statusProperty.set(i18n(\"terracotta.status.preparing\"));\n                progressProperty.bind(((TerracottaState.Preparing) state).progressProperty());\n                nodesProperty.setAll(getThirdPartyDownloadNodes());\n            } else if (state instanceof TerracottaState.Launching) {\n                statusProperty.set(i18n(\"terracotta.status.launching\"));\n                progressProperty.set(-1);\n                nodesProperty.setAll();\n            } else if (state instanceof TerracottaState.Unknown) {\n                statusProperty.set(i18n(\"terracotta.status.unknown\"));\n                progressProperty.set(-1);\n                nodesProperty.setAll();\n            } else if (state instanceof TerracottaState.Waiting) {\n                statusProperty.set(i18n(\"terracotta.status.waiting\"));\n                progressProperty.set(1);\n\n                TextFlow flow = FXUtils.segmentToTextFlow(i18n(\"terracotta.confirm.desc\"), Controllers::onHyperlinkAction);\n                flow.getStyleClass().add(\"terracotta-hint\");\n                flow.setLineSpacing(4);\n\n                var host = createLargeTitleLineButton();\n                host.setLeading(SVG.HOST, ICON_SIZE);\n                host.setTitle(i18n(\"terracotta.status.waiting.host.title\"));\n                host.setSubtitle(i18n(\"terracotta.status.waiting.host.desc\"));\n                host.setTrailingIcon(SVG.ARROW_FORWARD, ICON_SIZE);\n                host.setOnAction(event -> {\n                    if (LauncherHelper.countMangedProcesses() >= 1) {\n                        TerracottaState.HostScanning s1 = TerracottaManager.setScanning();\n                        if (s1 != null) {\n                            UI_STATE.set(s1);\n                        }\n                    } else {\n                        Controllers.dialog(new MessageDialogPane.Builder(\n                                i18n(\"terracotta.status.waiting.host.launch.desc\"),\n                                i18n(\"terracotta.status.waiting.host.launch.title\"),\n                                MessageDialogPane.MessageType.QUESTION\n                        ).addAction(i18n(\"version.launch\"), () -> {\n                            Profile profile = Profiles.getSelectedProfile();\n                            Versions.launch(profile, profile.getSelectedVersion(), launcherHelper -> {\n                                launcherHelper.setKeep();\n                                launcherHelper.setDisableOfflineSkin();\n                            });\n                        }).addCancel(i18n(\"terracotta.status.waiting.host.launch.skip\"), () -> {\n                            TerracottaState.HostScanning s1 = TerracottaManager.setScanning();\n                            if (s1 != null) {\n                                UI_STATE.set(s1);\n                            }\n                        }).addCancel(() -> {\n                        }).build());\n                    }\n                });\n\n                var guest = createLargeTitleLineButton();\n                guest.setLeading(SVG.ADD_CIRCLE, ICON_SIZE);\n                guest.setTitle(i18n(\"terracotta.status.waiting.guest.title\"));\n                guest.setSubtitle(i18n(\"terracotta.status.waiting.guest.desc\"));\n                guest.setTrailingIcon(SVG.ARROW_FORWARD, ICON_SIZE);\n                guest.setOnAction(event -> {\n                    Controllers.prompt(i18n(\"terracotta.status.waiting.guest.prompt.title\"), (code, handler) -> {\n                        Task<TerracottaState.GuestConnecting> task = TerracottaManager.setGuesting(code);\n                        if (task != null) {\n                            task.whenComplete(Schedulers.javafx(), (s, e) -> {\n                                if (e != null) {\n                                    handler.reject(i18n(\"terracotta.status.waiting.guest.prompt.invalid\"));\n                                } else {\n                                    handler.resolve();\n                                    UI_STATE.set(s);\n                                }\n                            }).setSignificance(Task.TaskSignificance.MINOR).start();\n                        } else {\n                            handler.resolve();\n                        }\n                    });\n                });\n\n                if (ThreadLocalRandom.current().nextDouble() < 0.02D) {\n                    var feedback = createLargeTitleLineButton();\n                    feedback.setLeading(SVG.FEEDBACK, ICON_SIZE);\n                    feedback.setTitle(i18n(\"terracotta.feedback.title\"));\n                    feedback.setSubtitle(i18n(\"terracotta.feedback.desc\"));\n                    feedback.setTrailingIcon(SVG.OPEN_IN_NEW, ICON_SIZE);\n                    FXUtils.onClicked(feedback, () -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK));\n\n                    nodesProperty.setAll(flow, host, guest, feedback);\n                } else {\n                    nodesProperty.setAll(flow, host, guest);\n                }\n            } else if (state instanceof TerracottaState.HostScanning) {\n                statusProperty.set(i18n(\"terracotta.status.scanning\"));\n                progressProperty.set(-1);\n\n                TextFlow body = FXUtils.segmentToTextFlow(i18n(\"terracotta.status.scanning.desc\"), Controllers::onHyperlinkAction);\n                body.getStyleClass().add(\"terracotta-hint\");\n                body.setLineSpacing(4);\n\n                var room = createLargeTitleLineButton();\n                room.setLeading(SVG.ARROW_BACK, ICON_SIZE);\n                room.setTitle(i18n(\"terracotta.back\"));\n                room.setSubtitle(i18n(\"terracotta.status.scanning.back\"));\n                room.setOnAction(event -> {\n                    TerracottaState.Waiting s = TerracottaManager.setWaiting();\n                    if (s != null) {\n                        UI_STATE.set(s);\n                    }\n                });\n\n                nodesProperty.setAll(body, room);\n            } else if (state instanceof TerracottaState.HostStarting) {\n                statusProperty.set(i18n(\"terracotta.status.host_starting\"));\n                progressProperty.set(-1);\n\n                var room = createLargeTitleLineButton();\n                room.setLeading(SVG.ARROW_BACK, ICON_SIZE);\n                room.setTitle(i18n(\"terracotta.back\"));\n                room.setSubtitle(i18n(\"terracotta.status.host_starting.back\"));\n                room.setOnAction(event -> {\n                    TerracottaState.Waiting s = TerracottaManager.setWaiting();\n                    if (s != null) {\n                        UI_STATE.set(s);\n                    }\n                });\n\n                nodesProperty.setAll(room);\n            } else if (state instanceof TerracottaState.HostOK hostOK) {\n                if (hostOK.isForkOf(legacyState)) {\n                    if (nodesProperty.get(nodesProperty.size() - 1) instanceof PlayerProfileUI profileUI) {\n                        profileUI.updateProfiles(hostOK.getProfiles());\n                    } else { // Should NOT happen\n                        nodesProperty.add(new PlayerProfileUI(hostOK.getProfiles()));\n                    }\n                    return;\n                } else {\n                    String cs = hostOK.getCode();\n                    copyCode(cs);\n\n                    statusProperty.set(i18n(\"terracotta.status.host_ok\"));\n                    progressProperty.set(1);\n\n                    VBox code = new VBox(4);\n                    code.setAlignment(Pos.CENTER);\n                    {\n                        Label desc = new Label(i18n(\"terracotta.status.host_ok.code\"));\n                        desc.setMouseTransparent(true);\n\n                        Label label = new Label(cs);\n                        label.setMouseTransparent(true);\n                        label.setStyle(\"-fx-font-size: 24\");\n                        label.setAlignment(Pos.CENTER);\n                        VBox.setMargin(label, new Insets(10, 0, 10, 0));\n\n                        code.getChildren().setAll(desc, label);\n                    }\n                    code.setCursor(Cursor.HAND);\n                    FXUtils.onClicked(code, () -> copyCode(cs));\n\n                    var copy = createLargeTitleLineButton();\n                    copy.setLeading(SVG.CONTENT_COPY, ICON_SIZE);\n                    copy.setTitle(i18n(\"terracotta.status.host_ok.code.copy\"));\n                    copy.setSubtitle(i18n(\"terracotta.status.host_ok.code.desc\"));\n                    FXUtils.onClicked(copy, () -> copyCode(cs));\n\n                    var back = createLargeTitleLineButton();\n                    back.setLeading(SVG.ARROW_BACK, ICON_SIZE);\n                    back.setTitle(i18n(\"terracotta.back\"));\n                    back.setSubtitle(i18n(\"terracotta.status.host_ok.back\"));\n                    back.setOnAction(event -> {\n                        TerracottaState.Waiting s = TerracottaManager.setWaiting();\n                        if (s != null) {\n                            UI_STATE.set(s);\n                        }\n                    });\n\n                    if (hostOK.getProfiles().isEmpty()) {\n                        nodesProperty.setAll(code, copy, back);\n                    } else {\n                        nodesProperty.setAll(code, copy, back, new PlayerProfileUI(hostOK.getProfiles()));\n                    }\n                }\n            } else if (state instanceof TerracottaState.GuestConnecting || state instanceof TerracottaState.GuestStarting) {\n                statusProperty.set(i18n(\"terracotta.status.guest_starting\"));\n                progressProperty.set(-1);\n\n                var room = createLargeTitleLineButton();\n                room.setLeading(SVG.ARROW_BACK, ICON_SIZE);\n                room.setTitle(i18n(\"terracotta.back\"));\n                room.setSubtitle(i18n(\"terracotta.status.guest_starting.back\"));\n                room.setOnAction(event -> {\n                    TerracottaState.Waiting s = TerracottaManager.setWaiting();\n                    if (s != null) {\n                        UI_STATE.set(s);\n                    }\n                });\n\n                nodesProperty.clear();\n                if (state instanceof TerracottaState.GuestStarting) {\n                    TerracottaState.GuestStarting.Difficulty difficulty = ((TerracottaState.GuestStarting) state).getDifficulty();\n                    if (difficulty != null && difficulty != TerracottaState.GuestStarting.Difficulty.UNKNOWN) {\n                        var info = createLargeTitleLineButton();\n                        info.setLeading(switch (difficulty) {\n                            case UNKNOWN -> throw new AssertionError();\n                            case EASIEST, SIMPLE -> SVG.INFO;\n                            case MEDIUM, TOUGH -> SVG.WARNING;\n                        }, ICON_SIZE);\n\n                        String difficultyID = difficulty.name().toLowerCase(Locale.ROOT);\n                        info.setTitle(i18n(String.format(\"terracotta.difficulty.%s\", difficultyID)));\n                        info.setSubtitle(i18n(\"terracotta.difficulty.estimate_only\"));\n\n                        nodesProperty.add(info);\n                    }\n                }\n\n                nodesProperty.add(room);\n            } else if (state instanceof TerracottaState.GuestOK guestOK) {\n                if (guestOK.isForkOf(legacyState)) {\n                    if (nodesProperty.get(nodesProperty.size() - 1) instanceof PlayerProfileUI profileUI) {\n                        profileUI.updateProfiles(guestOK.getProfiles());\n                    } else { // Should NOT happen\n                        nodesProperty.add(new PlayerProfileUI(guestOK.getProfiles()));\n                    }\n                    return;\n                } else {\n                    statusProperty.set(i18n(\"terracotta.status.guest_ok\"));\n                    progressProperty.set(1);\n\n                    var tutorial = createLargeTitleLineButton();\n                    tutorial.setTitle(i18n(\"terracotta.status.guest_ok.title\"));\n                    tutorial.setSubtitle(i18n(\"terracotta.status.guest_ok.desc\", guestOK.getUrl()));\n\n                    var back = createLargeTitleLineButton();\n                    back.setLeading(SVG.ARROW_BACK, ICON_SIZE);\n                    back.setTitle(i18n(\"terracotta.back\"));\n                    back.setSubtitle(i18n(\"terracotta.status.guest_ok.back\"));\n                    back.setOnAction(event -> {\n                        TerracottaState.Waiting s = TerracottaManager.setWaiting();\n                        if (s != null) {\n                            UI_STATE.set(s);\n                        }\n                    });\n\n                    if (guestOK.getProfiles().isEmpty()) {\n                        nodesProperty.setAll(tutorial, back);\n                    } else {\n                        nodesProperty.setAll(tutorial, back, new PlayerProfileUI(guestOK.getProfiles()));\n                    }\n                }\n            } else if (state instanceof TerracottaState.Exception exception) {\n                statusProperty.set(i18n(\"terracotta.status.exception.desc.\" + exception.getType().name().toLowerCase(Locale.ROOT)));\n                progressProperty.set(1);\n                nodesProperty.setAll();\n\n                var back = createLargeTitleLineButton();\n                back.setLeading(SVG.ARROW_BACK, ICON_SIZE);\n                back.setTitle(i18n(\"terracotta.back\"));\n                back.setSubtitle(i18n(\"terracotta.status.exception.back\"));\n                back.setOnAction(event -> {\n                    TerracottaState.Waiting s = TerracottaManager.setWaiting();\n                    if (s != null) {\n                        UI_STATE.set(s);\n                    }\n                });\n\n                SpinnerPane exportLog = new SpinnerPane();\n                var exportLogInner = createLargeTitleLineButton();\n                exportLogInner.setLeading(SVG.OUTPUT, ICON_SIZE);\n                exportLogInner.setTitle(i18n(\"terracotta.export_log\"));\n                exportLogInner.setSubtitle(i18n(\"terracotta.export_log.desc\"));\n                exportLog.setContent(exportLogInner);\n                ComponentList.setNoPadding(exportLog);\n                // FIXME: SpinnerPane loses its content width in loading state.\n                exportLog.minHeightProperty().bind(back.heightProperty());\n\n                exportLogInner.setOnAction(event -> {\n                    exportLog.setLoading(true);\n\n                    TerracottaManager.exportLogs().thenAcceptAsync(Schedulers.io(), data -> {\n                        if (data == null || data.isEmpty()) {\n                            return;\n                        }\n\n                        Path path = Path.of(\"terracotta-log-\" + LocalDateTime.now().format(\n                                DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH-mm-ss\")\n                        ) + \".zip\").toAbsolutePath();\n                        try (Zipper zipper = new Zipper(path)) {\n                            zipper.putTextFile(data, StandardCharsets.UTF_8, \"terracotta.log\");\n                            try (OutputStream os = zipper.putStream(\"hmcl-latest.log\")) {\n                                Logger.LOG.exportLogs(os);\n                            }\n                        }\n                        FXUtils.showFileInExplorer(path);\n                    }).thenRunAsync(\n                            () -> Thread.sleep(3000)\n                    ).whenComplete(\n                            Schedulers.javafx(),\n                            e -> exportLog.setLoading(false)\n                    ).start();\n                });\n\n                nodesProperty.setAll(back, exportLog);\n            } else if (state instanceof TerracottaState.Fatal fatal) {\n                String message = i18n(\"terracotta.status.fatal.\" + fatal.getType().name().toLowerCase(Locale.ROOT));\n\n                statusProperty.set(message);\n                progressProperty.set(1);\n\n                if (fatal.isRecoverable()) {\n                    var retry = createLargeTitleLineButton();\n                    retry.setLeading(SVG.RESTORE, ICON_SIZE);\n                    retry.setTitle(i18n(\"terracotta.status.fatal.retry\"));\n                    retry.setSubtitle(message);\n                    retry.setOnAction(event -> {\n                        TerracottaState s = TerracottaManager.recover();\n                        if (s != null) {\n                            UI_STATE.set(s);\n                        }\n                    });\n\n                    if (fatal.getType() == TerracottaState.Fatal.Type.NETWORK) {\n                        nodesProperty.setAll(retry, getThirdPartyDownloadNodes());\n                    } else {\n                        nodesProperty.setAll(retry);\n                    }\n                } else {\n                    nodesProperty.setAll();\n                }\n            } else {\n                throw new AssertionError(state.getClass().getName());\n            }\n\n            ComponentList components = new ComponentList();\n            {\n                VBox statusPane = new VBox(8);\n                VBox.setMargin(statusPane, new Insets(0, 0, 0, 4));\n                {\n                    Label status = new Label();\n                    status.textProperty().bind(statusProperty);\n                    JFXProgressBar progress = new JFXProgressBar();\n                    progress.progressProperty().bind(progressProperty);\n                    progress.setMaxWidth(Double.MAX_VALUE);\n\n                    statusPane.getChildren().setAll(status, progress);\n                }\n\n                ObservableList<Node> children = components.getContent();\n                children.add(statusPane);\n                children.addAll(nodesProperty);\n            }\n            // Prevent the shadow of components from being clipped\n            StackPane.setMargin(components, new Insets(0, 0, 5, 0));\n            transition.setContent(components, ContainerAnimations.SLIDE_UP_FADE_IN);\n        };\n        listener.changed(UI_STATE, null, UI_STATE.get());\n        holder.add(listener);\n        UI_STATE.addListener(new WeakChangeListener<>(listener));\n\n        VBox content = new VBox(10);\n        content.getChildren().add(ComponentList.createComponentListTitle(i18n(\"terracotta.status\")));\n        if (!LocaleUtils.IS_CHINA_MAINLAND) {\n            HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);\n            hintPane.setText(i18n(\"terracotta.unsupported.region\"));\n            content.getChildren().add(hintPane);\n        }\n        content.getChildren().add(transition);\n        content.setPadding(new Insets(10));\n        content.setFillWidth(true);\n\n        ScrollPane scrollPane = new ScrollPane(content);\n        FXUtils.smoothScrolling(scrollPane);\n        scrollPane.setFitToWidth(true);\n\n        getChildren().setAll(scrollPane);\n    }\n\n    private ComponentSublist getThirdPartyDownloadNodes() {\n        ComponentSublist locals = new ComponentSublist();\n\n        var header = new LinePane();\n        header.getStyleClass().add(\"no-padding\");\n        header.setLargeTitle(true);\n        header.setMinHeight(LinePane.USE_COMPUTED_SIZE);\n        header.setMouseTransparent(true);\n        header.setLeading(FXUtils.newBuiltinImage(\"/assets/img/terracotta.png\"));\n        header.setTitle(i18n(\"terracotta.from_local.title\"));\n        header.setSubtitle(i18n(\"terracotta.from_local.desc\"));\n        locals.setHeaderLeft(header);\n\n        for (TerracottaMetadata.Link link : TerracottaMetadata.PACKAGE_LINKS) {\n            LineButton item = new LineButton();\n            item.setTrailingIcon(SVG.OPEN_IN_NEW);\n            item.setTitle(link.description().getText(I18n.getLocale().getCandidateLocales()));\n            item.setOnAction(event -> Controllers.dialog(\n                    i18n(\"terracotta.from_local.guide\", TerracottaMetadata.PACKAGE_NAME),\n                    i18n(\"message.info\"), MessageDialogPane.MessageType.INFO,\n                    () -> FXUtils.openLink(link.link())\n            ));\n            locals.getContent().add(item);\n        }\n        return locals;\n    }\n\n    private void copyCode(String code) {\n        FXUtils.copyText(code, i18n(\"terracotta.status.host_ok.code.copy.toast\"));\n    }\n\n    private static final double ICON_SIZE = 28;\n\n    private static LineButton createLargeTitleLineButton() {\n        var lineButton = new LineButton();\n        lineButton.setLargeTitle(true);\n        return lineButton;\n    }\n\n    private static final class PlayerProfileUI extends VBox {\n        private final TransitionPane transition;\n\n        public PlayerProfileUI(List<TerracottaProfile> profiles) {\n            super(8);\n            VBox.setMargin(this, new Insets(0, 0, 0, 4));\n            {\n                Label status = new Label();\n                status.setText(i18n(\"terracotta.player_list\"));\n\n                transition = new TransitionPane();\n                getChildren().setAll(status, transition);\n\n                updateProfiles(profiles);\n            }\n        }\n\n        private void updateProfiles(List<TerracottaProfile> profiles) {\n            VBox pane = new VBox(8);\n\n            for (TerracottaProfile profile : profiles) {\n                TwoLineListItem item = new TwoLineListItem();\n                item.setTitle(profile.getName());\n                item.setSubtitle(profile.getVendor());\n                item.addTag(i18n(\"terracotta.player_kind.\" + profile.getType().name().toLowerCase(Locale.ROOT)));\n                pane.getChildren().add(item);\n            }\n\n            this.transition.setContent(pane, ContainerAnimations.SLIDE_UP_FADE_IN);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.terracotta;\n\nimport com.jfoenix.controls.JFXPopup;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport javafx.beans.value.ChangeListener;\nimport javafx.geometry.Insets;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.setting.Accounts;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.terracotta.TerracottaMetadata;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;\nimport org.jackhuang.hmcl.ui.account.AccountListPopupMenu;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.ui.main.MainPage;\nimport org.jackhuang.hmcl.ui.versions.GameListPopupMenu;\nimport org.jackhuang.hmcl.ui.versions.Versions;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {\n    private static final int TERRACOTTA_AGREEMENT_VERSION = 2;\n\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n(\"terracotta.terracotta\")));\n    private final TabHeader tab;\n    private final TabHeader.Tab<TerracottaControllerPage> statusPage = new TabHeader.Tab<>(\"statusPage\");\n    private final TransitionPane transitionPane = new TransitionPane();\n\n    @SuppressWarnings(\"unused\")\n    private ChangeListener<String> instanceChangeListenerHolder;\n\n    public TerracottaPage() {\n        statusPage.setNodeSupplier(TerracottaControllerPage::new);\n        tab = new TabHeader(transitionPane, statusPage);\n        tab.select(statusPage);\n\n        BorderPane left = new BorderPane();\n        FXUtils.setLimitWidth(left, 200);\n        VBox.setVgrow(left, Priority.ALWAYS);\n        setLeft(left);\n\n        AdvancedListBox sideBar = new AdvancedListBox()\n                .addNavigationDrawerTab(tab, statusPage, i18n(\"terracotta.status\"), SVG.TUNE);\n        left.setTop(sideBar);\n\n        AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();\n        accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));\n        accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());\n        FXUtils.onSecondaryButtonClicked(accountListItem, () -> AccountListPopupMenu.show(accountListItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, accountListItem.getWidth(), 0));\n\n        AdvancedListBox toolbar = new AdvancedListBox()\n                .add(accountListItem)\n                .addNavigationDrawerItem(i18n(\"version.launch\"), SVG.ROCKET_LAUNCH, () -> {\n                    Profile profile = Profiles.getSelectedProfile();\n                    Versions.launch(profile, profile.getSelectedVersion(), launcherHelper -> {\n                        launcherHelper.setKeep();\n                        launcherHelper.setDisableOfflineSkin();\n                    });\n                }, item -> {\n                    instanceChangeListenerHolder = FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(),\n                            instanceName -> item.setSubtitle(StringUtils.isNotBlank(instanceName) ? instanceName : i18n(\"version.empty\"))\n                    );\n\n                    MainPage mainPage = Controllers.getRootPage().getMainPage();\n                    FXUtils.onScroll(item, mainPage.getVersions(), list -> {\n                        String currentId = mainPage.getCurrentGame();\n                        return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));\n                    }, it -> mainPage.getProfile().setSelectedVersion(it.getId()));\n\n                    FXUtils.onSecondaryButtonClicked(item, () -> GameListPopupMenu.show(item,\n                            JFXPopup.PopupVPosition.BOTTOM,\n                            JFXPopup.PopupHPosition.LEFT,\n                            item.getWidth(),\n                            0,\n                            mainPage.getProfile(), mainPage.getVersions()));\n                })\n                .addNavigationDrawerItem(i18n(\"terracotta.feedback.title\"), SVG.FEEDBACK, () -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK));\n        BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));\n        left.setBottom(toolbar);\n\n        setCenter(transitionPane);\n    }\n\n    @Override\n    public void onPageShown() {\n        tab.onPageShown();\n\n        if (globalConfig().getTerracottaAgreementVersion() < TERRACOTTA_AGREEMENT_VERSION) {\n            Controllers.confirmWithCountdown(i18n(\"terracotta.confirm.desc\"), i18n(\"terracotta.confirm.title\"), 5, MessageDialogPane.MessageType.INFO, () -> {\n                globalConfig().setTerracottaAgreementVersion(TERRACOTTA_AGREEMENT_VERSION);\n            }, () -> fireEvent(new PageCloseEvent()));\n        }\n    }\n\n    @Override\n    public void onPageHidden() {\n        tab.onPageHidden();\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AdvancedVersionSettingPage.java",
    "content": "package org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.game.NativesDirectoryType;\nimport org.jackhuang.hmcl.game.Renderer;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.VersionSetting;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.nio.file.FileSystems;\nimport java.util.Arrays;\nimport java.util.Locale;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class AdvancedVersionSettingPage extends StackPane implements DecoratorPage {\n\n    private final ObjectProperty<State> stateProperty;\n\n    private final Profile profile;\n    private final String versionId;\n    private final VersionSetting versionSetting;\n\n    private final JFXTextField txtJVMArgs;\n    private final JFXTextField txtGameArgs;\n    private final JFXTextField txtEnvironmentVariables;\n    private final JFXTextField txtMetaspace;\n    private final JFXTextField txtWrapper;\n    private final JFXTextField txtPreLaunchCommand;\n    private final JFXTextField txtPostExitCommand;\n    private final LineToggleButton noJVMArgsPane;\n    private final LineToggleButton noOptimizingJVMArgsPane;\n    private final LineToggleButton noGameCheckPane;\n    private final LineToggleButton noJVMCheckPane;\n    private final LineToggleButton noNativesPatchPane;\n    private final LineToggleButton useNativeGLFWPane;\n    private final LineToggleButton useNativeOpenALPane;\n    private final ComponentSublist nativesDirSublist;\n    private final MultiFileItem<NativesDirectoryType> nativesDirItem;\n    private final MultiFileItem.FileOption<NativesDirectoryType> nativesDirCustomOption;\n    private final LineSelectButton<Renderer> rendererPane;\n\n    public AdvancedVersionSettingPage(Profile profile, @Nullable String versionId, VersionSetting versionSetting) {\n        this.profile = profile;\n        this.versionId = versionId;\n        this.versionSetting = versionSetting;\n        this.stateProperty = new SimpleObjectProperty<>(State.fromTitle(\n                versionId == null ? i18n(\"settings.advanced\") : i18n(\"settings.advanced.title\", versionId)\n        ));\n\n        this.getStyleClass().add(\"gray-background\");\n\n        ScrollPane scrollPane = new ScrollPane();\n        scrollPane.setFitToHeight(true);\n        scrollPane.setFitToWidth(true);\n        scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);\n        getChildren().setAll(scrollPane);\n\n        VBox rootPane = new VBox();\n        rootPane.setFocusTraversable(true);\n        rootPane.setFillWidth(true);\n        scrollPane.setContent(rootPane);\n        FXUtils.smoothScrolling(scrollPane);\n        rootPane.getStyleClass().add(\"card-list\");\n\n        ComponentList customCommandsPane = new ComponentList();\n        {\n            GridPane pane = new GridPane();\n            pane.setHgap(16);\n            pane.setVgap(8);\n            pane.getColumnConstraints().setAll(new ColumnConstraints(), FXUtils.getColumnHgrowing());\n\n            txtGameArgs = new JFXTextField();\n            txtGameArgs.setPromptText(i18n(\"settings.advanced.minecraft_arguments.prompt\"));\n            txtGameArgs.getStyleClass().add(\"fit-width\");\n            pane.addRow(0, new Label(i18n(\"settings.advanced.minecraft_arguments\")), txtGameArgs);\n\n            txtPreLaunchCommand = new JFXTextField();\n            txtPreLaunchCommand.setPromptText(i18n(\"settings.advanced.precall_command.prompt\"));\n            txtPreLaunchCommand.getStyleClass().add(\"fit-width\");\n            pane.addRow(1, new Label(i18n(\"settings.advanced.precall_command\")), txtPreLaunchCommand);\n\n            txtWrapper = new JFXTextField();\n            txtWrapper.setPromptText(i18n(\"settings.advanced.wrapper_launcher.prompt\"));\n            txtWrapper.getStyleClass().add(\"fit-width\");\n            pane.addRow(2, new Label(i18n(\"settings.advanced.wrapper_launcher\")), txtWrapper);\n\n            txtPostExitCommand = new JFXTextField();\n            txtPostExitCommand.setPromptText(i18n(\"settings.advanced.post_exit_command.prompt\"));\n            txtPostExitCommand.getStyleClass().add(\"fit-width\");\n            pane.addRow(3, new Label(i18n(\"settings.advanced.post_exit_command\")), txtPostExitCommand);\n\n            HintPane hintPane = new HintPane();\n            hintPane.setText(i18n(\"settings.advanced.custom_commands.hint\"));\n            GridPane.setColumnSpan(hintPane, 2);\n            pane.addRow(4, hintPane);\n\n            customCommandsPane.getContent().setAll(pane);\n        }\n\n        ComponentList jvmPane = new ComponentList();\n        {\n            GridPane pane = new GridPane();\n            ColumnConstraints title = new ColumnConstraints();\n            ColumnConstraints value = new ColumnConstraints();\n            value.setFillWidth(true);\n            value.setHgrow(Priority.ALWAYS);\n            pane.setHgap(16);\n            pane.setVgap(8);\n            pane.getColumnConstraints().setAll(title, value);\n\n            txtJVMArgs = new JFXTextField();\n            txtJVMArgs.getStyleClass().add(\"fit-width\");\n            pane.addRow(0, new Label(i18n(\"settings.advanced.jvm_args\")), txtJVMArgs);\n\n            HintPane hintPane = new HintPane();\n            hintPane.setText(i18n(\"settings.advanced.jvm_args.prompt\"));\n            GridPane.setColumnSpan(hintPane, 2);\n            pane.addRow(4, hintPane);\n\n            txtMetaspace = new JFXTextField();\n            txtMetaspace.setPromptText(i18n(\"settings.advanced.java_permanent_generation_space.prompt\"));\n            txtMetaspace.getStyleClass().add(\"fit-width\");\n            FXUtils.setValidateWhileTextChanged(txtMetaspace, true);\n            txtMetaspace.setValidators(new NumberValidator(i18n(\"input.number\"), true));\n            pane.addRow(1, new Label(i18n(\"settings.advanced.java_permanent_generation_space\")), txtMetaspace);\n\n            txtEnvironmentVariables = new JFXTextField();\n            txtEnvironmentVariables.getStyleClass().add(\"fit-width\");\n            pane.addRow(2, new Label(i18n(\"settings.advanced.environment_variables\")), txtEnvironmentVariables);\n\n            jvmPane.getContent().setAll(pane);\n        }\n\n        ComponentList workaroundPane = new ComponentList();\n\n        HintPane workaroundWarning = new HintPane(MessageDialogPane.MessageType.WARNING);\n        workaroundWarning.setText(i18n(\"settings.advanced.workaround.warning\"));\n\n        {\n            nativesDirItem = new MultiFileItem<>();\n            nativesDirSublist = new ComponentSublist();\n            nativesDirSublist.getContent().add(nativesDirItem);\n            nativesDirSublist.setTitle(i18n(\"settings.advanced.natives_directory\"));\n            nativesDirSublist.setHasSubtitle(true);\n            nativesDirCustomOption = new MultiFileItem.FileOption<>(i18n(\"settings.advanced.natives_directory.custom\"), NativesDirectoryType.CUSTOM)\n                    .setChooserTitle(i18n(\"settings.advanced.natives_directory.choose\"))\n                    .setDirectory(true);\n            nativesDirItem.loadChildren(Arrays.asList(\n                    new MultiFileItem.Option<>(i18n(\"settings.advanced.natives_directory.default\"), NativesDirectoryType.VERSION_FOLDER),\n                    nativesDirCustomOption\n            ));\n            HintPane nativesDirHint = new HintPane(MessageDialogPane.MessageType.WARNING);\n            nativesDirHint.setText(i18n(\"settings.advanced.natives_directory.hint\"));\n            nativesDirItem.getChildren().add(nativesDirHint);\n\n            rendererPane = new LineSelectButton<>();\n            rendererPane.setTitle(i18n(\"settings.advanced.renderer\"));\n            rendererPane.setConverter(e -> i18n(\"settings.advanced.renderer.\" + e.name().toLowerCase(Locale.ROOT)));\n            rendererPane.setDescriptionConverter(e -> {\n                String bundleKey = \"settings.advanced.renderer.\" + e.name().toLowerCase(Locale.ROOT) + \".desc\";\n                return I18n.hasKey(bundleKey) ? i18n(bundleKey) : null;\n            });\n            rendererPane.setItems(Renderer.values());\n\n            noJVMArgsPane = new LineToggleButton();\n            noJVMArgsPane.setTitle(i18n(\"settings.advanced.no_jvm_args\"));\n\n            noOptimizingJVMArgsPane = new LineToggleButton();\n            noOptimizingJVMArgsPane.setTitle(i18n(\"settings.advanced.no_optimizing_jvm_args\"));\n            noOptimizingJVMArgsPane.disableProperty().bind(noJVMArgsPane.selectedProperty());\n\n            noGameCheckPane = new LineToggleButton();\n            noGameCheckPane.setTitle(i18n(\"settings.advanced.dont_check_game_completeness\"));\n\n            noJVMCheckPane = new LineToggleButton();\n            noJVMCheckPane.setTitle(i18n(\"settings.advanced.dont_check_jvm_validity\"));\n\n            noNativesPatchPane = new LineToggleButton();\n            noNativesPatchPane.setTitle(i18n(\"settings.advanced.dont_patch_natives\"));\n\n            useNativeGLFWPane = new LineToggleButton();\n            useNativeGLFWPane.setTitle(i18n(\"settings.advanced.use_native_glfw\"));\n\n            useNativeOpenALPane = new LineToggleButton();\n            useNativeOpenALPane.setTitle(i18n(\"settings.advanced.use_native_openal\"));\n\n            workaroundPane.getContent().setAll(\n                    nativesDirSublist, rendererPane, noJVMArgsPane, noOptimizingJVMArgsPane, noGameCheckPane,\n                    noJVMCheckPane, noNativesPatchPane\n            );\n\n            if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {\n                workaroundPane.getContent().addAll(useNativeGLFWPane, useNativeOpenALPane);\n            } else {\n                ComponentSublist unsupportedOptionsSublist = new ComponentSublist();\n                unsupportedOptionsSublist.setTitle(i18n(\"settings.advanced.unsupported_system_options\"));\n                unsupportedOptionsSublist.getContent().addAll(useNativeGLFWPane, useNativeOpenALPane);\n                workaroundPane.getContent().add(unsupportedOptionsSublist);\n            }\n        }\n\n        rootPane.getChildren().addAll(\n                ComponentList.createComponentListTitle(i18n(\"settings.advanced.custom_commands\")), customCommandsPane,\n                ComponentList.createComponentListTitle(i18n(\"settings.advanced.jvm\")), jvmPane,\n                ComponentList.createComponentListTitle(i18n(\"settings.advanced.workaround\")), workaroundWarning, workaroundPane\n        );\n\n        bindProperties();\n    }\n\n    void bindProperties() {\n        nativesDirCustomOption.bindBidirectional(versionSetting.nativesDirProperty());\n        FXUtils.bindString(txtJVMArgs, versionSetting.javaArgsProperty());\n        FXUtils.bindString(txtGameArgs, versionSetting.minecraftArgsProperty());\n        FXUtils.bindString(txtMetaspace, versionSetting.permSizeProperty());\n        FXUtils.bindString(txtEnvironmentVariables, versionSetting.environmentVariablesProperty());\n        FXUtils.bindString(txtWrapper, versionSetting.wrapperProperty());\n        FXUtils.bindString(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());\n        FXUtils.bindString(txtPostExitCommand, versionSetting.postExitCommandProperty());\n        rendererPane.valueProperty().bindBidirectional(versionSetting.rendererProperty());\n        noGameCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckGameProperty());\n        noJVMCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckJVMProperty());\n        noJVMArgsPane.selectedProperty().bindBidirectional(versionSetting.noJVMArgsProperty());\n        noOptimizingJVMArgsPane.selectedProperty().bindBidirectional(versionSetting.noOptimizingJVMArgsProperty());\n        noNativesPatchPane.selectedProperty().bindBidirectional(versionSetting.notPatchNativesProperty());\n        useNativeGLFWPane.selectedProperty().bindBidirectional(versionSetting.useNativeGLFWProperty());\n        useNativeOpenALPane.selectedProperty().bindBidirectional(versionSetting.useNativeOpenALProperty());\n\n        nativesDirItem.selectedDataProperty().bindBidirectional(versionSetting.nativesDirTypeProperty());\n        nativesDirSublist.subtitleProperty().bind(Bindings.createStringBinding(() -> {\n            if (versionSetting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {\n                String nativesDirName = \"natives-\" + Platform.SYSTEM_PLATFORM;\n                if (versionId == null) {\n                    return String.join(FileSystems.getDefault().getSeparator(),\n                            FileUtils.getAbsolutePath(profile.getRepository().getBaseDirectory().resolve(\"versions\")),\n                            i18n(\"settings.advanced.natives_directory.default.version_id\"),\n                            nativesDirName\n                    );\n                } else {\n                    return profile.getRepository().getVersionRoot(versionId)\n                            .toAbsolutePath().normalize()\n                            .resolve(nativesDirName)\n                            .toString();\n                }\n            } else if (versionSetting.getNativesDirType() == NativesDirectoryType.CUSTOM) {\n                return versionSetting.getNativesDir();\n            } else {\n                return null;\n            }\n        }, versionSetting.nativesDirProperty(), versionSetting.nativesDirTypeProperty()));\n    }\n\n    void unbindProperties() {\n        nativesDirCustomOption.valueProperty().unbindBidirectional(versionSetting.nativesDirProperty());\n        FXUtils.unbind(txtJVMArgs, versionSetting.javaArgsProperty());\n        FXUtils.unbind(txtGameArgs, versionSetting.minecraftArgsProperty());\n        FXUtils.unbind(txtMetaspace, versionSetting.permSizeProperty());\n        FXUtils.unbind(txtEnvironmentVariables, versionSetting.environmentVariablesProperty());\n        FXUtils.unbind(txtWrapper, versionSetting.wrapperProperty());\n        FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());\n        FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());\n        rendererPane.valueProperty().unbindBidirectional(versionSetting.rendererProperty());\n        noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());\n        noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());\n        noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());\n        noOptimizingJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noOptimizingJVMArgsProperty());\n        noNativesPatchPane.selectedProperty().unbindBidirectional(versionSetting.notPatchNativesProperty());\n        useNativeGLFWPane.selectedProperty().unbindBidirectional(versionSetting.useNativeGLFWProperty());\n        useNativeOpenALPane.selectedProperty().unbindBidirectional(versionSetting.useNativeOpenALProperty());\n\n        nativesDirItem.selectedDataProperty().unbindBidirectional(versionSetting.nativesDirTypeProperty());\n        nativesDirSublist.subtitleProperty().unbind();\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return stateProperty;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport javafx.beans.property.BooleanProperty;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.Skin;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.game.World;\nimport org.jackhuang.hmcl.mod.Datapack;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.ListPageBase;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.javafx.MappedObservableList;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Objects;\nimport java.util.function.Predicate;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> implements WorldManagePage.WorldRefreshable {\n    private final World world;\n    private final Datapack datapack;\n    final BooleanProperty readOnly;\n\n    public DatapackListPage(WorldManagePage worldManagePage) {\n        world = worldManagePage.getWorld();\n        datapack = new Datapack(world.getFile().resolve(\"datapacks\"));\n        setItems(MappedObservableList.create(datapack.getPacks(), DatapackListPageSkin.DatapackInfoObject::new));\n        readOnly = worldManagePage.readOnlyProperty();\n        FXUtils.applyDragListener(this, it -> Objects.equals(\"zip\", FileUtils.getExtension(it)),\n                this::installMultiDatapack, this::refresh);\n\n        refresh();\n    }\n\n    private void installMultiDatapack(List<Path> datapackPath) {\n        datapackPath.forEach(this::installSingleDatapack);\n        if (readOnly.get()) {\n            Controllers.showToast(i18n(\"datapack.reload.toast\"));\n        }\n    }\n\n    private void installSingleDatapack(Path datapack) {\n        try {\n            this.datapack.installPack(datapack, world.getGameVersion());\n        } catch (IOException | IllegalArgumentException e) {\n            LOG.warning(\"Unable to parse datapack file \" + datapack, e);\n        }\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new DatapackListPageSkin(this);\n    }\n\n    public void refresh() {\n        setLoading(true);\n        Task.runAsync(datapack::loadFromDir)\n                .withRunAsync(Schedulers.javafx(), () -> setLoading(false))\n                .start();\n    }\n\n    public void add() {\n        FileChooser chooser = new FileChooser();\n        chooser.setTitle(i18n(\"datapack.add.title\"));\n        chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n(\"extension.datapack\"), \"*.zip\"));\n        List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));\n\n        if (res != null) {\n            installMultiDatapack(res);\n        }\n\n        datapack.loadFromDir();\n    }\n\n    void removeSelected(ObservableList<DatapackListPageSkin.DatapackInfoObject> selectedItems) {\n        selectedItems.stream()\n                .map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)\n                .forEach(pack -> {\n                    try {\n                        datapack.deletePack(pack);\n                    } catch (IOException e) {\n                        // Fail to remove mods if the game is running or the datapack is absent.\n                        LOG.warning(\"Failed to delete datapack \\\"\" + pack.getId() + \"\\\"\", e);\n                    }\n                });\n    }\n\n    void enableSelected(ObservableList<DatapackListPageSkin.DatapackInfoObject> selectedItems) {\n        selectedItems.stream()\n                .map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)\n                .forEach(pack -> pack.setActive(true));\n    }\n\n    void disableSelected(ObservableList<DatapackListPageSkin.DatapackInfoObject> selectedItems) {\n        selectedItems.stream()\n                .map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)\n                .forEach(pack -> pack.setActive(false));\n    }\n\n    void openDataPackFolder() {\n        FXUtils.openFolder(datapack.getPath());\n    }\n\n    @NotNull Predicate<DatapackListPageSkin.DatapackInfoObject> updateSearchPredicate(String queryString) {\n        if (queryString.isBlank()) {\n            return dataPack -> true;\n        }\n\n        final Predicate<String> stringPredicate;\n        if (queryString.startsWith(\"regex:\")) {\n            try {\n                Pattern pattern = Pattern.compile(StringUtils.substringAfter(queryString, \"regex:\"));\n                stringPredicate = s -> s != null && pattern.matcher(s).find();\n            } catch (Exception e) {\n                return dataPack -> false;\n            }\n        } else {\n            String lowerCaseFilter = queryString.toLowerCase(Locale.ROOT);\n            stringPredicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerCaseFilter);\n        }\n\n        return dataPack -> {\n            String id = dataPack.getPackInfo().getId();\n            String description = dataPack.getPackInfo().getDescription().toString();\n            return stringPredicate.test(id) || stringPredicate.test(description);\n        };\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPageSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXCheckBox;\nimport com.jfoenix.controls.JFXListView;\nimport com.jfoenix.controls.JFXTextField;\nimport com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;\nimport javafx.animation.PauseTransition;\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.collections.transformation.FilteredList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.SelectionMode;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.image.Image;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.StackPane;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.mod.Datapack;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.ref.SoftReference;\nimport java.lang.ref.WeakReference;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Consumer;\nimport java.util.stream.IntStream;\n\nimport static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\nfinal class DatapackListPageSkin extends SkinBase<DatapackListPage> {\n\n    private final TransitionPane toolbarPane;\n    private final HBox searchBar;\n    private final HBox normalToolbar;\n    private final HBox selectingToolbar;\n    InvalidationListener updateBarByStateWeakListener;\n\n    private final JFXListView<DatapackInfoObject> listView;\n    private final FilteredList<DatapackInfoObject> filteredList;\n\n    private final BooleanProperty isSearching = new SimpleBooleanProperty(false);\n    private final BooleanProperty isSelecting = new SimpleBooleanProperty(false);\n    private final JFXTextField searchField;\n\n    private static final AtomicInteger lastShiftClickIndex = new AtomicInteger(-1);\n    final Consumer<Integer> toggleSelect;\n\n    DatapackListPageSkin(DatapackListPage skinnable) {\n        super(skinnable);\n\n        StackPane pane = new StackPane();\n        pane.setPadding(new Insets(10));\n        pane.getStyleClass().addAll(\"notice-pane\");\n\n        ComponentList root = new ComponentList();\n        root.getStyleClass().add(\"no-padding\");\n        listView = new JFXListView<>();\n        filteredList = new FilteredList<>(skinnable.getItems());\n\n        {\n            toolbarPane = new TransitionPane();\n            searchBar = new HBox();\n            normalToolbar = new HBox();\n            selectingToolbar = new HBox();\n\n            normalToolbar.getChildren().addAll(\n                    createToolbarButton2(i18n(\"button.refresh\"), SVG.REFRESH, skinnable::refresh),\n                    createToolbarButton2(i18n(\"datapack.add\"), SVG.ADD, skinnable::add),\n                    createToolbarButton2(i18n(\"button.reveal_dir\"), SVG.FOLDER_OPEN, skinnable::openDataPackFolder),\n                    createToolbarButton2(i18n(\"search\"), SVG.SEARCH, () -> isSearching.set(true))\n            );\n\n            JFXButton removeButton = createToolbarButton2(i18n(\"button.remove\"), SVG.DELETE_FOREVER, () -> {\n                Controllers.confirm(i18n(\"button.remove.confirm\"), i18n(\"button.remove\"), () -> {\n                    skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());\n                }, null);\n            });\n            JFXButton enableButton = createToolbarButton2(i18n(\"mods.enable\"), SVG.CHECK, () ->\n                    skinnable.enableSelected(listView.getSelectionModel().getSelectedItems()));\n            JFXButton disableButton = createToolbarButton2(i18n(\"mods.disable\"), SVG.CLOSE, () ->\n                    skinnable.disableSelected(listView.getSelectionModel().getSelectedItems()));\n            removeButton.disableProperty().bind(getSkinnable().readOnly);\n            enableButton.disableProperty().bind(getSkinnable().readOnly);\n            disableButton.disableProperty().bind(getSkinnable().readOnly);\n\n            selectingToolbar.getChildren().addAll(\n                    removeButton,\n                    enableButton,\n                    disableButton,\n                    createToolbarButton2(i18n(\"button.select_all\"), SVG.SELECT_ALL, () ->\n                            listView.getSelectionModel().selectRange(0, listView.getItems().size())),//reason for not using selectAll() is that selectAll() first clears all selected then selects all, causing the toolbar to flicker\n                    createToolbarButton2(i18n(\"button.cancel\"), SVG.CANCEL, () ->\n                            listView.getSelectionModel().clearSelection())\n            );\n\n            searchBar.setAlignment(Pos.CENTER);\n            searchBar.setPadding(new Insets(0, 5, 0, 5));\n            searchField = new JFXTextField();\n            searchField.setPromptText(i18n(\"search\"));\n            HBox.setHgrow(searchField, Priority.ALWAYS);\n            PauseTransition pause = new PauseTransition(Duration.millis(100));\n            pause.setOnFinished(e -> filteredList.setPredicate(skinnable.updateSearchPredicate(searchField.getText())));\n            searchField.textProperty().addListener((observable, oldValue, newValue) -> {\n                pause.setRate(1);\n                pause.playFromStart();\n            });\n            JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE,\n                    () -> {\n                        isSearching.set(false);\n                        searchField.clear();\n                    });\n            FXUtils.onEscPressed(searchField, closeSearchBar::fire);\n            searchBar.getChildren().addAll(searchField, closeSearchBar);\n\n            root.addEventHandler(KeyEvent.KEY_PRESSED, e -> {\n                if (e.getCode() == KeyCode.ESCAPE) {\n                    if (listView.getSelectionModel().getSelectedItem() != null) {\n                        listView.getSelectionModel().clearSelection();\n                        e.consume();\n                    }\n                }\n            });\n\n            FXUtils.onChangeAndOperate(listView.getSelectionModel().selectedItemProperty(),\n                    selectedItem -> isSelecting.set(selectedItem != null));\n            root.getContent().add(toolbarPane);\n\n            updateBarByStateWeakListener = FXUtils.observeWeak(() -> {\n                if (isSelecting.get()) {\n                    changeToolbar(selectingToolbar);\n                } else if (!isSelecting.get() && !isSearching.get()) {\n                    changeToolbar(normalToolbar);\n                } else {\n                    changeToolbar(searchBar);\n                }\n            }, isSearching, isSelecting);\n        }\n\n        {\n            SpinnerPane center = new SpinnerPane();\n            ComponentList.setVgrow(center, Priority.ALWAYS);\n            center.loadingProperty().bind(skinnable.loadingProperty());\n\n            listView.setCellFactory(x -> new DatapackInfoListCell(listView, getSkinnable().readOnly));\n            listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);\n            this.listView.setItems(filteredList);\n\n            // ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here\n            FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);\n\n            center.setContent(listView);\n            root.getContent().add(center);\n        }\n\n        toggleSelect = i -> {\n            if (listView.getSelectionModel().isSelected(i)) {\n                listView.getSelectionModel().clearSelection(i);\n            } else {\n                listView.getSelectionModel().select(i);\n            }\n        };\n\n        pane.getChildren().setAll(root);\n        getChildren().setAll(pane);\n    }\n\n    private void changeToolbar(HBox newToolbar) {\n        Node oldToolbar = toolbarPane.getCurrentNode();\n        if (newToolbar != oldToolbar) {\n            toolbarPane.setContent(newToolbar, ContainerAnimations.FADE);\n            if (newToolbar == searchBar) {\n                // search button click will get focus while searchField request focus, this cause conflict.\n                // Defer focus request to next pulse avoids this conflict.\n                Platform.runLater(searchField::requestFocus);\n            }\n        }\n    }\n\n    static class DatapackInfoObject extends RecursiveTreeObject<DatapackInfoObject> {\n        private final BooleanProperty activeProperty;\n        private final Datapack.Pack packInfo;\n\n        private SoftReference<CompletableFuture<Image>> iconCache;\n\n        DatapackInfoObject(Datapack.Pack packInfo) {\n            this.packInfo = packInfo;\n            this.activeProperty = packInfo.activeProperty();\n        }\n\n        String getTitle() {\n            return packInfo.getId();\n        }\n\n        String getSubtitle() {\n            return packInfo.getDescription().toString();\n        }\n\n        Datapack.Pack getPackInfo() {\n            return packInfo;\n        }\n\n        Image loadIcon() {\n            Image image = null;\n            Path imagePath;\n            if (this.getPackInfo().isDirectory()) {\n                imagePath = getPackInfo().getPath().resolve(\"pack.png\");\n                try {\n                    image = FXUtils.loadImage(imagePath, 64, 64, true, true);\n                } catch (Exception e) {\n                    LOG.warning(\"fail to load image, datapack path: \" + getPackInfo().getPath(), e);\n                    return FXUtils.newBuiltinImage(\"/assets/img/unknown_pack.png\");\n                }\n            } else {\n                try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(getPackInfo().getPath())) {\n                    imagePath = fs.getPath(\"/pack.png\");\n                    if (Files.exists(imagePath)) {\n                        image = FXUtils.loadImage(imagePath, 64, 64, true, true);\n                    }\n                } catch (Exception e) {\n                    LOG.warning(\"fail to load image, datapack path: \" + getPackInfo().getPath(), e);\n                    return FXUtils.newBuiltinImage(\"/assets/img/unknown_pack.png\");\n                }\n            }\n\n            if (image != null && !image.isError() && image.getWidth() > 0 && image.getHeight() > 0 &&\n                    Math.abs(image.getWidth() - image.getHeight()) < 1) {\n                return image;\n            } else {\n                return FXUtils.newBuiltinImage(\"/assets/img/unknown_pack.png\");\n            }\n        }\n\n        public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<DatapackInfoObject>> current) {\n            SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;\n            CompletableFuture<Image> imageFuture;\n            if (iconCache != null && (imageFuture = iconCache.get()) != null) {\n                Image image = imageFuture.getNow(null);\n                if (image != null) {\n                    imageContainer.setImage(image);\n                    return;\n                }\n            } else {\n                imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io());\n                this.iconCache = new SoftReference<>(imageFuture);\n            }\n            imageContainer.setImage(FXUtils.newBuiltinImage(\"/assets/img/unknown_pack.png\"));\n            imageFuture.thenAcceptAsync(image -> {\n                if (current != null) {\n                    ObjectProperty<DatapackInfoObject> infoObjectProperty = current.get();\n                    if (infoObjectProperty == null || infoObjectProperty.get() != this) {\n                        // The current ListCell has already switched to another object\n                        return;\n                    }\n                }\n                imageContainer.setImage(image);\n            }, Schedulers.javafx());\n\n        }\n    }\n\n    private final class DatapackInfoListCell extends MDListCell<DatapackInfoObject> {\n        final JFXCheckBox checkBox = new JFXCheckBox();\n        ImageContainer imageContainer = new ImageContainer(32);\n        final TwoLineListItem content = new TwoLineListItem();\n        BooleanProperty booleanProperty;\n\n        DatapackInfoListCell(JFXListView<DatapackInfoObject> listView, BooleanProperty isReadOnlyProperty) {\n            super(listView);\n\n            HBox container = new HBox(8);\n            container.setPickOnBounds(false);\n            container.setAlignment(Pos.CENTER_LEFT);\n            HBox.setHgrow(content, Priority.ALWAYS);\n            content.setMouseTransparent(true);\n            setSelectable();\n\n            checkBox.disableProperty().bind(isReadOnlyProperty);\n\n            imageContainer.setImage(FXUtils.newBuiltinImage(\"/assets/img/unknown_pack.png\"));\n\n            StackPane.setMargin(container, new Insets(8));\n            container.getChildren().setAll(checkBox, imageContainer, content);\n            getContainer().getChildren().setAll(container);\n\n            getContainer().getParent().addEventHandler(MouseEvent.MOUSE_PRESSED, mouseEvent -> handleSelect(this, mouseEvent));\n        }\n\n        @Override\n        protected void updateControl(DatapackInfoObject dataItem, boolean empty) {\n            if (empty) return;\n            content.setTitle(dataItem.getTitle());\n            content.setSubtitle(dataItem.getSubtitle());\n            if (booleanProperty != null) {\n                checkBox.selectedProperty().unbindBidirectional(booleanProperty);\n            }\n            checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.activeProperty);\n            dataItem.loadIcon(imageContainer, new WeakReference<>(this.itemProperty()));\n        }\n    }\n\n    public void handleSelect(DatapackInfoListCell cell, MouseEvent mouseEvent) {\n        if (cell.isEmpty()) {\n            mouseEvent.consume();\n            return;\n        }\n\n        if (mouseEvent.isShiftDown()) {\n            int currentIndex = cell.getIndex();\n            if (lastShiftClickIndex.get() == -1) {\n                lastShiftClickIndex.set(currentIndex);\n                toggleSelect.accept(cell.getIndex());\n            } else if (listView.getItems().size() >= lastShiftClickIndex.get() && !(lastShiftClickIndex.get() < -1)) {\n                if (cell.isSelected()) {\n                    IntStream.rangeClosed(\n                                    Math.min(lastShiftClickIndex.get(), currentIndex),\n                                    Math.max(lastShiftClickIndex.get(), currentIndex))\n                            .forEach(listView.getSelectionModel()::clearSelection);\n                } else {\n                    listView.getSelectionModel().selectRange(lastShiftClickIndex.get(), currentIndex);\n                    listView.getSelectionModel().select(currentIndex);\n                }\n                lastShiftClickIndex.set(-1);\n            } else {\n                lastShiftClickIndex.set(currentIndex);\n                listView.getSelectionModel().select(currentIndex);\n            }\n        } else {\n            toggleSelect.accept(cell.getIndex());\n        }\n        cell.requestFocus();\n        mouseEvent.consume();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXComboBox;\nimport com.jfoenix.controls.JFXListView;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.ObjectBinding;\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.image.Image;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.*;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.RemoteModRepository;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.net.URI;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;\nimport static org.jackhuang.hmcl.ui.FXUtils.stringConverter;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;\n\npublic class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {\n    protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();\n    private final BooleanProperty loading = new SimpleBooleanProperty(false);\n    private final BooleanProperty failed = new SimpleBooleanProperty(false);\n    private final boolean versionSelection;\n    private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();\n    private final IntegerProperty pageOffset = new SimpleIntegerProperty(0);\n    private final IntegerProperty pageCount = new SimpleIntegerProperty(-1);\n    private final ListProperty<RemoteMod> items = new SimpleListProperty<>(this, \"items\", FXCollections.observableArrayList());\n    private final ObservableList<String> versions = FXCollections.observableArrayList();\n    private final StringProperty selectedVersion = new SimpleStringProperty();\n    private final DownloadPage.DownloadCallback callback;\n    private boolean searchInitialized = false;\n    protected final BooleanProperty supportChinese = new SimpleBooleanProperty();\n    private final ObservableList<Node> actions = FXCollections.observableArrayList();\n    protected final ListProperty<String> downloadSources = new SimpleListProperty<>(this, \"downloadSources\", FXCollections.observableArrayList());\n    protected final StringProperty downloadSource = new SimpleStringProperty();\n    private final WeakListenerHolder listenerHolder = new WeakListenerHolder();\n    private int searchID = 0;\n    protected RemoteModRepository repository;\n    private final DownloadProvider downloadProvider;\n\n    private Runnable retrySearch;\n\n    public DownloadListPage(RemoteModRepository repository) {\n        this(repository, null, false);\n    }\n\n    public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback, boolean versionSelection) {\n        this.repository = repository;\n        this.callback = callback;\n        this.versionSelection = versionSelection;\n        this.downloadProvider = DownloadProviders.getDownloadProvider();\n    }\n\n    public DownloadProvider getDownloadProvider() {\n        return downloadProvider;\n    }\n\n    public ObservableList<Node> getActions() {\n        return actions;\n    }\n\n    @Override\n    public void loadVersion(Profile profile, String version) {\n        this.version.set(new Profile.ProfileVersion(profile, version));\n\n        setLoading(false);\n        setFailed(false);\n\n        if (!searchInitialized) {\n            searchInitialized = true;\n            search(\"\", null, 0, \"\", RemoteModRepository.SortType.POPULARITY);\n        }\n\n        if (versionSelection) {\n            versions.setAll(profile.getRepository().getDisplayVersions()\n                    .map(Version::getId)\n                    .collect(Collectors.toList()));\n            selectedVersion.set(profile.getSelectedVersion());\n        }\n    }\n\n    public boolean isFailed() {\n        return failed.get();\n    }\n\n    public BooleanProperty failedProperty() {\n        return failed;\n    }\n\n    public void setFailed(boolean failed) {\n        this.failed.set(failed);\n    }\n\n    public boolean isLoading() {\n        return loading.get();\n    }\n\n    public BooleanProperty loadingProperty() {\n        return loading;\n    }\n\n    public void setLoading(boolean loading) {\n        this.loading.set(loading);\n    }\n\n    public void selectVersion(String versionID) {\n        FXUtils.runInFX(() -> selectedVersion.set(versionID));\n    }\n\n    private void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, RemoteModRepository.SortType sort) {\n        retrySearch = null;\n        setLoading(true);\n        setFailed(false);\n\n        int currentSearchID = searchID = searchID + 1;\n        Task.supplyAsync(() -> {\n            Profile.ProfileVersion version = this.version.get();\n            if (StringUtils.isBlank(version.getVersion())) {\n                return userGameVersion;\n            } else {\n                return StringUtils.isNotBlank(version.getVersion())\n                        ? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse(\"\")\n                        : \"\";\n            }\n        }).thenApplyAsync(\n                gameVersion -> repository.search(downloadProvider, gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)\n        ).whenComplete(Schedulers.javafx(), (result, exception) -> {\n            if (searchID != currentSearchID) {\n                return;\n            }\n\n            setLoading(false);\n            if (exception == null) {\n                items.setAll(result.getResults().collect(Collectors.toList()));\n                pageCount.set(result.getTotalPages());\n                failed.set(false);\n            } else {\n                failed.set(true);\n                pageCount.set(-1);\n                retrySearch = () -> search(userGameVersion, category, pageOffset, searchFilter, sort);\n            }\n        }).executor(true);\n    }\n\n    protected String getLocalizedCategory(String category) {\n        return repository instanceof ModrinthRemoteModRepository\n                ? i18n(\"modrinth.category.\" + category)\n                : i18n(\"curse.category.\" + category);\n    }\n\n    protected boolean shouldDisplayCategory(String category) {\n        return !\"minecraft\".equals(category);\n    }\n\n    private String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {\n        return StringUtils.repeats(' ', category.indent * 4) +\n                (category.getCategory() == null\n                        ? i18n(\"curse.category.0\")\n                        : getLocalizedCategory(category.getCategory().getId()));\n    }\n\n    protected String getLocalizedOfficialPage() {\n        if (repository instanceof ModrinthRemoteModRepository) {\n            return i18n(\"mods.modrinth\");\n        } else {\n            return i18n(\"mods.curseforge\");\n        }\n    }\n\n    protected Profile.ProfileVersion getProfileVersion() {\n        if (versionSelection) {\n            return new Profile.ProfileVersion(version.get().getProfile(), selectedVersion.get());\n        } else {\n            return version.get();\n        }\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new ModDownloadListPageSkin(this);\n    }\n\n    private static class ModDownloadListPageSkin extends SkinBase<DownloadListPage> {\n        private final JFXListView<RemoteMod> listView = new JFXListView<>();\n        private final RemoteImageLoader iconLoader;\n\n        protected ModDownloadListPageSkin(DownloadListPage control) {\n            super(control);\n\n            listView.getStyleClass().add(\"no-horizontal-scrollbar\");\n\n            iconLoader = new RemoteImageLoader(control.downloadProvider) {\n                @Override\n                protected @NotNull Task<Image> createLoadTask(@NotNull List<URI> uris) {\n                    return FXUtils.getRemoteImageTask(uris, 80, 80, true, true);\n                }\n            };\n\n            BorderPane pane = new BorderPane();\n\n            GridPane searchPane = new GridPane();\n            pane.setTop(searchPane);\n            searchPane.getStyleClass().addAll(\"card\");\n            BorderPane.setMargin(searchPane, new Insets(10, 10, 0, 10));\n\n            ColumnConstraints nameColumn = new ColumnConstraints();\n            nameColumn.setMinWidth(USE_PREF_SIZE);\n            ColumnConstraints column1 = new ColumnConstraints();\n            column1.setHgrow(Priority.ALWAYS);\n            ColumnConstraints column2 = new ColumnConstraints();\n            column2.setHgrow(Priority.ALWAYS);\n            searchPane.getColumnConstraints().setAll(nameColumn, column1, nameColumn, column2);\n\n            searchPane.setHgap(16);\n            searchPane.setVgap(10);\n\n            {\n                int rowIndex = 0;\n\n                if (control.versionSelection || !control.downloadSources.isEmpty()) {\n                    searchPane.addRow(rowIndex);\n                    int columns = 0;\n                    Node lastNode = null;\n                    if (control.versionSelection) {\n                        JFXComboBox<String> versionsComboBox = new JFXComboBox<>();\n                        versionsComboBox.setMaxWidth(Double.MAX_VALUE);\n                        Bindings.bindContent(versionsComboBox.getItems(), control.versions);\n                        selectedItemPropertyFor(versionsComboBox).bindBidirectional(control.selectedVersion);\n\n                        searchPane.add(new Label(i18n(\"version\")), columns++, rowIndex);\n                        searchPane.add(lastNode = versionsComboBox, columns++, rowIndex);\n                    }\n\n                    if (control.downloadSources.getSize() > 1) {\n                        JFXComboBox<String> downloadSourceComboBox = new JFXComboBox<>();\n                        downloadSourceComboBox.setMaxWidth(Double.MAX_VALUE);\n                        downloadSourceComboBox.getItems().setAll(control.downloadSources.get());\n                        downloadSourceComboBox.setConverter(stringConverter(I18n::i18n));\n                        selectedItemPropertyFor(downloadSourceComboBox).bindBidirectional(control.downloadSource);\n\n                        searchPane.add(new Label(i18n(\"settings.launcher.download_source\")), columns++, rowIndex);\n                        searchPane.add(lastNode = downloadSourceComboBox, columns++, rowIndex);\n                    }\n\n                    if (columns == 2) {\n                        GridPane.setColumnSpan(lastNode, 3);\n                    }\n\n                    rowIndex++;\n                }\n\n                JFXTextField nameField = new JFXTextField();\n                nameField.setPromptText(getSkinnable().supportChinese.get() ? i18n(\"search.hint.chinese\") : i18n(\"search.hint.english\"));\n                if (getSkinnable().supportChinese.get()) {\n                    FXUtils.installFastTooltip(nameField, i18n(\"search.hint.chinese\"));\n                } else {\n                    FXUtils.installFastTooltip(nameField, i18n(\"search.hint.english\"));\n                }\n\n                JFXComboBox<String> gameVersionField = new JFXComboBox<>();\n                gameVersionField.setMaxWidth(Double.MAX_VALUE);\n                gameVersionField.setEditable(true);\n                gameVersionField.getItems().setAll(GameVersionNumber.getDefaultGameVersions());\n                Label lblGameVersion = new Label(i18n(\"world.game_version\"));\n                searchPane.addRow(rowIndex++, new Label(i18n(\"mods.name\")), nameField, lblGameVersion, gameVersionField);\n\n                ObjectBinding<Boolean> hasVersion = BindingMapping.of(getSkinnable().version)\n                        .map(version -> version.getVersion() == null);\n                lblGameVersion.managedProperty().bind(hasVersion);\n                lblGameVersion.visibleProperty().bind(hasVersion);\n                gameVersionField.managedProperty().bind(hasVersion);\n                gameVersionField.visibleProperty().bind(hasVersion);\n                FXUtils.installFastTooltip(gameVersionField, i18n(\"search.enter\"));\n\n                FXUtils.onChangeAndOperate(getSkinnable().version, version -> {\n                    if (StringUtils.isNotBlank(version.getVersion())) {\n                        GridPane.setColumnSpan(nameField, 3);\n                    } else {\n                        GridPane.setColumnSpan(nameField, 1);\n                    }\n                });\n\n                StackPane categoryStackPane = new StackPane();\n                JFXComboBox<CategoryIndented> categoryComboBox = new JFXComboBox<>();\n                categoryComboBox.getItems().setAll(CategoryIndented.ALL);\n                categoryStackPane.getChildren().setAll(categoryComboBox);\n                categoryComboBox.prefWidthProperty().bind(categoryStackPane.widthProperty());\n                categoryComboBox.getStyleClass().add(\"fit-width\");\n                categoryComboBox.setPromptText(i18n(\"mods.category\"));\n                categoryComboBox.getSelectionModel().select(0);\n                categoryComboBox.setConverter(stringConverter(getSkinnable()::getLocalizedCategoryIndent));\n                FXUtils.onChangeAndOperate(getSkinnable().downloadSource, downloadSource -> {\n                    categoryComboBox.getItems().setAll(CategoryIndented.ALL);\n                    categoryComboBox.getSelectionModel().select(0);\n\n                    Task.supplyAsync(() -> getSkinnable().repository.getCategories())\n                            .thenAcceptAsync(Schedulers.javafx(), categories -> {\n                                if (!Objects.equals(getSkinnable().downloadSource.get(), downloadSource)) {\n                                    return;\n                                }\n\n                                List<CategoryIndented> result = new ArrayList<>();\n                                result.add(CategoryIndented.ALL);\n                                for (RemoteModRepository.Category category : Lang.toIterable(categories)) {\n                                    resolveCategory(category, 0, result);\n                                }\n                                categoryComboBox.getItems().setAll(result);\n                                categoryComboBox.getSelectionModel().select(0);\n                            }).start();\n                });\n\n                StackPane sortStackPane = new StackPane();\n                JFXComboBox<RemoteModRepository.SortType> sortComboBox = new JFXComboBox<>();\n                sortStackPane.getChildren().setAll(sortComboBox);\n                sortComboBox.prefWidthProperty().bind(sortStackPane.widthProperty());\n                sortComboBox.getStyleClass().add(\"fit-width\");\n                sortComboBox.setConverter(stringConverter(sortType -> i18n(\"curse.sort.\" + sortType.name().toLowerCase(Locale.ROOT))));\n                sortComboBox.getItems().setAll(RemoteModRepository.SortType.values());\n                sortComboBox.getSelectionModel().select(0);\n                searchPane.addRow(rowIndex++, new Label(i18n(\"mods.category\")), categoryStackPane, new Label(i18n(\"search.sort\")), sortStackPane);\n\n                IntegerProperty filterID = new SimpleIntegerProperty(this, \"Filter ID\", 0);\n                IntegerProperty currentFilterID = new SimpleIntegerProperty(this, \"Current Filter ID\", -1);\n                EventHandler<ActionEvent> searchAction = e -> {\n                    iconLoader.clearInvalidCache();\n                    if (currentFilterID.get() != -1 && currentFilterID.get() != filterID.get()) {\n                        control.pageOffset.set(0);\n                    }\n                    currentFilterID.set(filterID.get());\n\n                    int pageOffset = control.pageOffset.get();\n                    getSkinnable().search(gameVersionField.getSelectionModel().getSelectedItem(),\n                            Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())\n                                    .map(CategoryIndented::getCategory)\n                                    .orElse(null),\n                            pageOffset == -1 ? 0 : pageOffset,\n                            nameField.getText(),\n                            sortComboBox.getSelectionModel().getSelectedItem());\n                };\n\n                control.listenerHolder.add(FXUtils.observeWeak(\n                        () -> filterID.set(filterID.get() + 1),\n\n                        control.downloadSource,\n                        gameVersionField.getSelectionModel().selectedItemProperty(),\n                        categoryComboBox.getSelectionModel().selectedItemProperty(),\n                        nameField.textProperty(),\n                        sortComboBox.getSelectionModel().selectedItemProperty()\n                ));\n\n                HBox actionsBox = new HBox(8);\n                GridPane.setColumnSpan(actionsBox, 4);\n                actionsBox.setAlignment(Pos.CENTER);\n                {\n                    AggregatedObservableList<Node> actions = new AggregatedObservableList<>();\n\n                    Holder<Runnable> changeButton = new Holder<>();\n\n                    JFXButton firstPageButton = FXUtils.newBorderButton(i18n(\"search.first_page\"));\n                    firstPageButton.setOnAction(event -> {\n                        control.pageOffset.set(0);\n                        searchAction.handle(event);\n                        changeButton.value.run();\n                    });\n\n                    JFXButton previousPageButton = FXUtils.newBorderButton(i18n(\"search.previous_page\"));\n                    previousPageButton.setOnAction(event -> {\n                        int pageOffset = control.pageOffset.get();\n                        if (pageOffset > 0) {\n                            control.pageOffset.set(pageOffset - 1);\n                            searchAction.handle(event);\n                            changeButton.value.run();\n                        }\n                    });\n\n                    Label pageDescription = new Label();\n                    pageDescription.textProperty().bind(Bindings.createStringBinding(() -> {\n                        int pageCount = control.pageCount.get();\n                        return i18n(\"search.page_n\", control.pageOffset.get() + 1, pageCount == -1 ? \"-\" : String.valueOf(pageCount));\n                    }, control.pageOffset, control.pageCount));\n\n                    JFXButton nextPageButton = FXUtils.newBorderButton(i18n(\"search.next_page\"));\n                    nextPageButton.setOnAction(event -> {\n                        int nv = control.pageOffset.get() + 1;\n                        if (nv < control.pageCount.get()) {\n                            control.pageOffset.set(nv);\n                            searchAction.handle(event);\n                            changeButton.value.run();\n                        }\n                    });\n\n                    JFXButton lastPageButton = FXUtils.newBorderButton(i18n(\"search.last_page\"));\n                    lastPageButton.setOnAction(event -> {\n                        control.pageOffset.set(control.pageCount.get() - 1);\n                        searchAction.handle(event);\n                        changeButton.value.run();\n                    });\n\n                    firstPageButton.setDisable(true);\n                    previousPageButton.setDisable(true);\n                    lastPageButton.setDisable(true);\n                    nextPageButton.setDisable(true);\n\n                    changeButton.value = () -> {\n                        int pageOffset = control.pageOffset.get();\n                        int pageCount = control.pageCount.get();\n\n                        boolean disableAll = pageCount >= -1 && pageCount <= 1;\n\n                        boolean disablePrevious = disableAll || pageOffset == 0;\n                        firstPageButton.setDisable(disablePrevious);\n                        previousPageButton.setDisable(disablePrevious);\n\n                        boolean disableNext = disableAll || pageOffset == pageCount - 1;\n                        nextPageButton.setDisable(disableNext);\n                        lastPageButton.setDisable(disableNext);\n\n                        listView.scrollTo(0);\n                    };\n\n                    FXUtils.onChange(control.pageCount, pageCountN -> {\n                        int pageCount = pageCountN.intValue();\n\n                        if (pageCount != -1) {\n                            if (control.pageOffset.get() + 1 >= pageCount) {\n                                control.pageOffset.set(pageCount - 1);\n                            }\n                        }\n\n                        changeButton.value.run();\n                    });\n\n                    FXUtils.onChange(control.pageOffset, pageOffsetN -> {\n                        changeButton.value.run();\n                    });\n\n                    Pane placeholder = new Pane();\n                    HBox.setHgrow(placeholder, Priority.SOMETIMES);\n\n                    JFXButton searchButton = FXUtils.newRaisedButton(i18n(\"search\"));\n                    searchButton.setOnAction(searchAction);\n\n                    actions.appendList(FXCollections.observableArrayList(firstPageButton, previousPageButton, pageDescription, nextPageButton, lastPageButton, placeholder, searchButton));\n                    actions.appendList(control.actions);\n                    Bindings.bindContent(actionsBox.getChildren(), actions.getAggregatedList());\n                }\n\n                searchPane.addRow(rowIndex++, actionsBox);\n\n                FXUtils.onChange(control.downloadSource, v -> searchAction.handle(null));\n                nameField.setOnAction(searchAction);\n                gameVersionField.setOnAction(searchAction);\n                categoryComboBox.setOnAction(searchAction);\n                sortComboBox.setOnAction(searchAction);\n            }\n\n            SpinnerPane spinnerPane = new SpinnerPane();\n            pane.setCenter(spinnerPane);\n            {\n                spinnerPane.loadingProperty().bind(getSkinnable().loadingProperty());\n                spinnerPane.failedReasonProperty().bind(Bindings.createStringBinding(() -> {\n                    if (getSkinnable().isFailed()) {\n                        return i18n(\"download.failed.refresh\");\n                    } else {\n                        return null;\n                    }\n                }, getSkinnable().failedProperty()));\n                spinnerPane.setOnFailedAction(e -> {\n                    if (getSkinnable().retrySearch != null) {\n                        getSkinnable().retrySearch.run();\n                    }\n                });\n\n                spinnerPane.setContent(listView);\n                Bindings.bindContent(listView.getItems(), getSkinnable().items);\n                listView.setSelectionModel(new NoneMultipleSelectionModel<>());\n                // ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here\n                ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);\n\n                listView.setCellFactory(x -> new ListCell<>() {\n                    private static final Insets PADDING = new Insets(9, 9, 0, 9);\n\n                    private final RipplerContainer graphic;\n                    private final StackPane wrapper = new StackPane();\n\n                    private final TwoLineListItem content = new TwoLineListItem();\n                    private final ImageContainer imageContainer = new ImageContainer(40);\n\n                    {\n                        setPadding(PADDING);\n\n                        HBox container = new HBox(8);\n                        container.setPadding(new Insets(8));\n                        container.setCursor(Cursor.HAND);\n                        container.setAlignment(Pos.CENTER_LEFT);\n\n                        imageContainer.setMouseTransparent(true);\n\n                        container.getChildren().setAll(imageContainer, content);\n                        HBox.setHgrow(content, Priority.ALWAYS);\n\n                        this.graphic = new RipplerContainer(container);\n                        wrapper.getChildren().setAll(this.graphic);\n                        wrapper.getStyleClass().add(\"card-no-padding\");\n\n                        FXUtils.onClicked(wrapper, () -> {\n                            RemoteMod item = getItem();\n                            if (item != null)\n                                Controllers.navigate(new DownloadPage(getSkinnable(), item, getSkinnable().getProfileVersion(), getSkinnable().callback));\n                        });\n\n                        setPrefWidth(0);\n\n                        FXUtils.limitCellWidth(listView, this);\n\n                    }\n\n                    @Override\n                    protected void updateItem(RemoteMod item, boolean empty) {\n                        super.updateItem(item, empty);\n                        if (empty || item == null) {\n                            setGraphic(null);\n                        } else {\n                            ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(getSkinnable().repository.getType()).getModByCurseForgeId(item.getSlug());\n                            content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : item.getTitle());\n                            content.setSubtitle(item.getDescription());\n                            content.getTags().clear();\n                            for (String category : item.getCategories()) {\n                                if (getSkinnable().shouldDisplayCategory(category))\n                                    content.addTag(getSkinnable().getLocalizedCategory(category));\n                            }\n                            iconLoader.load(imageContainer.imageProperty(), item.getIconUrl());\n                            setGraphic(wrapper);\n                        }\n                    }\n                });\n            }\n\n            getChildren().setAll(pane);\n        }\n\n        private static class CategoryIndented {\n            private static final CategoryIndented ALL = new CategoryIndented(0, null);\n\n            private final int indent;\n            private final RemoteModRepository.Category category;\n\n            public CategoryIndented(int indent, RemoteModRepository.Category category) {\n                this.indent = indent;\n                this.category = category;\n            }\n\n            public int getIndent() {\n                return indent;\n            }\n\n            public RemoteModRepository.Category getCategory() {\n                return category;\n            }\n        }\n\n        private static void resolveCategory(RemoteModRepository.Category category, int indent, List<CategoryIndented> result) {\n            result.add(new CategoryIndented(indent, category));\n            for (RemoteModRepository.Category subcategory : category.getSubcategories()) {\n                resolveCategory(subcategory, indent + 1, result);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.RemoteModRepository;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.SimpleMultimap;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.EnumMap;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class DownloadPage extends Control implements DecoratorPage {\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();\n    private final BooleanProperty loaded = new SimpleBooleanProperty(false);\n    private final BooleanProperty loading = new SimpleBooleanProperty(false);\n    private final BooleanProperty failed = new SimpleBooleanProperty(false);\n    private final RemoteModRepository repository;\n    private final ModTranslations translations;\n    private final RemoteMod addon;\n    private final ModTranslations.Mod mod;\n    private final Profile.ProfileVersion version;\n    private final DownloadCallback callback;\n    private final DownloadListPage page;\n\n    private SimpleMultimap<String, RemoteMod.Version, List<RemoteMod.Version>> versions;\n\n    public DownloadPage(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {\n        this.page = page;\n        this.repository = page.repository;\n        this.addon = addon;\n        this.translations = ModTranslations.getTranslationsByRepositoryType(repository.getType());\n        this.mod = translations.getModByCurseForgeId(addon.getSlug());\n        this.version = version;\n        this.callback = callback;\n        loadModVersions();\n\n        this.state.set(State.fromTitle(addon.getTitle()));\n    }\n\n    private void loadModVersions() {\n        setLoading(true);\n        setFailed(false);\n\n        Task.supplyAsync(() -> {\n            Stream<RemoteMod.Version> versions = addon.getData().loadVersions(repository, page.getDownloadProvider());\n            return sortVersions(versions);\n        }).whenComplete(Schedulers.javafx(), (result, exception) -> {\n            if (exception == null) {\n                this.versions = result;\n\n                loaded.set(true);\n                setFailed(false);\n            } else {\n                setFailed(true);\n            }\n            setLoading(false);\n        }).start();\n    }\n\n    private SimpleMultimap<String, RemoteMod.Version, List<RemoteMod.Version>> sortVersions(Stream<RemoteMod.Version> versions) {\n        SimpleMultimap<String, RemoteMod.Version, List<RemoteMod.Version>> classifiedVersions\n                = new SimpleMultimap<>(HashMap::new, ArrayList::new);\n        versions.forEach(version -> {\n            for (String gameVersion : version.getGameVersions()) {\n                classifiedVersions.put(gameVersion, version);\n            }\n        });\n\n        for (String gameVersion : classifiedVersions.keys()) {\n            List<RemoteMod.Version> versionList = classifiedVersions.get(gameVersion);\n            versionList.sort(Comparator.comparing(RemoteMod.Version::getDatePublished).reversed());\n        }\n        return classifiedVersions;\n    }\n\n    public RemoteMod getAddon() {\n        return addon;\n    }\n\n    public Profile.ProfileVersion getVersion() {\n        return version;\n    }\n\n    public boolean isLoading() {\n        return loading.get();\n    }\n\n    public BooleanProperty loadingProperty() {\n        return loading;\n    }\n\n    public void setLoading(boolean loading) {\n        this.loading.set(loading);\n    }\n\n    public boolean isFailed() {\n        return failed.get();\n    }\n\n    public BooleanProperty failedProperty() {\n        return failed;\n    }\n\n    public void setFailed(boolean failed) {\n        this.failed.set(failed);\n    }\n\n    public void download(RemoteMod mod, RemoteMod.Version file) {\n        if (this.callback == null) {\n            saveAs(mod, file);\n        } else {\n            this.callback.download(page.getDownloadProvider(), version.getProfile(), version.getVersion(), mod, file);\n        }\n    }\n\n    public void saveAs(RemoteMod mod, RemoteMod.Version file) {\n        String extension = StringUtils.substringAfterLast(file.getFile().getFilename(), '.');\n\n        FileChooser fileChooser = new FileChooser();\n        fileChooser.setTitle(i18n(\"button.save_as\"));\n        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"file\"), \"*.\" + extension));\n        fileChooser.setInitialFileName(file.getFile().getFilename());\n        Path dest = FileUtils.toPath(fileChooser.showSaveDialog(Controllers.getStage()));\n        if (dest == null) {\n            return;\n        }\n\n        Controllers.taskDialog(\n                Task.composeAsync(() -> {\n                    var task = new FileDownloadTask(file.getFile().getUrl(), dest, file.getFile().getIntegrityCheck());\n                    task.setName(file.getName());\n                    return task;\n                }),\n                i18n(\"message.downloading\"),\n                TaskCancellationAction.NORMAL);\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new ModDownloadPageSkin(this);\n    }\n\n    private static class ModDownloadPageSkin extends SkinBase<DownloadPage> {\n\n        protected ModDownloadPageSkin(DownloadPage control) {\n            super(control);\n\n            VBox pane = new VBox(8);\n            pane.getStyleClass().add(\"gray-background\");\n            pane.setPadding(new Insets(10));\n\n            HBox descriptionPane = new HBox(8);\n            descriptionPane.setMinHeight(Region.USE_PREF_SIZE);\n            descriptionPane.setAlignment(Pos.CENTER);\n            pane.getChildren().add(descriptionPane);\n            descriptionPane.getStyleClass().add(\"card-non-transparent\");\n            {\n                var imageContainer = new ImageContainer(40);\n                if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) {\n                    imageContainer.imageProperty().bind(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 80, 80, true, true));\n                }\n                descriptionPane.getChildren().add(imageContainer);\n\n                TwoLineListItem content = new TwoLineListItem();\n                HBox.setHgrow(content, Priority.ALWAYS);\n                ModTranslations.Mod mod = getSkinnable().translations.getModByCurseForgeId(getSkinnable().addon.getSlug());\n                content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : getSkinnable().addon.getTitle());\n                content.setSubtitle(getSkinnable().addon.getDescription());\n                content.getSubtitleLabel().setWrapText(true);\n                getSkinnable().addon.getCategories().stream()\n                        .map(category -> getSkinnable().page.getLocalizedCategory(category))\n                        .forEach(content::addTag);\n                descriptionPane.getChildren().add(content);\n\n                if (getSkinnable().mod != null) {\n                    JFXHyperlink openMcmodButton = new JFXHyperlink(i18n(\"mods.mcmod\"));\n                    openMcmodButton.setExternalLink(getSkinnable().translations.getMcmodUrl(getSkinnable().mod));\n                    descriptionPane.getChildren().add(openMcmodButton);\n                    openMcmodButton.setMinWidth(Region.USE_PREF_SIZE);\n                }\n\n                JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());\n                openUrlButton.setExternalLink(getSkinnable().addon.getPageUrl());\n                descriptionPane.getChildren().add(openUrlButton);\n                openUrlButton.setMinWidth(Region.USE_PREF_SIZE);\n            }\n\n            SpinnerPane spinnerPane = new SpinnerPane();\n            VBox.setVgrow(spinnerPane, Priority.ALWAYS);\n            pane.getChildren().add(spinnerPane);\n            {\n                spinnerPane.loadingProperty().bind(getSkinnable().loadingProperty());\n                spinnerPane.failedReasonProperty().bind(Bindings.createStringBinding(() -> {\n                    if (getSkinnable().isFailed()) {\n                        return i18n(\"download.failed.refresh\");\n                    } else {\n                        return null;\n                    }\n                }, getSkinnable().failedProperty()));\n                spinnerPane.setOnFailedAction(e -> getSkinnable().loadModVersions());\n\n                ComponentList list = new ComponentList();\n                ScrollPane scrollPane = new ScrollPane(list);\n                FXUtils.smoothScrolling(scrollPane);\n                scrollPane.setFitToWidth(true);\n                scrollPane.setFitToHeight(true);\n                FXUtils.setOverflowHidden(scrollPane, 8);\n                StackPane.setAlignment(scrollPane, Pos.TOP_CENTER);\n                spinnerPane.setContent(scrollPane);\n\n                FXUtils.onChangeAndOperate(control.loaded, loaded -> {\n                    if (control.versions == null) return;\n\n                    if (control.version.getProfile() != null && control.version.getVersion() != null) {\n                        HMCLGameRepository repository = control.version.getProfile().getRepository();\n                        Version game = repository.getResolvedPreservingPatchesVersion(control.version.getVersion());\n                        String gameVersion = repository.getGameVersion(game).orElse(null);\n\n                        if (gameVersion != null) {\n                            List<RemoteMod.Version> modVersions = control.versions.get(gameVersion);\n                            if (modVersions != null && !modVersions.isEmpty()) {\n                                Set<ModLoaderType> targetLoaders = LibraryAnalyzer.analyze(game, gameVersion).getModLoaders();\n\n                                resolve:\n                                for (RemoteMod.Version modVersion : modVersions) {\n                                    for (ModLoaderType loader : modVersion.getLoaders()) {\n                                        if (targetLoaders.contains(loader)) {\n                                            list.getContent().addAll(\n                                                    ComponentList.createComponentListTitle(i18n(\"mods.download.recommend\", gameVersion)),\n                                                    new ModItem(control.addon, modVersion, control)\n                                            );\n                                            break resolve;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    for (String gameVersion : control.versions.keys().stream()\n                            .sorted(Collections.reverseOrder(GameVersionNumber::compare))\n                            .toList()) {\n                        List<RemoteMod.Version> versions = control.versions.get(gameVersion);\n                        if (versions == null || versions.isEmpty()) {\n                            continue;\n                        }\n\n                        var sublist = new ComponentSublist(() -> {\n                            ArrayList<ModItem> items = new ArrayList<>(versions.size());\n                            for (RemoteMod.Version v : versions) {\n                                items.add(new ModItem(control.addon, v, control));\n                            }\n                            return items;\n                        });\n                        sublist.getStyleClass().add(\"no-padding\");\n                        sublist.setTitle(\"Minecraft \" + gameVersion);\n\n                        list.getContent().add(sublist);\n                    }\n                });\n            }\n\n            getChildren().setAll(pane);\n        }\n    }\n\n    private static final class DependencyModItem extends StackPane {\n        public static final EnumMap<RemoteMod.DependencyType, String> I18N_KEY = new EnumMap<>(Lang.mapOf(\n                Pair.pair(RemoteMod.DependencyType.EMBEDDED, \"mods.dependency.embedded\"),\n                Pair.pair(RemoteMod.DependencyType.OPTIONAL, \"mods.dependency.optional\"),\n                Pair.pair(RemoteMod.DependencyType.REQUIRED, \"mods.dependency.required\"),\n                Pair.pair(RemoteMod.DependencyType.TOOL, \"mods.dependency.tool\"),\n                Pair.pair(RemoteMod.DependencyType.INCLUDE, \"mods.dependency.include\"),\n                Pair.pair(RemoteMod.DependencyType.INCOMPATIBLE, \"mods.dependency.incompatible\"),\n                Pair.pair(RemoteMod.DependencyType.BROKEN, \"mods.dependency.broken\")\n        ));\n\n        DependencyModItem(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, DownloadCallback callback) {\n            HBox pane = new HBox(8);\n            pane.setPadding(new Insets(0, 8, 0, 8));\n            pane.setAlignment(Pos.CENTER_LEFT);\n            TwoLineListItem content = new TwoLineListItem();\n            HBox.setHgrow(content, Priority.ALWAYS);\n            var imageView = new ImageContainer(40);\n            pane.getChildren().setAll(imageView, content);\n\n            RipplerContainer container = new RipplerContainer(pane);\n            FXUtils.onClicked(container, () -> {\n                fireEvent(new DialogCloseEvent());\n                Controllers.navigate(new DownloadPage(page, addon, version, callback));\n            });\n            getChildren().setAll(container);\n\n            if (addon != RemoteMod.BROKEN) {\n                ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(page.repository.getType()).getModByCurseForgeId(addon.getSlug());\n                content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : addon.getTitle());\n                content.setSubtitle(addon.getDescription());\n                for (String category : addon.getCategories()) {\n                    if (page.shouldDisplayCategory(category))\n                        content.addTag(page.getLocalizedCategory(category));\n                }\n                if (StringUtils.isNotBlank(addon.getIconUrl())) {\n                    imageView.imageProperty().bind(FXUtils.newRemoteImage(addon.getIconUrl(), 80, 80, true, true));\n                }\n            } else {\n                content.setTitle(i18n(\"mods.broken_dependency.title\"));\n                content.setSubtitle(i18n(\"mods.broken_dependency.desc\"));\n                imageView.setImage(FXUtils.newBuiltinImage(\"/assets/img/icon@4x.png\"));\n            }\n        }\n    }\n\n    private static final class ModItem extends StackPane {\n\n        ModItem(RemoteMod mod, RemoteMod.Version dataItem, DownloadPage selfPage) {\n            VBox pane = new VBox(8);\n            pane.setPadding(new Insets(8, 0, 8, 0));\n\n            {\n                HBox descPane = new HBox(8);\n                descPane.setPadding(new Insets(0, 8, 0, 8));\n                descPane.setAlignment(Pos.CENTER_LEFT);\n                descPane.setMouseTransparent(true);\n\n                {\n                    StackPane graphicPane = new StackPane();\n                    TwoLineListItem content = new TwoLineListItem();\n                    HBox.setHgrow(content, Priority.ALWAYS);\n                    content.setTitle(dataItem.getName());\n                    content.setSubtitle(I18n.formatDateTime(dataItem.getDatePublished()));\n\n                    switch (dataItem.getVersionType()) {\n                        case Alpha:\n                            content.addTag(i18n(\"mods.channel.alpha\"));\n                            graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE.createIcon(24));\n                            break;\n                        case Beta:\n                            content.addTag(i18n(\"mods.channel.beta\"));\n                            graphicPane.getChildren().setAll(SVG.BETA_CIRCLE.createIcon(24));\n                            break;\n                        case Release:\n                            content.addTag(i18n(\"mods.channel.release\"));\n                            graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE.createIcon(24));\n                            break;\n                    }\n\n                    for (ModLoaderType modLoaderType : dataItem.getLoaders()) {\n                        switch (modLoaderType) {\n                            case FORGE:\n                                content.addTag(i18n(\"install.installer.forge\"));\n                                break;\n                            case CLEANROOM:\n                                content.addTag(i18n(\"install.installer.cleanroom\"));\n                                break;\n                            case NEO_FORGED:\n                                content.addTag(i18n(\"install.installer.neoforge\"));\n                                break;\n                            case FABRIC:\n                                content.addTag(i18n(\"install.installer.fabric\"));\n                                break;\n                            case LITE_LOADER:\n                                content.addTag(i18n(\"install.installer.liteloader\"));\n                                break;\n                            case QUILT:\n                                content.addTag(i18n(\"install.installer.quilt\"));\n                                break;\n                        }\n                    }\n\n                    descPane.getChildren().setAll(graphicPane, content);\n                }\n\n                pane.getChildren().add(descPane);\n            }\n\n            RipplerContainer container = new RipplerContainer(pane);\n            FXUtils.onClicked(container, () -> Controllers.dialog(new ModVersion(mod, dataItem, selfPage)));\n            getChildren().setAll(container);\n\n            // Workaround for https://github.com/HMCL-dev/HMCL/issues/2129\n            this.setMinHeight(50);\n        }\n    }\n\n    private static final class ModVersion extends JFXDialogLayout {\n        public ModVersion(RemoteMod mod, RemoteMod.Version version, DownloadPage selfPage) {\n            RemoteModRepository.Type type = selfPage.repository.getType();\n\n            String title = switch (type) {\n                case WORLD -> \"world.download.title\";\n                case MODPACK -> \"modpack.download.title\";\n                case RESOURCE_PACK -> \"resourcepack.download.title\";\n                case SHADER_PACK -> \"shaderpack.download.title\";\n                default -> \"mods.download.title\";\n            };\n            this.setHeading(new HBox(new Label(i18n(title, version.getName()))));\n\n            VBox box = new VBox(8);\n            box.setPadding(new Insets(8));\n            ModItem modItem = new ModItem(mod, version, selfPage);\n            modItem.setMouseTransparent(true); // Item is displayed for info, clicking shouldn't open the dialog again\n            box.getChildren().setAll(modItem);\n            SpinnerPane spinnerPane = new SpinnerPane();\n            ScrollPane scrollPane = new ScrollPane();\n            ComponentList dependenciesList = new ComponentList();\n            loadDependencies(version, selfPage, spinnerPane, dependenciesList);\n            spinnerPane.setOnFailedAction(e -> loadDependencies(version, selfPage, spinnerPane, dependenciesList));\n\n            scrollPane.setContent(dependenciesList);\n            scrollPane.setFitToWidth(true);\n            scrollPane.setFitToHeight(true);\n            FXUtils.smoothScrolling(scrollPane);\n            FXUtils.setOverflowHidden(scrollPane, 8);\n            spinnerPane.setContent(scrollPane);\n            box.getChildren().add(spinnerPane);\n            VBox.setVgrow(spinnerPane, Priority.SOMETIMES);\n\n            this.setBody(box);\n\n            JFXButton downloadButton = null;\n            if (selfPage.callback != null) {\n                downloadButton = new JFXButton(type == RemoteModRepository.Type.MODPACK ? i18n(\"install.modpack\") : i18n(\"mods.install\"));\n                downloadButton.getStyleClass().add(\"dialog-accept\");\n                downloadButton.setOnAction(e -> {\n                    if (type == RemoteModRepository.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {\n                        fireEvent(new DialogCloseEvent());\n                    }\n                    selfPage.download(mod, version);\n                });\n            }\n\n            JFXButton saveAsButton = new JFXButton(i18n(\"mods.save_as\"));\n            saveAsButton.getStyleClass().add(\"dialog-accept\");\n            saveAsButton.setOnAction(e -> {\n                if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {\n                    fireEvent(new DialogCloseEvent());\n                }\n                selfPage.saveAs(mod, version);\n            });\n\n            JFXButton cancelButton = new JFXButton(i18n(\"button.cancel\"));\n            cancelButton.getStyleClass().add(\"dialog-cancel\");\n            cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));\n\n            if (downloadButton == null) {\n                this.setActions(saveAsButton, cancelButton);\n            } else {\n                this.setActions(downloadButton, saveAsButton, cancelButton);\n            }\n\n            this.prefWidthProperty().bind(BindingMapping.of(Controllers.getStage().widthProperty()).map(w -> w.doubleValue() * 0.7));\n            this.prefHeightProperty().bind(BindingMapping.of(Controllers.getStage().heightProperty()).map(w -> w.doubleValue() * 0.7));\n\n            onEscPressed(this, cancelButton::fire);\n        }\n\n        private void loadDependencies(RemoteMod.Version version, DownloadPage selfPage, SpinnerPane spinnerPane, ComponentList dependenciesList) {\n            spinnerPane.setLoading(true);\n            Task.composeAsync(() -> {\n                // TODO: Massive tasks may cause OOM.\n                EnumMap<RemoteMod.DependencyType, List<Node>> dependencies = new EnumMap<>(RemoteMod.DependencyType.class);\n                List<Task<?>> queue = new ArrayList<>(version.getDependencies().size());\n                for (RemoteMod.Dependency dependency : version.getDependencies()) {\n                    if (dependency.getType() == RemoteMod.DependencyType.INCOMPATIBLE || dependency.getType() == RemoteMod.DependencyType.BROKEN) {\n                        continue;\n                    }\n\n                    if (!dependencies.containsKey(dependency.getType())) {\n                        List<Node> list = new ArrayList<>();\n                        Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(dependency.getType())));\n                        title.setPadding(new Insets(0, 8, 0, 8));\n                        list.add(title);\n                        dependencies.put(dependency.getType(), list);\n                    }\n\n                    queue.add(Task.supplyAsync(Schedulers.io(), () -> dependency.load(selfPage.page.getDownloadProvider()))\n                            .setSignificance(Task.TaskSignificance.MINOR)\n                            .thenAcceptAsync(Schedulers.javafx(), dep -> {\n                                if (dep == RemoteMod.BROKEN) {\n                                    return;\n                                }\n                                DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dep, selfPage.version, selfPage.callback);\n                                dependencies.get(dependency.getType()).add(dependencyModItem);\n                            })\n                            .setSignificance(Task.TaskSignificance.MINOR));\n                }\n\n                return Task.allOf(queue).thenSupplyAsync(() ->\n                        dependencies.values().stream().flatMap(Collection::stream).collect(Collectors.toList())\n                );\n            }).whenComplete(Schedulers.javafx(), (result, exception) -> {\n                spinnerPane.setLoading(false);\n                if (exception == null) {\n                    dependenciesList.getContent().setAll(result);\n                    spinnerPane.setFailedReason(null);\n                } else {\n                    dependenciesList.getContent().setAll();\n                    spinnerPane.setFailedReason(i18n(\"download.failed.refresh\"));\n                }\n            }).start();\n        }\n    }\n\n    @FunctionalInterface\n    public interface DownloadCallback {\n        void download(DownloadProvider downloadProvider, Profile profile, @Nullable String version, RemoteMod mod, RemoteMod.Version file);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport javafx.geometry.Pos;\nimport org.jackhuang.hmcl.event.Event;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.setting.VersionIconType;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.construct.AdvancedListItem;\nimport org.jackhuang.hmcl.ui.construct.ImageContainer;\n\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class GameAdvancedListItem extends AdvancedListItem {\n    private final ImageContainer imageContainer;\n    private final WeakListenerHolder holder = new WeakListenerHolder();\n    private Profile profile;\n    @SuppressWarnings(\"unused\")\n    private Consumer<Event> onVersionIconChangedListener;\n\n    public GameAdvancedListItem() {\n        this.imageContainer = new ImageContainer(LEFT_GRAPHIC_SIZE);\n        imageContainer.setMouseTransparent(true);\n        AdvancedListItem.setAlignment(imageContainer, Pos.CENTER);\n        setLeftGraphic(imageContainer);\n\n        holder.add(FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(), this::loadVersion));\n    }\n\n    private void loadVersion(String version) {\n        if (Profiles.getSelectedProfile() != profile) {\n            profile = Profiles.getSelectedProfile();\n            if (profile != null) {\n                onVersionIconChangedListener = profile.getRepository().onVersionIconChanged.registerWeak(event -> {\n                    this.loadVersion(Profiles.getSelectedVersion());\n                });\n            }\n        }\n        if (version != null && Profiles.getSelectedProfile() != null &&\n                Profiles.getSelectedProfile().getRepository().hasVersion(version)) {\n            setTitle(i18n(\"version.manage.manage\"));\n            setSubtitle(version);\n            imageContainer.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));\n        } else {\n            setTitle(i18n(\"version.empty\"));\n            setSubtitle(i18n(\"version.empty.add\"));\n            imageContainer.setImage(VersionIconType.DEFAULT.getIcon());\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport javafx.beans.property.*;\nimport javafx.scene.image.Image;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;\nimport static org.jackhuang.hmcl.util.Lang.threadPool;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class GameItem {\n    private static final ThreadPoolExecutor POOL_VERSION_RESOLVE = threadPool(\"VersionResolve\", true, 1, 10, TimeUnit.SECONDS);\n\n    protected final Profile profile;\n    protected final String id;\n\n    private boolean initialized = false;\n    private StringProperty title;\n    private StringProperty tag;\n    private StringProperty subtitle;\n    private ObjectProperty<Image> image;\n\n    public GameItem(Profile profile, String id) {\n        this.profile = profile;\n        this.id = id;\n    }\n\n    public Profile getProfile() {\n        return profile;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    private void init() {\n        if (initialized)\n            return;\n\n        initialized = true;\n        title = new SimpleStringProperty();\n        tag = new SimpleStringProperty();\n        subtitle = new SimpleStringProperty();\n        image = new SimpleObjectProperty<>();\n\n        record Result(@Nullable String gameVersion, @Nullable String tag) {\n        }\n\n        CompletableFuture.supplyAsync(() -> {\n            // GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms)\n            Optional<String> gameVersion = profile.getRepository().getGameVersion(id);\n            String modPackVersion = null;\n            try {\n                ModpackConfiguration<?> config = profile.getRepository().readModpackConfiguration(id);\n                modPackVersion = config != null ? config.getVersion() : null;\n            } catch (IOException e) {\n                LOG.warning(\"Failed to read modpack configuration from \" + id, e);\n            }\n            return new Result(gameVersion.orElse(null), modPackVersion);\n        }, POOL_VERSION_RESOLVE).whenCompleteAsync((result, exception) -> {\n            if (exception == null) {\n                if (result.tag != null) {\n                    tag.set(result.tag);\n                }\n\n                StringBuilder libraries = new StringBuilder(Objects.requireNonNullElse(result.gameVersion, i18n(\"message.unknown\")));\n                LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id), result.gameVersion);\n                for (LibraryAnalyzer.LibraryMark mark : analyzer) {\n                    String libraryId = mark.getLibraryId();\n                    String libraryVersion = mark.getLibraryVersion();\n                    if (libraryId.equals(MINECRAFT.getPatchId())) continue;\n                    if (I18n.hasKey(\"install.installer.\" + libraryId)) {\n                        libraries.append(\", \").append(i18n(\"install.installer.\" + libraryId));\n                        if (libraryVersion != null)\n                            libraries.append(\": \").append(libraryVersion.replaceAll(\"(?i)\" + libraryId, \"\"));\n                    }\n                }\n\n                subtitle.set(libraries.toString());\n            } else {\n                LOG.warning(\"Failed to read version info from \" + id, exception);\n            }\n        }, Schedulers.javafx());\n\n        title.set(id);\n        image.set(profile.getRepository().getVersionIconImage(id));\n    }\n\n    public ReadOnlyStringProperty titleProperty() {\n        init();\n        return title;\n    }\n\n    public ReadOnlyStringProperty tagProperty() {\n        init();\n        return tag;\n    }\n\n    public ReadOnlyStringProperty subtitleProperty() {\n        init();\n        return subtitle;\n    }\n\n    public ReadOnlyObjectProperty<Image> imageProperty() {\n        init();\n        return image;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListCell.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.*;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.event.ActionEvent;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Region;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class GameListCell extends ListCell<GameListItem> {\n\n    private final Region graphic;\n\n    private final ImageContainer imageView;\n    private final TwoLineListItem content;\n\n    private final JFXRadioButton chkSelected;\n    private final JFXButton btnUpgrade;\n    private final JFXButton btnLaunch;\n    private final JFXButton btnManage;\n\n    private final HBox right;\n\n    private final StringProperty tag = new SimpleStringProperty();\n\n    public GameListCell() {\n        BorderPane root = new BorderPane();\n        root.getStyleClass().add(\"md-list-cell\");\n        root.setPadding(new Insets(8, 8, 8, 0));\n\n        RipplerContainer container = new RipplerContainer(root);\n        this.graphic = container;\n\n        {\n            this.chkSelected = new JFXRadioButton() {\n                @Override\n                public void fire() {\n                    if (!isDisable() && !isSelected()) {\n                        fireEvent(new ActionEvent());\n                        GameListItem item = GameListCell.this.getItem();\n                        if (item != null) {\n                            item.getProfile().setSelectedVersion(item.getId());\n                        }\n                    }\n                }\n            };\n            root.setLeft(chkSelected);\n            BorderPane.setAlignment(chkSelected, Pos.CENTER);\n        }\n\n        {\n            HBox center = new HBox();\n            center.setMouseTransparent(true);\n            root.setCenter(center);\n            center.setPrefWidth(Region.USE_PREF_SIZE);\n            center.setSpacing(8);\n            center.setAlignment(Pos.CENTER_LEFT);\n\n            this.imageView = new ImageContainer(32);\n\n            this.content = new TwoLineListItem();\n            BorderPane.setAlignment(content, Pos.CENTER);\n\n            FXUtils.onChangeAndOperate(tag, tag -> {\n                content.getTags().clear();\n                if (StringUtils.isNotBlank(tag))\n                    content.addTag(tag);\n            });\n\n            center.getChildren().setAll(imageView, content);\n        }\n\n        {\n            this.right = new HBox();\n            root.setRight(right);\n\n            right.setAlignment(Pos.CENTER_RIGHT);\n\n            this.btnUpgrade = FXUtils.newToggleButton4(SVG.UPDATE);\n            btnUpgrade.setOnAction(e -> {\n                GameListItem item = this.getItem();\n                if (item != null)\n                    item.update();\n            });\n            FXUtils.installFastTooltip(btnUpgrade, i18n(\"version.update\"));\n            right.getChildren().add(btnUpgrade);\n\n            this.btnLaunch = FXUtils.newToggleButton4(SVG.ROCKET_LAUNCH);\n            btnLaunch.setOnAction(e -> {\n                GameListItem item = this.getItem();\n                if (item != null)\n                    item.testGame();\n            });\n            BorderPane.setAlignment(btnLaunch, Pos.CENTER);\n            FXUtils.installFastTooltip(btnLaunch, i18n(\"version.launch.test\"));\n            right.getChildren().add(btnLaunch);\n\n            this.btnManage = FXUtils.newToggleButton4(SVG.MORE_VERT);\n            btnManage.setOnAction(e -> {\n                GameListItem item = this.getItem();\n                if (item == null)\n                    return;\n\n                JFXPopup popup = getPopup(item);\n                JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);\n                popup.show(root, vPosition, JFXPopup.PopupHPosition.RIGHT, 0, vPosition == JFXPopup.PopupVPosition.TOP ? root.getHeight() : -root.getHeight());\n            });\n            BorderPane.setAlignment(btnManage, Pos.CENTER);\n            FXUtils.installFastTooltip(btnManage, i18n(\"settings.game.management\"));\n            right.getChildren().add(btnManage);\n        }\n\n        root.setCursor(Cursor.HAND);\n        container.setOnMouseClicked(e -> {\n            GameListItem item = getItem();\n            if (item == null)\n                return;\n\n            if (e.getButton() == MouseButton.PRIMARY) {\n                if (e.getClickCount() == 1) {\n                    item.modifyGameSettings();\n                }\n            } else if (e.getButton() == MouseButton.SECONDARY) {\n                JFXPopup popup = getPopup(item);\n                JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);\n                popup.show(root, vPosition, JFXPopup.PopupHPosition.LEFT, e.getX(), vPosition == JFXPopup.PopupVPosition.TOP ? e.getY() : e.getY() - root.getHeight());\n            }\n        });\n    }\n\n    @Override\n    public void updateItem(GameListItem item, boolean empty) {\n        super.updateItem(item, empty);\n\n        this.imageView.imageProperty().unbind();\n        this.content.titleProperty().unbind();\n        this.content.subtitleProperty().unbind();\n        this.tag.unbind();\n        this.right.getChildren().clear();\n        this.chkSelected.selectedProperty().unbind();\n\n        if (empty || item == null) {\n            setGraphic(null);\n        } else {\n            setGraphic(this.graphic);\n\n            this.chkSelected.selectedProperty().bind(item.selectedProperty());\n            this.imageView.imageProperty().bind(item.imageProperty());\n            this.content.titleProperty().bind(item.titleProperty());\n            this.content.subtitleProperty().bind(item.subtitleProperty());\n            this.tag.bind(item.tagProperty());\n            if (item.canUpdate())\n                this.right.getChildren().add(btnUpgrade);\n            this.right.getChildren().addAll(btnLaunch, btnManage);\n        }\n    }\n\n    private static JFXPopup getPopup(GameListItem item) {\n        PopupMenu menu = new PopupMenu();\n        JFXPopup popup = new JFXPopup(menu);\n\n        menu.getContent().setAll(\n                new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n(\"version.launch.test\"), item::testGame, popup),\n                new IconedMenuItem(SVG.SCRIPT, i18n(\"version.launch_script\"), item::generateLaunchScript, popup),\n                new MenuSeparator(),\n                new IconedMenuItem(SVG.SETTINGS, i18n(\"version.manage.manage\"), item::modifyGameSettings, popup),\n                new MenuSeparator(),\n                new IconedMenuItem(SVG.EDIT, i18n(\"version.manage.rename\"), item::rename, popup),\n                new IconedMenuItem(SVG.FOLDER_COPY, i18n(\"version.manage.duplicate\"), item::duplicate, popup),\n                new IconedMenuItem(SVG.DELETE, i18n(\"version.manage.remove\"), item::remove, popup),\n                new IconedMenuItem(SVG.OUTPUT, i18n(\"modpack.export\"), item::export, popup),\n                new MenuSeparator(),\n                new IconedMenuItem(SVG.FOLDER_OPEN, i18n(\"folder.game\"), item::browse, popup));\n        return popup;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ReadOnlyBooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport org.jackhuang.hmcl.setting.Profile;\n\npublic class GameListItem extends GameItem {\n    private final boolean isModpack;\n    private final BooleanProperty selected = new SimpleBooleanProperty(this, \"selected\");\n\n    public GameListItem(Profile profile, String id) {\n        super(profile, id);\n        this.isModpack = profile.getRepository().isModpack(id);\n        selected.bind(profile.selectedVersionProperty().isEqualTo(id));\n    }\n\n    public ReadOnlyBooleanProperty selectedProperty() {\n        return selected;\n    }\n\n    public void rename() {\n        Versions.renameVersion(profile, id);\n    }\n\n    public void duplicate() {\n        Versions.duplicateVersion(profile, id);\n    }\n\n    public void remove() {\n        Versions.deleteVersion(profile, id);\n    }\n\n    public void export() {\n        Versions.exportVersion(profile, id);\n    }\n\n    public void browse() {\n        Versions.openFolder(profile, id);\n    }\n\n    public void testGame() {\n        Versions.testGame(profile, id);\n    }\n\n    public void launch() {\n        Versions.launch(profile, id);\n    }\n\n    public void modifyGameSettings() {\n        Versions.modifyGameSettings(profile, id);\n    }\n\n    public void generateLaunchScript() {\n        Versions.generateLaunchScript(profile, id);\n    }\n\n    public boolean canUpdate() {\n        return isModpack;\n    }\n\n    public void update() {\n        Versions.updateVersion(profile, id);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXListView;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.animation.PauseTransition;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.*;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.collections.transformation.FilteredList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.*;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.Profiles;\nimport org.jackhuang.hmcl.ui.*;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.AdvancedListBox;\nimport org.jackhuang.hmcl.ui.construct.AdvancedListItem;\nimport org.jackhuang.hmcl.ui.construct.ComponentList;\nimport org.jackhuang.hmcl.ui.construct.SpinnerPane;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.ui.profile.ProfileListItem;\nimport org.jackhuang.hmcl.ui.profile.ProfilePage;\nimport org.jackhuang.hmcl.util.FXThread;\nimport org.jackhuang.hmcl.util.javafx.MappedObservableList;\n\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.function.Predicate;\nimport java.util.regex.Pattern;\nimport java.util.regex.PatternSyntaxException;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.*;\nimport static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;\n\npublic class GameListPage extends DecoratorAnimatedPage implements DecoratorPage {\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n(\"version.manage\")));\n    private final ListProperty<Profile> profiles = new SimpleListProperty<>(FXCollections.observableArrayList());\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private final ObservableList<ProfileListItem> profileListItems;\n    private final ObjectProperty<Profile> selectedProfile;\n\n    public GameListPage() {\n        profileListItems = MappedObservableList.create(profilesProperty(), profile -> {\n            ProfileListItem item = new ProfileListItem(profile);\n            FXUtils.setLimitWidth(item, 200);\n            return item;\n        });\n        selectedProfile = createSelectedItemPropertyFor(profileListItems, Profile.class);\n\n        {\n            ScrollPane pane = new ScrollPane();\n            VBox.setVgrow(pane, Priority.ALWAYS);\n            {\n                AdvancedListItem addProfileItem = new AdvancedListItem();\n                addProfileItem.getStyleClass().add(\"navigation-drawer-item\");\n                addProfileItem.setTitle(i18n(\"profile.new\"));\n                addProfileItem.setLeftIcon(SVG.ADD_CIRCLE);\n                addProfileItem.setOnAction(e -> Controllers.navigate(new ProfilePage(null)));\n\n                pane.setFitToWidth(true);\n                VBox wrapper = new VBox();\n                wrapper.getStyleClass().add(\"advanced-list-box-content\");\n                VBox box = new VBox();\n                box.setFillWidth(true);\n                Bindings.bindContent(box.getChildren(), profileListItems);\n                wrapper.getChildren().setAll(box, addProfileItem);\n                pane.setContent(wrapper);\n            }\n\n            AdvancedListBox bottomLeftCornerList = new AdvancedListBox()\n                    .addNavigationDrawerItem(i18n(\"install.new_game\"), SVG.ADD_CIRCLE, Versions::addNewGame)\n                    .addNavigationDrawerItem(i18n(\"install.modpack\"), SVG.PACKAGE2, Versions::importModpack)\n                    .addNavigationDrawerItem(i18n(\"settings.type.global.manage\"), SVG.SETTINGS, this::modifyGlobalGameSettings);\n            FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 3 + 12 * 2);\n            setLeft(pane, bottomLeftCornerList);\n        }\n\n        setCenter(new GameList());\n    }\n\n    public ObjectProperty<Profile> selectedProfileProperty() {\n        return selectedProfile;\n    }\n\n    public ObservableList<Profile> getProfiles() {\n        return profiles.get();\n    }\n\n    public ListProperty<Profile> profilesProperty() {\n        return profiles;\n    }\n\n    public void setProfiles(ObservableList<Profile> profiles) {\n        this.profiles.set(profiles);\n    }\n\n    public void modifyGlobalGameSettings() {\n        Versions.modifyGlobalSettings(Profiles.getSelectedProfile());\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n\n    private static class GameList extends ListPageBase<GameListItem> {\n        private final WeakListenerHolder listenerHolder = new WeakListenerHolder();\n\n        private final ObservableList<GameListItem> sourceList = FXCollections.observableArrayList();\n        private final FilteredList<GameListItem> filteredList = new FilteredList<>(sourceList);\n\n        public GameList() {\n            setItems(filteredList);\n\n            Profiles.registerVersionsListener(this::loadVersions);\n\n            setOnFailedAction(e -> Controllers.navigate(Controllers.getDownloadPage()));\n        }\n\n        @FXThread\n        private void loadVersions(Profile profile) {\n            listenerHolder.clear();\n            setLoading(true);\n            setFailedReason(null);\n\n            List<GameListItem> versionItems = profile.getRepository().getDisplayVersions().map(instance -> new GameListItem(profile, instance.getId())).toList();\n\n            sourceList.setAll(versionItems);\n\n            if (versionItems.isEmpty()) {\n                setFailedReason(i18n(\"version.empty.hint\"));\n            }\n\n            setLoading(false);\n        }\n\n        private Predicate<GameListItem> createPredicate(String searchText) {\n            if (searchText == null || searchText.isEmpty()) {\n                return item -> true;\n            }\n\n            if (searchText.startsWith(\"regex:\")) {\n                String regex = searchText.substring(\"regex:\".length());\n                try {\n                    Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);\n                    return item -> pattern.matcher(item.id).find();\n                } catch (PatternSyntaxException e) {\n                    return item -> false;\n                }\n            } else {\n                return item -> item.id.toLowerCase(Locale.ROOT).contains(searchText.toLowerCase(Locale.ROOT));\n            }\n        }\n\n        public void refreshList() {\n            Profiles.getSelectedProfile().getRepository().refreshVersionsAsync().start();\n        }\n\n        @Override\n        protected Skin<?> createDefaultSkin() {\n            return new GameListSkin(this);\n        }\n\n        private static class GameListSkin extends SkinBase<GameList> {\n            private final TransitionPane toolbarPane;\n            private final HBox searchBar;\n            private final HBox toolbarNormal;\n\n            private final JFXTextField searchField;\n\n            public GameListSkin(GameList skinnable) {\n                super(skinnable);\n\n                StackPane pane = new StackPane();\n                pane.setPadding(new Insets(10));\n                pane.getStyleClass().addAll(\"notice-pane\");\n\n                ComponentList root = new ComponentList();\n                root.getStyleClass().add(\"no-padding\");\n                JFXListView<GameListItem> listView = new JFXListView<>();\n\n                {\n                    toolbarPane = new TransitionPane();\n\n                    searchBar = new HBox();\n                    toolbarNormal = new HBox();\n\n                    searchBar.setAlignment(Pos.CENTER);\n                    searchBar.setPadding(new Insets(0, 5, 0, 5));\n                    searchField = new JFXTextField();\n                    searchField.setPromptText(i18n(\"search\"));\n                    HBox.setHgrow(searchField, Priority.ALWAYS);\n                    PauseTransition pause = new PauseTransition(Duration.millis(100));\n                    pause.setOnFinished(e -> skinnable.filteredList.setPredicate(skinnable.createPredicate(searchField.getText())));\n                    searchField.textProperty().addListener((observable, oldValue, newValue) -> {\n                        pause.setRate(1);\n                        pause.playFromStart();\n                    });\n\n                    JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, () -> {\n                        changeToolbar(toolbarNormal);\n                        searchField.clear();\n                    });\n\n                    onEscPressed(searchField, closeSearchBar::fire);\n\n                    searchBar.getChildren().setAll(searchField, closeSearchBar);\n\n                    toolbarNormal.getChildren().setAll(createToolbarButton2(i18n(\"button.refresh\"), SVG.REFRESH, skinnable::refreshList), createToolbarButton2(i18n(\"search\"), SVG.SEARCH, () -> changeToolbar(searchBar)));\n\n                    toolbarPane.setContent(toolbarNormal, ContainerAnimations.FADE);\n\n                    root.getContent().add(toolbarPane);\n                }\n\n                {\n                    SpinnerPane center = new SpinnerPane();\n                    ComponentList.setVgrow(center, Priority.ALWAYS);\n                    center.loadingProperty().bind(skinnable.loadingProperty());\n                    center.failedReasonProperty().bind(skinnable.failedReasonProperty());\n\n                    listView.setCellFactory(x -> new GameListCell());\n                    listView.setItems(skinnable.getItems());\n\n                    ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);\n\n                    center.setContent(listView);\n                    root.getContent().add(center);\n                }\n\n                pane.getChildren().setAll(root);\n                getChildren().setAll(pane);\n            }\n\n            private void changeToolbar(HBox newToolbar) {\n                Node oldToolbar = toolbarPane.getCurrentNode();\n                if (newToolbar != oldToolbar) {\n                    toolbarPane.setContent(newToolbar, ContainerAnimations.FADE);\n                    if (newToolbar == searchBar) {\n                        runInFX(searchField::requestFocus);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPopupMenu.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXListView;\nimport com.jfoenix.controls.JFXPopup;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.ListView;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.StackPane;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.ImageContainer;\nimport org.jackhuang.hmcl.ui.construct.RipplerContainer;\nimport org.jackhuang.hmcl.ui.construct.TwoLineListItem;\nimport org.jackhuang.hmcl.util.StringUtils;\n\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\n/// @author Glavo\npublic final class GameListPopupMenu extends StackPane {\n\n    public static void show(Node owner, JFXPopup.PopupVPosition vAlign, JFXPopup.PopupHPosition hAlign,\n                            double initOffsetX, double initOffsetY,\n                            Profile profile, List<Version> versions) {\n        GameListPopupMenu menu = new GameListPopupMenu();\n        menu.getItems().setAll(versions.stream().map(it -> new GameItem(profile, it.getId())).toList());\n        JFXPopup popup = new JFXPopup(menu);\n        popup.show(owner, vAlign, hAlign, initOffsetX, initOffsetY);\n    }\n\n    private final JFXListView<GameItem> listView = new JFXListView<>();\n    private final BooleanBinding isEmpty = Bindings.isEmpty(listView.getItems());\n\n    public GameListPopupMenu() {\n        this.setMaxHeight(365);\n        this.getStyleClass().add(\"popup-menu-content\");\n\n        listView.setCellFactory(Cell::new);\n        listView.setFixedCellSize(60);\n        listView.setPrefWidth(300);\n\n        listView.prefHeightProperty().bind(Bindings.size(getItems()).multiply(60).add(2));\n\n        Label placeholder = new Label(i18n(\"version.empty\"));\n        placeholder.setStyle(\"-fx-padding: 10px; -fx-text-fill: -monet-on-surface-variant; -fx-font-style: italic;\");\n\n        FXUtils.onChangeAndOperate(isEmpty, empty -> {\n            getChildren().setAll(empty ? placeholder : listView);\n        });\n    }\n\n    public ObservableList<GameItem> getItems() {\n        return listView.getItems();\n    }\n\n    private static final class Cell extends ListCell<GameItem> {\n\n        private final Region graphic;\n\n        private final ImageContainer imageView;\n        private final TwoLineListItem content;\n\n        private final StringProperty tag = new SimpleStringProperty();\n\n        public Cell(ListView<GameItem> listView) {\n            this.setPadding(Insets.EMPTY);\n            HBox root = new HBox();\n\n            root.setSpacing(8);\n            root.setAlignment(Pos.CENTER_LEFT);\n\n            StackPane imageViewContainer = new StackPane();\n            FXUtils.setLimitWidth(imageViewContainer, 32);\n            FXUtils.setLimitHeight(imageViewContainer, 32);\n\n            this.imageView = new ImageContainer(32);\n            imageViewContainer.getChildren().setAll(imageView);\n\n            this.content = new TwoLineListItem();\n            FXUtils.onChangeAndOperate(tag, tag -> {\n                content.getTags().clear();\n                if (StringUtils.isNotBlank(tag)) {\n                    content.addTag(tag);\n                }\n            });\n            BorderPane.setAlignment(content, Pos.CENTER);\n            root.getChildren().setAll(imageView, content);\n\n            StackPane pane = new StackPane();\n            pane.getChildren().setAll(root);\n            pane.getStyleClass().add(\"menu-container\");\n            root.setMouseTransparent(true);\n\n            RipplerContainer ripplerContainer = new RipplerContainer(pane);\n            FXUtils.onClicked(ripplerContainer, () -> {\n                GameItem item = getItem();\n                if (item != null) {\n                    item.getProfile().setSelectedVersion(item.getId());\n                    if (getScene().getWindow() instanceof JFXPopup popup)\n                        popup.hide();\n                }\n            });\n            this.graphic = ripplerContainer;\n            ripplerContainer.maxWidthProperty().bind(listView.widthProperty().subtract(5));\n        }\n\n        @Override\n        protected void updateItem(GameItem item, boolean empty) {\n            super.updateItem(item, empty);\n\n            this.imageView.imageProperty().unbind();\n            this.content.titleProperty().unbind();\n            this.content.subtitleProperty().unbind();\n            this.tag.unbind();\n\n            if (empty || item == null) {\n                setGraphic(null);\n            } else {\n                setGraphic(this.graphic);\n\n                this.imageView.imageProperty().bind(item.imageProperty());\n                this.content.titleProperty().bind(item.titleProperty());\n                this.content.subtitleProperty().bind(item.subtitleProperty());\n                this.tag.bind(item.tagProperty());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport org.jackhuang.hmcl.game.LocalizedRemoteModRepository;\nimport org.jackhuang.hmcl.mod.RemoteModRepository;\nimport org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;\nimport org.jackhuang.hmcl.util.i18n.I18n;\n\nimport java.util.MissingResourceException;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class HMCLLocalizedDownloadListPage extends DownloadListPage {\n    public static DownloadListPage ofMod(DownloadPage.DownloadCallback callback, boolean versionSelection) {\n        return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MOD, CurseForgeRemoteModRepository.MODS, ModrinthRemoteModRepository.MODS);\n    }\n\n    public static DownloadListPage ofCurseForgeMod(DownloadPage.DownloadCallback callback, boolean versionSelection) {\n        return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MOD, CurseForgeRemoteModRepository.MODS, null);\n    }\n\n    public static DownloadListPage ofModrinthMod(DownloadPage.DownloadCallback callback, boolean versionSelection) {\n        return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MOD, null, ModrinthRemoteModRepository.MODS);\n    }\n\n    public static DownloadListPage ofModPack(DownloadPage.DownloadCallback callback, boolean versionSelection) {\n        return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MODPACK, CurseForgeRemoteModRepository.MODPACKS, ModrinthRemoteModRepository.MODPACKS);\n    }\n\n    public static DownloadListPage ofResourcePack(DownloadPage.DownloadCallback callback, boolean versionSelection) {\n        return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.RESOURCE_PACK, CurseForgeRemoteModRepository.RESOURCE_PACKS, ModrinthRemoteModRepository.RESOURCE_PACKS);\n    }\n\n    public static DownloadListPage ofShaderPack(DownloadPage.DownloadCallback callback, boolean versionSelection) {\n        var page = new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.SHADER_PACK, CurseForgeRemoteModRepository.SHADERS, ModrinthRemoteModRepository.SHADER_PACKS);\n        page.supportChinese.set(false);\n        return page;\n    }\n\n    private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, boolean versionSelection, RemoteModRepository.Type type, CurseForgeRemoteModRepository curseForge, ModrinthRemoteModRepository modrinth) {\n        super(null, callback, versionSelection);\n\n        repository = new Repository(type, curseForge, modrinth);\n\n        supportChinese.set(true);\n        downloadSources.get().setAll(\"mods.modrinth\", \"mods.curseforge\");\n        if (modrinth != null) {\n            downloadSource.set(\"mods.modrinth\");\n        } else if (curseForge != null) {\n            downloadSource.set(\"mods.curseforge\");\n        } else {\n            throw new AssertionError(\"Should not be here.\");\n        }\n    }\n\n    private class Repository extends LocalizedRemoteModRepository {\n        private final RemoteModRepository.Type type;\n        private final CurseForgeRemoteModRepository curseForge;\n        private final ModrinthRemoteModRepository modrinth;\n\n        public Repository(Type type, CurseForgeRemoteModRepository curseForge, ModrinthRemoteModRepository modrinth) {\n            this.type = type;\n            this.curseForge = curseForge;\n            this.modrinth = modrinth;\n        }\n\n        @Override\n        protected RemoteModRepository getBackedRemoteModRepository() {\n            if (\"mods.modrinth\".equals(downloadSource.get())) {\n                return modrinth;\n            } else {\n                return curseForge;\n            }\n        }\n\n        @Override\n        protected SortType getBackedRemoteModRepositorySortOrder() {\n            if (\"mods.modrinth\".equals(downloadSource.get())) {\n                return SortType.NAME;\n            } else {\n                return SortType.POPULARITY;\n            }\n        }\n\n        @Override\n        public Type getType() {\n            return type;\n        }\n    }\n\n    @Override\n    protected String getLocalizedCategory(String category) {\n        if (category.isEmpty()) {\n            return \"\";\n        }\n\n        String key = (\"mods.modrinth\".equals(downloadSource.get()) ? \"modrinth\" : \"curse\") + \".category.\" + category;\n        try {\n            return I18n.getResourceBundle().getString(key);\n        } catch (MissingResourceException e) {\n            LOG.warning(\"Cannot find key \" + key + \" in resource bundle\");\n            return category;\n        }\n    }\n\n    @Override\n    protected String getLocalizedOfficialPage() {\n        if (\"mods.modrinth\".equals(downloadSource.get())) {\n            return i18n(\"mods.modrinth\");\n        } else {\n            return i18n(\"mods.curseforge\");\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport javafx.application.Platform;\nimport javafx.scene.Node;\nimport javafx.scene.control.Skin;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.jackhuang.hmcl.task.TaskListener;\nimport org.jackhuang.hmcl.ui.*;\nimport org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class InstallerListPage extends ListPageBase<InstallerItem> implements VersionPage.VersionLoadable {\n    private Profile profile;\n    private String versionId;\n    private Version version;\n    private String gameVersion;\n\n    {\n        FXUtils.applyDragListener(this, it -> Arrays.asList(\"jar\", \"exe\").contains(FileUtils.getExtension(it)), mods -> {\n            if (!mods.isEmpty())\n                doInstallOffline(mods.get(0));\n        });\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new InstallerListPageSkin();\n    }\n\n    @Override\n    public void loadVersion(Profile profile, String versionId) {\n        this.profile = profile;\n        this.versionId = versionId;\n        this.version = profile.getRepository().getVersion(versionId);\n        this.gameVersion = null;\n\n        CompletableFuture.supplyAsync(() -> {\n            gameVersion = profile.getRepository().getGameVersion(version).orElse(null);\n\n            return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId), gameVersion);\n        }).thenAcceptAsync(analyzer -> {\n            itemsProperty().clear();\n\n            InstallerItem.InstallerItemGroup group = new InstallerItem.InstallerItemGroup(gameVersion, InstallerItem.Style.LIST_ITEM);\n\n            // Conventional libraries: game, fabric, legacyfabric, forge, cleanroom, neoforge, liteloader, optifine\n            for (InstallerItem item : group.getLibraries()) {\n                String libraryId = item.getLibraryId();\n\n                // Skip fabric-api and quilt-api and legacyfabric-api\n                if (libraryId.endsWith(\"-api\")) {\n                    continue;\n                }\n\n                String libraryVersion = analyzer.getVersion(libraryId).orElse(null);\n\n                if (libraryVersion != null) {\n                    item.versionProperty().set(new InstallerItem.InstalledState(\n                            libraryVersion,\n                            analyzer.getLibraryStatus(libraryId) != LibraryAnalyzer.LibraryMark.LibraryStatus.CLEAR,\n                            false\n                    ));\n                } else {\n                    item.versionProperty().set(null);\n                }\n\n                item.setOnInstall(() -> {\n                    Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));\n                });\n\n                item.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId)\n                        .thenComposeAsync(profile.getRepository()::saveAsync)\n                        .withComposeAsync(profile.getRepository().refreshVersionsAsync())\n                        .withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))\n                        .start());\n\n                itemsProperty().add(item);\n            }\n\n            // other third-party libraries which are unable to manage.\n            for (LibraryAnalyzer.LibraryMark mark : analyzer) {\n                String libraryId = mark.getLibraryId();\n                String libraryVersion = mark.getLibraryVersion();\n                if (\"mcbbs\".equals(libraryId))\n                    continue;\n\n                // we have done this library above.\n                if (LibraryAnalyzer.LibraryType.fromPatchId(libraryId) != null)\n                    continue;\n\n                InstallerItem installerItem = new InstallerItem(libraryId, InstallerItem.Style.LIST_ITEM);\n                installerItem.versionProperty().set(new InstallerItem.InstalledState(libraryVersion, false, false));\n                installerItem.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId)\n                        .thenComposeAsync(profile.getRepository()::saveAsync)\n                        .withComposeAsync(profile.getRepository().refreshVersionsAsync())\n                        .withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))\n                        .start());\n\n                itemsProperty().add(installerItem);\n            }\n        }, Platform::runLater);\n    }\n\n    public void installOffline() {\n        FileChooser chooser = new FileChooser();\n        chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"extension.modloader.installer\"), \"*.jar\", \"*.exe\"));\n        Path file = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));\n        if (file != null) doInstallOffline(file);\n    }\n\n    private void doInstallOffline(Path file) {\n        Task<?> task = profile.getDependency().installLibraryAsync(version, file)\n                .thenComposeAsync(profile.getRepository()::saveAsync)\n                .thenComposeAsync(profile.getRepository().refreshVersionsAsync());\n        task.setName(i18n(\"install.installer.install_offline\"));\n        TaskExecutor executor = task.executor(new TaskListener() {\n            @Override\n            public void onStop(boolean success, TaskExecutor executor) {\n                runInFX(() -> {\n                    if (success) {\n                        loadVersion(profile, versionId);\n                        Controllers.dialog(i18n(\"install.success\"));\n                    } else {\n                        if (executor.getException() == null)\n                            return;\n                        UpdateInstallerWizardProvider.alertFailureMessage(executor.getException(), null);\n                    }\n                });\n            }\n        });\n        Controllers.taskDialog(executor, i18n(\"install.installer.install_offline\"), TaskCancellationAction.NO_CANCEL);\n        executor.start();\n    }\n\n    private class InstallerListPageSkin extends ToolbarListPageSkin<InstallerItem, InstallerListPage> {\n\n        InstallerListPageSkin() {\n            super(InstallerListPage.this);\n        }\n\n        @Override\n        protected List<Node> initializeToolbar(InstallerListPage skinnable) {\n            return Collections.singletonList(\n                    createToolbarButton2(i18n(\"install.installer.install_offline\"), SVG.ADD, skinnable::installOffline)\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModCheckUpdatesTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class ModCheckUpdatesTask extends Task<List<LocalModFile.ModUpdate>> {\n    private final DownloadProvider downloadProvider;\n    private final List<Task<LocalModFile.ModUpdate>> dependents;\n\n    public ModCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection<LocalModFile> mods) {\n        this.downloadProvider = downloadProvider;\n        dependents = mods.stream().map(mod ->\n                Task.supplyAsync(Schedulers.io(), () -> {\n                    LocalModFile.ModUpdate candidate = null;\n                    for (RemoteMod.Type type : RemoteMod.Type.values()) {\n                        LocalModFile.ModUpdate update = null;\n                        try {\n                            update = mod.checkUpdates(downloadProvider, gameVersion, type.getRemoteModRepository());\n                        } catch (IOException e) {\n                            LOG.warning(String.format(\"Cannot check update for mod %s.\", mod.getFileName()), e);\n                        }\n                        if (update == null) {\n                            continue;\n                        }\n\n                        if (candidate == null || candidate.getCandidate().getDatePublished().isBefore(update.getCandidate().getDatePublished())) {\n                            candidate = update;\n                        }\n                    }\n\n                    return candidate;\n                }).setName(mod.getFileName()).setSignificance(TaskSignificance.MAJOR).withCounter(\"update.checking\")\n        ).toList();\n\n        setStage(\"update.checking\");\n        getProperties().put(\"total\", dependents.size());\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() {\n        notifyPropertiesChanged();\n    }\n\n    @Override\n    public Collection<? extends Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public boolean isRelyingOnDependents() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        setResult(dependents.stream()\n                .map(Task::getResult)\n                .filter(Objects::nonNull).toList());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.Skin;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.ListPageBase;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.PageAware;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> implements VersionPage.VersionLoadable, PageAware {\n    private final BooleanProperty modded = new SimpleBooleanProperty(this, \"modded\", false);\n\n    private final ReentrantLock lock = new ReentrantLock();\n\n    private ModManager modManager;\n    private Profile profile;\n    private String instanceId;\n    private String gameVersion;\n\n    final EnumSet<ModLoaderType> supportedLoaders = EnumSet.noneOf(ModLoaderType.class);\n\n    public ModListPage() {\n        FXUtils.applyDragListener(this, it -> Arrays.asList(\"jar\", \"zip\", \"litemod\").contains(FileUtils.getExtension(it)), mods -> {\n            mods.forEach(it -> {\n                try {\n                    modManager.addMod(it);\n                } catch (IOException | IllegalArgumentException e) {\n                    LOG.warning(\"Unable to parse mod file \" + it, e);\n                }\n            });\n            loadMods(modManager);\n        });\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new ModListPageSkin(this);\n    }\n\n    public void refresh() {\n        loadMods(modManager);\n    }\n\n    @Override\n    public void loadVersion(Profile profile, String id) {\n        this.profile = profile;\n        this.instanceId = id;\n\n        HMCLGameRepository repository = profile.getRepository();\n        Version resolved = repository.getResolvedPreservingPatchesVersion(id);\n        this.gameVersion = repository.getGameVersion(resolved).orElse(null);\n        LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved, gameVersion);\n        modded.set(analyzer.hasModLoader());\n        loadMods(profile.getRepository().getModManager(id));\n    }\n\n    private void loadMods(ModManager modManager) {\n        setLoading(true);\n\n        this.modManager = modManager;\n        CompletableFuture.supplyAsync(() -> {\n            lock.lock();\n            try {\n                modManager.refreshMods();\n                return modManager.getMods().stream().map(ModListPageSkin.ModInfoObject::new).toList();\n            } catch (IOException e) {\n                throw new UncheckedIOException(e);\n            } finally {\n                lock.unlock();\n            }\n        }, Schedulers.io()).whenCompleteAsync((list, exception) -> {\n            updateSupportedLoaders(modManager);\n\n            if (exception == null) {\n                getItems().setAll(list);\n            } else {\n                LOG.warning(\"Failed to load mods\", exception);\n                getItems().clear();\n            }\n            setLoading(false);\n        }, Schedulers.javafx());\n    }\n\n    private void updateSupportedLoaders(ModManager modManager) {\n        supportedLoaders.clear();\n\n        LibraryAnalyzer analyzer = modManager.getLibraryAnalyzer();\n        if (analyzer == null) {\n            Collections.addAll(supportedLoaders, ModLoaderType.values());\n            return;\n        }\n\n        for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {\n            if (type.isModLoader() && analyzer.has(type)) {\n                ModLoaderType modLoaderType = type.getModLoaderType();\n                if (modLoaderType != null) {\n                    supportedLoaders.add(modLoaderType);\n\n                    if (modLoaderType == ModLoaderType.CLEANROOM)\n                        supportedLoaders.add(ModLoaderType.FORGE);\n                }\n            }\n        }\n\n        if (analyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE) && \"1.20.1\".equals(gameVersion)) {\n            supportedLoaders.add(ModLoaderType.FORGE);\n        }\n\n        if (analyzer.has(LibraryAnalyzer.LibraryType.QUILT)) {\n            supportedLoaders.add(ModLoaderType.FABRIC);\n        }\n\n        if (analyzer.has(LibraryAnalyzer.LibraryType.LEGACY_FABRIC)) {\n            supportedLoaders.add(ModLoaderType.FABRIC);\n        }\n\n        if (analyzer.has(LibraryAnalyzer.LibraryType.FABRIC) && modManager.hasMod(\"kilt\", ModLoaderType.FABRIC)) {\n            supportedLoaders.add(ModLoaderType.FORGE);\n            supportedLoaders.add(ModLoaderType.NEO_FORGED);\n        }\n\n        // Sinytra Connector\n        if (analyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE) && modManager.hasMod(\"connectormod\", ModLoaderType.NEO_FORGED)\n                || \"1.20.1\".equals(gameVersion) && analyzer.has(LibraryAnalyzer.LibraryType.FORGE) && modManager.hasMod(\"connectormod\", ModLoaderType.FORGE)) {\n            supportedLoaders.add(ModLoaderType.FABRIC);\n        }\n    }\n\n    public void add() {\n        FileChooser chooser = new FileChooser();\n        chooser.setTitle(i18n(\"mods.add.title\"));\n        chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n(\"extension.mod\"), \"*.jar\", \"*.zip\", \"*.litemod\"));\n        List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));\n\n        if (res == null) return;\n\n        // It's guaranteed that succeeded and failed are thread safe here.\n        List<String> succeeded = new ArrayList<>(res.size());\n        List<String> failed = new ArrayList<>();\n\n        Task.runAsync(() -> {\n            for (Path file : res) {\n                try {\n                    modManager.addMod(file);\n                    succeeded.add(FileUtils.getName(file));\n                } catch (Exception e) {\n                    LOG.warning(\"Unable to add mod \" + file, e);\n                    failed.add(FileUtils.getName(file));\n\n                    // Actually addMod will not throw exceptions because FileChooser has already filtered files.\n                }\n            }\n        }).withRunAsync(Schedulers.javafx(), () -> {\n            List<String> prompt = new ArrayList<>(1);\n            if (!succeeded.isEmpty())\n                prompt.add(i18n(\"mods.add.success\", String.join(\", \", succeeded)));\n            if (!failed.isEmpty())\n                prompt.add(i18n(\"mods.add.failed\", String.join(\", \", failed)));\n            Controllers.dialog(String.join(\"\\n\", prompt), i18n(\"mods.add\"));\n            loadMods(modManager);\n        }).start();\n    }\n\n    void removeSelected(ObservableList<ModListPageSkin.ModInfoObject> selectedItems) {\n        try {\n            modManager.removeMods(selectedItems.stream()\n                    .filter(Objects::nonNull)\n                    .map(ModListPageSkin.ModInfoObject::getModInfo)\n                    .toArray(LocalModFile[]::new));\n            loadMods(modManager);\n        } catch (IOException ignore) {\n            // Fail to remove mods if the game is running or the mod is absent.\n        }\n    }\n\n    void enableSelected(ObservableList<ModListPageSkin.ModInfoObject> selectedItems) {\n        selectedItems.stream()\n                .filter(Objects::nonNull)\n                .map(ModListPageSkin.ModInfoObject::getModInfo)\n                .forEach(info -> info.setActive(true));\n    }\n\n    void disableSelected(ObservableList<ModListPageSkin.ModInfoObject> selectedItems) {\n        selectedItems.stream()\n                .filter(Objects::nonNull)\n                .map(ModListPageSkin.ModInfoObject::getModInfo)\n                .forEach(info -> info.setActive(false));\n    }\n\n    public void openModFolder() {\n        FXUtils.openFolder(profile.getRepository().getRunDirectory(instanceId).resolve(\"mods\"));\n    }\n\n    public void checkUpdates(Collection<LocalModFile> mods) {\n        Objects.requireNonNull(mods);\n        Runnable action = () -> Controllers.taskDialog(Task\n                        .composeAsync(() -> {\n                            Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);\n                            if (gameVersion.isPresent()) {\n                                return new ModCheckUpdatesTask(DownloadProviders.getDownloadProvider(), gameVersion.get(), mods);\n                            }\n                            return null;\n                        })\n                        .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                            if (exception instanceof CancellationException) return;\n                            if (exception != null || result == null) {\n                                Controllers.dialog(i18n(\"mods.check_updates.failed_check\"), i18n(\"message.failed\"), MessageDialogPane.MessageType.ERROR);\n                            } else if (result.isEmpty()) {\n                                Controllers.dialog(i18n(\"mods.check_updates.empty\"));\n                            } else {\n                                Controllers.navigateForward(new ModUpdatesPage(modManager, result));\n                            }\n                        })\n                        .withStagesHints(\"update.checking\"),\n                i18n(\"mods.check_updates\"), TaskCancellationAction.NORMAL);\n\n        if (profile.getRepository().isModpack(instanceId)) {\n            Controllers.confirm(\n                    i18n(\"mods.update_modpack_mod.warning\"), null,\n                    MessageDialogPane.MessageType.WARNING,\n                    action, null);\n        } else {\n            action.run();\n        }\n    }\n\n    public void download() {\n        Controllers.getDownloadPage().showModDownloads().selectVersion(instanceId);\n        Controllers.navigate(Controllers.getDownloadPage());\n    }\n\n    public void rollback(LocalModFile from, LocalModFile to) {\n        try {\n            modManager.rollback(from, to);\n            refresh();\n        } catch (IOException ex) {\n            Controllers.showToast(i18n(\"message.failed\"));\n        }\n    }\n\n    public boolean isModded() {\n        return modded.get();\n    }\n\n    public BooleanProperty moddedProperty() {\n        return modded;\n    }\n\n    public void setModded(boolean modded) {\n        this.modded.set(modded);\n    }\n\n    public Profile getProfile() {\n        return this.profile;\n    }\n\n    public String getInstanceId() {\n        return this.instanceId;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.*;\nimport javafx.animation.PauseTransition;\nimport javafx.application.Platform;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.collections.ListChangeListener;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.image.Image;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.StackPane;\nimport javafx.stage.Stage;\nimport javafx.util.Duration;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.RemoteModRepository;\nimport org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;\nimport org.jackhuang.hmcl.setting.DownloadProviders;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.VersionIconType;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.ref.SoftReference;\nimport java.lang.ref.WeakReference;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Predicate;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\nfinal class ModListPageSkin extends SkinBase<ModListPage> {\n\n    private final TransitionPane toolbarPane;\n    private final HBox searchBar;\n    private final HBox toolbarNormal;\n    private final HBox toolbarSelecting;\n\n    private final JFXListView<ModInfoObject> listView;\n    private final JFXTextField searchField;\n\n    // FXThread\n    private boolean isSearching = false;\n\n    ModListPageSkin(ModListPage skinnable) {\n        super(skinnable);\n\n        StackPane pane = new StackPane();\n        pane.setPadding(new Insets(10));\n        pane.getStyleClass().addAll(\"notice-pane\");\n\n        ComponentList root = new ComponentList();\n        root.getStyleClass().add(\"no-padding\");\n        listView = new JFXListView<>();\n        listView.getStyleClass().add(\"no-horizontal-scrollbar\");\n\n        {\n            toolbarPane = new TransitionPane();\n\n            searchBar = new HBox();\n            toolbarNormal = new HBox();\n            toolbarSelecting = new HBox();\n\n            // Search Bar\n            searchBar.setAlignment(Pos.CENTER);\n            searchBar.setPadding(new Insets(0, 5, 0, 5));\n            searchField = new JFXTextField();\n            searchField.setPromptText(i18n(\"search\"));\n            HBox.setHgrow(searchField, Priority.ALWAYS);\n            PauseTransition pause = new PauseTransition(Duration.millis(100));\n            pause.setOnFinished(e -> search());\n            searchField.textProperty().addListener((observable, oldValue, newValue) -> {\n                pause.setRate(1);\n                pause.playFromStart();\n            });\n\n            JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE,\n                    () -> {\n                        changeToolbar(toolbarNormal);\n\n                        isSearching = false;\n                        searchField.clear();\n                        Bindings.bindContent(listView.getItems(), getSkinnable().getItems());\n                    });\n\n            onEscPressed(searchField, closeSearchBar::fire);\n\n            searchBar.getChildren().setAll(searchField, closeSearchBar);\n\n            // Toolbar Normal\n            toolbarNormal.getChildren().setAll(\n                    createToolbarButton2(i18n(\"button.refresh\"), SVG.REFRESH, skinnable::refresh),\n                    createToolbarButton2(i18n(\"mods.add\"), SVG.ADD, skinnable::add),\n                    createToolbarButton2(i18n(\"button.reveal_dir\"), SVG.FOLDER_OPEN, skinnable::openModFolder),\n                    createToolbarButton2(i18n(\"mods.check_updates.button\"), SVG.UPDATE, () ->\n                            skinnable.checkUpdates(\n                                    listView.getItems().stream()\n                                            .map(ModInfoObject::getModInfo)\n                                            .toList()\n                            )\n                    ),\n                    createToolbarButton2(i18n(\"download\"), SVG.DOWNLOAD, skinnable::download),\n                    createToolbarButton2(i18n(\"search\"), SVG.SEARCH, () -> changeToolbar(searchBar))\n            );\n\n            // Toolbar Selecting\n            toolbarSelecting.getChildren().setAll(\n                    createToolbarButton2(i18n(\"button.remove\"), SVG.DELETE_FOREVER, () -> {\n                        Controllers.confirm(i18n(\"button.remove.confirm\"), i18n(\"button.remove\"), () -> {\n                            skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());\n                        }, null);\n                    }),\n                    createToolbarButton2(i18n(\"mods.enable\"), SVG.CHECK, () ->\n                            skinnable.enableSelected(listView.getSelectionModel().getSelectedItems())),\n                    createToolbarButton2(i18n(\"mods.disable\"), SVG.CLOSE, () ->\n                            skinnable.disableSelected(listView.getSelectionModel().getSelectedItems())),\n                    createToolbarButton2(i18n(\"mods.check_updates.button\"), SVG.UPDATE, () ->\n                            skinnable.checkUpdates(\n                                    listView.getSelectionModel().getSelectedItems().stream()\n                                            .map(ModInfoObject::getModInfo)\n                                            .toList()\n                            )\n                    ),\n                    createToolbarButton2(i18n(\"button.select_all\"), SVG.SELECT_ALL, () ->\n                            listView.getSelectionModel().selectAll()),\n                    createToolbarButton2(i18n(\"button.cancel\"), SVG.CANCEL, () ->\n                            listView.getSelectionModel().clearSelection())\n            );\n\n            FXUtils.onChangeAndOperate(listView.getSelectionModel().selectedItemProperty(),\n                    selectedItem -> {\n                        if (selectedItem == null)\n                            changeToolbar(isSearching ? searchBar : toolbarNormal);\n                        else\n                            changeToolbar(toolbarSelecting);\n                    });\n            root.getContent().add(toolbarPane);\n\n            // Clear selection when pressing ESC\n            root.addEventHandler(KeyEvent.KEY_PRESSED, e -> {\n                if (e.getCode() == KeyCode.ESCAPE) {\n                    if (listView.getSelectionModel().getSelectedItem() != null) {\n                        listView.getSelectionModel().clearSelection();\n                        e.consume();\n                    }\n                }\n            });\n        }\n\n        {\n            SpinnerPane center = new SpinnerPane();\n            ComponentList.setVgrow(center, Priority.ALWAYS);\n            center.loadingProperty().bind(skinnable.loadingProperty());\n\n            listView.setCellFactory(x -> new ModInfoListCell(listView));\n            listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);\n            Bindings.bindContent(listView.getItems(), skinnable.getItems());\n            skinnable.getItems().addListener((ListChangeListener<? super ModInfoObject>) c -> {\n                if (isSearching) {\n                    search();\n                }\n            });\n\n            listView.setOnContextMenuRequested(event -> {\n                ModInfoObject selectedItem = listView.getSelectionModel().getSelectedItem();\n                if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) {\n                    listView.getSelectionModel().clearSelection();\n                    Controllers.dialog(new ModInfoDialog(selectedItem));\n                }\n            });\n\n            // ListViewBehavior would consume ESC pressed event, preventing us from handling it\n            // So we ignore it here\n            ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);\n\n            center.setContent(listView);\n            root.getContent().add(center);\n        }\n\n        Label label = new Label(i18n(\"mods.not_modded\"));\n        label.prefWidthProperty().bind(pane.widthProperty().add(-100));\n\n        FXUtils.onChangeAndOperate(skinnable.moddedProperty(), modded -> {\n            if (modded) pane.getChildren().setAll(root);\n            else pane.getChildren().setAll(label);\n        });\n\n        getChildren().setAll(pane);\n    }\n\n    private void changeToolbar(HBox newToolbar) {\n        Node oldToolbar = toolbarPane.getCurrentNode();\n        if (newToolbar != oldToolbar) {\n            toolbarPane.setContent(newToolbar, ContainerAnimations.FADE);\n            if (newToolbar == searchBar) {\n                Platform.runLater(searchField::requestFocus);\n            }\n        }\n    }\n\n    private void search() {\n        isSearching = true;\n\n        Bindings.unbindContent(listView.getItems(), getSkinnable().getItems());\n\n        String queryString = searchField.getText();\n        if (StringUtils.isBlank(queryString)) {\n            listView.getItems().setAll(getSkinnable().getItems());\n        } else {\n            listView.getItems().clear();\n\n            Predicate<@Nullable String> predicate;\n            if (queryString.startsWith(\"regex:\")) {\n                try {\n                    Pattern pattern = Pattern.compile(queryString.substring(\"regex:\".length()));\n                    predicate = s -> s != null && pattern.matcher(s).find();\n                } catch (Throwable e) {\n                    LOG.warning(\"Illegal regular expression\", e);\n                    return;\n                }\n            } else {\n                String lowerQueryString = queryString.toLowerCase(Locale.ROOT);\n                predicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerQueryString);\n            }\n\n            // Do we need to search in the background thread?\n            for (ModInfoObject item : getSkinnable().getItems()) {\n                LocalModFile modInfo = item.getModInfo();\n                if (predicate.test(modInfo.getFileName())\n                        || predicate.test(modInfo.getName())\n                        || predicate.test(modInfo.getVersion())\n                        || predicate.test(modInfo.getGameVersion())\n                        || predicate.test(modInfo.getId())\n                        || predicate.test(Objects.toString(modInfo.getModLoaderType()))\n                        || predicate.test((item.getModTranslations() != null ? item.getModTranslations().getDisplayName() : null))) {\n                    listView.getItems().add(item);\n                }\n            }\n        }\n    }\n\n    static final class ModInfoObject {\n        private final BooleanProperty active;\n        private final LocalModFile localModFile;\n        private final @Nullable ModTranslations.Mod modTranslations;\n\n        private SoftReference<CompletableFuture<Image>> iconCache;\n\n        ModInfoObject(LocalModFile localModFile) {\n            this.localModFile = localModFile;\n            this.active = localModFile.activeProperty();\n\n            this.modTranslations = ModTranslations.MOD.getMod(localModFile.getId(), localModFile.getName());\n        }\n\n        LocalModFile getModInfo() {\n            return localModFile;\n        }\n\n        public @Nullable ModTranslations.Mod getModTranslations() {\n            return modTranslations;\n        }\n\n        @FXThread\n        private Image loadIcon() {\n            List<String> iconPaths = new ArrayList<>();\n\n            if (StringUtils.isNotBlank(this.localModFile.getLogoPath())) {\n                iconPaths.add(this.localModFile.getLogoPath());\n            }\n\n            iconPaths.addAll(List.of(\n                    \"icon.png\",\n                    \"logo.png\",\n                    \"mod_logo.png\",\n                    \"pack.png\",\n                    \"logoFile.png\",\n                    \"assets/icon.png\",\n                    \"assets/logo.png\",\n                    \"assets/mod_icon.png\",\n                    \"assets/mod_logo.png\",\n                    \"META-INF/icon.png\",\n                    \"META-INF/logo.png\",\n                    \"META-INF/mod_icon.png\",\n                    \"textures/icon.png\",\n                    \"textures/logo.png\",\n                    \"textures/mod_icon.png\",\n                    \"resources/icon.png\",\n                    \"resources/logo.png\",\n                    \"resources/mod_icon.png\"\n            ));\n\n            String modId = this.localModFile.getId();\n            if (StringUtils.isNotBlank(modId)) {\n                iconPaths.addAll(List.of(\n                        \"assets/\" + modId + \"/icon.png\",\n                        \"assets/\" + modId + \"/logo.png\",\n                        \"assets/\" + modId.replace(\"-\", \"\") + \"/icon.png\",\n                        \"assets/\" + modId.replace(\"-\", \"\") + \"/logo.png\",\n                        modId + \".png\",\n                        modId + \"-logo.png\",\n                        modId + \"-icon.png\",\n                        modId + \"_logo.png\",\n                        modId + \"_icon.png\",\n                        \"textures/\" + modId + \"/icon.png\",\n                        \"textures/\" + modId + \"/logo.png\",\n                        \"resources/\" + modId + \"/icon.png\",\n                        \"resources/\" + modId + \"/logo.png\"\n                ));\n            }\n\n            try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(this.localModFile.getFile())) {\n                for (String path : iconPaths) {\n                    Path iconPath = fs.getPath(path);\n                    if (Files.exists(iconPath)) {\n                        Image image = FXUtils.loadImage(iconPath, 80, 80, true, true);\n                        if (!image.isError() && image.getWidth() > 0 && image.getHeight() > 0 &&\n                                Math.abs(image.getWidth() - image.getHeight()) < 1) {\n                            return image;\n                        }\n                    }\n                }\n            } catch (Exception e) {\n                LOG.warning(\"Failed to load mod icons\", e);\n            }\n\n            return VersionIconType.getIconType(this.localModFile.getModLoaderType()).getIcon();\n        }\n\n        public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<ModInfoObject>> current) {\n            SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;\n            CompletableFuture<Image> imageFuture;\n            if (iconCache != null && (imageFuture = iconCache.get()) != null) {\n                Image image = imageFuture.getNow(null);\n                if (image != null) {\n                    imageContainer.setImage(image);\n                    return;\n                }\n            } else {\n                imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io());\n                this.iconCache = new SoftReference<>(imageFuture);\n            }\n            imageContainer.setImage(VersionIconType.getIconType(localModFile.getModLoaderType()).getIcon());\n            imageFuture.thenAcceptAsync(image -> {\n                if (current != null) {\n                    ObjectProperty<ModInfoObject> infoObjectProperty = current.get();\n                    if (infoObjectProperty == null || infoObjectProperty.get() != this) {\n                        // The current ListCell has already switched to another object\n                        return;\n                    }\n                }\n\n                imageContainer.setImage(image);\n            }, Schedulers.javafx());\n        }\n    }\n\n    final class ModInfoDialog extends JFXDialogLayout {\n\n        ModInfoDialog(ModInfoObject modInfo) {\n            HBox titleContainer = new HBox();\n            titleContainer.setSpacing(8);\n\n            Stage stage = Controllers.getStage();\n            maxWidthProperty().bind(stage.widthProperty().multiply(0.7));\n\n            var imageContainer = new ImageContainer(40);\n            modInfo.loadIcon(imageContainer, null);\n\n            TwoLineListItem title = new TwoLineListItem();\n            if (modInfo.getModTranslations() != null && I18n.isUseChinese())\n                title.setTitle(modInfo.getModTranslations().getDisplayName());\n            else\n                title.setTitle(modInfo.getModInfo().getName());\n\n            StringJoiner subtitle = new StringJoiner(\" | \");\n            subtitle.add(FileUtils.getName(modInfo.getModInfo().getFile()));\n            if (StringUtils.isNotBlank(modInfo.getModInfo().getGameVersion())) {\n                subtitle.add(modInfo.getModInfo().getGameVersion());\n            }\n            if (StringUtils.isNotBlank(modInfo.getModInfo().getVersion())) {\n                subtitle.add(modInfo.getModInfo().getVersion());\n            }\n            if (StringUtils.isNotBlank(modInfo.getModInfo().getAuthors())) {\n                subtitle.add(i18n(\"archive.author\") + \": \" + modInfo.getModInfo().getAuthors());\n            }\n            title.setSubtitle(subtitle.toString());\n\n            titleContainer.getChildren().setAll(imageContainer, title);\n            setHeading(titleContainer);\n\n            Label description = new Label(modInfo.getModInfo().getDescription().toString());\n            description.setWrapText(true);\n            FXUtils.copyOnDoubleClick(description);\n\n            ScrollPane descriptionPane = new ScrollPane(description);\n            FXUtils.smoothScrolling(descriptionPane);\n            descriptionPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);\n            descriptionPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);\n            descriptionPane.setFitToWidth(true);\n            description.heightProperty().addListener((obs, oldVal, newVal) -> {\n                double maxHeight = stage.getHeight() * 0.5;\n                double targetHeight = Math.min(newVal.doubleValue(), maxHeight);\n                descriptionPane.setPrefViewportHeight(targetHeight);\n            });\n\n            setBody(descriptionPane);\n\n            if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) {\n                for (Pair<String, ? extends RemoteModRepository> item : Arrays.asList(\n                        pair(\"mods.curseforge\", CurseForgeRemoteModRepository.MODS),\n                        pair(\"mods.modrinth\", ModrinthRemoteModRepository.MODS)\n                )) {\n                    RemoteModRepository repository = item.getValue();\n                    JFXHyperlink button = new JFXHyperlink(i18n(item.getKey()));\n                    Task.runAsync(() -> {\n                        Optional<RemoteMod.Version> versionOptional = repository.getRemoteVersionByLocalFile(modInfo.getModInfo(), modInfo.getModInfo().getFile());\n                        if (versionOptional.isPresent()) {\n                            RemoteMod remoteMod = repository.getModById(DownloadProviders.getDownloadProvider(), versionOptional.get().getModid());\n                            FXUtils.runInFX(() -> {\n                                for (ModLoaderType modLoaderType : versionOptional.get().getLoaders()) {\n                                    String loaderName = switch (modLoaderType) {\n                                        case FORGE -> i18n(\"install.installer.forge\");\n                                        case CLEANROOM -> i18n(\"install.installer.cleanroom\");\n                                        case LEGACY_FABRIC -> i18n(\"install.installer.legacyfabric\");\n                                        case NEO_FORGED -> i18n(\"install.installer.neoforge\");\n                                        case FABRIC -> i18n(\"install.installer.fabric\");\n                                        case LITE_LOADER -> i18n(\"install.installer.liteloader\");\n                                        case QUILT -> i18n(\"install.installer.quilt\");\n                                        default -> null;\n                                    };\n                                    if (loaderName == null)\n                                        continue;\n                                    if (title.getTags()\n                                            .stream()\n                                            .noneMatch(it -> it.getText().equals(loaderName))) {\n                                        title.addTag(loaderName);\n                                    }\n                                }\n\n                                button.setOnAction(e -> {\n                                    fireEvent(new DialogCloseEvent());\n                                    Controllers.navigate(new DownloadPage(\n                                            repository instanceof CurseForgeRemoteModRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false),\n                                            remoteMod,\n                                            new Profile.ProfileVersion(ModListPageSkin.this.getSkinnable().getProfile(), ModListPageSkin.this.getSkinnable().getInstanceId()),\n                                            (downloadProvider, profile, version, mod, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(downloadProvider, profile, version, file, \"mods\")\n                                    ));\n                                });\n                                button.setDisable(false);\n                            });\n                        }\n                    }).start();\n                    button.setDisable(true);\n                    getActions().add(button);\n                }\n            }\n\n            if (StringUtils.isNotBlank(modInfo.getModInfo().getUrl())) {\n                JFXHyperlink officialPageButton = new JFXHyperlink(i18n(\"mods.url\"));\n                officialPageButton.setOnAction(e -> {\n                    fireEvent(new DialogCloseEvent());\n                    FXUtils.openLink(modInfo.getModInfo().getUrl());\n                });\n\n                getActions().add(officialPageButton);\n            }\n\n            if (modInfo.getModTranslations() == null || StringUtils.isBlank(modInfo.getModTranslations().getMcmod())) {\n                JFXHyperlink searchButton = new JFXHyperlink(i18n(\"mods.mcmod.search\"));\n                searchButton.setOnAction(e -> {\n                    fireEvent(new DialogCloseEvent());\n                    FXUtils.openLink(NetworkUtils.withQuery(\"https://search.mcmod.cn/s\", mapOf(\n                            pair(\"key\", modInfo.getModInfo().getName()),\n                            pair(\"site\", \"all\"),\n                            pair(\"filter\", \"0\")\n                    )));\n                });\n                getActions().add(searchButton);\n            } else {\n                JFXHyperlink mcmodButton = new JFXHyperlink(i18n(\"mods.mcmod.page\"));\n                mcmodButton.setOnAction(e -> {\n                    fireEvent(new DialogCloseEvent());\n                    FXUtils.openLink(ModTranslations.MOD.getMcmodUrl(modInfo.getModTranslations()));\n                });\n                getActions().add(mcmodButton);\n            }\n\n            JFXButton okButton = new JFXButton();\n            okButton.getStyleClass().add(\"dialog-accept\");\n            okButton.setText(i18n(\"button.ok\"));\n            okButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));\n            getActions().add(okButton);\n\n            onEscPressed(this, okButton::fire);\n        }\n    }\n\n    private static final Lazy<PopupMenu> menu = new Lazy<>(PopupMenu::new);\n    private static final Lazy<JFXPopup> popup = new Lazy<>(() -> new JFXPopup(menu.get()));\n\n    final class ModInfoListCell extends MDListCell<ModInfoObject> {\n        private static final PseudoClass WARNING = PseudoClass.getPseudoClass(\"warning\");\n\n        JFXCheckBox checkBox = new JFXCheckBox();\n        ImageContainer imageContainer = new ImageContainer(24);\n        TwoLineListItem content = new TwoLineListItem();\n        JFXButton restoreButton = FXUtils.newToggleButton4(SVG.RESTORE);\n        JFXButton infoButton = FXUtils.newToggleButton4(SVG.INFO);\n        JFXButton revealButton = FXUtils.newToggleButton4(SVG.FOLDER);\n        BooleanProperty booleanProperty;\n\n        Tooltip warningTooltip;\n\n        ModInfoListCell(JFXListView<ModInfoObject> listView) {\n            super(listView);\n\n            this.getStyleClass().add(\"mod-info-list-cell\");\n\n            HBox container = new HBox(8);\n            container.setPickOnBounds(false);\n            container.setAlignment(Pos.CENTER_LEFT);\n            HBox.setHgrow(content, Priority.ALWAYS);\n            content.setMouseTransparent(true);\n            setSelectable();\n\n            imageContainer.setImage(VersionIconType.COMMAND.getIcon());\n\n            FXUtils.installFastTooltip(restoreButton, i18n(\"mods.restore\"));\n\n            container.getChildren().setAll(checkBox, imageContainer, content, restoreButton, revealButton, infoButton);\n\n            StackPane.setMargin(container, new Insets(8));\n            getContainer().getChildren().setAll(container);\n        }\n\n        @Override\n        protected void updateControl(ModInfoObject dataItem, boolean empty) {\n            pseudoClassStateChanged(WARNING, false);\n            if (warningTooltip != null) {\n                Tooltip.uninstall(this, warningTooltip);\n                warningTooltip = null;\n            }\n\n            if (empty) return;\n\n            List<String> warning = new ArrayList<>();\n\n            content.getTags().clear();\n\n            LocalModFile modInfo = dataItem.getModInfo();\n            ModTranslations.Mod modTranslations = dataItem.getModTranslations();\n\n            ModLoaderType modLoaderType = modInfo.getModLoaderType();\n\n            dataItem.loadIcon(imageContainer, new WeakReference<>(this.itemProperty()));\n\n            String displayName = modInfo.getName();\n            if (modTranslations != null && I18n.isUseChinese()) {\n                String chineseName = modTranslations.getName();\n                if (StringUtils.containsChinese(chineseName)) {\n                    if (StringUtils.containsEmoji(chineseName)) {\n                        StringBuilder builder = new StringBuilder();\n\n                        chineseName.codePoints().forEach(ch -> {\n                            if (ch < 0x1F300 || ch > 0x1FAFF)\n                                builder.appendCodePoint(ch);\n                        });\n\n                        chineseName = builder.toString().trim();\n                    }\n\n                    if (StringUtils.isNotBlank(chineseName) && !displayName.equalsIgnoreCase(chineseName)) {\n                        displayName = displayName + \" (\" + chineseName + \")\";\n                    }\n                }\n            }\n            content.setTitle(displayName);\n\n            StringJoiner joiner = new StringJoiner(\" | \");\n            if (modLoaderType != ModLoaderType.UNKNOWN && StringUtils.isNotBlank(modInfo.getId()))\n                joiner.add(modInfo.getId());\n\n            joiner.add(FileUtils.getName(modInfo.getFile()));\n\n            content.setSubtitle(joiner.toString());\n\n            if (modLoaderType == ModLoaderType.UNKNOWN) {\n                content.addTagWarning(i18n(\"mods.unknown\"));\n            } else if (!ModListPageSkin.this.getSkinnable().supportedLoaders.contains(modLoaderType)) {\n                warning.add(i18n(\"mods.warning.loader_mismatch\"));\n                switch (dataItem.getModInfo().getModLoaderType()) {\n                    case FORGE -> content.addTagWarning(i18n(\"install.installer.forge\"));\n                    case LEGACY_FABRIC -> content.addTagWarning(i18n(\"install.installer.legacyfabric\"));\n                    case CLEANROOM -> content.addTagWarning(i18n(\"install.installer.cleanroom\"));\n                    case NEO_FORGED -> content.addTagWarning(i18n(\"install.installer.neoforge\"));\n                    case FABRIC -> content.addTagWarning(i18n(\"install.installer.fabric\"));\n                    case LITE_LOADER -> content.addTagWarning(i18n(\"install.installer.liteloader\"));\n                    case QUILT -> content.addTagWarning(i18n(\"install.installer.quilt\"));\n                }\n            }\n\n            String modVersion = modInfo.getVersion();\n            if (StringUtils.isNotBlank(modVersion) && !\"${version}\".equals(modVersion)) {\n                content.addTag(modVersion);\n            }\n\n            if (booleanProperty != null) {\n                checkBox.selectedProperty().unbindBidirectional(booleanProperty);\n            }\n            checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);\n            restoreButton.setVisible(!modInfo.getMod().getOldFiles().isEmpty());\n            restoreButton.setOnAction(e -> {\n                menu.get().getContent().setAll(modInfo.getMod().getOldFiles().stream()\n                        .map(localModFile -> new IconedMenuItem(null, localModFile.getVersion(),\n                                () -> getSkinnable().rollback(modInfo, localModFile),\n                                popup.get()))\n                        .toList()\n                );\n\n                popup.get().show(restoreButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, restoreButton.getHeight());\n            });\n            revealButton.setOnAction(e -> FXUtils.showFileInExplorer(modInfo.getFile()));\n            infoButton.setOnAction(e -> Controllers.dialog(new ModInfoDialog(dataItem)));\n\n            if (!warning.isEmpty()) {\n                pseudoClassStateChanged(WARNING, true);\n\n                //noinspection ConstantValue\n                this.warningTooltip = warning.size() == 1\n                        ? new Tooltip(warning.get(0))\n                        : new Tooltip(String.join(\"\\n\", warning));\n                FXUtils.installFastTooltip(this, warningTooltip);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport org.jackhuang.hmcl.mod.RemoteModRepository;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\n/**\n * Parser for mod_data.txt\n *\n * @see <a href=\"https://www.mcmod.cn\">mcmod.cn</a>\n */\npublic enum ModTranslations {\n    MOD(\"/assets/mod_data.txt\") {\n        @Override\n        public String getMcmodUrl(Mod mod) {\n            return String.format(\"https://www.mcmod.cn/class/%s.html\", mod.getMcmod());\n        }\n    },\n    MODPACK(\"/assets/modpack_data.txt\") {\n        @Override\n        public String getMcmodUrl(Mod mod) {\n            return String.format(\"https://www.mcmod.cn/modpack/%s.html\", mod.getMcmod());\n        }\n    },\n    EMPTY(\"\") {\n        @Override\n        public String getMcmodUrl(Mod mod) {\n            return \"\";\n        }\n    };\n\n    public static ModTranslations getTranslationsByRepositoryType(RemoteModRepository.Type type) {\n        return switch (type) {\n            case MOD -> MOD;\n            case MODPACK -> MODPACK;\n            default -> EMPTY;\n        };\n    }\n\n    @SuppressWarnings(\"StatementWithEmptyBody\")\n    private static String cleanSubname(String subname) {\n        if (StringUtils.isBlank(subname))\n            return \"\";\n\n        StringBuilder builder = new StringBuilder(subname.length());\n        for (int i = 0; i < subname.length(); ) {\n            int ch = subname.codePointAt(i);\n            if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')\n                    || \".+\\\\\".indexOf(ch) >= 0) {\n                builder.appendCodePoint(ch);\n            } else if (Character.isWhitespace(ch)\n                    || \"':_-/&()[]{}|,!?~•\".indexOf(ch) >= 0\n                    || ch >= 0x1F300 && ch <= 0x1FAFF) {\n                // Remove these unnecessary characters from subname\n            } else {\n                // The subname contains unsupported characters, so we do not use this subname to match the mod\n                return \"\";\n            }\n            i += Character.charCount(ch);\n        }\n        return builder.length() == subname.length() ? subname : builder.toString();\n    }\n\n    private final String resourceName;\n    private volatile List<Mod> mods;\n    private volatile Map<String, Mod> modIdMap; // mod id -> mod\n    private volatile Map<String, Mod> subnameMap;\n    private volatile Map<String, Mod> curseForgeMap; // curseforge id -> mod\n    private volatile List<Pair<String, Mod>> keywords;\n    private volatile int maxKeywordLength = -1;\n\n    ModTranslations(String resourceName) {\n        this.resourceName = resourceName;\n    }\n\n    public @NotNull List<Mod> getMods() {\n        List<Mod> mods = this.mods;\n        if (mods != null)\n            return mods;\n\n        synchronized (this) {\n            mods = this.mods;\n            if (mods != null)\n                return mods;\n\n            if (StringUtils.isBlank(resourceName)) {\n                return this.mods = List.of();\n            }\n\n            //noinspection DataFlowIssue\n            try (BufferedReader reader = new BufferedReader(\n                    new InputStreamReader(\n                            ModTranslations.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8))) {\n                return this.mods = reader.lines().filter(line -> !line.startsWith(\"#\")).map(Mod::new).toList();\n            } catch (Exception e) {\n                LOG.warning(\"Failed to load \" + resourceName, e);\n                return this.mods = List.of();\n            }\n        }\n    }\n\n    private @NotNull Map<String, Mod> getModIdMap() {\n        Map<String, Mod> modIdMap = this.modIdMap;\n        if (modIdMap != null)\n            return modIdMap;\n        synchronized (this) {\n            modIdMap = this.modIdMap;\n            if (modIdMap != null)\n                return modIdMap;\n\n            List<Mod> mods = getMods();\n            modIdMap = new HashMap<>(mods.size());\n            for (Mod mod : mods) {\n                for (String id : mod.getModIds()) {\n                    if (StringUtils.isNotBlank(id)) {\n                        modIdMap.putIfAbsent(id, mod);\n                    }\n                }\n            }\n\n            return this.modIdMap = modIdMap;\n        }\n    }\n\n    private @NotNull Map<String, Mod> getSubnameMap() {\n        Map<String, Mod> subnameMap = this.subnameMap;\n        if (subnameMap != null)\n            return subnameMap;\n        synchronized (this) {\n            subnameMap = this.subnameMap;\n            if (subnameMap != null)\n                return subnameMap;\n\n            subnameMap = new HashMap<>();\n\n            List<Mod> mods = getMods();\n            for (Mod mod : mods) {\n                String subname = cleanSubname(mod.getSubname());\n                if (StringUtils.isNotBlank(subname)) {\n                    subnameMap.putIfAbsent(subname, mod);\n                }\n            }\n\n            return this.subnameMap = subnameMap;\n        }\n    }\n\n    private @NotNull Map<String, Mod> getCurseForgeMap() {\n        Map<String, Mod> curseForgeMap = this.curseForgeMap;\n        if (curseForgeMap != null)\n            return curseForgeMap;\n\n        synchronized (this) {\n            curseForgeMap = this.curseForgeMap;\n            if (curseForgeMap != null)\n                return curseForgeMap;\n\n            List<Mod> mods = getMods();\n            curseForgeMap = new HashMap<>(mods.size());\n            for (Mod mod : mods) {\n                if (StringUtils.isNotBlank(mod.getCurseforge())) {\n                    curseForgeMap.putIfAbsent(mod.getCurseforge(), mod);\n                }\n            }\n\n            return this.curseForgeMap = curseForgeMap;\n        }\n    }\n\n    private @NotNull List<Pair<String, Mod>> getKeywords() {\n        List<Pair<String, Mod>> keywords = this.keywords;\n        if (keywords != null)\n            return keywords;\n\n        synchronized (this) {\n            keywords = this.keywords;\n            if (keywords != null)\n                return keywords;\n\n            List<Mod> mods = getMods();\n\n            keywords = new ArrayList<>();\n            int maxKeywordLength = -1;\n            for (Mod mod : mods) {\n                if (StringUtils.isNotBlank(mod.getName())) {\n                    keywords.add(pair(mod.getName(), mod));\n                    maxKeywordLength = Math.max(maxKeywordLength, mod.getName().length());\n                }\n                if (StringUtils.isNotBlank(mod.getSubname())) {\n                    keywords.add(pair(mod.getSubname(), mod));\n                    maxKeywordLength = Math.max(maxKeywordLength, mod.getSubname().length());\n                }\n                if (StringUtils.isNotBlank(mod.getAbbr())) {\n                    keywords.add(pair(mod.getAbbr(), mod));\n                    maxKeywordLength = Math.max(maxKeywordLength, mod.getAbbr().length());\n                }\n            }\n\n            this.maxKeywordLength = maxKeywordLength;\n            return this.keywords = keywords;\n        }\n    }\n\n    private int getMaxKeywordLength() {\n        int maxKeywordLength = this.maxKeywordLength;\n        if (maxKeywordLength >= 0)\n            return maxKeywordLength;\n\n        // Ensure maxKeywordLength is initialized\n        getKeywords();\n        return this.maxKeywordLength;\n    }\n\n    @Nullable\n    public Mod getModByCurseForgeId(String id) {\n        if (StringUtils.isBlank(id)) return null;\n\n        return getCurseForgeMap().get(id);\n    }\n\n    @Nullable\n    public Mod getMod(String id, String subname) {\n        subname = cleanSubname(subname);\n        if (StringUtils.isNotBlank(subname)) {\n            Mod mod = getSubnameMap().get(subname);\n            if (mod != null && (StringUtils.isBlank(id) || mod.getModIds().contains(id)))\n                return mod;\n        }\n\n        if (StringUtils.isNotBlank(id))\n            return getModIdMap().get(id);\n\n        return null;\n    }\n\n    public abstract String getMcmodUrl(Mod mod);\n\n    public List<Mod> searchMod(String query) {\n        StringBuilder newQuery = query.chars()\n                .filter(ch -> !Character.isSpaceChar(ch))\n                .collect(StringBuilder::new, (sb, value) -> sb.append((char) value), StringBuilder::append);\n        query = newQuery.toString();\n\n        StringUtils.LongestCommonSubsequence lcs = new StringUtils.LongestCommonSubsequence(query.length(), getMaxKeywordLength());\n        List<Pair<Integer, Mod>> modList = new ArrayList<>();\n        for (Pair<String, Mod> keyword : getKeywords()) {\n            int value = lcs.calc(query, keyword.getKey());\n            if (value >= Math.max(1, query.length() - 3)) {\n                modList.add(pair(value, keyword.getValue()));\n            }\n        }\n        return modList.stream()\n                .sorted((a, b) -> -a.getKey().compareTo(b.getKey()))\n                .map(Pair::getValue)\n                .toList();\n    }\n\n    public static final class Mod {\n        private final String curseforge;\n        private final String mcmod;\n        private final List<String> modIds;\n        private final String name;\n        private final String subname;\n        private final String abbr;\n\n        public Mod(String line) {\n            String[] items = line.split(\";\", -1);\n            if (items.length != 6) {\n                throw new IllegalArgumentException(\"Illegal mod data line, 6 items expected \" + line);\n            }\n\n            curseforge = items[0];\n            mcmod = items[1];\n            modIds = List.of(items[2].split(\",\"));\n            name = items[3];\n            subname = items[4];\n            abbr = items[5];\n        }\n\n        public Mod(String curseforge, String mcmod, List<String> modIds, String name, String subname, String abbr) {\n            this.curseforge = curseforge;\n            this.mcmod = mcmod;\n            this.modIds = modIds;\n            this.name = name;\n            this.subname = subname;\n            this.abbr = abbr;\n        }\n\n        public String getDisplayName() {\n            StringBuilder builder = new StringBuilder();\n            if (StringUtils.isNotBlank(abbr)) {\n                builder.append(\"[\").append(abbr.trim()).append(\"] \");\n            }\n            builder.append(name);\n            if (StringUtils.isNotBlank(subname)) {\n                builder.append(\" (\").append(subname).append(\")\");\n            }\n            return builder.toString();\n        }\n\n        public String getCurseforge() {\n            return curseforge;\n        }\n\n        public String getMcmod() {\n            return mcmod;\n        }\n\n        public List<String> getModIds() {\n            return modIds;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public String getSubname() {\n            return subname;\n        }\n\n        public String getAbbr() {\n            return abbr;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXCheckBox;\nimport javafx.beans.property.*;\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.TableColumn;\nimport javafx.scene.control.TableView;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.JFXCheckBoxTableCell;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.PageCloseEvent;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.io.CSVTable;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class ModUpdatesPage extends BorderPane implements DecoratorPage {\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n(\"mods.check_updates\")));\n\n    private final ModManager modManager;\n    private final ObservableList<ModUpdateObject> objects;\n\n    @SuppressWarnings(\"unchecked\")\n    public ModUpdatesPage(ModManager modManager, List<LocalModFile.ModUpdate> updates) {\n        this.modManager = modManager;\n\n        getStyleClass().add(\"gray-background\");\n\n        TableColumn<ModUpdateObject, Boolean> enabledColumn = new TableColumn<>();\n        var allEnabledBox = new JFXCheckBox();\n        enabledColumn.setStyle(\"-fx-alignment: CENTER;\");\n        enabledColumn.setGraphic(allEnabledBox);\n        enabledColumn.setCellFactory(JFXCheckBoxTableCell.forTableColumn(enabledColumn));\n        setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty);\n        enabledColumn.setEditable(true);\n        enabledColumn.setMaxWidth(40);\n        enabledColumn.setMinWidth(40);\n\n        TableColumn<ModUpdateObject, String> fileNameColumn = new TableColumn<>(i18n(\"mods.check_updates.file\"));\n        fileNameColumn.setPrefWidth(200);\n        setupCellValueFactory(fileNameColumn, ModUpdateObject::fileNameProperty);\n\n        TableColumn<ModUpdateObject, String> currentVersionColumn = new TableColumn<>(i18n(\"mods.check_updates.current_version\"));\n        currentVersionColumn.setPrefWidth(200);\n        setupCellValueFactory(currentVersionColumn, ModUpdateObject::currentVersionProperty);\n\n        TableColumn<ModUpdateObject, String> targetVersionColumn = new TableColumn<>(i18n(\"mods.check_updates.target_version\"));\n        targetVersionColumn.setPrefWidth(200);\n        setupCellValueFactory(targetVersionColumn, ModUpdateObject::targetVersionProperty);\n\n        TableColumn<ModUpdateObject, String> sourceColumn = new TableColumn<>(i18n(\"mods.check_updates.source\"));\n        setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty);\n\n        objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList()));\n        FXUtils.bindAllEnabled(allEnabledBox.selectedProperty(), objects.stream().map(o -> o.enabled).toArray(BooleanProperty[]::new));\n\n        TableView<ModUpdateObject> table = new TableView<>(objects);\n        table.setEditable(true);\n        table.getColumns().setAll(enabledColumn, fileNameColumn, currentVersionColumn, targetVersionColumn, sourceColumn);\n        setMargin(table, new Insets(10, 10, 5, 10));\n\n        setCenter(table);\n\n        HBox actions = new HBox(8);\n        actions.setPadding(new Insets(8));\n        actions.setAlignment(Pos.CENTER_RIGHT);\n\n        JFXButton exportListButton = FXUtils.newRaisedButton(i18n(\"button.export\"));\n        exportListButton.setOnAction(e -> exportList());\n\n        JFXButton nextButton = FXUtils.newRaisedButton(i18n(\"mods.check_updates.confirm\"));\n        nextButton.setOnAction(e -> updateMods());\n\n        JFXButton cancelButton = FXUtils.newRaisedButton(i18n(\"button.cancel\"));\n        cancelButton.setOnAction(e -> fireEvent(new PageCloseEvent()));\n        onEscPressed(this, cancelButton::fire);\n        onEscPressed(table, cancelButton::fire);\n\n        actions.getChildren().setAll(exportListButton, nextButton, cancelButton);\n        setBottom(actions);\n    }\n\n    private <T> void setupCellValueFactory(TableColumn<ModUpdateObject, T> column, Function<ModUpdateObject, ObservableValue<T>> mapper) {\n        column.setCellValueFactory(param -> mapper.apply(param.getValue()));\n    }\n\n    private void updateMods() {\n        ModUpdateTask task = new ModUpdateTask(\n                modManager,\n                objects.stream()\n                        .filter(o -> o.enabled.get())\n                        .map(object -> pair(object.data.getLocalMod(), object.data.getCandidate()))\n                        .collect(Collectors.toList()));\n        Controllers.taskDialog(\n                task.whenComplete(Schedulers.javafx(), exception -> {\n                    fireEvent(new PageCloseEvent());\n                    if (!task.getFailedMods().isEmpty()) {\n                        Controllers.dialog(i18n(\"mods.check_updates.failed_download\") + \"\\n\" +\n                                        task.getFailedMods().stream().map(LocalModFile::getFileName).collect(Collectors.joining(\"\\n\")),\n                                i18n(\"install.failed\"),\n                                MessageDialogPane.MessageType.ERROR);\n                    }\n\n                    if (exception == null) {\n                        Controllers.dialog(i18n(\"install.success\"));\n                    }\n                }),\n                i18n(\"mods.check_updates\"),\n                TaskCancellationAction.NORMAL);\n    }\n\n    private void exportList() {\n        Path path = Paths.get(\"hmcl-mod-update-list-\" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH-mm-ss\")) + \".csv\").toAbsolutePath();\n\n        Controllers.taskDialog(Task.runAsync(() -> {\n            CSVTable csvTable = new CSVTable();\n\n            csvTable.set(0, 0, \"Source File Name\");\n            csvTable.set(1, 0, \"Current Version\");\n            csvTable.set(2, 0, \"Target Version\");\n            csvTable.set(3, 0, \"Update Source\");\n\n            for (int i = 0; i < objects.size(); i++) {\n                csvTable.set(0, i + 1, objects.get(i).fileName.get());\n                csvTable.set(1, i + 1, objects.get(i).currentVersion.get());\n                csvTable.set(2, i + 1, objects.get(i).targetVersion.get());\n                csvTable.set(3, i + 1, objects.get(i).source.get());\n            }\n\n            csvTable.write(path);\n\n            FXUtils.showFileInExplorer(path);\n        }).whenComplete(Schedulers.javafx(), exception -> {\n            if (exception == null) {\n                Controllers.dialog(path.toString(), i18n(\"message.success\"));\n            } else {\n                Controllers.dialog(\"\", i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n            }\n        }), i18n(\"button.export\"), TaskCancellationAction.NORMAL);\n    }\n\n    @Override\n    public ReadOnlyObjectWrapper<State> stateProperty() {\n        return state;\n    }\n\n    private static final class ModUpdateObject {\n        final LocalModFile.ModUpdate data;\n        final BooleanProperty enabled = new SimpleBooleanProperty();\n        final StringProperty fileName = new SimpleStringProperty();\n        final StringProperty currentVersion = new SimpleStringProperty();\n        final StringProperty targetVersion = new SimpleStringProperty();\n        final StringProperty source = new SimpleStringProperty();\n\n        public ModUpdateObject(LocalModFile.ModUpdate data) {\n            this.data = data;\n\n            enabled.set(!data.getLocalMod().getModManager().isDisabled(data.getLocalMod().getFile()));\n            fileName.set(data.getLocalMod().getFileName());\n            currentVersion.set(data.getCurrentVersion().getVersion());\n            targetVersion.set(data.getCandidate().getVersion());\n            switch (data.getCurrentVersion().getSelf().getType()) {\n                case CURSEFORGE:\n                    source.set(i18n(\"mods.curseforge\"));\n                    break;\n                case MODRINTH:\n                    source.set(i18n(\"mods.modrinth\"));\n            }\n        }\n\n        public boolean isEnabled() {\n            return enabled.get();\n        }\n\n        public BooleanProperty enabledProperty() {\n            return enabled;\n        }\n\n        public void setEnabled(boolean enabled) {\n            this.enabled.set(enabled);\n        }\n\n        public String getFileName() {\n            return fileName.get();\n        }\n\n        public StringProperty fileNameProperty() {\n            return fileName;\n        }\n\n        public void setFileName(String fileName) {\n            this.fileName.set(fileName);\n        }\n\n        public String getCurrentVersion() {\n            return currentVersion.get();\n        }\n\n        public StringProperty currentVersionProperty() {\n            return currentVersion;\n        }\n\n        public void setCurrentVersion(String currentVersion) {\n            this.currentVersion.set(currentVersion);\n        }\n\n        public String getTargetVersion() {\n            return targetVersion.get();\n        }\n\n        public StringProperty targetVersionProperty() {\n            return targetVersion;\n        }\n\n        public void setTargetVersion(String targetVersion) {\n            this.targetVersion.set(targetVersion);\n        }\n\n        public String getSource() {\n            return source.get();\n        }\n\n        public StringProperty sourceProperty() {\n            return source;\n        }\n\n        public void setSource(String source) {\n            this.source.set(source);\n        }\n    }\n\n    public static class ModUpdateTask extends Task<Void> {\n        private final Collection<Task<?>> dependents;\n        private final List<LocalModFile> failedMods = new ArrayList<>();\n\n        ModUpdateTask(ModManager modManager, List<Pair<LocalModFile, RemoteMod.Version>> mods) {\n            setStage(\"mods.check_updates.confirm\");\n            getProperties().put(\"total\", mods.size());\n\n            this.dependents = new ArrayList<>();\n            for (Pair<LocalModFile, RemoteMod.Version> mod : mods) {\n                LocalModFile local = mod.getKey();\n                RemoteMod.Version remote = mod.getValue();\n                boolean isDisabled = local.getModManager().isDisabled(local.getFile());\n\n                dependents.add(Task\n                        .runAsync(Schedulers.javafx(), () -> local.setOld(true))\n                        .thenComposeAsync(() -> {\n                            String fileName = remote.getFile().getFilename();\n                            if (isDisabled)\n                                fileName += ModManager.DISABLED_EXTENSION;\n\n                            var task = new FileDownloadTask(\n                                    remote.getFile().getUrl(),\n                                    modManager.getModsDirectory().resolve(fileName));\n\n                            task.setName(remote.getName());\n                            return task;\n                        })\n                        .whenComplete(Schedulers.javafx(), exception -> {\n                            if (exception != null) {\n                                // restore state if failed\n                                local.setOld(false);\n                                if (isDisabled)\n                                    local.disable();\n                                failedMods.add(local);\n                            }\n                        })\n                        .withCounter(\"mods.check_updates.confirm\"));\n            }\n        }\n\n        public List<LocalModFile> getFailedMods() {\n            return failedMods;\n        }\n\n        @Override\n        public Collection<Task<?>> getDependents() {\n            return dependents;\n        }\n\n        @Override\n        public boolean doPreExecute() {\n            return true;\n        }\n\n        @Override\n        public void preExecute() {\n            notifyPropertiesChanged();\n        }\n\n        @Override\n        public boolean isRelyingOnDependents() {\n            return false;\n        }\n\n        @Override\n        public void execute() throws Exception {\n            if (!isDependentsSucceeded())\n                throw getException();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcepackListPage.java",
    "content": "package org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXListView;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.Skin;\nimport javafx.scene.image.Image;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.resourcepack.ResourcepackFile;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.*;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.lang.ref.WeakReference;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class ResourcepackListPage extends ListPageBase<ResourcepackListPage.ResourcepackInfoObject> implements VersionPage.VersionLoadable {\n    private Path resourcepackDirectory;\n\n    public ResourcepackListPage() {\n        FXUtils.applyDragListener(this, file -> file.getFileName().toString().endsWith(\".zip\"), this::addFiles);\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new ResourcepackListPageSkin(this);\n    }\n\n    @Override\n    public void loadVersion(Profile profile, String version) {\n        this.resourcepackDirectory = profile.getRepository().getResourcepacksDirectory(version);\n\n        try {\n            if (!Files.exists(resourcepackDirectory)) {\n                Files.createDirectories(resourcepackDirectory);\n            }\n        } catch (IOException e) {\n            LOG.warning(\"Failed to create resourcepack directory\" + resourcepackDirectory, e);\n        }\n        refresh();\n    }\n\n    public void refresh() {\n        if (resourcepackDirectory == null || !Files.isDirectory(resourcepackDirectory)) return;\n        setLoading(true);\n        Task.supplyAsync(Schedulers.io(), () -> {\n            try (Stream<Path> stream = Files.list(resourcepackDirectory)) {\n                return stream.sorted(Comparator.comparing(FileUtils::getName))\n                        .flatMap(item -> {\n                            try {\n                                return Stream.of(ResourcepackFile.parse(item)).filter(Objects::nonNull).map(ResourcepackInfoObject::new);\n                            } catch (IOException e) {\n                                LOG.warning(\"Failed to load resourcepack \" + item, e);\n                                return Stream.empty();\n                            }\n                        })\n                        .toList();\n            }\n        }).whenComplete(Schedulers.javafx(), ((result, exception) -> {\n            if (exception == null) {\n                getItems().setAll(result);\n            } else {\n                LOG.warning(\"Failed to load resourcepacks\", exception);\n                getItems().clear();\n            }\n            setLoading(false);\n        })).start();\n    }\n\n    public void addFiles(List<Path> files) {\n        if (resourcepackDirectory == null) return;\n\n        try {\n            for (Path file : files) {\n                Path target = resourcepackDirectory.resolve(file.getFileName());\n                if (!Files.exists(target)) {\n                    Files.copy(file, target);\n                }\n            }\n        } catch (IOException e) {\n            LOG.warning(\"Failed to add resourcepacks\", e);\n            Controllers.dialog(i18n(\"resourcepack.add.failed\"), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n        }\n\n        refresh();\n    }\n\n    public void onAddFiles() {\n        FileChooser fileChooser = new FileChooser();\n        fileChooser.setTitle(i18n(\"resourcepack.add.title\"));\n        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"extension.resourcepack\"), \"*.zip\"));\n        List<Path> files = FileUtils.toPaths(fileChooser.showOpenMultipleDialog(Controllers.getStage()));\n        if (files != null && !files.isEmpty()) {\n            addFiles(files);\n        }\n    }\n\n    private void onDownload() {\n        Controllers.getDownloadPage().showResourcepackDownloads();\n        Controllers.navigate(Controllers.getDownloadPage());\n    }\n\n    private static final class ResourcepackListPageSkin extends ToolbarListPageSkin<ResourcepackInfoObject, ResourcepackListPage> {\n\n        public ResourcepackListPageSkin(ResourcepackListPage control) {\n            super(control);\n        }\n\n        @Override\n        protected List<Node> initializeToolbar(ResourcepackListPage skinnable) {\n            return List.of(\n                    createToolbarButton2(i18n(\"button.refresh\"), SVG.REFRESH, skinnable::refresh),\n                    createToolbarButton2(i18n(\"resourcepack.add\"), SVG.ADD, skinnable::onAddFiles),\n                    createToolbarButton2(i18n(\"resourcepack.download\"), SVG.DOWNLOAD, skinnable::onDownload)\n            );\n        }\n\n        @Override\n        protected ListCell<ResourcepackInfoObject> createListCell(JFXListView<ResourcepackInfoObject> listView) {\n            return new ResourcepackListCell(listView, getSkinnable());\n        }\n    }\n\n    public static class ResourcepackInfoObject {\n        private final ResourcepackFile file;\n        private WeakReference<Image> iconCache;\n\n        public ResourcepackInfoObject(ResourcepackFile file) {\n            this.file = file;\n        }\n\n        public ResourcepackFile getFile() {\n            return file;\n        }\n\n        Image getIcon() {\n            Image image = null;\n            if (iconCache != null && (image = iconCache.get()) != null) {\n                return image;\n            }\n            byte[] iconData = file.getIcon();\n            if (iconData != null) {\n                try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) {\n                    image = new Image(inputStream, 64, 64, true, true);\n                } catch (Exception e) {\n                    LOG.warning(\"Failed to load resourcepack icon \" + file.getPath(), e);\n                }\n            }\n\n            if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 ||\n                    (Math.abs(image.getWidth() - image.getHeight()) >= 1)) {\n                image = FXUtils.newBuiltinImage(\"/assets/img/unknown_pack.png\");\n            }\n            iconCache = new WeakReference<>(image);\n            return image;\n        }\n    }\n\n    private static final class ResourcepackListCell extends MDListCell<ResourcepackInfoObject> {\n        private final ImageContainer imageView = new ImageContainer(32);\n        private final TwoLineListItem content = new TwoLineListItem();\n        private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);\n        private final JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);\n        private final ResourcepackListPage page;\n\n        public ResourcepackListCell(JFXListView<ResourcepackInfoObject> listView, ResourcepackListPage page) {\n            super(listView);\n\n            this.page = page;\n\n            BorderPane root = new BorderPane();\n            root.getStyleClass().add(\"md-list-cell\");\n            root.setPadding(new Insets(8));\n\n            HBox left = new HBox(8);\n            left.setAlignment(Pos.CENTER);\n            left.getChildren().add(imageView);\n            left.setPadding(new Insets(0, 8, 0, 0));\n            FXUtils.setLimitWidth(left, 48);\n            root.setLeft(left);\n\n            HBox.setHgrow(content, Priority.ALWAYS);\n            root.setCenter(content);\n\n            HBox right = new HBox(8);\n            right.setAlignment(Pos.CENTER_RIGHT);\n            right.getChildren().setAll(btnReveal, btnDelete);\n            root.setRight(right);\n\n            getContainer().getChildren().add(new RipplerContainer(root));\n        }\n\n        @Override\n        protected void updateControl(ResourcepackListPage.ResourcepackInfoObject item, boolean empty) {\n            if (empty || item == null) {\n                return;\n            }\n\n            ResourcepackFile file = item.getFile();\n            imageView.setImage(item.getIcon());\n\n            content.setTitle(file.getName());\n            LocalModFile.Description description = file.getDescription();\n            content.setSubtitle(description != null ? description.toString() : \"\");\n\n            FXUtils.installFastTooltip(btnReveal, i18n(\"reveal.in_file_manager\"));\n            btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(file.getPath()));\n\n            btnDelete.setOnAction(event ->\n                    Controllers.confirm(i18n(\"button.remove.confirm\"), i18n(\"button.remove\"),\n                            () -> onDelete(file), null));\n        }\n\n        private void onDelete(ResourcepackFile file) {\n            try {\n                if (Files.isDirectory(file.getPath())) {\n                    FileUtils.deleteDirectory(file.getPath());\n                } else {\n                    Files.delete(file.getPath());\n                }\n                page.refresh();\n            } catch (IOException e) {\n                Controllers.dialog(i18n(\"resourcepack.delete.failed\", e.getMessage()), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n                LOG.warning(\"Failed to delete resourcepack\", e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXDialogLayout;\nimport com.jfoenix.controls.JFXListView;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.PixelWriter;\nimport javafx.scene.image.WritableImage;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.schematic.LitematicFile;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.*;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.FileAlreadyExistsException;\nimport java.nio.file.Files;\nimport java.nio.file.NoSuchFileException;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class SchematicsPage extends ListPageBase<SchematicsPage.Item> implements VersionPage.VersionLoadable {\n\n    private static String translateAuthorName(String author) {\n        if (I18n.isUseChinese() && \"hsds\".equals(author)) {\n            return \"黑山大叔\";\n        }\n        return author;\n    }\n\n    private Path schematicsDirectory;\n    private DirItem currentDirectory;\n\n    public SchematicsPage() {\n        FXUtils.applyDragListener(this,\n                file -> currentDirectory != null && Files.isRegularFile(file) && FileUtils.getName(file).endsWith(\".litematic\"),\n                this::addFiles\n        );\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new SchematicsPageSkin();\n    }\n\n    @Override\n    public void loadVersion(Profile profile, String version) {\n        this.schematicsDirectory = profile.getRepository().getSchematicsDirectory(version);\n\n        refresh();\n    }\n\n    public void refresh() {\n        Path schematicsDirectory = this.schematicsDirectory;\n        if (schematicsDirectory == null) return;\n\n        setLoading(true);\n        Task.supplyAsync(() -> loadAll(schematicsDirectory, null))\n                .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                    setLoading(false);\n                    if (exception == null) {\n                        DirItem target = result;\n                        if (currentDirectory != null) {\n                            loop:\n                            for (int i = 0; i < currentDirectory.relativePath.size(); i++) {\n                                String dirName = currentDirectory.relativePath.get(i);\n\n                                for (Item child : target.children) {\n                                    if (child instanceof DirItem && child.getName().equals(dirName)) {\n                                        target = (DirItem) child;\n                                        continue loop;\n                                    }\n                                }\n                                break;\n                            }\n                        }\n\n                        navigateTo(target);\n                    } else {\n                        LOG.warning(\"Failed to load schematics\", exception);\n                    }\n                }).start();\n    }\n\n    public void addFiles(List<Path> files) {\n        if (currentDirectory == null)\n            return;\n\n        Path dir = currentDirectory.path;\n        try {\n            // Can be executed in the background, but be careful that users can call loadVersion during this time\n            Files.createDirectories(dir);\n            for (Path file : files) {\n                Files.copy(file, dir.resolve(file.getFileName()));\n            }\n            refresh();\n        } catch (FileAlreadyExistsException ignored) {\n        } catch (IOException e) {\n            Controllers.dialog(i18n(\"schematics.add.failed\"), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n            LOG.warning(\"Failed to add schematics to \" + dir, e);\n        }\n    }\n\n    public void onAddFiles() {\n        FileChooser fileChooser = new FileChooser();\n        fileChooser.setTitle(i18n(\"schematics.add.title\"));\n        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(\n                i18n(\"extension.schematic\"), \"*.litematic\"));\n        List<Path> files = FileUtils.toPaths(fileChooser.showOpenMultipleDialog(Controllers.getStage()));\n        if (files != null && !files.isEmpty()) {\n            addFiles(files);\n        }\n    }\n\n    public void onCreateDirectory() {\n        if (currentDirectory == null)\n            return;\n\n        Path parent = currentDirectory.path;\n        Controllers.dialog(new InputDialogPane(\n                i18n(\"schematics.create_directory.prompt\"),\n                \"\",\n                (result, handler) -> {\n                    if (StringUtils.isBlank(result)) {\n                        handler.reject(i18n(\"schematics.create_directory.failed.empty_name\"));\n                        return;\n                    }\n\n                    if (result.contains(\"/\") || result.contains(\"\\\\\") || !FileUtils.isNameValid(result)) {\n                        handler.reject(i18n(\"schematics.create_directory.failed.invalid_name\"));\n                        return;\n                    }\n\n                    Path targetDir = parent.resolve(result);\n                    if (Files.exists(targetDir)) {\n                        handler.reject(i18n(\"schematics.create_directory.failed.already_exists\"));\n                        return;\n                    }\n\n                    try {\n                        Files.createDirectories(targetDir);\n                        handler.resolve();\n                        refresh();\n                    } catch (IOException e) {\n                        LOG.warning(\"Failed to create directory: \" + targetDir, e);\n                        handler.reject(i18n(\"schematics.create_directory.failed\", targetDir));\n                    }\n                }));\n    }\n\n    private DirItem loadAll(Path dir, @Nullable DirItem parent) {\n        DirItem item = new DirItem(dir, parent);\n\n        try (Stream<Path> stream = Files.list(dir)) {\n            for (Path path : Lang.toIterable(stream)) {\n                if (Files.isDirectory(path)) {\n                    item.children.add(loadAll(path, item));\n                } else if (path.getFileName().toString().endsWith(\".litematic\") && Files.isRegularFile(path)) {\n                    try {\n                        item.children.add(new LitematicFileItem(LitematicFile.load(path)));\n                    } catch (IOException e) {\n                        LOG.warning(\"Failed to load litematic file: \" + path, e);\n                    }\n                }\n            }\n        } catch (NoSuchFileException ignored) {\n        } catch (IOException e) {\n            LOG.warning(\"Failed to load schematics in \" + dir, e);\n        }\n\n        item.children.sort(Comparator.naturalOrder());\n        return item;\n    }\n\n    private void navigateTo(DirItem item) {\n        currentDirectory = item;\n        getItems().clear();\n        if (item.parent != null) {\n            getItems().add(new BackItem(item.parent));\n        }\n        getItems().addAll(item.children);\n    }\n\n    abstract sealed class Item implements Comparable<Item> {\n\n        boolean isDirectory() {\n            return this instanceof DirItem;\n        }\n\n        abstract Path getPath();\n\n        abstract String getName();\n\n        abstract String getDescription();\n\n        abstract SVG getIcon();\n\n        Node getIcon(int size) {\n            StackPane icon = new StackPane();\n            icon.setPrefSize(size, size);\n            icon.setMaxSize(size, size);\n            icon.getChildren().add(getIcon().createIcon(size));\n            return icon;\n        }\n\n        abstract int order();\n\n        abstract void onClick();\n\n        abstract void onReveal();\n\n        abstract void onDelete();\n\n        @Override\n        public int compareTo(@NotNull SchematicsPage.Item o) {\n            if (this.order() != o.order())\n                return Integer.compare(this.order(), o.order());\n\n            return this.getName().compareTo(o.getName());\n        }\n    }\n\n    private final class BackItem extends Item {\n\n        private final DirItem parent;\n\n        BackItem(DirItem parent) {\n            this.parent = parent;\n        }\n\n        @Override\n        int order() {\n            return 0;\n        }\n\n        @Override\n        Path getPath() {\n            return null;\n        }\n\n        @Override\n        String getName() {\n            return \"..\";\n        }\n\n        @Override\n        String getDescription() {\n            return i18n(\"schematics.back_to\", parent.getName());\n        }\n\n        @Override\n        SVG getIcon() {\n            return SVG.FOLDER;\n        }\n\n        @Override\n        void onClick() {\n            navigateTo(parent);\n        }\n\n        @Override\n        void onReveal() {\n            throw new UnsupportedOperationException(\"Unreachable\");\n        }\n\n        @Override\n        void onDelete() {\n            throw new UnsupportedOperationException(\"Unreachable\");\n        }\n    }\n\n    private final class DirItem extends Item {\n        final Path path;\n        final @Nullable DirItem parent;\n        final List<Item> children = new ArrayList<>();\n        final List<String> relativePath;\n\n        DirItem(Path path, @Nullable DirItem parent) {\n            this.path = path;\n            this.parent = parent;\n\n            if (parent != null) {\n                this.relativePath = new ArrayList<>(parent.relativePath);\n                relativePath.add(path.getFileName().toString());\n            } else {\n                this.relativePath = Collections.emptyList();\n            }\n        }\n\n        @Override\n        int order() {\n            return 1;\n        }\n\n        @Override\n        Path getPath() {\n            return path;\n        }\n\n        @Override\n        public String getName() {\n            return path.getFileName().toString();\n        }\n\n        @Override\n        String getDescription() {\n            return i18n(\"schematics.sub_items\", children.size());\n        }\n\n        @Override\n        SVG getIcon() {\n            return SVG.FOLDER;\n        }\n\n        @Override\n        void onClick() {\n            navigateTo(this);\n        }\n\n        @Override\n        void onReveal() {\n            FXUtils.openFolder(path);\n        }\n\n        @Override\n        void onDelete() {\n            try {\n                FileUtils.cleanDirectory(path);\n                Files.deleteIfExists(path);\n                refresh();\n            } catch (IOException e) {\n                LOG.warning(\"Failed to delete directory: \" + path, e);\n            }\n        }\n    }\n\n    private final class LitematicFileItem extends Item {\n        final LitematicFile file;\n        final String name;\n        final Image image;\n\n        private LitematicFileItem(LitematicFile file) {\n            this.file = file;\n\n            String name = file.getName();\n            if (name != null && !\"Unnamed\".equals(name)) {\n                this.name = name;\n            } else {\n                this.name = StringUtils.removeSuffix(file.getFile().getFileName().toString(), \".litematic\");\n            }\n\n            WritableImage image = null;\n            int[] previewImageData = file.getPreviewImageData();\n            if (previewImageData != null && previewImageData.length > 0) {\n                int size = (int) Math.sqrt(previewImageData.length);\n                if ((size * size) == previewImageData.length) {\n                    image = new WritableImage(size, size);\n                    PixelWriter pixelWriter = image.getPixelWriter();\n\n                    for (int y = 0, i = 0; y < size; ++y) {\n                        for (int x = 0; x < size; ++x) {\n                            pixelWriter.setArgb(x, y, previewImageData[i++]);\n                        }\n                    }\n\n                }\n            }\n            this.image = image;\n        }\n\n        @Override\n        int order() {\n            return 2;\n        }\n\n        @Override\n        Path getPath() {\n            return file.getFile();\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        @Override\n        String getDescription() {\n            return file.getFile().getFileName().toString();\n        }\n\n        @Override\n        SVG getIcon() {\n            return SVG.SCHEMA;\n        }\n\n        public @Nullable Image getImage() {\n            return image;\n        }\n\n        Node getIcon(int size) {\n            if (image == null) {\n                return super.getIcon(size);\n            } else {\n                var imageView = new ImageContainer(size);\n                imageView.setImage(image);\n                return imageView;\n            }\n        }\n\n        @Override\n        void onClick() {\n            Controllers.dialog(new LitematicInfoDialog());\n        }\n\n        @Override\n        void onReveal() {\n            FXUtils.showFileInExplorer(file.getFile());\n        }\n\n        @Override\n        void onDelete() {\n            try {\n                Files.deleteIfExists(file.getFile());\n                refresh();\n            } catch (IOException e) {\n                LOG.warning(\"Failed to delete litematic file: \" + file.getFile(), e);\n            }\n        }\n\n        private final class LitematicInfoDialog extends JFXDialogLayout {\n            private final ComponentList details;\n\n            private void addDetailItem(String key, Object detail) {\n                BorderPane borderPane = new BorderPane();\n                borderPane.setLeft(new Label(key));\n                borderPane.setRight(new Label(detail.toString()));\n                details.getContent().add(borderPane);\n            }\n\n            private void updateContent(LitematicFile file) {\n                details.getContent().clear();\n                addDetailItem(i18n(\"schematics.info.name\"), file.getName());\n                if (StringUtils.isNotBlank(file.getAuthor()))\n                    addDetailItem(i18n(\"schematics.info.schematic_author\"), translateAuthorName(file.getAuthor()));\n                if (file.getTimeCreated() != null)\n                    addDetailItem(i18n(\"schematics.info.time_created\"), I18n.formatDateTime(file.getTimeCreated()));\n                if (file.getTimeModified() != null && !file.getTimeModified().equals(file.getTimeCreated()))\n                    addDetailItem(i18n(\"schematics.info.time_modified\"), I18n.formatDateTime(file.getTimeModified()));\n                if (file.getRegionCount() > 0)\n                    addDetailItem(i18n(\"schematics.info.region_count\"), String.valueOf(file.getRegionCount()));\n                if (file.getTotalVolume() > 0)\n                    addDetailItem(i18n(\"schematics.info.total_volume\"), file.getTotalVolume());\n                if (file.getTotalBlocks() > 0)\n                    addDetailItem(i18n(\"schematics.info.total_blocks\"), file.getTotalBlocks());\n                if (file.getEnclosingSize() != null)\n                    addDetailItem(i18n(\"schematics.info.enclosing_size\"),\n                            String.format(\"%d x %d x %d\", (int) file.getEnclosingSize().getX(),\n                                    (int) file.getEnclosingSize().getY(),\n                                    (int) file.getEnclosingSize().getZ()));\n\n                addDetailItem(i18n(\"schematics.info.version\"), file.getVersion());\n            }\n\n            LitematicInfoDialog() {\n                HBox titleBox = new HBox(8);\n                {\n                    Node icon = getIcon(40);\n\n                    TwoLineListItem title = new TwoLineListItem();\n                    title.setTitle(getName());\n                    title.setSubtitle(file.getFile().getFileName().toString());\n\n                    titleBox.getChildren().setAll(icon, title);\n                    setHeading(titleBox);\n                }\n\n                {\n                    this.details = new ComponentList();\n                    StackPane detailsContainer = new StackPane();\n                    detailsContainer.setPadding(new Insets(10, 0, 0, 0));\n                    detailsContainer.getChildren().add(details);\n                    setBody(detailsContainer);\n                }\n\n                {\n                    JFXButton okButton = new JFXButton();\n                    okButton.getStyleClass().add(\"dialog-accept\");\n                    okButton.setText(i18n(\"button.ok\"));\n                    okButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));\n                    getActions().add(okButton);\n\n                    onEscPressed(this, okButton::fire);\n                }\n\n                updateContent(file);\n            }\n        }\n    }\n\n    private static final class Cell extends ListCell<Item> {\n\n        private final RipplerContainer graphics;\n        private final BorderPane root;\n        private final StackPane left;\n        private final TwoLineListItem center;\n        private final HBox right;\n\n        private final ImageContainer iconImageView;\n        private final SVGContainer iconSVGView;\n\n        private final Tooltip tooltip = new Tooltip();\n\n        public Cell() {\n            this.root = new BorderPane();\n            root.getStyleClass().add(\"md-list-cell\");\n            root.setPadding(new Insets(8));\n\n            {\n                this.left = new StackPane();\n                left.setPadding(new Insets(0, 8, 0, 0));\n\n                this.iconImageView = new ImageContainer(32);\n                this.iconSVGView = new SVGContainer(32);\n\n                BorderPane.setAlignment(left, Pos.CENTER);\n                root.setLeft(left);\n            }\n\n            {\n                this.center = new TwoLineListItem();\n                root.setCenter(center);\n            }\n\n            {\n                this.right = new HBox(8);\n                right.setAlignment(Pos.CENTER_RIGHT);\n\n                JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);\n                FXUtils.installFastTooltip(btnReveal, i18n(\"reveal.in_file_manager\"));\n                btnReveal.setOnAction(event -> {\n                    Item item = getItem();\n                    if (item != null && !(item instanceof BackItem))\n                        item.onReveal();\n                });\n\n                JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);\n                btnDelete.setOnAction(event -> {\n                    Item item = getItem();\n                    if (item != null && !(item instanceof BackItem)) {\n                        Controllers.confirm(i18n(\"button.remove.confirm\"), i18n(\"button.remove\"),\n                                item::onDelete, null);\n                    }\n                });\n\n                right.getChildren().setAll(btnReveal, btnDelete);\n            }\n\n            this.graphics = new RipplerContainer(root);\n            FXUtils.onClicked(graphics, () -> {\n                Item item = getItem();\n                if (item != null)\n                    item.onClick();\n            });\n        }\n\n        @Override\n        protected void updateItem(Item item, boolean empty) {\n            super.updateItem(item, empty);\n\n            iconImageView.setImage(null);\n\n            if (empty || item == null) {\n                setGraphic(null);\n                center.setTitle(\"\");\n                center.setSubtitle(\"\");\n            } else {\n                if (item instanceof LitematicFileItem fileItem && fileItem.getImage() != null) {\n                    iconImageView.setImage(fileItem.getImage());\n                    left.getChildren().setAll(iconImageView);\n                } else {\n                    iconSVGView.setIcon(item.getIcon());\n                    left.getChildren().setAll(iconSVGView);\n                }\n\n                center.setTitle(item.getName());\n                center.setSubtitle(item.getDescription());\n\n                Path path = item.getPath();\n                if (path != null) {\n                    tooltip.setText(FileUtils.getAbsolutePath(path));\n                    FXUtils.installSlowTooltip(left, tooltip);\n                } else {\n                    tooltip.setText(\"\");\n                    Tooltip.uninstall(left, tooltip);\n                }\n\n                root.setRight(item instanceof BackItem ? null : right);\n\n                setGraphic(graphics);\n            }\n        }\n    }\n\n    private final class SchematicsPageSkin extends ToolbarListPageSkin<Item, SchematicsPage> {\n        SchematicsPageSkin() {\n            super(SchematicsPage.this);\n        }\n\n        @Override\n        protected List<Node> initializeToolbar(SchematicsPage skinnable) {\n            return Arrays.asList(\n                    createToolbarButton2(i18n(\"button.refresh\"), SVG.REFRESH, skinnable::refresh),\n                    createToolbarButton2(i18n(\"schematics.add\"), SVG.ADD, skinnable::onAddFiles),\n                    createToolbarButton2(i18n(\"schematics.create_directory\"), SVG.CREATE_NEW_FOLDER, skinnable::onCreateDirectory)\n            );\n        }\n\n        @Override\n        protected ListCell<Item> createListCell(JFXListView<Item> listView) {\n            return new Cell();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport javafx.scene.Node;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.FlowPane;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.event.Event;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.setting.VersionIconType;\nimport org.jackhuang.hmcl.setting.VersionSetting;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.DialogPane;\nimport org.jackhuang.hmcl.ui.construct.RipplerContainer;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class VersionIconDialog extends DialogPane {\n    private final Profile profile;\n    private final String versionId;\n    private final Runnable onFinish;\n    private final VersionSetting vs;\n\n    public VersionIconDialog(Profile profile, String versionId, Runnable onFinish) {\n        this.profile = profile;\n        this.versionId = versionId;\n        this.onFinish = onFinish;\n        this.vs = profile.getRepository().getLocalVersionSettingOrCreate(versionId);\n\n        setTitle(i18n(\"settings.icon\"));\n        FlowPane pane = new FlowPane();\n        setBody(pane);\n\n        pane.getChildren().setAll(\n                createCustomIcon(),\n                createIcon(VersionIconType.GRASS),\n                createIcon(VersionIconType.CHEST),\n                createIcon(VersionIconType.CHICKEN),\n                createIcon(VersionIconType.COMMAND),\n                createIcon(VersionIconType.APRIL_FOOLS),\n                createIcon(VersionIconType.OPTIFINE),\n                createIcon(VersionIconType.CRAFT_TABLE),\n                createIcon(VersionIconType.FABRIC),\n                createIcon(VersionIconType.LEGACY_FABRIC),\n                createIcon(VersionIconType.FORGE),\n                createIcon(VersionIconType.CLEANROOM),\n                createIcon(VersionIconType.NEO_FORGE),\n                createIcon(VersionIconType.FURNACE),\n                createIcon(VersionIconType.QUILT)\n        );\n    }\n\n    private void exploreIcon() {\n        FileChooser chooser = new FileChooser();\n        chooser.getExtensionFilters().add(FXUtils.getImageExtensionFilter());\n        Path selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));\n        if (selectedFile != null) {\n            try {\n                profile.getRepository().setVersionIconFile(versionId, selectedFile);\n\n                if (vs != null) {\n                    vs.setVersionIcon(VersionIconType.DEFAULT);\n                }\n\n                onAccept();\n            } catch (IOException | IllegalArgumentException e) {\n                LOG.error(\"Failed to set icon file: \" + selectedFile, e);\n            }\n        }\n    }\n\n    private Node createCustomIcon() {\n        Node shape = SVG.ADD_CIRCLE.createIcon(32);\n        shape.setMouseTransparent(true);\n        RipplerContainer container = new RipplerContainer(shape);\n        FXUtils.setLimitWidth(container, 36);\n        FXUtils.setLimitHeight(container, 36);\n        FXUtils.onClicked(container, this::exploreIcon);\n        return container;\n    }\n\n    private Node createIcon(VersionIconType type) {\n        ImageView imageView = new ImageView(type.getIcon());\n        imageView.setMouseTransparent(true);\n        RipplerContainer container = new RipplerContainer(imageView);\n        FXUtils.setLimitWidth(container, 36);\n        FXUtils.setLimitHeight(container, 36);\n        FXUtils.onClicked(container, () -> {\n            if (vs != null) {\n                vs.setVersionIcon(type);\n                onAccept();\n            }\n        });\n        return container;\n    }\n\n    @Override\n    protected void onAccept() {\n        profile.getRepository().onVersionIconChanged.fireEvent(new Event(this));\n        onFinish.run();\n        super.onAccept();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXPopup;\nimport javafx.application.Platform;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.*;\nimport javafx.event.Event;\nimport javafx.event.EventType;\nimport javafx.scene.Node;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.event.EventBus;\nimport org.jackhuang.hmcl.event.EventPriority;\nimport org.jackhuang.hmcl.event.RefreshedVersionsEvent;\nimport org.jackhuang.hmcl.game.GameRepository;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.util.Optional;\nimport java.util.function.Supplier;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class VersionPage extends DecoratorAnimatedPage implements DecoratorPage {\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();\n    private final TabHeader tab;\n    private final TabHeader.Tab<VersionSettingsPage> versionSettingsTab = new TabHeader.Tab<>(\"versionSettingsTab\");\n    private final TabHeader.Tab<InstallerListPage> installerListTab = new TabHeader.Tab<>(\"installerListTab\");\n    private final TabHeader.Tab<ModListPage> modListTab = new TabHeader.Tab<>(\"modListTab\");\n    private final TabHeader.Tab<WorldListPage> worldListTab = new TabHeader.Tab<>(\"worldList\");\n    private final TabHeader.Tab<SchematicsPage> schematicsTab = new TabHeader.Tab<>(\"schematicsTab\");\n    private final TabHeader.Tab<ResourcepackListPage> resourcePackTab = new TabHeader.Tab<>(\"resourcePackTab\");\n    private final TransitionPane transitionPane = new TransitionPane();\n    private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();\n    private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();\n    private final WeakListenerHolder listenerHolder = new WeakListenerHolder();\n\n    private String preferredVersionName = null;\n\n    public static class WorkingDirChangedEvent extends Event {\n        public static final EventType<WorkingDirChangedEvent> EVENT_TYPE = new EventType<>(Event.ANY, \"WORKING_DIR_CHANGED\");\n        public WorkingDirChangedEvent() {\n            super(EVENT_TYPE);\n        }\n    }\n\n    public VersionPage() {\n        versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));\n        installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));\n        modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));\n        resourcePackTab.setNodeSupplier(loadVersionFor(ResourcepackListPage::new));\n        worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new));\n        schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new));\n\n        tab = new TabHeader(transitionPane, versionSettingsTab, installerListTab, modListTab, resourcePackTab, worldListTab, schematicsTab);\n        tab.select(versionSettingsTab);\n\n        addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);\n\n        addEventHandler(WorkingDirChangedEvent.EVENT_TYPE, event -> {\n            if (this.version.get() != null) {\n                if (installerListTab.isInitialized())\n                    installerListTab.getNode().loadVersion(getProfile(), getVersion());\n                if (modListTab.isInitialized())\n                    modListTab.getNode().loadVersion(getProfile(), getVersion());\n                if (resourcePackTab.isInitialized())\n                    resourcePackTab.getNode().loadVersion(getProfile(), getVersion());\n                if (worldListTab.isInitialized())\n                    worldListTab.getNode().loadVersion(getProfile(), getVersion());\n                if (schematicsTab.isInitialized())\n                    schematicsTab.getNode().loadVersion(getProfile(), getVersion());\n            }\n        });\n        \n        listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));\n    }\n\n    private void checkSelectedVersion() {\n        runInFX(() -> {\n            if (this.version.get() == null) return;\n            GameRepository repository = this.version.get().getProfile().getRepository();\n            if (!repository.hasVersion(this.version.get().getVersion())) {\n                if (preferredVersionName != null) {\n                    loadVersion(preferredVersionName, this.version.get().getProfile());\n                } else {\n                    fireEvent(new PageCloseEvent());\n                }\n            }\n        });\n    }\n\n    private <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) {\n        return () -> {\n            T node = nodeSupplier.get();\n            if (version.get() != null) {\n                if (node instanceof VersionPage.VersionLoadable) {\n                    ((VersionLoadable) node).loadVersion(version.get().getProfile(), version.get().getVersion());\n                }\n            }\n            return node;\n        };\n    }\n\n    public void showInstanceSettings() {\n        tab.select(versionSettingsTab, false);\n    }\n\n    public void setVersion(String version, Profile profile) {\n        this.version.set(new Profile.ProfileVersion(profile, version));\n    }\n\n    public void loadVersion(String version, Profile profile) {\n        // If we jumped to game list page and deleted this version\n        // and back to this page, we should return to main page.\n        if (this.version.get() != null && (!getProfile().getRepository().isLoaded() ||\n                !getProfile().getRepository().hasVersion(version))) {\n            Platform.runLater(() -> fireEvent(new PageCloseEvent()));\n            return;\n        }\n\n        setVersion(version, profile);\n        preferredVersionName = version;\n\n        if (versionSettingsTab.isInitialized())\n            versionSettingsTab.getNode().loadVersion(profile, version);\n        if (installerListTab.isInitialized())\n            installerListTab.getNode().loadVersion(profile, version);\n        if (modListTab.isInitialized())\n            modListTab.getNode().loadVersion(profile, version);\n        if (resourcePackTab.isInitialized())\n            resourcePackTab.getNode().loadVersion(profile, version);\n        if (worldListTab.isInitialized())\n            worldListTab.getNode().loadVersion(profile, version);\n        if (schematicsTab.isInitialized())\n            schematicsTab.getNode().loadVersion(profile, version);\n        currentVersionUpgradable.set(profile.getRepository().isModpack(version));\n    }\n\n    private void onNavigated(Navigator.NavigationEvent event) {\n        if (this.version.get() == null)\n            throw new IllegalStateException();\n\n        // If we jumped to game list page and deleted this version\n        // and back to this page, we should return to main page.\n        if (!getProfile().getRepository().isLoaded() ||\n                !getProfile().getRepository().hasVersion(getVersion())) {\n            Platform.runLater(() -> fireEvent(new PageCloseEvent()));\n            return;\n        }\n\n        loadVersion(getVersion(), getProfile());\n    }\n\n    private void onBrowse(String sub) {\n        FXUtils.openFolder(getProfile().getRepository().getRunDirectory(getVersion()).resolve(sub));\n    }\n\n    private void redownloadAssetIndex() {\n        Versions.updateGameAssets(getProfile(), getVersion());\n    }\n\n    private void clearLibraries() {\n        FileUtils.deleteDirectoryQuietly(getProfile().getRepository().getBaseDirectory().resolve(\"libraries\"));\n    }\n\n    private void clearAssets() {\n        HMCLGameRepository baseDirectory = getProfile().getRepository();\n        FileUtils.deleteDirectoryQuietly(baseDirectory.getBaseDirectory().resolve(\"assets\"));\n        if (version.get() != null) {\n            FileUtils.deleteDirectoryQuietly(baseDirectory.getRunDirectory(version.get().getVersion()).resolve(\"resources\"));\n        }\n    }\n\n    private void clearJunkFiles() {\n        Versions.cleanVersion(getProfile(), getVersion());\n    }\n\n    private void testGame() {\n        Versions.testGame(getProfile(), getVersion());\n    }\n\n    private void updateGame() {\n        Versions.updateVersion(getProfile(), getVersion());\n    }\n\n    private void generateLaunchScript() {\n        Versions.generateLaunchScript(getProfile(), getVersion());\n    }\n\n    private void export() {\n        Versions.exportVersion(getProfile(), getVersion());\n    }\n\n    private void rename() {\n        Versions.renameVersion(getProfile(), getVersion())\n                .thenApply(newVersionName -> this.preferredVersionName = newVersionName);\n    }\n\n    private void remove() {\n        Versions.deleteVersion(getProfile(), getVersion());\n    }\n\n    private void duplicate() {\n        Versions.duplicateVersion(getProfile(), getVersion());\n    }\n\n    public Profile getProfile() {\n        return Optional.ofNullable(version.get()).map(Profile.ProfileVersion::getProfile).orElse(null);\n    }\n\n    public String getVersion() {\n        return Optional.ofNullable(version.get()).map(Profile.ProfileVersion::getVersion).orElse(null);\n    }\n\n    @Override\n    protected Skin createDefaultSkin() {\n        return new Skin(this);\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n\n    public static class Skin extends DecoratorAnimatedPageSkin<VersionPage> {\n\n        /**\n         * Constructor for all SkinBase instances.\n         *\n         * @param control The control for which this Skin should attach to.\n         */\n        protected Skin(VersionPage control) {\n            super(control);\n\n            {\n                AdvancedListBox sideBar = new AdvancedListBox()\n                        .addNavigationDrawerTab(control.tab, control.versionSettingsTab, i18n(\"settings.game\"), SVG.SETTINGS, SVG.SETTINGS_FILL)\n                        .addNavigationDrawerTab(control.tab, control.installerListTab, i18n(\"settings.tabs.installers\"), SVG.DEPLOYED_CODE, SVG.DEPLOYED_CODE_FILL)\n                        .addNavigationDrawerTab(control.tab, control.modListTab, i18n(\"mods.manage\"), SVG.EXTENSION, SVG.EXTENSION_FILL)\n                        .addNavigationDrawerTab(control.tab, control.resourcePackTab, i18n(\"resourcepack.manage\"), SVG.TEXTURE)\n                        .addNavigationDrawerTab(control.tab, control.worldListTab, i18n(\"world.manage\"), SVG.PUBLIC)\n                        .addNavigationDrawerTab(control.tab, control.schematicsTab, i18n(\"schematics.manage\"), SVG.SCHEMA, SVG.SCHEMA_FILL);\n                VBox.setVgrow(sideBar, Priority.ALWAYS);\n\n                PopupMenu browseList = new PopupMenu();\n                JFXPopup browsePopup = new JFXPopup(browseList);\n                browseList.getContent().setAll(\n                        new IconedMenuItem(SVG.STADIA_CONTROLLER, i18n(\"folder.game\"), () -> control.onBrowse(\"\"), browsePopup),\n                        new IconedMenuItem(SVG.EXTENSION, i18n(\"folder.mod\"), () -> control.onBrowse(\"mods\"), browsePopup),\n                        new IconedMenuItem(SVG.TEXTURE, i18n(\"folder.resourcepacks\"), () -> control.onBrowse(\"resourcepacks\"), browsePopup),\n                        new IconedMenuItem(SVG.PUBLIC, i18n(\"folder.saves\"), () -> control.onBrowse(\"saves\"), browsePopup),\n                        new IconedMenuItem(SVG.SCHEMA, i18n(\"folder.schematics\"), () -> control.onBrowse(\"schematics\"), browsePopup),\n                        new IconedMenuItem(SVG.WB_SUNNY, i18n(\"folder.shaderpacks\"), () -> control.onBrowse(\"shaderpacks\"), browsePopup),\n                        new IconedMenuItem(SVG.SCREENSHOT_MONITOR, i18n(\"folder.screenshots\"), () -> control.onBrowse(\"screenshots\"), browsePopup),\n                        new IconedMenuItem(SVG.SETTINGS, i18n(\"folder.config\"), () -> control.onBrowse(\"config\"), browsePopup),\n                        new IconedMenuItem(SVG.SCRIPT, i18n(\"folder.logs\"), () -> control.onBrowse(\"logs\"), browsePopup)\n                );\n\n                PopupMenu managementList = new PopupMenu();\n                JFXPopup managementPopup = new JFXPopup(managementList);\n                managementList.getContent().setAll(\n                        new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n(\"version.launch.test\"), control::testGame, managementPopup),\n                        new IconedMenuItem(SVG.SCRIPT, i18n(\"version.launch_script\"), control::generateLaunchScript, managementPopup),\n                        new MenuSeparator(),\n                        new IconedMenuItem(SVG.EDIT, i18n(\"version.manage.rename\"), control::rename, managementPopup),\n                        new IconedMenuItem(SVG.FOLDER_COPY, i18n(\"version.manage.duplicate\"), control::duplicate, managementPopup),\n                        new IconedMenuItem(SVG.DELETE, i18n(\"version.manage.remove\"), control::remove, managementPopup),\n                        new IconedMenuItem(SVG.OUTPUT, i18n(\"modpack.export\"), control::export, managementPopup),\n                        new MenuSeparator(),\n                        new IconedMenuItem(null, i18n(\"version.manage.redownload_assets_index\"), control::redownloadAssetIndex, managementPopup),\n                        new IconedMenuItem(null, i18n(\"version.manage.remove_assets\"), control::clearAssets, managementPopup),\n                        new IconedMenuItem(null, i18n(\"version.manage.remove_libraries\"), control::clearLibraries, managementPopup),\n                        new IconedMenuItem(null, i18n(\"version.manage.clean\"), control::clearJunkFiles, managementPopup).addTooltip(i18n(\"version.manage.clean.tooltip\"))\n                );\n\n                AdvancedListBox toolbar = new AdvancedListBox()\n                        .addNavigationDrawerItem(i18n(\"version.update\"), SVG.UPDATE, control::updateGame, upgradeItem -> {\n                            upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);\n                        })\n                        .addNavigationDrawerItem(i18n(\"version.launch.test\"), SVG.ROCKET_LAUNCH, control::testGame)\n                        .addNavigationDrawerItem(i18n(\"settings.game.exploration\"), SVG.FOLDER_OPEN, null, browseMenuItem -> {\n                            browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));\n                        })\n                        .addNavigationDrawerItem(i18n(\"settings.game.management\"), SVG.MENU, null, managementItem -> {\n                            managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));\n                        });\n                toolbar.getStyleClass().add(\"advanced-list-box-clear-padding\");\n                FXUtils.setLimitHeight(toolbar, 40 * 4 + 12 * 2);\n\n                setLeft(sideBar, toolbar);\n            }\n\n            control.state.bind(Bindings.createObjectBinding(() ->\n                            State.fromTitle(i18n(\"version.manage.manage.title\", getSkinnable().getVersion()), -1),\n                    getSkinnable().version));\n\n            //control.transitionPane.getStyleClass().add(\"gray-background\");\n            //FXUtils.setOverflowHidden(control.transitionPane, 8);\n            setCenter(control.transitionPane);\n        }\n    }\n\n    public interface VersionLoadable {\n        void loadVersion(Profile profile, String version);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.*;\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.*;\nimport javafx.beans.value.ChangeListener;\nimport javafx.geometry.HPos;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.geometry.Rectangle2D;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.Toggle;\nimport javafx.scene.layout.*;\nimport javafx.scene.text.Text;\nimport javafx.stage.FileChooser;\nimport javafx.stage.Screen;\nimport org.jackhuang.hmcl.game.GameDirectoryType;\nimport org.jackhuang.hmcl.game.HMCLGameRepository;\nimport org.jackhuang.hmcl.game.ProcessPriority;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.java.JavaManager;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.setting.*;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.WeakListenerHolder;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\nimport org.jackhuang.hmcl.util.javafx.PropertyUtils;\nimport org.jackhuang.hmcl.util.javafx.SafeStringConverter;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemInfo;\nimport org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.lang.ref.WeakReference;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.jackhuang.hmcl.util.DataSizeUnit.GIGABYTES;\nimport static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class VersionSettingsPage extends StackPane implements DecoratorPage, VersionPage.VersionLoadable, PageAware {\n\n    private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(new State(\"\", null, false, false, false));\n\n    private AdvancedVersionSettingPage advancedVersionSettingPage;\n\n    private VersionSetting lastVersionSetting = null;\n    private Profile profile;\n    private WeakListenerHolder listenerHolder;\n    private String versionId;\n\n    private final VBox rootPane;\n    private final JFXComboBox<String> cboWindowsSize;\n    private final JFXTextField txtServerIP;\n    private final ComponentList componentList;\n    private final LineSelectButton<LauncherVisibility> launcherVisibilityPane;\n    private final JFXCheckBox chkAutoAllocate;\n    private final JFXCheckBox chkFullscreen;\n    private final ComponentSublist javaSublist;\n    private final MultiFileItem<Pair<JavaVersionType, JavaRuntime>> javaItem;\n    private final MultiFileItem.Option<Pair<JavaVersionType, JavaRuntime>> javaAutoDeterminedOption;\n    private final MultiFileItem.StringOption<Pair<JavaVersionType, JavaRuntime>> javaVersionOption;\n    private final MultiFileItem.FileOption<Pair<JavaVersionType, JavaRuntime>> javaCustomOption;\n\n    private final ComponentSublist gameDirSublist;\n    private final MultiFileItem<GameDirectoryType> gameDirItem;\n    private final MultiFileItem.FileOption<GameDirectoryType> gameDirCustomOption;\n    private final LineSelectButton<ProcessPriority> processPriorityPane;\n    private final LineToggleButton showLogsPane;\n    private final LineToggleButton enableDebugLogOutputPane;\n    private final ImagePickerItem iconPickerItem;\n\n    private final ChangeListener<Collection<JavaRuntime>> javaListChangeListener;\n    private final InvalidationListener usesGlobalListener;\n    private final ChangeListener<Boolean> specificSettingsListener;\n    private final InvalidationListener javaListener = any -> initJavaSubtitle();\n\n    private final ChangeListener<GameDirectoryType> gameDirTypeListener = (ob, o, n) -> fireWorkingDirChanged();\n    private final ChangeListener<String> gameDirListener = (ob, o, n) -> fireWorkingDirChanged();\n\n    private boolean updatingJavaSetting = false;\n    private boolean updatingSelectedJava = false;\n\n    private final StringProperty selectedVersion = new SimpleStringProperty();\n    private final BooleanProperty navigateToSpecificSettings = new SimpleBooleanProperty(false);\n    private final BooleanProperty enableSpecificSettings = new SimpleBooleanProperty(false);\n    private final IntegerProperty maxMemory = new SimpleIntegerProperty();\n    private final BooleanProperty modpack = new SimpleBooleanProperty();\n\n    private final ReadOnlyObjectProperty<PhysicalMemoryStatus> memoryStatus = UpdateMemoryStatus.memoryStatusProperty();\n\n    public VersionSettingsPage(boolean globalSetting) {\n        ScrollPane scrollPane = new ScrollPane();\n        scrollPane.setFitToHeight(true);\n        scrollPane.setFitToWidth(true);\n        scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);\n        getChildren().setAll(scrollPane);\n\n        rootPane = new VBox();\n        rootPane.setFillWidth(true);\n        scrollPane.setContent(rootPane);\n        FXUtils.smoothScrolling(scrollPane);\n        rootPane.getStyleClass().add(\"card-list\");\n\n        if (globalSetting) {\n            HintPane specificSettingsHint = new HintPane(MessageDialogPane.MessageType.WARNING);\n            Text text = new Text();\n            text.textProperty().bind(BindingMapping.of(selectedVersion)\n                    .map(selectedVersion -> i18n(\"settings.type.special.edit.hint\", selectedVersion)));\n\n            JFXHyperlink specificSettingsLink = new JFXHyperlink(i18n(\"settings.type.special.edit\"));\n            specificSettingsLink.setOnAction(e -> editSpecificSettings());\n\n            specificSettingsHint.setChildren(text, specificSettingsLink);\n            specificSettingsHint.managedProperty().bind(navigateToSpecificSettings);\n            specificSettingsHint.visibleProperty().bind(navigateToSpecificSettings);\n\n            iconPickerItem = null;\n\n            rootPane.getChildren().addAll(specificSettingsHint);\n        } else {\n            HintPane gameDirHint = new HintPane(MessageDialogPane.MessageType.INFO);\n            gameDirHint.setText(i18n(\"settings.game.working_directory.hint\"));\n            rootPane.getChildren().add(gameDirHint);\n\n            ComponentList iconPickerItemWrapper = new ComponentList();\n            rootPane.getChildren().add(iconPickerItemWrapper);\n\n            iconPickerItem = new ImagePickerItem();\n            iconPickerItem.setImage(FXUtils.newBuiltinImage(\"/assets/img/icon.png\"));\n            iconPickerItem.setTitle(i18n(\"settings.icon\"));\n            iconPickerItem.setOnSelectButtonClicked(e -> onExploreIcon());\n            iconPickerItem.setOnDeleteButtonClicked(e -> onDeleteIcon());\n            iconPickerItemWrapper.getContent().setAll(iconPickerItem);\n\n            BorderPane settingsTypePane = new BorderPane();\n            settingsTypePane.disableProperty().bind(modpack);\n            rootPane.getChildren().add(settingsTypePane);\n\n            JFXCheckBox enableSpecificCheckBox = new JFXCheckBox();\n            enableSpecificCheckBox.selectedProperty().bindBidirectional(enableSpecificSettings);\n            settingsTypePane.setLeft(enableSpecificCheckBox);\n            enableSpecificCheckBox.setText(i18n(\"settings.type.special.enable\"));\n            BorderPane.setAlignment(enableSpecificCheckBox, Pos.CENTER_RIGHT);\n\n            JFXButton editGlobalSettingsButton = FXUtils.newRaisedButton(i18n(\"settings.type.global.edit\"));\n            settingsTypePane.setRight(editGlobalSettingsButton);\n            editGlobalSettingsButton.disableProperty().bind(enableSpecificCheckBox.selectedProperty());\n            BorderPane.setAlignment(editGlobalSettingsButton, Pos.CENTER_RIGHT);\n            editGlobalSettingsButton.setOnAction(e -> editGlobalSettings());\n        }\n\n        {\n            componentList = new ComponentList();\n\n            if (!globalSetting) {\n                var copyGlobalButton = LineButton.createNavigationButton();\n                copyGlobalButton.setTitle(i18n(\"settings.game.copy_global\"));\n                copyGlobalButton.setOnAction(event ->\n                        Controllers.confirm(i18n(\"settings.game.copy_global.copy_all.confirm\"), null, () -> {\n                            Set<String> ignored = new HashSet<>(Arrays.asList(\n                                    \"usesGlobal\",\n                                    \"versionIcon\"\n                            ));\n\n                            PropertyUtils.copyProperties(profile.getGlobal(), lastVersionSetting, name -> !ignored.contains(name));\n                        }, null));\n\n                componentList.getContent().add(copyGlobalButton);\n            }\n\n            javaItem = new MultiFileItem<>();\n            javaSublist = new ComponentSublist();\n            javaSublist.getContent().add(javaItem);\n            javaSublist.setTitle(i18n(\"settings.game.java_directory\"));\n            javaSublist.setHasSubtitle(true);\n            javaAutoDeterminedOption = new MultiFileItem.Option<>(i18n(\"settings.game.java_directory.auto\"), pair(JavaVersionType.AUTO, null));\n            javaVersionOption = new MultiFileItem.StringOption<>(i18n(\"settings.game.java_directory.version\"), pair(JavaVersionType.VERSION, null));\n            javaVersionOption.setValidators(new NumberValidator(true));\n            FXUtils.setLimitWidth(javaVersionOption.getCustomField(), 40);\n            javaCustomOption = new MultiFileItem.FileOption<Pair<JavaVersionType, JavaRuntime>>(i18n(\"settings.custom\"), pair(JavaVersionType.CUSTOM, null))\n                    .setChooserTitle(i18n(\"settings.game.java_directory.choose\"));\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)\n                javaCustomOption.addExtensionFilter(new FileChooser.ExtensionFilter(\"Java\", \"java.exe\"));\n\n            javaListChangeListener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), allJava -> {\n                List<MultiFileItem.Option<Pair<JavaVersionType, JavaRuntime>>> options = new ArrayList<>();\n                options.add(javaAutoDeterminedOption);\n                options.add(javaVersionOption);\n                if (allJava != null) {\n                    boolean isX86 = Architecture.SYSTEM_ARCH.isX86() && allJava.stream().allMatch(java -> java.getArchitecture().isX86());\n\n                    for (JavaRuntime java : allJava) {\n                        options.add(new MultiFileItem.Option<>(\n                                i18n(\"settings.game.java_directory.template\",\n                                        java.getVersion(),\n                                        isX86 ? i18n(\"settings.game.java_directory.bit\", java.getBits().getBit())\n                                                : java.getPlatform().getArchitecture().getDisplayName()),\n                                pair(JavaVersionType.DETECTED, java))\n                                .setSubtitle(java.getBinary().toString()));\n                    }\n                }\n\n                options.add(javaCustomOption);\n                javaItem.loadChildren(options);\n                initializeSelectedJava();\n            });\n\n            gameDirItem = new MultiFileItem<>();\n            gameDirSublist = new ComponentSublist();\n            gameDirSublist.getContent().add(gameDirItem);\n            gameDirSublist.setTitle(i18n(\"settings.game.working_directory\"));\n            gameDirSublist.setHasSubtitle(versionId != null);\n            gameDirItem.disableProperty().bind(modpack);\n            gameDirCustomOption = new MultiFileItem.FileOption<>(i18n(\"settings.custom\"), GameDirectoryType.CUSTOM)\n                    .setChooserTitle(i18n(\"settings.game.working_directory.choose\"))\n                    .setDirectory(true);\n\n            gameDirItem.loadChildren(Arrays.asList(\n                    new MultiFileItem.Option<>(i18n(\"settings.advanced.game_dir.default\"), GameDirectoryType.ROOT_FOLDER),\n                    new MultiFileItem.Option<>(i18n(\"settings.advanced.game_dir.independent\"), GameDirectoryType.VERSION_FOLDER),\n                    gameDirCustomOption\n            ));\n\n            VBox maxMemoryPane = new VBox(8);\n            {\n                Label title = new Label(i18n(\"settings.memory\"));\n                VBox.setMargin(title, new Insets(0, 0, 8, 0));\n\n                chkAutoAllocate = new JFXCheckBox(i18n(\"settings.memory.auto_allocate\"));\n                VBox.setMargin(chkAutoAllocate, new Insets(0, 0, 8, 5));\n\n                HBox lowerBoundPane = new HBox(8);\n                lowerBoundPane.setStyle(\"-fx-view-order: -1;\"); // prevent the indicator from being covered by the progress bar\n                lowerBoundPane.setAlignment(Pos.CENTER);\n                VBox.setMargin(lowerBoundPane, new Insets(0, 0, 0, 16));\n                {\n                    Label label = new Label();\n                    label.textProperty().bind(Bindings.createStringBinding(() -> {\n                        if (chkAutoAllocate.isSelected()) {\n                            return i18n(\"settings.memory.lower_bound\");\n                        } else {\n                            return i18n(\"settings.memory\");\n                        }\n                    }, chkAutoAllocate.selectedProperty()));\n\n                    JFXSlider slider = new JFXSlider(0, 1, 0);\n                    HBox.setMargin(slider, new Insets(0, 0, 0, 8));\n                    HBox.setHgrow(slider, Priority.ALWAYS);\n                    slider.setValueFactory(self -> Bindings.createStringBinding(() -> (int) (self.getValue() * 100) + \"%\", self.valueProperty()));\n                    AtomicBoolean changedByTextField = new AtomicBoolean(false);\n                    FXUtils.onChangeAndOperate(maxMemory, maxMemory -> {\n                        changedByTextField.set(true);\n                        slider.setValue(maxMemory.intValue() * 1.0 / MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize()));\n                        changedByTextField.set(false);\n                    });\n                    slider.valueProperty().addListener((value, oldVal, newVal) -> {\n                        if (changedByTextField.get()) return;\n                        maxMemory.set((int) (value.getValue().doubleValue() * MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize())));\n                    });\n\n                    JFXTextField txtMaxMemory = new JFXTextField();\n                    FXUtils.setLimitWidth(txtMaxMemory, 60);\n                    FXUtils.setValidateWhileTextChanged(txtMaxMemory, true);\n                    txtMaxMemory.textProperty().bindBidirectional(maxMemory, SafeStringConverter.fromInteger());\n                    txtMaxMemory.setValidators(new NumberValidator(i18n(\"input.number\"), false));\n\n                    lowerBoundPane.getChildren().setAll(label, slider, txtMaxMemory, new Label(i18n(\"settings.memory.unit.mib\")));\n                }\n\n                StackPane progressBarPane = new StackPane();\n                progressBarPane.setAlignment(Pos.CENTER_LEFT);\n                VBox.setMargin(progressBarPane, new Insets(8, 0, 0, 16));\n                {\n                    progressBarPane.setMinHeight(4);\n                    progressBarPane.getStyleClass().add(\"memory-total\");\n\n                    StackPane usedMemory = new StackPane();\n                    usedMemory.getStyleClass().add(\"memory-used\");\n                    usedMemory.maxWidthProperty().bind(Bindings.createDoubleBinding(() ->\n                                    progressBarPane.getWidth() *\n                                            (memoryStatus.get().getUsed() * 1.0 / memoryStatus.get().getTotal()), progressBarPane.widthProperty(),\n                            memoryStatus));\n                    StackPane allocateMemory = new StackPane();\n                    allocateMemory.getStyleClass().add(\"memory-allocate\");\n                    allocateMemory.maxWidthProperty().bind(Bindings.createDoubleBinding(() ->\n                                    progressBarPane.getWidth() *\n                                            Math.min(1.0,\n                                                    (double) (HMCLGameRepository.getAllocatedMemory(maxMemory.get() * 1024L * 1024L, memoryStatus.get().getAvailable(), chkAutoAllocate.isSelected())\n                                                            + memoryStatus.get().getUsed()) / memoryStatus.get().getTotal()), progressBarPane.widthProperty(),\n                            maxMemory, memoryStatus, chkAutoAllocate.selectedProperty()));\n\n                    progressBarPane.getChildren().setAll(allocateMemory, usedMemory);\n                }\n\n                BorderPane digitalPane = new BorderPane();\n                VBox.setMargin(digitalPane, new Insets(0, 0, 0, 16));\n                {\n                    Label lblPhysicalMemory = new Label();\n                    lblPhysicalMemory.getStyleClass().add(\"memory-label\");\n                    digitalPane.setLeft(lblPhysicalMemory);\n                    lblPhysicalMemory.textProperty().bind(Bindings.createStringBinding(() ->\n                            i18n(\"settings.memory.used_per_total\",\n                                    GIGABYTES.convertFromBytes(memoryStatus.get().getUsed()),\n                                    GIGABYTES.convertFromBytes(memoryStatus.get().getTotal())), memoryStatus));\n\n                    Label lblAllocateMemory = new Label();\n                    lblAllocateMemory.textProperty().bind(Bindings.createStringBinding(() -> {\n                        long maxMemory = Lang.parseInt(this.maxMemory.get(), 0) * 1024L * 1024L;\n                        return i18n(memoryStatus.get().hasAvailable() && maxMemory > memoryStatus.get().getAvailable()\n                                        ? (chkAutoAllocate.isSelected() ? \"settings.memory.allocate.auto.exceeded\" : \"settings.memory.allocate.manual.exceeded\")\n                                        : (chkAutoAllocate.isSelected() ? \"settings.memory.allocate.auto\" : \"settings.memory.allocate.manual\"),\n                                GIGABYTES.convertFromBytes(maxMemory),\n                                GIGABYTES.convertFromBytes(HMCLGameRepository.getAllocatedMemory(maxMemory, memoryStatus.get().getAvailable(), chkAutoAllocate.isSelected())),\n                                GIGABYTES.convertFromBytes(memoryStatus.get().getAvailable()));\n                    }, memoryStatus, maxMemory, chkAutoAllocate.selectedProperty()));\n                    lblAllocateMemory.getStyleClass().add(\"memory-label\");\n                    digitalPane.setRight(lblAllocateMemory);\n                }\n\n                maxMemoryPane.getChildren().setAll(title, chkAutoAllocate, lowerBoundPane, progressBarPane, digitalPane);\n            }\n\n            launcherVisibilityPane = new LineSelectButton<>();\n            launcherVisibilityPane.setTitle(i18n(\"settings.advanced.launcher_visible\"));\n            launcherVisibilityPane.setItems(LauncherVisibility.values());\n            launcherVisibilityPane.setConverter(e -> i18n(\"settings.advanced.launcher_visibility.\" + e.name().toLowerCase(Locale.ROOT)));\n\n            BorderPane dimensionPane = new BorderPane();\n            {\n                Label label = new Label(i18n(\"settings.game.dimension\"));\n                dimensionPane.setLeft(label);\n                BorderPane.setAlignment(label, Pos.CENTER_LEFT);\n\n                BorderPane right = new BorderPane();\n                dimensionPane.setRight(right);\n                {\n                    cboWindowsSize = new JFXComboBox<>();\n                    cboWindowsSize.setPrefWidth(150);\n                    right.setLeft(cboWindowsSize);\n                    cboWindowsSize.setEditable(true);\n                    cboWindowsSize.setStyle(\"-fx-padding: 4 4 4 16\");\n                    cboWindowsSize.setPromptText(\"854x480\");\n                    cboWindowsSize.getItems().setAll(getSupportedResolutions());\n\n                    chkFullscreen = new JFXCheckBox();\n                    right.setRight(chkFullscreen);\n                    chkFullscreen.setText(i18n(\"settings.game.fullscreen\"));\n                    chkFullscreen.setAlignment(Pos.CENTER);\n                    BorderPane.setAlignment(chkFullscreen, Pos.CENTER);\n                    BorderPane.setMargin(chkFullscreen, new Insets(0, 0, 0, 7));\n\n                    cboWindowsSize.disableProperty().bind(chkFullscreen.selectedProperty());\n                }\n            }\n\n            showLogsPane = new LineToggleButton();\n            showLogsPane.setTitle(i18n(\"settings.show_log\"));\n\n            enableDebugLogOutputPane = new LineToggleButton();\n            enableDebugLogOutputPane.setTitle(i18n(\"settings.enable_debug_log_output\"));\n            processPriorityPane = new LineSelectButton<>();\n            processPriorityPane.setTitle(i18n(\"settings.advanced.process_priority\"));\n            processPriorityPane.setConverter(e -> i18n(\"settings.advanced.process_priority.\" + e.name().toLowerCase(Locale.ROOT)));\n            processPriorityPane.setDescriptionConverter(e -> {\n                String bundleKey = \"settings.advanced.process_priority.\" + e.name().toLowerCase(Locale.ROOT) + \".desc\";\n                return I18n.hasKey(bundleKey) ? i18n(bundleKey) : null;\n            });\n            processPriorityPane.setItems(ProcessPriority.values());\n\n            GridPane serverPane = new GridPane();\n            {\n                ColumnConstraints title = new ColumnConstraints();\n                ColumnConstraints value = new ColumnConstraints();\n                value.setFillWidth(true);\n                value.setHgrow(Priority.ALWAYS);\n                value.setHalignment(HPos.RIGHT);\n                serverPane.setHgap(16);\n                serverPane.setVgap(8);\n                serverPane.getColumnConstraints().setAll(title, value);\n\n                txtServerIP = new JFXTextField();\n                txtServerIP.setPromptText(i18n(\"settings.advanced.server_ip.prompt\"));\n                Validator.addTo(txtServerIP).accept(str -> {\n                    if (StringUtils.isBlank(str))\n                        return true;\n                    try {\n                        ServerAddress.parse(str);\n                        return true;\n                    } catch (Exception ignored) {\n                        return false;\n                    }\n                });\n                FXUtils.setLimitWidth(txtServerIP, 300);\n                serverPane.addRow(0, new Label(i18n(\"settings.advanced.server_ip\")), txtServerIP);\n            }\n\n            var showAdvancedSettingPane = LineButton.createNavigationButton();\n            showAdvancedSettingPane.setTitle(i18n(\"settings.advanced\"));\n            showAdvancedSettingPane.setOnAction(event -> {\n                if (lastVersionSetting != null) {\n                    if (advancedVersionSettingPage == null)\n                        advancedVersionSettingPage = new AdvancedVersionSettingPage(profile, versionId, lastVersionSetting);\n\n                    Controllers.navigateForward(advancedVersionSettingPage);\n                }\n            });\n\n            componentList.getContent().addAll(\n                    javaSublist,\n                    gameDirSublist,\n                    maxMemoryPane,\n                    launcherVisibilityPane,\n                    dimensionPane,\n                    showLogsPane,\n                    enableDebugLogOutputPane,\n                    processPriorityPane,\n                    serverPane,\n                    showAdvancedSettingPane\n            );\n        }\n\n        rootPane.getChildren().add(componentList);\n\n        usesGlobalListener = any -> enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal());\n        specificSettingsListener = (a, b, newValue) -> {\n            if (versionId == null) return;\n\n            // do not call versionSettings.setUsesGlobal(true/false)\n            // because versionSettings can be the global one.\n            // global versionSettings.usesGlobal is always true.\n            if (newValue)\n                profile.getRepository().specializeVersionSetting(versionId);\n            else\n                profile.getRepository().globalizeVersionSetting(versionId);\n\n            FXUtils.runInFX(() -> {\n                loadVersion(profile, versionId);\n                fireWorkingDirChanged();\n            });\n        };\n\n        addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);\n\n        componentList.disableProperty().bind(enableSpecificSettings.not());\n    }\n    \n    private void fireWorkingDirChanged() {\n        FXUtils.runInFX(() -> fireEvent(new VersionPage.WorkingDirChangedEvent()));\n    }\n\n    @Override\n    public void loadVersion(Profile profile, String versionId) {\n        this.profile = profile;\n        this.versionId = versionId;\n        this.listenerHolder = new WeakListenerHolder();\n\n        if (versionId == null) {\n            enableSpecificSettings.set(true);\n            state.set(State.fromTitle(Profiles.getProfileDisplayName(profile) + \" - \" + i18n(\"settings.type.global.manage\")));\n\n            listenerHolder.add(FXUtils.onWeakChangeAndOperate(profile.selectedVersionProperty(), selectedVersion -> {\n                this.selectedVersion.setValue(selectedVersion);\n\n                VersionSetting specializedVersionSetting = profile.getRepository().getLocalVersionSetting(selectedVersion);\n                if (specializedVersionSetting != null) {\n                    navigateToSpecificSettings.bind(specializedVersionSetting.usesGlobalProperty().not());\n                } else {\n                    navigateToSpecificSettings.unbind();\n                    navigateToSpecificSettings.set(false);\n                }\n            }));\n        } else {\n            navigateToSpecificSettings.unbind();\n            navigateToSpecificSettings.set(false);\n        }\n\n        VersionSetting versionSetting = profile.getVersionSetting(versionId);\n\n        modpack.set(versionId != null && profile.getRepository().isModpack(versionId));\n\n        // unbind data fields\n        if (lastVersionSetting != null) {\n            FXUtils.unbindWindowsSize(cboWindowsSize, lastVersionSetting.widthProperty(), lastVersionSetting.heightProperty());\n            maxMemory.unbindBidirectional(lastVersionSetting.maxMemoryProperty());\n            javaCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.javaDirProperty());\n            gameDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.gameDirProperty());\n            FXUtils.unbind(txtServerIP, lastVersionSetting.serverIpProperty());\n            chkAutoAllocate.selectedProperty().unbindBidirectional(lastVersionSetting.autoMemoryProperty());\n            chkFullscreen.selectedProperty().unbindBidirectional(lastVersionSetting.fullscreenProperty());\n            showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());\n            enableDebugLogOutputPane.selectedProperty().unbindBidirectional(lastVersionSetting.enableDebugLogOutputProperty());\n            launcherVisibilityPane.valueProperty().unbindBidirectional(lastVersionSetting.launcherVisibilityProperty());\n            processPriorityPane.valueProperty().unbindBidirectional(lastVersionSetting.processPriorityProperty());\n\n            lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);\n            lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);\n            lastVersionSetting.javaDirProperty().removeListener(javaListener);\n            lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener);\n            lastVersionSetting.javaVersionProperty().removeListener(javaListener);\n\n            lastVersionSetting.gameDirTypeProperty().removeListener(gameDirTypeListener);\n            lastVersionSetting.gameDirProperty().removeListener(gameDirListener);\n\n            gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());\n            gameDirSublist.subtitleProperty().unbind();\n\n            enableSpecificSettings.removeListener(specificSettingsListener);\n\n            if (advancedVersionSettingPage != null) {\n                advancedVersionSettingPage.unbindProperties();\n                advancedVersionSettingPage = null;\n            }\n        }\n\n        // unbind data fields\n        javaItem.setToggleSelectedListener(null);\n        javaVersionOption.valueProperty().unbind();\n\n        // bind new data fields\n        FXUtils.bindWindowsSize(cboWindowsSize, versionSetting.widthProperty(), versionSetting.heightProperty());\n        maxMemory.bindBidirectional(versionSetting.maxMemoryProperty());\n\n        javaCustomOption.bindBidirectional(versionSetting.javaDirProperty());\n        gameDirCustomOption.bindBidirectional(versionSetting.gameDirProperty());\n        FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());\n        chkAutoAllocate.selectedProperty().bindBidirectional(versionSetting.autoMemoryProperty());\n        chkFullscreen.selectedProperty().bindBidirectional(versionSetting.fullscreenProperty());\n        showLogsPane.selectedProperty().bindBidirectional(versionSetting.showLogsProperty());\n        enableDebugLogOutputPane.selectedProperty().bindBidirectional(versionSetting.enableDebugLogOutputProperty());\n        launcherVisibilityPane.valueProperty().bindBidirectional(versionSetting.launcherVisibilityProperty());\n        processPriorityPane.valueProperty().bindBidirectional(versionSetting.processPriorityProperty());\n\n        if (versionId != null)\n            enableSpecificSettings.set(!versionSetting.isUsesGlobal());\n        versionSetting.usesGlobalProperty().addListener(usesGlobalListener);\n        enableSpecificSettings.addListener(specificSettingsListener);\n\n        javaItem.setToggleSelectedListener(newValue -> {\n            if (javaItem.getSelectedData() == null || updatingSelectedJava)\n                return;\n\n            updatingJavaSetting = true;\n\n            if (javaVersionOption.isSelected()) {\n                javaVersionOption.valueProperty().bindBidirectional(versionSetting.javaVersionProperty());\n            } else {\n                javaVersionOption.valueProperty().unbind();\n                javaVersionOption.setValue(\"\");\n            }\n\n            if (javaCustomOption.isSelected()) {\n                versionSetting.setUsesCustomJavaDir();\n            } else if (javaAutoDeterminedOption.isSelected()) {\n                versionSetting.setJavaAutoSelected();\n            } else if (javaVersionOption.isSelected()) {\n                if (versionSetting.getJavaVersionType() != JavaVersionType.VERSION)\n                    versionSetting.setJavaVersion(\"\");\n                versionSetting.setJavaVersionType(JavaVersionType.VERSION);\n                versionSetting.setDefaultJavaPath(null);\n            } else {\n                @SuppressWarnings(\"unchecked\")\n                JavaRuntime java = ((Pair<JavaVersionType, JavaRuntime>) newValue.getUserData()).getValue();\n                versionSetting.setJavaVersionType(JavaVersionType.DETECTED);\n                versionSetting.setJavaVersion(java.getVersion());\n                versionSetting.setDefaultJavaPath(java.getBinary().toString());\n            }\n\n            updatingJavaSetting = false;\n        });\n\n        versionSetting.javaVersionTypeProperty().addListener(javaListener);\n        versionSetting.javaDirProperty().addListener(javaListener);\n        versionSetting.defaultJavaPathPropertyProperty().addListener(javaListener);\n        versionSetting.javaVersionProperty().addListener(javaListener);\n\n        gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty());\n        gameDirSublist.subtitleProperty().bind(Bindings.createStringBinding(() -> profile.getRepository().getRunDirectory(versionId).toAbsolutePath().normalize().toString(),\n                versionSetting.gameDirProperty(), versionSetting.gameDirTypeProperty()));\n\n        versionSetting.gameDirTypeProperty().addListener(gameDirTypeListener);\n        versionSetting.gameDirProperty().addListener(gameDirListener);\n\n        lastVersionSetting = versionSetting;\n\n        initJavaSubtitle();\n\n        loadIcon();\n    }\n\n    private void initializeSelectedJava() {\n        if (lastVersionSetting == null || updatingJavaSetting)\n            return;\n\n        updatingSelectedJava = true;\n        switch (lastVersionSetting.getJavaVersionType()) {\n            case CUSTOM:\n                javaCustomOption.setSelected(true);\n                break;\n            case VERSION:\n                javaVersionOption.setSelected(true);\n                javaVersionOption.setValue(lastVersionSetting.getJavaVersion());\n                break;\n            case AUTO:\n                javaAutoDeterminedOption.setSelected(true);\n                break;\n            default:\n                Toggle toggle = null;\n                if (JavaManager.isInitialized()) {\n                    try {\n                        JavaRuntime java = lastVersionSetting.getJava(null, null);\n                        if (java != null) {\n                            for (Toggle t : javaItem.getGroup().getToggles()) {\n                                if (t.getUserData() != null) {\n                                    @SuppressWarnings(\"unchecked\")\n                                    Pair<JavaVersionType, JavaRuntime> userData = (Pair<JavaVersionType, JavaRuntime>) t.getUserData();\n                                    if (userData.getValue() != null && java.getBinary().equals(userData.getValue().getBinary())) {\n                                        toggle = t;\n                                        break;\n\n                                    }\n                                }\n                            }\n                        }\n                    } catch (InterruptedException ignored) {\n                    }\n                }\n\n                if (toggle != null) {\n                    toggle.setSelected(true);\n                } else {\n                    Toggle selectedToggle = javaItem.getGroup().getSelectedToggle();\n                    if (selectedToggle != null) {\n                        selectedToggle.setSelected(false);\n                    }\n                }\n                break;\n        }\n        updatingSelectedJava = false;\n    }\n\n    private void initJavaSubtitle() {\n        FXUtils.checkFxUserThread();\n        if (lastVersionSetting == null)\n            return;\n        initializeSelectedJava();\n        HMCLGameRepository repository = this.profile.getRepository();\n        String versionId = this.versionId;\n        JavaVersionType javaVersionType = lastVersionSetting.getJavaVersionType();\n        boolean autoSelected = javaVersionType == JavaVersionType.AUTO || javaVersionType == JavaVersionType.VERSION;\n\n        if (versionId == null && autoSelected) {\n            javaSublist.setSubtitle(i18n(\"settings.game.java_directory.auto\"));\n            return;\n        }\n\n        Pair<JavaVersionType, JavaRuntime> selectedData = javaItem.getSelectedData();\n        if (selectedData != null && selectedData.getValue() != null) {\n            javaSublist.setSubtitle(selectedData.getValue().getBinary().toString());\n            return;\n        }\n\n        if (JavaManager.isInitialized()) {\n            GameVersionNumber gameVersionNumber;\n            Version version;\n            if (versionId == null) {\n                gameVersionNumber = GameVersionNumber.unknown();\n                version = null;\n            } else {\n                gameVersionNumber = GameVersionNumber.asGameVersion(repository.getGameVersion(versionId));\n                version = repository.getResolvedVersion(versionId);\n            }\n\n            try {\n                JavaRuntime java = lastVersionSetting.getJava(gameVersionNumber, version);\n                if (java != null) {\n                    javaSublist.setSubtitle(java.getBinary().toString());\n                } else {\n                    javaSublist.setSubtitle(autoSelected ? i18n(\"settings.game.java_directory.auto.not_found\") : i18n(\"settings.game.java_directory.invalid\"));\n                }\n                return;\n            } catch (InterruptedException ignored) {\n            }\n        }\n\n        javaSublist.setSubtitle(\"\");\n    }\n\n    private void editSpecificSettings() {\n        Versions.modifyGameSettings(profile, profile.getSelectedVersion());\n    }\n\n    private void editGlobalSettings() {\n        Versions.modifyGlobalSettings(profile);\n    }\n\n    private void onExploreIcon() {\n        if (versionId == null)\n            return;\n\n        Controllers.dialog(new VersionIconDialog(profile, versionId, this::loadIcon));\n    }\n\n    private void onDeleteIcon() {\n        if (versionId == null)\n            return;\n\n        profile.getRepository().deleteIconFile(versionId);\n        VersionSetting localVersionSetting = profile.getRepository().getLocalVersionSettingOrCreate(versionId);\n        if (localVersionSetting != null) {\n            localVersionSetting.setVersionIcon(VersionIconType.DEFAULT);\n        }\n        loadIcon();\n    }\n\n    private void loadIcon() {\n        if (versionId == null) {\n            return;\n        }\n\n        iconPickerItem.setImage(profile.getRepository().getVersionIconImage(versionId));\n    }\n\n    private static List<String> getSupportedResolutions() {\n        int maxScreenWidth = 0;\n        int maxScreenHeight = 0;\n\n        for (Screen screen : Screen.getScreens()) {\n            Rectangle2D bounds = screen.getBounds();\n            int screenWidth = (int) (bounds.getWidth() * screen.getOutputScaleX());\n            int screenHeight = (int) (bounds.getHeight() * screen.getOutputScaleY());\n\n            maxScreenWidth = Math.max(maxScreenWidth, screenWidth);\n            maxScreenHeight = Math.max(maxScreenHeight, screenHeight);\n        }\n\n        List<String> resolutions = new ArrayList<>(List.of(\"854x480\", \"1280x720\", \"1600x900\"));\n\n        if (maxScreenWidth >= 1920 && maxScreenHeight >= 1080) resolutions.add(\"1920x1080\");\n        if (maxScreenWidth >= 2560 && maxScreenHeight >= 1440) resolutions.add(\"2560x1440\");\n        if (maxScreenWidth >= 3840 && maxScreenHeight >= 2160) resolutions.add(\"3840x2160\");\n\n        return resolutions;\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state.getReadOnlyProperty();\n    }\n\n    private static final class UpdateMemoryStatus extends Thread {\n\n        @FXThread\n        private static WeakReference<ObjectProperty<PhysicalMemoryStatus>> memoryStatusPropertyCache;\n\n        @FXThread\n        static ReadOnlyObjectProperty<PhysicalMemoryStatus> memoryStatusProperty() {\n            if (memoryStatusPropertyCache != null) {\n                var property = memoryStatusPropertyCache.get();\n                if (property != null) {\n                    return property;\n                }\n            }\n\n            ObjectProperty<PhysicalMemoryStatus> property = new SimpleObjectProperty<>(PhysicalMemoryStatus.INVALID);\n            memoryStatusPropertyCache = new WeakReference<>(property);\n            new UpdateMemoryStatus(memoryStatusPropertyCache).start();\n            return property;\n        }\n\n        private final WeakReference<ObjectProperty<PhysicalMemoryStatus>> memoryStatusPropertyRef;\n\n        UpdateMemoryStatus(WeakReference<ObjectProperty<PhysicalMemoryStatus>> memoryStatusPropertyRef) {\n            this.memoryStatusPropertyRef = memoryStatusPropertyRef;\n\n            setName(\"UpdateMemoryStatus\");\n            setDaemon(true);\n            setPriority(Thread.MIN_PRIORITY);\n        }\n\n        @Override\n        public void run() {\n            while (true) {\n                PhysicalMemoryStatus status = SystemInfo.getPhysicalMemoryStatus();\n\n                var memoryStatusProperty = memoryStatusPropertyRef.get();\n                if (memoryStatusProperty == null)\n                    return;\n\n                if (Controllers.isStopped())\n                    return;\n\n                Platform.runLater(() -> memoryStatusProperty.set(status));\n\n                try {\n                    //noinspection BusyWait\n                    Thread.sleep(1000);\n                } catch (InterruptedException e) {\n                    return;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.application.Platform;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.game.GameAssetDownloadTask;\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.setting.*;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.account.CreateAccountPane;\nimport org.jackhuang.hmcl.ui.construct.DialogCloseEvent;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.PromptDialogPane;\nimport org.jackhuang.hmcl.ui.construct.Validator;\nimport org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;\nimport org.jackhuang.hmcl.ui.export.ExportWizardProvider;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class Versions {\n    private Versions() {\n    }\n\n    public static void addNewGame() {\n        Controllers.getDownloadPage().showGameDownloads();\n        Controllers.navigate(Controllers.getDownloadPage());\n    }\n\n    public static void importModpack() {\n        Profile profile = Profiles.getSelectedProfile();\n        if (profile.getRepository().isLoaded()) {\n            Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile), i18n(\"install.modpack\"));\n        }\n    }\n\n    public static void downloadModpackImpl(DownloadProvider downloadProvider, Profile profile, String version, RemoteMod mod, RemoteMod.Version file) {\n        Path modpack;\n        List<URI> downloadURLs;\n        try {\n            downloadURLs = downloadProvider.injectURLWithCandidates(file.getFile().getUrl());\n            modpack = Files.createTempFile(\"modpack\", \".zip\");\n        } catch (IOException | IllegalArgumentException e) {\n            Controllers.dialog(\n                    i18n(\"install.failed.downloading.detail\", file.getFile().getUrl()) + \"\\n\" + StringUtils.getStackTrace(e),\n                    i18n(\"download.failed.no_code\"), MessageDialogPane.MessageType.ERROR);\n            return;\n        }\n        Controllers.taskDialog(\n                new FileDownloadTask(downloadURLs, modpack)\n                        .whenComplete(Schedulers.javafx(), e -> {\n                            if (e == null) {\n                                ModpackInstallWizardProvider installWizardProvider;\n                                if (version != null)\n                                    installWizardProvider = new ModpackInstallWizardProvider(profile, modpack, version);\n                                else\n                                    installWizardProvider = new ModpackInstallWizardProvider(profile, modpack);\n                                if (StringUtils.isNotBlank(mod.getIconUrl()))\n                                    installWizardProvider.setIconUrl(mod.getIconUrl());\n                                Controllers.getDecorator().startWizard(installWizardProvider);\n                            } else if (e instanceof CancellationException) {\n                                Controllers.showToast(i18n(\"message.cancelled\"));\n                            } else {\n                                Controllers.dialog(\n                                        i18n(\"install.failed.downloading.detail\", file.getFile().getUrl()) + \"\\n\" + StringUtils.getStackTrace(e),\n                                        i18n(\"download.failed.no_code\"), MessageDialogPane.MessageType.ERROR);\n                            }\n                        }),\n                i18n(\"message.downloading\"),\n                TaskCancellationAction.NORMAL\n        );\n    }\n\n    public static void deleteVersion(Profile profile, String version) {\n        boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == GameDirectoryType.VERSION_FOLDER;\n        String message = isIndependent ? i18n(\"version.manage.remove.confirm.independent\", version) :\n                i18n(\"version.manage.remove.confirm.trash\", version, version + \"_removed\");\n\n        JFXButton deleteButton = new JFXButton(i18n(\"button.delete\"));\n        deleteButton.getStyleClass().add(\"dialog-error\");\n        deleteButton.setOnAction(e -> {\n            Task.supplyAsync(Schedulers.io(), () -> profile.getRepository().removeVersionFromDisk(version))\n                    .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                        if (exception != null || !Boolean.TRUE.equals(result)) {\n                            Controllers.dialog(i18n(\"version.manage.remove.failed\"), i18n(\"message.error\"), MessageDialogPane.MessageType.ERROR);\n                        }\n                    }).start();\n        });\n\n        Controllers.confirmAction(message, i18n(\"message.warning\"), MessageDialogPane.MessageType.WARNING, deleteButton);\n    }\n\n    public static CompletableFuture<String> renameVersion(Profile profile, String version) {\n        return Controllers.prompt(i18n(\"version.manage.rename.message\"), (newName, handler) -> {\n            if (newName.equals(version)) {\n                handler.resolve();\n                return;\n            }\n            if (profile.getRepository().renameVersion(version, newName)) {\n                handler.resolve();\n                profile.getRepository().refreshVersionsAsync()\n                        .thenRunAsync(Schedulers.javafx(), () -> {\n                            if (profile.getRepository().hasVersion(newName)) {\n                                profile.setSelectedVersion(newName);\n                            }\n                        }).start();\n            } else {\n                handler.reject(i18n(\"version.manage.rename.fail\"));\n            }\n        }, version, new Validator(i18n(\"install.new_game.malformed\"), HMCLGameRepository::isValidVersionId),\n            new Validator(i18n(\"install.new_game.already_exists\"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName) || newVersionName.equals(version)));\n    }\n\n    public static void exportVersion(Profile profile, String version) {\n        Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), i18n(\"modpack.wizard\"));\n    }\n\n    public static void openFolder(Profile profile, String version) {\n        FXUtils.openFolder(profile.getRepository().getRunDirectory(version));\n    }\n\n    public static void duplicateVersion(Profile profile, String version) {\n        Controllers.prompt(\n                new PromptDialogPane.Builder(i18n(\"version.manage.duplicate.prompt\"), (res, handler) -> {\n                    String newVersionName = ((PromptDialogPane.Builder.StringQuestion) res.get(1)).getValue();\n                    boolean copySaves = ((PromptDialogPane.Builder.BooleanQuestion) res.get(2)).getValue();\n                    Task.runAsync(() -> profile.getRepository().duplicateVersion(version, newVersionName, copySaves))\n                            .thenComposeAsync(profile.getRepository().refreshVersionsAsync())\n                            .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                                if (exception == null) {\n                                    handler.resolve();\n                                } else {\n                                    handler.reject(StringUtils.getStackTrace(exception));\n                                    if (!profile.getRepository().versionIdConflicts(newVersionName)) {\n                                        profile.getRepository().removeVersionFromDisk(newVersionName);\n                                    }\n                                }\n                            }).start();\n                })\n                        .addQuestion(new PromptDialogPane.Builder.HintQuestion(i18n(\"version.manage.duplicate.confirm\")))\n                        .addQuestion(new PromptDialogPane.Builder.StringQuestion(null, version,\n                                new Validator(i18n(\"install.new_game.malformed\"), HMCLGameRepository::isValidVersionId),\n                                new Validator(i18n(\"install.new_game.already_exists\"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName))))\n                        .addQuestion(new PromptDialogPane.Builder.BooleanQuestion(i18n(\"version.manage.duplicate.duplicate_save\"), false)));\n    }\n\n    public static void updateVersion(Profile profile, String version) {\n        Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, version));\n    }\n\n    public static void updateGameAssets(Profile profile, String version) {\n        TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version), GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true)\n                .executor();\n        Controllers.taskDialog(executor, i18n(\"version.manage.redownload_assets_index\"), TaskCancellationAction.NO_CANCEL);\n        executor.start();\n    }\n\n    public static void cleanVersion(Profile profile, String id) {\n        try {\n            profile.getRepository().clean(id);\n        } catch (IOException e) {\n            LOG.warning(\"Unable to clean game directory\", e);\n        }\n    }\n\n    @SafeVarargs\n    public static void generateLaunchScript(Profile profile, String id, Consumer<LauncherHelper>... injecters) {\n        if (!checkVersionForLaunching(profile, id))\n            return;\n        ensureSelectedAccount(account -> {\n            GameRepository repository = profile.getRepository();\n            FileChooser chooser = new FileChooser();\n            if (Files.isDirectory(repository.getRunDirectory(id)))\n                chooser.setInitialDirectory(repository.getRunDirectory(id).toFile());\n            chooser.setTitle(i18n(\"version.launch_script.save\"));\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n                chooser.getExtensionFilters().add(\n                        new FileChooser.ExtensionFilter(i18n(\"extension.command\"), \"*.command\")\n                );\n            }\n            chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS\n                    ? new FileChooser.ExtensionFilter(i18n(\"extension.bat\"), \"*.bat\")\n                    : new FileChooser.ExtensionFilter(i18n(\"extension.sh\"), \"*.sh\"));\n            chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"extension.ps1\"), \"*.ps1\"));\n            Path file = FileUtils.toPath(chooser.showSaveDialog(Controllers.getStage()));\n            if (file != null) {\n                LauncherHelper launcherHelper = new LauncherHelper(profile, account, id);\n                for (Consumer<LauncherHelper> injecter : injecters) {\n                    injecter.accept(launcherHelper);\n                }\n                launcherHelper.makeLaunchScript(file);\n            }\n        });\n    }\n\n    @SafeVarargs\n    public static void launch(Profile profile, String id, Consumer<LauncherHelper>... injecters) {\n        if (!checkVersionForLaunching(profile, id))\n            return;\n        ensureSelectedAccount(account -> {\n            LauncherHelper launcherHelper = new LauncherHelper(profile, account, id);\n            for (Consumer<LauncherHelper> injecter : injecters) {\n                injecter.accept(launcherHelper);\n            }\n            launcherHelper.launch();\n        });\n    }\n\n    public static void testGame(Profile profile, String id) {\n        launch(profile, id, LauncherHelper::setTestMode);\n    }\n\n    public static void launchAndEnterWorld(Profile profile, String id, String worldFolderName) {\n        launch(profile, id, launcherHelper ->\n                launcherHelper.setQuickPlayOption(new QuickPlayOption.SinglePlayer(worldFolderName)));\n    }\n\n    public static void generateLaunchScriptForQuickEnterWorld(Profile profile, String id, String worldFolderName) {\n        generateLaunchScript(profile, id, launcherHelper ->\n                launcherHelper.setQuickPlayOption(new QuickPlayOption.SinglePlayer(worldFolderName)));\n    }\n\n    private static boolean checkVersionForLaunching(Profile profile, String id) {\n        if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id)) {\n            JFXButton gotoDownload = new JFXButton(i18n(\"version.empty.launch.goto_download\"));\n            gotoDownload.getStyleClass().add(\"dialog-accept\");\n            gotoDownload.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));\n\n            Controllers.confirmAction(i18n(\"version.empty.launch\"), i18n(\"launch.failed\"),\n                    MessageDialogPane.MessageType.ERROR,\n                    gotoDownload,\n                    null);\n            return false;\n        } else {\n            return true;\n        }\n    }\n\n    private static void ensureSelectedAccount(Consumer<Account> action) {\n        Account account = Accounts.getSelectedAccount();\n        if (ConfigHolder.isNewlyCreated() && !AuthlibInjectorServers.getServers().isEmpty() &&\n                !(account instanceof AuthlibInjectorAccount && AuthlibInjectorServers.getServers().contains(((AuthlibInjectorAccount) account).getServer()))) {\n            CreateAccountPane dialog = new CreateAccountPane(AuthlibInjectorServers.getServers().iterator().next());\n            dialog.addEventHandler(DialogCloseEvent.CLOSE, e -> {\n                Account newAccount = Accounts.getSelectedAccount();\n                if (newAccount == null) {\n                    // user cancelled operation\n                } else {\n                    Platform.runLater(() -> action.accept(newAccount));\n                }\n            });\n            Controllers.dialog(dialog);\n        } else if (account == null) {\n            CreateAccountPane dialog = new CreateAccountPane();\n            dialog.addEventHandler(DialogCloseEvent.CLOSE, e -> {\n                Account newAccount = Accounts.getSelectedAccount();\n                if (newAccount == null) {\n                    // user cancelled operation\n                } else {\n                    Platform.runLater(() -> action.accept(newAccount));\n                }\n            });\n            Controllers.dialog(dialog);\n        } else {\n            action.accept(account);\n        }\n    }\n\n    public static void modifyGlobalSettings(Profile profile) {\n        Controllers.getSettingsPage().showGameSettings(profile);\n        Controllers.navigate(Controllers.getSettingsPage());\n    }\n\n    public static void modifyGameSettings(Profile profile, String version) {\n        Controllers.getVersionPage().setVersion(version, profile);\n        Controllers.getVersionPage().showInstanceSettings();\n        // VersionPage.loadVersion will be invoked after navigation\n        Controllers.navigate(Controllers.getVersionPage());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport org.jackhuang.hmcl.game.World;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.*;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.time.LocalDateTime;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\n/**\n * @author Glavo\n */\npublic final class WorldBackupTask extends Task<Path> {\n\n    private final World world;\n    private final Path backupsDir;\n    private final boolean needLock;\n\n    public WorldBackupTask(World world, Path backupsDir, boolean needLock) {\n        this.world = world;\n        this.backupsDir = backupsDir;\n        this.needLock = needLock;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        try (FileChannel lockChannel = needLock ? world.lock() : null) {\n            Files.createDirectories(backupsDir);\n            String time = LocalDateTime.now().format(WorldBackupsPage.TIME_FORMATTER);\n            String baseName = time + \"_\" + world.getFileName();\n            Path backupFile = null;\n            OutputStream outputStream = null;\n\n            int count;\n            for (count = 0; count < 256; count++) {\n                try {\n                    backupFile = backupsDir.resolve(baseName + (count == 0 ? \"\" : \" \" + count) + \".zip\").toAbsolutePath();\n                    outputStream = Files.newOutputStream(backupFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);\n                    break;\n                } catch (FileAlreadyExistsException ignored) {\n                }\n            }\n\n            if (outputStream == null)\n                throw new IOException(\"Too many attempts\");\n\n            try (ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(outputStream))) {\n                String rootName = world.getFileName();\n                Path rootDir = this.world.getFile();\n                Files.walkFileTree(this.world.getFile(), new SimpleFileVisitor<Path>() {\n                    @Override\n                    public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {\n                        if (path.endsWith(\"session.lock\")) {\n                            return FileVisitResult.CONTINUE;\n                        }\n                        zipOutputStream.putNextEntry(new ZipEntry(rootName + \"/\" + rootDir.relativize(path).toString().replace('\\\\', '/')));\n                        Files.copy(path, zipOutputStream);\n                        zipOutputStream.closeEntry();\n                        return FileVisitResult.CONTINUE;\n                    }\n                });\n            }\n\n            setResult(backupFile);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.collections.FXCollections;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport org.jackhuang.hmcl.game.World;\nimport org.jackhuang.hmcl.game.WorldLockedException;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.*;\nimport org.jackhuang.hmcl.ui.construct.ImageContainer;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.construct.RipplerContainer;\nimport org.jackhuang.hmcl.ui.construct.TwoLineListItem;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.nio.file.*;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;\nimport static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.BackupInfo> implements WorldManagePage.WorldRefreshable {\n    static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(\"yyyy-MM-dd_HH-mm-ss\");\n\n    private final World world;\n    private final Path backupsDir;\n    private final BooleanProperty readOnly;\n    private final Pattern backupFileNamePattern;\n\n    public WorldBackupsPage(WorldManagePage worldManagePage) {\n        this.world = worldManagePage.getWorld();\n        this.backupsDir = worldManagePage.getBackupsDir();\n        this.readOnly = worldManagePage.readOnlyProperty();\n        this.backupFileNamePattern = Pattern.compile(\"(?<datetime>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_\" + Pattern.quote(world.getFileName()) + \"( (?<count>[0-9]+))?\\\\.zip\");\n\n        refresh();\n    }\n\n    public void refresh() {\n        setLoading(true);\n        Task.supplyAsync(() -> {\n            if (Files.isDirectory(backupsDir)) {\n                try (Stream<Path> paths = Files.list(backupsDir)) {\n                    ArrayList<BackupInfo> result = new ArrayList<>();\n\n                    paths.forEach(path -> {\n                        if (Files.isRegularFile(path)) {\n                            try {\n                                Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());\n                                if (matcher.matches()) {\n                                    LocalDateTime time = LocalDateTime.parse(matcher.group(\"datetime\"), TIME_FORMATTER);\n                                    int count = 0;\n\n                                    if (matcher.group(\"count\") != null) {\n                                        count = Integer.parseInt(matcher.group(\"count\"));\n                                    }\n\n                                    result.add(new BackupInfo(path, new World(path), time, count));\n                                }\n                            } catch (Throwable e) {\n                                LOG.warning(\"Failed to load backup file \" + path, e);\n                            }\n                        }\n                    });\n\n                    result.sort(Comparator.naturalOrder());\n                    return result;\n                }\n            } else {\n                return new ArrayList<BackupInfo>();\n            }\n        }).whenComplete(Schedulers.javafx(), (result, exception) -> {\n            this.setLoading(false);\n            if (exception == null) {\n                this.setItems(FXCollections.observableList(result));\n            } else {\n                LOG.warning(\"Failed to load backups\", exception);\n            }\n        }).start();\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new WorldBackupsPageSkin();\n    }\n\n    void createBackup() {\n        Controllers.taskDialog(new WorldBackupTask(world, backupsDir, false).setName(i18n(\"world.backup.processing\")).thenApplyAsync(path -> {\n            Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());\n            if (!matcher.matches()) {\n                throw new AssertionError(\"Wrong backup file name\" + path);\n            }\n\n            LocalDateTime time = LocalDateTime.parse(matcher.group(\"datetime\"), TIME_FORMATTER);\n            int count = 0;\n\n            if (matcher.group(\"count\") != null) {\n                count = Integer.parseInt(matcher.group(\"count\"));\n            }\n\n            return Pair.pair(path, new BackupInfo(path, new World(path), time, count));\n        }).whenComplete(Schedulers.javafx(), (result, exception) -> {\n            if (exception == null) {\n                WorldBackupsPage.this.getItems().add(result.getValue());\n                WorldBackupsPage.this.getItems().sort(Comparator.naturalOrder());\n                Controllers.dialog(i18n(\"world.backup.create.success\", result.getKey()), null, MessageDialogPane.MessageType.INFO);\n            } else if (exception instanceof WorldLockedException) {\n                Controllers.dialog(i18n(\"world.locked.failed\"), null, MessageDialogPane.MessageType.WARNING);\n            } else {\n                LOG.warning(\"Failed to create backup\", exception);\n                Controllers.dialog(i18n(\"world.backup.create.failed\", StringUtils.getStackTrace(exception)), null, MessageDialogPane.MessageType.WARNING);\n            }\n        }), i18n(\"world.backup\"), null);\n    }\n\n    private final class WorldBackupsPageSkin extends ToolbarListPageSkin<BackupInfo, WorldBackupsPage> {\n\n        WorldBackupsPageSkin() {\n            super(WorldBackupsPage.this);\n        }\n\n        @Override\n        protected List<Node> initializeToolbar(WorldBackupsPage skinnable) {\n            JFXButton createBackup = createToolbarButton2(i18n(\"world.backup.create.new_one\"), SVG.ARCHIVE, skinnable::createBackup);\n            createBackup.disableProperty().bind(getSkinnable().readOnly);\n\n            return Arrays.asList(\n                    createToolbarButton2(i18n(\"button.refresh\"), SVG.REFRESH, skinnable::refresh),\n                    createBackup\n            );\n        }\n    }\n\n    public final class BackupInfo extends Control implements Comparable<BackupInfo> {\n        private final Path file;\n        private final World backupWorld;\n        private final LocalDateTime backupTime;\n        private final int count;\n\n        public BackupInfo(Path file, World backupWorld, LocalDateTime backupTime, int count) {\n            this.file = file;\n            this.backupWorld = backupWorld;\n            this.backupTime = backupTime;\n            this.count = count;\n        }\n\n        public World getBackupWorld() {\n            return backupWorld;\n        }\n\n        public LocalDateTime getBackupTime() {\n            return backupTime;\n        }\n\n        @Override\n        protected Skin<?> createDefaultSkin() {\n            return new BackupInfoSkin(this);\n        }\n\n        void onReveal() {\n            FXUtils.showFileInExplorer(file);\n        }\n\n        void onDelete() {\n            WorldBackupsPage.this.getItems().remove(this);\n            Task.runAsync(() -> Files.delete(file)).start();\n        }\n\n        @Override\n        public int compareTo(@NotNull WorldBackupsPage.BackupInfo that) {\n            int c = this.backupTime.compareTo(that.backupTime);\n            return c != 0 ? c : Integer.compare(this.count, that.count);\n        }\n    }\n\n    private static final class BackupInfoSkin extends SkinBase<BackupInfo> {\n\n        BackupInfoSkin(BackupInfo skinnable) {\n            super(skinnable);\n\n            World world = skinnable.getBackupWorld();\n\n            BorderPane root = new BorderPane();\n            root.getStyleClass().add(\"md-list-cell\");\n            root.setPadding(new Insets(8));\n\n            {\n                StackPane left = new StackPane();\n                root.setLeft(left);\n                left.setPadding(new Insets(0, 8, 0, 0));\n\n                var imageView = new ImageContainer(32);\n                left.getChildren().add(imageView);\n                imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage(\"/assets/img/unknown_server.png\") : world.getIcon());\n            }\n\n            {\n                TwoLineListItem item = new TwoLineListItem();\n                root.setCenter(item);\n\n                if (skinnable.getBackupWorld().getWorldName() != null)\n                    item.setTitle(parseColorEscapes(skinnable.getBackupWorld().getWorldName()));\n                item.setSubtitle(formatDateTime(skinnable.getBackupTime()) + (skinnable.count == 0 ? \"\" : \" (\" + skinnable.count + \")\"));\n\n                if (world.getGameVersion() != null)\n                    item.addTag(I18n.getDisplayVersion(world.getGameVersion()));\n            }\n\n            {\n                HBox right = new HBox(8);\n                root.setRight(right);\n                right.setAlignment(Pos.CENTER_RIGHT);\n\n                JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);\n                right.getChildren().add(btnReveal);\n                FXUtils.installFastTooltip(btnReveal, i18n(\"reveal.in_file_manager\"));\n                btnReveal.setOnAction(event -> skinnable.onReveal());\n\n                JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);\n                right.getChildren().add(btnDelete);\n                FXUtils.installFastTooltip(btnDelete, i18n(\"world.backup.delete\"));\n                btnDelete.setOnAction(event -> Controllers.confirm(i18n(\"button.remove.confirm\"), i18n(\"button.remove\"), skinnable::onDelete, null));\n            }\n\n            getChildren().setAll(new RipplerContainer(root));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.scene.control.Skin;\nimport org.jackhuang.hmcl.game.World;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.wizard.WizardSinglePage;\nimport org.jackhuang.hmcl.util.i18n.I18n;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class WorldExportPage extends WizardSinglePage {\n    private final StringProperty path = new SimpleStringProperty();\n    private final StringProperty gameVersion = new SimpleStringProperty();\n    private final StringProperty worldName = new SimpleStringProperty();\n    private final World world;\n\n    public WorldExportPage(World world, Path export, Runnable onFinish) {\n        super(onFinish);\n\n        this.world = world;\n\n        path.set(export.toString());\n        if (world.getGameVersion() != null)\n            gameVersion.set(I18n.getDisplayVersion(world.getGameVersion()));\n        worldName.set(world.getWorldName());\n    }\n\n    @Override\n    protected Skin<?> createDefaultSkin() {\n        return new WorldExportPageSkin(this);\n    }\n\n    public StringProperty pathProperty() {\n        return path;\n    }\n\n    public StringProperty gameVersionProperty() {\n        return gameVersion;\n    }\n\n    public StringProperty worldNameProperty() {\n        return worldName;\n    }\n\n    public void export() {\n        onFinish.run();\n    }\n\n    @Override\n    public String getTitle() {\n        return i18n(\"world.export.wizard\", world.getFileName());\n    }\n\n    @Override\n    protected Object finish() {\n        return Task.runAsync(i18n(\"world.export.wizard\", worldName.get()), () -> world.export(Paths.get(path.get()), worldName.get()));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPageSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.beans.binding.Bindings;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.SkinBase;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.construct.ComponentList;\nimport org.jackhuang.hmcl.ui.construct.LineFileChooserButton;\nimport org.jackhuang.hmcl.ui.construct.LinePane;\nimport org.jackhuang.hmcl.ui.construct.LineTextPane;\n\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic class WorldExportPageSkin extends SkinBase<WorldExportPage> {\n\n    public WorldExportPageSkin(WorldExportPage skinnable) {\n        super(skinnable);\n\n        VBox container = new VBox();\n        container.setSpacing(16);\n        container.setAlignment(Pos.CENTER);\n        FXUtils.setLimitWidth(container, 500);\n\n        ComponentList list = new ComponentList();\n\n        var chooseFileButton = new LineFileChooserButton();\n        chooseFileButton.setTitle(i18n(\"world.export.location\"));\n        chooseFileButton.setType(LineFileChooserButton.Type.SAVE_FILE);\n        chooseFileButton.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"world\"), \"*.zip\"));\n        chooseFileButton.locationProperty().bindBidirectional(skinnable.pathProperty());\n\n        var worldNamePane = new LinePane();\n        worldNamePane.setTitle(i18n(\"world.name\"));\n        JFXTextField txtWorldName = new JFXTextField();\n        txtWorldName.textProperty().bindBidirectional(skinnable.worldNameProperty());\n        txtWorldName.setPrefWidth(300);\n        worldNamePane.setRight(txtWorldName);\n\n        LineTextPane gameVersionPane = new LineTextPane();\n        gameVersionPane.setTitle(i18n(\"world.game_version\"));\n        gameVersionPane.textProperty().bind(skinnable.gameVersionProperty());\n\n        list.getContent().setAll(chooseFileButton, worldNamePane, gameVersionPane);\n\n        container.getChildren().setAll(\n                ComponentList.createComponentListTitle(i18n(\"world.export\")),\n                list\n        );\n\n        JFXButton btnExport = FXUtils.newRaisedButton(i18n(\"button.export\"));\n        btnExport.disableProperty().bind(Bindings.createBooleanBinding(() -> txtWorldName.getText().isEmpty() || Files.exists(Paths.get(chooseFileButton.getLocation())),\n                txtWorldName.textProperty().isEmpty(), chooseFileButton.locationProperty()));\n        btnExport.setOnAction(e -> skinnable.export());\n        HBox bottom = new HBox();\n        bottom.setAlignment(Pos.CENTER_RIGHT);\n        bottom.getChildren().setAll(btnExport);\n        container.getChildren().add(bottom);\n\n        getChildren().setAll(container);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.github.steveice10.opennbt.tag.builtin.*;\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXTextField;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.effect.BoxBlur;\nimport javafx.scene.image.Image;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.game.World;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.text.DecimalFormat;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Locale;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class WorldInfoPage extends SpinnerPane implements WorldManagePage.WorldRefreshable {\n    private final WorldManagePage worldManagePage;\n    private boolean isReadOnly;\n    private final World world;\n    private CompoundTag levelData;\n    private CompoundTag playerData;\n\n    private final ImageContainer iconImageView = new ImageContainer(32);\n\n    public WorldInfoPage(WorldManagePage worldManagePage) {\n        this.worldManagePage = worldManagePage;\n        this.world = worldManagePage.getWorld();\n        refresh();\n    }\n\n    private void updateControls() {\n        CompoundTag dataTag = levelData.get(\"Data\");\n        CompoundTag playerTag = playerData;\n\n        ScrollPane scrollPane = new ScrollPane();\n        scrollPane.setFitToHeight(true);\n        scrollPane.setFitToWidth(true);\n        scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);\n        setContent(scrollPane);\n\n        VBox rootPane = new VBox();\n        rootPane.setFillWidth(true);\n        scrollPane.setContent(rootPane);\n        FXUtils.smoothScrolling(scrollPane);\n        rootPane.getStyleClass().add(\"card-list\");\n\n        ComponentList worldInfo = new ComponentList();\n        {\n            var worldNamePane = new LinePane();\n            {\n                worldNamePane.setTitle(i18n(\"world.name\"));\n                JFXTextField worldNameField = new JFXTextField();\n                setRightTextField(worldNamePane, worldNameField, 200);\n\n                if (dataTag.get(\"LevelName\") instanceof StringTag worldNameTag) {\n                    var worldName = new SimpleStringProperty(worldNameTag.getValue());\n                    FXUtils.bindString(worldNameField, worldName);\n                    worldNameField.getProperties().put(WorldInfoPage.class.getName() + \".worldNameProperty\", worldName);\n                    worldName.addListener((observable, oldValue, newValue) -> {\n                        if (StringUtils.isNotBlank(newValue)) {\n                            try {\n                                world.setWorldName(newValue);\n                                worldManagePage.setTitle(i18n(\"world.manage.title\", StringUtils.parseColorEscapes(world.getWorldName())));\n                            } catch (Exception e) {\n                                LOG.warning(\"Failed to set world name\", e);\n                            }\n                        }\n                    });\n                } else {\n                    worldNameField.setDisable(true);\n                }\n            }\n\n            var gameVersionPane = new LineTextPane();\n            {\n                gameVersionPane.setTitle(i18n(\"world.info.game_version\"));\n                gameVersionPane.setText(world.getGameVersion() == null ? \"\" : world.getGameVersion().toNormalizedString());\n            }\n\n            var iconPane = new LinePane();\n            {\n                iconPane.setTitle(i18n(\"world.icon\"));\n\n                {\n                    iconImageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage(\"/assets/img/unknown_server.png\") : world.getIcon());\n                }\n\n                JFXButton editIconButton = FXUtils.newToggleButton4(SVG.EDIT, 20);\n                JFXButton resetIconButton = FXUtils.newToggleButton4(SVG.RESTORE, 20);\n                {\n                    editIconButton.setDisable(isReadOnly);\n                    editIconButton.setOnAction(event -> Controllers.confirm(\n                            I18n.i18n(\"world.icon.change.tip\"),\n                            I18n.i18n(\"world.icon.change\"),\n                            MessageDialogPane.MessageType.INFO,\n                            this::changeWorldIcon,\n                            null\n                    ));\n                    FXUtils.installFastTooltip(editIconButton, i18n(\"button.edit\"));\n\n                    resetIconButton.setDisable(isReadOnly);\n                    resetIconButton.setOnAction(event -> this.clearWorldIcon());\n                    FXUtils.installFastTooltip(resetIconButton, i18n(\"button.reset\"));\n                }\n\n                HBox hBox = new HBox(8);\n                hBox.setAlignment(Pos.CENTER_LEFT);\n                hBox.getChildren().addAll(iconImageView, editIconButton, resetIconButton);\n\n                iconPane.setRight(hBox);\n            }\n\n            var seedPane = new LinePane();\n            {\n                seedPane.setTitle(i18n(\"world.info.random_seed\"));\n\n                SimpleBooleanProperty visibility = new SimpleBooleanProperty();\n                StackPane visibilityButton = new StackPane();\n                {\n                    visibilityButton.setCursor(Cursor.HAND);\n                    visibilityButton.setAlignment(Pos.CENTER_RIGHT);\n                    FXUtils.onClicked(visibilityButton, () -> visibility.set(!visibility.get()));\n                }\n\n                Label seedLabel = new Label();\n                {\n                    FXUtils.copyOnDoubleClick(seedLabel);\n                    seedLabel.setAlignment(Pos.CENTER_RIGHT);\n\n                    seedLabel.setText(world.getSeed() != null ? world.getSeed().toString() : \"\");\n\n                    BoxBlur blur = new BoxBlur();\n                    blur.setIterations(3);\n                    FXUtils.onChangeAndOperate(visibility, isVisibility -> {\n                        SVG icon = isVisibility ? SVG.VISIBILITY : SVG.VISIBILITY_OFF;\n                        visibilityButton.getChildren().setAll(icon.createIcon(12));\n                        seedLabel.setEffect(isVisibility ? null : blur);\n                    });\n                }\n\n                HBox right = new HBox(8);\n                {\n                    right.setAlignment(Pos.CENTER_RIGHT);\n                    right.getChildren().setAll(visibilityButton, seedLabel);\n                    seedPane.setRight(right);\n                }\n            }\n\n            var worldSpawnPoint = new LineTextPane();\n            {\n                worldSpawnPoint.setTitle(i18n(\"world.info.spawn\"));\n\n                String value;\n                // Valid after 1.21.9-pre1\n                if (dataTag.get(\"spawn\") instanceof CompoundTag spawnTag && spawnTag.get(\"pos\") instanceof IntArrayTag posTag) {\n                    value = (spawnTag.get(\"dimension\") instanceof StringTag dimensionTag)\n                            ? Dimension.of(dimensionTag).formatPosition(posTag)\n                            : Dimension.OVERWORLD.formatPosition(posTag);\n                }\n                // Valid before 1.21.9-pre1\n                else if (dataTag.get(\"SpawnX\") instanceof IntTag intX\n                        && dataTag.get(\"SpawnY\") instanceof IntTag intY\n                        && dataTag.get(\"SpawnZ\") instanceof IntTag intZ) {\n                    value = Dimension.OVERWORLD.formatPosition(intX.getValue(), intY.getValue(), intZ.getValue());\n                } else {\n                    value = null;\n                }\n\n                worldSpawnPoint.setText(value);\n            }\n\n            var lastPlayedPane = new LineTextPane();\n            {\n                lastPlayedPane.setTitle(i18n(\"world.info.last_played\"));\n                lastPlayedPane.setText(formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())));\n            }\n\n            var timePane = new LineTextPane();\n            {\n                timePane.setTitle(i18n(\"world.info.time\"));\n                if (dataTag.get(\"Time\") instanceof LongTag timeTag) {\n                    Duration duration = Duration.ofSeconds(timeTag.getValue() / 20);\n                    timePane.setText(i18n(\"world.info.time.format\", duration.toDays(), duration.toHoursPart(), duration.toMinutesPart()));\n                }\n            }\n\n            var allowCheatsButton = new LineToggleButton();\n            {\n                allowCheatsButton.setTitle(i18n(\"world.info.allow_cheats\"));\n                allowCheatsButton.setDisable(isReadOnly);\n\n                bindTagAndToggleButton(dataTag.get(\"allowCommands\"), allowCheatsButton);\n            }\n\n            var generateFeaturesButton = new LineToggleButton();\n            {\n                generateFeaturesButton.setTitle(i18n(\"world.info.generate_features\"));\n                generateFeaturesButton.setDisable(isReadOnly);\n                // Valid before (1.16)20w20a\n                if (dataTag.get(\"MapFeatures\") instanceof ByteTag generateFeaturesTag) {\n                    bindTagAndToggleButton(generateFeaturesTag, generateFeaturesButton);\n                }\n                // Valid after (1.16)20w20a\n                else if (world.getNormalizedWorldGenSettingsData() != null) {\n                    CompoundTag unifiedWorldGenSettingsDataTag = world.getNormalizedWorldGenSettingsData();\n                    Tag tag = unifiedWorldGenSettingsDataTag.get(\"generate_features\"); // Valid between (1.16)20w20a and 26.1-snapshot-6\n                    if (tag == null)\n                        tag = unifiedWorldGenSettingsDataTag.get(\"generate_structures\"); // Valid after 26.1-snapshot-6\n                    bindTagAndToggleButton(tag, generateFeaturesButton);\n                } else {\n                    generateFeaturesButton.setDisable(true);\n                }\n            }\n\n            var difficultyButton = new LineSelectButton<Difficulty>();\n            {\n                difficultyButton.setTitle(i18n(\"world.info.difficulty\"));\n                difficultyButton.setDisable(isReadOnly);\n                difficultyButton.setItems(Difficulty.items);\n\n                Difficulty difficulty;\n                // Valid before 26.1-snapshot-6\n                if (dataTag.get(\"Difficulty\") instanceof ByteTag difficultyTag\n                        && (difficulty = Difficulty.of(difficultyTag.getValue())) != null) {\n                    difficultyButton.setValue(difficulty);\n                    difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> {\n                        if (newValue != null) {\n                            difficultyTag.setValue((byte) newValue.ordinal());\n                            saveWorldData();\n                        }\n                    });\n                }\n                // Valid after 26.1-snapshot-6\n                else if (dataTag.get(\"difficulty_settings\") instanceof CompoundTag difficultySettingTag\n                        && difficultySettingTag.get(\"difficulty\") instanceof StringTag difficultyTag\n                        && (difficulty = Difficulty.of(difficultyTag.getValue())) != null) {\n                    difficultyButton.setValue(difficulty);\n                    difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> {\n                        if (newValue != null) {\n                            difficultyTag.setValue(newValue.getTagStringValue());\n                            saveWorldData();\n                        }\n                    });\n                } else {\n                    difficultyButton.setDisable(true);\n                }\n            }\n\n            var difficultyLockPane = new LineToggleButton();\n            {\n                difficultyLockPane.setTitle(i18n(\"world.info.difficulty_lock\"));\n                difficultyLockPane.setDisable(isReadOnly);\n                // Valid before 26.1-snapshot-6\n                if (dataTag.get(\"DifficultyLocked\") instanceof ByteTag difficultyLockedTag) {\n                    bindTagAndToggleButton(difficultyLockedTag, difficultyLockPane);\n                }\n                // Valid after 26.1-snapshot-6\n                else if (dataTag.get(\"difficulty_settings\") instanceof CompoundTag difficultySettingTag\n                        && difficultySettingTag.get(\"locked\") instanceof ByteTag lockedTag) {\n                    bindTagAndToggleButton(lockedTag, difficultyLockPane);\n                } else {\n                    difficultyLockPane.setDisable(true);\n                }\n            }\n\n            worldInfo.getContent().setAll(\n                    worldNamePane, gameVersionPane, iconPane, seedPane, worldSpawnPoint, lastPlayedPane, timePane,\n                    allowCheatsButton, generateFeaturesButton, difficultyButton, difficultyLockPane);\n\n            rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n(\"world.info\")), worldInfo);\n        }\n\n        if (playerTag != null) {\n            ComponentList playerInfo = new ComponentList();\n\n            var locationPane = new LineTextPane();\n            {\n                locationPane.setTitle(i18n(\"world.info.player.location\"));\n                Dimension dimension = Dimension.of(playerTag.get(\"Dimension\"));\n                if (dimension != null && playerTag.get(\"Pos\") instanceof ListTag posTag) {\n                    locationPane.setText(dimension.formatPosition(posTag));\n                }\n            }\n\n            var lastDeathLocationPane = new LineTextPane();\n            {\n                lastDeathLocationPane.setTitle(i18n(\"world.info.player.last_death_location\"));\n                // Valid after 22w14a; prior to this version, the game did not record the last death location data.\n                if (playerTag.get(\"LastDeathLocation\") instanceof CompoundTag LastDeathLocationTag) {\n                    Dimension dimension = Dimension.of(LastDeathLocationTag.get(\"dimension\"));\n                    if (dimension != null && LastDeathLocationTag.get(\"pos\") instanceof IntArrayTag posTag) {\n                        lastDeathLocationPane.setText(dimension.formatPosition(posTag));\n                    }\n                }\n            }\n\n            var spawnPane = new LineTextPane();\n            {\n                spawnPane.setTitle(i18n(\"world.info.player.spawn\"));\n                if (playerTag.get(\"respawn\") instanceof CompoundTag respawnTag\n                        && respawnTag.get(\"dimension\") instanceof StringTag dimensionTag\n                        && respawnTag.get(\"pos\") instanceof IntArrayTag intArrayTag\n                        && intArrayTag.length() >= 3) { // Valid after 25w07a\n                    spawnPane.setText(Dimension.of(dimensionTag).formatPosition(intArrayTag));\n                } else if (playerTag.get(\"SpawnX\") instanceof IntTag intX\n                        && playerTag.get(\"SpawnY\") instanceof IntTag intY\n                        && playerTag.get(\"SpawnZ\") instanceof IntTag intZ) { // Valid before 25w07a\n                    // SpawnDimension tag is valid after 20w12a. Prior to this version, the game did not record the respawn point dimension and respawned in the Overworld.\n                    spawnPane.setText((playerTag.get(\"SpawnDimension\") instanceof StringTag dimensionTag ? Dimension.of(dimensionTag) : Dimension.OVERWORLD)\n                            .formatPosition(intX.getValue(), intY.getValue(), intZ.getValue()));\n                }\n            }\n\n            var playerGameTypePane = new LineSelectButton<GameType>();\n            {\n                playerGameTypePane.setTitle(i18n(\"world.info.player.game_type\"));\n                playerGameTypePane.setDisable(worldManagePage.isReadOnly());\n                playerGameTypePane.setItems(GameType.items);\n\n                // Valid before 26.1-snapshot-6\n                Tag rawHardcoreTag = dataTag.get(\"hardcore\");\n                // Valid after 26.1-snapshot-6\n                if (rawHardcoreTag == null && dataTag.get(\"difficulty_settings\") instanceof CompoundTag difficultySettingsTag) {\n                    rawHardcoreTag = difficultySettingsTag.get(\"hardcore\");\n                }\n\n                if (playerTag.get(\"playerGameType\") instanceof IntTag playerGameTypeTag && rawHardcoreTag instanceof ByteTag hardcoreTag) {\n                    boolean isHardcore = hardcoreTag.getValue() == 1;\n                    GameType gameType = GameType.of(playerGameTypeTag.getValue(), isHardcore);\n                    if (gameType != null) {\n                        playerGameTypePane.setValue(gameType);\n                        playerGameTypePane.valueProperty().addListener((o, oldValue, newValue) -> {\n                            if (newValue != null) {\n                                if (newValue == GameType.HARDCORE) {\n                                    playerGameTypeTag.setValue(0); // survival (hardcore worlds are survival+hardcore flag)\n                                    hardcoreTag.setValue((byte) 1);\n                                } else {\n                                    playerGameTypeTag.setValue(newValue.ordinal());\n                                    hardcoreTag.setValue((byte) 0);\n                                }\n                                saveWorldData();\n                            }\n                        });\n                    } else {\n                        playerGameTypePane.setDisable(true);\n                    }\n                } else {\n                    playerGameTypePane.setDisable(true);\n                }\n            }\n\n            var healthPane = new LinePane();\n            {\n                healthPane.setTitle(i18n(\"world.info.player.health\"));\n                setRightTextField(healthPane, 50, playerTag.get(\"Health\"));\n            }\n\n            var foodLevelPane = new LinePane();\n            {\n                foodLevelPane.setTitle(i18n(\"world.info.player.food_level\"));\n                setRightTextField(foodLevelPane, 50, playerTag.get(\"foodLevel\"));\n            }\n\n            var foodSaturationPane = new LinePane();\n            {\n                foodSaturationPane.setTitle(i18n(\"world.info.player.food_saturation_level\"));\n                setRightTextField(foodSaturationPane, 50, playerTag.get(\"foodSaturationLevel\"));\n            }\n\n            var xpLevelPane = new LinePane();\n            {\n                xpLevelPane.setTitle(i18n(\"world.info.player.xp_level\"));\n                setRightTextField(xpLevelPane, 50, playerTag.get(\"XpLevel\"));\n            }\n\n            playerInfo.getContent().setAll(\n                    locationPane, lastDeathLocationPane, spawnPane, playerGameTypePane,\n                    healthPane, foodLevelPane, foodSaturationPane, xpLevelPane\n            );\n\n            rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n(\"world.info.player\")), playerInfo);\n        }\n    }\n\n    private void setRightTextField(LinePane linePane, int perfWidth, Tag tag) {\n        JFXTextField textField = new JFXTextField();\n        setRightTextField(linePane, textField, perfWidth);\n        if (tag instanceof IntTag intTag) {\n            bindTagAndTextField(intTag, textField);\n        } else if (tag instanceof FloatTag floatTag) {\n            bindTagAndTextField(floatTag, textField);\n        } else {\n            textField.setDisable(true);\n        }\n    }\n\n    private void setRightTextField(LinePane linePane, JFXTextField textField, int perfWidth) {\n        textField.setDisable(isReadOnly);\n        textField.setPrefWidth(perfWidth);\n        linePane.setRight(textField);\n    }\n\n    private void bindTagAndToggleButton(Tag tag, LineToggleButton toggleButton) {\n        if (tag instanceof ByteTag byteTag) {\n            byte value = byteTag.getValue();\n            if (value == 0 || value == 1) {\n                toggleButton.setSelected(value == 1);\n                toggleButton.selectedProperty().addListener((o, oldValue, newValue) -> {\n                    try {\n                        byteTag.setValue((byte) (newValue ? 1 : 0));\n                        saveWorldData();\n                    } catch (Exception e) {\n                        toggleButton.setSelected(oldValue);\n                        LOG.warning(\"Exception happened when saving world info\", e);\n                    }\n                });\n            } else {\n                toggleButton.setDisable(true);\n            }\n        } else {\n            toggleButton.setDisable(true);\n        }\n    }\n\n    private void bindTagAndTextField(IntTag intTag, JFXTextField jfxTextField) {\n        jfxTextField.setText(intTag.getValue().toString());\n\n        jfxTextField.textProperty().addListener((o, oldValue, newValue) -> {\n            if (newValue != null) {\n                try {\n                    Integer integer = Lang.toIntOrNull(newValue);\n                    if (integer != null) {\n                        intTag.setValue(integer);\n                        saveWorldData();\n                    }\n                } catch (Exception e) {\n                    jfxTextField.setText(oldValue);\n                    LOG.warning(\"Exception happened when saving world data\", e);\n                }\n            }\n        });\n        FXUtils.setValidateWhileTextChanged(jfxTextField, true);\n        jfxTextField.setValidators(new NumberValidator(i18n(\"input.number\"), true));\n    }\n\n    private void bindTagAndTextField(FloatTag floatTag, JFXTextField jfxTextField) {\n        jfxTextField.setText(new DecimalFormat(\"0.#\").format(floatTag.getValue()));\n\n        jfxTextField.textProperty().addListener((o, oldValue, newValue) -> {\n            if (newValue != null) {\n                try {\n                    Float floatValue = Lang.toFloatOrNull(newValue);\n                    if (floatValue != null) {\n                        floatTag.setValue(floatValue);\n                        saveWorldData();\n                    }\n                } catch (Exception e) {\n                    jfxTextField.setText(oldValue);\n                    LOG.warning(\"Exception happened when saving world data\", e);\n                }\n            }\n        });\n        FXUtils.setValidateWhileTextChanged(jfxTextField, true);\n        jfxTextField.setValidators(new DoubleValidator(i18n(\"input.number\"), true));\n    }\n\n    private void saveWorldData() {\n        LOG.info(\"Saving data of world \" + world.getWorldName());\n        try {\n            this.world.writeWorldData();\n        } catch (IOException e) {\n            LOG.warning(\"Failed to save world data\", e);\n        }\n    }\n\n    @Override\n    public void refresh() {\n        setFailedReason(null);\n        try {\n            this.isReadOnly = worldManagePage.isReadOnly();\n            this.levelData = world.getLevelData();\n            this.playerData = world.getPlayerData();\n            updateControls();\n        } catch (Exception e) {\n            LOG.warning(\"Failed to refresh world info\", e);\n            setFailedReason(i18n(\"world.info.failed\"));\n        }\n    }\n\n    private record Dimension(String name) {\n        static final Dimension OVERWORLD = new Dimension(null);\n        static final Dimension THE_NETHER = new Dimension(i18n(\"world.info.dimension.the_nether\"));\n        static final Dimension THE_END = new Dimension(i18n(\"world.info.dimension.the_end\"));\n\n        static Dimension of(Tag tag) {\n            if (tag instanceof IntTag intTag) {\n                return switch (intTag.getValue()) {\n                    case 0 -> OVERWORLD;\n                    case -1 -> THE_NETHER;\n                    case 1 -> THE_END;\n                    default -> null;\n                };\n            } else if (tag instanceof StringTag stringTag) {\n                String id = stringTag.getValue();\n                return switch (id) {\n                    case \"overworld\", \"minecraft:overworld\" -> OVERWORLD;\n                    case \"the_nether\", \"minecraft:the_nether\" -> THE_NETHER;\n                    case \"the_end\", \"minecraft:the_end\" -> THE_END;\n                    default -> new Dimension(id);\n                };\n            } else {\n                return null;\n            }\n        }\n\n        String formatPosition(Tag tag) {\n            if (tag instanceof ListTag listTag && listTag.size() == 3) {\n\n                Tag x = listTag.get(0);\n                Tag y = listTag.get(1);\n                Tag z = listTag.get(2);\n\n                if (x instanceof DoubleTag && y instanceof DoubleTag && z instanceof DoubleTag) {\n                    return this == OVERWORLD\n                            ? String.format(\"(%.2f, %.2f, %.2f)\", x.getValue(), y.getValue(), z.getValue())\n                            : String.format(\"%s (%.2f, %.2f, %.2f)\", name, x.getValue(), y.getValue(), z.getValue());\n                }\n\n                return null;\n            }\n\n            if (tag instanceof IntArrayTag intArrayTag) {\n\n                int x = intArrayTag.getValue(0);\n                int y = intArrayTag.getValue(1);\n                int z = intArrayTag.getValue(2);\n\n                return this == OVERWORLD\n                        ? String.format(\"(%d, %d, %d)\", x, y, z)\n                        : String.format(\"%s (%d, %d, %d)\", name, x, y, z);\n            }\n\n            return null;\n        }\n\n        String formatPosition(int x, int y, int z) {\n            return this == OVERWORLD\n                    ? String.format(\"(%d, %d, %d)\", x, y, z)\n                    : String.format(\"%s (%d, %d, %d)\", name, x, y, z);\n        }\n\n        String formatPosition(double x, double y, double z) {\n            return this == OVERWORLD\n                    ? String.format(\"(%.2f, %.2f, %.2f)\", x, y, z)\n                    : String.format(\"%s (%.2f, %.2f, %.2f)\", name, x, y, z);\n        }\n    }\n\n    private enum Difficulty {\n        PEACEFUL, EASY, NORMAL, HARD;\n\n        static final ObservableList<Difficulty> items = FXCollections.observableList(Arrays.asList(values()));\n\n        static Difficulty of(int d) {\n            return (d >= 0 && d < items.size()) ? items.get(d) : null;\n        }\n\n        static Difficulty of(String name) {\n            for (Difficulty d : items) if (d.name().toLowerCase(Locale.ROOT).equals(name)) return d;\n            return null;\n        }\n\n        String getTagStringValue() {\n            return this.name().toLowerCase(Locale.ROOT);\n        }\n\n        @Override\n        public String toString() {\n            return i18n(\"world.info.difficulty.\" + name().toLowerCase(Locale.ROOT));\n        }\n    }\n\n    private enum GameType {\n        SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR, HARDCORE;\n\n        static final ObservableList<GameType> items = FXCollections.observableList(Arrays.asList(values()));\n\n        static GameType of(int d, boolean hardcore) {\n            if (hardcore && d == 0) return HARDCORE; // hardcore + survival\n            return d >= 0 && d < 4 ? items.get(d) : null;\n        }\n\n        @Override\n        public String toString() {\n            return i18n(\"world.info.player.game_type.\" + name().toLowerCase(Locale.ROOT));\n        }\n    }\n\n    private void changeWorldIcon() {\n        FileChooser fileChooser = new FileChooser();\n        fileChooser.setTitle(i18n(\"world.icon.choose.title\"));\n        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"extension.png\"), \"*.png\"));\n        fileChooser.setInitialFileName(\"icon.png\");\n\n        Path iconPath = FileUtils.toPath(fileChooser.showOpenDialog(Controllers.getStage()));\n        if (iconPath == null) return;\n\n        Image image;\n        try {\n            image = FXUtils.loadImage(iconPath);\n        } catch (Exception e) {\n            LOG.warning(\"Failed to load image\", e);\n            Controllers.dialog(i18n(\"world.icon.change.fail.load.text\"), i18n(\"world.icon.change.fail.load.title\"), MessageDialogPane.MessageType.ERROR);\n            return;\n        }\n        if ((int) image.getWidth() == 64 && (int) image.getHeight() == 64) {\n            Path output = world.getFile().resolve(\"icon.png\");\n            saveWorldIcon(iconPath, image, output);\n        } else {\n            Controllers.dialog(i18n(\"world.icon.change.fail.not_64x64.text\", (int) image.getWidth(), (int) image.getHeight()), i18n(\"world.icon.change.fail.not_64x64.title\"), MessageDialogPane.MessageType.ERROR);\n        }\n    }\n\n    private void saveWorldIcon(Path sourcePath, Image image, Path targetPath) {\n        Image oldImage = iconImageView.getImage();\n        try {\n            FileUtils.copyFile(sourcePath, targetPath);\n            iconImageView.setImage(image);\n            Controllers.showToast(i18n(\"world.icon.change.succeed.toast\"));\n        } catch (IOException e) {\n            LOG.warning(\"Failed to save world icon \" + e.getMessage());\n            iconImageView.setImage(oldImage);\n        }\n    }\n\n    private void clearWorldIcon() {\n        Path output = world.getFile().resolve(\"icon.png\");\n        try {\n            Files.deleteIfExists(output);\n            iconImageView.setImage(FXUtils.newBuiltinImage(\"/assets/img/unknown_server.png\"));\n        } catch (IOException e) {\n            LOG.warning(\"Failed to delete world icon \", e);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXButton;\nimport com.jfoenix.controls.JFXCheckBox;\nimport com.jfoenix.controls.JFXListView;\nimport com.jfoenix.controls.JFXPopup;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ReadOnlyBooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.Skin;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.game.World;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.*;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.util.ChunkBaseApp;\nimport org.jackhuang.hmcl.util.i18n.I18n;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.io.IOException;\nimport java.nio.file.FileAlreadyExistsException;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;\nimport static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;\nimport static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class WorldListPage extends ListPageBase<World> implements VersionPage.VersionLoadable {\n    private final BooleanProperty showAll = new SimpleBooleanProperty(this, \"showAll\", false);\n\n    private Path savesDir;\n    private List<World> worlds;\n    private Profile profile;\n    private String instanceId;\n    private final BooleanProperty supportQuickPlay = new SimpleBooleanProperty(this, \"supportQuickPlay\", false);\n\n    private int refreshCount = 0;\n\n    public WorldListPage() {\n        FXUtils.applyDragListener(this, it -> \"zip\".equals(FileUtils.getExtension(it)), modpacks -> {\n            installWorld(modpacks.get(0));\n        });\n\n        showAll.addListener(e -> updateWorldList());\n    }\n\n    @Override\n    protected Skin<WorldListPage> createDefaultSkin() {\n        return new WorldListPageSkin();\n    }\n\n    @Override\n    public void loadVersion(Profile profile, String id) {\n        this.profile = profile;\n        this.instanceId = id;\n        this.savesDir = profile.getRepository().getSavesDirectory(id);\n        refresh();\n    }\n\n    private void updateWorldList() {\n        if (worlds == null) {\n            getItems().clear();\n        } else if (showAll.get()) {\n            getItems().setAll(worlds);\n        } else {\n            GameVersionNumber gameVersion = profile.getRepository().getGameVersion(instanceId).map(GameVersionNumber::asGameVersion).orElse(null);\n            getItems().setAll(worlds.stream()\n                    .filter(world -> world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))\n                    .toList());\n        }\n    }\n\n    public void refresh() {\n        if (profile == null || instanceId == null)\n            return;\n\n        int currentRefresh = ++refreshCount;\n\n        setLoading(true);\n        Task.supplyAsync(Schedulers.io(), () -> {\n            // Ensure the game version number is parsed\n            profile.getRepository().getGameVersion(instanceId);\n            return World.getWorlds(savesDir);\n        }).whenComplete(Schedulers.javafx(), (result, exception) -> {\n            if (refreshCount != currentRefresh) {\n                // A newer refresh task is running, discard this result\n                return;\n            }\n\n            Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);\n            supportQuickPlay.set(World.supportQuickPlay(GameVersionNumber.asGameVersion(gameVersion)));\n\n            worlds = result;\n            updateWorldList();\n\n            if (exception != null)\n                LOG.warning(\"Failed to load world list page\", exception);\n\n            setLoading(false);\n        }).start();\n    }\n\n    public void add() {\n        FileChooser chooser = new FileChooser();\n        chooser.setTitle(i18n(\"world.add.title\"));\n        chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n(\"extension.world\"), \"*.zip\"));\n        List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));\n\n        if (res == null || res.isEmpty()) return;\n        installWorld(res.get(0));\n    }\n\n    public void download() {\n        Controllers.getDownloadPage().showWorldDownloads();\n        Controllers.navigate(Controllers.getDownloadPage());\n    }\n\n    private void installWorld(Path zipFile) {\n        // Only accept one world file because user is required to confirm the new world name\n        // Or too many input dialogs are popped.\n        Task.supplyAsync(() -> new World(zipFile))\n                .whenComplete(Schedulers.javafx(), world -> {\n                    Controllers.prompt(i18n(\"world.name.enter\"), (name, handler) -> {\n                        Task.runAsync(() -> world.install(savesDir, name))\n                                .whenComplete(Schedulers.javafx(), () -> {\n                                    handler.resolve();\n                                    refresh();\n                                }, e -> {\n                                    if (e instanceof FileAlreadyExistsException)\n                                        handler.reject(i18n(\"world.add.failed\", i18n(\"world.add.already_exists\")));\n                                    else if (e instanceof IOException && e.getCause() instanceof InvalidPathException)\n                                        handler.reject(i18n(\"world.add.failed\", i18n(\"install.new_game.malformed\")));\n                                    else\n                                        handler.reject(i18n(\"world.add.failed\", e.getClass().getName() + \": \" + e.getLocalizedMessage()));\n                                }).start();\n                    }, world.getWorldName(), new Validator(i18n(\"install.new_game.malformed\"), FileUtils::isNameValid));\n                }, e -> {\n                    LOG.warning(\"Unable to parse world file \" + zipFile, e);\n                    Controllers.dialog(i18n(\"world.add.invalid\"));\n                }).start();\n    }\n\n    private void showManagePage(World world) {\n        Controllers.navigate(new WorldManagePage(world, profile, instanceId));\n    }\n\n    public void export(World world) {\n        WorldManageUIUtils.export(world);\n    }\n\n    public void delete(World world) {\n        WorldManageUIUtils.delete(world, this::refresh);\n    }\n\n    public void copy(World world) {\n        WorldManageUIUtils.copyWorld(world, this::refresh);\n    }\n\n    public void reveal(World world) {\n        FXUtils.openFolder(world.getFile());\n    }\n\n    public void launch(World world) {\n        Versions.launchAndEnterWorld(profile, instanceId, world.getFileName());\n    }\n\n    public void generateLaunchScript(World world) {\n        Versions.generateLaunchScriptForQuickEnterWorld(profile, instanceId, world.getFileName());\n    }\n\n    public BooleanProperty showAllProperty() {\n        return showAll;\n    }\n\n    public ReadOnlyBooleanProperty supportQuickPlayProperty() {\n        return supportQuickPlay;\n    }\n\n    private final class WorldListPageSkin extends ToolbarListPageSkin<World, WorldListPage> {\n\n        WorldListPageSkin() {\n            super(WorldListPage.this);\n        }\n\n        @Override\n        protected List<Node> initializeToolbar(WorldListPage skinnable) {\n            JFXCheckBox chkShowAll = new JFXCheckBox(i18n(\"world.show_all\"));\n            chkShowAll.selectedProperty().bindBidirectional(skinnable.showAllProperty());\n\n            return Arrays.asList(\n                    chkShowAll,\n                    createToolbarButton2(i18n(\"button.refresh\"), SVG.REFRESH, skinnable::refresh),\n                    createToolbarButton2(i18n(\"world.add\"), SVG.ADD, skinnable::add),\n                    createToolbarButton2(i18n(\"world.download\"), SVG.DOWNLOAD, skinnable::download)\n            );\n        }\n\n        @Override\n        protected ListCell<World> createListCell(JFXListView<World> listView) {\n            return new WorldListCell(getSkinnable());\n        }\n    }\n\n    private static final class WorldListCell extends ListCell<World> {\n\n        private final WorldListPage page;\n\n        private final RipplerContainer graphic;\n        private final ImageContainer imageView;\n        private final Tooltip leftTooltip;\n        private final TwoLineListItem content;\n        private final JFXButton btnLaunch;\n\n        public WorldListCell(WorldListPage page) {\n            this.page = page;\n\n            var root = new BorderPane();\n            root.getStyleClass().add(\"md-list-cell\");\n            root.setPadding(new Insets(8));\n\n            {\n                StackPane left = new StackPane();\n                this.leftTooltip = new Tooltip();\n                FXUtils.installSlowTooltip(left, leftTooltip);\n                root.setLeft(left);\n                left.setPadding(new Insets(0, 8, 0, 0));\n\n                this.imageView = new ImageContainer(32);\n                left.getChildren().add(imageView);\n            }\n\n            {\n                this.content = new TwoLineListItem();\n                root.setCenter(content);\n                content.setMouseTransparent(true);\n            }\n\n            {\n                HBox right = new HBox(8);\n                root.setRight(right);\n                right.setAlignment(Pos.CENTER_RIGHT);\n\n                btnLaunch = FXUtils.newToggleButton4(SVG.ROCKET_LAUNCH);\n                btnLaunch.visibleProperty().bind(page.supportQuickPlayProperty());\n                btnLaunch.managedProperty().bind(btnLaunch.visibleProperty());\n                right.getChildren().add(btnLaunch);\n                FXUtils.installFastTooltip(btnLaunch, i18n(\"version.launch\"));\n                btnLaunch.setOnAction(event -> {\n                    World world = getItem();\n                    if (world != null)\n                        page.launch(world);\n                });\n\n                JFXButton btnMore = FXUtils.newToggleButton4(SVG.MORE_VERT);\n                right.getChildren().add(btnMore);\n                btnMore.setOnAction(event -> {\n                    World world = getItem();\n                    if (world != null)\n                        showPopupMenu(world, page.supportQuickPlayProperty().get(), JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight());\n                });\n            }\n\n            this.graphic = new RipplerContainer(root);\n            graphic.setOnMouseClicked(event -> {\n                if (event.getClickCount() != 1)\n                    return;\n\n                World world = getItem();\n                if (world == null)\n                    return;\n\n                if (event.getButton() == MouseButton.PRIMARY)\n                    page.showManagePage(world);\n                else if (event.getButton() == MouseButton.SECONDARY)\n                    showPopupMenu(world, page.supportQuickPlayProperty().get(), JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());\n            });\n        }\n\n        @Override\n        protected void updateItem(World world, boolean empty) {\n            super.updateItem(world, empty);\n\n            this.content.getTags().clear();\n\n            if (empty || world == null) {\n                setGraphic(null);\n                imageView.setImage(null);\n                leftTooltip.setText(\"\");\n                content.setTitle(\"\");\n                content.setSubtitle(\"\");\n            } else {\n                imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage(\"/assets/img/unknown_server.png\") : world.getIcon());\n                leftTooltip.setText(world.getFile().toString());\n                content.setTitle(world.getWorldName() != null ? parseColorEscapes(world.getWorldName()) : \"\");\n\n                if (world.getGameVersion() != null)\n                    content.addTag(I18n.getDisplayVersion(world.getGameVersion()));\n                if (world.isLocked()) {\n                    content.addTag(i18n(\"world.locked\"));\n                    btnLaunch.setDisable(true);\n                } else {\n                    btnLaunch.setDisable(false);\n                }\n\n                content.setSubtitle(i18n(\"world.datetime\", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed()))));\n\n                setGraphic(graphic);\n            }\n        }\n\n        // Popup Menu\n\n        public void showPopupMenu(World world, boolean supportQuickPlay, JFXPopup.PopupHPosition hPosition, double initOffsetX, double initOffsetY) {\n            boolean worldLocked = world.isLocked();\n\n            PopupMenu popupMenu = new PopupMenu();\n            JFXPopup popup = new JFXPopup(popupMenu);\n\n            if (supportQuickPlay) {\n\n                IconedMenuItem launchItem = new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n(\"version.launch_and_enter_world\"), () -> page.launch(world), popup);\n                launchItem.setDisable(worldLocked);\n\n                popupMenu.getContent().addAll(\n                        launchItem,\n                        new IconedMenuItem(SVG.SCRIPT, i18n(\"version.launch_script\"), () -> page.generateLaunchScript(world), popup),\n                        new MenuSeparator()\n                );\n            }\n\n            popupMenu.getContent().add(new IconedMenuItem(SVG.SETTINGS, i18n(\"world.manage.button\"), () -> page.showManagePage(world), popup));\n\n            if (ChunkBaseApp.isSupported(world)) {\n                popupMenu.getContent().addAll(\n                        new MenuSeparator(),\n                        new IconedMenuItem(SVG.EXPLORE, i18n(\"world.chunkbase.seed_map\"), () -> ChunkBaseApp.openSeedMap(world), popup),\n                        new IconedMenuItem(SVG.VISIBILITY, i18n(\"world.chunkbase.stronghold\"), () -> ChunkBaseApp.openStrongholdFinder(world), popup),\n                        new IconedMenuItem(SVG.FORT, i18n(\"world.chunkbase.nether_fortress\"), () -> ChunkBaseApp.openNetherFortressFinder(world), popup)\n                );\n\n                if (ChunkBaseApp.supportEndCity(world)) {\n                    popupMenu.getContent().add(new IconedMenuItem(SVG.LOCATION_CITY, i18n(\"world.chunkbase.end_city\"), () -> ChunkBaseApp.openEndCityFinder(world), popup));\n                }\n            }\n\n            IconedMenuItem exportMenuItem = new IconedMenuItem(SVG.OUTPUT, i18n(\"world.export\"), () -> page.export(world), popup);\n            exportMenuItem.setDisable(worldLocked);\n\n            IconedMenuItem deleteMenuItem = new IconedMenuItem(SVG.DELETE_FOREVER, i18n(\"world.delete\"), () -> page.delete(world), popup);\n            deleteMenuItem.setDisable(worldLocked);\n\n            IconedMenuItem duplicateMenuItem = new IconedMenuItem(SVG.CONTENT_COPY, i18n(\"world.duplicate\"), () -> page.copy(world), popup);\n            duplicateMenuItem.setDisable(worldLocked);\n\n            popupMenu.getContent().addAll(\n                    new MenuSeparator(),\n                    exportMenuItem,\n                    deleteMenuItem,\n                    duplicateMenuItem\n            );\n\n            popupMenu.getContent().addAll(\n                    new MenuSeparator(),\n                    new IconedMenuItem(SVG.FOLDER_OPEN, i18n(\"folder.world\"), () -> page.reveal(world), popup)\n            );\n\n            JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(this, popup);\n            popup.show(this, vPosition, hPosition, initOffsetX, vPosition == JFXPopup.PopupVPosition.TOP ? initOffsetY : -initOffsetY);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport com.jfoenix.controls.JFXPopup;\nimport javafx.application.Platform;\nimport javafx.beans.property.*;\nimport javafx.geometry.Insets;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport org.jackhuang.hmcl.game.World;\nimport org.jackhuang.hmcl.setting.Profile;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.ui.SVG;\nimport org.jackhuang.hmcl.ui.animation.TransitionPane;\nimport org.jackhuang.hmcl.ui.construct.*;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;\nimport org.jackhuang.hmcl.ui.decorator.DecoratorPage;\nimport org.jackhuang.hmcl.util.ChunkBaseApp;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Path;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class WorldManagePage extends DecoratorAnimatedPage implements DecoratorPage {\n\n    private final World world;\n    private final Path backupsDir;\n    private final Profile profile;\n    private final String instanceId;\n    private final boolean supportQuickPlay;\n    private FileChannel sessionLockChannel;\n\n    private final ObjectProperty<State> state;\n    private boolean isFirstNavigation = true;\n    private final BooleanProperty refreshable = new SimpleBooleanProperty(true);\n    private final BooleanProperty readOnly = new SimpleBooleanProperty(false);\n\n    private final TransitionPane transitionPane = new TransitionPane();\n    private final TabHeader header = new TabHeader(transitionPane);\n    private final TabHeader.Tab<WorldInfoPage> worldInfoTab = new TabHeader.Tab<>(\"worldInfoPage\");\n    private final TabHeader.Tab<WorldBackupsPage> worldBackupsTab = new TabHeader.Tab<>(\"worldBackupsPage\");\n    private final TabHeader.Tab<DatapackListPage> datapackTab = new TabHeader.Tab<>(\"datapackListPage\");\n\n    public WorldManagePage(World world, Profile profile, String instanceId) {\n        this.world = world;\n        this.backupsDir = profile.getRepository().getBackupsDirectory(instanceId);\n        this.profile = profile;\n        this.instanceId = instanceId;\n\n        updateSessionLockChannel();\n\n        try {\n            this.world.reloadWorldData();\n        } catch (IOException e) {\n            LOG.warning(\"Can not load world data of world: \" + this.world.getFile(), e);\n            this.addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> closePageForLoadingFail());\n        }\n\n        worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));\n        worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));\n        datapackTab.setNodeSupplier(() -> new DatapackListPage(this));\n\n        this.state = new SimpleObjectProperty<>(new State(i18n(\"world.manage.title\", StringUtils.parseColorEscapes(world.getWorldName())), null, true, true, true));\n\n        Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);\n        supportQuickPlay = World.supportQuickPlay(GameVersionNumber.asGameVersion(gameVersion));\n\n        this.addEventHandler(Navigator.NavigationEvent.EXITED, this::onExited);\n        this.addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);\n    }\n\n    @Override\n    protected @NotNull Skin createDefaultSkin() {\n        return new Skin(this);\n    }\n\n    @Override\n    public void refresh() {\n        updateSessionLockChannel();\n        try {\n            world.reloadWorldData();\n        } catch (IOException e) {\n            LOG.warning(\"Can not load world data of world: \" + world.getFile(), e);\n            closePageForLoadingFail();\n            return;\n        }\n\n        for (var tab : header.getTabs()) {\n            if (tab.getNode() instanceof WorldRefreshable r) {\n                r.refresh();\n            }\n        }\n    }\n\n    private void closePageForLoadingFail() {\n        Platform.runLater(() -> {\n            fireEvent(new PageCloseEvent());\n            Controllers.dialog(i18n(\"world.load.fail\"), null, MessageDialogPane.MessageType.ERROR);\n        });\n    }\n\n    private void updateSessionLockChannel() {\n        if (sessionLockChannel == null || !sessionLockChannel.isOpen()) {\n            sessionLockChannel = WorldManageUIUtils.getSessionLockChannel(world);\n            readOnly.set(sessionLockChannel == null);\n        }\n    }\n\n    private void onNavigated(Navigator.NavigationEvent event) {\n        if (isFirstNavigation)\n            isFirstNavigation = false;\n        else\n            refresh();\n    }\n\n    public void onExited(Navigator.NavigationEvent event) {\n        try {\n            WorldManageUIUtils.closeSessionLockChannel(world, sessionLockChannel);\n        } catch (IOException ignored) {\n        }\n    }\n\n    public void launch() {\n        fireEvent(new PageCloseEvent());\n        Versions.launchAndEnterWorld(profile, instanceId, world.getFileName());\n    }\n\n    public void generateLaunchScript() {\n        Versions.generateLaunchScriptForQuickEnterWorld(profile, instanceId, world.getFileName());\n    }\n\n    @Override\n    public ReadOnlyObjectProperty<State> stateProperty() {\n        return state;\n    }\n\n    public void setTitle(String title) {\n        this.state.set(new DecoratorPage.State(title, null, true, true, true));\n    }\n\n    public World getWorld() {\n        return world;\n    }\n\n    public Path getBackupsDir() {\n        return backupsDir;\n    }\n\n    public boolean isReadOnly() {\n        return readOnly.get();\n    }\n\n    public BooleanProperty readOnlyProperty() {\n        return readOnly;\n    }\n\n    @Override\n    public BooleanProperty refreshableProperty() {\n        return refreshable;\n    }\n\n    public static class Skin extends DecoratorAnimatedPageSkin<WorldManagePage> {\n\n        protected Skin(WorldManagePage control) {\n            super(control);\n\n            setCenter(control.transitionPane);\n            setLeft(getSidebar());\n        }\n\n        private BorderPane getSidebar() {\n            BorderPane sidebar = new BorderPane();\n            {\n                FXUtils.setLimitWidth(sidebar, 200);\n                VBox.setVgrow(sidebar, Priority.ALWAYS);\n            }\n\n            sidebar.setTop(getTabBar());\n            sidebar.setBottom(getToolBar());\n\n            return sidebar;\n        }\n\n        private AdvancedListBox getTabBar() {\n            AdvancedListBox tabBar = new AdvancedListBox();\n            {\n                getSkinnable().header.getTabs().addAll(getSkinnable().worldInfoTab, getSkinnable().worldBackupsTab);\n                getSkinnable().header.select(getSkinnable().worldInfoTab);\n\n                tabBar.addNavigationDrawerTab(getSkinnable().header, getSkinnable().worldInfoTab, i18n(\"world.info\"), SVG.INFO, SVG.INFO_FILL)\n                        .addNavigationDrawerTab(getSkinnable().header, getSkinnable().worldBackupsTab, i18n(\"world.backup\"), SVG.ARCHIVE, SVG.ARCHIVE_FILL);\n\n                if (getSkinnable().world.supportDatapacks()) {\n                    getSkinnable().header.getTabs().add(getSkinnable().datapackTab);\n                    tabBar.addNavigationDrawerTab(getSkinnable().header, getSkinnable().datapackTab, i18n(\"world.datapack\"), SVG.EXTENSION, SVG.EXTENSION_FILL);\n                }\n            }\n\n            return tabBar;\n        }\n\n        private AdvancedListBox getToolBar() {\n            AdvancedListBox toolbar = new AdvancedListBox();\n            BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));\n            {\n                if (getSkinnable().supportQuickPlay) {\n                    toolbar.addNavigationDrawerItem(i18n(\"version.launch\"), SVG.ROCKET_LAUNCH, () -> getSkinnable().launch(), advancedListItem -> advancedListItem.disableProperty().bind(getSkinnable().readOnlyProperty()));\n                }\n\n                if (ChunkBaseApp.isSupported(getSkinnable().world)) {\n                    PopupMenu chunkBasePopupMenu = new PopupMenu();\n                    JFXPopup chunkBasePopup = new JFXPopup(chunkBasePopupMenu);\n\n                    chunkBasePopupMenu.getContent().addAll(\n                            new IconedMenuItem(SVG.EXPLORE, i18n(\"world.chunkbase.seed_map\"), () -> ChunkBaseApp.openSeedMap(getSkinnable().world), chunkBasePopup),\n                            new IconedMenuItem(SVG.VISIBILITY, i18n(\"world.chunkbase.stronghold\"), () -> ChunkBaseApp.openStrongholdFinder(getSkinnable().world), chunkBasePopup),\n                            new IconedMenuItem(SVG.FORT, i18n(\"world.chunkbase.nether_fortress\"), () -> ChunkBaseApp.openNetherFortressFinder(getSkinnable().world), chunkBasePopup)\n                    );\n\n                    if (ChunkBaseApp.supportEndCity(getSkinnable().world)) {\n                        chunkBasePopupMenu.getContent().add(\n                                new IconedMenuItem(SVG.LOCATION_CITY, i18n(\"world.chunkbase.end_city\"), () -> ChunkBaseApp.openEndCityFinder(getSkinnable().world), chunkBasePopup));\n                    }\n\n                    toolbar.addNavigationDrawerItem(i18n(\"world.chunkbase\"), SVG.EXPLORE, null, chunkBaseMenuItem ->\n                            chunkBaseMenuItem.setOnAction(e ->\n                                    chunkBasePopup.show(chunkBaseMenuItem,\n                                            JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT,\n                                            chunkBaseMenuItem.getWidth(), 0)));\n                }\n\n                toolbar.addNavigationDrawerItem(i18n(\"settings.game.exploration\"), SVG.FOLDER_OPEN, () -> FXUtils.openFolder(getSkinnable().world.getFile()));\n\n                {\n                    PopupMenu managePopupMenu = new PopupMenu();\n                    JFXPopup managePopup = new JFXPopup(managePopupMenu);\n\n                    if (getSkinnable().supportQuickPlay) {\n                        managePopupMenu.getContent().addAll(\n                                new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n(\"version.launch\"), () -> getSkinnable().launch(), managePopup),\n                                new IconedMenuItem(SVG.SCRIPT, i18n(\"version.launch_script\"), () -> getSkinnable().generateLaunchScript(), managePopup),\n                                new MenuSeparator()\n                        );\n                    }\n\n                    managePopupMenu.getContent().addAll(\n                            new IconedMenuItem(SVG.OUTPUT, i18n(\"world.export\"), () -> WorldManageUIUtils.export(getSkinnable().world, getSkinnable().sessionLockChannel), managePopup),\n                            new IconedMenuItem(SVG.DELETE_FOREVER, i18n(\"world.delete\"), () -> WorldManageUIUtils.delete(getSkinnable().world, () -> getSkinnable().fireEvent(new PageCloseEvent()), getSkinnable().sessionLockChannel), managePopup),\n                            new IconedMenuItem(SVG.CONTENT_COPY, i18n(\"world.duplicate\"), () -> WorldManageUIUtils.copyWorld(getSkinnable().world, null), managePopup)\n                    );\n\n                    toolbar.addNavigationDrawerItem(i18n(\"settings.game.management\"), SVG.MENU, null, managePopupMenuItem ->\n                    {\n                        managePopupMenuItem.setOnAction(e ->\n                                managePopup.show(managePopupMenuItem,\n                                        JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT,\n                                        managePopupMenuItem.getWidth(), 0));\n                        managePopupMenuItem.disableProperty().bind(getSkinnable().readOnlyProperty());\n                    });\n                }\n            }\n            return toolbar;\n        }\n    }\n\n    public interface WorldRefreshable {\n        void refresh();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManageUIUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.versions;\n\nimport javafx.stage.FileChooser;\nimport org.jackhuang.hmcl.game.World;\nimport org.jackhuang.hmcl.game.WorldLockedException;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.construct.InputDialogPane;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane;\nimport org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class WorldManageUIUtils {\n    private WorldManageUIUtils() {\n    }\n\n    public static void delete(World world, Runnable runnable) {\n        delete(world, runnable, null);\n    }\n\n    public static void delete(World world, Runnable runnable, FileChannel sessionLockChannel) {\n        Controllers.confirm(\n                i18n(\"button.remove.confirm\"),\n                i18n(\"world.delete\"),\n                () -> Task.runAsync(() -> closeSessionLockChannel(world, sessionLockChannel))\n                        .thenRunAsync(world::delete)\n                        .whenComplete(Schedulers.javafx(), (result, exception) -> {\n                            if (exception == null) {\n                                runnable.run();\n                            } else if (exception instanceof WorldLockedException) {\n                                Controllers.dialog(i18n(\"world.locked.failed\"), null, MessageDialogPane.MessageType.WARNING);\n                            } else {\n                                Controllers.dialog(i18n(\"world.delete.failed\", StringUtils.getStackTrace(exception)), null, MessageDialogPane.MessageType.WARNING);\n                            }\n                        }).start(),\n                null\n        );\n    }\n\n    public static void export(World world) {\n        export(world, null);\n    }\n\n    public static void export(World world, FileChannel sessionLockChannel) {\n        FileChooser fileChooser = new FileChooser();\n        fileChooser.setTitle(i18n(\"world.export.title\"));\n        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n(\"world\"), \"*.zip\"));\n        fileChooser.setInitialFileName(world.getWorldName() + \".zip\");\n        Path file = FileUtils.toPath(fileChooser.showSaveDialog(Controllers.getStage()));\n        if (file == null) {\n            return;\n        }\n\n        try {\n            closeSessionLockChannel(world, sessionLockChannel);\n        } catch (IOException e) {\n            return;\n        }\n\n        Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller -> new WorldExportPage(world, file, controller::onFinish)));\n    }\n\n    public static void copyWorld(World world, Runnable runnable) {\n        Path worldPath = world.getFile();\n        Controllers.dialog(new InputDialogPane(\n                i18n(\"world.duplicate.prompt\"),\n                \"\",\n                (result, handler) -> {\n                    if (StringUtils.isBlank(result)) {\n                        handler.reject(i18n(\"world.duplicate.failed.empty_name\"));\n                        return;\n                    }\n\n                    if (result.contains(\"/\") || result.contains(\"\\\\\") || !FileUtils.isNameValid(result)) {\n                        handler.reject(i18n(\"world.duplicate.failed.invalid_name\"));\n                        return;\n                    }\n\n                    Path targetDir = worldPath.resolveSibling(result);\n                    if (Files.exists(targetDir)) {\n                        handler.reject(i18n(\"world.duplicate.failed.already_exists\"));\n                        return;\n                    }\n\n                    Task.runAsync(Schedulers.io(), () -> world.copy(result))\n                            .thenAcceptAsync(Schedulers.javafx(), (Void) -> Controllers.showToast(i18n(\"world.duplicate.success.toast\")))\n                            .thenAcceptAsync(Schedulers.javafx(), (Void) -> {\n                                        if (runnable != null) {\n                                            runnable.run();\n                                        }\n                                    }\n                            ).whenComplete(Schedulers.javafx(), (throwable) -> {\n                                if (throwable == null) {\n                                    handler.resolve();\n                                } else {\n                                    handler.reject(i18n(\"world.duplicate.failed\"));\n                                    LOG.warning(\"Failed to duplicate world \" + world.getFile(), throwable);\n                                }\n                            })\n                            .start();\n                }));\n    }\n\n    public static void closeSessionLockChannel(World world, FileChannel sessionLockChannel) throws IOException {\n        if (sessionLockChannel != null) {\n            try {\n                sessionLockChannel.close();\n                LOG.info(\"Closed session lock channel of the world \" + world.getFileName());\n            } catch (IOException e) {\n                throw new IOException(\"Failed to close session lock channel of the world \" + world.getFile(), e);\n            }\n        }\n    }\n\n    public static FileChannel getSessionLockChannel(World world) {\n        try {\n            FileChannel lock = world.lock();\n            LOG.info(\"Acquired lock on world \" + world.getFileName());\n            return lock;\n        } catch (WorldLockedException ignored) {\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport javafx.scene.control.Label;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.jackhuang.hmcl.ui.construct.TaskListPane;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\nimport java.util.Queue;\n\npublic abstract class AbstractWizardDisplayer implements WizardDisplayer {\n    private final Queue<Object> cancelQueue;\n\n    public AbstractWizardDisplayer(Queue<Object> cancelQueue) {\n        this.cancelQueue = cancelQueue;\n    }\n\n    @Override\n    public void handleTask(SettingsMap settings, Task<?> task) {\n        TaskExecutor executor = task.withRunAsync(Schedulers.javafx(), this::navigateToSuccess).executor();\n        TaskListPane pane = new TaskListPane();\n        pane.setExecutor(executor);\n        navigateTo(pane, Navigation.NavigationDirection.FINISH);\n        cancelQueue.add(executor);\n        executor.start();\n    }\n\n    @Override\n    public void onCancel() {\n        while (!cancelQueue.isEmpty()) {\n            Object x = cancelQueue.poll();\n            if (x instanceof TaskExecutor) ((TaskExecutor) x).cancel();\n            else if (x instanceof Thread) ((Thread) x).interrupt();\n            else throw new IllegalStateException(\"Unrecognized cancel queue element: \" + x);\n        }\n    }\n\n    void navigateToSuccess() {\n        navigateTo(new Label(\"Successful\"), Navigation.NavigationDirection.FINISH);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Navigation.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport org.jackhuang.hmcl.ui.animation.ContainerAnimations;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\npublic interface Navigation {\n\n    void onStart();\n\n    void onNext();\n\n    void onPrev(boolean cleanUp);\n\n    boolean canPrev();\n\n    void onFinish();\n\n    void onEnd();\n\n    void onCancel();\n\n    SettingsMap getSettings();\n\n    enum NavigationDirection {\n        START(ContainerAnimations.NONE),\n        PREVIOUS(ContainerAnimations.BACKWARD),\n        NEXT(ContainerAnimations.FORWARD),\n        FINISH(ContainerAnimations.FORWARD);\n\n        private final ContainerAnimations animation;\n\n        NavigationDirection(ContainerAnimations animation) {\n            this.animation = animation;\n        }\n\n        public ContainerAnimations getAnimation() {\n            return animation;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Refreshable.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\n\npublic interface Refreshable {\n    void refresh();\n\n    default BooleanProperty refreshableProperty() {\n        return new SimpleBooleanProperty(false);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/SinglePageWizardProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\nimport java.util.function.Function;\n\npublic class SinglePageWizardProvider implements WizardProvider {\n\n    private final Function<WizardController, WizardSinglePage> provider;\n    private WizardSinglePage page;\n\n    public SinglePageWizardProvider(Function<WizardController, WizardSinglePage> provider) {\n        this.provider = provider;\n    }\n\n    @Override\n    public void start(SettingsMap settings) {\n    }\n\n    @Override\n    public Object finish(SettingsMap settings) {\n        return page.finish();\n    }\n\n    @Override\n    public Node createPage(WizardController controller, int step, SettingsMap settings) {\n        if (step != 0) throw new IllegalStateException(\"Step must be 0\");\n\n        return page = provider.apply(controller);\n    }\n\n    @Override\n    public boolean cancel() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Summary.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport com.jfoenix.controls.JFXListView;\nimport com.jfoenix.controls.JFXTextArea;\nimport javafx.scene.Node;\n\n/**\n * @author huangyuhui\n */\npublic final class Summary {\n    private final Node component;\n    private final Object result;\n\n    public Summary(String[] items, Object result) {\n        JFXListView<String> view = new JFXListView<>();\n        view.getItems().addAll(items);\n\n        this.component = view;\n        this.result = result;\n    }\n\n    public Summary(String text, Object result) {\n        JFXTextArea area = new JFXTextArea(text);\n        area.setEditable(false);\n\n        this.component = area;\n        this.result = result;\n    }\n\n    public Summary(Node component, Object result) {\n        this.component = component;\n        this.result = result;\n    }\n\n    /**\n     * The component that will display the summary information\n     */\n    public Node getComponent() {\n        return component;\n    }\n\n    /**\n     * The object that represents the actual result of whatever that Wizard\n     * that created this Summary object computes, or null.\n     */\n    public Object getResult() {\n        return result;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport javafx.beans.property.StringProperty;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.jackhuang.hmcl.task.TaskListener;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.construct.DialogCloseEvent;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;\nimport org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;\nimport org.jackhuang.hmcl.util.SettingsMap;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\n\nimport java.util.Queue;\nimport java.util.concurrent.CancellationException;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.runInFX;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplayer {\n\n    public TaskExecutorDialogWizardDisplayer(Queue<Object> cancelQueue) {\n        super(cancelQueue);\n    }\n\n    @Override\n    public void handleTask(SettingsMap settings, Task<?> task) {\n        TaskExecutorDialogPane pane = new TaskExecutorDialogPane(new TaskCancellationAction(it -> {\n            it.fireEvent(new DialogCloseEvent());\n            onEnd();\n        }));\n\n        pane.setTitle(i18n(\"message.doing\"));\n        if (settings.containsKey(\"title\")) {\n            Object title = settings.get(\"title\");\n            if (title instanceof StringProperty titleProperty)\n                pane.titleProperty().bind(titleProperty);\n            else if (title instanceof String titleMessage)\n                pane.setTitle(titleMessage);\n        }\n\n        runInFX(() -> {\n            TaskExecutor executor = task.executor(new TaskListener() {\n                @Override\n                public void onStop(boolean success, TaskExecutor executor) {\n                    runInFX(() -> {\n                        if (success) {\n                            if (settings.get(\"success_message\") instanceof String successMessage)\n                                Controllers.dialog(successMessage, null, MessageType.SUCCESS, () -> onEnd());\n                            else if (!settings.containsKey(\"forbid_success_message\"))\n                                Controllers.dialog(i18n(\"message.success\"), null, MessageType.SUCCESS, () -> onEnd());\n                        } else {\n                            if (executor.getException() == null)\n                                return;\n\n                            if (executor.getException() instanceof CancellationException) {\n                                onEnd();\n                                return;\n                            }\n\n                            String appendix = StringUtils.getStackTrace(executor.getException());\n                            if (settings.get(WizardProvider.FailureCallback.KEY) != null)\n                                settings.get(WizardProvider.FailureCallback.KEY).onFail(settings, executor.getException(), () -> onEnd());\n                            else if (settings.get(\"failure_message\") instanceof String failureMessage)\n                                Controllers.dialog(appendix, failureMessage, MessageType.ERROR, () -> onEnd());\n                            else if (!settings.containsKey(\"forbid_failure_message\"))\n                                Controllers.dialog(appendix, i18n(\"wizard.failed\"), MessageType.ERROR, () -> onEnd());\n                        }\n\n                    });\n                }\n            });\n            pane.setExecutor(executor);\n            Controllers.dialog(pane);\n            executor.start();\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardController.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class WizardController implements Navigation {\n    private final WizardDisplayer displayer;\n    private WizardProvider provider = null;\n    private final SettingsMap settings = new SettingsMap();\n    private final Stack<Node> pages = new Stack<>();\n    private boolean stopped = false;\n\n    public WizardController(WizardDisplayer displayer) {\n        this.displayer = displayer;\n    }\n\n    @Override\n    public SettingsMap getSettings() {\n        return settings;\n    }\n\n    public WizardDisplayer getDisplayer() {\n        return displayer;\n    }\n\n    public void setProvider(WizardProvider provider) {\n        this.provider = provider;\n    }\n\n    public List<Node> getPages() {\n        return Collections.unmodifiableList(pages);\n    }\n\n    @Override\n    public void onStart() {\n        Objects.requireNonNull(provider);\n\n        settings.clear();\n        provider.start(settings);\n\n        pages.clear();\n        Node page = navigatingTo(0);\n        pages.push(page);\n\n        if (stopped) { // navigatingTo may stop this wizard.\n            return;\n        }\n\n        if (page instanceof WizardPage)\n            ((WizardPage) page).onNavigate(settings);\n\n        displayer.onStart();\n\n        LOG.info(\"Navigating to \" + page + \", pages: \" + pages);\n        displayer.navigateTo(page, NavigationDirection.START);\n    }\n\n    @Override\n    public void onNext() {\n        onNext(navigatingTo(pages.size()));\n    }\n\n    public void onNext(Node page) {\n        onNext(page, NavigationDirection.NEXT);\n    }\n\n    public void onNext(Node page, NavigationDirection direction) {\n        pages.push(page);\n\n        if (stopped) { // navigatingTo may stop this wizard.\n            return;\n        }\n\n        if (page instanceof WizardPage)\n            ((WizardPage) page).onNavigate(settings);\n\n        LOG.info(\"Navigating to \" + page + \", pages: \" + pages);\n        displayer.navigateTo(page, direction);\n    }\n\n    @Override\n    public void onPrev(boolean cleanUp) {\n        onPrev(cleanUp, NavigationDirection.PREVIOUS);\n    }\n\n    public void onPrev(boolean cleanUp, NavigationDirection direction) {\n        if (!canPrev()) {\n            if (provider.cancelIfCannotGoBack()) {\n                onCancel();\n                return;\n            } else {\n                throw new IllegalStateException(\"Cannot go backward since this is the back page. Pages: \" + pages);\n            }\n        }\n\n        Node page = pages.pop();\n        if (cleanUp && page instanceof WizardPage)\n            ((WizardPage) page).cleanup(settings);\n\n        Node prevPage = pages.peek();\n        if (prevPage instanceof WizardPage)\n            ((WizardPage) prevPage).onNavigate(settings);\n\n        LOG.info(\"Navigating to \" + prevPage + \", pages: \" + pages);\n        displayer.navigateTo(prevPage, direction);\n    }\n\n    @Override\n    public boolean canPrev() {\n        return pages.size() > 1;\n    }\n\n    @Override\n    public void onFinish() {\n        Object result = provider.finish(settings);\n        if (result instanceof Summary) displayer.navigateTo(((Summary) result).getComponent(), NavigationDirection.NEXT);\n        else if (result instanceof Task<?>) displayer.handleTask(settings, ((Task<?>) result));\n        else if (result != null) throw new IllegalStateException(\"Unrecognized wizard result: \" + result);\n    }\n\n    @Override\n    public void onEnd() {\n        stopped = true;\n        settings.clear();\n        pages.clear();\n        displayer.onEnd();\n    }\n\n    @Override\n    public void onCancel() {\n        displayer.onCancel();\n        onEnd();\n    }\n\n    protected Node navigatingTo(int step) {\n        return provider.createPage(this, step, settings);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\npublic interface WizardDisplayer {\n    default void onStart() {\n    }\n\n    default void onEnd() {\n    }\n\n    default void onCancel() {\n    }\n\n    void navigateTo(Node page, Navigation.NavigationDirection nav);\n\n    void handleTask(SettingsMap settings, Task<?> task);\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport org.jackhuang.hmcl.util.SettingsMap;\n\npublic interface WizardPage {\n    default void onNavigate(SettingsMap settings) {\n    }\n\n    default void cleanup(SettingsMap settings) {\n    }\n\n    String getTitle();\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport javafx.scene.Node;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\npublic interface WizardProvider {\n    void start(SettingsMap settings);\n\n    Object finish(SettingsMap settings);\n\n    Node createPage(WizardController controller, int step, SettingsMap settings);\n\n    boolean cancel();\n\n    default boolean cancelIfCannotGoBack() {\n        return false;\n    }\n\n    interface FailureCallback {\n        SettingsMap.Key<FailureCallback> KEY = new SettingsMap.Key<>(\"failure_callback\");\n\n        void onFail(SettingsMap settings, Exception exception, Runnable next);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardSinglePage.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.wizard;\n\nimport javafx.scene.control.Control;\nimport org.jackhuang.hmcl.util.SettingsMap;\n\npublic abstract class WizardSinglePage extends Control implements WizardPage {\n    protected final Runnable onFinish;\n\n    protected WizardSinglePage(Runnable onFinish) {\n        this.onFinish = onFinish;\n    }\n\n    protected abstract Object finish();\n\n    @Override\n    public void cleanup(SettingsMap settings) {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/upgrade/ExecutableHeaderHelper.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.upgrade;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.Buffer;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.channels.FileChannel.MapMode;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\nimport org.jackhuang.hmcl.util.io.IOUtils;\n\nimport static java.nio.file.StandardOpenOption.*;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\n/**\n * Helper class for adding/removing executable header from HMCL file.\n *\n * @author yushijinhun\n */\nfinal class ExecutableHeaderHelper {\n    private ExecutableHeaderHelper() {}\n\n    private static Map<String, String> suffix2header = mapOf(\n            pair(\"exe\", \"assets/HMCLauncher.exe\"),\n            pair(\"sh\", \"assets/HMCLauncher.sh\")\n    );\n\n    private static Optional<String> getSuffix(Path file) {\n        String filename = file.getFileName().toString();\n        int idxDot = filename.lastIndexOf('.');\n        if (idxDot < 0) {\n            return Optional.empty();\n        } else {\n            return Optional.of(filename.substring(idxDot + 1));\n        }\n    }\n\n    private static Optional<byte[]> readHeader(ZipFile zip, String suffix) throws IOException {\n        String location = suffix2header.get(suffix);\n        if (location != null) {\n            ZipEntry entry = zip.getEntry(location);\n            if (entry != null && !entry.isDirectory()) {\n                try (InputStream in = zip.getInputStream(entry)) {\n                    return Optional.of(IOUtils.readFully(in));\n                }\n            }\n        }\n        return Optional.empty();\n    }\n\n    private static int detectHeaderLength(ZipFile zip, FileChannel channel) throws IOException {\n        ByteBuffer buf = channel.map(MapMode.READ_ONLY, 0, channel.size());\n        suffixLoop: for (String suffix : suffix2header.keySet()) {\n            Optional<byte[]> header = readHeader(zip, suffix);\n            if (header.isPresent()) {\n                ((Buffer) buf).rewind();\n                for (byte b : header.get()) {\n                    if (!buf.hasRemaining() || b != buf.get()) {\n                        continue suffixLoop;\n                    }\n                }\n                return header.get().length;\n            }\n        }\n        return 0;\n    }\n\n    /**\n     * Copies the executable and removes its header.\n     */\n    public static void copyWithoutHeader(Path from, Path to) throws IOException {\n        try (\n                FileChannel in = FileChannel.open(from, READ);\n                FileChannel out = FileChannel.open(to, CREATE, WRITE, TRUNCATE_EXISTING);\n                ZipFile zip = new ZipFile(from.toFile())\n        ) {\n            in.transferTo(detectHeaderLength(zip, in), Long.MAX_VALUE, out);\n        }\n    }\n\n    /**\n     * Copies the executable and appends the header according to the suffix.\n     */\n    public static void copyWithHeader(Path from, Path to) throws IOException {\n        try (\n                FileChannel in = FileChannel.open(from, READ);\n                FileChannel out = FileChannel.open(to, CREATE, WRITE, TRUNCATE_EXISTING);\n                ZipFile zip = new ZipFile(from.toFile())\n        ) {\n            Optional<String> suffix = getSuffix(to);\n            if (suffix.isPresent()) {\n                Optional<byte[]> header = readHeader(zip, suffix.get());\n                if (header.isPresent()) {\n                    out.write(ByteBuffer.wrap(header.get()));\n                }\n            }\n\n            in.transferTo(detectHeaderLength(zip, in), Long.MAX_VALUE, out);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.upgrade;\n\nimport org.jackhuang.hmcl.task.FileDownloadTask;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nfinal class HMCLDownloadTask extends FileDownloadTask {\n\n    private final RemoteVersion.Type archiveFormat;\n\n    public HMCLDownloadTask(RemoteVersion version, Path target) {\n        super(version.getUrl(), target, version.getIntegrityCheck());\n        archiveFormat = version.getType();\n    }\n\n    @Override\n    public void execute() throws Exception {\n        super.execute();\n\n        try {\n            Path target = getPath();\n            switch (archiveFormat) {\n                case JAR:\n                    break;\n                default:\n                    throw new IllegalArgumentException(\"Unknown format: \" + archiveFormat);\n            }\n        } catch (Throwable e) {\n            try {\n                Files.deleteIfExists(getPath());\n            } catch (Throwable e2) {\n                e.addSuppressed(e2);\n            }\n            throw e;\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.upgrade;\n\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jackhuang.hmcl.util.io.JarUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Path;\nimport java.security.*;\nimport java.security.spec.X509EncodedKeySpec;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.TreeMap;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * A class that checks the integrity of HMCL.\n *\n * @author yushijinhun\n */\npublic final class IntegrityChecker {\n    private IntegrityChecker() {}\n\n    public static final boolean DISABLE_SELF_INTEGRITY_CHECK = \"true\".equals(System.getProperty(\"hmcl.self_integrity_check.disable\"));\n\n    private static final String SIGNATURE_FILE = \"META-INF/hmcl_signature\";\n    private static final String PUBLIC_KEY_FILE = \"assets/hmcl_signature_publickey.der\";\n\n    private static PublicKey getPublicKey() throws IOException {\n        try (InputStream in = IntegrityChecker.class.getResourceAsStream(\"/\" + PUBLIC_KEY_FILE)) {\n            if (in == null) {\n                throw new IOException(\"Public key not found\");\n            }\n            return KeyFactory.getInstance(\"RSA\").generatePublic(new X509EncodedKeySpec(IOUtils.readFully(in)));\n        } catch (GeneralSecurityException e) {\n            throw new IOException(\"Failed to load public key\", e);\n        }\n    }\n\n    static void verifyJar(Path jarPath) throws IOException {\n        PublicKey publickey = getPublicKey();\n        MessageDigest md = DigestUtils.getDigest(\"SHA-512\");\n\n        byte[] signature = null;\n        Map<String, byte[]> fileFingerprints = new TreeMap<>();\n        try (ZipFile zip = new ZipFile(jarPath.toFile())) {\n            for (ZipEntry entry : Lang.toIterable(zip.entries())) {\n                String filename = entry.getName();\n                try (InputStream in = zip.getInputStream(entry)) {\n                    if (in == null) {\n                        throw new IOException(\"entry is null\");\n                    }\n\n                    if (SIGNATURE_FILE.equals(filename)) {\n                        signature = IOUtils.readFully(in);\n                    } else {\n                        md.reset();\n                        fileFingerprints.put(filename, DigestUtils.digest(md, in));\n                    }\n                }\n            }\n        }\n\n        if (signature == null) {\n            throw new IOException(\"Signature is missing\");\n        }\n\n        try {\n            Signature verifier = Signature.getInstance(\"SHA512withRSA\");\n            verifier.initVerify(publickey);\n            for (Entry<String, byte[]> entry : fileFingerprints.entrySet()) {\n                md.reset();\n                verifier.update(md.digest(entry.getKey().getBytes(UTF_8)));\n                verifier.update(entry.getValue());\n            }\n            if (!verifier.verify(signature)) {\n                throw new IOException(\"Invalid signature: \" + jarPath);\n            }\n        } catch (GeneralSecurityException e) {\n            throw new IOException(\"Failed to verify signature\", e);\n        }\n    }\n\n    private static volatile Boolean selfVerified = null;\n\n    /**\n     * Checks whether the current application is verified.\n     * This method is blocking.\n     */\n    public static boolean isSelfVerified() {\n        if (selfVerified != null) {\n            return selfVerified;\n        }\n\n        synchronized (IntegrityChecker.class) {\n            if (selfVerified != null) {\n                return selfVerified;\n            }\n\n            try {\n                Path jarPath = JarUtils.thisJarPath();\n                if (jarPath == null) {\n                    throw new IOException(\"Failed to find current HMCL location\");\n                }\n\n                verifyJar(jarPath);\n                LOG.info(\"Successfully verified current JAR\");\n                selfVerified = true;\n            } catch (IOException e) {\n                LOG.warning(\"Failed to verify myself, is the JAR corrupt?\", e);\n                selfVerified = false;\n            }\n\n            return selfVerified;\n        }\n    }\n\n    public static boolean isOfficial() {\n        return isSelfVerified() || (Metadata.GITHUB_SHA != null && Metadata.BUILD_CHANNEL.equals(\"nightly\"));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.upgrade;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.io.IOException;\nimport java.util.Optional;\n\npublic final class RemoteVersion {\n\n    public static RemoteVersion fetch(UpdateChannel channel, boolean preview, String url) throws IOException {\n        try {\n            JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(url), JsonObject.class);\n            String version = Optional.ofNullable(response.get(\"version\")).map(JsonElement::getAsString).orElseThrow(() -> new IOException(\"version is missing\"));\n            String jarUrl = Optional.ofNullable(response.get(\"jar\")).map(JsonElement::getAsString).orElse(null);\n            String jarHash = Optional.ofNullable(response.get(\"jarsha1\")).map(JsonElement::getAsString).orElse(null);\n            boolean force = Optional.ofNullable(response.get(\"force\")).map(JsonElement::getAsBoolean).orElse(false);\n            if (jarUrl != null && jarHash != null) {\n                return new RemoteVersion(channel, version, jarUrl, Type.JAR, new IntegrityCheck(\"SHA-1\", jarHash), preview, force);\n            } else {\n                throw new IOException(\"No download url is available\");\n            }\n        } catch (JsonParseException e) {\n            throw new IOException(\"Malformed response\", e);\n        }\n    }\n\n    private final UpdateChannel channel;\n    private final String version;\n    private final String url;\n    private final Type type;\n    private final IntegrityCheck integrityCheck;\n    private final boolean preview;\n    private final boolean force;\n\n    public RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck, boolean preview, boolean force) {\n        this.channel = channel;\n        this.version = version;\n        this.url = url;\n        this.type = type;\n        this.integrityCheck = integrityCheck;\n        this.preview = preview;\n        this.force = force;\n    }\n\n    public UpdateChannel getChannel() {\n        return channel;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public Type getType() {\n        return type;\n    }\n\n    public IntegrityCheck getIntegrityCheck() {\n        return integrityCheck;\n    }\n\n    public boolean isPreview() {\n        return preview;\n    }\n\n    public boolean isForce() {\n        return force;\n    }\n\n    @Override\n    public String toString() {\n        return \"[\" + version + \" from \" + url + \"]\";\n    }\n\n    public enum Type {\n        JAR\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChannel.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.upgrade;\n\nimport org.jackhuang.hmcl.Metadata;\n\npublic enum UpdateChannel {\n    STABLE(\"stable\"),\n    DEVELOPMENT(\"dev\"),\n    NIGHTLY(\"nightly\");\n\n    public final String channelName;\n\n    UpdateChannel(String channelName) {\n        this.channelName = channelName;\n    }\n\n    public static UpdateChannel getChannel() {\n        if (Metadata.isDev()) {\n            return DEVELOPMENT;\n        } else if (Metadata.isNightly()) {\n            return NIGHTLY;\n        } else {\n            return STABLE;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.upgrade;\n\nimport javafx.application.Platform;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.property.*;\nimport javafx.beans.value.ObservableBooleanValue;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.io.IOException;\nimport java.util.LinkedHashMap;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\nimport static org.jackhuang.hmcl.util.Lang.*;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class UpdateChecker {\n    private UpdateChecker() {\n    }\n\n    private static final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();\n    private static final BooleanBinding outdated = Bindings.createBooleanBinding(\n            () -> {\n                RemoteVersion latest = latestVersion.get();\n                if (latest == null || isDevelopmentVersion(Metadata.VERSION)) {\n                    return false;\n                } else if (latest.isForce()\n                        || Metadata.isNightly()\n                        || latest.getChannel() == UpdateChannel.NIGHTLY\n                        || latest.getChannel() != UpdateChannel.getChannel()) {\n                    return !latest.getVersion().equals(Metadata.VERSION);\n                } else {\n                    return VersionNumber.compare(Metadata.VERSION, latest.getVersion()) < 0;\n                }\n            },\n            latestVersion);\n    private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);\n\n    public static void init() {\n        requestCheckUpdate(UpdateChannel.getChannel(), config().isAcceptPreviewUpdate());\n    }\n\n    public static RemoteVersion getLatestVersion() {\n        return latestVersion.get();\n    }\n\n    public static ReadOnlyObjectProperty<RemoteVersion> latestVersionProperty() {\n        return latestVersion;\n    }\n\n    public static boolean isOutdated() {\n        return outdated.get();\n    }\n\n    public static ObservableBooleanValue outdatedProperty() {\n        return outdated;\n    }\n\n    public static boolean isCheckingUpdate() {\n        return checkingUpdate.get();\n    }\n\n    public static ReadOnlyBooleanProperty checkingUpdateProperty() {\n        return checkingUpdate.getReadOnlyProperty();\n    }\n\n    private static RemoteVersion checkUpdate(UpdateChannel channel, boolean preview) throws IOException {\n        if (!IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK && !IntegrityChecker.isSelfVerified()) {\n            throw new IOException(\"Self verification failed\");\n        }\n\n        var query = new LinkedHashMap<String, String>();\n        query.put(\"version\", Metadata.VERSION);\n        query.put(\"channel\", preview ? channel.channelName + \"-preview\" : channel.channelName);\n\n        String url = NetworkUtils.withQuery(Metadata.HMCL_UPDATE_URL, query);\n        return RemoteVersion.fetch(channel, preview, url);\n    }\n\n    private static boolean isDevelopmentVersion(String version) {\n        return version.contains(\"@\") || // eg. @develop@\n                version.contains(\"SNAPSHOT\"); // eg. 3.5.SNAPSHOT\n    }\n\n    public static void requestCheckUpdate(UpdateChannel channel, boolean preview) {\n        Platform.runLater(() -> {\n            if (isCheckingUpdate())\n                return;\n            checkingUpdate.set(true);\n\n            thread(() -> {\n                RemoteVersion result = null;\n                try {\n                    result = checkUpdate(channel, preview);\n                    LOG.info(\"Latest version (\" + channel + \", preview=\" + preview + \") is \" + result);\n                } catch (Throwable e) {\n                    LOG.warning(\"Failed to check for update\", e);\n                }\n\n                RemoteVersion finalResult = result;\n                Platform.runLater(() -> {\n                    checkingUpdate.set(false);\n                    if (finalResult != null) {\n                        latestVersion.set(finalResult);\n                    }\n                });\n            }, \"Update Checker\", true);\n        });\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.upgrade;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonParseException;\nimport javafx.application.Platform;\n\nimport org.jackhuang.hmcl.EntryPoint;\nimport org.jackhuang.hmcl.Main;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.jackhuang.hmcl.ui.Controllers;\nimport org.jackhuang.hmcl.ui.UpgradeDialog;\nimport org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.SwingUtils;\nimport org.jackhuang.hmcl.util.TaskCancellationAction;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.io.IOException;\nimport java.lang.management.ManagementFactory;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.concurrent.CancellationException;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.ui.FXUtils.checkFxUserThread;\nimport static org.jackhuang.hmcl.util.Lang.thread;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\npublic final class UpdateHandler {\n    private UpdateHandler() {\n    }\n\n    /**\n     * @return whether to exit\n     */\n    public static boolean processArguments(String[] args) {\n        breakForceUpdateFeature();\n\n        if (isNestedApplication()) {\n            // updated from old versions\n            try {\n                performMigration();\n            } catch (IOException e) {\n                LOG.warning(\"Failed to perform migration\", e);\n                SwingUtils.showErrorDialog(i18n(\"fatal.apply_update_failure\", Metadata.PUBLISH_URL) + \"\\n\" + StringUtils.getStackTrace(e));\n            }\n            return true;\n        }\n\n        if (args.length == 2 && args[0].equals(\"--apply-to\")) {\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && !OperatingSystem.isWindows7OrLater()) {\n                SwingUtils.showErrorDialog(i18n(\"fatal.apply_update_need_win7\", Metadata.PUBLISH_URL));\n                return true;\n            }\n\n            try {\n                applyUpdate(Paths.get(args[1]));\n            } catch (IOException e) {\n                LOG.warning(\"Failed to apply update\", e);\n                SwingUtils.showErrorDialog(i18n(\"fatal.apply_update_failure\", Metadata.PUBLISH_URL) + \"\\n\" + StringUtils.getStackTrace(e));\n            }\n            return true;\n        }\n\n        if (isFirstLaunchAfterUpgrade()) {\n            SwingUtils.showInfoDialog(i18n(\"fatal.migration_requires_manual_reboot\"));\n            return true;\n        }\n\n        return false;\n    }\n\n    public static void updateFrom(RemoteVersion version) {\n        checkFxUserThread();\n\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && !OperatingSystem.isWindows7OrLater()) {\n            Controllers.dialog(i18n(\"fatal.apply_update_need_win7\", Metadata.PUBLISH_URL), i18n(\"message.error\"), MessageType.ERROR);\n            return;\n        }\n\n        Controllers.dialog(new UpgradeDialog(version, () -> {\n            Path downloaded;\n            try {\n                downloaded = Files.createTempFile(\"hmcl-update-\", \".jar\");\n            } catch (IOException e) {\n                LOG.warning(\"Failed to create temp file\", e);\n                return;\n            }\n\n            Task<?> task = new HMCLDownloadTask(version, downloaded);\n\n            TaskExecutor executor = task.executor();\n            Controllers.taskDialog(executor, i18n(\"message.downloading\"), TaskCancellationAction.NORMAL);\n            thread(() -> {\n                boolean success = executor.test();\n\n                if (success) {\n                    try {\n                        if (!IntegrityChecker.isSelfVerified() && !IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK) {\n                            throw new IOException(\"Current JAR is not verified\");\n                        }\n\n                        requestUpdate(downloaded, getCurrentLocation());\n                        EntryPoint.exit(0);\n                    } catch (IOException e) {\n                        LOG.warning(\"Failed to update to \" + version, e);\n                        Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n(\"update.failed\"), MessageType.ERROR));\n                    }\n\n                } else {\n                    Exception e = executor.getException();\n                    LOG.warning(\"Failed to update to \" + version, e);\n                    if (e instanceof CancellationException) {\n                        Platform.runLater(() -> Controllers.showToast(i18n(\"message.cancelled\")));\n                    } else {\n                        Platform.runLater(() -> Controllers.dialog(e.toString(), i18n(\"update.failed\"), MessageType.ERROR));\n                    }\n                }\n            });\n        }));\n    }\n\n    private static void applyUpdate(Path target) throws IOException {\n        LOG.info(\"Applying update to \" + target);\n\n        Path self = getCurrentLocation();\n        if (!IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK && !IntegrityChecker.isSelfVerified()) {\n            throw new IOException(\"Self verification failed\");\n        }\n        ExecutableHeaderHelper.copyWithHeader(self, target);\n\n        Optional<Path> newFilename = tryRename(target, Metadata.VERSION);\n        if (newFilename.isPresent()) {\n            LOG.info(\"Move \" + target + \" to \" + newFilename.get());\n            try {\n                Files.move(target, newFilename.get());\n                target = newFilename.get();\n            } catch (IOException e) {\n                LOG.warning(\"Failed to move target\", e);\n            }\n        }\n\n        startJava(target);\n    }\n\n    private static void requestUpdate(Path updateTo, Path self) throws IOException {\n        if (!IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK) {\n            IntegrityChecker.verifyJar(updateTo);\n        }\n        startJava(updateTo, \"--apply-to\", self.toString());\n    }\n\n    public static void startJava(Path jar, String... appArgs) throws IOException {\n        List<String> commandline = new ArrayList<>();\n        commandline.add(JavaRuntime.getDefault().getBinary().toString());\n\n        try {\n            for (String inputArgument : ManagementFactory.getRuntimeMXBean().getInputArguments()) {\n                if (inputArgument.startsWith(\"-D\") || inputArgument.startsWith(\"-X\")) {\n                    commandline.add(inputArgument);\n                }\n            }\n        } catch (Throwable ignored) {\n            // ManagementFactory not available\n            for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {\n                if (entry.getKey() instanceof String key && key.startsWith(\"hmcl.\")) {\n                    commandline.add(\"-D\" + key + \"=\" + entry.getValue());\n                }\n            }\n        }\n\n        commandline.add(\"-jar\");\n        commandline.add(jar.toAbsolutePath().toString());\n        commandline.addAll(Arrays.asList(appArgs));\n        LOG.info(\"Starting process: \" + commandline);\n        new ProcessBuilder(commandline)\n                .directory(Paths.get(\"\").toAbsolutePath().toFile())\n                .inheritIO()\n                .start();\n    }\n\n    private static Optional<Path> tryRename(Path path, String newVersion) {\n        String filename = path.getFileName().toString();\n        Matcher matcher = Pattern.compile(\"^(?<prefix>[hH][mM][cC][lL][.-])(?<version>\\\\d+(?:\\\\.\\\\d+)*)(?<suffix>\\\\.[^.]+)$\").matcher(filename);\n        if (matcher.find()) {\n            String newFilename = matcher.group(\"prefix\") + newVersion + matcher.group(\"suffix\");\n            if (!newFilename.equals(filename)) {\n                return Optional.of(path.resolveSibling(newFilename));\n            }\n        }\n        return Optional.empty();\n    }\n\n    private static Path getCurrentLocation() throws IOException {\n        Path path = JarUtils.thisJarPath();\n        if (path == null) {\n            throw new IOException(\"Failed to find current HMCL location\");\n        }\n        return path;\n    }\n\n    // ==== support for old versions ===\n    private static void performMigration() throws IOException {\n        LOG.info(\"Migrating from old versions\");\n\n        Path location = getParentApplicationLocation()\n                .orElseThrow(() -> new IOException(\"Failed to get parent application location\"));\n\n        requestUpdate(getCurrentLocation(), location);\n    }\n\n    /**\n     * This method must be called from the main thread.\n     */\n    private static boolean isNestedApplication() {\n        StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();\n        for (int i = 0; i < stacktrace.length; i++) {\n            StackTraceElement element = stacktrace[i];\n            if (Main.class.getName().equals(element.getClassName()) && \"main\".equals(element.getMethodName())) {\n                // we've reached the main method\n                return i + 1 != stacktrace.length;\n            }\n        }\n        return false;\n    }\n\n    private static Optional<Path> getParentApplicationLocation() {\n        String command = System.getProperty(\"sun.java.command\");\n        if (command != null) {\n            Path path = Paths.get(command);\n            if (Files.isRegularFile(path)) {\n                return Optional.of(path.toAbsolutePath());\n            }\n        }\n        return Optional.empty();\n    }\n\n    private static boolean isFirstLaunchAfterUpgrade() {\n        Path currentPath = JarUtils.thisJarPath();\n        if (currentPath != null) {\n            Path updated = Metadata.HMCL_GLOBAL_DIRECTORY.resolve(\"HMCL-\" + Metadata.VERSION + \".jar\");\n            if (currentPath.equals(updated.toAbsolutePath())) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private static void breakForceUpdateFeature() {\n        Path hmclVersionJson = Metadata.HMCL_GLOBAL_DIRECTORY.resolve(\"hmclver.json\");\n        if (Files.isRegularFile(hmclVersionJson)) {\n            try {\n                Map<?, ?> content = new Gson().fromJson(Files.readString(hmclVersionJson), Map.class);\n                Object ver = content.get(\"ver\");\n                if (ver instanceof String && ((String) ver).startsWith(\"3.\")) {\n                    Files.delete(hmclVersionJson);\n                    LOG.info(\"Successfully broke the force update feature\");\n                }\n            } catch (IOException e) {\n                LOG.warning(\"Failed to break the force update feature\", e);\n            } catch (JsonParseException e) {\n                hmclVersionJson.toFile().delete();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/AggregatedObservableList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Function;\n\npublic final class AggregatedObservableList<T> {\n\n    private final List<ObservableList<? extends T>> lists = new ArrayList<>();\n    private final List<Integer> sizes = new ArrayList<>();\n    private final List<InternalListModificationListener> listeners = new ArrayList<>();\n    private final ObservableList<T> aggregatedList = FXCollections.observableArrayList();\n\n    public AggregatedObservableList() {\n\n    }\n\n    /**\n     * The Aggregated Observable List. This list is unmodifiable, because sorting this list would mess up the entire bookkeeping we do here.\n     *\n     * @return an unmodifiable view of the aggregatedList\n     */\n    public ObservableList<T> getAggregatedList() {\n        return aggregatedList;\n    }\n\n    public void appendList(@NotNull ObservableList<? extends T> list) {\n        assert !lists.contains(list) : \"List is already contained: \" + list;\n        lists.add(list);\n        final InternalListModificationListener listener = new InternalListModificationListener(list);\n        list.addListener(listener);\n        //System.out.println(\"list = \" + list + \" puttingInMap=\" + list.hashCode());\n        sizes.add(list.size());\n        aggregatedList.addAll(list);\n        listeners.add(listener);\n        assert lists.size() == sizes.size() && lists.size() == listeners.size() :\n                \"lists.size=\" + lists.size() + \" not equal to sizes.size=\" + sizes.size() + \" or not equal to listeners.size=\" + listeners.size();\n    }\n\n    public void prependList(@NotNull ObservableList<? extends T> list) {\n        assert !lists.contains(list) : \"List is already contained: \" + list;\n        lists.add(0, list);\n        final InternalListModificationListener listener = new InternalListModificationListener(list);\n        list.addListener(listener);\n        //System.out.println(\"list = \" + list + \" puttingInMap=\" + list.hashCode());\n        sizes.add(0, list.size());\n        aggregatedList.addAll(0, list);\n        listeners.add(0, listener);\n        assert lists.size() == sizes.size() && lists.size() == listeners.size() :\n                \"lists.size=\" + lists.size() + \" not equal to sizes.size=\" + sizes.size() + \" or not equal to listeners.size=\" + listeners.size();\n    }\n\n    public void removeList(@NotNull ObservableList<? extends T> list) {\n        assert lists.size() == sizes.size() && lists.size() == listeners.size() :\n                \"lists.size=\" + lists.size() + \" not equal to sizes.size=\" + sizes.size() + \" or not equal to listeners.size=\" + listeners.size();\n        final int index = lists.indexOf(list);\n        if (index < 0) {\n            throw new IllegalArgumentException(\"Cannot remove a list that is not contained: \" + list + \" lists=\" + lists);\n        }\n        final int startIndex = getStartIndex(list);\n        final int endIndex = getEndIndex(list, startIndex);\n        // we want to find the start index of this list inside the aggregated List. End index will be start + size - 1.\n        lists.remove(list);\n        sizes.remove(index);\n        final InternalListModificationListener listener = listeners.remove(index);\n        list.removeListener(listener);\n        aggregatedList.remove(startIndex, endIndex + 1); // end + 1 because end is exclusive\n        assert lists.size() == sizes.size() && lists.size() == listeners.size() :\n                \"lists.size=\" + lists.size() + \" not equal to sizes.size=\" + sizes.size() + \" or not equal to listeners.size=\" + listeners.size();\n    }\n\n    /**\n     * Get the start index of this list inside the aggregated List.\n     * This is a private function. we can safely assume, that the list is in the map.\n     *\n     * @param list the list in question\n     * @return the start index of this list in the aggregated List\n     */\n    private int getStartIndex(@NotNull ObservableList<? extends T> list) {\n        int startIndex = 0;\n        //System.out.println(\"=== searching startIndex of \" + list);\n        assert lists.size() == sizes.size() : \"lists.size=\" + lists.size() + \" not equal to sizes.size=\" + sizes.size();\n        final int listIndex = lists.indexOf(list);\n        for (int i = 0; i < listIndex; i++) {\n            final Integer size = sizes.get(i);\n            startIndex += size;\n            //System.out.println(\" startIndex = \" + startIndex + \" added=\" + size);\n        }\n        //System.out.println(\"startIndex = \" + startIndex);\n        return startIndex;\n    }\n\n    /**\n     * Get the end index of this list inside the aggregated List.\n     * This is a private function. we can safely assume, that the list is in the map.\n     *\n     * @param list       the list in question\n     * @param startIndex the start of the list (retrieve with {@link #getStartIndex(ObservableList)}\n     * @return the end index of this list in the aggregated List\n     */\n    private int getEndIndex(@NotNull ObservableList<? extends T> list, int startIndex) {\n        assert lists.size() == sizes.size() : \"lists.size=\" + lists.size() + \" not equal to sizes.size=\" + sizes.size();\n        final int index = lists.indexOf(list);\n        return startIndex + sizes.get(index) - 1;\n    }\n\n    private final class InternalListModificationListener implements ListChangeListener<T> {\n\n        @NotNull\n        private final ObservableList<? extends T> list;\n\n        public InternalListModificationListener(@NotNull ObservableList<? extends T> list) {\n            this.list = list;\n        }\n\n        /**\n         * Called after a change has been made to an ObservableList.\n         *\n         * @param change an object representing the change that was done\n         * @see Change\n         */\n        @Override\n        public void onChanged(Change<? extends T> change) {\n            final ObservableList<? extends T> changedList = change.getList();\n            final int startIndex = getStartIndex(list);\n            final int index = lists.indexOf(list);\n            final int newSize = changedList.size();\n            //System.out.println(\"onChanged for list=\" + list + \" aggregate=\" + aggregatedList);\n            while (change.next()) {\n                final int from = change.getFrom();\n                final int to = change.getTo();\n                //System.out.println(\" startIndex=\" + startIndex + \" from=\" + from + \" to=\" + to);\n                if (change.wasPermutated()) {\n                    final ArrayList<T> copy = new ArrayList<>(aggregatedList.subList(startIndex + from, startIndex + to));\n                    //System.out.println(\"  permutating sublist=\" + copy);\n                    for (int oldIndex = from; oldIndex < to; oldIndex++) {\n                        int newIndex = change.getPermutation(oldIndex);\n                        copy.set(newIndex - from, aggregatedList.get(startIndex + oldIndex));\n                    }\n                    //System.out.println(\"  permutating done sublist=\" + copy);\n                    aggregatedList.subList(startIndex + from, startIndex + to).clear();\n                    aggregatedList.addAll(startIndex + from, copy);\n                } else if (change.wasUpdated()) {\n                    // do nothing\n                } else {\n                    if (change.wasRemoved()) {\n                        List<? extends T> removed = change.getRemoved();\n                        //System.out.println(\"  removed= \" + removed);\n                        // IMPORTANT! FROM == TO when removing items.\n                        aggregatedList.remove(startIndex + from, startIndex + from + removed.size());\n                    }\n                    if (change.wasAdded()) {\n                        List<? extends T> added = change.getAddedSubList();\n                        //System.out.println(\"  added= \" + added);\n                        //add those elements to your data\n                        aggregatedList.addAll(startIndex + from, added);\n                    }\n                }\n            }\n            // update the size of the list in the map\n            //System.out.println(\"list = \" + list + \" puttingInMap=\" + list.hashCode());\n            sizes.set(index, newSize);\n            //System.out.println(\"listSizesMap = \" + sizes);\n        }\n\n    }\n\n    public String dump() {\n        return dump(x -> x);\n    }\n\n    public String dump(Function<T, Object> function) {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"[\");\n        aggregatedList.forEach(el -> sb.append(function.apply(el)).append(\",\"));\n        final int length = sb.length();\n        sb.replace(length - 1, length, \"\");\n        sb.append(\"]\");\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/AprilFools.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.util.i18n.LocaleUtils;\n\nimport java.time.LocalDate;\nimport java.time.Month;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.setting.ConfigHolder.config;\n\n/// April Fools' Day utilities.\n///\n/// This class provides methods to check if it is April Fools' Day or near April Fools' Day.\n/// It also provides a method to check if April Fools is enabled.\n///\n/// @author Glavo\npublic final class AprilFools {\n\n    private static final boolean ENABLED;\n    private static final boolean SHOW_APRIL_FOOLS_SETTINGS;\n\n    static {\n        var date = LocalDate.now();\n\n        // Some countries/regions may oppose April Fools' Day for various reasons.\n        // Therefore, we use a regional whitelist to avoid risks.\n        // Currently, we have only listed a limited set of countries/regions for testing.\n        // We will investigate more countries/regions in the future to expand this list.\n        boolean supportedRegion = List.of(\n                \"CN\", \"TW\", \"HK\", \"MO\", \"JP\", \"KR\", \"VN\", \"SG\", \"MY\",\n                \"ES\", \"DE\", \"FR\", \"GB\", \"RU\", \"UA\", \"US\"\n        ).contains(LocaleUtils.SYSTEM_DEFAULT.getCountry());\n\n        boolean aprilFoolsMode;\n        String value = System.getProperty(\"hmcl.april_fools\", System.getenv(\"HMCL_APRIL_FOOLS\"));\n        if (\"true\".equalsIgnoreCase(value))\n            aprilFoolsMode = true;\n        else if (\"false\".equalsIgnoreCase(value) || !supportedRegion)\n            aprilFoolsMode = false;\n        else\n            aprilFoolsMode = date.getMonth() == Month.APRIL && date.getDayOfMonth() == 1;\n\n        ENABLED = aprilFoolsMode && !config().isDisableAprilFools();\n        SHOW_APRIL_FOOLS_SETTINGS = aprilFoolsMode || supportedRegion && date.getMonth() == Month.MARCH && date.getDayOfMonth() > 30;\n    }\n\n    /// Whether April Fools settings should be shown.\n    ///\n    /// This method returns true if April Fools settings should be shown.\n    public static boolean isShowAprilFoolsSettings() {\n        return SHOW_APRIL_FOOLS_SETTINGS;\n    }\n\n    /// Whether April Fools is enabled.\n    ///\n    /// This method returns true if April Fools is enabled.\n    public static boolean isEnabled() {\n        return ENABLED;\n    }\n\n    private AprilFools() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/ChunkBaseApp.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.game.World;\nimport org.jackhuang.hmcl.ui.FXUtils;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\n\npublic final class ChunkBaseApp {\n    private static final String CHUNK_BASE_URL = \"https://www.chunkbase.com\";\n\n    private static final GameVersionNumber MIN_GAME_VERSION = GameVersionNumber.asGameVersion(\"1.7\");\n    private static final GameVersionNumber MIN_END_CITY_VERSION = GameVersionNumber.asGameVersion(\"1.13\");\n\n    private static final String[] SEED_MAP_GAME_VERSIONS = {\n            \"1.21.9\", \"1.21.6\", \"1.21.5\", \"1.21.4\", \"1.21.2\", \"1.21\", \"1.20\",\n            \"1.19.3\", \"1.19\", \"1.18\", \"1.17\", \"1.16\", \"1.15\", \"1.14\", \"1.13\",\n            \"1.12\", \"1.11\", \"1.10\", \"1.9\", \"1.8\", \"1.7\"\n    };\n\n    public static final String[] STRONGHOLD_FINDER_GAME_VERSIONS = {\n            \"1.20\", \"1.19.3\", \"1.19\", \"1.18\", \"1.16\", \"1.13\", \"1.9\", \"1.7\"\n    };\n\n    public static final String[] NETHER_FORTRESS_GAME_VERSIONS = {\n            \"1.18\", \"1.16\", \"1.7\"\n    };\n\n    public static final String[] END_CITY_GAME_VERSIONS = {\n            \"1.19\", \"1.13\"\n    };\n\n    public static boolean isSupported(@NotNull World world) {\n        return world.getSeed() != null && world.getGameVersion() != null &&\n                world.getGameVersion().compareTo(MIN_GAME_VERSION) >= 0;\n    }\n\n    public static boolean supportEndCity(@NotNull World world) {\n        return world.getSeed() != null && world.getGameVersion() != null &&\n                world.getGameVersion().compareTo(MIN_END_CITY_VERSION) >= 0;\n    }\n\n    public static ChunkBaseApp newBuilder(String app, long seed) {\n        return new ChunkBaseApp(new StringBuilder(CHUNK_BASE_URL).append(\"/apps/\").append(app).append(\"#seed=\").append(seed));\n    }\n\n    public static void openSeedMap(World world) {\n        assert isSupported(world);\n\n        newBuilder(\"seed-map\", Objects.requireNonNull(world.getSeed()))\n                .addPlatform(world.getGameVersion(), world.isLargeBiomes(), SEED_MAP_GAME_VERSIONS)\n                .open();\n    }\n\n    public static void openStrongholdFinder(World world) {\n        assert isSupported(world);\n\n        newBuilder(\"stronghold-finder\", Objects.requireNonNull(world.getSeed()))\n                .addPlatform(world.getGameVersion(), world.isLargeBiomes(), STRONGHOLD_FINDER_GAME_VERSIONS)\n                .open();\n    }\n\n    public static void openNetherFortressFinder(World world) {\n        assert isSupported(world);\n\n        newBuilder(\"nether-fortress-finder\", Objects.requireNonNull(world.getSeed()))\n                .addPlatform(world.getGameVersion(), false, NETHER_FORTRESS_GAME_VERSIONS)\n                .open();\n    }\n\n    public static void openEndCityFinder(World world) {\n        assert isSupported(world);\n\n        newBuilder(\"endcity-finder\", Objects.requireNonNull(world.getSeed()))\n                .addPlatform(world.getGameVersion(), false, END_CITY_GAME_VERSIONS)\n                .open();\n    }\n\n    private final StringBuilder builder;\n\n    private ChunkBaseApp(StringBuilder builder) {\n        this.builder = builder;\n    }\n\n    public ChunkBaseApp add(String key, String value) {\n        builder.append('&').append(key).append('=').append(value);\n        return this;\n    }\n\n    public ChunkBaseApp addPlatform(GameVersionNumber gameVersion, boolean largeBiomes, String[] versionList) {\n        String version = null;\n        for (String candidateVersion : versionList) {\n            if (gameVersion.compareTo(candidateVersion) >= 0) {\n                version = candidateVersion;\n                break;\n            }\n        }\n\n        if (version == null) {\n            version = versionList[versionList.length - 1]; // Use the last version if no suitable version found\n        }\n\n        add(\"platform\", \"java_\" + version.replace('.', '_') + (largeBiomes ? \"_lb\" : \"\"));\n        return this;\n    }\n\n    public void open() {\n        FXUtils.openLink(builder.toString());\n    }\n\n    @Override\n    public String toString() {\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport javafx.application.Platform;\nimport javafx.scene.control.Alert;\nimport javafx.scene.control.Alert.AlertType;\nimport org.jackhuang.hmcl.countly.CrashReport;\nimport org.jackhuang.hmcl.ui.CrashWindow;\n\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huangyuhui\n */\npublic final class CrashReporter implements Thread.UncaughtExceptionHandler {\n\n    // Lazy initialization resources\n    private static final class Hole {\n        @SuppressWarnings(\"unchecked\")\n        static final Pair<String, String>[] SOURCE = (Pair<String, String>[]) new Pair<?, ?>[]{\n                pair(\"Location is not set\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"UnsatisfiedLinkError\", i18n(\"crash.user_fault\")),\n                pair(\"java.time.zone.ZoneRulesException: Unable to load TZDB time-zone rules\", i18n(\"crash.user_fault\")),\n                pair(\"java.lang.NoClassDefFoundError\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"org.jackhuang.hmcl.util.ResourceNotFoundError\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"java.lang.VerifyError\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"java.lang.NoSuchMethodError\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"java.lang.NoSuchFieldError\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"javax.imageio.IIOException\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"netscape.javascript.JSException\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"java.lang.IncompatibleClassChangeError\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"java.lang.ClassFormatError\", i18n(\"crash.NoClassDefFound\")),\n                pair(\"com.sun.javafx.css.StyleManager.findMatchingStyles\", i18n(\"launcher.update_java\")),\n                pair(\"NoSuchAlgorithmException\", \"Has your operating system been installed completely or is a ghost system?\")\n        };\n    }\n\n    private boolean checkThrowable(Throwable e) {\n        String s = StringUtils.getStackTrace(e);\n        for (Pair<String, String> entry : Hole.SOURCE)\n            if (s.contains(entry.getKey())) {\n                if (StringUtils.isNotBlank(entry.getValue())) {\n                    String info = entry.getValue();\n                    LOG.error(info);\n                    try {\n                        Alert alert = new Alert(AlertType.INFORMATION, info);\n                        alert.setTitle(i18n(\"message.info\"));\n                        alert.setHeaderText(i18n(\"message.info\"));\n                        alert.showAndWait();\n                    } catch (Throwable t) {\n                        LOG.error(\"Unable to show message\", t);\n                    }\n                }\n                return false;\n            }\n        return true;\n    }\n\n    private final boolean showCrashWindow;\n\n    public CrashReporter(boolean showCrashWindow) {\n        this.showCrashWindow = showCrashWindow;\n    }\n\n    @Override\n    public void uncaughtException(Thread t, Throwable e) {\n        LOG.error(\"Uncaught exception in thread \" + t.getName(), e);\n\n        try {\n            CrashReport report = new CrashReport(t, e);\n            if (!report.shouldBeReport())\n                return;\n\n            LOG.error(report.getDisplayText());\n            Platform.runLater(() -> {\n                if (checkThrowable(e)) {\n                    if (showCrashWindow) {\n                        new CrashWindow(report).show();\n                    }\n                }\n            });\n        } catch (Throwable handlingException) {\n            LOG.error(\"Unable to handle uncaught exception\", handlingException);\n        }\n\n        FileSaver.shutdown();\n        LOG.shutdown();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/FileSaver.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author Glavo\npublic final class FileSaver extends Thread {\n\n    private static final BlockingQueue<Action> queue = new LinkedBlockingQueue<>();\n    private static final AtomicBoolean running = new AtomicBoolean(false);\n    private static final ReentrantLock runningLock = new ReentrantLock();\n    private static volatile boolean shutdown = false;\n\n    private static void addAction(Action action) {\n        queue.add(action);\n        if (running.compareAndSet(false, true)) {\n            new FileSaver().start();\n        }\n    }\n\n    private static void doSave(Map<Path, String> map) {\n        for (Map.Entry<Path, String> entry : map.entrySet()) {\n            saveSync(entry.getKey(), entry.getValue());\n        }\n    }\n\n    public static void save(Path file, String content) {\n        Objects.requireNonNull(file);\n        Objects.requireNonNull(content);\n\n        ShutdownHook.ensureInstalled();\n\n        addAction(new DoSave(file, content));\n    }\n\n    public static void saveSync(Path file, String content) {\n        LOG.info(\"Saving file \" + file);\n        try {\n            FileUtils.saveSafely(file, content);\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to save \" + file, e);\n        }\n    }\n\n    public static void shutdown() {\n        shutdown = true;\n        queue.add(Shutdown.INSTANCE);\n    }\n\n    /// Wait for all saves to complete.\n    ///\n    /// This method should be called before the [#shutdown()] method.\n    public static void waitForAllSaves() throws InterruptedException {\n        assert !shutdown;\n        Wait wait = new Wait();\n        addAction(wait);\n        wait.await();\n    }\n\n    private FileSaver() {\n        super(\"FileSaver\");\n    }\n\n    private boolean stopped = false;\n\n    private void stopCurrentSaver() {\n        // Ensure that each saver calls `running.set(false)` at most once\n        if (!stopped) {\n            stopped = true;\n            running.set(false);\n        }\n    }\n\n    @Override\n    public void run() {\n        runningLock.lock();\n        try {\n            HashMap<Path, String> map = new HashMap<>();\n            ArrayList<Action> buffer = new ArrayList<>();\n            ArrayList<Wait> waits = new ArrayList<>();\n\n            while (!stopped) {\n                if (shutdown) {\n                    stopCurrentSaver();\n                } else {\n                    Action head = queue.poll(30, TimeUnit.SECONDS);\n                    if (head instanceof DoSave save) {\n                        map.put(save.file(), save.content());\n                        //noinspection BusyWait\n                        Thread.sleep(200); // Waiting for more changes\n                    } else if (head instanceof Wait wait) {\n                        waits.add(wait);\n                    } else if (head == null || head instanceof Shutdown) {\n                        // Shutdown or timeout\n                        stopCurrentSaver();\n                    }\n                }\n\n                while (queue.drainTo(buffer) > 0) {\n                    for (Action action : buffer) {\n                        if (action instanceof DoSave save) {\n                            map.put(save.file(), save.content());\n                        } else if (action instanceof Wait wait) {\n                            waits.add(wait);\n                        } else if (action instanceof Shutdown) {\n                            stopCurrentSaver();\n                        }\n                    }\n                    buffer.clear();\n                }\n\n                doSave(map);\n                map.clear();\n\n                for (Wait wait : waits) {\n                    wait.countDown();\n                }\n                waits.clear();\n            }\n        } catch (InterruptedException e) {\n            throw new AssertionError(\"This thread cannot be interrupted\", e);\n        } finally {\n            runningLock.unlock();\n        }\n    }\n\n    private sealed interface Action {\n    }\n\n    private record DoSave(Path file, String content) implements Action {\n    }\n\n    private static final class Wait implements Action {\n        private final CountDownLatch latch = new CountDownLatch(1);\n\n        public void await() throws InterruptedException {\n            latch.await();\n        }\n\n        public void countDown() {\n            latch.countDown();\n        }\n    }\n\n    private enum Shutdown implements Action {\n        INSTANCE\n    }\n\n    private static final class ShutdownHook extends Thread {\n\n        static {\n            Runtime.getRuntime().addShutdownHook(new ShutdownHook());\n        }\n\n        static void ensureInstalled() {\n            // Ensure the shutdown hook is installed\n        }\n\n        @Override\n        public void run() {\n            shutdown();\n            runningLock.lock();\n            try {\n                HashMap<Path, String> map = new HashMap<>();\n                for (Action action : queue) {\n                    if (action instanceof DoSave save) {\n                        map.put(save.file(), save.content());\n                    }\n                }\n                doSave(map);\n            } finally {\n                runningLock.unlock();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/JavaFXPatcher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.lang.module.Configuration;\nimport java.lang.module.ModuleFinder;\nimport java.lang.module.ModuleReference;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * Utility for Adding JavaFX to module path.\n *\n * @author ZekerZhayard\n */\npublic final class JavaFXPatcher {\n    private JavaFXPatcher() {\n    }\n\n    /**\n     * Add JavaFX to module path at runtime.\n     *\n     * @param modules  All module names\n     * @param jarPaths All jar paths\n     * @throws ReflectiveOperationException When the call to add these jars to the system module path failed.\n     */\n    public static void patch(Set<String> modules, Path[] jarPaths, String[] addOpens) throws ReflectiveOperationException {\n        // Find all modules\n        ModuleFinder finder = ModuleFinder.of(jarPaths);\n\n        // Load all modules as unnamed module\n        for (ModuleReference mref : finder.findAll()) {\n            ((jdk.internal.loader.BuiltinClassLoader) ClassLoader.getSystemClassLoader()).loadModule(mref);\n        }\n\n        // Define all modules\n        Configuration config = Configuration.resolveAndBind(finder, List.of(ModuleLayer.boot().configuration()), finder, modules);\n        ModuleLayer layer = ModuleLayer.defineModules(config, List.of(ModuleLayer.boot()), name -> ClassLoader.getSystemClassLoader()).layer();\n\n        // Add-Exports and Add-Opens\n        try {\n            // Some hacks\n            MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());\n            MethodHandle handle = lookup.findVirtual(Module.class, \"implAddOpensToAllUnnamed\", MethodType.methodType(void.class, String.class));\n            for (String target : addOpens) {\n                String[] name = target.split(\"/\", 2); // <module>/<package>\n                layer.findModule(name[0]).ifPresent(m -> {\n                    try {\n                        handle.invokeWithArguments(m, name[1]);\n                    } catch (Throwable throwable) {\n                        throw new RuntimeException(throwable);\n                    }\n                });\n            }\n        } catch (Throwable t) {\n            throw new ReflectiveOperationException(t);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/Lazy.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\n/**\n * Non thread-safe lazy initialization wrapper.\n *\n * @param <T> value type\n */\npublic final class Lazy<T> {\n    private Supplier<T> supplier;\n    private T value = null;\n\n    public Lazy(Supplier<T> supplier) {\n        this.supplier = Objects.requireNonNull(supplier);\n    }\n\n    public T get() {\n        if (supplier != null) {\n            value = supplier.get();\n            supplier = null;\n        }\n        return value;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.setting.VersionSetting;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.platform.OSVersion;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class NativePatcher {\n\n    private static final Library NONEXISTENT_LIBRARY = new Library(null);\n\n    private static final Map<Platform, Map<String, Library>> natives = new HashMap<>();\n\n    private static Map<String, Library> getNatives(Platform platform) {\n        return natives.computeIfAbsent(platform, p -> {\n            //noinspection ConstantConditions\n            try (Reader reader = new InputStreamReader(NativePatcher.class.getResourceAsStream(\"/assets/natives.json\"), StandardCharsets.UTF_8)) {\n                Map<String, Map<String, Library>> natives = JsonUtils.GSON.fromJson(reader, mapTypeOf(String.class, mapTypeOf(String.class, Library.class)));\n                return natives.getOrDefault(p.toString(), Collections.emptyMap());\n            } catch (IOException e) {\n                LOG.warning(\"Failed to load native library list\", e);\n                return Collections.emptyMap();\n            }\n        });\n    }\n\n    public static Version patchNative(DefaultGameRepository repository,\n                                      Version version, String gameVersion,\n                                      JavaRuntime javaVersion,\n                                      VersionSetting settings,\n                                      List<String> javaArguments) {\n        if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) {\n            if (gameVersion != null && GameVersionNumber.compare(gameVersion, \"1.19\") < 0)\n                return version;\n\n            ArrayList<Library> newLibraries = new ArrayList<>();\n            for (Library library : version.getLibraries()) {\n                if (!library.appliesToCurrentEnvironment())\n                    continue;\n\n                if (library.getClassifier() == null\n                        || !library.getArtifactId().startsWith(\"lwjgl\")\n                        || !library.getClassifier().startsWith(\"natives\")) {\n                    newLibraries.add(library);\n                }\n            }\n            return version.setLibraries(newLibraries);\n        }\n\n        final boolean useNativeGLFW = settings.isUseNativeGLFW();\n        final boolean useNativeOpenAL = settings.isUseNativeOpenAL();\n\n        if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && (useNativeGLFW || useNativeOpenAL)\n                && gameVersion != null && GameVersionNumber.compare(gameVersion, \"1.19\") >= 0) {\n\n            version = version.setLibraries(version.getLibraries().stream()\n                    .filter(library -> {\n                        if (library.getClassifier() != null && library.getClassifier().startsWith(\"natives\")\n                                && \"org.lwjgl\".equals(library.getGroupId())) {\n                            if ((useNativeGLFW && \"lwjgl-glfw\".equals(library.getArtifactId()))\n                                    || (useNativeOpenAL && \"lwjgl-openal\".equals(library.getArtifactId()))) {\n                                LOG.info(\"Filter out \" + library.getName());\n                                return false;\n                            }\n                        }\n\n                        return true;\n                    })\n                    .collect(Collectors.toList()));\n        }\n\n        // Try patch natives\n\n        OperatingSystem os = javaVersion.getPlatform().getOperatingSystem();\n        Architecture arch = javaVersion.getArchitecture();\n        GameVersionNumber gameVersionNumber = gameVersion != null ? GameVersionNumber.asGameVersion(gameVersion) : null;\n\n        if (settings.isNotPatchNatives())\n            return version;\n\n        if (arch.isX86() && (os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.MACOS))\n            return version;\n\n        if (arch == Architecture.ARM64 && (os == OperatingSystem.MACOS || os == OperatingSystem.WINDOWS)\n                && gameVersionNumber != null\n                && gameVersionNumber.compareTo(\"1.19\") >= 0)\n            return version;\n\n        Map<String, Library> replacements = getNatives(javaVersion.getPlatform());\n        if (replacements.isEmpty()) {\n            LOG.warning(\"No alternative native library provided for platform \" + javaVersion.getPlatform());\n            return version;\n        }\n\n        boolean lwjglVersionChanged = false;\n        ArrayList<Library> newLibraries = new ArrayList<>();\n        for (Library library : version.getLibraries()) {\n            if (!library.appliesToCurrentEnvironment())\n                continue;\n\n            if (library.isNative()) {\n                Library replacement = replacements.getOrDefault(library.getName() + \":natives\", NONEXISTENT_LIBRARY);\n                if (replacement == NONEXISTENT_LIBRARY) {\n                    LOG.warning(\"No alternative native library \" + library.getName() + \":natives provided for platform \" + javaVersion.getPlatform());\n                    newLibraries.add(library);\n                } else if (replacement != null) {\n                    LOG.info(\"Replace \" + library.getName() + \":natives with \" + replacement.getName());\n                    newLibraries.add(replacement);\n                }\n            } else {\n                Library replacement = replacements.getOrDefault(library.getName(), NONEXISTENT_LIBRARY);\n                if (replacement == NONEXISTENT_LIBRARY) {\n                    newLibraries.add(library);\n                } else if (replacement != null) {\n                    LOG.info(\"Replace \" + library.getName() + \" with \" + replacement.getName());\n                    newLibraries.add(replacement);\n\n                    if (\"org.lwjgl:lwjgl\".equals(library.getName()) && !Objects.equals(library.getVersion(), replacement.getVersion())) {\n                        lwjglVersionChanged = true;\n                    }\n                }\n            }\n        }\n\n        if (lwjglVersionChanged) {\n            ModManager modManager = repository.getModManager(version.getId());\n            try {\n                for (LocalModFile mod : modManager.getMods()) {\n                    if (\"sodium\".equals(mod.getId())) {\n                        // https://github.com/CaffeineMC/sodium/issues/2561\n                        javaArguments.add(\"-Dsodium.checks.issue2561=false\");\n                        break;\n                    }\n                }\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to get mods\", e);\n            }\n        }\n\n        return version.setLibraries(newLibraries);\n    }\n\n    public static @Nullable Library getWindowsMesaLoader(@NotNull JavaRuntime javaVersion, @NotNull Renderer renderer, @NotNull OSVersion windowsVersion) {\n        if (renderer == Renderer.DEFAULT)\n            return null;\n\n        if (windowsVersion.isAtLeast(OSVersion.WINDOWS_10)) {\n            return getNatives(javaVersion.getPlatform()).get(\"mesa-loader\");\n        } else if (windowsVersion.isAtLeast(OSVersion.WINDOWS_7)) {\n            if (renderer == Renderer.LLVMPIPE)\n                return getNatives(javaVersion.getPlatform()).get(\"software-renderer-loader\");\n            else\n                return null;\n        } else {\n            return null;\n        }\n    }\n\n    public static SupportStatus checkSupportedStatus(GameVersionNumber gameVersion, Platform platform,\n                                                     OSVersion systemVersion) {\n        if (platform.equals(Platform.WINDOWS_X86_64)) {\n            if (!systemVersion.isAtLeast(OSVersion.WINDOWS_7) && gameVersion.isAtLeast(\"1.20.5\", \"24w14a\"))\n                return SupportStatus.UNSUPPORTED;\n\n            return SupportStatus.OFFICIAL_SUPPORTED;\n        }\n\n        if (platform.equals(Platform.MACOS_X86_64) || platform.equals(Platform.LINUX_X86_64))\n            return SupportStatus.OFFICIAL_SUPPORTED;\n\n        if (platform.equals(Platform.WINDOWS_X86) || platform.equals(Platform.LINUX_X86)) {\n            if (gameVersion.isAtLeast(\"1.20.5\", \"24w14a\"))\n                return SupportStatus.UNSUPPORTED;\n            else\n                return SupportStatus.OFFICIAL_SUPPORTED;\n        }\n\n        if (platform.equals(Platform.WINDOWS_ARM64) || platform.equals(Platform.MACOS_ARM64)) {\n            if (gameVersion.compareTo(\"1.19\") >= 0)\n                return SupportStatus.OFFICIAL_SUPPORTED;\n\n            String minVersion = platform.getOperatingSystem() == OperatingSystem.WINDOWS\n                    ? \"1.8\"\n                    : \"1.6\";\n\n            return gameVersion.compareTo(minVersion) >= 0\n                    ? SupportStatus.LAUNCHER_SUPPORTED\n                    : SupportStatus.TRANSLATION_SUPPORTED;\n        }\n\n        String minVersion = null;\n        String maxVersion = null;\n\n        if (platform.equals(Platform.FREEBSD_X86_64)) {\n            minVersion = \"1.13\";\n        } else if (platform.equals(Platform.LINUX_ARM64)) {\n            minVersion = \"1.6\";\n        } else if (platform.equals(Platform.LINUX_RISCV64)) {\n            minVersion = \"1.8\";\n\n            if (gameVersion.compareTo(\"1.21.5\") > 0 && gameVersion.compareTo(\"26.1-snapshot-8\") < 0) {\n                // LWJGL version mismatch\n                return SupportStatus.UNSUPPORTED;\n            }\n        } else if (platform.equals(Platform.LINUX_LOONGARCH64)) {\n            minVersion = \"1.6\";\n        } else if (platform.equals(Platform.LINUX_LOONGARCH64_OW)) {\n            minVersion = \"1.6\";\n            maxVersion = \"1.20.1\";\n        } else if (platform.equals(Platform.LINUX_MIPS64EL) || platform.equals(Platform.LINUX_ARM32)) {\n            minVersion = \"1.8\";\n            maxVersion = \"1.20.1\";\n        }\n\n        if (minVersion != null) {\n            if (gameVersion.compareTo(minVersion) >= 0) {\n                if (maxVersion != null && gameVersion.compareTo(maxVersion) > 0)\n                    return SupportStatus.UNSUPPORTED;\n\n                String[] defaultGameVersions = GameVersionNumber.getDefaultGameVersions();\n                if (defaultGameVersions.length > 0 && gameVersion.compareTo(defaultGameVersions[0]) > 0) {\n                    return SupportStatus.UNTESTED;\n                }\n                return SupportStatus.LAUNCHER_SUPPORTED;\n            } else {\n                return SupportStatus.UNSUPPORTED;\n            }\n        }\n\n        return SupportStatus.UNTESTED;\n    }\n\n    public enum SupportStatus {\n        OFFICIAL_SUPPORTED,\n        LAUNCHER_SUPPORTED,\n        TRANSLATION_SUPPORTED,\n        UNTESTED,\n        UNSUPPORTED,\n    }\n\n    private NativePatcher() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/QrCodeUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport io.nayuki.qrcodegen.QrCode;\n\n/// @author Glavo\npublic final class QrCodeUtils {\n    public static String toSVGPath(QrCode qr) {\n        return toSVGPath(qr, 0);\n    }\n\n    public static String toSVGPath(QrCode qr, int border) {\n        int actualSize = qr.size + border * 2;\n\n        var builder = new StringBuilder(qr.size * qr.size * 12);\n        builder.append('M').append(actualSize).append(' ').append(actualSize).append(\"ZM0 0Z\");\n\n        for (int y = 0; y < qr.size; y++) {\n            for (int x = 0; x < qr.size; x++) {\n                if (qr.getModule(x, y)) {\n                    if (x != 0 || y != 0)\n                        builder.append(' ');\n                    builder.append(\"M\").append(x + border).append(',').append(y + border).append(\"h1v1h-1z\");\n                }\n            }\n        }\n        return builder.toString();\n    }\n\n    private QrCodeUtils() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/RemoteImageLoader.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport javafx.beans.value.WritableValue;\nimport javafx.scene.image.Image;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.ref.WeakReference;\nimport java.net.URI;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author Glavo\npublic abstract class RemoteImageLoader {\n    private final DownloadProvider downloadProvider;\n    private final Map<URI, WeakReference<Image>> cache = new HashMap<>();\n    private final Map<URI, List<WeakReference<WritableValue<Image>>>> pendingRequests = new HashMap<>();\n    private final WeakHashMap<WritableValue<Image>, URI> reverseLookup = new WeakHashMap<>();\n\n    public RemoteImageLoader(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    protected @Nullable Image getPlaceholder() {\n        return null;\n    }\n\n    protected abstract @NotNull Task<Image> createLoadTask(@NotNull List<URI> uris);\n\n    @FXThread\n    public void load(@NotNull WritableValue<Image> writableValue, String url) {\n        URI uri = NetworkUtils.toURIOrNull(url);\n        if (uri == null) {\n            reverseLookup.remove(writableValue);\n            writableValue.setValue(getPlaceholder());\n            return;\n        }\n\n        WeakReference<Image> reference = cache.get(uri);\n        if (reference != null) {\n            Image image = reference.get();\n            if (image != null) {\n                reverseLookup.remove(writableValue);\n                writableValue.setValue(image);\n                return;\n            }\n            cache.remove(uri);\n        }\n\n        writableValue.setValue(getPlaceholder());\n\n        {\n            List<WeakReference<WritableValue<Image>>> list = pendingRequests.get(uri);\n            if (list != null) {\n                list.add(new WeakReference<>(writableValue));\n                reverseLookup.put(writableValue, uri);\n                return;\n            } else {\n                list = new ArrayList<>(1);\n                list.add(new WeakReference<>(writableValue));\n                pendingRequests.put(uri, list);\n                reverseLookup.put(writableValue, uri);\n            }\n        }\n\n        createLoadTask(downloadProvider.injectURLWithCandidates(url)).whenComplete(Schedulers.javafx(), (result, exception) -> {\n            Image image;\n            if (exception == null) {\n                image = result;\n            } else {\n                LOG.warning(\"Failed to load image from \" + uri, exception);\n                image = getPlaceholder();\n            }\n\n            cache.put(uri, new WeakReference<>(image));\n            List<WeakReference<WritableValue<Image>>> list = pendingRequests.remove(uri);\n            if (list != null) {\n                for (WeakReference<WritableValue<Image>> ref : list) {\n                    WritableValue<Image> target = ref.get();\n                    if (target != null && uri.equals(reverseLookup.get(target))) {\n                        reverseLookup.remove(target);\n                        target.setValue(image);\n                    }\n                }\n            }\n        }).start();\n    }\n\n    @FXThread\n    public void unload(@NotNull WritableValue<Image> writableValue) {\n        reverseLookup.remove(writableValue);\n    }\n\n    @FXThread\n    public void clearInvalidCache() {\n        cache.entrySet().removeIf(entry -> entry.getValue().get() == null);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/ResourceNotFoundError.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.io.InputStream;\n\n/**\n * Suppress the throwable when we make sure the resource cannot miss.\n * @see CrashReporter\n */\npublic final class ResourceNotFoundError extends Error {\n    public ResourceNotFoundError(String message) {\n        super(message);\n    }\n\n    public ResourceNotFoundError(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public static InputStream getResourceAsStream(String url) {\n        InputStream stream = ResourceNotFoundError.class.getResourceAsStream(url);\n        if (stream == null)\n            throw new ResourceNotFoundError(\"Resource not found: \" + url);\n        return stream;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/Restarter.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.upgrade.UpdateHandler;\nimport org.jackhuang.hmcl.util.io.JarUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author Glavo\npublic final class Restarter {\n\n    /// Restart the current application.\n    public static void restartSelf() throws IOException {\n        LOG.info(\"Restarting HMCL\");\n\n        Path thisJar = JarUtils.thisJarPath();\n        if (thisJar == null) {\n            throw new IOException(\"Failed to find current HMCL location\");\n        }\n\n        UpdateHandler.startJava(thisJar);\n    }\n\n    private Restarter() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2017-2021 Matthew Coley\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.EntryPoint;\nimport org.jackhuang.hmcl.Metadata;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.ChecksumMismatchException;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.platform.Platform;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.WindowAdapter;\nimport java.awt.event.WindowEvent;\nimport java.io.*;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.MessageDigest;\nimport java.util.List;\nimport java.util.*;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static java.util.stream.Collectors.toSet;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.i18n.I18n.i18n;\n\n// From: https://github.com/Col-E/Recaf/blob/7378b397cee664ae81b7963b0355ef8ff013c3a7/src/main/java/me/coley/recaf/util/self/SelfDependencyPatcher.java\npublic final class SelfDependencyPatcher {\n    private final List<DependencyDescriptor> dependencies = DependencyDescriptor.readDependencies();\n    private final List<Repository> repositories;\n    private final Repository defaultRepository;\n    private final byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];\n    private final MessageDigest digest = DigestUtils.getDigest(\"SHA-1\");\n\n    private SelfDependencyPatcher() throws PatchException {\n        // We can only self-patch JavaFX on specific platform.\n        if (dependencies == null) {\n            throw new PatchException(\"Unsupported platform: operating system %s, architecture %s\".formatted(\n                    System.getProperty(\"os.name\"), System.getProperty(\"os.arch\")));\n        }\n\n        final String customUrl = System.getProperty(\"hmcl.openjfx.repo\");\n        if (customUrl == null) {\n            if (System.getProperty(\"user.country\", \"\").equalsIgnoreCase(\"CN\")) {\n                defaultRepository = Repository.TENCENTCLOUD_MIRROR;\n            } else {\n                defaultRepository = Repository.MAVEN_CENTRAL;\n            }\n            repositories = List.of(Repository.MAVEN_CENTRAL, Repository.TENCENTCLOUD_MIRROR);\n        } else {\n            defaultRepository = new Repository(String.format(i18n(\"repositories.custom\"), customUrl), customUrl);\n            repositories = List.of(Repository.MAVEN_CENTRAL, Repository.TENCENTCLOUD_MIRROR, defaultRepository);\n        }\n    }\n\n    private static final class DependencyDescriptor {\n        private static final String DEPENDENCIES_LIST_FILE = \"/assets/openjfx-dependencies.json\";\n        private static final Path DEPENDENCIES_DIR_PATH = Metadata.DEPENDENCIES_DIRECTORY.resolve(Platform.CURRENT_PLATFORM.toString()).resolve(\"openjfx\");\n\n        static List<DependencyDescriptor> readDependencies() {\n            //noinspection ConstantConditions\n            try (Reader reader = new InputStreamReader(SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE), UTF_8)) {\n                Map<String, Map<String, List<DependencyDescriptor>>> allDependencies =\n                        JsonUtils.GSON.fromJson(reader, mapTypeOf(String.class, mapTypeOf(String.class, listTypeOf(DependencyDescriptor.class))));\n                Map<String, List<DependencyDescriptor>> platform = allDependencies.get(Platform.CURRENT_PLATFORM.toString());\n                if (platform == null)\n                    return null;\n\n                if (JavaRuntime.CURRENT_VERSION >= 23) {\n                    List<DependencyDescriptor> modernDependencies = platform.get(\"modern\");\n                    if (modernDependencies != null)\n                        return modernDependencies;\n                }\n                return platform.get(\"classic\");\n            } catch (IOException e) {\n                throw new UncheckedIOException(e);\n            }\n        }\n\n        public String module;\n        public String groupId;\n        public String artifactId;\n        public String version;\n        public String classifier;\n        public String sha1;\n\n        public String filename() {\n            return artifactId + \"-\" + version + \"-\" + classifier + \".jar\";\n        }\n\n        public String sha1() {\n            return sha1;\n        }\n\n        public Path localPath() {\n            return DEPENDENCIES_DIR_PATH.resolve(filename());\n        }\n    }\n\n    private record Repository(String name, String url) {\n        public static final Repository MAVEN_CENTRAL = new Repository(i18n(\"repositories.maven_central\"), \"https://repo1.maven.org/maven2\");\n        public static final Repository TENCENTCLOUD_MIRROR = new Repository(i18n(\"repositories.tencentcloud_mirror\"), \"https://mirrors.cloud.tencent.com/nexus/repository/maven-public\");\n\n        public String resolveDependencyURL(DependencyDescriptor descriptor) {\n            return String.format(\"%s/%s/%s/%s/%s\",\n                    url,\n                    descriptor.groupId.replace('.', '/'),\n                    descriptor.artifactId, descriptor.version,\n                    descriptor.filename());\n        }\n    }\n\n    /**\n     * Patch in any missing dependencies, if any.\n     */\n    public static void patch() throws PatchException, CancellationException {\n        // Do nothing if JavaFX is detected\n        try {\n            Class.forName(\"javafx.application.Application\");\n            return;\n        } catch (Exception ignored) {\n        }\n\n        SelfDependencyPatcher patcher = new SelfDependencyPatcher();\n\n        // Otherwise we're free to download in Java 11+\n        LOG.info(\"Missing JavaFX dependencies, attempting to patch in missing classes\");\n\n        // Download missing dependencies\n        List<DependencyDescriptor> missingDependencies = patcher.checkMissingDependencies();\n        if (!missingDependencies.isEmpty()) {\n            try {\n                patcher.fetchDependencies(missingDependencies);\n            } catch (IOException e) {\n                throw new PatchException(\"Failed to download dependencies\", e);\n            }\n        }\n\n        // Add the dependencies\n        try {\n            patcher.loadFromCache();\n        } catch (IOException ex) {\n            throw new PatchException(\"Failed to load JavaFX cache\", ex);\n        } catch (ReflectiveOperationException | NoClassDefFoundError ex) {\n            throw new PatchException(\"Failed to add dependencies to classpath!\", ex);\n        }\n        LOG.info(\" - Done!\");\n    }\n\n    private Repository showChooseRepositoryDialog() {\n        final JPanel panel = new JPanel();\n        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));\n\n        for (String line : i18n(\"repositories.chooser\").split(\"\\n\")) {\n            panel.add(new JLabel(line));\n        }\n\n        final ButtonGroup buttonGroup = new ButtonGroup();\n\n        for (Repository repository : repositories) {\n            final JRadioButton button = new JRadioButton(repository.name);\n            button.putClientProperty(\"repository\", repository);\n            buttonGroup.add(button);\n            panel.add(button);\n            if (repository == defaultRepository) {\n                button.setSelected(true);\n            }\n        }\n\n        int res = JOptionPane.showConfirmDialog(null, panel, i18n(\"repositories.chooser.title\"), JOptionPane.OK_CANCEL_OPTION);\n\n        if (res == JOptionPane.OK_OPTION) {\n            final Enumeration<AbstractButton> buttons = buttonGroup.getElements();\n            while (buttons.hasMoreElements()) {\n                final AbstractButton button = buttons.nextElement();\n                if (button.isSelected()) {\n                    return (Repository) button.getClientProperty(\"repository\");\n                }\n            }\n        } else {\n            LOG.info(\"User choose not to download JavaFX\");\n            EntryPoint.exit(0);\n        }\n        throw new AssertionError();\n    }\n\n    /**\n     * Inject them into the current classpath.\n     *\n     * @throws IOException                  When the locally cached dependency urls cannot be resolved.\n     * @throws ReflectiveOperationException When the call to add these urls to the system classpath failed.\n     */\n    private void loadFromCache() throws IOException, ReflectiveOperationException {\n        LOG.info(\" - Loading dependencies...\");\n\n        Set<String> modules = dependencies.stream()\n                .map(it -> it.module)\n                .collect(toSet());\n\n        Path[] jars = dependencies.stream()\n                .map(DependencyDescriptor::localPath)\n                .toArray(Path[]::new);\n\n        String addOpens = JarUtils.getAttribute(\"hmcl.add-opens\", null);\n        JavaFXPatcher.patch(modules, jars, addOpens != null ? addOpens.split(\" \") : new String[0]);\n    }\n\n    /**\n     * Download dependencies.\n     *\n     * @throws IOException When the files cannot be fetched or saved.\n     */\n    private void fetchDependencies(List<DependencyDescriptor> dependencies) throws IOException {\n        SwingUtils.initLookAndFeel();\n\n        boolean isFirstTime = true;\n\n        Repository repository = defaultRepository;\n\n        int count = 0;\n        while (true) {\n            AtomicBoolean isCancelled = new AtomicBoolean();\n            AtomicBoolean showDetails = new AtomicBoolean();\n\n            ProgressFrame dialog = new ProgressFrame(i18n(\"download.javafx\"));\n            dialog.setProgressMaximum(dependencies.size() + 1);\n            dialog.setProgress(count);\n            dialog.setOnCancel(() -> isCancelled.set(true));\n            dialog.setOnChangeSource(() -> {\n                isCancelled.set(true);\n                showDetails.set(true);\n            });\n            dialog.setVisible(true);\n            try {\n                if (isFirstTime) {\n                    isFirstTime = false;\n                    try {\n                        //noinspection BusyWait\n                        Thread.sleep(1000);\n                    } catch (InterruptedException ignored) {\n                    }\n                }\n                Files.createDirectories(DependencyDescriptor.DEPENDENCIES_DIR_PATH);\n                for (int i = count; i < dependencies.size(); i++) {\n                    if (isCancelled.get()) {\n                        throw new CancellationException();\n                    }\n\n                    DependencyDescriptor dependency = dependencies.get(i);\n\n                    final String url = repository.resolveDependencyURL(dependency);\n                    SwingUtilities.invokeLater(() -> {\n                        dialog.setCurrent(dependency.module);\n                        dialog.incrementProgress();\n                    });\n\n                    LOG.info(\"Downloading \" + url);\n\n                    try (InputStream is = new URL(url).openStream();\n                         OutputStream os = Files.newOutputStream(dependency.localPath())) {\n\n                        int read;\n                        while ((read = is.read(buffer, 0, IOUtils.DEFAULT_BUFFER_SIZE)) >= 0) {\n                            if (isCancelled.get()) {\n                                try {\n                                    os.close();\n                                } finally {\n                                    Files.deleteIfExists(dependency.localPath());\n                                }\n                                throw new CancellationException();\n                            }\n                            os.write(buffer, 0, read);\n                        }\n                    }\n                    verifyChecksum(dependency);\n                    count++;\n                }\n            } catch (CancellationException e) {\n                dialog.dispose();\n                if (showDetails.get()) {\n                    repository = showChooseRepositoryDialog();\n                    continue;\n                } else {\n                    throw e;\n                }\n            }\n            dialog.dispose();\n            return;\n        }\n    }\n\n    private List<DependencyDescriptor> checkMissingDependencies() {\n        List<DependencyDescriptor> missing = new ArrayList<>();\n\n        for (DependencyDescriptor dependency : dependencies) {\n            if (!Files.exists(dependency.localPath())) {\n                missing.add(dependency);\n                continue;\n            }\n\n            try {\n                verifyChecksum(dependency);\n            } catch (ChecksumMismatchException e) {\n                LOG.warning(\"Corrupted dependency \" + dependency.filename() + \": \" + e.getMessage());\n                missing.add(dependency);\n            } catch (IOException e) {\n                throw new UncheckedIOException(e);\n            }\n        }\n\n        return missing;\n    }\n\n    private void verifyChecksum(DependencyDescriptor dependency) throws IOException, ChecksumMismatchException {\n        digest.reset();\n        try (InputStream is = Files.newInputStream(dependency.localPath())) {\n            int read;\n            while ((read = is.read(buffer, 0, IOUtils.DEFAULT_BUFFER_SIZE)) > -1) {\n                digest.update(buffer, 0, read);\n            }\n        }\n\n        String sha1 = HexFormat.of().formatHex(digest.digest());\n        if (!dependency.sha1().equalsIgnoreCase(sha1))\n            throw new ChecksumMismatchException(\"SHA-1\", dependency.sha1(), sha1);\n    }\n\n    public static class PatchException extends Exception {\n        PatchException(String message) {\n            super(message);\n        }\n\n        PatchException(String message, Throwable cause) {\n            super(message, cause);\n        }\n    }\n\n    public static class ProgressFrame extends JDialog {\n\n        private final JProgressBar progressBar;\n        private final JLabel progressText;\n        private final JButton btnChangeSource;\n        private final JButton btnCancel;\n\n        public ProgressFrame(String title) {\n            JPanel panel = new JPanel();\n\n            setResizable(false);\n            setTitle(title);\n            setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\n            setBounds(100, 100, 500, 200);\n            setContentPane(panel);\n            setLocationRelativeTo(null);\n\n            JPanel content = new JPanel();\n            content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));\n\n            for (String note : i18n(\"download.javafx.notes\").split(\"\\n\")) {\n                content.add(new JLabel(note));\n            }\n            content.add(new JLabel(\"<html><br/></html>\"));\n            progressText = new JLabel(i18n(\"download.javafx.prepare\"));\n            content.add(progressText);\n            progressBar = new JProgressBar();\n            content.add(progressBar);\n\n            final JPanel buttonBar = new JPanel();\n            btnChangeSource = new JButton(i18n(\"button.change_source\"));\n            btnCancel = new JButton(i18n(\"button.cancel\"));\n            buttonBar.add(btnChangeSource);\n            buttonBar.add(btnCancel);\n\n            panel.setLayout(new BorderLayout());\n            panel.setBorder(BorderFactory.createEmptyBorder(10, 5, 0, 5));\n            panel.add(content, BorderLayout.CENTER);\n            panel.add(buttonBar, BorderLayout.SOUTH);\n        }\n\n        public void setCurrent(String component) {\n            progressText.setText(i18n(\"download.javafx.component\", component));\n        }\n\n        public void setProgressMaximum(int total) {\n            progressBar.setMaximum(total);\n        }\n\n        public void setProgress(int n) {\n            progressBar.setValue(n);\n        }\n\n        public void incrementProgress() {\n            progressBar.setValue(progressBar.getValue() + 1);\n        }\n\n        public void setOnCancel(Runnable action) {\n            btnCancel.addActionListener(e -> action.run());\n            addWindowListener(new WindowAdapter() {\n                @Override\n                public void windowClosing(WindowEvent e) {\n                    action.run();\n                }\n            });\n        }\n\n        public void setOnChangeSource(Runnable action) {\n            btnChangeSource.addActionListener(e -> action.run());\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/SwingFXUtils.java",
    "content": "// Copy from javafx.swing\n/*\n * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage org.jackhuang.hmcl.util;\n\nimport javafx.scene.image.*;\nimport javafx.scene.image.Image;\n\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.awt.image.DataBufferInt;\nimport java.awt.image.SampleModel;\nimport java.awt.image.SinglePixelPackedSampleModel;\nimport java.nio.IntBuffer;\n\n/**\n * This class provides utility methods for converting data types between\n * Swing/AWT and JavaFX formats.\n *\n * @since JavaFX 2.2\n */\npublic final class SwingFXUtils {\n    private SwingFXUtils() {\n    }\n\n    /**\n     * Snapshots the specified {@link BufferedImage} and stores a copy of\n     * its pixels into a JavaFX {@link Image} object, creating a new\n     * object if needed.\n     * The returned {@code Image} will be a static snapshot of the state\n     * of the pixels in the {@code BufferedImage} at the time the method\n     * completes.  Further changes to the {@code BufferedImage} will not\n     * be reflected in the {@code Image}.\n     * <p>\n     * The optional JavaFX {@link WritableImage} parameter may be reused\n     * to store the copy of the pixels.\n     * A new {@code Image} will be created if the supplied object is null,\n     * is too small or of a type which the image pixels cannot be easily\n     * converted into.\n     *\n     * @param bimg the {@code BufferedImage} object to be converted\n     * @param wimg an optional {@code WritableImage} object that can be\n     *             used to store the returned pixel data\n     * @return an {@code Image} object representing a snapshot of the\n     * current pixels in the {@code BufferedImage}.\n     * @since JavaFX 2.2\n     */\n    public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {\n        int bw = bimg.getWidth();\n        int bh = bimg.getHeight();\n        switch (bimg.getType()) {\n            case BufferedImage.TYPE_INT_ARGB:\n            case BufferedImage.TYPE_INT_ARGB_PRE:\n                break;\n            default:\n                BufferedImage converted =\n                        new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE);\n                Graphics2D g2d = converted.createGraphics();\n                g2d.drawImage(bimg, 0, 0, null);\n                g2d.dispose();\n                bimg = converted;\n                break;\n        }\n        // assert(bimg.getType == TYPE_INT_ARGB[_PRE]);\n        if (wimg != null) {\n            int iw = (int) wimg.getWidth();\n            int ih = (int) wimg.getHeight();\n            if (iw < bw || ih < bh) {\n                wimg = null;\n            } else if (bw < iw || bh < ih) {\n                int[] empty = new int[iw];\n                PixelWriter pw = wimg.getPixelWriter();\n                PixelFormat<IntBuffer> pf = PixelFormat.getIntArgbPreInstance();\n                if (bw < iw) {\n                    pw.setPixels(bw, 0, iw - bw, bh, pf, empty, 0, 0);\n                }\n                if (bh < ih) {\n                    pw.setPixels(0, bh, iw, ih - bh, pf, empty, 0, 0);\n                }\n            }\n        }\n        if (wimg == null) {\n            wimg = new WritableImage(bw, bh);\n        }\n        PixelWriter pw = wimg.getPixelWriter();\n        DataBufferInt db = (DataBufferInt) bimg.getRaster().getDataBuffer();\n        int[] data = db.getData();\n        int offset = bimg.getRaster().getDataBuffer().getOffset();\n        int scan = 0;\n        SampleModel sm = bimg.getRaster().getSampleModel();\n        if (sm instanceof SinglePixelPackedSampleModel) {\n            scan = ((SinglePixelPackedSampleModel) sm).getScanlineStride();\n        }\n\n        PixelFormat<IntBuffer> pf = (bimg.isAlphaPremultiplied() ?\n                PixelFormat.getIntArgbPreInstance() :\n                PixelFormat.getIntArgbInstance());\n        pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);\n        return wimg;\n    }\n\n    public static WritableImage toFXImage(BufferedImage bimg, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {\n        if (requestedWidth <= 0. || requestedHeight <= 0.) {\n            return toFXImage(bimg, null);\n        }\n\n        int width = (int) requestedWidth;\n        int height = (int) requestedHeight;\n\n        // Calculate actual dimensions if preserveRatio is true\n        if (preserveRatio) {\n            double originalWidth = bimg.getWidth();\n            double originalHeight = bimg.getHeight();\n            double scaleX = requestedWidth / originalWidth;\n            double scaleY = requestedHeight / originalHeight;\n            double scale = Math.min(scaleX, scaleY);\n\n            width = (int) (originalWidth * scale);\n            height = (int) (originalHeight * scale);\n        }\n\n        // Create scaled BufferedImage\n        BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);\n        Graphics2D g2d = scaledImage.createGraphics();\n        try {\n            if (smooth) {\n                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);\n                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);\n                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\n            }\n\n            g2d.drawImage(bimg, 0, 0, width, height, null);\n        } finally {\n            g2d.dispose();\n        }\n\n        return toFXImage(scaledImage, null);\n    }\n}\n\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/TaskCancellationAction.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;\n\nimport java.util.function.Consumer;\n\npublic final class TaskCancellationAction {\n    public static TaskCancellationAction NO_CANCEL = new TaskCancellationAction((Consumer<TaskExecutorDialogPane>) null);\n    public static TaskCancellationAction NORMAL = new TaskCancellationAction(() -> {\n    });\n\n    private final Consumer<TaskExecutorDialogPane> cancellationAction;\n\n    public TaskCancellationAction(Runnable cancellationAction) {\n        this.cancellationAction = it -> cancellationAction.run();\n    }\n\n    public TaskCancellationAction(Consumer<TaskExecutorDialogPane> cancellationAction) {\n        this.cancellationAction = cancellationAction;\n    }\n\n    public Consumer<TaskExecutorDialogPane> getCancellationAction() {\n        return cancellationAction;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n;\n\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.download.game.GameRemoteVersion;\nimport org.jackhuang.hmcl.util.i18n.translator.Translator;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.PropertyKey;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.time.temporal.TemporalAccessor;\nimport java.util.*;\n\npublic final class I18n {\n\n    private I18n() {\n    }\n\n    private static volatile SupportedLocale locale = SupportedLocale.DEFAULT;\n\n    public static void setLocale(SupportedLocale locale) {\n        I18n.locale = locale;\n    }\n\n    public static SupportedLocale getLocale() {\n        return locale;\n    }\n\n    public static boolean isUpsideDown() {\n        return LocaleUtils.getScript(locale.getDisplayLocale()).equals(\"Qabs\");\n    }\n\n    public static boolean isUseChinese() {\n        return LocaleUtils.isChinese(locale.getDisplayLocale());\n    }\n\n    public static ResourceBundle getResourceBundle() {\n        return locale.getResourceBundle();\n    }\n\n    public static Translator getTranslator() {\n        return locale.getTranslator();\n    }\n\n    public static String i18n(@PropertyKey(resourceBundle = \"assets.lang.I18N\") String key, Object... formatArgs) {\n        return locale.i18n(key, formatArgs);\n    }\n\n    public static String i18n(@PropertyKey(resourceBundle = \"assets.lang.I18N\") String key) {\n        return locale.i18n(key);\n    }\n\n    public static String formatDateTime(TemporalAccessor time) {\n        return getTranslator().formatDateTime(time);\n    }\n\n    public static String formatSpeed(long bytes) {\n        return getTranslator().formatSpeed(bytes);\n    }\n\n    public static String getDisplayVersion(RemoteVersion version) {\n        return getTranslator().getDisplayVersion(version);\n    }\n\n    public static String getDisplayVersion(GameVersionNumber version) {\n        return getTranslator().getDisplayVersion(version);\n    }\n\n    /// Find the builtin localized resource with given name and suffix.\n    ///\n    /// For example, if the current locale is `zh-CN`, when calling `getBuiltinResource(\"assets.lang.foo\", \"json\")`,\n    /// this method will look for the following built-in resources in order:\n    ///\n    ///  - `assets/lang/foo_zh_Hans_CN.json`\n    ///  - `assets/lang/foo_zh_Hans.json`\n    ///  - `assets/lang/foo_zh_CN.json`\n    ///  - `assets/lang/foo_zh.json`\n    ///  - `assets/lang/foo.json`\n    ///\n    /// This method will return the first found resource;\n    /// if none of the above resources exist, it returns `null`.\n    public static @Nullable URL getBuiltinResource(String name, String suffix) {\n        var control = DefaultResourceBundleControl.INSTANCE;\n        var classLoader = I18n.class.getClassLoader();\n        for (Locale locale : locale.getCandidateLocales()) {\n            String resourceName = control.toResourceName(control.toBundleName(name, locale), suffix);\n            URL input = classLoader.getResource(resourceName);\n            if (input != null)\n                return input;\n        }\n        return null;\n    }\n\n    /// @see [#getBuiltinResource(String, String) ]\n    public static @Nullable InputStream getBuiltinResourceAsStream(String name, String suffix) {\n        URL resource = getBuiltinResource(name, suffix);\n        try {\n            return resource != null ? resource.openStream() : null;\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n    public static String getWikiLink(GameRemoteVersion remoteVersion) {\n        return MinecraftWiki.getWikiLink(locale, remoteVersion);\n    }\n\n    public static boolean hasKey(String key) {\n        return getResourceBundle().containsKey(key);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/MinecraftWiki.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n;\n\nimport org.jackhuang.hmcl.download.game.GameRemoteVersion;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.i18n.translator.Translator_lzh;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.util.Locale;\nimport java.util.regex.Pattern;\n\npublic final class MinecraftWiki {\n\n    private static final Pattern SNAPSHOT_PATTERN = Pattern.compile(\"^[0-9]{2}w[0-9]{2}.+$\");\n\n    public static String getWikiLink(SupportedLocale locale, GameRemoteVersion version) {\n        String wikiVersion = StringUtils.removeSuffix(version.getSelfVersion(), \"_unobfuscated\");\n        var gameVersion = GameVersionNumber.asGameVersion(wikiVersion);\n\n        if (locale.getLocale().getLanguage().equals(\"lzh\")) {\n            String translatedVersion;\n            if (wikiVersion.startsWith(\"2.0\"))\n                translatedVersion = \"二點〇\";\n            else if (wikiVersion.startsWith(\"1.0.0-rc2\"))\n                translatedVersion = Translator_lzh.translateGameVersion(GameVersionNumber.asGameVersion(\"1.0.0-rc2\"));\n            else\n                translatedVersion = Translator_lzh.translateGameVersion(gameVersion);\n\n            if (translatedVersion.equals(gameVersion.toString()) || gameVersion instanceof GameVersionNumber.Old) {\n                return getWikiLink(SupportedLocale.getLocale(LocaleUtils.LOCALE_ZH_HANT), version);\n            } else if (SNAPSHOT_PATTERN.matcher(wikiVersion).matches()) {\n                return locale.i18n(\"wiki.version.game.snapshot\", translatedVersion);\n            } else {\n                return locale.i18n(\"wiki.version.game\", translatedVersion);\n            }\n        }\n\n        String variantSuffix;\n        if (LocaleUtils.isChinese(locale.getLocale())) {\n            if (!\"Hant\".equals(LocaleUtils.getScript(locale.getLocale()))) {\n                variantSuffix = \"?variant=zh-cn\";\n            } else {\n                String region = locale.getLocale().getCountry();\n                variantSuffix = region.equals(\"HK\") || region.equals(\"MO\")\n                        ? \"?variant=zh-hk\"\n                        : \"?variant=zh-tw\";\n            }\n        } else\n            variantSuffix = \"\";\n\n        replace:\n        if (gameVersion instanceof GameVersionNumber.Release) {\n            if (wikiVersion.startsWith(\"1.0\")) {\n                if (wikiVersion.equals(\"1.0\"))\n                    wikiVersion = \"1.0.0\";\n                else if (wikiVersion.startsWith(\"1.0.0-rc2\"))\n                    wikiVersion = \"1.0.0-rc2\";\n            }\n        } else if (gameVersion instanceof GameVersionNumber.LegacySnapshot) {\n            return locale.i18n(\"wiki.version.game.snapshot\", wikiVersion) + variantSuffix;\n        } else {\n            if (wikiVersion.length() >= 6 && wikiVersion.charAt(2) == 'w') {\n                // Starting from 2020, all April Fools' versions follow this pattern\n                if (SNAPSHOT_PATTERN.matcher(wikiVersion).matches()) {\n                    if (wikiVersion.equals(\"22w13oneblockatatime\"))\n                        wikiVersion = \"22w13oneBlockAtATime\";\n                    return locale.i18n(\"wiki.version.game.snapshot\", wikiVersion) + variantSuffix;\n                }\n            }\n\n            String lower = wikiVersion.toLowerCase(Locale.ROOT);\n            switch (lower) {\n                case \"0.30-1\":\n                case \"0.30-2\":\n                case \"c0.30_01c\":\n                    wikiVersion = \"Classic_0.30\";\n                    break replace;\n                case \"in-20100206-2103\":\n                    wikiVersion = \"Indev_20100206\";\n                    break replace;\n                case \"inf-20100630-1\":\n                    wikiVersion = \"Infdev_20100630\";\n                    break replace;\n                case \"inf-20100630-2\":\n                    wikiVersion = \"Alpha_v1.0.0\";\n                    break replace;\n                case \"1.19_deep_dark_experimental_snapshot-1\":\n                    wikiVersion = \"1.19-exp1\";\n                    break replace;\n                case \"in-20100130\":\n                    wikiVersion = \"Indev_0.31_20100130\";\n                    break replace;\n                case \"b1.6-tb3\":\n                    wikiVersion = \"Beta_1.6_Test_Build_3\";\n                    break replace;\n                case \"1.14_combat-212796\":\n                    wikiVersion = \"1.14.3_-_Combat_Test\";\n                    break replace;\n                case \"1.14_combat-0\":\n                    wikiVersion = \"Combat_Test_2\";\n                    break replace;\n                case \"1.14_combat-3\":\n                    wikiVersion = \"Combat_Test_3\";\n                    break replace;\n                case \"1_15_combat-1\":\n                    wikiVersion = \"Combat_Test_4\";\n                    break replace;\n                case \"1_15_combat-6\":\n                    wikiVersion = \"Combat_Test_5\";\n                    break replace;\n                case \"1_16_combat-0\":\n                    wikiVersion = \"Combat_Test_6\";\n                    break replace;\n                case \"1_16_combat-1\":\n                    wikiVersion = \"Combat_Test_7\";\n                    break replace;\n                case \"1_16_combat-2\":\n                    wikiVersion = \"Combat_Test_7b\";\n                    break replace;\n                case \"1_16_combat-3\":\n                    wikiVersion = \"Combat_Test_7c\";\n                    break replace;\n                case \"1_16_combat-4\":\n                    wikiVersion = \"Combat_Test_8\";\n                    break replace;\n                case \"1_16_combat-5\":\n                    wikiVersion = \"Combat_Test_8b\";\n                    break replace;\n                case \"1_16_combat-6\":\n                    wikiVersion = \"Combat_Test_8c\";\n                    break replace;\n            }\n\n            if (lower.startsWith(\"2.0\"))\n                wikiVersion = \"2.0\";\n            else if (lower.startsWith(\"b1.8-pre1\"))\n                wikiVersion = \"Beta_1.8-pre1\";\n            else if (lower.startsWith(\"b1.1-\"))\n                wikiVersion = \"Beta_1.1\";\n            else if (lower.startsWith(\"a1.1.0\"))\n                wikiVersion = \"Alpha_v1.1.0\";\n            else if (lower.startsWith(\"a1.0.14\"))\n                wikiVersion = \"Alpha_v1.0.14\";\n            else if (lower.startsWith(\"a1.0.13_01\"))\n                wikiVersion = \"Alpha_v1.0.13_01\";\n            else if (lower.startsWith(\"in-20100214\"))\n                wikiVersion = \"Indev_20100214\";\n            else if (lower.contains(\"experimental-snapshot\"))\n                wikiVersion = lower.replace(\"_experimental-snapshot-\", \"-exp\");\n            else if (lower.startsWith(\"inf-\"))\n                wikiVersion = lower.replace(\"inf-\", \"Infdev_\");\n            else if (lower.startsWith(\"in-\"))\n                wikiVersion = lower.replace(\"in-\", \"Indev_\");\n            else if (lower.startsWith(\"rd-\"))\n                wikiVersion = \"pre-Classic_\" + lower;\n            else if (lower.startsWith(\"b\"))\n                wikiVersion = lower.replace(\"b\", \"Beta_\");\n            else if (lower.startsWith(\"a\"))\n                wikiVersion = lower.replace(\"a\", \"Alpha_v\");\n            else if (lower.startsWith(\"c\"))\n                wikiVersion = lower\n                        .replace(\"c\", \"Classic_\")\n                        .replace(\"st\", \"SURVIVAL_TEST\");\n        }\n\n        return locale.i18n(\"wiki.version.game\", wikiVersion) + variantSuffix;\n    }\n\n    private MinecraftWiki() {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/SupportedLocale.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.i18n.translator.Translator;\nimport org.jetbrains.annotations.PropertyKey;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n@JsonAdapter(SupportedLocale.TypeAdapter.class)\npublic final class SupportedLocale {\n    public static final SupportedLocale DEFAULT = new SupportedLocale();\n\n    public static boolean isExperimentalSupported(Locale locale) {\n        return \"ar\".equals(locale.getLanguage());\n    }\n\n    private static final ConcurrentMap<Locale, SupportedLocale> LOCALES = new ConcurrentHashMap<>();\n\n    public static List<SupportedLocale> getSupportedLocales() {\n        List<SupportedLocale> list = new ArrayList<>();\n        list.add(DEFAULT);\n\n        InputStream locales = SupportedLocale.class.getResourceAsStream(\"/assets/lang/languages.json\");\n        if (locales != null) {\n            try (locales) {\n                for (SupportedLocale locale : JsonUtils.fromNonNullJsonFully(locales, JsonUtils.listTypeOf(SupportedLocale.class))) {\n                    if (!isExperimentalSupported(locale.getLocale())) {\n                        list.add(locale);\n                    }\n                }\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to load languages.json\", e);\n            }\n        }\n        return List.copyOf(list);\n    }\n\n    public static SupportedLocale getLocale(Locale locale) {\n        return LOCALES.computeIfAbsent(locale, SupportedLocale::new);\n    }\n\n    public static SupportedLocale getLocaleByName(String name) {\n        if (name == null || name.isBlank() || \"def\".equals(name) || \"default\".equals(name))\n            return DEFAULT;\n\n        return getLocale(Locale.forLanguageTag(name.trim().replace('_', '-')));\n    }\n\n    private final boolean isDefault;\n    private final String name;\n    private final Locale locale;\n    private final Locale displayLocale;\n    private final TextDirection textDirection;\n\n    private ResourceBundle resourceBundle;\n    private ResourceBundle localeNamesBundle;\n    private List<Locale> candidateLocales;\n    private Translator translator;\n\n    SupportedLocale() {\n        this.isDefault = true;\n        this.name = \"def\"; // TODO: Change to \"default\" after updating the Config format\n\n        String language = System.getenv(\"HMCL_LANGUAGE\");\n        if (StringUtils.isBlank(language)) {\n            this.locale = LocaleUtils.SYSTEM_DEFAULT;\n            this.displayLocale = isExperimentalSupported(this.locale)\n                    ? Locale.ENGLISH\n                    : this.locale;\n        } else {\n            this.locale = Locale.forLanguageTag(language);\n            this.displayLocale = this.locale;\n        }\n        this.textDirection = LocaleUtils.getTextDirection(locale);\n    }\n\n    SupportedLocale(Locale locale) {\n        this.isDefault = false;\n        this.name = locale.toLanguageTag();\n        this.locale = locale;\n        this.displayLocale = locale;\n        this.textDirection = LocaleUtils.getTextDirection(locale);\n    }\n\n    public boolean isDefault() {\n        return isDefault;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public Locale getLocale() {\n        return locale;\n    }\n\n    /// Used to represent the text display language of HMCL.\n    ///\n    /// Usually equivalent to [#getLocale()],\n    /// but for [experimentally supported languages][#isExperimentalSupported(Locale)],\n    /// it falls back to ENGLISH by default.\n    public Locale getDisplayLocale() {\n        return displayLocale;\n    }\n\n    public TextDirection getTextDirection() {\n        return textDirection;\n    }\n\n    public String getDisplayName(SupportedLocale inLocale) {\n        if (isDefault()) {\n            try {\n                return inLocale.getResourceBundle().getString(\"lang.default\");\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to get localized name for default locale\", e);\n                return \"Default\";\n            }\n        }\n\n        Locale currentLocale = this.getLocale();\n\n        String language = currentLocale.getLanguage();\n        String subLanguage;\n        {\n            String parentLanguage = LocaleUtils.getParentLanguage(language);\n            if (parentLanguage != null) {\n                subLanguage = language;\n                language = parentLanguage;\n            } else {\n                subLanguage = \"\";\n            }\n        }\n\n        ResourceBundle localeNames = inLocale.getLocaleNamesBundle();\n\n        String languageDisplayName = language;\n        try {\n            languageDisplayName = localeNames.getString(language);\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get localized name for language \" + language, e);\n        }\n\n        // Currently, HMCL does not support any locales with regions or variants, so they are not handled for now\n        List<String> subTags = Stream.of(subLanguage, currentLocale.getScript())\n                .filter(it -> !it.isEmpty())\n                .map(it -> {\n                    try {\n                        return localeNames.getString(it);\n                    } catch (Throwable e) {\n                        LOG.warning(\"Failed to get localized name of \" + it, e);\n                    }\n                    return it;\n                }).toList();\n\n        return subTags.isEmpty()\n                ? languageDisplayName\n                : languageDisplayName + \" (\" + String.join(\", \", subTags) + \")\";\n    }\n\n    public ResourceBundle getResourceBundle() {\n        ResourceBundle bundle = resourceBundle;\n        if (resourceBundle == null)\n            resourceBundle = bundle = ResourceBundle.getBundle(\"assets.lang.I18N\", displayLocale,\n                    DefaultResourceBundleControl.INSTANCE);\n\n        return bundle;\n    }\n\n    public ResourceBundle getLocaleNamesBundle() {\n        ResourceBundle bundle = localeNamesBundle;\n        if (localeNamesBundle == null)\n            localeNamesBundle = bundle = ResourceBundle.getBundle(\"assets.lang.LocaleNames\", displayLocale,\n                    DefaultResourceBundleControl.INSTANCE);\n\n        return bundle;\n    }\n\n    public List<Locale> getCandidateLocales() {\n        if (candidateLocales == null)\n            candidateLocales = List.copyOf(LocaleUtils.getCandidateLocales(displayLocale));\n        return candidateLocales;\n    }\n\n    public String i18n(@PropertyKey(resourceBundle = \"assets.lang.I18N\") String key, Object... formatArgs) {\n        try {\n            return String.format(getResourceBundle().getString(key), formatArgs);\n        } catch (MissingResourceException e) {\n            LOG.error(\"Cannot find key \" + key + \" in resource bundle\", e);\n        } catch (IllegalFormatException e) {\n            LOG.error(\"Illegal format string, key=\" + key + \", args=\" + Arrays.toString(formatArgs), e);\n        }\n\n        return key + Arrays.toString(formatArgs);\n    }\n\n    public String i18n(@PropertyKey(resourceBundle = \"assets.lang.I18N\") String key) {\n        try {\n            return getResourceBundle().getString(key);\n        } catch (MissingResourceException e) {\n            LOG.error(\"Cannot find key \" + key + \" in resource bundle\", e);\n            return key;\n        }\n    }\n\n    public String getFcMatchPattern() {\n        String language = locale.getLanguage();\n        String region = locale.getCountry();\n\n        if (LocaleUtils.isEnglish(locale))\n            return \"\";\n\n        if (LocaleUtils.isChinese(locale)) {\n            String lang;\n            String charset;\n\n            String script = LocaleUtils.getScript(locale);\n            switch (script) {\n                case \"Hans\":\n                    lang = region.equals(\"SG\") || region.equals(\"MY\")\n                            ? \"zh-\" + region\n                            : \"zh-CN\";\n                    charset = \"0x6e38,0x620f\";\n                    break;\n                case \"Hant\":\n                    lang = region.equals(\"HK\") || region.equals(\"MO\")\n                            ? \"zh-\" + region\n                            : \"zh-TW\";\n                    charset = \"0x904a,0x6232\";\n                    break;\n                default:\n                    return \"\";\n            }\n\n            return \":lang=\" + lang + \":charset=\" + charset;\n        }\n\n        return region.isEmpty() ? language : language + \"-\" + region;\n    }\n\n    public Translator getTranslator() {\n        Translator translator = this.translator;\n        if (translator != null)\n            return translator;\n\n        List<Locale> candidateLocales = getCandidateLocales();\n\n        for (Locale candidateLocale : candidateLocales) {\n            String className = DefaultResourceBundleControl.INSTANCE.toBundleName(Translator.class.getSimpleName(), candidateLocale);\n            if (Translator.class.getResource(className + \".class\") != null) {\n                try {\n                    Class<?> clazz = Class.forName(Translator.class.getPackageName() + \".\" + className);\n\n                    MethodHandle constructor = MethodHandles.publicLookup()\n                            .findConstructor(clazz, MethodType.methodType(void.class, SupportedLocale.class));\n\n                    return this.translator = (Translator) constructor.invoke(this);\n                } catch (Throwable e) {\n                    LOG.warning(\"Failed to create instance for \" + className, e);\n                }\n            }\n        }\n        return this.translator = new Translator(this);\n    }\n\n    public boolean isSameLanguage(SupportedLocale other) {\n        return LocaleUtils.getRootLanguage(this.getLocale())\n                .equals(LocaleUtils.getRootLanguage(other.getLocale()));\n    }\n\n    public static final class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {\n        @Override\n        public void write(JsonWriter out, SupportedLocale value) throws IOException {\n            out.value(value.getName());\n        }\n\n        @Override\n        public SupportedLocale read(JsonReader in) throws IOException {\n            if (in.peek() == JsonToken.NULL)\n                return DEFAULT;\n\n            String language = in.nextString();\n            return getLocaleByName(switch (language) {\n                // TODO: Remove these compatibility codes after updating the Config format\n                case \"zh_CN\" -> \"zh-Hans\"; // For compatibility\n                case \"zh\" -> \"zh-Hant\";    // For compatibility\n                default -> language;\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/translator/Translator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n.translator;\n\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.util.i18n.SupportedLocale;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.time.ZoneId;\nimport java.time.format.DateTimeFormatter;\nimport java.time.temporal.TemporalAccessor;\nimport java.util.Locale;\n\n/// @author Glavo\npublic class Translator {\n    protected final SupportedLocale supportedLocale;\n    protected final Locale displayLocale;\n\n    public Translator(SupportedLocale supportedLocale) {\n        this.supportedLocale = supportedLocale;\n        this.displayLocale = supportedLocale.getDisplayLocale();\n    }\n\n    public final SupportedLocale getSupportedLocale() {\n        return supportedLocale;\n    }\n\n    public final Locale getDisplayLocale() {\n        return displayLocale;\n    }\n\n    public String getDisplayVersion(RemoteVersion remoteVersion) {\n        return remoteVersion.getSelfVersion();\n    }\n\n    public String getDisplayVersion(GameVersionNumber versionNumber) {\n        return versionNumber.toNormalizedString();\n    }\n\n    /// @see [#formatDateTime(TemporalAccessor)]\n    protected DateTimeFormatter dateTimeFormatter;\n\n    public String formatDateTime(TemporalAccessor time) {\n        DateTimeFormatter formatter = dateTimeFormatter;\n        if (formatter == null) {\n            formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(supportedLocale.getResourceBundle().getString(\"datetime.format\"))\n                    .withZone(ZoneId.systemDefault());\n        }\n        return formatter.format(time);\n    }\n\n    public String formatSpeed(long bytes) {\n        if (bytes < 1024) {\n            return supportedLocale.i18n(\"download.speed.byte_per_second\", bytes);\n        } else if (bytes < 1024 * 1024) {\n            return supportedLocale.i18n(\"download.speed.kibibyte_per_second\", (double) bytes / 1024);\n        } else {\n            return supportedLocale.i18n(\"download.speed.megabyte_per_second\", (double) bytes / (1024 * 1024));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/translator/Translator_en_Qabs.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n.translator;\n\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.util.i18n.SupportedLocale;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.time.temporal.TemporalAccessor;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.Locale;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author Glavo\npublic class Translator_en_Qabs extends Translator {\n    private static final Map<Integer, Integer> MAPPER;\n\n    static {\n        var map = new LinkedHashMap<Integer, Integer>();\n        InputStream inputStream = Translator_en_Qabs.class.getResourceAsStream(\"/assets/lang/upside_down.txt\");\n        if (inputStream != null) {\n            try (inputStream) {\n                new String(inputStream.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> {\n                    if (line.isBlank() || line.startsWith(\"#\"))\n                        return;\n\n                    if (line.length() != 2) {\n                        LOG.warning(\"Invalid line: \" + line);\n                        return;\n                    }\n\n                    map.put((int) line.charAt(0), (int) line.charAt(1));\n                });\n            } catch (IOException e) {\n                LOG.warning(\"Failed to load upside_down.txt\", e);\n            }\n        } else {\n            LOG.warning(\"upside_down.txt not found\");\n        }\n        MAPPER = Collections.unmodifiableMap(map);\n    }\n\n    public static String translate(String str) {\n        StringBuilder builder = new StringBuilder(str.length());\n        str.codePoints().forEach(ch -> builder.appendCodePoint(MAPPER.getOrDefault(ch, ch)));\n        return builder.reverse().toString();\n    }\n\n    private final SupportedLocale originalLocale = SupportedLocale.getLocale(Locale.ENGLISH);\n\n    public Translator_en_Qabs(SupportedLocale locale) {\n        super(locale);\n    }\n\n    @Override\n    public String getDisplayVersion(RemoteVersion remoteVersion) {\n        return translate(remoteVersion.getSelfVersion());\n    }\n\n    @Override\n    public String formatDateTime(TemporalAccessor time) {\n        return translate(originalLocale.getTranslator().formatDateTime(time));\n    }\n\n    @Override\n    public String formatSpeed(long bytes) {\n        return translate(originalLocale.getTranslator().formatSpeed(bytes));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/translator/Translator_lzh.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n.translator;\n\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.download.game.GameRemoteVersion;\nimport org.jackhuang.hmcl.util.i18n.SupportedLocale;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.temporal.TemporalAccessor;\nimport java.util.Locale;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/// @author Glavo\npublic class Translator_lzh extends Translator {\n    private static final String DOT = \"點\";\n\n    private static final String[] NUMBERS = {\n            \"〇\", \"一\", \"二\", \"三\", \"四\", \"五\", \"六\", \"七\", \"八\", \"九\",\n            \"十\", \"十一\", \"十二\", \"十三\", \"十四\", \"十五\", \"十六\", \"十七\", \"十八\", \"十九\",\n            \"廿\", \"廿一\", \"廿二\", \"廿三\", \"廿四\", \"廿五\", \"廿六\", \"廿七\", \"廿八\", \"廿九\",\n            \"卅\", \"卅一\", \"卅二\", \"卅三\", \"卅四\", \"卅五\", \"卅六\", \"卅七\", \"卅八\", \"卅九\",\n            \"卌\", \"卌一\", \"卌二\", \"卌三\", \"卌四\", \"卌五\", \"卌六\", \"卌七\", \"卌八\", \"卌九\",\n            \"五十\", \"五十一\", \"五十二\", \"五十三\", \"五十四\", \"五十五\", \"五十六\", \"五十七\", \"五十八\", \"五十九\",\n            \"六十\", \"六十一\", \"六十二\", \"六十三\", \"六十四\", \"六十五\", \"六十六\", \"六十七\", \"六十八\", \"六十九\",\n    };\n\n    private static final char[] TIAN_GAN = {'甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'};\n    private static final char[] DI_ZHI = {'子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'};\n\n    private static String digitToString(char digit) {\n        return digit >= '0' && digit <= '9'\n                ? NUMBERS[digit - '0']\n                : String.valueOf(digit);\n    }\n\n    private static String numberToString(int number) {\n        return number >= 0 && number < NUMBERS.length ? NUMBERS[number] : String.valueOf(number);\n    }\n\n    private static void appendDigitByDigit(StringBuilder builder, String number) {\n        for (int i = 0; i < number.length(); i++) {\n            builder.append(digitToString(number.charAt(i)));\n        }\n    }\n\n    private static int mod(int a, int b) {\n        int r = a % b;\n        return r >= 0 ? r : r + b;\n    }\n\n    static void appendYear(StringBuilder builder, int year) {\n        int yearOffset = year - 1984;\n\n        builder.append(TIAN_GAN[mod(yearOffset, TIAN_GAN.length)]);\n        builder.append(DI_ZHI[mod(yearOffset, DI_ZHI.length)]);\n    }\n\n    static void appendHour(StringBuilder builder, int hour) {\n        builder.append(DI_ZHI[((hour + 1) % 24) / 2]);\n        builder.append(hour % 2 == 0 ? '正' : '初');\n    }\n\n    public static String translateGameVersion(GameVersionNumber gameVersion) {\n        if (gameVersion instanceof GameVersionNumber.Release release) {\n            StringBuilder builder = new StringBuilder();\n            appendDigitByDigit(builder, String.valueOf(release.getMajor()));\n            builder.append(DOT);\n            appendDigitByDigit(builder, String.valueOf(release.getMinor()));\n\n            if (release.getPatch() != 0) {\n                builder.append(DOT);\n                appendDigitByDigit(builder, String.valueOf(release.getPatch()));\n            }\n\n            switch (release.getEaType()) {\n                case GA -> {\n                    // do nothing\n                }\n                case PRE_RELEASE -> {\n                    builder.append(\"之預\");\n                    appendDigitByDigit(builder, release.getEaVersion().toString());\n                }\n                case RELEASE_CANDIDATE -> {\n                    builder.append(\"之候\");\n                    appendDigitByDigit(builder, release.getEaVersion().toString());\n                }\n                default -> {\n                    // Unsupported\n                    return gameVersion.toString();\n                }\n            }\n\n            switch (release.getAdditional()) {\n                case NONE -> {\n                }\n                case UNOBFUSCATED -> {\n                    builder.append(\"涇渭\");\n                }\n                default -> {\n                    // Unsupported\n                    return gameVersion.toString();\n                }\n            }\n\n            return builder.toString();\n        } else if (gameVersion instanceof GameVersionNumber.LegacySnapshot snapshot) {\n            StringBuilder builder = new StringBuilder();\n\n            appendDigitByDigit(builder, String.valueOf(snapshot.getYear()));\n            builder.append('週');\n            appendDigitByDigit(builder, String.valueOf(snapshot.getWeek()));\n\n            char suffix = snapshot.getSuffix();\n            if (suffix >= 'a' && (suffix - 'a') < TIAN_GAN.length)\n                builder.append(TIAN_GAN[suffix - 'a']);\n            else\n                builder.append(suffix);\n\n            if (snapshot.isUnobfuscated())\n                builder.append(\"涇渭\");\n\n            return builder.toString();\n        } else if (gameVersion instanceof GameVersionNumber.Special) {\n            String version = gameVersion.toString();\n            return switch (version.toLowerCase(Locale.ROOT)) {\n                case \"2.0\" -> \"二點〇\";\n                case \"2.0_blue\" -> \"二點〇藍\";\n                case \"2.0_red\" -> \"二點〇赤\";\n                case \"2.0_purple\" -> \"二點〇紫\";\n                case \"1.rv-pre1\" -> \"一點真視之預一\";\n                case \"3d shareware v1.34\" -> \"躍然享件一點三四\";\n                case \"13w12~\" -> \"一三週一二閏\";\n                case \"20w14infinite\", \"20w14~\", \"20w14∞\" -> \"二〇週一四宇\";\n                case \"22w13oneblockatatime\" -> \"二二週一三典\";\n                case \"23w13a_or_b\" -> \"二三週一三暨\";\n                case \"24w14potato\" -> \"二四週一四芋\";\n                case \"25w14craftmine\" -> \"二五週一四礦\";\n                default -> version;\n            };\n        } else {\n            return gameVersion.toString();\n        }\n    }\n\n    private static final Pattern GENERIC_VERSION_PATTERN =\n            Pattern.compile(\"^[0-9]+(\\\\.[0-9]+)*\");\n\n    public static String translateGenericVersion(String version) {\n        Matcher matcher = GENERIC_VERSION_PATTERN.matcher(version);\n        if (matcher.find()) {\n            String prefix = matcher.group();\n            StringBuilder builder = new StringBuilder(version.length());\n\n            for (int i = 0; i < prefix.length(); i++) {\n                char ch = prefix.charAt(i);\n                if (ch >= '0' && ch <= '9')\n                    builder.append(digitToString(ch));\n                else if (ch == '.')\n                    builder.append(DOT);\n                else\n                    builder.append(ch);\n            }\n            builder.append(version, prefix.length(), version.length());\n            return builder.toString();\n        }\n\n        return version;\n    }\n\n    public Translator_lzh(SupportedLocale locale) {\n        super(locale);\n    }\n\n    @Override\n    public String getDisplayVersion(RemoteVersion remoteVersion) {\n        if (remoteVersion instanceof GameRemoteVersion)\n            return translateGameVersion(GameVersionNumber.asGameVersion(remoteVersion.getSelfVersion()));\n        else\n            return translateGenericVersion(remoteVersion.getSelfVersion());\n    }\n\n    @Override\n    public String getDisplayVersion(GameVersionNumber versionNumber) {\n        return translateGameVersion(versionNumber);\n    }\n\n    @Override\n    public String formatDateTime(TemporalAccessor time) {\n        LocalDateTime localDateTime;\n        if (time instanceof Instant instant)\n            localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();\n        else\n            localDateTime = LocalDateTime.from(time);\n\n        StringBuilder builder = new StringBuilder(16);\n\n        appendYear(builder, localDateTime.getYear());\n        builder.append('年');\n        builder.append(numberToString(localDateTime.getMonthValue()));\n        builder.append('月');\n        builder.append(numberToString(localDateTime.getDayOfMonth()));\n        builder.append('日');\n\n        builder.append(' ');\n\n        appendHour(builder, localDateTime.getHour());\n        builder.append(numberToString(localDateTime.getMinute()));\n        builder.append('分');\n        builder.append(numberToString(localDateTime.getSecond()));\n        builder.append('秒');\n\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/url/HMCLURLStreamHandlerProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.url;\n\nimport org.jackhuang.hmcl.util.url.data.DataURLHandle;\n\nimport java.net.URLStreamHandler;\nimport java.net.spi.URLStreamHandlerProvider;\n\n/**\n * @author Glavo\n */\npublic final class HMCLURLStreamHandlerProvider extends URLStreamHandlerProvider {\n    @Override\n    public URLStreamHandler createURLStreamHandler(String protocol) {\n        switch (protocol) {\n            case \"data\":\n                return new DataURLHandle();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/url/data/DataURLConnection.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.url.data;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.net.URLConnection;\n\n/**\n * @author Glavo\n */\npublic final class DataURLConnection extends URLConnection {\n    private final DataUri dataUri;\n    private byte[] data;\n    private InputStream inputStream;\n\n    DataURLConnection(URL url, DataUri dataUri) {\n        super(url);\n        this.dataUri = dataUri;\n    }\n\n    @Override\n    public void connect() throws IOException {\n        if (!connected) {\n            connected = true;\n            data = dataUri.readBytes();\n            inputStream = new ByteArrayInputStream(this.data);\n        }\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        connect();\n        return inputStream;\n    }\n\n    @Override\n    public String getContentType() {\n        return dataUri.getMediaType();\n    }\n\n    @Override\n    public long getContentLengthLong() {\n        return data != null ? data.length : -1;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/url/data/DataURLHandle.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.url.data;\n\nimport java.io.IOException;\nimport java.net.*;\n\n/**\n * @author Glavo\n */\npublic final class DataURLHandle extends URLStreamHandler {\n    @Override\n    protected URLConnection openConnection(URL u) throws IOException {\n        try {\n            URI uri = u.toURI();\n            if (!DataUri.isDataUri(uri))\n                throw new MalformedURLException(\"URI is not a data URI: \" + u);\n\n            return new DataURLConnection(u, new DataUri(uri));\n        } catch (URISyntaxException e) {\n            throw new MalformedURLException(e.getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/java/org/jackhuang/hmcl/util/url/data/DataUri.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.url.data;\n\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.util.Base64;\n\n/**\n * @author Glavo\n */\npublic final class DataUri {\n    public static final String SCHEME = \"data\";\n\n    private static IllegalArgumentException invalidUri(URI uri) {\n        return new IllegalArgumentException(\"Invalid data URI: \" + uri);\n    }\n\n    public static boolean isDataUri(URI uri) {\n        return uri != null && SCHEME.equals(uri.getScheme());\n    }\n\n    private final @NotNull String mediaType;\n    private final @NotNull Charset charset;\n    private final boolean base64;\n    private final @NotNull String rawData;\n\n    public DataUri(URI uri) {\n        if (!uri.getScheme().equals(SCHEME)) {\n            throw new IllegalArgumentException(\"URI scheme must be \" + SCHEME);\n        }\n\n        String schemeSpecificPart = uri.getSchemeSpecificPart();\n        if (schemeSpecificPart == null)\n            throw invalidUri(uri);\n\n        int comma = schemeSpecificPart.indexOf(',');\n        if (comma < 0)\n            throw invalidUri(uri);\n\n        String mediaType = schemeSpecificPart.substring(0, comma);\n        boolean base64 = mediaType.endsWith(\";base64\");\n        if (base64)\n            mediaType = mediaType.substring(0, mediaType.length() - \";base64\".length());\n\n        this.mediaType = mediaType.trim();\n        this.charset = NetworkUtils.getCharsetFromContentType(mediaType);\n        this.base64 = base64;\n        this.rawData = schemeSpecificPart.substring(comma + 1);\n    }\n\n    public @NotNull String getMediaType() {\n        return mediaType;\n    }\n\n    public @NotNull Charset getCharset() {\n        return charset;\n    }\n\n    public boolean isBase64() {\n        return base64;\n    }\n\n    public @NotNull String getRawData() {\n        return rawData;\n    }\n\n    public byte[] readBytes() throws IOException {\n        if (base64) {\n            try {\n                return Base64.getDecoder().decode(rawData);\n            } catch (IllegalArgumentException e) {\n                throw new IOException(e);\n            }\n        } else {\n            return rawData.getBytes(charset);\n        }\n    }\n\n    public String readString() throws IOException {\n        if (base64) {\n            try {\n                return new String(Base64.getDecoder().decode(rawData), charset);\n            } catch (IllegalArgumentException e) {\n                throw new IOException(e);\n            }\n        } else {\n            return rawData;\n        }\n    }\n\n    @Override\n    public String toString() {\n        return String.format(\"DataUri{mediaType='%s', charset=%s, base64=%s, body='%s'}\", mediaType, charset, base64, rawData);\n    }\n}\n"
  },
  {
    "path": "HMCL/src/main/resources/META-INF/services/java.net.spi.URLStreamHandlerProvider",
    "content": "org.jackhuang.hmcl.util.url.HMCLURLStreamHandlerProvider"
  },
  {
    "path": "HMCL/src/main/resources/assets/HMCLauncher.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\n# Switch message language\nif [ -z \"${LANG##zh_*}\" ]; then\n  _HMCL_USE_CHINESE=true\nelse\n  _HMCL_USE_CHINESE=false\nfi\n\n# _HMCL_OS\ncase \"$OSTYPE\" in\n  linux*)\n    _HMCL_OS=\"linux\";;\n  darwin*)\n    _HMCL_OS=\"macos\";;\n  freebsd*)\n    _HMCL_OS=\"freebsd\";;\n  msys*|cygwin*)\n    _HMCL_OS=\"windows\";;\n  *)\n    _HMCL_OS=\"unknown\";;\nesac\n\n# Normalize _HMCL_ARCH\ncase \"$(uname -m)\" in\n  x86_64|x86-64|amd64|em64t|x64)\n    _HMCL_ARCH=\"x86_64\";;\n  x86_32|x86-32|x86|ia32|i386|i486|i586|i686|i86pc|x32)\n    _HMCL_ARCH=\"x86\";;\n  arm64|aarch64|armv8*|armv9*)\n    _HMCL_ARCH=\"arm64\";;\n  arm|arm32|aarch32|armv7*)\n    _HMCL_ARCH=\"arm32\";;\n  riscv64)\n    _HMCL_ARCH=\"riscv64\";;\n  loongarch64)\n    _HMCL_ARCH=\"loongarch64\";;\n  mips64)\n    _HMCL_ARCH=\"mips64el\";;\n  *)\n    _HMCL_ARCH=\"unknown\";;\nesac\n\n# Self path\n_HMCL_PATH=\"${BASH_SOURCE[0]}\"\n_HMCL_DIR=$(dirname \"$_HMCL_PATH\")\n\nif [ \"$_HMCL_OS\" == \"windows\" ]; then\n  _HMCL_JAVA_EXE_NAME=\"java.exe\"\nelse\n  _HMCL_JAVA_EXE_NAME=\"java\"\nfi\n\n# _HMCL_VM_OPTIONS\nif [ -n \"${HMCL_JAVA_OPTS+x}\" ]; then\n  _HMCL_VM_OPTIONS=${HMCL_JAVA_OPTS}\nelse\n  _HMCL_VM_OPTIONS=\"-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15\"\nfi\n\nfunction show_warning_console() {\n    echo -e \"\\033[1;31m$1\\033[0m\" >&2\n}\n\nfunction show_warning_dialog() {\n  if [ -n \"$3\" ]; then\n    if [ -n \"$(command -v xdg-open)\" ]; then\n      if [ -n \"$(command -v zenity)\" ]; then\n        zenity --question --title=\"$1\" --text=\"$2\" && xdg-open \"$3\"\n      elif [ -n \"$(command -v kdialog)\" ]; then\n        kdialog --title \"$1\" --warningyesno \"$2\" && xdg-open \"$3\"\n      fi\n    fi\n  else\n    if [ -n \"$(command -v zenity)\" ]; then\n      zenity --info --title=\"$1\" --text=\"$2\"\n    elif [ -n \"$(command -v kdialog)\" ]; then\n      kdialog --title \"$1\" --msgbox \"$2\"\n    fi\n  fi\n}\n\nfunction show_warning() {\n    show_warning_console \"$1: $2\"\n    show_warning_dialog \"$1\" \"$2\"\n}\n\n# First, find Java in HMCL_JAVA_HOME\nif [ -n \"${HMCL_JAVA_HOME+x}\" ]; then\n  if [ -x \"$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME\" ]; then\n    exec \"$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME\" $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\n  else\n    if [ \"$_HMCL_USE_CHINESE\" == true ]; then\n      show_warning \"错误\" \"环境变量 HMCL_JAVA_HOME 的值无效，请设置为合法的 Java 路径。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\"\n    else\n      show_warning \"Error\" \"The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path.\\nYou can visit the https://docs.hmcl.net/help.html page for help.\"\n    fi\n    exit 1\n  fi\nfi\n\n# Find Java in HMCL_DIR\ncase \"$_HMCL_ARCH\" in\n  x86_64)\n    if [ -x \"$_HMCL_DIR/jre-x64/bin/$_HMCL_JAVA_EXE_NAME\" ]; then\n      exec \"$_HMCL_DIR/jre-x64/bin/$_HMCL_JAVA_EXE_NAME\" $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\n    fi\n    if [ -x \"$_HMCL_DIR/jre-x86/bin/$_HMCL_JAVA_EXE_NAME\" ]; then\n      exec \"$_HMCL_DIR/jre-x86/bin/$_HMCL_JAVA_EXE_NAME\" $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\n    fi\n    ;;\n  x86)\n    if [ -x \"$_HMCL_DIR/jre-x86/bin/$_HMCL_JAVA_EXE_NAME\" ]; then\n      exec \"$_HMCL_DIR/jre-x86/bin/$_HMCL_JAVA_EXE_NAME\" $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\n    fi\n    ;;\n  arm64)\n    if [ -x \"$_HMCL_DIR/jre-arm64/bin/$_HMCL_JAVA_EXE_NAME\" ]; then\n      exec \"$_HMCL_DIR/jre-arm64/bin/$_HMCL_JAVA_EXE_NAME\" $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\n    fi\n    ;;\n  arm32)\n    if [ -x \"$_HMCL_DIR/jre-arm32/bin/$_HMCL_JAVA_EXE_NAME\" ]; then\n      exec \"$_HMCL_DIR/jre-arm32/bin/$_HMCL_JAVA_EXE_NAME\" $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\n    fi\n    ;;\n  riscv64)\n    if [ -x \"$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME\" ]; then\n      exec \"$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME\" $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\n    fi\n    ;;\n  loongarch64)\n    if [ -x \"$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME\" ]; then\n      exec \"$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME\" $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\n    fi\n    ;;\nesac\n\n# Find Java in JAVA_HOME\nif [ -f \"$JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME\" ]; then\n  exec \"$JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME\" $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\nfi\n\n# Find Java in PATH\nif [ -x \"$(command -v $_HMCL_JAVA_EXE_NAME)\" ]; then\n  exec $_HMCL_JAVA_EXE_NAME $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\nfi\n\nif [ \"$_HMCL_OS\" == \"freebsd\" ] && [ -x \"$(command -v javavm)\" ]; then\n  exec javavm $_HMCL_VM_OPTIONS -jar \"$_HMCL_PATH\"\nfi\n\n# Java not found\n\ncase \"$_HMCL_OS-$_HMCL_ARCH\" in\n  windows-x86|windows-x86_64|windows-arm64)\n    _HMCL_JAVA_DOWNLOAD_PAGE=\"https://docs.hmcl.net/downloads/windows/$_HMCL_ARCH.html\"\n    ;;\n  linux-x86|linux-x86_64|linux-arm64|linux-arm32|linux-riscv64|linux-loongarch64)\n    _HMCL_JAVA_DOWNLOAD_PAGE=\"https://docs.hmcl.net/downloads/linux/$_HMCL_ARCH.html\"\n    ;;\n  macos-x86_64|macos-arm64)\n    _HMCL_JAVA_DOWNLOAD_PAGE=\"https://docs.hmcl.net/downloads/macos/$_HMCL_ARCH.html\"\n    ;;\nesac\n\nif [ \"$_HMCL_USE_CHINESE\" == true ]; then\n  _HMCL_WARNING_MESSAGE=\"运行 HMCL 需要 Java 运行时环境，请安装 Java 并设置环境变量后重试。\"\n\n  if [ -n \"$_HMCL_JAVA_DOWNLOAD_PAGE\" ]; then\n    show_warning_console \"错误: $_HMCL_WARNING_MESSAGE\\n你可以前往此处下载:\\n$_HMCL_JAVA_DOWNLOAD_PAGE\"\n    show_warning_dialog  \"错误\" \"$_HMCL_WARNING_MESSAGE\\n\\n是否要前往 Java 下载页面？\" \"$_HMCL_JAVA_DOWNLOAD_PAGE\"\n  else\n    show_warning \"错误\" \"$_HMCL_WARNING_MESSAGE\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\"\n  fi\nelse\n  _HMCL_WARNING_MESSAGE=\"The Java runtime environment is required to run HMCL.\\nPlease install Java and set the environment variables and try again.\"\n  if [ -n \"$_HMCL_JAVA_DOWNLOAD_PAGE\" ]; then\n    show_warning_console \"Error: $_HMCL_WARNING_MESSAGE\\nYou can download it from here:\\n$_HMCL_JAVA_DOWNLOAD_PAGE\"\n    show_warning_dialog  \"Error\" \"$_HMCL_WARNING_MESSAGE\\n\\nDo you want to go to the Java download page?\" \"$_HMCL_JAVA_DOWNLOAD_PAGE\"\n  else\n    show_warning \"Error\" \"$_HMCL_WARNING_MESSAGE\\nYou can visit the https://docs.hmcl.net/help.html page for help.\"\n  fi\nfi\n\nexit 1\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/about/deps.json",
    "content": "[\n  {\n    \"title\" : \"OpenJFX\",\n    \"subtitle\" : \"Copyright © 2013, 2024, Oracle and/or its affiliates.\\nLicensed under the GPL 2 with Classpath Exception.\",\n    \"externalLink\" : \"https://openjfx.io\"\n  },\n  {\n    \"title\" : \"JFoenix\",\n    \"subtitle\" : \"Copyright © 2016 JFoenix.\\nLicensed under the MIT License.\",\n    \"externalLink\" : \"https://github.com/sshahine/JFoenix\"\n  },\n  {\n    \"title\" : \"Gson\",\n    \"subtitle\" : \"Copyright © 2008 Google Inc.\\nLicensed under the Apache 2.0 License.\",\n    \"externalLink\" : \"https://github.com/google/gson\"\n  },\n  {\n    \"title\" : \"Apache Commons Compress\",\n    \"subtitle\" : \"Licensed under the Apache 2.0 License.\",\n    \"externalLink\" : \"https://commons.apache.org/proper/commons-compress/\"\n  },\n  {\n    \"title\" : \"XZ for Java\",\n    \"subtitle\" : \"Lasse Collin, Igor Pavlov, and/or Brett Okken.\\nPublic Domain.\",\n    \"externalLink\" : \"https://tukaani.org/xz/java.html\"\n  },\n  {\n    \"title\" : \"FX Gson\",\n    \"subtitle\" : \"Copyright © 2016 Joffrey Bion.\\nLicensed under the MIT License.\",\n    \"externalLink\" : \"https://github.com/joffrey-bion/fx-gson\"\n  },\n  {\n    \"title\" : \"jsoup\",\n    \"subtitle\" : \"Copyright © 2009 - 2025 Jonathan Hedley (https://jsoup.org/)\\nLicensed under the MIT License.\",\n    \"externalLink\" : \"https://jsoup.org/\"\n  },\n  {\n    \"title\" : \"NanoHTTPD\",\n    \"subtitle\" : \"Copyright © 2012 - 2015 nanohttpd.\\nLicensed under the BSD 3-clause License.\",\n    \"externalLink\" : \"https://github.com/NanoHttpd/nanohttpd\"\n  },\n  {\n    \"title\" : \"Constant Pool Scanner\",\n    \"subtitle\" : \"Copyright © 1997-2010 Oracle and/or its affiliates.\\nLicensed under the GPL 2 or the CDDL.\",\n    \"externalLink\" : \"https://github.com/jenkinsci/constant-pool-scanner\"\n  },\n  {\n    \"title\" : \"OpenNBT\",\n    \"subtitle\" : \"Copyright © 2013-2021 Steveice10.\\nLicensed under the MIT License.\",\n    \"externalLink\" : \"https://github.com/GeyserMC/OpenNBT\"\n  },\n  {\n    \"title\" : \"minecraft-jfx-skin\",\n    \"subtitle\" : \"Copyright © 2016 InfinityStudio.\\nLicensed under the GPL 3.\",\n    \"externalLink\" : \"https://github.com/InfinityStudio/minecraft-jfx-skin\"\n  },\n  {\n    \"title\" : \"SimplePNG\",\n    \"subtitle\" : \"Copyright © 2023 Glavo.\\nLicensed under the Apache 2.0 License.\",\n    \"externalLink\" : \"https://github.com/Glavo/SimplePNG\"\n  },\n  {\n    \"title\" : \"Java Native Access\",\n    \"subtitle\" : \"Licensed under the LGPL 2.1 License or the Apache 2.0 License.\",\n    \"externalLink\" : \"https://github.com/java-native-access/jna\"\n  },\n  {\n    \"title\" : \"Java Animated PNG\",\n    \"subtitle\" : \"Copyright © 2015 Andrew Ellerton.\\nLicensed under the Apache 2.0 License.\",\n    \"externalLink\" : \"https://github.com/aellerton/japng\"\n  },\n  {\n    \"title\": \"Terracotta\",\n    \"subtitle\": \"Copyright © 2025 Burning_TNT.\\nLicensed under the AGPL 3.0 License.\",\n    \"externalLink\": \"https://github.com/burningtnt/Terracotta\"\n  },\n  {\n    \"title\": \"EasyTier\",\n    \"subtitle\": \"Copyright © 2024-present Easytier Programme within The Commons Conservancy.\\nLicensed under the LGPL 3.0 License.\",\n    \"externalLink\": \"https://github.com/EasyTier/EasyTier\"\n  },\n  {\n    \"title\": \"MonetFX\",\n    \"subtitle\": \"Copyright © 2025 Glavo.\\nLicensed under the Apache 2.0 License.\",\n    \"externalLink\": \"https://github.com/Glavo/MonetFX\"\n  }\n]"
  },
  {
    "path": "HMCL/src/main/resources/assets/about/thanks.json",
    "content": "[\n  {\n    \"image\" : \"/assets/img/yushijinhun.png\",\n    \"title\" : \"yushijinhun\",\n    \"subtitleLocalized\" : \"about.thanks_to.yushijinhun.statement\",\n    \"externalLink\" : \"https://github.com/yushijinhun\"\n  },\n  {\n    \"image\" : \"/assets/img/bangbang93.png\",\n    \"title\" : \"bangbang93\",\n    \"subtitleLocalized\" : \"about.thanks_to.bangbang93.statement\",\n    \"externalLink\" : \"https://www.bangbang93.com\"\n  },\n  {\n    \"image\" : \"/assets/img/glavo.png\",\n    \"title\" : \"Glavo\",\n    \"subtitleLocalized\" : \"about.thanks_to.glavo.statement\",\n    \"externalLink\" : \"https://github.com/Glavo\"\n  },\n  {\n    \"image\" : \"/assets/img/zekerzhayard.png\",\n    \"title\" : \"ZekerZhayard\",\n    \"subtitleLocalized\" : \"about.thanks_to.zekerzhayard.statement\",\n    \"externalLink\" : \"https://github.com/ZekerZhayard\"\n  },\n  {\n    \"image\" : \"/assets/img/zkitefly.png\",\n    \"title\" : \"Zkitefly\",\n    \"subtitleLocalized\" : \"about.thanks_to.zkitefly.statement\",\n    \"externalLink\" : \"https://github.com/zkitefly\"\n  },\n  {\n    \"image\" : \"/assets/img/burningtnt.png\",\n    \"title\" : \"Burning_TNT\",\n    \"subtitleLocalized\" : \"about.thanks_to.burningtnt.statement\",\n    \"externalLink\" : \"https://github.com/burningtnt\"\n  },\n  {\n    \"image\" : \"/assets/img/ShulkerSakura.png\",\n    \"title\" : \"ShulkerSakura\",\n    \"subtitleLocalized\" : \"about.thanks_to.shulkersakura.statement\",\n    \"externalLink\" : \"https://github.com/ShulkerSakura\"\n  },\n  {\n    \"image\" : \"/assets/img/gamerteam.png\",\n    \"title\" : \"gamerteam\",\n    \"subtitleLocalized\" : \"about.thanks_to.gamerteam.statement\",\n    \"externalLink\" : \"https://github.com/ZhaiSoul\"\n  },\n  {\n    \"image\" : \"/assets/img/red_lnn.png\",\n    \"title\" : \"Red_lnn\",\n    \"subtitleLocalized\" : \"about.thanks_to.red_lnn.statement\",\n    \"externalLink\" : \"https://redlnn.top\"\n  },\n  {\n    \"image\" : \"/assets/img/mcmod.png\",\n    \"titleLocalized\" : \"about.thanks_to.mcmod\",\n    \"subtitleLocalized\" : \"about.thanks_to.mcmod.statement\",\n    \"externalLink\" : \"https://www.mcmod.cn\"\n  },\n  {\n    \"image\" : \"/assets/img/chest.png\",\n    \"titleLocalized\" : \"about.thanks_to.mcbbs\",\n    \"subtitleLocalized\" : \"about.thanks_to.mcbbs.statement\"\n  },\n  {\n    \"image\" : \"/assets/img/mcim.png\",\n    \"titleLocalized\" : \"about.thanks_to.mcim\",\n    \"subtitleLocalized\" : \"about.thanks_to.mcim.statement\",\n    \"externalLink\" : \"https://github.com/mcmod-info-mirror\"\n  },\n  {\n    \"image\" : {\n      \"light\" : \"/assets/img/github.png\",\n      \"dark\" : \"/assets/img/github-white.png\"\n    },\n    \"titleLocalized\" : \"about.thanks_to.contributors\",\n    \"subtitleLocalized\" : \"about.thanks_to.contributors.statement\",\n    \"externalLink\" : \"https://github.com/HMCL-dev/HMCL/graphs/contributors\"\n  },\n  {\n    \"image\" : \"/assets/img/icon.png\",\n    \"titleLocalized\" : \"about.thanks_to.users\",\n    \"subtitleLocalized\" : \"about.thanks_to.users.statement\",\n    \"externalLink\" : \"https://docs.hmcl.net/groups.html\"\n  }\n]"
  },
  {
    "path": "HMCL/src/main/resources/assets/css/blue.css",
    "content": "* {\n    -monet-primary: #4352A5;\n    -monet-on-primary: #FFFFFF;\n    -monet-primary-container: #5C6BC0;\n    -monet-on-primary-container: #F8F6FF;\n    -monet-primary-fixed: #DEE0FF;\n    -monet-primary-fixed-dim: #BAC3FF;\n    -monet-on-primary-fixed: #00105B;\n    -monet-on-primary-fixed-variant: #2F3F92;\n    -monet-secondary: #575C7F;\n    -monet-on-secondary: #FFFFFF;\n    -monet-secondary-container: #D0D5FD;\n    -monet-on-secondary-container: #565B7D;\n    -monet-secondary-fixed: #DEE0FF;\n    -monet-secondary-fixed-dim: #BFC4EC;\n    -monet-on-secondary-fixed: #141938;\n    -monet-on-secondary-fixed-variant: #3F4566;\n    -monet-tertiary: #775200;\n    -monet-on-tertiary: #FFFFFF;\n    -monet-tertiary-container: #976900;\n    -monet-on-tertiary-container: #FFF6EE;\n    -monet-tertiary-fixed: #FFDEAC;\n    -monet-tertiary-fixed-dim: #F6BD58;\n    -monet-on-tertiary-fixed: #281900;\n    -monet-on-tertiary-fixed-variant: #5F4100;\n    -monet-error: #BA1A1A;\n    -monet-on-error: #FFFFFF;\n    -monet-error-container: #FFDAD6;\n    -monet-on-error-container: #93000A;\n    -monet-surface: #FBF8FF;\n    -monet-on-surface: #1B1B21;\n    -monet-surface-dim: #DBD9E1;\n    -monet-surface-bright: #FBF8FF;\n    -monet-surface-container-lowest: #FFFFFF;\n    -monet-surface-container-low: #F5F2FA;\n    -monet-surface-container: #EFEDF5;\n    -monet-surface-container-high: #E9E7EF;\n    -monet-surface-container-highest: #E3E1E9;\n    -monet-surface-variant: #E2E1EF;\n    -monet-on-surface-variant: #454651;\n    -monet-background: #FBF8FF;\n    -monet-on-background: #1B1B21;\n    -monet-outline: #767683;\n    -monet-outline-variant: #C6C5D3;\n    -monet-shadow: #000000;\n    -monet-scrim: #000000;\n    -monet-inverse-surface: #303036;\n    -monet-inverse-on-surface: #F2EFF7;\n    -monet-inverse-primary: #BAC3FF;\n    -monet-surface-tint: #4858AB;\n    -monet-primary-seed: #5C6BC0;\n    -monet-primary-transparent-50: #4352A580;\n    -monet-secondary-container-transparent-50: #D0D5FD80;\n    -monet-surface-transparent-50: #FBF8FF80;\n    -monet-surface-transparent-80: #FBF8FFCC;\n    -monet-on-surface-variant-transparent-38: #45465161;\n    -monet-surface-container-low-transparent-80: #F5F2FACC;\n    -monet-secondary-container-transparent-80: #D0D5FDCC;\n    -monet-inverse-surface-transparent-80: #303036CC;\n}"
  },
  {
    "path": "HMCL/src/main/resources/assets/css/brightness-dark.css",
    "content": ".hint {\n    -fx-hyperlink-fill: -monet-inverse-primary;\n}\n\n.two-line-list-item {\n    -fixed-warning-tag-background: #2c0b0e;\n}\n\n.log-window {\n    -fixed-log-toggle-selected: #DEE2E6;\n    -fixed-log-toggle-unselected: #6C757D;\n    -fixed-log-text-fill: #FFFFFF;\n    -fixed-log-trace: #495057;\n    -fixed-log-debug: #343A40;\n    -fixed-log-info: #212529;\n    -fixed-log-warn: #331904;\n    -fixed-log-error: #58151C;\n    -fixed-log-fatal: #842029;\n    -fixed-log-selected: #6C757D;\n}\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/css/brightness-light.css",
    "content": ".hint {\n    -fx-hyperlink-fill: -monet-primary;\n}\n\n.two-line-list-item {\n    -fixed-warning-tag-background: #F1AEB5;\n}\n\n.log-window {\n    -fixed-log-toggle-selected: #000000;\n    -fixed-log-toggle-unselected: #6C757D;\n    -fixed-log-text-fill: #000000;\n    -fixed-log-trace: #EEE9E0;\n    -fixed-log-debug: #EEE9E0;\n    -fixed-log-info: #FFFFFF;\n    -fixed-log-warn: #FFEECC;\n    -fixed-log-error: #FFCCBB;\n    -fixed-log-fatal: #F7A699;\n    -fixed-log-selected: #C4C4C4;\n}\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/css/font.css",
    "content": "/**\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n.root {\n    /* https://github.com/HMCL-dev/HMCL/pull/3423 */\n    -fx-font-family: -fx-base-font-family;\n}"
  },
  {
    "path": "HMCL/src/main/resources/assets/css/root.css",
    "content": "/**\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n\n.root {\n}\n\n.svg {\n    -fx-fill: -monet-on-surface;\n}\n\n.label {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.scroll-bar .thumb {\n    -fx-fill: -monet-surface-tint;\n    -fx-arc-width: 8;\n    -fx-arc-height: 8;\n}\n\n.title-label {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.subtitle-label {\n    -fx-text-fill: -monet-on-surface-variant;\n}\n\n.hyperlink {\n    -fx-text-fill: -monet-primary;\n}\n\n.hyperlink:focused {\n    -fx-border-color: -monet-primary;\n}\n\n.hint {\n    -fx-background-radius: 5;\n    -fx-border-width: 1;\n    -fx-border-radius: 5;\n    -fx-padding: 6;\n}\n\n.hint .hyperlink {\n    -fx-fill: -monet-primary;\n    -fx-text-fill: -monet-primary;\n}\n\n.hint.info {\n    -fx-background-color: -monet-primary-fixed-dim;\n    -fx-border-color: -monet-outline;\n}\n\n.hint.info Text, .hint.info .svg {\n    -fx-fill: -monet-on-primary-fixed;\n}\n\n.hint.success {\n    -fx-background-color: #d1e7dd;\n    -fx-border-color: -monet-outline;\n}\n\n.hint.success Text, .hint.success .svg {\n    -fx-fill: #0a3622;\n}\n\n.hint.error {\n    -fx-background-color: -monet-error-container;\n    -fx-border-color: -monet-outline;\n}\n\n.hint.error Text, .hint.error .svg {\n    -fx-fill: -monet-on-error-container;\n}\n\n.hint.warning {\n    -fx-background-color: -monet-tertiary-fixed-dim;\n    -fx-border-color: -monet-outline;\n}\n\n.hint.warning Text, .hint.warning .svg {\n    -fx-fill: -monet-on-tertiary-fixed;\n}\n\n.hint.warning .hyperlink {\n    -fx-fill: -fx-hyperlink-fill;\n    -fx-text-fill: -fx-hyperlink-fill;\n}\n\n.skin-pane .jfx-text-field {\n    -fx-pref-width: 200;\n}\n\n.bold {\n    -fx-font-weight: bold;\n}\n\n.memory-label {\n}\n\n.memory-used {\n    -fx-background-color: -monet-primary;\n}\n\n.memory-used:disabled {\n    -fx-opacity: 0.4;\n}\n\n.memory-allocate {\n    -fx-background-color: -monet-primary-transparent-50;\n}\n\n.memory-allocate:disabled {\n    -fx-opacity: 0.4;\n}\n\n.memory-total {\n    -fx-background-color: -monet-surface-container;\n}\n\n.memory-total:disabled {\n    -fx-opacity: 0.4;\n}\n\n.update-label {\n    -fx-text-fill: -monet-tertiary;\n}\n\n.radio-button-title-label {\n    -fx-font-size: 16px;\n    -fx-padding: 14.0 0.0 -20.0 0.0;\n    -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87);\n}\n\n.announcement {\n    -fx-padding: 16;\n}\n\n.announcement .title {\n    -fx-text-fill: -monet-on-surface;\n    -fx-font-size: 14px;\n    -fx-font-weight: BOLD;\n}\n\n.announcement Text {\n    -fx-fill: -monet-on-surface;\n    -fx-font-size: 13px;\n}\n\n.announcement .hyperlink {\n    -fx-fill: -monet-primary;\n    -fx-font-size: 13px;\n}\n\n.rippler-container {\n    -jfx-rippler-fill: -monet-on-surface-variant;\n}\n\n.advanced-list-item .rippler-container {\n    -jfx-rippler-fill: -monet-on-secondary-container;\n}\n\n.advanced-list-item > .rippler-container > .container {\n    -fx-padding: 10 16 10 16;\n    -fx-background-color: null;\n}\n\n.advanced-list-item > .rippler-container > .container > .two-line-list-item {\n    -fx-alignment: center-left;\n    -fx-padding: 0 0 0 10;\n}\n\n.advanced-list-item > .rippler-container > .container > .two-line-list-item > .first-line > .title {\n    -fx-font-size: 13;\n    -fx-text-alignment: justify;\n}\n\n.advanced-list-item > .rippler-container > .container > .two-line-list-item > HBox > .subtitle {\n    -fx-font-size: 10;\n    -fx-text-alignment: justify;\n}\n\n.advanced-list-item:selected > .rippler-container > .container {\n    -fx-background-color: -monet-secondary-container-transparent-50;\n}\n\n.advanced-list-item:selected > .rippler-container > .container > .two-line-list-item > .first-line > .title {\n    -fx-text-fill: -monet-on-secondary-container;\n    -fx-font-weight: bold;\n}\n\n.advanced-list-item:selected .svg {\n    -fx-fill: -monet-on-secondary-container;\n}\n\n.navigation-drawer-item > .rippler-container > .container > VBox {\n    -fx-padding: 0 0 0 0;\n}\n\n.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title {\n    -fx-font-size: 13;\n}\n\n.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > HBox > .subtitle {\n    -fx-font-size: 10;\n}\n\n.profile-list-item > .rippler-container > BorderPane {\n    -fx-padding: 8 0 8 16;\n}\n\n.profile-list-item:selected > .rippler-container > BorderPane {\n    -fx-background-color: -monet-secondary-container-transparent-50;\n}\n\n.profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title {\n    -fx-text-fill: -monet-on-secondary-container;\n    -fx-font-weight: bold;\n}\n\n.profile-list-item:selected .svg {\n    -fx-fill: -monet-on-secondary-container;\n}\n\n.notice-pane > .label {\n    -fx-text-fill: -monet-on-surface;\n    -fx-font-size: 20;\n    -fx-wrap-text: true;\n    -fx-text-alignment: CENTER;\n}\n\n.class-title {\n    -fx-font-size: 12px;\n    -fx-padding: 8 16 8 16;\n}\n\n.class-title Text {\n    -fx-fill: -monet-on-surface;\n}\n\n.class-title Rectangle {\n    -fx-fill: -monet-on-surface-variant;\n}\n\n.advanced-list-box-item {\n}\n\n.advanced-list-box-content {\n    -fx-padding: 12 0 0 0;\n    -fx-spacing: 0;\n}\n\n.no-padding .advanced-list-box-content {\n    -fx-padding: 0 0 0 0;\n}\n\n.iconed-item {\n    -jfx-rippler-fill: -monet-secondary-container;\n}\n\n.iconed-item .iconed-item-container {\n    -fx-padding: 10 16 10 16;\n    -fx-spacing: 10;\n    -fx-font-size: 14;\n    -fx-alignment: CENTER_LEFT;\n}\n\n.iconed-menu-item {\n    -jfx-rippler-fill: -monet-secondary-container;\n}\n\n.iconed-menu-item .iconed-item-container {\n    -fx-padding: 8 16 8 16;\n    -fx-spacing: 10;\n    -fx-font-size: 12;\n    -fx-alignment: CENTER_LEFT;\n}\n\n.menu-container {\n    -fx-padding: 8 16 8 16;\n}\n\n.popup-menu .scroll-bar .thumb {\n    -fx-fill: -monet-surface-tint;\n}\n\n.popup-menu .scroll-pane > .scroll-bar:vertical,\n.popup-menu .scroll-pane > .scroll-bar:horizontal {\n    -fx-opacity: 0;\n}\n\n.popup-menu-content {\n    -fx-padding: 4 0 4 0;\n}\n\n.two-line-list-item > .first-line > .title {\n    -fx-text-fill: -monet-on-surface;\n    -fx-font-size: 15px;\n    -fx-padding: 0 8 0 0;\n}\n\n.two-line-list-item > HBox > .subtitle {\n    -fx-text-fill: -monet-on-surface-variant;\n    -fx-font-weight: normal;\n    -fx-font-size: 12px;\n}\n\n.two-line-list-item > .second-line > .subtitle Text {\n    -fx-fill: -monet-on-surface-variant;\n}\n\n.two-line-list-item > .first-line .scroll-bar {\n    -fx-opacity: 0;\n    -fx-max-height: 0;\n    -fx-min-height: 0;\n    -fx-pref-height: 0;\n}\n\n.two-line-list-item > .first-line .tag {\n    -fx-text-fill: -monet-on-secondary-container;\n    -fx-background-color: -monet-secondary-container;\n    -fx-background-radius: 2;\n    -fx-padding: 2;\n    -fx-font-weight: normal;\n    -fx-font-size: 12px;\n}\n\n.two-line-list-item > .first-line .tag:warning {\n    -fx-text-fill: -monet-on-error-container;\n    -fx-background-color: -fixed-warning-tag-background;\n}\n\n.two-line-item-second-large {\n\n}\n\n.two-line-item-second-large > .first-line > .title, .two-line-item-second-large-title {\n    -fx-text-fill: -monet-on-surface-variant;\n    -fx-font-weight: normal;\n    -fx-font-size: 12px;\n}\n\n.two-line-item-second-large > HBox > .subtitle {\n    -fx-text-fill: -monet-on-surface;\n    -fx-font-size: 15px;\n}\n\n.terracotta-hint Text {\n    -fx-fill: -monet-on-surface;\n}\n\n.game-crash-window {\n    -fx-background-color: -monet-surface-container;\n}\n\n.game-crash-window .crash-reason-text-flow Text {\n    -fx-fill: -monet-on-surface;\n}\n\n.game-crash-window .crash-reason-text-flow .hyperlink {\n    -fx-fill: -monet-primary;\n}\n\n.wrap-text > HBox > .subtitle {\n    -fx-wrap-text: true;\n}\n\n.bubble {\n    -fx-background-color: -monet-inverse-surface-transparent-80;\n    -fx-background-radius: 2px;\n}\n\n.bubble > HBox > .two-line-list-item > .first-line > .title,\n.bubble > HBox > .two-line-list-item > HBox > .subtitle {\n    -fx-text-fill: -monet-inverse-on-surface;\n}\n\n.bubble .svg {\n    -fx-fill: -monet-inverse-on-surface;\n}\n\n.sponsor-pane {\n    -fx-cursor: hand;\n}\n\n.installer-item-wrapper {\n    -fx-background-color: -monet-surface;\n    -fx-background-radius: 4;\n    -fx-pref-width: 180px;\n}\n\n.installer-item-wrapper:card {\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);\n}\n\n.installer-item-wrapper .installer-item:list-item {\n    -fx-padding: 8px;\n    -fx-border-color: -monet-outline-variant;\n    -fx-border-width: 0 0 1 0;\n    -fx-alignment: center-left;\n}\n\n.installer-item-wrapper .installer-item:list-item > .installer-item-name {\n    -fx-pref-width: 80px;\n}\n\n.installer-item-wrapper .installer-item:list-item > .installer-item-status {\n    -fx-max-width: infinity;\n}\n\n.installer-item-wrapper .installer-item:card {\n    -fx-alignment: center;\n}\n\n/*******************************************************************************\n *                                                                             *\n * Main Page                                                                   *\n *                                                                             *\n ******************************************************************************/\n\n.launch-pane {\n    -fx-max-height: 57px;\n    -fx-min-height: 57px;\n    -fx-max-width: 230px;\n    -fx-min-width: 230px;\n}\n\n.launch-pane > .jfx-button {\n    -fx-translate-y: 1px;\n    -fx-max-height: 55px;\n    -fx-min-height: 55px;\n    -fx-background-color: -monet-primary-container;\n    -fx-opacity: 1; /* Override the opacity of disabled button */\n    -fx-cursor: hand;\n}\n\n.launch-pane > .jfx-button.launch-button {\n    -fx-max-width: 207px;\n    -fx-min-width: 207px;\n    -fx-border-width: 0 3px 0 0;\n    -fx-border-color: -monet-on-surface-variant;\n    -fx-background-radius: 4px 0 0 4px;\n}\n\n.launch-pane > .jfx-button.menu-button {\n    -fx-max-width: 20px;\n    -fx-min-width: 20px;\n    -fx-font-size: 15px;\n    -fx-background-radius: 0 4px 4px 0;\n}\n\n.launch-pane > .jfx-button > StackPane > .jfx-rippler {\n    -jfx-rippler-fill: -monet-on-primary-container;\n    -jfx-mask-type: CIRCLE;\n    -fx-padding: 0;\n}\n\n.launch-pane > .jfx-button, .jfx-button * {\n    -fx-text-fill: -monet-on-primary-container;\n    -fx-font-size: 14px;\n}\n\n.launch-pane .svg {\n    -fx-fill: -monet-on-primary-container;\n}\n\n/*******************************************************************************\n *                                                                             *\n * JFX Tab Pane                                                                *\n *                                                                             *\n ******************************************************************************/\n\n.tab-header-background {\n    -fx-background-color: -monet-surface;\n}\n\n.tab-selected-line {\n    -fx-background-color: derive(-monet-primary-container, -30%);\n}\n\n/* TODO: It seems not actually used */\n.tab-rippler {\n    -jfx-rippler-fill: derive(-monet-primary-container, 30%);\n}\n\n.jfx-tab-pane .jfx-rippler {\n    -jfx-rippler-fill: white;\n}\n\n.tab-label {\n    -fx-font-size: 16;\n}\n\n.tab-label {\n    -fx-padding: 10 17 10 17;\n}\n\n.add-account-tab-header .tab-label {\n    -fx-font-size: 12;\n}\n\n/*******************************************************************************\n *                                                                             *\n * JFX Dialog Layout                                                           *\n *                                                                             *\n ******************************************************************************/\n\n.dialog-trigger {\n    -fx-background-color: WHITE;\n    -jfx-button-type: RAISED;\n    -fx-font-size: 14.0px;\n}\n\n.jfx-dialog-layout {\n    -fx-padding: 24.0px 24.0px 16.0px 24.0px;\n    -fx-background-color: -monet-surface-container-high;\n    -fx-text-fill: -monet-on-surface;\n}\n\n.jfx-layout-heading {\n    -fx-font-weight: BOLD;\n    -fx-font-size: 20.0px;\n    -fx-alignment: center-left;\n    -fx-padding: 5.0 0.0 5.0 0.0;\n}\n\n.jfx-layout-body {\n    -fx-pref-width: 400px;\n    -fx-wrap-text: true;\n}\n\n.jfx-layout-body Text {\n    -fx-fill: -monet-on-surface;\n}\n\n.jfx-layout-actions {\n    -fx-pref-width: 400;\n    -fx-padding: 10.0px 0.0 0.0 0.0;\n    -fx-alignment: center-right;\n}\n\n.dialog-error {\n    -fx-text-fill: -monet-error;\n    -fx-padding: 0.7em 0.8em;\n}\n\n.dialog-accept {\n    -fx-text-fill: -monet-primary;\n    -fx-padding: 0.7em 0.8em;\n}\n\n.dialog-cancel {\n    -fx-text-fill: -monet-on-surface-variant;\n    -fx-padding: 0.7em 0.8em;\n}\n\n.task-executor-dialog-layout {\n    -fx-background-color: -monet-surface-container-high;\n    -fx-text-fill: -monet-on-surface;\n}\n\n/*******************************************************************************\n *                                                                             *\n * Microsoft Login Dialog                                                      *\n *                                                                             *\n ******************************************************************************/\n\n.microsoft-login-dialog:bodyonly {\n    -fx-padding: 0px 0px 0px 0px;\n}\n\n.method-title {\n    -fx-text-fill: -monet-on-surface;\n    -fx-font-weight: bold;\n}\n\n.method-desc {\n    -fx-text-fill: -monet-outline;\n    -fx-font-size: 0.9em;\n    -fx-line-spacing: 2;\n    -fx-wrap-text: true;\n    -fx-text-alignment: center;\n}\n\n.code-box {\n    -fx-background-color: -monet-surface-variant;\n    -fx-background-radius: 6;\n    -fx-padding: 0 10 0 10;\n    -fx-alignment: center;\n}\n\n.code-box .code-label {\n    -fx-font-size: 22px;\n    -fx-font-weight: bold;\n    -fx-text-fill: -monet-primary;\n}\n\n/*******************************************************************************\n *                                                                             *\n * JFX Pop Up                                                                  *\n *                                                                             *\n ******************************************************************************/\n\n.jfx-popup-overlay-pane {\n    -fx-background-color: transparent;\n}\n\n.jfx-popup-container {\n    -fx-background-color: -monet-surface;\n    -fx-background-radius: 4;\n}\n\n.popup-list-view {\n    -fx-pref-width: 150.0px;\n}\n\n/*******************************************************************************\n *                                                                             *\n * JFX Snack Bar                                                               *\n *                                                                             *\n ******************************************************************************/\n\n.jfx-snackbar-content {\n    -fx-background-color: -monet-inverse-surface;\n    -fx-padding: 5;\n    -fx-spacing: 5;\n}\n\n.jfx-snackbar-toast {\n    -fx-text-fill: -monet-inverse-on-surface;\n}\n\n.jfx-snackbar-action {\n    -fx-text-fill: -monet-inverse-primary;\n}\n\n/*******************************************************************************\n *                                                                             *\n * JFX Icons                                                                   *\n *                                                                             *\n ******************************************************************************/\n\n.icon {\n    -fx-fill: #FE774D;\n    -fx-padding: 10.0;\n    -fx-cursor: hand;\n}\n\n/*******************************************************************************\n *                                                                             *\n * JFX Tool Bar                                                                *\n *                                                                             *\n ******************************************************************************/\n\n.jfx-tool-bar {\n    -fx-font-size: 13.0;\n    -fx-pref-width: 100.0%;\n    -fx-min-height: 40px;\n    -fx-max-height: 40px;\n    -fx-pref-height: 40px;\n}\n\n.jfx-tool-bar .jfx-decorator-title {\n    -fx-font-weight: BOLD;\n}\n\n.jfx-tool-bar.background {\n    -fx-background-color: -monet-primary-container;\n}\n\n/*.jfx-tool-bar HBox {*/\n/*    -fx-alignment: center-left;*/\n/*    -fx-padding: 0.0 5.0;*/\n/*}*/\n\n.jfx-tool-bar .jfx-decorator-button {\n    -fx-min-width: 40px;\n    -fx-max-width: 40px;\n    -fx-pref-width: 40px;\n    -fx-min-height: 40px;\n    -fx-max-height: 40px;\n    -fx-pref-height: 40px;\n    -fx-cursor: hand;\n}\n\n.jfx-tool-bar .jfx-decorator-button .svg {\n    -fx-fill: -monet-on-primary-container;\n}\n\n.jfx-tool-bar Label {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.jfx-tool-bar.gray-background Label {\n    -fx-text-fill: BLACK;\n}\n\n.jfx-tool-bar .jfx-options-burger {\n    -fx-padding: 22px;\n}\n\n.jfx-tool-bar .jfx-options-burger StackPane {\n    -fx-pref-width: 4px;\n}\n\n.jfx-tool-bar .jfx-rippler {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.jfx-tool-bar-second {\n    -fx-pref-height: 42;\n    -fx-padding: 2 2 2 2;\n    -fx-background-color: -monet-primary-container;\n    -fx-alignment: CENTER-LEFT;\n    -fx-spacing: 8;\n}\n\n.jfx-tool-bar-second .label {\n    -fx-text-fill: -monet-on-primary-container;\n    -fx-font-size: 16;\n    -fx-font-weight: bold;\n}\n\n.jfx-tool-bar-second .jfx-rippler {\n    -jfx-rippler-fill: -monet-on-surface;\n}\n\n.jfx-tool-bar-button {\n    -fx-text-fill: -monet-on-surface;\n    -fx-toggle-icon4-size: 37px;\n    -fx-pref-height: -fx-toggle-icon4-size;\n    -fx-max-height: -fx-toggle-icon4-size;\n    -fx-min-height: -fx-toggle-icon4-size;\n    -fx-background-radius: 5px;\n    -fx-background-color: transparent;\n    -jfx-toggle-color: -monet-on-surface;\n    -jfx-untoggle-color: transparent;\n}\n\n.jfx-tool-bar-button .icon {\n    -fx-fill: rgb(204.0, 204.0, 51.0);\n    -fx-padding: 10.0;\n}\n\n.jfx-decorator-button {\n    -fx-max-width: 40px;\n    -fx-background-radius: 5px;\n    -fx-max-height: 40px;\n    -fx-background-color: transparent;\n}\n\n.jfx-decorator-button .icon {\n    -fx-fill: rgb(204.0, 204.0, 51.0);\n    -fx-padding: 0.0;\n}\n\n.jfx-decorator-button .jfx-rippler {\n    -jfx-mask-type: CIRCLE;\n    -fx-padding: 0.0;\n}\n\n/*******************************************************************************\n *                                                                             *\n * JFX Radio Button                                                            *\n *                                                                             *\n ******************************************************************************/\n\n.jfx-radio-button {\n    -jfx-selected-color: -monet-primary;\n    -jfx-unselected-color: -monet-on-surface-variant;\n    -fx-text-fill: -monet-on-surface;\n}\n\n/*******************************************************************************\n *                                                                             *\n * JFX Slider                                                                  *\n *                                                                             *\n ******************************************************************************/\n\n.jfx-slider .track,\n.jfx-slider:vertical .track {\n    -fx-background-color: -monet-secondary-container;\n    -fx-background-radius: 5;\n    -fx-background-insets: 0;\n    -fx-pref-width: 2px;\n    -fx-pref-height: 2px;\n}\n\n.jfx-slider .thumb,\n.jfx-slider:focused .thumb {\n    -fx-background-color: -monet-primary;\n    -fx-background-radius: 20;\n    -fx-background-insets: 0;\n}\n\n.jfx-slider .colored-track {\n    -fx-background-color: -monet-primary;\n    -fx-background-radius: 5 0 0 5;\n    -fx-background-insets: 0;\n}\n\n.jfx-slider .slider-value {\n    -fx-stroke: -monet-inverse-on-surface;\n    -fx-font-size: 10;\n}\n\n.jfx-slider .animated-thumb {\n    -fx-pref-width: 30px;\n    -fx-pref-height: 30px;\n    -fx-background-color: -monet-inverse-surface;\n    -fx-background-radius: 50% 50% 50% 0%;\n    -fx-background-insets: 0;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX Rippler                                                                  *\n*                                                                              *\n*******************************************************************************/\n\n.jfx-rippler:hover {\n    -fx-cursor: hand;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX Button                                                                   *\n*                                                                              *\n*******************************************************************************/\n\n.jfx-button {\n    -jfx-disable-visual-focus: true;\n    -fx-cursor: hand;\n}\n\n.jfx-button .jfx-rippler {\n    -jfx-rippler-fill: -monet-on-surface;\n    -jfx-mask-type: CIRCLE;\n    -fx-padding: 0.0;\n}\n\n.jfx-button-raised {\n    -fx-background-color: -monet-primary;\n}\n\n.jfx-button-raised .jfx-rippler {\n    -jfx-rippler-fill: -monet-on-primary;\n    -jfx-mask-type: CIRCLE;\n    -fx-padding: 0.0;\n}\n\n.jfx-button-raised, .jfx-button-raised * {\n    -fx-text-fill: -monet-on-primary;\n    -fx-font-size: 14px;\n}\n\n.jfx-button-border {\n    -fx-background-color: transparent;\n    -fx-border-color: -monet-outline;\n    -fx-border-radius: 5px;\n    -fx-border-width: 0.2px;\n    -fx-padding: 8px;\n}\n\n.jfx-button-border, .jfx-button-border * {\n    -fx-text-fill: -monet-primary;\n}\n\n.jfx-button-raised-round {\n    -fx-background-color: -monet-primary-container;\n    -fx-background-radius: 50px;\n}\n\n.menu-up-down-button .label {\n    -fx-text-fill: -monet-on-surface;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX Check Box                                                                *\n*                                                                              *\n*******************************************************************************/\n\n.jfx-check-box {\n    -fx-font-weight: BOLD;\n    -fx-text-fill: -monet-on-surface;\n    -jfx-checked-color: -monet-primary;\n    -jfx-unchecked-color: transparent;\n}\n\n.table-view .jfx-check-box .jfx-rippler {\n    -jfx-rippler-disabled: true;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX Progress Bar                                                             *\n*                                                                              *\n*******************************************************************************/\n\n\n.jfx-progress-bar > .track {\n    -fx-background-color: -monet-secondary-container;\n}\n\n.jfx-progress-bar > .bar {\n    -fx-background-color: -monet-primary;\n    -fx-padding: 2;\n}\n\n.jfx-progress-bar > .track,\n.jfx-progress-bar > .bar {\n    -fx-background-radius: 2;\n    -fx-background-insets: 0;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX Textfield                                                                *\n*                                                                              *\n*******************************************************************************/\n\n.jfx-text-field, .jfx-password-field, .jfx-text-area {\n    -fx-background-color: -monet-surface-container-highest;\n    -fx-highlight-fill: -monet-primary;\n    -fx-text-fill: -monet-on-surface;\n    -fx-font-weight: BOLD;\n    -fx-prompt-text-fill: -monet-on-surface-variant;\n    -fx-alignment: top-left;\n    -fx-max-width: 1000000000;\n    -jfx-focus-color: -monet-primary;\n    -fx-padding: 8;\n    -jfx-unfocus-color: transparent;\n}\n\n.jfx-text-field .context-menu {\n    -fx-background-color: -monet-surface-container;\n    -fx-text-fill: -monet-on-surface;\n}\n\n.jfx-text-field .context-menu .menu-item:focused {\n    -fx-background-color: -monet-secondary-container;\n    -fx-text-fill: -monet-on-secondary-container;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX List View                                                                *\n*                                                                              *\n*******************************************************************************/\n\n.no-padding .list-cell {\n    -fx-padding: 0;\n}\n\n.jfx-list-cell, .list-cell {\n    -fx-background-color: transparent;\n}\n\n.list-cell:selected, .jfx-list-cell:selected,\n.list-cell:hover, .jfx-list-cell:hover {\n    -fx-text-fill: black;\n}\n\n.jfx-list-cell .jfx-rippler {\n    -jfx-rippler-fill: -monet-primary-container;\n}\n\n/*.list-cell:odd:selected > .jfx-rippler > StackPane,*/\n/*.list-cell:even:selected > .jfx-rippler > StackPane {*/\n/*    -fx-background-color: derive(-monet-primary, 30%);*/\n/*}*/\n\n.jfx-list-view {\n    -fx-background-insets: 0.0;\n    -jfx-cell-horizontal-margin: 0.0;\n    -jfx-cell-vertical-margin: 5.0;\n    -jfx-vertical-gap: 10.0;\n    -jfx-expanded: false;\n    -fx-background-color: transparent;\n}\n\n.jfx-list-view .scroll-bar {\n    -fx-skin: \"org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin\";\n}\n\n.jfx-list-view .scroll-bar .track {\n    -fx-fill: transparent;\n}\n\n.jfx-list-view-float {\n    -fx-background-insets: 0.0;\n    -jfx-cell-horizontal-margin: 0.0;\n    -jfx-cell-vertical-margin: 5.0;\n    -jfx-vertical-gap: 10.0;\n    -jfx-expanded: false;\n    -fx-background-color: transparent;\n}\n\n.no-horizontal-scrollbar .scroll-bar:horizontal {\n    -fx-opacity: 0;\n    -fx-max-height: 0;\n    -fx-min-height: 0;\n    -fx-pref-height: 0;\n}\n\n.options-list {\n    -fx-background-color: transparent;\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);\n}\n\n.depth-0 {\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0), 0, 0, 0, 0);\n}\n\n.depth-1 {\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);\n}\n\n.depth-2 {\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 15, 0.16, 0, 4);\n}\n\n.depth-3 {\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 20, 0.19, 0, 6);\n}\n\n.depth-4 {\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 25, 0.25, 0, 8);\n}\n\n.depth-5 {\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 30, 0.3, 0, 10);\n}\n\n.padding-8 {\n    -fx-padding: 8px;\n}\n\n.card {\n    -fx-background-color: -monet-surface-container-low-transparent-80;\n    -fx-background-radius: 4;\n    -fx-padding: 8px;\n\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);\n}\n\n.card-no-padding {\n    -fx-background-color: -monet-surface-container-low-transparent-80;\n    -fx-background-radius: 4;\n\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);\n}\n\n.card-non-transparent {\n    -fx-background-color: -monet-surface-container-low;\n    -fx-background-radius: 4;\n    -fx-padding: 8px;\n\n    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);\n}\n\n.card-list {\n    -fx-padding: 10;\n    -fx-spacing: 10;\n}\n\n.md-list-cell {\n    -fx-border-color: -monet-outline-variant;\n    -fx-border-width: 0 0 1 0;\n}\n\n.md-list-cell:selected {\n    -fx-background-color: -monet-secondary-container;\n}\n\n.mod-info-list-cell:warning {\n    -fx-background-color: -monet-error-container;\n}\n\n.options-sublist {\n    -fx-background-color: -monet-surface;\n}\n\n.options-list-item {\n    -fx-background-color: -monet-surface;\n    -fx-border-color: -monet-outline-variant;\n    -fx-border-width: 1 0 0 0;\n    -fx-padding: 10 16 10 16;\n    -fx-font-size: 12;\n}\n\n.options-list-item .svg {\n    -fx-fill: -monet-on-surface;\n    -fx-border-color: -monet-outline-variant;\n}\n\n.options-list-item .label {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.options-list-item .label.subtitle {\n    -fx-text-fill: -monet-on-surface-variant;\n}\n\n.options-list-item.no-padding {\n    -fx-padding: 0;\n}\n\n.no-padding .options-list-item {\n    -fx-padding: 0;\n}\n\n.options-list-item:first {\n    -fx-background-radius: 4 4 0 0;\n    -fx-border-width: 0;\n}\n\n.options-list-item:last {\n    -fx-background-radius: 0 0 4 4;\n}\n\n.options-list-item:first:last {\n    -fx-background-radius: 4 4 4 4;\n    -fx-border-width: 0;\n}\n\n.options-sublist-wrapper .expand-icon .svg {\n    -fx-fill: -monet-on-surface-variant;\n}\n\n.options-sublist-wrapper .expand-icon .svg:disabled {\n    -fx-opacity: 0.4;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX Toggle Button                                                            *\n*                                                                              *\n*******************************************************************************/\n\n.jfx-toggle-button,\n.jfx-toggle-button:armed,\n.jfx-toggle-button:hover,\n.jfx-toggle-button:focused,\n.jfx-toggle-button:selected,\n.jfx-toggle-button:focused:selected {\n    -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT;\n    -fx-background-radius: 3px;\n    -fx-background-insets: 0px;\n\n    -jfx-toggle-color: -monet-primary;\n    -jfx-toggle-line-color: -monet-secondary-container;\n    -jfx-untoggle-color: -monet-outline;\n    -jfx-untoggle-line-color: -monet-surface-container-highest;\n    -jfx-size: 10;\n}\n\n\n.jfx-toggle-button Line {\n    -fx-stroke: -jfx-untoggle-line-color;\n}\n\n.jfx-toggle-button:selected Line {\n    -fx-stroke: -jfx-toggle-line-color;\n}\n\n.jfx-toggle-button Circle {\n    -fx-fill: -jfx-untoggle-color;\n}\n\n.jfx-toggle-button:selected Circle {\n    -fx-fill: -jfx-toggle-color;\n}\n\n.jfx-toggle-button .jfx-rippler {\n    -jfx-rippler-fill: -jfx-untoggle-line-color;\n}\n\n.jfx-toggle-button:selected .jfx-rippler {\n    -jfx-rippler-fill: -jfx-toggle-line-color;\n}\n\n\n.toggle-label {\n    -fx-font-size: 14.0px;\n}\n\n/* unused */\n.toggle-icon1 .icon {\n    -fx-fill: #4285F4;\n    -fx-padding: 20.0;\n}\n\n.toggle-icon1 {\n    -fx-pref-width: 80px;\n    -fx-background-radius: 80px;\n    -fx-pref-height: 80px;\n    -fx-background-color: transparent;\n    -jfx-toggle-color: rgba(66.0, 133.0, 244.0, 0.29885056614875793);\n    -jfx-untoggle-color: transparent;\n}\n\n.toggle-icon1 .jfx-rippler {\n    -jfx-rippler-fill: rgba(66.0, 133.0, 244.0, 0.29885056614875793);\n    -jfx-mask-type: CIRCLE;\n}\n\n.toggle-icon2, .toggle-icon3, .jfx-decorator-button {\n    -fx-pref-width: 50px;\n    -fx-background-radius: 50px;\n    -fx-pref-height: 50px;\n    -fx-background-color: transparent;\n}\n\n/* unused */\n.toggle-icon2 .icon {\n    -fx-fill: RED;\n}\n\n.toggle-icon2 .jfx-rippler {\n    -jfx-rippler-fill: RED;\n    -jfx-mask-type: CIRCLE;\n}\n\n/* unused */\n.toggle-icon3 {\n    -fx-toggle-icon4-size: 35px;\n    -fx-pref-width: -fx-toggle-icon4-size;\n    -fx-max-width: -fx-toggle-icon4-size;\n    -fx-min-width: -fx-toggle-icon4-size;\n    -fx-pref-height: -fx-toggle-icon4-size;\n    -fx-max-height: -fx-toggle-icon4-size;\n    -fx-min-height: -fx-toggle-icon4-size;\n    -fx-background-radius: 50px;\n    -fx-background-color: transparent;\n    -jfx-toggle-color: white;\n    -jfx-untoggle-color: transparent;\n}\n\n.toggle-icon3 .icon {\n    -fx-fill: rgb(204.0, 204.0, 51.0);\n    -fx-padding: 10.0;\n}\n\n.toggle-icon3 .jfx-rippler {\n    -jfx-rippler-fill: white;\n    -jfx-mask-type: CIRCLE;\n}\n\n/* used */\n.toggle-icon4 {\n    -fx-toggle-icon4-size: 30px;\n    -fx-pref-width: -fx-toggle-icon4-size;\n    -fx-max-width: -fx-toggle-icon4-size;\n    -fx-min-width: -fx-toggle-icon4-size;\n    -fx-pref-height: -fx-toggle-icon4-size;\n    -fx-max-height: -fx-toggle-icon4-size;\n    -fx-min-height: -fx-toggle-icon4-size;\n    -fx-background-radius: 50px;\n    -fx-background-color: transparent;\n    -jfx-toggle-color: -monet-primary;\n    -jfx-untoggle-color: transparent;\n}\n\n.toggle-icon4 .icon {\n    -fx-fill: rgb(204.0, 204.0, 51.0);\n    -fx-padding: 10.0;\n}\n\n.toggle-icon4 .jfx-rippler {\n    -jfx-rippler-fill: -monet-primary;\n    -jfx-mask-type: CIRCLE;\n}\n\n.toggle-icon-tiny {\n    -fx-toggle-icon-tiny-size: 15px;\n    -fx-pref-width: -fx-toggle-icon-tiny-size;\n    -fx-max-width: -fx-toggle-icon-tiny-size;\n    -fx-min-width: -fx-toggle-icon-tiny-size;\n    -fx-pref-height: -fx-toggle-icon-tiny-size;\n    -fx-max-height: -fx-toggle-icon-tiny-size;\n    -fx-min-height: -fx-toggle-icon-tiny-size;\n    -fx-background-radius: 25px;\n    -fx-background-color: transparent;\n    -jfx-toggle-color: -monet-primary;\n    -jfx-untoggle-color: transparent;\n}\n\n.toggle-icon-tiny .icon {\n    -fx-fill: rgb(204.0, 204.0, 51.0);\n    -fx-padding: 5.0;\n}\n\n.toggle-icon-tiny .jfx-rippler {\n    -jfx-rippler-fill: -monet-primary;\n    -jfx-mask-type: CIRCLE;\n}\n\n.announcement-close-button {\n    -fx-announcement-close-button-size: 20px;\n    -fx-pref-width: -fx-announcement-close-button-size;\n    -fx-max-width: -fx-announcement-close-button-size;\n    -fx-min-width: -fx-announcement-close-button-size;\n    -fx-pref-height: -fx-announcement-close-button-size;\n    -fx-max-height: -fx-announcement-close-button-size;\n    -fx-min-height: -fx-announcement-close-button-size;\n    -fx-background-radius: 50px;\n    -fx-background-color: transparent;\n}\n\n.announcement-close-button .jfx-rippler {\n    -jfx-rippler-fill: -monet-primary;\n    -jfx-mask-type: CIRCLE;\n}\n\n.transparent {\n    -fx-background-color: null;\n}\n\n/*******************************************************************************\n*                                                                              *\n* Log Window                                                                   *\n*                                                                              *\n*******************************************************************************/\n\n.log-toggle {\n    -fx-border: 1px;\n    -fx-background-insets: 0;\n    -fx-border-color: -fixed-log-toggle-unselected;\n    -fx-text-fill: -fixed-log-toggle-unselected;\n}\n\n.log-toggle:selected {\n    -fx-border: 1px;\n    -fx-border-color: -fixed-log-toggle-selected;\n    -fx-text-fill: -fixed-log-toggle-selected;\n}\n\n.log-toggle.fatal {\n    -fx-background-color: -fixed-log-fatal;\n}\n\n.log-toggle.error {\n    -fx-background-color: -fixed-log-error;\n}\n\n.log-toggle.warn {\n    -fx-background-color: -fixed-log-warn;\n}\n\n.log-toggle.info {\n    -fx-background-color: -fixed-log-info;\n}\n\n.log-toggle.debug {\n    -fx-background-color: -fixed-log-debug;\n}\n\n.log-toggle.trace {\n    -fx-background-color: -fixed-log-trace;\n}\n\n.log-window-list-cell:selected {\n    -fx-background-color: -fixed-log-selected;\n}\n\n.log-window {\n    -fx-background-color: -monet-surface-container;\n}\n\n.log-window .scroll-bar .thumb {\n    -fx-fill: -monet-surface-tint;\n}\n\n.log-window .jfx-button {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.log-window-list-cell {\n    -fx-text-fill: -fixed-log-text-fill;\n    -fx-border-width: 0 0 1 0;\n    -fx-border-color: -monet-outline-variant;\n}\n\n.log-window-list-cell:empty {\n    -fx-border-width: 0;\n}\n\n.log-window-list-cell:fatal {\n    -fx-text-fill: -fixed-log-text-fill;\n    -fx-background-color: -fixed-log-fatal;\n}\n\n.log-window-list-cell:error {\n    -fx-text-fill: -fixed-log-text-fill;\n    -fx-background-color: -fixed-log-error;\n}\n\n.log-window-list-cell:warn {\n    -fx-text-fill: -fixed-log-text-fill;\n    -fx-background-color: -fixed-log-warn;\n}\n\n.log-window-list-cell:info {\n    -fx-text-fill: -fixed-log-text-fill;\n    -fx-background-color: -fixed-log-info;\n}\n\n.log-window-list-cell {\n    -fx-background-color: -fixed-log-info;\n}\n\n.log-window-list-cell:debug {\n    -fx-text-fill: -fixed-log-text-fill;\n    -fx-background-color: -fixed-log-debug;\n}\n\n.log-window-list-cell:trace {\n    -fx-text-fill: -fixed-log-text-fill;\n    -fx-background-color: -fixed-log-trace;\n}\n\n.log-window-list-cell:selected {\n    -fx-text-fill: -fixed-log-text-fill;\n    -fx-background-color: -fixed-log-selected;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX Spinner                                                                  *\n*                                                                              *\n*******************************************************************************/\n\n.jfx-spinner .arc {\n    -fx-stroke: -monet-primary-container;\n}\n\n.small-spinner {\n    -jfx-radius: 9;\n}\n\n.small-spinner .arc {\n    -fx-stroke-width: 3.0;\n}\n\n.small-spinner-pane .jfx-spinner {\n    -jfx-radius: 9;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX Combo Box                                                                *\n*                                                                              *\n*******************************************************************************/\n\n.jfx-combo-box {\n    -jfx-focus-color: transparent;\n    -jfx-unfocus-color: transparent;\n    -fx-background-color: -monet-surface-container-highest;\n    -fx-padding: 4;\n    -fx-max-width: 1000000000;\n}\n\n.jfx-combo-box .text {\n    -fx-fill: -monet-on-surface;\n}\n\n.jfx-combo-box .text-field {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.combo-box-popup .list-view {\n    -fx-background-color: -monet-surface-container;\n}\n\n.combo-box-popup .list-view .jfx-list-cell {\n    -fx-background-color: -monet-surface-container;\n    -fx-text-fill: -monet-on-surface;\n    -fx-background-insets: 0.0;\n}\n\n.combo-box-popup .list-view .list-cell:odd:selected > .jfx-rippler > StackPane,\n.combo-box-popup .list-view .list-cell:even:selected > .jfx-rippler > StackPane {\n    -fx-background-color: -monet-secondary-container;\n    -fx-text-fill: -monet-on-secondary-container;\n}\n\n.jfx-combo-box-warning {\n    -jfx-focus-color: #D34336;\n    -jfx-unfocus-color: #D34336;\n}\n\n.jfx-combo-box-warning .text {\n    -fx-fill: #D34336;\n}\n\n/*.combo-box-popup .list-view .jfx-list-cell .jfx-rippler {*/\n/*    -jfx-rippler-fill: -monet-primary-container;*/\n/*}*/\n\n/*******************************************************************************\n*                                                                              *\n* JFX Color Picker                                                             *\n*                                                                              *\n*******************************************************************************/\n\n.jfx-color-picker:armed,\n.jfx-color-picker:hover,\n.jfx-color-picker:focused,\n.jfx-color-picker {\n    -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT;\n    -fx-background-radius: 3px;\n    -fx-background-insets: 0px;\n    -fx-min-height: 25px;\n}\n\n.color-palette-region {\n}\n\n.color-palette-region .jfx-button {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.color-palette {\n    -fx-background-color: -monet-surface;\n}\n\n.custom-color-dialog .jfx-tab-pane {\n    -fx-background-color: -monet-surface;\n}\n\n.custom-color-dialog .custom-color-field {\n    -jfx-unfocus-color: -monet-on-surface;\n    -fx-background-color: TRANSPARENT;\n    -fx-font-weight: BOLD;\n    -fx-alignment: top-left;\n    -fx-max-width: 300;\n}\n\n.custom-color-dialog .tab .tab-label {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.custom-color-dialog .tab .jfx-rippler {\n    -jfx-rippler-fill: -monet-on-surface;\n}\n\n.custom-color-dialog .tab-selected-line {\n    -fx-background-color: -monet-on-surface;\n}\n\n/*******************************************************************************\n *                                                                             *\n * Table View                                                                  *\n *                                                                             *\n ******************************************************************************/\n\n/* WE DEFINITELY NEED JFX TABLE-VIEW */\n\n.table-view {\n    -fx-padding: 3;\n    -fx-background-color: -monet-surface-container-low;\n}\n\n.table-view,\n.table-view .column-header,\n.table-view .filler,\n.table-view .column-header-background,\n.table-row-cell {\n    -fx-background-radius: 6;\n    -fx-border-radius: 6;\n}\n\n.table-view .column-header,\n.table-view .filler {\n    -fx-background-color: transparent;\n}\n\n.table-view .column-header-background {\n    -fx-background-color: -monet-surface-container-highest;\n}\n\n.table-row-cell {\n    -fx-background-color: -monet-surface-container;\n    -fx-table-cell-border-color: -monet-surface-container-lowest;\n}\n\n.table-row-cell:odd {\n    -fx-background-color: -monet-surface-container-high;\n}\n\n.table-cell {\n    -fx-background-color: transparent;\n    -fx-text-fill: -monet-on-surface;\n}\n\n.table-view > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected {\n    -fx-background-color: -monet-secondary-container;\n}\n\n.table-view > .virtual-flow > .scroll-bar {\n    -fx-skin: \"org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin\";\n}\n\n.table-view > .virtual-flow > .scroll-bar .track {\n    -fx-fill: transparent;\n}\n\n/*******************************************************************************\n *                                                                             *\n * JFX Tree View                                                               *\n *                                                                             *\n ******************************************************************************/\n\n.tree-view {\n    -fx-padding: 5;\n    -fx-background-radius: 6;\n    -fx-background-color: -monet-surface-container-low;\n    -fx-border-radius: 6;\n}\n\n.tree-cell {\n    -fx-padding: 5;\n    -fx-background-radius: 6;\n    -fx-background-color: transparent;\n    -fx-text-fill: -monet-on-surface;\n    -fx-border-radius: 6;\n}\n\n.tree-cell:focused {\n    -fx-background-color: -monet-secondary-container;\n}\n\n.tree-cell .arrow {\n    -fx-background-color: -monet-on-surface;\n}\n\n.tree-cell .jfx-rippler {\n    -jfx-rippler-disabled: true;\n}\n\n.tree-view > .virtual-flow > .scroll-bar {\n    -fx-skin: \"org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin\";\n}\n\n.tree-view > .virtual-flow > .scroll-bar .track {\n    -fx-fill: transparent;\n}\n\n/*******************************************************************************\n*                                                                              *\n* JFX Decorator                                                                *\n*                                                                              *\n*******************************************************************************/\n\n.jfx-decorator {\n    -fx-decorator-color: -monet-primary-container;\n}\n\n.jfx-decorator-drawer {\n    -fx-background-color: rgba(244, 244, 244, 0.5);\n}\n\n.jfx-decorator-title {\n    -fx-font-size: 14;\n}\n\n.window {\n    -fx-background-color: transparent;\n    -fx-padding: 8;\n}\n\n.debug-border {\n    -fx-border-color: red;\n    -fx-border-width: 1;\n}\n\n.content-background {\n    -fx-background-color: -monet-surface-transparent-50;\n}\n\n.gray-background {\n    -fx-background-color: -monet-surface-transparent-50;\n}\n\n/*******************************************************************************\n *                                                                             *\n * Scroll Bar\t\t\t                                                       *\n *                                                                             *\n ******************************************************************************/\n\n.scroll-bar:vertical > .track-background, .scroll-bar:horizontal > .track-background {\n    -fx-background-color: #F1F1F1;\n    -fx-background-insets: 0.0;\n}\n\n.scroll-bar:vertical .thumb, .scroll-bar:horizontal .thumb {\n    -fx-background-color: #BCBCBC;\n    -fx-background-insets: 0.0;\n    -fx-background-radius: 1.0;\n}\n\n.scroll-bar > .increment-button, .scroll-bar > .decrement-button {\n    -fx-padding: 5 2 5 2;\n}\n\n.scroll-bar > .increment-button, .scroll-bar > .decrement-button, .scroll-bar:hover > .increment-button, .scroll-bar:hover > .decrement-button {\n    -fx-background-color: transparent;\n}\n\n.scroll-bar > .increment-button > .increment-arrow, .scroll-bar > .decrement-button > .decrement-arrow {\n    -fx-background-color: rgb(150.0, 150.0, 150.0);\n}\n\n.scroll-bar > .increment-button > .increment-arrow {\n    -fx-shape: \"M298 426h428l-214 214z\";\n}\n\n.scroll-bar > .decrement-button > .decrement-arrow {\n    -fx-shape: \"M298 598l214-214 214 214h-428z\";\n}\n\n.jfx-combo-box .scroll-bar {\n    -fx-skin: \"org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin\";\n}\n\n.jfx-combo-box .scroll-bar .track {\n    -fx-fill: transparent;\n}\n\n/*******************************************************************************\n *                                                                             *\n * Scroll Pane\t\t\t                                                       *\n *                                                                             *\n ******************************************************************************/\n\n.scroll-pane {\n    -fx-background-color: null;\n    -fx-background-insets: 0;\n    -fx-padding: 0;\n    -fx-snap-to-pixel: false;\n}\n\n.scroll-pane:focused {\n    -fx-background-insets: 0;\n}\n\n.scroll-pane .corner {\n    -fx-background-insets: 0;\n}\n\n.scroll-pane > .viewport {\n    -fx-background-color: null;\n}\n\n.scroll-pane .scroll-bar {\n    -fx-skin: \"org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin\";\n}\n\n.scroll-pane .scroll-bar .track {\n    -fx-fill: transparent;\n}\n\n/*******************************************************************************\n*                                                                              *\n* Error Facade                                                                 *\n*                                                                              *\n*******************************************************************************/\n\n.error-label {\n    -fx-text-fill: -monet-error;\n    -fx-font-size: 0.75em;\n    -fx-font-weight: bold;\n}\n\n.error-icon {\n    -fx-fill: -monet-error;\n    -fx-font-size: 1.0em;\n}\n\n.jfx-text-field:error, .jfx-password-field:error, .jfx-text-area:error, .jfx-combo-box:error {\n    -jfx-focus-color: -monet-error;\n    -jfx-unfocus-color: -monet-error;\n}\n\n.jfx-text-field .error-label, .jfx-password-field .error-label, .jfx-text-area .error-label {\n    -fx-text-fill: -monet-error;\n    -fx-font-size: 0.75em;\n}\n\n.jfx-text-field .error-icon, .jfx-password-field .error-icon, .jfx-text-area .error-icon {\n    -fx-fill: -monet-error;\n    -fx-font-size: 1.0em;\n}\n\n.fit-width {\n    -fx-pref-width: 100%;\n}\n\n/*******************************************************************************\n*                                                                              *\n* HTML Renderer                                                                *\n*                                                                              *\n*******************************************************************************/\n\n.html {\n    -fx-font-size: 16;\n}\n\n.html-hyperlink {\n    -fx-fill: -monet-primary;\n}\n\n.html-h1 {\n    -fx-font-size: 22;\n}\n\n.html-h2 {\n    -fx-font-size: 20;\n}\n\n.html-h3 {\n    -fx-font-size: 18;\n}\n\n.html-bold {\n    -fx-font-weight: bold;\n}\n\n.html-italic {\n    -fx-font-style: italic;\n}\n\n/*******************************************************************************\n *                                                                             *\n * Tooltip                                                                     *\n *                                                                             *\n ******************************************************************************/\n\n.tooltip {\n    -fx-text-fill: -monet-inverse-on-surface;\n    -fx-background-color: -monet-inverse-surface;\n}\n\n.tooltip .text {\n    -fx-fill: -monet-inverse-on-surface;\n}\n\n/*******************************************************************************\n *                                                                             *\n * Line Component                                                              *\n *                                                                             *\n ******************************************************************************/\n\n.line-component .line-component-container {\n    -fx-padding: 10 16 10 16;\n}\n\n.line-component .subtitle-label {\n    -fx-text-fill: -monet-on-surface-variant;\n}\n\n.line-component.no-padding .line-component-container {\n    -fx-padding: 0;\n}\n\n.line-component:large-title .title-label {\n    -fx-font-size: 15px;\n}\n\n.line-button .svg:disabled {\n    -fx-opacity: 0.4;\n}\n\n.line-button .trailing-label {\n    -fx-text-fill: -monet-on-surface-variant;\n}\n\n.line-button .trailing-icon .svg {\n    -fx-fill: -monet-on-surface-variant;\n    -fx-opacity: 1;\n}\n\n.line-button .trailing-icon .svg:disabled {\n    -fx-opacity: 0.4;\n}\n\n.line-select-button .menu-container .title-label {\n    -fx-font-size: 13px;\n}\n\n.line-select-button .menu-container .title-label {\n    -fx-text-fill: -monet-on-surface;\n}\n\n.line-select-button .menu-container .subtitle-label {\n    -fx-text-fill: -monet-on-surface-variant;\n}\n\n.line-select-button .menu-container:selected .title-label {\n    -fx-text-fill: -monet-primary;\n}\n\n.line-select-button .menu-container:selected .subtitle-label {\n    -fx-text-fill: -monet-primary-container;\n}\n\n.line-toggle-button .jfx-toggle-button {\n    -fx-padding: 0;\n}"
  },
  {
    "path": "HMCL/src/main/resources/assets/lang/I18N.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Contributors: dxNeil, machinesmith42\n# and Byacrya for basically retranslating it\n\nabout=About\nabout.copyright=Copyright\nabout.copyright.statement=Copyright © 2013-2026 huangyuhui and contributors.\nabout.author=Author\nabout.author.statement=bilibili @huanghongxun\nabout.claim=EULA\nabout.claim.statement=Click this link for full text.\nabout.dependency=Third-party Libraries\nabout.legal=Legal Acknowledgement\nabout.thanks_to=Thanks to\nabout.thanks_to.bangbang93.statement=For providing the BMCLAPI download mirror. Please consider donating!\nabout.thanks_to.burningtnt.statement=Contribute a lot of technical support to HMCL.\nabout.thanks_to.contributors=All contributors on GitHub\nabout.thanks_to.contributors.statement=Without the awesome open-source community, HMCL would not have made it so far.\nabout.thanks_to.gamerteam.statement=For providing the default background image.\nabout.thanks_to.glavo.statement=Responsible for maintaining HMCL.\nabout.thanks_to.zekerzhayard.statement=Contribute a lot of technical support to HMCL.\nabout.thanks_to.zkitefly.statement=Responsible for maintaining the documentation of HMCL.\nabout.thanks_to.mcbbs=MCBBS (Minecraft Chinese Forum)\nabout.thanks_to.mcbbs.statement=For providing the mcbbs.net download mirror for Chinese Mainland users. (No longer available)\nabout.thanks_to.mcim=mcmod-info-mirror\nabout.thanks_to.mcim.statement=For providing the mod info cache acceleration service for Chinese Mainland users.\nabout.thanks_to.mcmod=MCMod (mcmod.cn)\nabout.thanks_to.mcmod.statement=For providing the Simplified Chinese translations and wiki for various mods.\nabout.thanks_to.red_lnn.statement=For providing the default background image.\nabout.thanks_to.shulkersakura.statement=For providing the logo for HMCL.\nabout.thanks_to.users=HMCL User Group Members\nabout.thanks_to.users.statement=Thanks for donations, bug reports, and so on.\nabout.thanks_to.yushijinhun.statement=For providing the authlib-injector related support.\nabout.open_source=Open Source\nabout.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)\n\naccount=Accounts\naccount.cape=Cape\naccount.character=Player\naccount.choose=Choose a Player\naccount.create=Add Account\naccount.create.microsoft=Add a Microsoft Account\naccount.create.offline=Add an Offline Account\naccount.create.authlibInjector=Add an authlib-injector Account\naccount.email=Email\naccount.empty=No Accounts\naccount.failed=Failed to refresh account.\naccount.failed.character_deleted=The player has already been deleted.\naccount.failed.connect_authentication_server=Failed to connect to the authentication server, your network connection may be down.\naccount.failed.connect_injector_server=Failed to connect to the authentication server. Please check your network and make sure you entered the correct URL.\naccount.failed.injector_download_failure=Failed to download the authlib-injector. Please check your network, or try switching to a different download source.\naccount.failed.invalid_credentials=Incorrect password or rate limited. Please try again later.\naccount.failed.invalid_password=Invalid password.\naccount.failed.invalid_token=Please try logging in again.\naccount.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should log in using your migrated Microsoft account again.\naccount.failed.no_character=There are no characters linked to this account.\naccount.failed.server_disconnected=Failed to connect to the authentication server. You can log in using offline mode or try logging in again.\\n\\\n   If the issue persists after multiple attempts, please try logging in to the account again.\naccount.failed.server_response_malformed=Invalid server response. The authentication server may be down.\naccount.failed.ssl=An SSL error occurred while connecting to the server. Please try updating your Java.\naccount.failed.dns=An SSL error occurred while connecting to the server. DNS resolution may be incorrect. Please try changing your DNS server or using a proxy service.\naccount.failed.wrong_account=You have logged in to the wrong account.\naccount.hmcl.hint=You need to click \"Log in\" and complete the process in the opened browser window.\naccount.injector.add=New Auth Server\naccount.injector.empty=None (You can click \"+\" to add one)\naccount.injector.http=Warning: This server uses the unsafe HTTP protocol. Anyone intercepting your connection would be able to see your credentials in plaintext.\naccount.injector.link.homepage=Homepage\naccount.injector.link.register=Register\naccount.injector.server=Authentication Server\naccount.injector.server_url=Server URL\naccount.injector.server_name=Server Name\naccount.login=Log in\naccount.login.hint=We never store your password.\naccount.login.skip=Log in offline\naccount.login.retry=Retry\naccount.login.refresh=Log in again\naccount.login.refresh.microsoft.hint=You need to log in to your Microsoft account again because the account authorization is invalid.\naccount.login.restricted=Sign in to your Microsoft account to enable this feature\naccount.logout=Logout\naccount.register=Register\naccount.manage=Account List\naccount.copy_uuid=Copy UUID of the Account\naccount.methods=Login Type\naccount.methods.authlib_injector=authlib-injector\naccount.methods.microsoft=Microsoft\naccount.methods.microsoft.birth=How to Change Your Account Birth Date\naccount.methods.microsoft.code=Code (Copied to Clipboard)\naccount.methods.microsoft.close_page=Microsoft account authorization is now completed.\\n\\\n   \\n\\\n   There are some extra works for us, but you can safely close this tab for now.\naccount.methods.microsoft.deauthorize=Deauthorize\naccount.methods.microsoft.error.add_family=Please click <a href=\"https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a\">here</a> to change your account birth date to be over 18 years old, or add your account to a family.\naccount.methods.microsoft.error.country_unavailable=Xbox Live is not available in your current country/region.\naccount.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please click <a href=\"https://www.minecraft.net/msaprofile/mygames/editprofile\">here</a> to link one.\naccount.methods.microsoft.error.no_character=Please confirm that you have purchased Minecraft: Java Edition.\\n\\\n  If you have already purchased it, a game profile may not have been created. Please click <a href=\"https://www.minecraft.net/msaprofile/mygames/editprofile\">here</a> to create a game profile.\naccount.methods.microsoft.error.banned=Your account may have been banned by Xbox Live.\\n\\\n  You can click <a href=\"https://enforcement.xbox.com/enforcement/showenforcementhistory\">here</a> to check the ban status of your account.\naccount.methods.microsoft.error.unknown=Failed to log in, error code: %d.\naccount.methods.microsoft.error.wrong_verify_method=Failed to log in. Please try logging into your account using PASSWORD instead of other login methods.\naccount.methods.microsoft.logging_in=Logging in...\naccount.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name\naccount.methods.microsoft.hint=Click the \"Log in\" button to start adding your Microsoft account.\naccount.methods.microsoft.methods.device=Log In with QR Code\naccount.methods.microsoft.methods.device.hint=Scan QR code or visit <a href=\"%s\">%s</a> to complete login, enter <b>%s</b> in the opened page.\naccount.methods.microsoft.methods.browser=Log In via Browser\naccount.methods.microsoft.methods.browser.hint=Click the \"Log in\" button or <a href=\"%s\">copy the link</a> and paste it into the browser to log in.\naccount.methods.microsoft.manual=<b>If your internet connection is bad, it may cause web pages to load slowly or fail to load altogether.\\nYou may try again later or switch to a different internet connection.</b>\naccount.methods.microsoft.profile=Account Profile\naccount.methods.microsoft.purchase=Buy Minecraft\naccount.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the <a href=\"https://hmcl.huangyuhui.net/download\">official build</a> to log in.\naccount.methods.microsoft.snapshot.tooltip=You are using an unofficial build of HMCL. Please download the official build to refresh the account.\naccount.methods.microsoft.snapshot.website=Official Website\naccount.methods.offline=Offline\naccount.methods.offline.name.special_characters=Use only letters, numbers, and underscores (max 16 chars)\naccount.methods.offline.name.invalid=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.\\n\\\n   \\n\\\n   \\  · Legitimate: HuangYu, huang_Yu, Huang_Yu_123;\\n\\\n   \\  · Illegal: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\\n\\\n   \\n\\\n   Using the illegal username will prevent you from joining most servers and may conflict with some mods, causing the game to crash.\naccount.methods.offline.uuid=UUID\naccount.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft players, and each launcher may generate UUIDs differently. Changing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\\n\\\n   \\n\\\n   This option is for advanced users only. We do not recommend changing this option unless you know what you are doing.\naccount.methods.offline.uuid.malformed=Invalid format.\naccount.methods.ban_query=Ban Query\naccount.missing=No Accounts\naccount.missing.add=Click here to add one.\naccount.move_to_global=Convert to Global Account\\nThe account information will be saved in a config file of the current user directory of the system.\naccount.move_to_portable=Convert to Portable Account\\nThe account information will be saved in a config file in the same directory as HMCL.\naccount.not_logged_in=Not Logged in\naccount.password=Password\naccount.portable=Portable\naccount.skin=Skin\naccount.skin.file=Skin File\naccount.skin.model=Model\naccount.skin.model.default=Classic\naccount.skin.model.slim=Slim\naccount.skin.type.alex=Alex\naccount.skin.type.csl_api=Blessing Skin\naccount.skin.type.csl_api.location=Address\naccount.skin.type.csl_api.location.hint=CustomSkinAPI URL\naccount.skin.type.little_skin=LittleSkin\naccount.skin.type.little_skin.hint=You need to create a player with the same player name as your offline account on your skin provider site. Your skin will now be set to the skin assigned to your player on the skin provider site.\naccount.skin.type.local_file=Local Skin File\naccount.skin.type.steve=Steve\naccount.skin.upload=Upload/Edit Skin\naccount.skin.upload.failed=Failed to upload skin.\naccount.skin.invalid_skin=Invalid skin file.\naccount.username=Username\n\narchive.author=Author(s)\narchive.date=Publish Date\narchive.file.name=File Name\narchive.version=Version\n\nassets.download=Downloading Assets\nassets.download_all=Validating Assets Integrity\nassets.index.malformed=Index files of downloaded assets are corrupted. You can resolve this problem by clicking \"Manage → Update Game Assets\" on the \"Edit Instance\" page.\n\nbutton.cancel=Cancel\nbutton.change_source=Change Download Source\nbutton.clear=Clear\nbutton.copy_and_exit=Copy and Exit\nbutton.delete=Delete\nbutton.do_not_show_again=Don't show again\nbutton.edit=Edit\nbutton.install=Install\nbutton.export=Export\nbutton.no=No\nbutton.ok=OK\nbutton.ok.countdown=OK (%d)\nbutton.reset=Reset\nbutton.reveal_dir=Reveal\nbutton.refresh=Refresh\nbutton.remove=Remove\nbutton.remove.confirm=Are you sure you want to permanently remove it? This action cannot be undone!\nbutton.retry=Retry\nbutton.save=Save\nbutton.save_as=Save As\nbutton.select_all=Select All\nbutton.view=View\nbutton.yes=Yes\n\ncontact=Feedback\ncontact.chat=Join Group Chat\ncontact.chat.discord=Discord\ncontact.chat.discord.statement=Welcome to join our Discord server.\ncontact.chat.qq_group=HMCL User QQ Group\ncontact.chat.qq_group.statement=Welcome to join our user QQ group.\ncontact.feedback=Feedback Channel\ncontact.feedback.github=GitHub Issues\ncontact.feedback.github.statement=Submit an issue on GitHub.\n\ncolor.recent=Recommended\ncolor.custom=Custom Color\n\ncrash.NoClassDefFound=Please verify the integrity of this software, or try updating your Java.\ncrash.user_fault=The launcher crashed due to corrupted Java or system environment. Please make sure your Java or OS is installed properly.\n\ncurse.category.0=All\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4471\ncurse.category.4474=Sci-Fi\ncurse.category.4481=Small / Light\ncurse.category.4483=Combat\ncurse.category.4477=Mini Game\ncurse.category.4478=Quests\ncurse.category.4484=Multiplayer\ncurse.category.4476=Exploration\ncurse.category.4736=Skyblock\ncurse.category.4475=Adventure and RPG\ncurse.category.4487=FTB\ncurse.category.4480=Map Based\ncurse.category.4479=Hardcore\ncurse.category.4482=Extra Large\ncurse.category.4472=Tech\ncurse.category.4473=Magic\ncurse.category.5128=Vanilla+\ncurse.category.7418=Horror\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5299=Education\ncurse.category.5232=Galacticraft\ncurse.category.5129=Vanilla+\ncurse.category.5189=Utility & QOL\ncurse.category.6814=Performance\ncurse.category.6954=Integrated Dynamics\ncurse.category.6484=Create\ncurse.category.6821=Bug Fixes\ncurse.category.6145=Skyblock\ncurse.category.5190=QOL\ncurse.category.5191=Utility & QOL\ncurse.category.5192=FancyMenu\ncurse.category.423=Map and Information\ncurse.category.426=Addons\ncurse.category.434=Armor, Tools, and Weapons\ncurse.category.409=Structures\ncurse.category.4485=Blood Magic\ncurse.category.420=Storage\ncurse.category.429=Industrial Craft\ncurse.category.419=Magic\ncurse.category.412=Technology\ncurse.category.4557=Redstone\ncurse.category.428=Tinker's Construct\n# '\ncurse.category.414=Player Transport\ncurse.category.4486=Lucky Blocks\ncurse.category.432=Buildcraft\ncurse.category.418=Genetics\ncurse.category.4671=Twitch Integration\ncurse.category.5314=KubeJS\ncurse.category.408=Ores and Resources\ncurse.category.4773=CraftTweaker\ncurse.category.430=Thaumcraft\ncurse.category.422=Adventure and RPG\ncurse.category.413=Processing\ncurse.category.417=Energy\ncurse.category.415=Energy, Fluid, and Item Transport\ncurse.category.433=Forestry\ncurse.category.425=Miscellaneous\ncurse.category.4545=Applied Energistics 2\ncurse.category.416=Farming\ncurse.category.421=API and Library\ncurse.category.4780=Fabric\ncurse.category.424=Cosmetic\ncurse.category.406=World gen\ncurse.category.435=Server Utility\ncurse.category.411=Mobs\ncurse.category.407=Biomes\ncurse.category.427=Thermal Expansion\ncurse.category.410=Dimensions\ncurse.category.436=Food\ncurse.category.4558=Redstone\ncurse.category.4843=Automation\ncurse.category.4906=MCreator\ncurse.category.7669=Twilight Forest\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/12\ncurse.category.5244=Font Packs\ncurse.category.5193=Data Packs\ncurse.category.399=Steampunk\ncurse.category.396=128x\ncurse.category.398=512x and Higher\ncurse.category.397=256x\ncurse.category.405=Miscellaneous\ncurse.category.395=64x\ncurse.category.400=Photo Realistic\ncurse.category.393=16x\ncurse.category.403=Traditional\ncurse.category.394=32x\ncurse.category.404=Animated\ncurse.category.4465=Mod Support\ncurse.category.402=Medieval\ncurse.category.401=Modern\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/17\ncurse.category.4464=Modded World\ncurse.category.250=Game Map\ncurse.category.249=Creation\ncurse.category.251=Parkour\ncurse.category.253=Survival\ncurse.category.248=Adventure\ncurse.category.252=Puzzle\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4546\ncurse.category.4551=Hardcore Questing Mode\ncurse.category.4548=Lucky Blocks\ncurse.category.4556=Progression\ncurse.category.4752=Building Gadgets\ncurse.category.4553=CraftTweaker\ncurse.category.4554=Recipes\ncurse.category.4549=Guidebook\ncurse.category.4547=Configuration\ncurse.category.4550=Quests\ncurse.category.4555=World Gen\ncurse.category.4552=Scripts\n\ncurse.category.6553=Realistic\ncurse.category.6554=Fantasy\ncurse.category.6555=Vanilla\n\ncurse.sort.author=Author\ncurse.sort.date_created=Date Created\ncurse.sort.last_updated=Last Updated\ncurse.sort.name=Name\ncurse.sort.popularity=Popularity\ncurse.sort.total_downloads=Total Downloads\n\ndatetime.format=MMM d, yyyy, h\\:mm\\:ss a\n\ndownload=Download\ndownload.hint=Install games and modpacks or download mods, resource packs, shaders, and worlds.\ndownload.code.404=File \"%s\" not found on the remote server.\ndownload.content=Addons\ndownload.shader=Shaders\ndownload.curseforge.unavailable=This HMCL build does not support access to CurseForge. Please use the official build to access CurseForge.\ndownload.existing=The file cannot be saved because it already exists. You can click \"Save As\" to save the file elsewhere.\ndownload.external_link=Visit Download Website\ndownload.failed=Failed to download \"%1$s\", response code: %2$d.\ndownload.failed.empty=No versions are available. Please click here to go back.\ndownload.failed.no_code=Failed to download\ndownload.failed.refresh=Failed to fetch version list. Please click here to retry.\ndownload.game=New Game\ndownload.provider.bmclapi=BMCLAPI\ndownload.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/\ndownload.provider.mojang=Official\ndownload.provider.mojang.desc=OptiFine is provided by BMCLAPI\ndownload.provider.official=From Official Sources\ndownload.provider.official.desc=Latest, but may load slowly\ndownload.provider.balanced=From Fastest Available\ndownload.provider.balanced.desc=Balanced, but may not be the latest\ndownload.provider.mirror=From Mirror\ndownload.provider.mirror.desc=Fast, but may not be the latest\ndownload.java=Downloading Java\ndownload.java.process=Java Download Process\ndownload.javafx=Downloading dependencies for the launcher...\ndownload.javafx.notes=We are currently downloading dependencies for HMCL from the Internet.\\n\\\n   \\n\\\n   You can click \"Change Download Source\" to choose the download source, or\\nclick \"Cancel\" to stop and exit.\\n\\\n   Note: If your download speed is too slow, you can try switching to another mirror.\ndownload.javafx.component=Downloading module \"%s\"\ndownload.javafx.prepare=Preparing to download\ndownload.speed.byte_per_second=%d B/s\ndownload.speed.kibibyte_per_second=%.1f KiB/s\ndownload.speed.megabyte_per_second=%.1f MiB/s\n\nexception.access_denied=HMCL is unable to access the file \"%s\". It may be locked by another process.\\n\\\n   \\n\\\n   For Windows users, you can open \"Resource Monitor\" to check if another process is currently using it. If so, you can try again after terminating that process.\\n\\\n   If not, please check if your user account has adequate permissions to access it.\nexception.artifact_malformed=Cannot verify the integrity of the downloaded files.\nexception.ssl_handshake=Failed to establish SSL connection because the SSL certificate is missing from the current Java installation. You can try opening HMCL with another Java installation and try again.\nexception.dns.pollution=Failed to establish an SSL connection. DNS resolution may be incorrect. Please try changing your DNS server or using a proxy service.\n\nextension.bat=Windows Batch File\nextension.png=Image File\nextension.ps1=Windows PowerShell Script\nextension.sh=Shell Script\nextension.command=macOS Shell Script\n\nextension.datapack=Datapack Archive\nextension.mod=Mod File\nextension.modloader.installer=Mod Loader Installer\nextension.resourcepack=Resource Pack Archive\nextension.schematic=Schematic File\nextension.world=World Archive\n\nfatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher cannot create the HMCL directory (%s). Please move HMCL to another location and reopen it.\nfatal.javafx.incomplete=The JavaFX environment is incomplete.\\n\\\n   Please try replacing your Java or reinstalling OpenJFX.\nfatal.javafx.missing=Missing JavaFX environment. Please open Hello Minecraft! Launcher with Java, which includes OpenJFX.\nfatal.config_change_owner_root=You are using the root account to open Hello Minecraft! Launcher. This may prevent you from opening HMCL with another account in the future.\\n\\\n   Do you still want to continue?\nfatal.config_in_temp_dir=You are opening Hello Minecraft! Launcher in a temporary directory. Your settings and game data may be lost.\\n\\\n   It is recommended to move HMCL to another location and reopen it.\\n\\\n   Do you still want to continue?\nfatal.config_loading_failure=Cannot load configuration files.\\n\\\n   Please make sure that Hello Minecraft! Launcher has read and write access to \"%s\" and the files in it.\\n\\\n   For macOS, try putting HMCL somewhere with permissions other than \"Desktop\", \"Downloads\", and \"Documents\" and try again.\nfatal.config_loading_failure.unix=Hello Minecraft! Launcher could not load the configuration file because it was created by user \"%1$s\".\\n\\\n   Please open HMCL as root user (not recommended), or execute the following command in the terminal to change the ownership of the configuration file to the current user:\\n%2$s\nfatal.config_unsupported_version=The current configuration file was created by a newer version of Hello Minecraft! Launcher, and this version of HMCL cannot load it properly.\\n\\\n   Please update and restart HMCL.\\n\\\n   Before updating the launcher, any settings you modify will not be saved.\nfatal.mac_app_translocation=Hello Minecraft! Launcher is isolated to a temporary directory by the OS due to macOS security mechanisms.\\n\\\n   Please move HMCL to a different directory before attempting to open. Otherwise, your settings and game data may be lost after restarting.\\n\\\n   Do you still want to continue?\nfatal.migration_requires_manual_reboot=Hello Minecraft! Launcher has been upgraded. Please restart the launcher.\nfatal.apply_update_failure=We are sorry, but Hello Minecraft! Launcher is unable to update automatically.\\n\\\n   \\n\\\n   You can update manually by downloading a newer launcher version from %s.\\n\\\n   If the problem persists, please consider reporting this to us.\nfatal.apply_update_need_win7=Hello Minecraft! Launcher cannot automatically update on Windows XP/Vista.\\n\\\n   \\n\\\n   You can update manually by downloading a newer launcher version from %s.\nfatal.deprecated_java_version=HMCL will require Java 17 or later to run in the future, but will still support launching games with Java 6~16.\\n\\\n\\n\\\nIt is recommended to install the latest version of Java to ensure that HMCL works properly.\\n\\\n\\n\\\nYou can continue to keep the old version of Java. HMCL can recognize and manage multiple Java versions and will automatically select the appropriate Java for you based on the game version.\nfatal.deprecated_java_version.update=HMCL will require Java 17 or later to run in the future. Please install the latest version of Java to ensure that HMCL can complete the upgrade.\\n\\\n\\n\\\nYou can continue to keep the old version of Java.\\\nHMCL can recognize and manage multiple Java versions and will automatically select the appropriate Java for you based on the game version.\nfatal.deprecated_java_version.download_link=Download Java %d\nfatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive, some features might not be working. Please try updating your Java or moving the launcher to another directory.\nfatal.illegal_char=Your working directory contains an illegal character \"=\". You will not be able to use authlib-injector or change the skin of your offline account.\nfatal.unsupported_platform=Minecraft is not fully supported on your platform yet, so you may experience missing features or even be unable to launch the game.\\n\\\n   \\n\\\n   If you cannot launch Minecraft 1.17 and later, you can try switching the \"Renderer\" to \"Mesa LLVMpipe\" in \"Global/Instance-specific Settings → Advanced Settings\" to use CPU rendering for better compatibility.\nfatal.unsupported_platform.loongarch=Hello Minecraft! Launcher has provided support for the Loongson platform.\\n\\\n   If you encounter problems when playing a game, you can visit https://docs.hmcl.net/groups.html for help.\nfatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher has provided support for the Apple silicon platform, using native ARM Java to launch games to get a smoother gaming experience.\\n\\\n   If you encounter problems when playing a game, launching the game with Java based on x86-64 architecture may offer better compatibility.\nfatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided native support for the Windows on Arm platform. If you encounter problems when playing a game, please try launching the game with Java based on x86 architecture.\\n\\\n   \\n\\\n   If you are using the <b>Qualcomm</b> platform, you may need to install the <a href=\"ms-windows-store://pdp/?productid=9NQPSL29BFFF\">OpenGL Compatibility Pack</a> before playing games.\\n\\\n   Click the link to navigate to the Microsoft Store and install the compatibility pack.\n\nfile=File\n\nfolder.config=Configs\nfolder.game=Working Directory\nfolder.logs=Logs\nfolder.mod=Mods\nfolder.resourcepacks=Resource Packs\nfolder.shaderpacks=Shader Packs\nfolder.saves=Worlds\nfolder.schematics=Schematics\nfolder.screenshots=Screenshots\nfolder.world=World Directory\n\ngame=Games\ngame.crash.feedback=<b>Please do not share screenshots or photos of this interface with others!</b> If you ask for help from others, please click <b>\"Export Crash Logs\"</b> and send the exported file to others for analysis.\ngame.crash.info=Crash Info\ngame.crash.reason=Crash Cause\ngame.crash.reason.analyzing=Analyzing...\ngame.crash.reason.multiple=Several reasons detected:\\n\\n\ngame.crash.reason.block=The game crashed because of a block in the world.\\n\\\n   \\n\\\n   You can try removing this block using the <a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">map editors</a> or deleting the mod that added it.\\n\\\n   \\n\\\n   Block Type: %1$s\\n\\\n   Location: %2$s\ngame.crash.reason.bootstrap_failed=The game crashed because of the \"%1$s\" mod.\\n\\\n   \\n\\\n   You can try deleting or updating it.\ngame.crash.reason.config=The game crashed because the mod \"%1$s\" could not parse its config file \"%2$s\".\ngame.crash.reason.debug_crash=The game crashed because you triggered it manually. So you probably know why :)\ngame.crash.reason.duplicated_mod=The game cannot continue to run because of duplicate mods \"%1$s\".\\n\\\n   \\n\\\n   %2$s\\n\\\n   \\n\\\n   Each mod can only be installed once. Please delete the duplicate mod and try again.\ngame.crash.reason.entity=The game crashed because of an entity in the world.\\n\\\n   \\n\\\n   You can try removing the entity using the <a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">map editors</a> or deleting the mod that added it.\\n\\\n   \\n\\\n   Entity Type: %1$s\\n\\\n   Location: %2$s\ngame.crash.reason.modmixin_failure=The game crashed because some mods failed to inject.\\n\\\n   \\n\\\n   This generally means that the mod has a bug or is incompatible with the current environment.\\n\\\n   \\n\\\n   You can check the log to find the error mod.\ngame.crash.reason.mod_repeat_installation=The game crashed because of duplicate mods.\\n\\\n   \\n\\\n   Each mod can only be installed once. Please delete the duplicate mod and then relaunch the game.\ngame.crash.reason.forge_error=Forge/NeoForge may have provided error information.\\n\\\n   \\n\\\n   You can view the log and make corresponding processing according to the log information in the error report.\\n\\\n   \\n\\\n   If you do not see the error message, you can view the error report to understand how the error occurred.\\n\\\n   %1$s\ngame.crash.reason.mod_resolution0=The game crashed because of some mod problems. You can check the logs to find the faulty mod(s).\ngame.crash.reason.mixin_apply_mod_failed=The game crashed because the mixin could not be applied to the mod \"%1$s\".\\n\\\n   \\n\\\n   You can try deleting or updating the mod to resolve the problem.\ngame.crash.reason.java_version_is_too_high=The game crashed because the Java version is too new to continue running.\\n\\\n   \\n\\\n   Please use the previous major Java version in \"Global/Instance-specific Settings → Java\" and then launch the game.\\n\\\n   \\n\\\n   If not, you can download it from <a href=\"https://www.java.com/download/\">java.com (Java 8)</a> or <a href=\"https://bell-sw.com/pages/downloads/#downloads\">BellSoft Liberica Full JRE (Java 17)</a> and other distributions to download and install one (restart the launcher after installation).\ngame.crash.reason.need_jdk11=The game crashed because of an inappropriate Java version.\\n\\\n   \\n\\\n   You need to download and install Java 11, and set it in \"Global/Instance-specific Settings → Java\".\ngame.crash.reason.mod_name=The game crashed because of mod filename problems.\\n\\\n   \\n\\\n   Mod file names should use only English letters (A~Z, a~z), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half-width.\\n\\\n   \\n\\\n   Please navigate to the mod directory and change all non-compliant mod file names using the compliant characters above.\ngame.crash.reason.incomplete_forge_installation=The game cannot continue to run because of an incomplete Forge/NeoForge installation.\\n\\\n   \\n\\\n   Please reinstall Forge/NeoForge in \"Edit Instance → Loaders\".\ngame.crash.reason.fabric_version_0_12=Fabric Loader 0.12 or later is incompatible with currently installed mods. You need to downgrade it to 0.11.7.\ngame.crash.reason.fabric_warnings=The Fabric Loader warned:\\n\\\n   \\n\\\n   %1$s\ngame.crash.reason.file_already_exists=The game crashed because the file \"%1$s\" already exists.\\n\\\n   \\n\\\n   You can try backing up and deleting that file, then relaunching the game.\ngame.crash.reason.file_changed=The game crashed because the file verification failed.\\n\\\n   \\n\\\n   If you modified the Minecraft.jar file, you will need to revert the modification or re-download the game.\ngame.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource/texture packs.\\n\\\n   \\n\\\n   Please disable the mods, shaders, or resource/texture packs you are using and then try again.\ngame.crash.reason.graphics_driver=The game crashed because of a problem with your graphics driver.\\n\\\n   \\n\\\n   Please try again after updating your graphics driver to the latest version.\\n\\\n   \\n\\\n   If your computer has a dedicated graphics card, you need to check whether the game uses integrated/core graphics. If so, please open the launcher using your dedicated graphics card. If the problem persists, you probably should consider using a new graphics card or a new computer.\\n\\\n   \\n\\\n   If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or earlier requires <a href=\"https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html\">Java 1.8.0_51 or earlier</a> for Intel(R) Core(TM) 3000 processor series or earlier.\ngame.crash.reason.macos_failed_to_find_service_port_for_display=The game cannot continue because the OpenGL window on the Apple silicon platform failed to initialize.\\n\\\n   \\n\\\n   For this problem, HMCL does not have direct solutions at the moment. Please try opening any browser and going fullscreen, then return to HMCL, launch the game, and <b>quickly return to the browser page</b> before the game window pops up, wait for the game window to appear, and then switch back to the game window.\ngame.crash.reason.illegal_access_error=The game crashed because of some mod(s).\\n\\\n   \\n\\\n   If you know this: \"%1$s\", you can update or delete the mod(s) and then try again.\ngame.crash.reason.install_mixinbootstrap=The game crashed because of the missing MixinBootstrap.\\n\\\n   \\n\\\n   You can try installing <a href=\"https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap\">MixinBootstrap</a> to resolve the problem. If it crashes after installation, try adding an exclamation mark (!) in front of the file name of this mod to try to resolve the problem.\ngame.crash.reason.optifine_is_not_compatible_with_forge=The game crashed because OptiFine is not compatible with the current Forge installation.\\n\\\n   \\n\\\n   Please navigate to the <a href=\"https://optifine.net/downloads\">official website of OptiFine</a>, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in \"Edit Instance → Loaders\".\\n\\\n   \\n\\\n   After testing, we believe that too high or too low OptiFine versions may cause crashes.\ngame.crash.reason.mod_files_are_decompressed=The game crashed because the mod file was extracted.\\n\\\n   \\n\\\n   Please put the entire mod file directly into the mod directory!\\n\\\n   \\n\\\n   If extraction causes errors in the game, please delete the extracted mod in the mod directory and then launch the game.\ngame.crash.reason.shaders_mod=The game crashed because of both OptiFine and Shaders mod are installed at the same time.\\n\\\n   \\n\\\n   Just remove the Shader mod because OptiFine has built-in support for shaders.\ngame.crash.reason.rtss_forest_sodium=The game crashed because the RivaTuner Statistical Server (RTSS) is not compatible with Sodium.\\n\\\n   \\n\\\n   Click <a href=\"https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\">here</a> for more details.\ngame.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The game crashed because you installed too many mods and exceeded the game ID limit.\\n\\\n   \\n\\\n   Please try installing <a href=\"https://www.curseforge.com/minecraft/mc-mods/jeid\">JEID</a> or deleting some large mods.\ngame.crash.reason.night_config_fixes=The game crashed because of some problems with Night Config.\\n\\\n   \\n\\\n   You can try installing the <a href=\"https://www.curseforge.com/minecraft/mc-mods/night-config-fixes\">Night Config Fixes</a> mod, which may help you with this problem.\\n\\\n   \\n\\\n   For more information, visit the <a href=\"https://github.com/Fuzss/nightconfigfixes\">GitHub repository</a> of this mod.\ngame.crash.reason.optifine_causes_the_world_to_fail_to_load=The game may not continue to run because of the OptiFine.\\n\\\n   \\n\\\n   This problem only occurs in a specific OptiFine version. You can try changing the OptiFine version in \"Edit Instance → Loaders\".\ngame.crash.reason.jdk_9=The game crashed because the Java version is too new for this instance.\\n\\\n   \\n\\\n   You need to download and install Java 8 and choose it in \"Global/Instance-specific Settings → Java\".\ngame.crash.reason.jvm_32bit=The game crashed because the current memory allocation exceeded the 32-bit JVM limit.\\n\\\n   \\n\\\n   If your OS is 64-bit, please install and use a 64-bit Java version. Otherwise, you may need to reinstall a 64-bit OS or get a moderner computer.\\n\\\n   \\n\\\n   Or, you can disable the \"Automatically Allocate\" option in \"Global/Instance-specific Settings → Memory\" and set the maximum memory allocation size to 1024 MiB or below.\ngame.crash.reason.loading_crashed_forge=The game crashed because of the mod \"%1$s\" (%2$s).\\n\\\n   \\n\\\n   You can try deleting or updating it.\ngame.crash.reason.loading_crashed_fabric=The game crashed because of the mod \"%1$s\".\\n\\\n   \\n\\\n   You can try deleting or updating it.\ngame.crash.reason.mac_jdk_8u261=The game crashed because your current Forge or OptiFine version is incompatible with your Java installation.\\n\\\n   \\n\\\n   Please try updating Forge and OptiFine, or try using Java 8u251 or previous versions.\ngame.crash.reason.forge_repeat_installation=The game crashed because of a duplicate Forge installation. <a href=\"https://github.com/HMCL-dev/HMCL/issues/1880\">This is a known problem</a>\\n\\\n   \\n\\\n   It is recommended to submit feedback on GitHub along with this log so we can find more clues and resolve the issue.\\n\\\n   \\n\\\n   Currently you can uninstall Forge and reinstall it in \"Edit Instance → Loaders\".\ngame.crash.reason.optifine_repeat_installation=The game crashed because of a duplicate OptiFine installation.\\n\\\n   \\n\\\n   Please delete OptiFine in the mod directory or uninstall it in \"Edit Instance → Loaders\".\ngame.crash.reason.memory_exceeded=The game crashed because too much memory was allocated for a small page file.\\n\\\n   \\n\\\n   You can try disabling the \"Automatically Allocate\" option in \"Global/Instance-specific Settings → Memory\" and adjust the value till the game launches.\\n\\\n   \\n\\\n   You can also try increasing the page file size in system settings.\ngame.crash.reason.mod=The game crashed because of the mod \"%1$s\".\\n\\\n   \\n\\\n   You may update or delete the mod and then try again.\ngame.crash.reason.mod_resolution=The game cannot continue to run because of mod dependency problems.\\n\\\n   \\n\\\n   Fabric provided the following details:\\n\\\n   \\n\\\n   %1$s\ngame.crash.reason.forgemod_resolution=The game cannot continue to run because of mod dependency problems.\\n\\\n   \\n\\\n   Forge/NeoForge provided the following details:\\n\\\n   %1$s\ngame.crash.reason.forge_found_duplicate_mods=The game crashed because of a duplicate mod problem. Forge/NeoForge provided the following information:\\n\\\n   %1$s\ngame.crash.reason.mod_resolution_collection=The game crashed because the mod version is not compatible.\\n\\\n   \\n\\\n   \"%1$s\" requires mod \"%2$s\".\\n\\\n   \\n\\\n   You need to upgrade or downgrade \"%3$s\" before continuing.\ngame.crash.reason.mod_resolution_conflict=The game crashed because of conflicting mods.\\n\\\n   \\n\\\n   \"%1$s\" is incompatible with \"%2$s\".\ngame.crash.reason.mod_resolution_missing=The game crashed because some dependent mods were not installed.\\n\\\n   \\n\\\n   \"%1$s\" requires mod \"%2$s\".\\n\\\n   \\n\\\n   This means that you have to download and install \"%2$s\" first to continue playing.\ngame.crash.reason.mod_resolution_missing_minecraft=The game crashed because a mod is incompatible with the current Minecraft version.\\n\\\n   \\n\\\n   \"%1$s\" requires Minecraft version %2$s.\\n\\\n   \\n\\\n   If you want to play with this mod version installed, you should change the game version of your instance.\\n\\\n   \\n\\\n   Otherwise, you should install a version that is compatible with this Minecraft version.\ngame.crash.reason.mod_resolution_mod_version=%1$s (Version: %2$s)\ngame.crash.reason.mod_resolution_mod_version.any=%1$s (Any Version)\ngame.crash.reason.modlauncher_8=The game crashed because your current Forge version is not compatible with your Java installation. Please try updating Forge.\ngame.crash.reason.no_class_def_found_error=The game cannot continue to run because of incomplete code.\\n\\\n   \\n\\\n   Your game instance is missing \"%1$s\". This might be because a mod is missing, an incompatible mod is installed, or some files are corrupted.\\n\\\n   \\n\\\n   You may need to reinstall the game and all mods or ask someone for help.\ngame.crash.reason.no_such_method_error=The game cannot continue to run because of incomplete code.\\n\\\n   \\n\\\n   Your game instance might be missing a mod, installed an incompatible mod, or some files might be corrupted.\\n\\\n   \\n\\\n   You may need to reinstall the game and all mods or ask someone for help.\ngame.crash.reason.opengl_not_supported=The game crashed because your graphics driver does not support OpenGL.\\n\\\n   \\n\\\n   If you are streaming the game over the Internet or using a remote desktop environment, please play the game on your local machine.\\n\\\n   \\n\\\n   Or, you can update your graphic driver to the latest version and then try again.\\n\\\n   \\n\\\n   If your computer has a dedicated graphics card, please make sure the game is indeed using it for rendering. If the problem persists, please consider getting a new graphics card or a new computer.\ngame.crash.reason.openj9=The game is unable to run on an OpenJ9 JVM. Please switch to a Java that uses the Hotspot JVM in \"Global/Instance-specific Settings → Java\" and relaunch the game. If you do not have one, you can download one.\ngame.crash.reason.out_of_memory=The game crashed because the computer ran out of memory.\\n\\\n   \\n\\\n   Maybe there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in \"Global/Instance-specific Settings → Memory\".\\n\\\n   \\n\\\n   If you still encounter these problems, you may need a better computer.\ngame.crash.reason.resolution_too_high=The game crashed because the resource/texture pack resolution is too high.\\n\\\n   \\n\\\n   You should switch to a resource/texture pack with lower resolution or consider buying a better graphics card with more VRAM.\ngame.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking \"Logs\".\\n\\\n   \\n\\\n   There are some keywords that might contain some mod names. You can search them online to figure out the problem yourself.\\n\\\n   \\n\\\n   %s\ngame.crash.reason.too_old_java=The game crashed because you are using an outdated Java version.\\n\\\n   \\n\\\n   You need to switch to a newer Java version (%1$s) in \"Global/Instance-specific Settings → Java\" and then relaunch the game. You can download Java from <a href=\"https://learn.microsoft.com/java/openjdk/download\">here</a>.\ngame.crash.reason.unknown=We are not able to figure out why the game crashed. Please refer to the game logs.\ngame.crash.reason.unsatisfied_link_error=Failed to launch Minecraft because of missing libraries: %1$s.\\n\\\n   \\n\\\n   If you have edited the native library path, please make sure these libraries do exist. Or, please try launching again after reverting it to default.\\n\\\n   \\n\\\n   If you have not, please check if you have missing dependency mods.\\n\\\n   \\n\\\n   Otherwise, if you believe this is caused by HMCL, please provide feedback to us.\ngame.crash.title=Game Crashed\ngame.directory=Game Path\ngame.version=Game Instance\n\nhelp=Help\nhelp.doc=Hello Minecraft! Launcher Documentation\nhelp.detail=For datapack and modpack makers.\n\ninput.email=The username must be an email address.\ninput.number=The input must be numbers.\ninput.not_empty=This is a required field.\ninput.url=The input must be a valid URL.\n\ninstall=New Instance\ninstall.change_version=Change Version\ninstall.change_version.confirm=Are you sure you want to switch %1$s from version %2$s to %3$s?\ninstall.change_version.process=Change Version Process\ninstall.failed=Failed to install\ninstall.failed.downloading=Failed to download some required files.\ninstall.failed.downloading.detail=Failed to download file: %s\ninstall.failed.downloading.timeout=Download timeout when fetching: %s\ninstall.failed.install_online=Failed to identify the provided file. If you are installing a mod, navigate to the \"Mods\" page.\ninstall.failed.malformed=The downloaded files are corrupted. You can try resolving this problem by switching to another download source in \"Settings → Download → Download Source\".\ninstall.failed.optifine_conflict=Cannot install both OptiFine and Fabric on Minecraft 1.13 or later.\ninstall.failed.optifine_forge_1.17=For Minecraft 1.17.1, Forge is only compatible with OptiFine H1 pre2 or later. You can install them by checking \"Snapshots\" when choosing an OptiFine version in HMCL.\ninstall.failed.version_mismatch=This loader requires the game version %1$s, but the installed one is %2$s.\ninstall.installer.change_version=%s Incompatible\ninstall.installer.choose=Choose Your %s Version\ninstall.installer.cleanroom=Cleanroom\ninstall.installer.depend=Requires %s\ninstall.installer.do_not_install=Do not install\ninstall.installer.fabric=Fabric\ninstall.installer.fabric-api=Fabric API\ninstall.installer.legacyfabric=Legacy Fabric\ninstall.installer.legacyfabric-api=Legacy Fabric API\ninstall.installer.fabric-quilt-api.warning=%1$s is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the %1$s will not function. If you do want to change the directory, you should reinstall it.\ninstall.installer.forge=Forge\ninstall.installer.neoforge=NeoForge\ninstall.installer.game=Minecraft\ninstall.installer.incompatible=Incompatible with %s\ninstall.installer.install=Installing %s\ninstall.installer.install_offline=Install/Update from Local File\ninstall.installer.install_offline.tooltip=We support using the local (Neo)Forge, Cleanroom, and OptiFine installer.\ninstall.installer.install_online=Online Install\ninstall.installer.install_online.tooltip=We currently support Forge, NeoForge, Cleanroom, OptiFine, Fabric, Legacy Fabric, Quilt, and LiteLoader.\ninstall.installer.liteloader=LiteLoader\ninstall.installer.not_installed=Not installed\ninstall.installer.optifine=OptiFine\ninstall.installer.quilt=Quilt\ninstall.installer.quilt-api=QSL/QFAPI\ninstall.installer.version=%s\ninstall.installer.external_version=%s (Installed by external process, which cannot be configured)\ninstall.installing=Installing\ninstall.modpack=Install Modpack\ninstall.modpack.installation=Modpack Installation\ninstall.name.invalid=The name contains special characters (such as emoji or CJK characters).\\nIt is recommended to change the name to include only English letters, numbers, and underscores to avoid potential issues when launching the game.\\nDo you want to proceed with the installation?\ninstall.new_game=Install Instance\ninstall.new_game.already_exists=This instance name already exists. Please use another name.\ninstall.new_game.current_game_version=Current Instance Version\ninstall.new_game.installation=Instance Installation\ninstall.new_game.malformed=Invalid name.\ninstall.select=Choose operation\ninstall.success=Successfully installed.\n\njava.add=Add\njava.add.failed=This Java is invalid or incompatible with the current platform.\njava.disable=Disable Java\njava.disable.confirm=Are you sure you want to disable this Java?\njava.disabled.management=Disabled Java\njava.disabled.management.remove=Remove this Java from the list\njava.disabled.management.restore=Re-enable this Java\njava.download=Download\njava.download.banshanjdk-8=Download Banshan JDK 8\njava.download.load_list.failed=Failed to load version list\njava.download.more=More Java distributions\njava.download.title=Download Java\njava.download.prompt=Please choose the Java version you want to download:\njava.download.distribution=Distribution\njava.download.version=Version\njava.download.packageType=Package Type\njava.management=Java Management\njava.info.architecture=Architecture\njava.info.vendor=Vendor\njava.info.version=Version\njava.info.disco.distribution=Distribution\njava.install=Install Java\njava.install.archive=Source Path\njava.install.failed.exists=This name is already owned\njava.install.failed.invalid=This archive is not a valid Java installation package, so it cannot be installed.\njava.install.failed.unsupported_platform=This Java is not compatible with the current platform, so it cannot be installed.\njava.install.name=Name\njava.install.warning.invalid_character=Illegal character in name\njava.installing=Installing Java\njava.uninstall=Uninstall Java\njava.uninstall.confirm=Are you sure you want to uninstall this Java? This action cannot be undone!\n\nlang.default=Use System Locales\n\nlaunch.advice=%s Do you still want to continue to launch?\nlaunch.advice.multi=The following problems were detected:\\n\\n%s\\n\\nThese problems may prevent you from launching the game or affect gaming experience.\\nDo you still want to continue to launch?\nlaunch.advice.java.auto=The current Java version is not compatible with the instance.\\n\\nClick \"Yes\" to automatically choose the most compatible Java version. Or, you can navigate to \"Global/Instance-specific Settings → Java\" to choose one yourself.\nlaunch.advice.java.modded_java_7=Minecraft 1.7.2 and previous versions require Java 7 or earlier.\nlaunch.advice.cleanroom=Cleanroom %2$s can only be run on Java %1$s or later. Please use Java %1$s or later versions.\nlaunch.advice.corrected=We have resolved the Java problem. If you still want to use your choice of Java version, you can disable \"Do not check JVM compatibility\" in \"Global/Instance-specific Settings → Advanced Settings\".\nlaunch.advice.uncorrected=If you still want to use your choice of Java version, you can disable \"Do not check JVM compatibility\" in \"Global/Instance-specific Settings → Advanced Settings\".\nlaunch.advice.different_platform=The 64-bit Java version is recommended for your device, but you have installed a 32-bit one.\nlaunch.advice.forge2760_liteloader=Forge version 2760 is not compatible with LiteLoader. Please consider upgrading Forge to version 2773 or later.\nlaunch.advice.forge28_2_2_optifine=Forge version 28.2.2 or later is not compatible with OptiFine. Please consider downgrading Forge to version 28.2.1 or earlier.\nlaunch.advice.forge37_0_60=Forge versions prior to 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or later, or launch the game with Java 16.\nlaunch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or later versions.\nlaunch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions prior to 1.8.0_51. Please install the latest Java 8 version.\nlaunch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later. Please use Java 8 instead.\nlaunch.advice.modded_java=Some mods may not be compatible with newer Java versions. It is recommended to use Java %1$s to launch Minecraft %2$s.\nlaunch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge.\nlaunch.advice.newer_java=You are using an older Java version to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.\nlaunch.advice.not_enough_space=You have allocated a memory size larger than the actual %d MiB of memory installed on your computer. You may experience degraded performance or even be unable to launch the game.\nlaunch.advice.require_newer_java_version=The current game version requires Java %s, but we could not find one. Do you want to download one now?\nlaunch.advice.too_large_memory_for_32bit=You have allocated a memory size larger than the memory limitation of the 32-bit Java installation. You may be unable to launch the game.\nlaunch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or earlier only supports Java 8 for the Linux x86-64 platform because the later versions cannot load 32-bit native libraries like liblwjgl.so\\n\\nPlease download it from java.com or install OpenJDK 8.\nlaunch.advice.vanilla_x86.translation=Minecraft is not fully supported on your platform, so you may experience missing features or even be unable to launch the game.\\nYou can download Java for the <b>x86-64</b> architecture <a href=\"https://learn.microsoft.com/java/openjdk/download\">here</a> for a full gaming experience.\nlaunch.advice.unknown=The game cannot be launched due to the following reasons:\nlaunch.failed=Failed to launch\nlaunch.failed.cannot_create_jvm=We are unable to create a JVM. It may be caused by incorrect JVM arguments. You can try resolving it by removing all arguments you added in \"Global/Instance-specific Settings → Advanced Settings → JVM Options\".\nlaunch.failed.creating_process=We are unable to create a new process. Please check your Java path.\\n\nlaunch.failed.command_too_long=The command length exceeds the maximum length of a batch script. Please try exporting it as a PowerShell script.\nlaunch.failed.decompressing_natives=Failed to extract native libraries.\\n\nlaunch.failed.download_library=Failed to download libraries \"%s\".\nlaunch.failed.executable_permission=Failed to make the launch script executable.\nlaunch.failed.execution_policy=Set Execution Policy\nlaunch.failed.execution_policy.failed_to_set=Failed to set execution policy\nlaunch.failed.execution_policy.hint=The current execution policy prevents the execution of PowerShell scripts.\\n\\nClick \"OK\" to allow the current user to execute PowerShell scripts, or click \"Cancel\" to keep it as it is.\nlaunch.failed.exited_abnormally=Game crashed. Please refer to the crash log for more details.\nlaunch.failed.java_version_too_low=The Java version you specified is too low. Please reset the Java version.\nlaunch.failed.no_accepted_java=Failed to find a compatible Java version, do you want to launch the game with the default Java?\\nClick \"Yes\" to launch the game with the default Java.\\nOr, you can navigate to \"Global/Instance-specific Settings → Java\" to choose one yourself.\nlaunch.failed.sigkill=Game was forcibly terminated by the user or system.\nlaunch.state.dependencies=Resolving dependencies\nlaunch.state.done=Launched\nlaunch.state.java=Checking Java version\nlaunch.state.logging_in=Logging in\nlaunch.state.modpack=Downloading required files\nlaunch.state.waiting_launching=Waiting for the game to launch\nlaunch.invalid_java=Invalid Java path. Please reset the Java path.\n\nlauncher=Launcher\nlauncher.agreement=ToS and EULA\nlauncher.agreement.accept=Accept\nlauncher.agreement.decline=Decline\nlauncher.agreement.hint=You must agree to the EULA to use this software.\nlauncher.april_fools.switch_lzh=HMCL Launcher now supports Classical Chinese, do you want to switch to Classical Chinese mode?\\nYou can switch back to English in \"置設 → 貫用 → 文\".\nlauncher.april_fools.switch_lzh.confirm=Confirm switching to Classical Chinese mode?\\nClick \"OK\" to exit HMCL and restart it in Classical Chinese mode; click \"Cancel\" to keep the current language and enter HMCL homepage.\nlauncher.background=Background Image\nlauncher.background.choose=Choose background image\nlauncher.background.classic=Classic\nlauncher.background.default=Default\nlauncher.background.default.tooltip=Or \"background.png/.jpg/.gif/.webp\" and the images in the \"bg\" directory\nlauncher.background.network=From URL\nlauncher.background.paint=Solid Color\nlauncher.cache_directory=Cache Directory\nlauncher.cache_directory.clean=Clear Cache\nlauncher.cache_directory.choose=Choose cache directory\nlauncher.cache_directory.default=Default (\"%APPDATA%/.minecraft\" or \"~/.minecraft\")\nlauncher.cache_directory.disabled=Disabled\nlauncher.cache_directory.invalid=Failed to create a cache directory, falling back to default.\nlauncher.contact=Contact Us\nlauncher.crash=Hello Minecraft! Launcher has encountered a fatal error! Please copy the following log and ask for help on our Discord, QQ group, GitHub, or other Minecraft forum.\nlauncher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error because your Java is corrupted. Please uninstall your Java and download a suitable Java <a href=\"https://bell-sw.com/pages/downloads/#downloads\">here</a>.\nlauncher.crash.hmcl_out_dated=Hello Minecraft! Launcher has encountered a fatal error! Your launcher is outdated. Please update your launcher!\nlauncher.update_java=Please update your Java version.\n\nlibraries.download=Downloading Libraries\n\nlogin.empty_username=You have not set your username yet!\nlogin.enter_password=Please enter your password.\n\nlogwindow.show_lines=Show Row Number\nlogwindow.terminate_game=Kill Game Process\nlogwindow.title=Log\nlogwindow.help=You can navigate to the HMCL community and find others for help.\nlogwindow.autoscroll=Auto-scroll\nlogwindow.export_game_crash_logs=Export Crash Logs\nlogwindow.export_dump=Export Game Stack Dump\nlogwindow.export_dump.no_dependency=Your Java does not contain the dependencies to create the stack dump. Please join our Discord or QQ group for help.\n\nmain_page=Home\n\nmessage.cancelled=Operation Canceled\nmessage.confirm=Confirm\nmessage.copied=Copied to clipboard\nmessage.default=Default\nmessage.doing=Please wait\nmessage.downloading=Downloading\nmessage.error=Error\nmessage.failed=Operation Failed\nmessage.info=Information\nmessage.success=Operation successfully completed\nmessage.unknown=Unknown\nmessage.warning=Warning\nmessage.question=Question\n\nmodpack=Modpacks\nmodpack.choose=Choose Modpack\nmodpack.choose.local=Import from Local File\nmodpack.choose.local.detail=You can drag the modpack file here.\nmodpack.choose.remote=Download from URL\nmodpack.choose.remote.detail=A direct download link to the remote modpack file is required.\nmodpack.choose.repository=Download Modpack from CurseForge or Modrinth\nmodpack.choose.repository.detail=You can choose the desired modpack on the next page.\nmodpack.choose.remote.tooltip=Please enter your modpack URL\nmodpack.completion=Downloading dependencies\nmodpack.desc=Describe your modpack, including an introduction and probably some changelog. Markdown and images from URL are currently supported.\nmodpack.description=Modpack Description\nmodpack.download=Download Modpacks\nmodpack.download.title=Download Modpack - %1s\nmodpack.enter_name=Enter a name for this modpack.\nmodpack.export=Export as Modpack\nmodpack.export.as=Export Modpack As...\nmodpack.file_api=Modpack URL Prefix\nmodpack.files.blueprints=BuildCraft Blueprints\nmodpack.files.config=Mod Configuration Files\nmodpack.files.dumps=NEI Debug Output Files\nmodpack.files.hmclversion_cfg=Launcher Configuration File\nmodpack.files.liteconfig=LiteLoader Related Files\nmodpack.files.mods=Mods\nmodpack.files.mods.voxelmods=VoxelMods Options\nmodpack.files.options_txt=Minecraft Option File\nmodpack.files.optionsshaders_txt=Shader Option File\nmodpack.files.resourcepacks=Resource/Texture Packs\nmodpack.files.saves=Worlds\nmodpack.files.scripts=MineTweaker Configuration File\nmodpack.files.servers_dat=Server List File\nmodpack.installing=Installing modpack\nmodpack.installing.given=Installing %s modpack\nmodpack.introduction=Curse, Modrinth, MultiMC, and MCBBS modpacks are currently supported.\nmodpack.invalid=Invalid modpack, you can try downloading it again.\nmodpack.mismatched_type=Modpack type mismatched, the current instance is a(n) %1$s type, but the provided one is %2$s type.\nmodpack.name=Modpack Name\nmodpack.not_a_valid_name=Invalid modpack name.\nmodpack.origin=Source\nmodpack.origin.url=Official Website\nmodpack.origin.mcbbs=MCBBS\nmodpack.origin.mcbbs.prompt=Post ID\nmodpack.scan=Parsing Modpack Index\nmodpack.task.install=Import Modpack\nmodpack.task.install.error=Failed to identify this modpack. We currently only support Curse, Modrinth, MultiMC, and MCBBS modpacks.\nmodpack.type.curse=Curse\nmodpack.type.curse.error=Failed to download dependencies. Please try again or use a proxy server.\nmodpack.type.curse.not_found=Some dependencies are no longer available. Please try installing a newer modpack version.\nmodpack.type.manual.warning=The modpack is manually packaged by the publisher, which may already contain a launcher. It is recommended to try extracting the modpack and running the game with its own launcher. HMCL can still import it, with no guarantee of its usability. Still continue?\nmodpack.type.mcbbs=MCBBS\nmodpack.type.mcbbs.export=Can be imported by Hello Minecraft! Launcher\nmodpack.type.modrinth=Modrinth\nmodpack.type.modrinth.export=Can be imported by popular third-party launchers\nmodpack.type.multimc=MultiMC\nmodpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC\nmodpack.type.server=Auto-Update Modpack from Server\nmodpack.type.server.export=Allows server owner to remotely update the game instance\nmodpack.type.server.malformed=Invalid modpack manifest. Please contact the modpack maker to resolve this problem.\nmodpack.unsupported=Unsupported modpack format\nmodpack.update=Updating modpack\nmodpack.wizard=Modpack Export Guide\nmodpack.wizard.step.1=Basic Settings\nmodpack.wizard.step.1.title=Some basic informations for the modpack.\nmodpack.wizard.step.2=Choose Files\nmodpack.wizard.step.2.title=Choose files you wanted to add to the modpack.\nmodpack.wizard.step.3=Modpack Type\nmodpack.wizard.step.3.title=Choose modpack type you wanted to export as.\nmodpack.wizard.step.initialization.exported_version=Game Instance to Export\nmodpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you will need a file-hosting server)\nmodpack.wizard.step.initialization.include_launcher=Include the launcher\nmodpack.wizard.step.initialization.modrinth.info=The launcher will match CurseForge/Modrinth remote resources instead of local files (including mods, resource packs, and shader packs) during modpack creation to reduce the modpack size, and mark files with \".disabled\" extension as optional for installation.\nmodpack.wizard.step.initialization.no_create_remote_files=Do not match remote files\nmodpack.wizard.step.initialization.save=Export to...\nmodpack.wizard.step.initialization.skip_curseforge_remote_files=Do not match CurseForge remote resources\nmodpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game can be launched normally and Minecraft is a release version instead of a snapshot. The launcher will save your download settings.\\n\\\n   \\n\\\n   Keep in mind that you are not allowed to add mods and resource packs that explicitly state they could not to be distributed or put in a modpack.\nmodpack.wizard.step.initialization.server=Click here for more information on how to make a server modpack that can be automatically updated.\n\nmodrinth.category.adventure=Adventure\nmodrinth.category.atmosphere=Atmosphere\nmodrinth.category.audio=Audio\nmodrinth.category.babric=Babric\nmodrinth.category.blocks=Blocks\nmodrinth.category.bloom=Bloom\nmodrinth.category.bta-babric=BTA (Babric)\nmodrinth.category.bukkit=Bukkit\nmodrinth.category.bungeecord=BungeeCord\nmodrinth.category.canvas=Canvas\nmodrinth.category.cartoon=Cartoon\nmodrinth.category.challenging=Challenging\nmodrinth.category.colored-lighting=Colored Lighting\nmodrinth.category.combat=Combat\nmodrinth.category.core-shaders=Core Shaders\nmodrinth.category.cursed=Cursed\nmodrinth.category.datapack=Datapack\nmodrinth.category.decoration=Decoration\nmodrinth.category.economy=Economy\nmodrinth.category.entities=Entities\nmodrinth.category.environment=Environment\nmodrinth.category.equipment=Equipment\nmodrinth.category.fabric=Fabric\nmodrinth.category.fantasy=Fantasy\nmodrinth.category.folia=Folia\nmodrinth.category.foliage=Foliage\nmodrinth.category.fonts=Fonts\nmodrinth.category.food=Food\nmodrinth.category.forge=Forge\nmodrinth.category.game-mechanics=Game Mechanics\nmodrinth.category.gui=GUI\nmodrinth.category.high=High\nmodrinth.category.iris=Iris\nmodrinth.category.items=Items\nmodrinth.category.java-agent=Java Agent\nmodrinth.category.kitchen-sink=Kitchen-Sink\nmodrinth.category.legacy-fabric=Legacy Fabric\nmodrinth.category.library=Library\nmodrinth.category.lightweight=Lightweight\nmodrinth.category.liteloader=LiteLoader\nmodrinth.category.locale=Locale\nmodrinth.category.low=Low\nmodrinth.category.magic=Magic\nmodrinth.category.management=Management\nmodrinth.category.medium=Medium\nmodrinth.category.minecraft=Minecraft\nmodrinth.category.minigame=Minigame\nmodrinth.category.misc=Misc\nmodrinth.category.mobs=Mobs\nmodrinth.category.modded=Modded\nmodrinth.category.models=Models\nmodrinth.category.modloader=Modloader\nmodrinth.category.multiplayer=Multiplayer\nmodrinth.category.neoforge=NeoForge\nmodrinth.category.nilloader=NilLoader\nmodrinth.category.optifine=OptiFine\nmodrinth.category.optimization=Optimization\nmodrinth.category.ornithe=Ornithe\nmodrinth.category.paper=Paper\nmodrinth.category.path-tracing=Path Tracing\nmodrinth.category.pbr=PBR\nmodrinth.category.potato=Potato\nmodrinth.category.purpur=Purpur\nmodrinth.category.quests=Quests\nmodrinth.category.quilt=Quilt\nmodrinth.category.realistic=Realistic\nmodrinth.category.reflections=Reflections\nmodrinth.category.rift=Rift\nmodrinth.category.screenshot=Screenshot\nmodrinth.category.semi-realistic=Semi-realistic\nmodrinth.category.shadows=Shadows\nmodrinth.category.simplistic=Simplistic\nmodrinth.category.social=Social\nmodrinth.category.spigot=Spigot\nmodrinth.category.sponge=Sponge\nmodrinth.category.storage=Storage\nmodrinth.category.technology=Technology\nmodrinth.category.themed=Themed\nmodrinth.category.transportation=Transportation\nmodrinth.category.tweaks=Tweaks\nmodrinth.category.utility=Utility\nmodrinth.category.vanilla=Vanilla\nmodrinth.category.vanilla-like=Vanilla-like\nmodrinth.category.velocity=Velocity\nmodrinth.category.waterfall=Waterfall\nmodrinth.category.worldgen=Worldgen\nmodrinth.category.8x-=8x-\nmodrinth.category.16x=16x\nmodrinth.category.32x=32x\nmodrinth.category.48x=48x\nmodrinth.category.64x=64x\nmodrinth.category.128x=128x\nmodrinth.category.256x=256x\nmodrinth.category.512x+=512x+\n\nmods=Mods\nmods.add=Add\nmods.add.failed=Failed to add mod %s.\nmods.add.success=%s was successfully added.\nmods.add.title=Choose mod file you want to add\nmods.broken_dependency.title=Broken dependency\nmods.broken_dependency.desc=This dependency existed before, but it does not exist anymore. Try using another download source.\nmods.category=Category\nmods.channel.alpha=Alpha\nmods.channel.beta=Beta\nmods.channel.release=Release\nmods.check_updates=Mod update process\nmods.check_updates.button=Update\nmods.check_updates.confirm=Update\nmods.check_updates.current_version=Current Version\nmods.check_updates.empty=All mods are up-to-date\nmods.check_updates.failed_check=Failed to check for updates.\nmods.check_updates.failed_download=Failed to download some files.\nmods.check_updates.file=File\nmods.check_updates.source=Source\nmods.check_updates.target_version=Target Version\nmods.curseforge=CurseForge\nmods.dependency.embedded=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately)\nmods.dependency.optional=Optional Dependencies (If missing, the game will run normally, but the mod features may be missing)\nmods.dependency.required=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching)\nmods.dependency.tool=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching)\nmods.dependency.include=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately)\nmods.dependency.incompatible=Incompatible Mods (Installing these mods at the same time will prevent the game from launching)\nmods.dependency.broken=Broken Dependencies (This mod existed before, but it does not exist anymore. Try using another download source.)\nmods.disable=Disable\nmods.download=Download Mod\nmods.download.title=Download Mod - %1s\nmods.download.recommend=Recommended Mod Version - Minecraft %1s\nmods.enable=Enable\nmods.game.version=Game Version\nmods.manage=Mods\nmods.mcbbs=MCBBS\nmods.mcmod=MCMod\nmods.mcmod.page=MCMod Page\nmods.mcmod.search=Search in MCMod\nmods.modrinth=Modrinth\nmods.name=Name\nmods.not_modded=You must install a modloader (Forge, NeoForge, Fabric, Legacy Fabric, Quilt, or LiteLoader) first to manage your mods!\nmods.restore=Restore\nmods.url=Official Page\nmods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot launch. Are you sure you want to update?\nmods.warning.loader_mismatch=Mod loader mismatch\nmods.install=Install\nmods.save_as=Save As\nmods.unknown=Unknown Mod\n\nmenu.undo=Undo\nmenu.redo=Redo\nmenu.cut=Cut\nmenu.copy=Copy\nmenu.paste=Paste\nmenu.deleteselection=Delete\nmenu.selectall=Select All\n\nnbt.entries=%s entries\nnbt.open.failed=Failed to open file\nnbt.save.failed=Failed to save file\nnbt.title=View File - %s\n\ndatapack=Datapacks\ndatapack.add=Add\ndatapack.add.title=Choose datapack archive you want to add\ndatapack.reload.toast=Minecraft is running, please use the /reload command to reload the data pack\ndatapack.title=World [%s] - Datapacks\n\nweb.failed=Failed to load page\nweb.open_in_browser=Do you want to open this address in a browser:\\n%s\nweb.view_in_browser=View all in browser\n\nworld=Worlds\nworld.add=Add\nworld.add.already_exists=This world already exists.\nworld.add.failed=Failed to add this world: %s\nworld.add.invalid=Failed to parse the world.\nworld.add.title=Choose world archive you want to add\nworld.backup=World Backup\nworld.backup.create.new_one=New Backup\nworld.backup.create.failed=Failed to create backup.\\n%s\nworld.backup.create.success=Successfully created a new backup: %s\nworld.backup.delete=Delete this backup\nworld.backup.processing=Backing up ...\nworld.chunkbase=Chunk Base\nworld.chunkbase.end_city=End City\nworld.chunkbase.seed_map=Seed Map\nworld.chunkbase.stronghold=Stronghold\nworld.chunkbase.nether_fortress=Nether Fortress\nworld.duplicate=Duplicate the World\nworld.duplicate.prompt=Please enter the name of the duplicated world\nworld.duplicate.failed.already_exists=Directory already exists\nworld.duplicate.failed.empty_name=Name cannot be empty\nworld.duplicate.failed.invalid_name=Name contains invalid characters\nworld.duplicate.failed=Failed to duplicate the world\nworld.duplicate.success.toast=Successfully duplicated the world\nworld.datapack=Datapacks\nworld.datetime=Last played on %s\nworld.delete=Delete the World\nworld.delete.failed=Failed to delete world.\\n%s\nworld.download=Download\nworld.download.title=Download World - %1s\nworld.export=Export the World\nworld.export.title=Choose the directory for this exported world\nworld.export.location=Save As\nworld.export.wizard=Export World \"%s\"\nworld.game_version=Game Version\nworld.icon=World Icon\nworld.icon.change=Change world icon\nworld.icon.change.fail.load.title=Failed to parse image\nworld.icon.change.fail.load.text=This image appears to be corrupted, and HMCL cannot parse it.\nworld.icon.change.fail.not_64x64.title=Image size error\nworld.icon.change.fail.not_64x64.text=The image resolution is %d×%d instead of 64×64. Please provide a 64×64 image and try again.\nworld.icon.change.succeed.toast=Successfully updated the world icon.\nworld.icon.change.tip=A 64×64 PNG image is required. Images with an incorrect resolution cannot be parsed by Minecraft.\nworld.icon.choose.title=Select world icon\nworld.info=World Information\nworld.info.basic=Basic Information\nworld.info.allow_cheats=Allow Commands/Cheats\nworld.info.dimension.the_nether=The Nether\nworld.info.dimension.the_end=The End\nworld.info.difficulty=Difficulty\nworld.info.difficulty.peaceful=Peaceful\nworld.info.difficulty.easy=Easy\nworld.info.difficulty.normal=Normal\nworld.info.difficulty.hard=Hard\nworld.info.difficulty_lock=Lock Difficulty\nworld.info.failed=Failed to read the world info\nworld.info.game_version=Game Version\nworld.info.last_played=Last Played\nworld.info.generate_features=Generate Structures\nworld.info.player=Player Information\nworld.info.player.food_level=Hunger Level\nworld.info.player.food_saturation_level=Saturation\nworld.info.player.game_type=Game Mode\nworld.info.player.game_type.adventure=Adventure\nworld.info.player.game_type.creative=Creative\nworld.info.player.game_type.hardcore=Hardcore\nworld.info.player.game_type.spectator=Spectator\nworld.info.player.game_type.survival=Survival\nworld.info.player.health=Health\nworld.info.player.last_death_location=Last Death Location\nworld.info.player.location=Location\nworld.info.player.spawn=Spawn Location\nworld.info.player.xp_level=Experience Level\nworld.info.random_seed=Seed\nworld.info.spawn=World Spawn Location\nworld.info.time=Played Time\nworld.info.time.format=%dd %dh %dm\nworld.load.fail=Failed to load world\nworld.locked=In use\nworld.locked.failed=The world is currently in use. Please close the game and try again.\nworld.manage=Worlds\nworld.manage.button=World Management\nworld.manage.title=World - %s\nworld.name=World Name\nworld.name.enter=Enter the world name\nworld.show_all=Show All\n\nprofile=Game Directories\nprofile.already_exists=This name already exists. Please use a different name.\nprofile.default=Current\nprofile.home=Minecraft Launcher\nprofile.instance_directory=Game Directory\nprofile.instance_directory.choose=Choose game directory\nprofile.manage=Instance Directory List\nprofile.name=Name\nprofile.new=New Directory\nprofile.title=Game Directories\nprofile.selected=Selected\nprofile.use_relative_path=Use a relative path for the game directory if possible\n\nrepositories.custom=Custom Maven Repository (%s)\nrepositories.maven_central=Universal (Maven Central)\nrepositories.tencentcloud_mirror=Chinese Mainland Mirror (Tencent Cloud Maven Repository)\nrepositories.chooser=HMCL requires JavaFX to work.\\n\\\n   \\n\\\n   Please click \"OK\" to download JavaFX from the specified repository, or click \"Cancel\" to exit.\\n\\\n   \\n\\\n   Repositories:\nrepositories.chooser.title=Choose download source for JavaFX\n\nresourcepack=Resource Packs\nresourcepack.add=Add\nresourcepack.add.title=Choose resource pack archive you want to add\nresourcepack.manage=Resource Packs\nresourcepack.download=Download\nresourcepack.add.failed=Failed to add resource pack\nresourcepack.delete.failed=Failed to delete resource pack\nresourcepack.download.title=Download Resource Pack - %1s\n\nreveal.in_file_manager=Reveal in File Manager\n\nschematics=Schematics\nschematics.add=Add\nschematics.add.failed=Failed to add schematic files\nschematics.add.title=Choose schematic file you want to import\nschematics.back_to=Back to \"%s\"\nschematics.create_directory=New Directory\nschematics.create_directory.prompt=Please enter the new directory name\nschematics.create_directory.failed=Failed to create directory\nschematics.create_directory.failed.already_exists=Directory already exists\nschematics.create_directory.failed.empty_name=Name cannot be empty\nschematics.create_directory.failed.invalid_name=Name contains invalid characters\nschematics.info.description=Description\nschematics.info.enclosing_size=Enclosing Size\nschematics.info.name=Name\nschematics.info.region_count=Regions\nschematics.info.schematic_author=Author\nschematics.info.time_created=Created Time\nschematics.info.time_modified=Modified Time\nschematics.info.total_blocks=Total Blocks\nschematics.info.total_volume=Total Volume\nschematics.info.version=Schematic Version\nschematics.manage=Schematics\nschematics.sub_items=%d sub-item(s)\n\nsearch=Search\nsearch.hint.chinese=Search in English and Chinese\nsearch.hint.english=Search in English only\nsearch.enter=Enter text here\nsearch.sort=Sort By\nsearch.first_page=First\nsearch.previous_page=Previous\nsearch.next_page=Next\nsearch.last_page=Last\nsearch.page_n=%1$d / %2$s\n\nselector.choose=Choose\nselector.choose_file=Choose file\nselector.custom=Custom\n\nsettings=Settings\n\nsettings.advanced=Advanced Settings\nsettings.advanced.modify=Edit Advanced Settings\nsettings.advanced.title=Advanced Settings - %s\nsettings.advanced.custom_commands=Custom Commands\nsettings.advanced.custom_commands.hint=The following environment variables are provided:\\n\\\n   \\  · $INST_NAME: instance name.\\n\\\n   \\  · $INST_ID: instance name.\\n\\\n   \\  · $INST_DIR: absolute path of the instance working directory.\\n\\\n   \\  · $INST_MC_DIR: absolute path of the game directory.\\n\\\n   \\  · $INST_JAVA: java binary used for launch.\\n\\\n   \\  · $INST_FORGE: set if Forge is installed.\\n\\\n   \\  · $INST_NEOFORGE: set if NeoForge is installed.\\n\\\n   \\  · $INST_CLEANROOM: set if Cleanroom is installed.\\n\\\n   \\  · $INST_LITELOADER: set if LiteLoader is installed.\\n\\\n   \\  · $INST_OPTIFINE: set if OptiFine is installed.\\n\\\n   \\  · $INST_FABRIC: set if Fabric is installed.\\n\\\n   \\  · $INST_LEGACYFABRIC: set if Legacy Fabric is installed.\\n\\\n   \\  · $INST_QUILT: set if Quilt is installed.\nsettings.advanced.dont_check_game_completeness=Do not check game integrity\nsettings.advanced.dont_check_jvm_validity=Do not check JVM compatibility\nsettings.advanced.dont_patch_natives=Do not attempt to automatically replace native libraries\nsettings.advanced.environment_variables=Environment Variables\nsettings.advanced.game_dir.default=Default (\".minecraft/\")\nsettings.advanced.game_dir.independent=Isolated (\".minecraft/versions/<instance name>/\", except for assets and libraries)\nsettings.advanced.java_permanent_generation_space=PermGen Space\nsettings.advanced.java_permanent_generation_space.prompt=in MiB\nsettings.advanced.jvm=JVM Options\nsettings.advanced.jvm_args=JVM Arguments\nsettings.advanced.jvm_args.prompt=\\  · If the arguments entered in \"JVM Arguments\" are the same as the default arguments, it will not be added.\\n\\\n   \\  · Enter any GC arguments in \"JVM Arguments\", and the G1 argument of the default arguments will be disabled.\\n\\\n   \\  · Enable \"Do not add default JVM arguments\" to launch the game without adding default arguments.\nsettings.advanced.launcher_visibility.close=Close the launcher after the game launches\nsettings.advanced.launcher_visibility.hide=Hide the launcher after the game launches\nsettings.advanced.launcher_visibility.hide_and_reopen=Hide the launcher and show it when the game closes\nsettings.advanced.launcher_visibility.keep=Keep the launcher visible\nsettings.advanced.launcher_visible=Launcher Visibility\nsettings.advanced.minecraft_arguments=Launch Arguments\nsettings.advanced.minecraft_arguments.prompt=Default\nsettings.advanced.natives_directory=Native Library Path\nsettings.advanced.natives_directory.choose=Choose the location of the desired native library\nsettings.advanced.natives_directory.custom=Custom\nsettings.advanced.natives_directory.default=Default\nsettings.advanced.natives_directory.default.version_id=<Instance Name>\nsettings.advanced.natives_directory.hint=This option is intended only for users of Apple silicon or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\\n\\\n   \\n\\\n   Before proceeding, please make sure all libraries (e.g. lwjgl.dll, libopenal.so) are provided in your desired directory.\\n\\\n   Note: It is recommended to use a fully English-letters path for the specified local library file. Otherwise it may lead to game launch failure.\nsettings.advanced.no_jvm_args=Do not add default JVM arguments\nsettings.advanced.no_optimizing_jvm_args=Do not add default JVM optimization arguments\nsettings.advanced.precall_command=Pre-launch Command\nsettings.advanced.precall_command.prompt=Commands to execute before the game launches\nsettings.advanced.process_priority=Process Priority\nsettings.advanced.process_priority.low=Low\nsettings.advanced.process_priority.low.desc=\nsettings.advanced.process_priority.below_normal=Below Normal\nsettings.advanced.process_priority.below_normal.desc=\nsettings.advanced.process_priority.normal=Normal\nsettings.advanced.process_priority.normal.desc=\nsettings.advanced.process_priority.above_normal=Above Normal\nsettings.advanced.process_priority.above_normal.desc=\nsettings.advanced.process_priority.high=High\nsettings.advanced.process_priority.high.desc=\nsettings.advanced.post_exit_command=Post-exit Command\nsettings.advanced.post_exit_command.prompt=Commands to execute after the game exits\nsettings.advanced.renderer=Renderer\nsettings.advanced.renderer.default=Default\nsettings.advanced.renderer.default.desc=OpenGL\nsettings.advanced.renderer.d3d12=Mesa D3D12\nsettings.advanced.renderer.d3d12.desc=DirectX 12 (Poor performance and compatibility)\nsettings.advanced.renderer.llvmpipe=Mesa LLVMpipe\nsettings.advanced.renderer.llvmpipe.desc=Software (Poor performance, best compatibility)\nsettings.advanced.renderer.zink=Mesa Zink\nsettings.advanced.renderer.zink.desc=Vulkan (Best performance, poor compatibility)\nsettings.advanced.server_ip=Server Address\nsettings.advanced.server_ip.prompt=Automatically join after launching the game\nsettings.advanced.unsupported_system_options=Settings not applicable to the current system\nsettings.advanced.use_native_glfw=[Linux/FreeBSD Only] Use System GLFW\nsettings.advanced.use_native_openal=[Linux/FreeBSD Only] Use System OpenAL\nsettings.advanced.workaround=Workaround\nsettings.advanced.workaround.warning=Workaround options are intended only for advanced users. Tweaking with these options may crash the game. Unless you know what you are doing, please do not edit these options.\nsettings.advanced.wrapper_launcher=Wrapper Command\nsettings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrapper program like \"optirun\" on Linux\n\nsettings.custom=Custom\n\nsettings.game=Settings\nsettings.game.copy_global=Copy from Global Settings\nsettings.game.copy_global.copy_all=Copy All\nsettings.game.copy_global.copy_all.confirm=Are you sure you want to overwrite the current instance settings? This action cannot be undone!\nsettings.game.current=Game\nsettings.game.dimension=Resolution\nsettings.game.exploration=Explore\nsettings.game.fullscreen=Fullscreen\nsettings.game.java_directory=Java\nsettings.game.java_directory.auto=Automatically Choose\nsettings.game.java_directory.auto.not_found=No suitable Java version was installed.\nsettings.game.java_directory.bit=%s bit\nsettings.game.java_directory.choose=Choose Java\nsettings.game.java_directory.invalid=Incorrect Java path\nsettings.game.java_directory.version=Specify Java Version\nsettings.game.java_directory.template=%1$s (%2$s)\nsettings.game.management=Manage\nsettings.game.working_directory=Working Directory\nsettings.game.working_directory.choose=Choose the working directory\nsettings.game.working_directory.hint=Enable the \"Isolated\" option in \"Working Directory\" to allow the current instance to store its settings, worlds, and mods in a separate directory.\\n\\\n   \\n\\\n   It is recommended to enable this option to avoid mod conflicts, but you will need to move your worlds manually.\n\nsettings.icon=Icon\n\nsettings.launcher=Launcher Settings\nsettings.launcher.appearance=Appearance\nsettings.launcher.brightness=Theme Mode\nsettings.launcher.brightness.auto=Follow System Settings\nsettings.launcher.brightness.dark=Dark Mode\nsettings.launcher.brightness.light=Light Mode\nsettings.launcher.common_path.tooltip=HMCL will put all game assets and dependencies here. If there are existing libraries in the game directory, then HMCL will prefer to use them first.\nsettings.launcher.debug=Debug\nsettings.launcher.disable_april_fools=Do not enable April Fools features\nsettings.launcher.disable_auto_game_options=Do not switch game language\nsettings.launcher.download=Download\nsettings.launcher.download.threads=Threads\nsettings.launcher.download.threads.auto=Automatically Determine\nsettings.launcher.download.threads.hint=Too many threads may cause your system to freeze, and your download speed may be affected by your ISP and download servers. It is not always the case that more threads increase your download speed.\nsettings.launcher.download_source=Download Source\nsettings.launcher.download_source.auto=Automatically Choose Download Sources\nsettings.launcher.enable_game_list=Show instance list in homepage\nsettings.launcher.font=Font\nsettings.launcher.font.anti_aliasing=Anti-aliasing\nsettings.launcher.font.anti_aliasing.auto=Auto\nsettings.launcher.font.anti_aliasing.gray=Grayscale\nsettings.launcher.font.anti_aliasing.lcd=Sub-pixel\nsettings.launcher.general=General\nsettings.launcher.language=Language\nsettings.launcher.launcher_log.export=Export Launcher Logs\nsettings.launcher.launcher_log.export.failed=Failed to export logs.\nsettings.launcher.launcher_log.export.success=Logs have been exported to \"%s\".\nsettings.launcher.launcher_log.reveal=Reveal Logs in File Manager\nsettings.launcher.log=Logging\nsettings.launcher.log.font=Font\nsettings.launcher.proxy=Proxy\nsettings.launcher.proxy.authentication=Requires Authentication\nsettings.launcher.proxy.default=Use System Proxy\nsettings.launcher.proxy.host=Host\nsettings.launcher.proxy.http=HTTP\nsettings.launcher.proxy.none=No Proxy\nsettings.launcher.proxy.password=Password\nsettings.launcher.proxy.port=Port\nsettings.launcher.proxy.socks=SOCKS\nsettings.launcher.proxy.username=Username\nsettings.launcher.theme=Theme Color\nsettings.launcher.title_transparent=Transparent Titlebar\nsettings.launcher.turn_off_animations=Disable Animation\nsettings.launcher.version_list_source=Version List\nsettings.launcher.background.settings.opacity=Opacity\n\nsettings.memory=Memory\nsettings.memory.allocate.auto=%1$.1f GiB Minimum / %2$.1f GiB Allocated\nsettings.memory.allocate.auto.exceeded=%1$.1f GiB Minimum / %2$.1f GiB Allocated (%3$.1f GiB Available)\nsettings.memory.allocate.manual=%1$.1f GiB Allocated\nsettings.memory.allocate.manual.exceeded=%1$.1f GiB Allocated (%3$.1f GiB Available)\nsettings.memory.auto_allocate=Automatically Allocate\nsettings.memory.lower_bound=Minimum Memory\nsettings.memory.unit.mib=MiB\nsettings.memory.used_per_total=%1$.1f GiB Used / %2$.1f GiB Total\nsettings.physical_memory=Physical Memory Size\nsettings.show_log=Show Logs\nsettings.enable_debug_log_output=Output debug log\nsettings.tabs.installers=Loaders\nsettings.take_effect_after_restart=Applies After Restart\nsettings.type=Settings Type of Instance\nsettings.type.global=Global Settings (Shared Among Instances without the \"Instance-specific Settings\" enabled)\nsettings.type.global.manage=Global Settings\nsettings.type.global.edit=Edit Global Settings\nsettings.type.special.enable=Enable Instance-specific Settings\nsettings.type.special.edit=Edit Current Instance Settings\nsettings.type.special.edit.hint=Current instance \"%s\" has enabled the \"Instance-specific Settings\". All options on this page will NOT affect that instance. Click here to edit its own settings.\n\nshaderpack.download.title=Download Shader - %1s\n\nsponsor=Donors\nsponsor.bmclapi=Downloads for the Chinese Mainland are provided by BMCLAPI. Click here for more information.\nsponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher that allows users to manage multiple Minecraft instances easily. Click here for more information.\n\nsystem.architecture=Architecture\nsystem.operating_system=Operating System\n\nterracotta=Multiplayer\nterracotta.terracotta=Terracotta | Multiplayer\nterracotta.status=Lobby\nterracotta.back=Exit\nterracotta.feedback.title=Fill Out Feedback Form\nterracotta.feedback.desc=As HMCL updates Multiplayer Core, we hope you can take 10 seconds to fill out the feedback form.\nterracotta.sudo_installing=HMCL must verify your password before installing Multiplayer Core\nterracotta.difficulty.easiest=Excellent network: almost connected!\nterracotta.difficulty.simple=Good network: connection may take some time\nterracotta.difficulty.medium=Average network: enabling fallback routes, though connection may fail\nterracotta.difficulty.tough=Poor network: enabling fallback routes, though connection may fail\nterracotta.difficulty.estimate_only=Success rate is an estimate based on host and client NAT types, for reference only!\nterracotta.from_local.title=Third-party download channels for Multiplayer Core\nterracotta.from_local.desc=In some areas, the built-in default download channel may be unstable.\nterracotta.from_local.guide=Please download Multiplayer Core package named %s. Once downloaded, drag the file into the current page to install it.\nterracotta.from_local.file_name_mismatch=You should download the Multiplayer Core package named %1$s instead of %2$s\nterracotta.export_log=Exports the Multiplayer Core log\nterracotta.export_log.desc=Gathering more information for analysis\nterracotta.status.bootstrap=Gathering information\nterracotta.status.uninitialized.not_exist=Multiplayer Core: Not Downloaded\nterracotta.status.uninitialized.not_exist.title=Download Multiplayer Core (~ 8MiB)\nterracotta.status.uninitialized.update=Multiplayer Core: Update Available\nterracotta.status.uninitialized.update.title=Update Multiplayer Core (~ 8MiB)\nterracotta.status.uninitialized.desc=You legally promise to strictly abide by all laws and regulations of your country or region during the multiplayer process.\nterracotta.confirm.title=User Notice\nterracotta.confirm.desc=Terracotta is a third-party open source free software. Please provide feedback on any issues you encounter through the relevant channels during use.\\n\\\n  Terracotta uses P2P technology. After a successful connection, users in the room will connect directly to each other, and no third-party server will relay your traffic. The final multiplayer experience largely depends on the network conditions of the participants.\\n\\\n  During the entire multiplayer process, you must strictly comply with all laws and regulations of your country and region.\nterracotta.status.preparing=Multiplayer Core: Downloading (DO NOT exit HMCL)\nterracotta.status.launching=Multiplayer Core: Initializing\nterracotta.status.unknown=Multiplayer Core: Initializing\nterracotta.status.waiting=Multiplayer Core: Ready\nterracotta.status.waiting.host.title=I want to host a session\nterracotta.status.waiting.host.desc=Create a room and generate an invite code to play with friends\nterracotta.status.waiting.host.launch.title=You seem to have forgotten to launch the game\nterracotta.status.waiting.host.launch.desc=No running game found\nterracotta.status.waiting.host.launch.skip=Game has launched\nterracotta.status.waiting.guest.title=I want to join a session\nterracotta.status.waiting.guest.desc=Enter the invite code from the host player to join the game world\nterracotta.status.waiting.guest.prompt.title=Please enter the invite code from the host\nterracotta.status.waiting.guest.prompt.invalid=Invalid invite code\nterracotta.status.scanning=Scanning LAN worlds\nterracotta.status.scanning.desc=Please <a href=\"hmcl://game/launch\">start the game</a>, open a world, press ESC, select \"Open to LAN\", then select \"Start LAN World\".\nterracotta.status.scanning.back=This will also stop scanning LAN worlds.\nterracotta.status.host_starting=Room Creating\nterracotta.status.host_starting.back=This will stop creating the room.\nterracotta.status.host_ok=Room created\nterracotta.status.host_ok.code=Invitation code (Copied)\nterracotta.status.host_ok.code.copy=Copy invitation code\nterracotta.status.host_ok.code.copy.toast=Invitation code has been copied to clipboard\nterracotta.status.host_ok.code.desc=Please remind your friends to select Guest mode in HMCL - Multiplayer or PCL CE and enter this invitation code.\nterracotta.status.host_ok.back=This will also close the room, other guests will leave and cannot rejoin.\nterracotta.status.guest_starting=Joining room\nterracotta.status.guest_starting.back=This will not stop other guests from joining the room.\nterracotta.status.guest_ok=Room Joined\nterracotta.status.guest_ok.back=This will not stop other guests from joining the room.\nterracotta.status.guest_ok.title=Please launch the game, select Multiplayer, and double-click Terracotta Lobby.\nterracotta.status.guest_ok.desc=Backup address: %s\nterracotta.status.exception.back=Please try again\nterracotta.status.exception.desc.ping_host_fail=Failed to join room: Room is closed or network unstable\nterracotta.status.exception.desc.ping_host_rst=Room connection lost: Room is closed or network unstable\nterracotta.status.exception.desc.guest_et_crash=Failed to join room: EasyTier crashed, please report this issue to developers\nterracotta.status.exception.desc.host_et_crash=Failed to create room: EasyTier crashed, please report this issue to developers\nterracotta.status.exception.desc.ping_server_rst=Room closed: You exited the game world, room closed automatically\nterracotta.status.exception.desc.scaffolding_invalid_response=Invalid Protocol：Host has sent invalid response, please report this issue to developers\nterracotta.status.fatal.retry=Retry\nterracotta.status.fatal.network=Failed to download Multiplayer Core. Please check your network connection and try again.\nterracotta.status.fatal.install=Fatal Error: Unable to install Multiplayer Core.\nterracotta.status.fatal.terracotta=Fatal Error: Unable to connect to Multiplayer Core.\nterracotta.status.fatal.unknown=Fatal Error: Unknown.\nterracotta.player_list=Player List\nterracotta.player_anonymous=Anonymous Player\nterracotta.player_kind.host=Host\nterracotta.player_kind.local=Yourself\nterracotta.player_kind.guest=Guest\nterracotta.unsupported=Multiplayer is not yet supported on the current platform.\nterracotta.unsupported.os.windows.old=Multiplayer requires Windows 10 or later. Please update your system.\nterracotta.unsupported.arch.32bit=Multiplayer is not supported on 32-bit systems. Please upgrade to a 64-bit system.\nterracotta.unsupported.arch.loongarch64_ow=Multiplayer is not supported on Linux LoongArch64 Old World distributions. Please update to a New World distribution (such as AOSC OS).\nterracotta.unsupported.region=The multiplayer feature is currently only available to users in Chinese Mainland and may not be available in your region.\n\nunofficial.hint=You are using an unofficial build of HMCL. We cannot guarantee its security.\n\nupdate=Update\nupdate.accept=Update\nupdate.changelog=Changelog\nupdate.channel.dev=Beta\nupdate.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Release channel builds.\\n\\\n   \\n\\\n   If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href=\"hmcl://settings/feedback\">Feedback</a> page.\\n\\\n   \\n\\\n   Follow <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> on Bilibili to stay up to date on important HMCL news, or <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> to learn about HMCL development progress.\nupdate.channel.dev.title=Beta Channel Notice\nupdate.channel.nightly=Nightly\nupdate.channel.nightly.hint=You are currently using a Nightly channel build of the launcher. While it may include some extra features, it is also always less stable than the other channel builds.\\n\\\n   \\n\\\n   If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href=\"hmcl://settings/feedback\">Feedback</a> page.\\n\\\n   \\n\\\n   Follow <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> on Bilibili to stay up to date on important HMCL news, or <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> to learn about HMCL development progress.\nupdate.channel.nightly.title=Nightly Channel Notice\nupdate.channel.stable=Release\nupdate.checking=Checking for updates\nupdate.disable_auto_show_update_dialog=Do not show update dialog automatically\nupdate.disable_auto_show_update_dialog.subtitle=Enable this option to prevent HMCL from automatically showing the update dialog.\nupdate.failed=Failed to update\nupdate.found=Update Available!\nupdate.newest_version=Latest version: %s\nupdate.bubble.title=Update Available: %s\nupdate.bubble.subtitle=Click here to update\nupdate.note=Beta and Nightly channels may have more features or fixes, but they also come with more potential problems.\nupdate.latest=This is the latest version\nupdate.no_browser=Cannot open in system browser. But we copied the link to your clipboard, and you can open it manually.\nupdate.tooltip=Update\nupdate.preview=Preview HMCL releases early\nupdate.preview.subtitle=Enable this option to receive new versions of HMCL early for testing before their official release.\n\nversion=Games\nversion.name=Instance Name\nversion.cannot_read=Failed to parse the game instance, installation cannot continue.\nversion.empty=No Instances\nversion.empty.add=Add new instance\nversion.empty.launch=No available instances.\\nYou can go to the \"Download\" page to get the game, or switch the game directory in the \"All Instances\" page.\nversion.empty.launch.goto_download=Go to Download Page\nversion.empty.hint=There are no Minecraft instances here.\\nYou can try switching to another game directory or clicking here to download one.\nversion.game.all=All\nversion.game.april_fools=April Fools\nversion.game.old=Historical\nversion.game.release=Release\nversion.game.releases=Releases\nversion.game.snapshot=Snapshot\nversion.game.snapshots=Snapshots\nversion.game.support_status.unsupported=Unsupported\nversion.game.support_status.untested=Untested\nversion.game.type=Type\nversion.launch=Launch Game\nversion.launch_and_enter_world=Play World\nversion.launch.empty=Start Game\nversion.launch.empty.installing=Installing Game\nversion.launch.empty.tooltip=Install and launch the latest official release\nversion.launch.test=Test Launch\nversion.switch=Switch Instance\nversion.launch_script=Export Launch Script\nversion.launch_script.failed=Failed to export launch script.\nversion.launch_script.save=Export Launch Script\nversion.launch_script.success=Exported launch script as %s.\nversion.manage=All Instances\nversion.manage.clean=Delete Log Files\nversion.manage.clean.tooltip=Delete the files in \"logs\" and \"crash-reports\" directories.\nversion.manage.duplicate=Duplicate Instance\nversion.manage.duplicate.duplicate_save=Duplicate Worlds\nversion.manage.duplicate.prompt=Enter New Instance Name\nversion.manage.duplicate.confirm=The duplicated instance will have a copy of all files in the instance directory (\".minecraft/versions/<instance name>\"), with an isolated working directory and settings.\nversion.manage.manage=Edit Instance\nversion.manage.manage.title=Edit Instance - %1s\nversion.manage.redownload_assets_index=Update Game Assets\nversion.manage.remove=Delete Instance\nversion.manage.remove.confirm.trash=Are you sure you want to remove the instance \"%1$s\"? You can still find its files in your recycle bin by the name of \"%2$s\".\nversion.manage.remove.confirm.independent=Since this instance is stored in an isolated directory, deleting it will also delete its worlds and other data. Do you still want to delete the instance \"%s\"?\nversion.manage.remove.failed=Failed to delete the instance. Some files might be in use.\nversion.manage.remove_assets=Delete All Assets\nversion.manage.remove_libraries=Delete All Libraries\nversion.manage.rename=Rename Instance\nversion.manage.rename.message=Enter New Instance Name\nversion.manage.rename.fail=Failed to rename the instance. Some files might be in use, or the name contains an invalid character.\nversion.search=Name\nversion.search.prompt=Enter the version name to search\nversion.settings=Settings\nversion.update=Update Modpack\n\nwarning.java_interpreted_mode=HMCL is running in an interpreted Java environment, which will greatly affect performance.\\n\\\n  \\n\\\n  We recommend using a Java with JIT support to open HMCL for the best experience.\nwarning.software_rendering=HMCL is currently using software rendering, which will greatly affect performance.\n\nwiki.tooltip=Minecraft Wiki Page\nwiki.version.game=https://minecraft.wiki/w/Java_Edition_%s\nwiki.version.game.snapshot=https://minecraft.wiki/w/Java_Edition_%s\n\nwizard.prev=< Prev\nwizard.failed=Failed\nwizard.finish=Finish\nwizard.next=Next >"
  },
  {
    "path": "HMCL/src/main/resources/assets/lang/I18N_ar.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Contributors: dxNeil, machinesmith42\n# and Byacrya for basically retranslating it\n\nabout=حول\nabout.copyright=حقوق النشر\nabout.copyright.statement=حقوق النشر © 2013-2026 huangyuhui والمساهمون.\nabout.author=المطوّر\nabout.author.statement=bilibili @huanghongxun\nabout.claim=اتفاقية الترخيص\nabout.claim.statement=انقر على هذا الرابط للاطلاع على النص الكامل.\nabout.dependency=مكتبات الطرف الثالث\nabout.legal=الإقرارات القانونية\nabout.thanks_to=شكر وتقدير\nabout.thanks_to.bangbang93.statement=لتوفيره مرآة التنزيل BMCLAPI. يُرجى التفكير في التبرع!\nabout.thanks_to.burningtnt.statement=قدّم دعماً تقنياً كبيراً لـ HMCL.\nabout.thanks_to.contributors=جميع المساهمين على GitHub\nabout.thanks_to.contributors.statement=بدون مجتمع المصدر المفتوح الرائع، لم يكن HMCL ليصل إلى ما هو عليه اليوم.\nabout.thanks_to.gamerteam.statement=لتوفير صورة الخلفية الافتراضية.\nabout.thanks_to.glavo.statement=مسؤول عن صيانة HMCL.\nabout.thanks_to.zekerzhayard.statement=قدّم دعماً تقنياً كبيراً لـ HMCL.\nabout.thanks_to.zkitefly.statement=مسؤول عن صيانة توثيق HMCL.\nabout.thanks_to.mcbbs=MCBBS (منتدى Minecraft الصيني)\nabout.thanks_to.mcbbs.statement=لتوفير مرآة تنزيل mcbbs.net لمستخدمي البر الرئيسي الصيني. (لم تعد متاحة)\nabout.thanks_to.mcim=mcmod-info-mirror\nabout.thanks_to.mcim.statement=لتوفير خدمة تسريع ذاكرة التخزين المؤقت لمعلومات الموديفكيشن لمستخدمي البر الرئيسي الصيني.\nabout.thanks_to.mcmod=MCMod (mcmod.cn)\nabout.thanks_to.mcmod.statement=لتوفير ترجمات الصينية المبسطة وويكي لمختلف الموديفكيشنز.\nabout.thanks_to.red_lnn.statement=لتوفير صورة الخلفية الافتراضية.\nabout.thanks_to.shulkersakura.statement=لتوفير الشعار الخاص بـ HMCL.\nabout.thanks_to.users=أعضاء مجموعة مستخدمي HMCL\nabout.thanks_to.users.statement=شكراً على التبرعات وتقارير الأخطاء وغير ذلك.\nabout.thanks_to.yushijinhun.statement=لتوفير الدعم المتعلق بـ authlib-injector.\nabout.open_source=المصدر المفتوح\nabout.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)\n\naccount=الحسابات\naccount.cape=الرداء\naccount.character=اللاعب\naccount.choose=اختر لاعباً\naccount.create=إضافة حساب\naccount.create.microsoft=إضافة حساب Microsoft\naccount.create.offline=إضافة حساب غير متصل\naccount.create.authlibInjector=إضافة حساب authlib-injector\naccount.email=البريد الإلكتروني\naccount.empty=لا توجد حسابات\naccount.failed=فشل تحديث الحساب.\naccount.failed.character_deleted=تم حذف هذا اللاعب مسبقاً.\naccount.failed.connect_authentication_server=فشل الاتصال بخادم المصادقة، قد يكون اتصالك بالإنترنت معطلاً.\naccount.failed.connect_injector_server=فشل الاتصال بخادم المصادقة. يُرجى التحقق من شبكتك والتأكد من إدخال الرابط الصحيح.\naccount.failed.injector_download_failure=فشل تنزيل authlib-injector. يُرجى التحقق من شبكتك، أو المحاولة بالتبديل إلى مصدر تنزيل آخر.\naccount.failed.invalid_credentials=كلمة المرور غير صحيحة أو تم تجاوز الحد المسموح. يُرجى المحاولة لاحقاً.\naccount.failed.invalid_password=كلمة المرور غير صالحة.\naccount.failed.invalid_token=يُرجى إعادة تسجيل الدخول.\naccount.failed.migration=يحتاج حسابك إلى الترحيل إلى حساب Microsoft. إذا أتممت ذلك مسبقاً، يُرجى تسجيل الدخول بحساب Microsoft الجديد.\naccount.failed.no_character=لا توجد شخصيات مرتبطة بهذا الحساب.\naccount.failed.server_disconnected=فشل الاتصال بخادم المصادقة. يمكنك تسجيل الدخول في وضع عدم الاتصال أو إعادة المحاولة.\\n\\\n   إذا استمرت المشكلة بعد محاولات متعددة، يُرجى إعادة تسجيل الدخول إلى حسابك.\naccount.failed.server_response_malformed=استجابة الخادم غير صالحة. قد يكون خادم المصادقة معطلاً.\naccount.failed.ssl=حدث خطأ SSL أثناء الاتصال بالخادم. يُرجى محاولة تحديث Java.\naccount.failed.dns=حدث خطأ SSL أثناء الاتصال بالخادم. قد يكون تحليل DNS غير صحيح. يُرجى تغيير خادم DNS الخاص بك أو استخدام خدمة وكيل.\naccount.failed.wrong_account=لقد سجّلت الدخول إلى حساب خاطئ.\naccount.hmcl.hint=تحتاج إلى النقر على \"تسجيل الدخول\" وإتمام العملية في نافذة المتصفح المفتوحة.\naccount.injector.add=خادم مصادقة جديد\naccount.injector.empty=لا يوجد (يمكنك النقر على \"+\" لإضافة واحد)\naccount.injector.http=تحذير: يستخدم هذا الخادم بروتوكول HTTP غير الآمن. أي شخص يعترض اتصالك سيتمكن من رؤية بياناتك بنص واضح.\naccount.injector.link.homepage=الصفحة الرئيسية\naccount.injector.link.register=التسجيل\naccount.injector.server=خادم المصادقة\naccount.injector.server_url=رابط الخادم\naccount.injector.server_name=اسم الخادم\naccount.login=تسجيل الدخول\naccount.login.hint=نحن لا نحتفظ بكلمة مرورك أبداً.\naccount.login.skip=الدخول بدون اتصال\naccount.login.retry=إعادة المحاولة\naccount.login.refresh=تسجيل الدخول مجدداً\naccount.login.refresh.microsoft.hint=تحتاج إلى تسجيل الدخول إلى حساب Microsoft مجدداً لأن تفويض الحساب انتهت صلاحيته.\naccount.login.restricted=سجّل الدخول إلى حساب Microsoft لتفعيل هذه الميزة\naccount.logout=تسجيل الخروج\naccount.register=إنشاء حساب\naccount.manage=قائمة الحسابات\naccount.copy_uuid=نسخ UUID الحساب\naccount.methods=نوع تسجيل الدخول\naccount.methods.authlib_injector=authlib-injector\naccount.methods.microsoft=Microsoft\naccount.methods.microsoft.birth=كيفية تغيير تاريخ ميلاد حسابك\naccount.methods.microsoft.code=الرمز (تم النسخ إلى الحافظة)\naccount.methods.microsoft.close_page=اكتمل تفويض حساب Microsoft.\\n\\\n   \\n\\\n   لا يزال هناك بعض الخطوات من جانبنا، لكن يمكنك إغلاق هذا التبويب بأمان الآن.\naccount.methods.microsoft.deauthorize=إلغاء التفويض\naccount.methods.microsoft.error.add_family=يُرجى النقر <a href=\"https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a\">هنا</a> لتغيير تاريخ ميلاد حسابك ليكون فوق 18 عاماً، أو إضافة حسابك إلى عائلة.\naccount.methods.microsoft.error.country_unavailable=Xbox Live غير متاح في بلدك/منطقتك الحالية.\naccount.methods.microsoft.error.missing_xbox_account=لا يوجد حساب Xbox مرتبط بحساب Microsoft الخاص بك بعد. يُرجى النقر <a href=\"https://www.minecraft.net/msaprofile/mygames/editprofile\">هنا</a> لربط واحد.\naccount.methods.microsoft.error.no_character=يُرجى التأكد من أنك اشتريت Minecraft: Java Edition.\\n\\\n  إذا اشتريته بالفعل، فقد لا يكون ملف اللعبة قد تم إنشاؤه. يُرجى النقر <a href=\"https://www.minecraft.net/msaprofile/mygames/editprofile\">هنا</a> لإنشاء ملف اللعبة.\naccount.methods.microsoft.error.banned=قد يكون حسابك محظوراً من Xbox Live.\\n\\\n  يمكنك النقر <a href=\"https://enforcement.xbox.com/enforcement/showenforcementhistory\">هنا</a> للتحقق من حالة الحظر لحسابك.\naccount.methods.microsoft.error.unknown=فشل تسجيل الدخول، رمز الخطأ: %d.\naccount.methods.microsoft.error.wrong_verify_method=فشل تسجيل الدخول. يُرجى محاولة تسجيل الدخول إلى حسابك باستخدام كلمة المرور بدلاً من طرق الدخول الأخرى.\naccount.methods.microsoft.logging_in=جارٍ تسجيل الدخول...\naccount.methods.microsoft.makegameidsettings=إنشاء ملف اللعبة / تعديل اسم الملف\naccount.methods.microsoft.hint=انقر على زر \"تسجيل الدخول\" لبدء إضافة حساب Microsoft الخاص بك.\naccount.methods.microsoft.methods.device=تسجيل الدخول برمز QR\naccount.methods.microsoft.methods.device.hint=امسح رمز QR أو زر <a href=\"%s\">%s</a> لإتمام تسجيل الدخول، وأدخل <b>%s</b> في الصفحة المفتوحة.\naccount.methods.microsoft.methods.browser=تسجيل الدخول عبر المتصفح\naccount.methods.microsoft.methods.browser.hint=انقر على زر \"تسجيل الدخول\" أو <a href=\"%s\">انسخ الرابط</a> والصقه في المتصفح لتسجيل الدخول.\naccount.methods.microsoft.manual=<b>إذا كان اتصالك بالإنترنت ضعيفاً، قد يتسبب ذلك في بطء تحميل صفحات الويب أو فشلها كلياً.\\nيمكنك المحاولة لاحقاً أو التبديل إلى اتصال إنترنت مختلف.</b>\naccount.methods.microsoft.profile=ملف الحساب\naccount.methods.microsoft.purchase=شراء Minecraft\naccount.methods.microsoft.snapshot=أنت تستخدم إصداراً غير رسمي من HMCL. يُرجى تنزيل <a href=\"https://hmcl.huangyuhui.net/download\">الإصدار الرسمي</a> لتسجيل الدخول.\naccount.methods.microsoft.snapshot.tooltip=أنت تستخدم إصداراً غير رسمي من HMCL. يُرجى تنزيل الإصدار الرسمي لتحديث الحساب.\naccount.methods.microsoft.snapshot.website=الموقع الرسمي\naccount.methods.offline=غير متصل\naccount.methods.offline.name.special_characters=استخدم الحروف والأرقام والشرطات السفلية فقط (16 حرفاً كحد أقصى)\naccount.methods.offline.name.invalid=يُنصح باستخدام الحروف الإنجليزية والأرقام والشرطات السفلية فقط في اسم المستخدم، على ألا يتجاوز الطول 16 حرفاً.\\n\\\n   \\n\\\n   \\  · صحيح: HuangYu, huang_Yu, Huang_Yu_123;\\n\\\n   \\  · غير صحيح: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\\n\\\n   \\n\\\n   استخدام اسم مستخدم غير صحيح سيمنعك من الانضمام إلى معظم الخوادم وقد يتعارض مع بعض الموديفكيشنز مما يتسبب في تعطل اللعبة.\naccount.methods.offline.uuid=UUID\naccount.methods.offline.uuid.hint=UUID هو معرّف فريد للاعبي Minecraft، وقد يولّد كل مشغّل UUID بطريقة مختلفة. تغييره إلى الذي يولّده مشغّل آخر يتيح لك الاحتفاظ بعناصرك في حساب غير المتصل.\\n\\\n   \\n\\\n   هذا الخيار مخصص للمستخدمين المتقدمين فقط. لا نوصي بتغيير هذا الخيار إلا إذا كنت تعرف ما تفعله.\naccount.methods.offline.uuid.malformed=تنسيق غير صالح.\naccount.methods.ban_query=الاستعلام عن الحظر\naccount.missing=لا توجد حسابات\naccount.missing.add=انقر هنا لإضافة واحد.\naccount.move_to_global=تحويل إلى حساب عام\\nسيتم حفظ معلومات الحساب في ملف إعدادات في مجلد المستخدم الحالي للنظام.\naccount.move_to_portable=تحويل إلى حساب محمول\\nسيتم حفظ معلومات الحساب في ملف إعدادات في نفس المجلد الذي يوجد فيه HMCL.\naccount.not_logged_in=غير مسجّل الدخول\naccount.password=كلمة المرور\naccount.portable=محمول\naccount.skin=المظهر\naccount.skin.file=ملف المظهر\naccount.skin.model=النموذج\naccount.skin.model.default=كلاسيكي\naccount.skin.model.slim=نحيل\naccount.skin.type.alex=Alex\naccount.skin.type.csl_api=Blessing Skin\naccount.skin.type.csl_api.location=العنوان\naccount.skin.type.csl_api.location.hint=رابط CustomSkinAPI\naccount.skin.type.little_skin=LittleSkin\naccount.skin.type.little_skin.hint=تحتاج إلى إنشاء لاعب بنفس اسم حسابك غير المتصل على موقع مزود المظهر. سيتم تعيين مظهرك على المظهر المعيّن للاعبك على موقع مزود المظهر.\naccount.skin.type.local_file=ملف مظهر محلي\naccount.skin.type.steve=Steve\naccount.skin.upload=رفع/تعديل المظهر\naccount.skin.upload.failed=فشل رفع المظهر.\naccount.skin.invalid_skin=ملف المظهر غير صالح.\naccount.username=اسم المستخدم\n\narchive.author=المؤلف/المؤلفون\narchive.date=تاريخ النشر\narchive.file.name=اسم الملف\narchive.version=الإصدار\n\nassets.download=جارٍ تنزيل الأصول\nassets.download_all=جارٍ التحقق من سلامة الأصول\nassets.index.malformed=ملفات فهرس الأصول المنزّلة تالفة. يمكنك حل هذه المشكلة بالنقر على \"إدارة ← تحديث أصول اللعبة\" في صفحة \"تعديل النسخة\".\n\nbutton.cancel=إلغاء\nbutton.change_source=تغيير مصدر التنزيل\nbutton.clear=مسح\nbutton.copy_and_exit=نسخ والخروج\nbutton.delete=حذف\nbutton.do_not_show_again=لا تُظهر مجدداً\nbutton.edit=تعديل\nbutton.install=تثبيت\nbutton.export=تصدير\nbutton.no=لا\nbutton.ok=موافق\nbutton.ok.countdown=موافق (%d)\nbutton.reset=إعادة الضبط\nbutton.reveal_dir=فتح المجلد\nbutton.refresh=تحديث\nbutton.remove=إزالة\nbutton.remove.confirm=هل أنت متأكد من الإزالة النهائية؟ لا يمكن التراجع عن هذا الإجراء!\nbutton.retry=إعادة المحاولة\nbutton.save=حفظ\nbutton.save_as=حفظ باسم\nbutton.select_all=تحديد الكل\nbutton.view=عرض\nbutton.yes=نعم\n\ncontact=تواصل معنا\ncontact.chat=انضم إلى مجموعة الدردشة\ncontact.chat.discord=Discord\ncontact.chat.discord.statement=مرحباً بك في خادم Discord الخاص بنا.\ncontact.chat.qq_group=مجموعة QQ لمستخدمي HMCL\ncontact.chat.qq_group.statement=مرحباً بك في مجموعة المستخدمين على QQ.\ncontact.feedback=قناة الإبلاغ عن المشكلات\ncontact.feedback.github=GitHub Issues\ncontact.feedback.github.statement=أرسل تقريراً عن مشكلة على GitHub.\n\ncolor.recent=مقترح\ncolor.custom=لون مخصص\n\ncrash.NoClassDefFound=يُرجى التحقق من سلامة هذا البرنامج، أو محاولة تحديث Java.\ncrash.user_fault=تعطّل المشغّل بسبب تلف Java أو بيئة النظام. يُرجى التأكد من تثبيت Java أو نظام التشغيل بشكل صحيح.\n\ncurse.category.0=الكل\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4471\ncurse.category.4474=خيال علمي\ncurse.category.4481=صغير / خفيف\ncurse.category.4483=قتال\ncurse.category.4477=ألعاب صغيرة\ncurse.category.4478=مهام\ncurse.category.4484=متعدد اللاعبين\ncurse.category.4476=استكشاف\ncurse.category.4736=Skyblock\ncurse.category.4475=مغامرة ولعب أدوار\ncurse.category.4487=FTB\ncurse.category.4480=قائم على الخرائط\ncurse.category.4479=صعوبة قصوى\ncurse.category.4482=كبير جداً\ncurse.category.4472=تقنية\ncurse.category.4473=سحر\ncurse.category.5128=Vanilla+\ncurse.category.7418=رعب\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5299=تعليم\ncurse.category.5232=Galacticraft\ncurse.category.5129=Vanilla+\ncurse.category.5189=أدوات مساعدة وتحسين جودة اللعب\ncurse.category.6814=أداء\ncurse.category.6954=Integrated Dynamics\ncurse.category.6484=Create\ncurse.category.6821=إصلاح الأخطاء\ncurse.category.6145=Skyblock\ncurse.category.5190=تحسين جودة اللعب\ncurse.category.5191=أدوات مساعدة وتحسين جودة اللعب\ncurse.category.5192=FancyMenu\ncurse.category.423=خرائط ومعلومات\ncurse.category.426=إضافات\ncurse.category.434=دروع وأدوات وأسلحة\ncurse.category.409=منشآت\ncurse.category.4485=Blood Magic\ncurse.category.420=تخزين\ncurse.category.429=Industrial Craft\ncurse.category.419=سحر\ncurse.category.412=تكنولوجيا\ncurse.category.4557=ريدستون\ncurse.category.428=Tinker's Construct\n# '\ncurse.category.414=نقل اللاعب\ncurse.category.4486=Lucky Blocks\ncurse.category.432=Buildcraft\ncurse.category.418=جينات\ncurse.category.4671=Twitch Integration\ncurse.category.5314=KubeJS\ncurse.category.408=خامات وموارد\ncurse.category.4773=CraftTweaker\ncurse.category.430=Thaumcraft\ncurse.category.422=مغامرة ولعب أدوار\ncurse.category.413=معالجة\ncurse.category.417=طاقة\ncurse.category.415=نقل الطاقة والسوائل والعناصر\ncurse.category.433=Forestry\ncurse.category.425=متنوع\ncurse.category.4545=Applied Energistics 2\ncurse.category.416=زراعة\ncurse.category.421=واجهات برمجية ومكتبات\ncurse.category.4780=Fabric\ncurse.category.424=مظهر\ncurse.category.406=توليد العالم\ncurse.category.435=أدوات الخادم\ncurse.category.411=مخلوقات\ncurse.category.407=بيئات\ncurse.category.427=Thermal Expansion\ncurse.category.410=أبعاد\ncurse.category.436=طعام\ncurse.category.4558=ريدستون\ncurse.category.4843=أتمتة\ncurse.category.4906=MCreator\ncurse.category.7669=Twilight Forest\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/12\ncurse.category.5244=حزم خطوط\ncurse.category.5193=حزم بيانات\ncurse.category.399=ستيمبانك\ncurse.category.396=128x\ncurse.category.398=512x وأعلى\ncurse.category.397=256x\ncurse.category.405=متنوع\ncurse.category.395=64x\ncurse.category.400=واقعي\ncurse.category.393=16x\ncurse.category.403=تقليدي\ncurse.category.394=32x\ncurse.category.404=متحرك\ncurse.category.4465=دعم المودات\ncurse.category.402=قرون وسطى\ncurse.category.401=عصري\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/17\ncurse.category.4464=عالم المودات\ncurse.category.250=خريطة لعبة\ncurse.category.249=إبداعي\ncurse.category.251=بارقور\ncurse.category.253=بقاء\ncurse.category.248=مغامرة\ncurse.category.252=ألغاز\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4546\ncurse.category.4551=Hardcore Questing Mode\ncurse.category.4548=Lucky Blocks\ncurse.category.4556=تطور\ncurse.category.4752=Building Gadgets\ncurse.category.4553=CraftTweaker\ncurse.category.4554=وصفات\ncurse.category.4549=دليل اللعبة\ncurse.category.4547=إعدادات\ncurse.category.4550=مهام\ncurse.category.4555=توليد العالم\ncurse.category.4552=سكريبتات\n\ncurse.sort.author=المؤلف\ncurse.sort.date_created=تاريخ الإنشاء\ncurse.sort.last_updated=آخر تحديث\ncurse.sort.name=الاسم\ncurse.sort.popularity=الشعبية\ncurse.sort.total_downloads=إجمالي التنزيلات\n\ndatetime.format=MMM d, yyyy, h\\:mm\\:ss a\n\ndownload=تنزيل\ndownload.hint=ثبّت الألعاب وحزم المودات أو نزّل المودات وحزم الموارد والظلال والعوالم.\ndownload.code.404=الملف \"%s\" غير موجود على الخادم البعيد.\ndownload.content=الإضافات\ndownload.shader=الظلال\ndownload.curseforge.unavailable=هذا الإصدار من HMCL لا يدعم الوصول إلى CurseForge. يُرجى استخدام الإصدار الرسمي للوصول إلى CurseForge.\ndownload.existing=لا يمكن حفظ الملف لأنه موجود مسبقاً. يمكنك النقر على \"حفظ باسم\" لحفظ الملف في مكان آخر.\ndownload.external_link=زيارة موقع التنزيل\ndownload.failed=فشل تنزيل \"%1$s\"، رمز الاستجابة: %2$d.\ndownload.failed.empty=لا توجد إصدارات متاحة. انقر هنا للرجوع.\ndownload.failed.no_code=فشل التنزيل\ndownload.failed.refresh=فشل جلب قائمة الإصدارات. انقر هنا لإعادة المحاولة.\ndownload.game=لعبة جديدة\ndownload.provider.bmclapi=BMCLAPI\ndownload.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/\ndownload.provider.mojang=رسمي\ndownload.provider.mojang.desc=يتم توفير OptiFine عبر BMCLAPI\ndownload.provider.official=من المصادر الرسمية\ndownload.provider.official.desc=الأحدث، لكن قد يكون التحميل بطيئاً\ndownload.provider.balanced=من أسرع المصادر المتاحة\ndownload.provider.balanced.desc=متوازن، لكن قد لا يكون الأحدث\ndownload.provider.mirror=من مرآة\ndownload.provider.mirror.desc=سريع، لكن قد لا يكون الأحدث\ndownload.java=جارٍ تنزيل Java\ndownload.java.process=عملية تنزيل Java\ndownload.javafx=جارٍ تنزيل مكتبات المشغّل...\ndownload.javafx.notes=نقوم حالياً بتنزيل مكتبات HMCL من الإنترنت.\\n\\\n   \\n\\\n   يمكنك النقر على \"تغيير مصدر التنزيل\" لاختيار مصدر التنزيل، أو\\nالنقر على \"إلغاء\" للإيقاف والخروج.\\n\\\n   ملاحظة: إذا كانت سرعة التنزيل بطيئة جداً، يمكنك المحاولة بالتبديل إلى مرآة أخرى.\ndownload.javafx.component=جارٍ تنزيل الوحدة \"%s\"\ndownload.javafx.prepare=جارٍ التحضير للتنزيل\ndownload.speed.byte_per_second=%d B/s\ndownload.speed.kibibyte_per_second=%.1f KiB/s\ndownload.speed.megabyte_per_second=%.1f MiB/s\n\nexception.access_denied=لا يستطيع HMCL الوصول إلى الملف \"%s\". قد يكون مقفلاً بواسطة عملية أخرى.\\n\\\n   \\n\\\n   لمستخدمي Windows، يمكنك فتح \"مراقب الموارد\" للتحقق مما إذا كانت عملية أخرى تستخدمه حالياً. إذا كان الأمر كذلك، يمكنك المحاولة مجدداً بعد إنهاء تلك العملية.\\n\\\n   إذا لم يكن كذلك، يُرجى التحقق من أن حساب المستخدم الخاص بك يملك الصلاحيات الكافية للوصول إليه.\nexception.artifact_malformed=لا يمكن التحقق من سلامة الملفات المنزّلة.\nexception.ssl_handshake=فشل إنشاء اتصال SSL لأن شهادة SSL مفقودة من تثبيت Java الحالي. يمكنك محاولة فتح HMCL بتثبيت Java آخر والمحاولة مجدداً.\nexception.dns.pollution=فشل إنشاء اتصال SSL. قد يكون تحليل DNS غير صحيح. يُرجى تغيير خادم DNS الخاص بك أو استخدام خدمة وكيل.\n\nextension.bat=ملف دُفعي Windows\nextension.png=ملف صورة\nextension.ps1=سكريبت Windows PowerShell\nextension.sh=سكريبت شيل\nextension.command=سكريبت شيل macOS\n\nextension.datapack=أرشيف حزمة البيانات\nextension.mod=ملف مود\nextension.modloader=مثبّت محمّل المودات\nextension.resourcepack=أرشيف حزمة الموارد\nextension.schematic=ملف مخطط\nextension.world=أرشيف العالم\n\nfatal.create_hmcl_current_directory_failure=لا يستطيع Hello Minecraft! Launcher إنشاء مجلد HMCL (%s). يُرجى نقل HMCL إلى موقع آخر وإعادة تشغيله.\nfatal.javafx.incomplete=بيئة JavaFX غير مكتملة.\\n\\\n   يُرجى محاولة استبدال Java أو إعادة تثبيت OpenJFX.\nfatal.javafx.missing=بيئة JavaFX مفقودة. يُرجى فتح Hello Minecraft! Launcher باستخدام Java الذي يتضمن OpenJFX.\nfatal.config_change_owner_root=أنت تستخدم حساب الجذر لفتح Hello Minecraft! Launcher. قد يمنعك ذلك من فتح HMCL بحساب آخر في المستقبل.\\n\\\n   هل تريد المتابعة؟\nfatal.config_in_temp_dir=أنت تفتح Hello Minecraft! Launcher في مجلد مؤقت. قد تُفقد إعداداتك وبيانات اللعبة.\\n\\\n   يُنصح بنقل HMCL إلى موقع آخر وإعادة تشغيله.\\n\\\n   هل تريد المتابعة؟\nfatal.config_loading_failure=لا يمكن تحميل ملفات الإعدادات.\\n\\\n   يُرجى التأكد من أن Hello Minecraft! Launcher يملك صلاحيات القراءة والكتابة على \"%s\" والملفات الموجودة فيه.\\n\\\n   لمستخدمي macOS، جرّب وضع HMCL في موقع بصلاحيات غير \"سطح المكتب\" و\"التنزيلات\" و\"المستندات\" وأعد المحاولة.\nfatal.config_loading_failure.unix=لا يستطيع Hello Minecraft! Launcher تحميل ملف الإعدادات لأنه أُنشئ بواسطة المستخدم \"%1$s\".\\n\\\n   يُرجى فتح HMCL كمستخدم جذر (غير موصى به)، أو تنفيذ الأمر التالي في الطرفية لتغيير ملكية ملف الإعدادات إلى المستخدم الحالي:\\n%2$s\nfatal.config_unsupported_version=ملف الإعدادات الحالي أُنشئ بواسطة إصدار أحدث من Hello Minecraft! Launcher، ولا يستطيع هذا الإصدار من HMCL تحميله بشكل صحيح.\\n\\\n   يُرجى تحديث HMCL وإعادة تشغيله.\\n\\\n   قبل تحديث المشغّل، لن يتم حفظ أي إعدادات تقوم بتعديلها.\nfatal.mac_app_translocation=تم عزل Hello Minecraft! Launcher في مجلد مؤقت بواسطة نظام التشغيل بسبب آليات أمان macOS.\\n\\\n   يُرجى نقل HMCL إلى مجلد آخر قبل محاولة الفتح. وإلا، قد تُفقد إعداداتك وبيانات اللعبة بعد إعادة التشغيل.\\n\\\n   هل تريد المتابعة؟\nfatal.migration_requires_manual_reboot=تم ترقية Hello Minecraft! Launcher. يُرجى إعادة تشغيل المشغّل.\nfatal.apply_update_failure=نأسف، لكن Hello Minecraft! Launcher غير قادر على التحديث التلقائي.\\n\\\n   \\n\\\n   يمكنك التحديث يدوياً بتنزيل إصدار أحدث من المشغّل من %s.\\n\\\n   إذا استمرت المشكلة، يُرجى إرسال تقرير عنها إلينا.\nfatal.apply_update_need_win7=لا يستطيع Hello Minecraft! Launcher التحديث التلقائي على Windows XP/Vista.\\n\\\n   \\n\\\n   يمكنك التحديث يدوياً بتنزيل إصدار أحدث من المشغّل من %s.\nfatal.deprecated_java_version=سيتطلب HMCL Java 17 أو أحدث للتشغيل في المستقبل، لكنه سيستمر في دعم تشغيل الألعاب باستخدام Java 6~16.\\n\\\n\\n\\\nيُنصح بتثبيت أحدث إصدار من Java لضمان عمل HMCL بشكل صحيح.\\n\\\n\\n\\\nيمكنك الاحتفاظ بإصدار Java القديم. يستطيع HMCL التعرف على إصدارات Java المتعددة وإدارتها وسيختار تلقائياً Java المناسب بناءً على إصدار اللعبة.\nfatal.deprecated_java_version.update=سيتطلب HMCL Java 17 أو أحدث للتشغيل في المستقبل. يُرجى تثبيت أحدث إصدار من Java لضمان قدرة HMCL على إتمام الترقية.\\n\\\n\\n\\\nيمكنك الاحتفاظ بإصدار Java القديم.\\\nيستطيع HMCL التعرف على إصدارات Java المتعددة وإدارتها وسيختار تلقائياً Java المناسب بناءً على إصدار اللعبة.\nfatal.deprecated_java_version.download_link=تنزيل Java %d\nfatal.samba=إذا فتحت Hello Minecraft! Launcher من محرك أقراص شبكة Samba، قد لا تعمل بعض الميزات. يُرجى محاولة تحديث Java أو نقل المشغّل إلى مجلد آخر.\nfatal.illegal_char=مسار المستخدم الخاص بك يحتوي على حرف غير مسموح به \"=\". لن تتمكن من استخدام authlib-injector أو تغيير مظهر حسابك غير المتصل.\nfatal.unsupported_platform=لا يدعم Minecraft منصتك الحالية بشكل كامل بعد، لذا قد تواجه ميزات مفقودة أو عدم القدرة على تشغيل اللعبة.\\n\\\n   \\n\\\n   إذا لم تتمكن من تشغيل Minecraft 1.17 أو أحدث، يمكنك محاولة تغيير \"المُصيِّر\" إلى \"Mesa LLVMpipe\" في \"الإعدادات العامة/الخاصة بالنسخة ← الإعدادات المتقدمة\" لاستخدام معالجة المعالج المركزي لتوافق أفضل.\nfatal.unsupported_platform.loongarch=وفّر Hello Minecraft! Launcher دعماً لمنصة Loongson.\\n\\\n   إذا واجهت مشاكل أثناء اللعب، يمكنك زيارة https://docs.hmcl.net/groups.html للحصول على مساعدة.\nfatal.unsupported_platform.macos_arm64=وفّر Hello Minecraft! Launcher دعماً لمنصة Apple silicon، باستخدام Java ARM الأصلي لتشغيل الألعاب للحصول على تجربة لعب أكثر سلاسة.\\n\\\n   إذا واجهت مشاكل أثناء اللعب، قد يوفر تشغيل اللعبة بـ Java المبني على معمارية x86-64 توافقاً أفضل.\nfatal.unsupported_platform.windows_arm64=وفّر Hello Minecraft! Launcher دعماً أصلياً لمنصة Windows on Arm. إذا واجهت مشاكل أثناء اللعب، يُرجى محاولة تشغيل اللعبة بـ Java المبني على معمارية x86.\\n\\\n   \\n\\\n   إذا كنت تستخدم منصة <b>Qualcomm</b>، قد تحتاج إلى تثبيت <a href=\"ms-windows-store://pdp/?productid=9NQPSL29BFFF\">حزمة توافق OpenGL</a> قبل اللعب.\\n\\\n   انقر على الرابط للانتقال إلى Microsoft Store وتثبيت حزمة التوافق.\n\nfile=ملف\n\nfolder.config=الإعدادات\nfolder.game=مجلد العمل\nfolder.logs=السجلات\nfolder.mod=المودات\nfolder.resourcepacks=حزم الموارد\nfolder.shaderpacks=حزم الظلال\nfolder.saves=العوالم\nfolder.schematics=المخططات\nfolder.screenshots=لقطات الشاشة\nfolder.world=مجلد العالم\n\ngame=الألعاب\ngame.crash.feedback=<b>لا تشارك لقطات الشاشة أو صور هذه الواجهة مع الآخرين!</b> إذا طلبت المساعدة من الآخرين، انقر على <b>\"تصدير سجلات التعطل\"</b> وأرسل الملف المُصدَّر لهم للتحليل.\ngame.crash.info=معلومات التعطل\ngame.crash.reason=سبب التعطل\ngame.crash.reason.analyzing=جارٍ التحليل...\ngame.crash.reason.multiple=تم اكتشاف عدة أسباب:\\n\\n\ngame.crash.reason.block=تعطّلت اللعبة بسبب بلوك في العالم.\\n\\\n   \\n\\\n   يمكنك محاولة إزالة هذا البلوك باستخدام <a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">محررات الخرائط</a> أو حذف المود الذي أضافه.\\n\\\n   \\n\\\n   نوع البلوك: %1$s\\n\\\n   الموقع: %2$s\ngame.crash.reason.bootstrap_failed=تعطّلت اللعبة بسبب المود \"%1$s\".\\n\\\n   \\n\\\n   يمكنك محاولة حذفه أو تحديثه.\ngame.crash.reason.config=تعطّلت اللعبة لأن المود \"%1$s\" لم يتمكن من قراءة ملف الإعداد \"%2$s\".\ngame.crash.reason.debug_crash=تعطّلت اللعبة لأنك أثرت ذلك يدوياً. إذاً أنت على الأرجح تعرف السبب :)\ngame.crash.reason.duplicated_mod=لا تستطيع اللعبة الاستمرار بسبب تكرار المودات \"%1$s\".\\n\\\n   \\n\\\n   %2$s\\n\\\n   \\n\\\n   يمكن تثبيت كل مود مرة واحدة فقط. يُرجى حذف المود المكرر وإعادة المحاولة.\ngame.crash.reason.entity=تعطّلت اللعبة بسبب كيان في العالم.\\n\\\n   \\n\\\n   يمكنك محاولة إزالة الكيان باستخدام <a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">محررات الخرائط</a> أو حذف المود الذي أضافه.\\n\\\n   \\n\\\n   نوع الكيان: %1$s\\n\\\n   الموقع: %2$s\ngame.crash.reason.modmixin_failure=تعطّلت اللعبة لأن بعض المودات فشلت في الحقن.\\n\\\n   \\n\\\n   يعني هذا عموماً أن المود يحتوي على خطأ أو غير متوافق مع البيئة الحالية.\\n\\\n   \\n\\\n   يمكنك مراجعة السجل للعثور على المود المسبب للخطأ.\ngame.crash.reason.mod_repeat_installation=تعطّلت اللعبة بسبب مودات مكررة.\\n\\\n   \\n\\\n   يمكن تثبيت كل مود مرة واحدة فقط. يُرجى حذف المود المكرر ثم إعادة تشغيل اللعبة.\ngame.crash.reason.forge_error=قد يكون Forge/NeoForge قد أعطى معلومات عن الخطأ.\\n\\\n   \\n\\\n   يمكنك مراجعة السجل ومعالجة المشكلة وفقاً لمعلومات السجل في تقرير الخطأ.\\n\\\n   \\n\\\n   إذا لم تجد رسالة الخطأ، يمكنك مراجعة تقرير الخطأ لفهم كيفية حدوثه.\\n\\\n   %1$s\ngame.crash.reason.mod_resolution0=تعطّلت اللعبة بسبب بعض مشاكل المودات. يمكنك مراجعة السجلات للعثور على المود/المودات المعطوبة.\ngame.crash.reason.mixin_apply_mod_failed=تعطّلت اللعبة لأن mixin لم يتمكن من التطبيق على المود \"%1$s\".\\n\\\n   \\n\\\n   يمكنك محاولة حذف أو تحديث المود لحل المشكلة.\ngame.crash.reason.java_version_is_too_high=تعطّلت اللعبة لأن إصدار Java حديث جداً للاستمرار في التشغيل.\\n\\\n   \\n\\\n   يُرجى استخدام الإصدار الرئيسي السابق من Java في \"الإعدادات العامة/الخاصة بالنسخة ← Java\" ثم تشغيل اللعبة.\\n\\\n   \\n\\\n   إذا لم يكن لديك واحد، يمكنك تنزيله من <a href=\"https://www.java.com/download/\">java.com (Java 8)</a> أو <a href=\"https://bell-sw.com/pages/downloads/#downloads\">BellSoft Liberica Full JRE (Java 17)</a> وتوزيعات أخرى (أعد تشغيل المشغّل بعد التثبيت).\ngame.crash.reason.need_jdk11=تعطّلت اللعبة بسبب إصدار Java غير مناسب.\\n\\\n   \\n\\\n   تحتاج إلى تنزيل وتثبيت Java 11، وضبطه في \"الإعدادات العامة/الخاصة بالنسخة ← Java\".\ngame.crash.reason.mod_name=تعطّلت اللعبة بسبب مشاكل في أسماء ملفات المودات.\\n\\\n   \\n\\\n   يجب أن تستخدم أسماء ملفات المودات الحروف الإنجليزية فقط (A~Z، a~z) والأرقام (0~9) والشرطات (-) والشرطات السفلية (_) والنقاط (.) بالأحجام النصفية.\\n\\\n   \\n\\\n   يُرجى الانتقال إلى مجلد المودات وتغيير جميع أسماء ملفات المودات غير المتوافقة باستخدام الأحرف المتوافقة أعلاه.\ngame.crash.reason.incomplete_forge_installation=لا تستطيع اللعبة الاستمرار بسبب تثبيت Forge/NeoForge غير مكتمل.\\n\\\n   \\n\\\n   يُرجى إعادة تثبيت Forge/NeoForge في \"تعديل النسخة ← محمّلات المودات\".\ngame.crash.reason.fabric_version_0_12=Fabric Loader 0.12 أو أحدث غير متوافق مع المودات المثبتة حالياً. تحتاج إلى تخفيض إصداره إلى 0.11.7.\ngame.crash.reason.fabric_warnings=أعطى Fabric Loader التحذير التالي:\\n\\\n   \\n\\\n   %1$s\ngame.crash.reason.file_already_exists=تعطّلت اللعبة لأن الملف \"%1$s\" موجود مسبقاً.\\n\\\n   \\n\\\n   يمكنك محاولة نسخ احتياطي لذلك الملف وحذفه، ثم إعادة تشغيل اللعبة.\ngame.crash.reason.file_changed=تعطّلت اللعبة لأن التحقق من الملف فشل.\\n\\\n   \\n\\\n   إذا قمت بتعديل ملف Minecraft.jar، ستحتاج إلى التراجع عن التعديل أو إعادة تنزيل اللعبة.\ngame.crash.reason.gl_operation_failure=تعطّلت اللعبة بسبب بعض المودات أو الظلال أو حزم الموارد/القوام.\\n\\\n   \\n\\\n   يُرجى تعطيل المودات أو الظلال أو حزم الموارد/القوام التي تستخدمها ثم إعادة المحاولة.\ngame.crash.reason.graphics_driver=تعطّلت اللعبة بسبب مشكلة في تعريف بطاقة الرسومات الخاصة بك.\\n\\\n   \\n\\\n   يُرجى إعادة المحاولة بعد تحديث تعريف بطاقة الرسومات إلى أحدث إصدار.\\n\\\n   \\n\\\n   إذا كان جهازك يحتوي على بطاقة رسومات مخصصة، تحتاج إلى التحقق مما إذا كانت اللعبة تستخدم الرسومات المدمجة/الأساسية. إذا كان الأمر كذلك، يُرجى فتح المشغّل باستخدام بطاقة الرسومات المخصصة. إذا استمرت المشكلة، ربما ينبغي لك التفكير في استخدام بطاقة رسومات جديدة أو حاسوب جديد.\\n\\\n   \\n\\\n   إذا كنت تستخدم بطاقة الرسومات المدمجة، لاحظ أن Minecraft 1.16.5 أو أقدم يتطلب <a href=\"https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html\">Java 1.8.0_51 أو أقدم</a> لسلسلة معالجات Intel(R) Core(TM) 3000 أو أقدم.\ngame.crash.reason.macos_failed_to_find_service_port_for_display=لا تستطيع اللعبة الاستمرار لأن نافذة OpenGL على منصة Apple silicon فشلت في التهيئة.\\n\\\n   \\n\\\n   لهذه المشكلة، لا يملك HMCL حلولاً مباشرة في الوقت الحالي. يُرجى محاولة فتح أي متصفح والدخول إلى وضع ملء الشاشة، ثم العودة إلى HMCL وتشغيل اللعبة، و<b>العودة بسرعة إلى صفحة المتصفح</b> قبل ظهور نافذة اللعبة، وانتظر حتى تظهر نافذة اللعبة، ثم التبديل إلى نافذة اللعبة.\ngame.crash.reason.illegal_access_error=تعطّلت اللعبة بسبب بعض المودات.\\n\\\n   \\n\\\n   إذا كنت تعرف هذا: \"%1$s\"، يمكنك تحديث أو حذف المود/المودات ثم إعادة المحاولة.\ngame.crash.reason.install_mixinbootstrap=تعطّلت اللعبة بسبب غياب MixinBootstrap.\\n\\\n   \\n\\\n   يمكنك محاولة تثبيت <a href=\"https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap\">MixinBootstrap</a> لحل المشكلة. إذا تعطّل بعد التثبيت، جرّب إضافة علامة تعجب (!) أمام اسم ملف هذا المود لمحاولة حل المشكلة.\ngame.crash.reason.optifine_is_not_compatible_with_forge=تعطّلت اللعبة لأن OptiFine غير متوافق مع تثبيت Forge الحالي.\\n\\\n   \\n\\\n   يُرجى الانتقال إلى <a href=\"https://optifine.net/downloads\">الموقع الرسمي لـ OptiFine</a>، والتحقق من توافق إصدار Forge مع OptiFine، وإعادة تثبيت النسخة بدقة وفقاً للإصدار المقابل، أو تغيير إصدار OptiFine في \"تعديل النسخة ← محمّلات المودات\".\\n\\\n   \\n\\\n   بعد الاختبار، نعتقد أن إصدارات OptiFine المرتفعة أو المنخفضة جداً قد تسبب تعطلاً.\ngame.crash.reason.mod_files_are_decompressed=تعطّلت اللعبة لأن ملف المود تم فك ضغطه.\\n\\\n   \\n\\\n   يُرجى وضع ملف المود كاملاً مباشرةً في مجلد المودات!\\n\\\n   \\n\\\n   إذا تسبّب فك الضغط في أخطاء في اللعبة، يُرجى حذف المود المفكوك الضغط في مجلد المودات ثم تشغيل اللعبة.\ngame.crash.reason.shaders_mod=تعطّلت اللعبة بسبب تثبيت كل من OptiFine ومود الظلال في نفس الوقت.\\n\\\n   \\n\\\n   فقط أزل مود الظلال لأن OptiFine يحتوي على دعم مدمج للظلال.\ngame.crash.reason.rtss_forest_sodium=تعطّلت اللعبة لأن RivaTuner Statistical Server (RTSS) غير متوافق مع Sodium.\\n\\\n   \\n\\\n   انقر <a href=\"https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\">هنا</a> للمزيد من التفاصيل.\ngame.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=تعطّلت اللعبة لأنك ثبّتت عدداً كبيراً جداً من المودات وتجاوزت حد معرّفات اللعبة.\\n\\\n   \\n\\\n   يُرجى محاولة تثبيت <a href=\"https://www.curseforge.com/minecraft/mc-mods/jeid\">JEID</a> أو حذف بعض المودات الكبيرة.\ngame.crash.reason.night_config_fixes=تعطّلت اللعبة بسبب بعض مشاكل Night Config.\\n\\\n   \\n\\\n   يمكنك محاولة تثبيت مود <a href=\"https://www.curseforge.com/minecraft/mc-mods/night-config-fixes\">Night Config Fixes</a>، الذي قد يساعدك في حل هذه المشكلة.\\n\\\n   \\n\\\n   لمزيد من المعلومات، زر <a href=\"https://github.com/Fuzss/nightconfigfixes\">مستودع GitHub</a> لهذا المود.\ngame.crash.reason.optifine_causes_the_world_to_fail_to_load=قد لا تتمكن اللعبة من الاستمرار بسبب OptiFine.\\n\\\n   \\n\\\n   تحدث هذه المشكلة في إصدار معين من OptiFine فقط. يمكنك محاولة تغيير إصدار OptiFine في \"تعديل النسخة ← محمّلات المودات\".\ngame.crash.reason.jdk_9=تعطّلت اللعبة لأن إصدار Java حديث جداً لهذه النسخة.\\n\\\n   \\n\\\n   تحتاج إلى تنزيل وتثبيت Java 8 واختياره في \"الإعدادات العامة/الخاصة بالنسخة ← Java\".\ngame.crash.reason.jvm_32bit=تعطّلت اللعبة لأن تخصيص الذاكرة الحالي تجاوز حد JVM 32-bit.\\n\\\n   \\n\\\n   إذا كان نظام التشغيل 64-bit، يُرجى تثبيت واستخدام إصدار Java 64-bit. وإلا، قد تحتاج إلى إعادة تثبيت نظام تشغيل 64-bit أو الحصول على حاسوب أحدث.\\n\\\n   \\n\\\n   أو، يمكنك تعطيل خيار \"التخصيص التلقائي\" في \"الإعدادات العامة/الخاصة بالنسخة ← الذاكرة\" وضبط الحجم الأقصى لتخصيص الذاكرة على 1024 ميجابايت أو أقل.\ngame.crash.reason.loading_crashed_forge=تعطّلت اللعبة بسبب المود \"%1$s\" (%2$s).\\n\\\n   \\n\\\n   يمكنك محاولة حذفه أو تحديثه.\ngame.crash.reason.loading_crashed_fabric=تعطّلت اللعبة بسبب المود \"%1$s\".\\n\\\n   \\n\\\n   يمكنك محاولة حذفه أو تحديثه.\ngame.crash.reason.mac_jdk_8u261=تعطّلت اللعبة لأن إصدار Forge أو OptiFine الحالي غير متوافق مع تثبيت Java الخاص بك.\\n\\\n   \\n\\\n   يُرجى محاولة تحديث Forge وOptiFine، أو محاولة استخدام Java 8u251 أو الإصدارات السابقة.\ngame.crash.reason.forge_repeat_installation=تعطّلت اللعبة بسبب تثبيت Forge مكرر. <a href=\"https://github.com/HMCL-dev/HMCL/issues/1880\">هذه مشكلة معروفة</a>\\n\\\n   \\n\\\n   يُنصح بإرسال تقرير على GitHub مع هذا السجل حتى نتمكن من إيجاد مزيد من الأدلة وحل المشكلة.\\n\\\n   \\n\\\n   حالياً يمكنك إلغاء تثبيت Forge وإعادة تثبيته في \"تعديل النسخة ← محمّلات المودات\".\ngame.crash.reason.optifine_repeat_installation=تعطّلت اللعبة بسبب تثبيت OptiFine مكرر.\\n\\\n   \\n\\\n   يُرجى حذف OptiFine من مجلد المودات أو إلغاء تثبيته في \"تعديل النسخة ← محمّلات المودات\".\ngame.crash.reason.memory_exceeded=تعطّلت اللعبة لأن الذاكرة المخصصة كانت كبيرة جداً لملف الصفحة الصغير.\\n\\\n   \\n\\\n   يمكنك محاولة تعطيل خيار \"التخصيص التلقائي\" في \"الإعدادات العامة/الخاصة بالنسخة ← الذاكرة\" وضبط القيمة حتى تعمل اللعبة.\\n\\\n   \\n\\\n   يمكنك أيضاً محاولة زيادة حجم ملف الصفحة في إعدادات النظام.\ngame.crash.reason.mod=تعطّلت اللعبة بسبب المود \"%1$s\".\\n\\\n   \\n\\\n   يمكنك تحديث أو حذف المود ثم إعادة المحاولة.\ngame.crash.reason.mod_resolution=لا تستطيع اللعبة الاستمرار بسبب مشاكل في تبعيات المودات.\\n\\\n   \\n\\\n   أعطى Fabric التفاصيل التالية:\\n\\\n   \\n\\\n   %1$s\ngame.crash.reason.forgemod_resolution=لا تستطيع اللعبة الاستمرار بسبب مشاكل في تبعيات المودات.\\n\\\n   \\n\\\n   أعطى Forge/NeoForge التفاصيل التالية:\\n\\\n   %1$s\ngame.crash.reason.forge_found_duplicate_mods=تعطّلت اللعبة بسبب مشكلة مودات مكررة. أعطى Forge/NeoForge المعلومات التالية:\\n\\\n   %1$s\ngame.crash.reason.mod_resolution_collection=تعطّلت اللعبة لأن إصدار المود غير متوافق.\\n\\\n   \\n\\\n   يتطلب \"%1$s\" المود \"%2$s\".\\n\\\n   \\n\\\n   تحتاج إلى ترقية أو تخفيض إصدار \"%3$s\" قبل المتابعة.\ngame.crash.reason.mod_resolution_conflict=تعطّلت اللعبة بسبب مودات متعارضة.\\n\\\n   \\n\\\n   \"%1$s\" غير متوافق مع \"%2$s\".\ngame.crash.reason.mod_resolution_missing=تعطّلت اللعبة لأن بعض المودات التابعة لم يتم تثبيتها.\\n\\\n   \\n\\\n   يتطلب \"%1$s\" المود \"%2$s\".\\n\\\n   \\n\\\n   هذا يعني أنك تحتاج إلى تنزيل وتثبيت \"%2$s\" أولاً للمتابعة.\ngame.crash.reason.mod_resolution_missing_minecraft=تعطّلت اللعبة لأن مود غير متوافق مع إصدار Minecraft الحالي.\\n\\\n   \\n\\\n   يتطلب \"%1$s\" إصدار Minecraft %2$s.\\n\\\n   \\n\\\n   إذا أردت اللعب بهذا الإصدار من المود مثبتاً، يجب تغيير إصدار اللعبة في نسختك.\\n\\\n   \\n\\\n   وإلا، يجب تثبيت إصدار متوافق مع إصدار Minecraft هذا.\ngame.crash.reason.mod_resolution_mod_version=%1$s (الإصدار: %2$s)\ngame.crash.reason.mod_resolution_mod_version.any=%1$s (أي إصدار)\ngame.crash.reason.modlauncher_8=تعطّلت اللعبة لأن إصدار Forge الحالي غير متوافق مع تثبيت Java الخاص بك. يُرجى محاولة تحديث Forge.\ngame.crash.reason.no_class_def_found_error=لا تستطيع اللعبة الاستمرار بسبب كود غير مكتمل.\\n\\\n   \\n\\\n   نسختك من اللعبة تفتقد \"%1$s\". قد يكون ذلك بسبب غياب مود، أو تثبيت مود غير متوافق، أو تلف بعض الملفات.\\n\\\n   \\n\\\n   قد تحتاج إلى إعادة تثبيت اللعبة وجميع المودات أو طلب المساعدة.\ngame.crash.reason.no_such_method_error=لا تستطيع اللعبة الاستمرار بسبب كود غير مكتمل.\\n\\\n   \\n\\\n   قد تفتقد نسختك من اللعبة مود معين، أو تحتوي على مود غير متوافق، أو قد تكون بعض الملفات تالفة.\\n\\\n   \\n\\\n   قد تحتاج إلى إعادة تثبيت اللعبة وجميع المودات أو طلب المساعدة.\ngame.crash.reason.opengl_not_supported=تعطّلت اللعبة لأن تعريف بطاقة الرسومات لا يدعم OpenGL.\\n\\\n   \\n\\\n   إذا كنت تبث اللعبة عبر الإنترنت أو تستخدم بيئة سطح مكتب بعيد، يُرجى اللعب على جهازك المحلي.\\n\\\n   \\n\\\n   أو، يمكنك تحديث تعريف بطاقة الرسومات إلى أحدث إصدار ثم إعادة المحاولة.\\n\\\n   \\n\\\n   إذا كان جهازك يحتوي على بطاقة رسومات مخصصة، يُرجى التأكد من أن اللعبة تستخدمها فعلاً للرسم. إذا استمرت المشكلة، يُرجى التفكير في الحصول على بطاقة رسومات جديدة أو حاسوب جديد.\ngame.crash.reason.openj9=اللعبة غير قادرة على العمل على JVM من نوع OpenJ9. يُرجى التبديل إلى Java يستخدم Hotspot JVM في \"الإعدادات العامة/الخاصة بالنسخة ← Java\" وإعادة تشغيل اللعبة. إذا لم يكن لديك واحد، يمكنك تنزيله.\ngame.crash.reason.out_of_memory=تعطّلت اللعبة لأن الحاسوب نفد منه الذاكرة.\\n\\\n   \\n\\\n   ربما لا توجد ذاكرة كافية متاحة أو تم تثبيت عدد كبير جداً من المودات. يمكنك محاولة حل المشكلة بزيادة الذاكرة المخصصة في \"الإعدادات العامة/الخاصة بالنسخة ← الذاكرة\".\\n\\\n   \\n\\\n   إذا استمررت في مواجهة هذه المشاكل، قد تحتاج إلى حاسوب أفضل.\ngame.crash.reason.resolution_too_high=تعطّلت اللعبة لأن دقة حزمة الموارد/القوام عالية جداً.\\n\\\n   \\n\\\n   يجب التبديل إلى حزمة موارد/قوام بدقة أقل أو التفكير في شراء بطاقة رسومات أفضل بذاكرة VRAM أكبر.\ngame.crash.reason.stacktrace=سبب التعطل غير معروف. يمكنك مراجعة تفاصيله بالنقر على \"السجلات\".\\n\\\n   \\n\\\n   هناك بعض الكلمات المفتاحية التي قد تحتوي على أسماء مودات. يمكنك البحث عنها عبر الإنترنت لتحديد المشكلة بنفسك.\\n\\\n   \\n\\\n   %s\ngame.crash.reason.too_old_java=تعطّلت اللعبة لأنك تستخدم إصداراً قديماً من Java.\\n\\\n   \\n\\\n   تحتاج إلى التبديل إلى إصدار Java أحدث (%1$s) في \"الإعدادات العامة/الخاصة بالنسخة ← Java\" ثم إعادة تشغيل اللعبة. يمكنك تنزيل Java من <a href=\"https://learn.microsoft.com/java/openjdk/download\">هنا</a>.\ngame.crash.reason.unknown=لم نتمكن من تحديد سبب تعطل اللعبة. يُرجى الرجوع إلى سجلات اللعبة.\ngame.crash.reason.unsatisfied_link_error=فشل تشغيل Minecraft بسبب مكتبات مفقودة: %1$s.\\n\\\n   \\n\\\n   إذا قمت بتعديل مسار المكتبة الأصلية، يُرجى التأكد من وجود هذه المكتبات. أو، يُرجى محاولة التشغيل مجدداً بعد إعادتها إلى الوضع الافتراضي.\\n\\\n   \\n\\\n   إذا لم تفعل ذلك، يُرجى التحقق مما إذا كانت لديك مودات تابعة مفقودة.\\n\\\n   \\n\\\n   وإلا، إذا اعتقدت أن HMCL هو السبب، يُرجى إرسال تقرير عن المشكلة إلينا.\ngame.crash.title=تعطّلت اللعبة\ngame.directory=مسار اللعبة\ngame.version=نسخة اللعبة\n\nhelp=مساعدة\nhelp.doc=توثيق Hello Minecraft! Launcher\nhelp.detail=لصانعي حزم البيانات وحزم المودات.\n\ninput.email=يجب أن يكون اسم المستخدم بريداً إلكترونياً.\ninput.number=يجب أن يكون الإدخال أرقاماً.\ninput.not_empty=هذا الحقل مطلوب.\ninput.url=يجب أن يكون الإدخال رابطاً صالحاً.\n\ninstall=نسخة جديدة\ninstall.change_version=تغيير الإصدار\ninstall.change_version.confirm=هل أنت متأكد من تغيير %1$s من الإصدار %2$s إلى %3$s؟\ninstall.change_version.process=عملية تغيير الإصدار\ninstall.failed=فشل التثبيت\ninstall.failed.downloading=فشل تنزيل بعض الملفات المطلوبة.\ninstall.failed.downloading.detail=فشل تنزيل الملف: %s\ninstall.failed.downloading.timeout=انتهت مهلة التنزيل عند جلب: %s\ninstall.failed.install_online=فشل التعرف على الملف المقدم. إذا كنت تثبّت مود، انتقل إلى صفحة \"المودات\".\ninstall.failed.malformed=الملفات المنزّلة تالفة. يمكنك محاولة حل هذه المشكلة بالتبديل إلى مصدر تنزيل آخر في \"الإعدادات ← التنزيل ← مصدر التنزيل\".\ninstall.failed.optifine_conflict=لا يمكن تثبيت كل من OptiFine وFabric معاً على Minecraft 1.13 أو أحدث.\ninstall.failed.optifine_forge_1.17=في Minecraft 1.17.1، يتوافق Forge فقط مع OptiFine H1 pre2 أو أحدث. يمكنك تثبيتها بتفعيل \"الإصدارات التجريبية\" عند اختيار إصدار OptiFine في HMCL.\ninstall.failed.version_mismatch=يتطلب هذا المحمّل إصدار اللعبة %1$s، لكن الإصدار المثبت هو %2$s.\ninstall.installer.change_version=%s غير متوافق\ninstall.installer.choose=اختر إصدار %s\ninstall.installer.cleanroom=Cleanroom\ninstall.installer.depend=يتطلب %s\ninstall.installer.do_not_install=لا تثبّت\ninstall.installer.fabric=Fabric\ninstall.installer.fabric-api=Fabric API\ninstall.installer.legacyfabric=Legacy Fabric\ninstall.installer.legacyfabric-api=Legacy Fabric API\ninstall.installer.fabric-quilt-api.warning=%1$s هو مود وسيتم تثبيته في مجلد مودات نسخة اللعبة. يُرجى عدم تغيير مجلد عمل اللعبة، وإلا لن يعمل %1$s. إذا أردت تغيير المجلد، يجب إعادة تثبيته.\ninstall.installer.forge=Forge\ninstall.installer.neoforge=NeoForge\ninstall.installer.game=Minecraft\ninstall.installer.incompatible=غير متوافق مع %s\ninstall.installer.install=جارٍ تثبيت %s\ninstall.installer.install_offline=تثبيت/تحديث من ملف محلي\ninstall.installer.install_offline.tooltip=ندعم استخدام مثبّت (Neo)Forge وCleanroom وOptiFine المحلي.\ninstall.installer.install_online=تثبيت عبر الإنترنت\ninstall.installer.install_online.tooltip=ندعم حالياً Forge وNeoForge وCleanroom وOptiFine وFabric وLegacy Fabric وQuilt وLiteLoader.\ninstall.installer.liteloader=LiteLoader\ninstall.installer.not_installed=غير مثبّت\ninstall.installer.optifine=OptiFine\ninstall.installer.quilt=Quilt\ninstall.installer.quilt-api=QSL/QFAPI\ninstall.installer.version=%s\ninstall.installer.external_version=%s (مثبَّت بواسطة عملية خارجية، لا يمكن إعداده)\ninstall.installing=جارٍ التثبيت\ninstall.modpack=تثبيت حزمة مودات\ninstall.modpack.installation=تثبيت حزمة المودات\ninstall.name.invalid=الاسم يحتوي على أحرف خاصة (مثل الرموز التعبيرية أو أحرف CJK).\\nيُنصح بتغيير الاسم ليحتوي على الحروف الإنجليزية والأرقام والشرطات السفلية فقط لتجنب مشاكل محتملة عند تشغيل اللعبة.\\nهل تريد المتابعة؟\ninstall.new_game=تثبيت نسخة\ninstall.new_game.already_exists=اسم هذه النسخة موجود مسبقاً. يُرجى اختيار اسم آخر.\ninstall.new_game.current_game_version=إصدار النسخة الحالية\ninstall.new_game.installation=تثبيت النسخة\ninstall.new_game.malformed=اسم غير صالح.\ninstall.select=اختر العملية\ninstall.success=تم التثبيت بنجاح.\n\njava.add=إضافة\njava.add.failed=هذا Java غير صالح أو غير متوافق مع المنصة الحالية.\njava.disable=تعطيل Java\njava.disable.confirm=هل أنت متأكد من تعطيل هذا Java؟\njava.disabled.management=Java المعطّل\njava.disabled.management.remove=إزالة هذا Java من القائمة\njava.disabled.management.restore=إعادة تفعيل هذا Java\njava.download=تنزيل\njava.download.banshanjdk-8=تنزيل Banshan JDK 8\njava.download.load_list.failed=فشل تحميل قائمة الإصدارات\njava.download.more=توزيعات Java إضافية\njava.download.title=تنزيل Java\njava.download.prompt=يُرجى اختيار إصدار Java الذي تريد تنزيله:\njava.download.distribution=التوزيع\njava.download.version=الإصدار\njava.download.packageType=نوع الحزمة\njava.management=إدارة Java\njava.info.architecture=المعمارية\njava.info.vendor=المزوّد\njava.info.version=الإصدار\njava.info.disco.distribution=التوزيع\njava.install=تثبيت Java\njava.install.archive=مسار المصدر\njava.install.failed.exists=هذا الاسم مستخدم مسبقاً\njava.install.failed.invalid=هذه الأرشيف ليست حزمة تثبيت Java صالحة، لذا لا يمكن تثبيتها.\njava.install.failed.unsupported_platform=هذا Java غير متوافق مع المنصة الحالية، لذا لا يمكن تثبيته.\njava.install.name=الاسم\njava.install.warning.invalid_character=حرف غير مسموح به في الاسم\njava.installing=جارٍ تثبيت Java\njava.uninstall=إلغاء تثبيت Java\njava.uninstall.confirm=هل أنت متأكد من إلغاء تثبيت هذا Java؟ لا يمكن التراجع عن هذا الإجراء!\n\nlang.default=استخدام لغة النظام\n\nlaunch.advice=%s هل تريد المتابعة والتشغيل؟\nlaunch.advice.multi=تم اكتشاف المشاكل التالية:\\n\\n%s\\n\\nقد تمنع هذه المشاكل تشغيل اللعبة أو تؤثر على تجربة اللعب.\\nهل تريد المتابعة والتشغيل؟\nlaunch.advice.java.auto=إصدار Java الحالي غير متوافق مع هذه النسخة.\\n\\nانقر \"نعم\" لاختيار إصدار Java الأكثر توافقاً تلقائياً. أو، انتقل إلى \"الإعدادات العامة/الخاصة بالنسخة ← Java\" لاختياره بنفسك.\nlaunch.advice.java.modded_java_7=يتطلب Minecraft 1.7.2 والإصدارات الأقدم Java 7 أو أقدم.\nlaunch.advice.cleanroom=لا يعمل Cleanroom إلا على Java 21 أو أحدث. يُرجى استخدام Java 21 أو إصدار أحدث.\nlaunch.advice.corrected=لقد حللنا مشكلة Java. إذا كنت تريد استخدام إصدار Java الذي اخترته، يمكنك تعطيل \"عدم التحقق من توافق JVM\" في \"الإعدادات العامة/الخاصة بالنسخة ← الإعدادات المتقدمة\".\nlaunch.advice.uncorrected=إذا كنت تريد استخدام إصدار Java الذي اخترته، يمكنك تعطيل \"عدم التحقق من توافق JVM\" في \"الإعدادات العامة/الخاصة بالنسخة ← الإعدادات المتقدمة\".\nlaunch.advice.different_platform=يُنصح باستخدام إصدار Java 64-bit لجهازك، لكنك ثبّتت إصداراً 32-bit.\nlaunch.advice.forge2760_liteloader=إصدار Forge 2760 غير متوافق مع LiteLoader. يُرجى الترقية إلى Forge 2773 أو أحدث.\nlaunch.advice.forge28_2_2_optifine=إصدار Forge 28.2.2 أو أحدث غير متوافق مع OptiFine. يُرجى تخفيض Forge إلى 28.2.1 أو أقدم.\nlaunch.advice.forge37_0_60=إصدارات Forge قبل 37.0.60 غير متوافقة مع Java 17. يُرجى تحديث Forge إلى 37.0.60 أو أحدث، أو تشغيل اللعبة بـ Java 16.\nlaunch.advice.java8_1_13=يعمل Minecraft 1.13 وأحدث فقط على Java 8 أو أحدث. يُرجى استخدام Java 8 أو إصدار أحدث.\nlaunch.advice.java8_51_1_13=قد يتعطل Minecraft 1.13 على إصدارات Java 8 قبل 1.8.0_51. يُرجى تثبيت أحدث إصدار من Java 8.\nlaunch.advice.java9=لا يمكن تشغيل Minecraft 1.12 أو أقدم على Java 9 أو أحدث. يُرجى استخدام Java 8.\nlaunch.advice.modded_java=قد لا تكون بعض المودات متوافقة مع إصدارات Java الأحدث. يُنصح باستخدام Java %1$s لتشغيل Minecraft %2$s.\nlaunch.advice.modlauncher8=إصدار Forge الذي تستخدمه غير متوافق مع إصدار Java الحالي. يُرجى محاولة تحديث Forge.\nlaunch.advice.newer_java=أنت تستخدم إصداراً قديماً من Java لتشغيل اللعبة. يُنصح بالتحديث إلى Java 8، وإلا قد تتسبب بعض المودات في تعطل اللعبة.\nlaunch.advice.not_enough_space=لقد خصّصت حجم ذاكرة أكبر من الذاكرة الفعلية المثبّتة (%d ميجابايت) على حاسوبك. قد تواجه أداءً منخفضاً أو عدم القدرة على تشغيل اللعبة.\nlaunch.advice.require_newer_java_version=يتطلب إصدار اللعبة الحالي Java %s، لكن لم نجد أياً. هل تريد تنزيله الآن؟\nlaunch.advice.too_large_memory_for_32bit=لقد خصّصت حجم ذاكرة يتجاوز حد تثبيت Java 32-bit. قد لا تتمكن من تشغيل اللعبة.\nlaunch.advice.vanilla_linux_java_8=يدعم Minecraft 1.12.2 أو أقدم Java 8 فقط على منصة Linux x86-64 لأن الإصدارات الأحدث لا تستطيع تحميل المكتبات الأصلية 32-bit مثل liblwjgl.so\\n\\nيُرجى تنزيله من java.com أو تثبيت OpenJDK 8.\nlaunch.advice.vanilla_x86.translation=Minecraft غير مدعوم بشكل كامل على منصتك، لذا قد تواجه ميزات مفقودة أو عدم القدرة على تشغيل اللعبة.\\nيمكنك تنزيل Java لمعمارية <b>x86-64</b> من <a href=\"https://learn.microsoft.com/java/openjdk/download\">هنا</a> لتجربة لعب كاملة.\nlaunch.advice.unknown=لا يمكن تشغيل اللعبة للأسباب التالية:\nlaunch.failed=فشل التشغيل\nlaunch.failed.cannot_create_jvm=لا نستطيع إنشاء JVM. قد يكون بسبب وسيطات JVM غير صحيحة. يمكنك محاولة حل المشكلة بإزالة جميع الوسيطات التي أضفتها في \"الإعدادات العامة/الخاصة بالنسخة ← الإعدادات المتقدمة ← خيارات JVM\".\nlaunch.failed.creating_process=لا نستطيع إنشاء عملية جديدة. يُرجى التحقق من مسار Java الخاص بك.\\n\nlaunch.failed.command_too_long=طول الأمر يتجاوز الحد الأقصى لسكريبت الدُفعة. يُرجى محاولة تصديره كسكريبت PowerShell.\nlaunch.failed.decompressing_natives=فشل استخراج المكتبات الأصلية.\\n\nlaunch.failed.download_library=فشل تنزيل المكتبات \"%s\".\nlaunch.failed.executable_permission=فشل جعل سكريبت التشغيل قابلاً للتنفيذ.\nlaunch.failed.execution_policy=ضبط سياسة التنفيذ\nlaunch.failed.execution_policy.failed_to_set=فشل ضبط سياسة التنفيذ\nlaunch.failed.execution_policy.hint=سياسة التنفيذ الحالية تمنع تشغيل سكريبتات PowerShell.\\n\\nانقر \"موافق\" للسماح للمستخدم الحالي بتشغيل سكريبتات PowerShell، أو انقر \"إلغاء\" للإبقاء على الوضع الحالي.\nlaunch.failed.exited_abnormally=تعطّلت اللعبة. يُرجى مراجعة سجل التعطل للمزيد من التفاصيل.\nlaunch.failed.java_version_too_low=إصدار Java الذي حددته منخفض جداً. يُرجى إعادة ضبط إصدار Java.\nlaunch.failed.no_accepted_java=فشل إيجاد إصدار Java متوافق، هل تريد تشغيل اللعبة بـ Java الافتراضي؟\\nانقر \"نعم\" لتشغيل اللعبة بـ Java الافتراضي.\\nأو، انتقل إلى \"الإعدادات العامة/الخاصة بالنسخة ← Java\" لاختياره بنفسك.\nlaunch.failed.sigkill=أُنهيت اللعبة قسراً من قِبل المستخدم أو النظام.\nlaunch.state.dependencies=جارٍ حل التبعيات\nlaunch.state.done=تم التشغيل\nlaunch.state.java=جارٍ التحقق من إصدار Java\nlaunch.state.logging_in=جارٍ تسجيل الدخول\nlaunch.state.modpack=جارٍ تنزيل الملفات المطلوبة\nlaunch.state.waiting_launching=في انتظار تشغيل اللعبة\nlaunch.invalid_java=مسار Java غير صالح. يُرجى إعادة ضبط مسار Java.\n\nlauncher=المشغّل\nlauncher.agreement=الشروط واتفاقية الترخيص\nlauncher.agreement.accept=قبول\nlauncher.agreement.decline=رفض\nlauncher.agreement.hint=يجب الموافقة على اتفاقية الترخيص لاستخدام هذا البرنامج.\nlauncher.background=صورة الخلفية\nlauncher.background.choose=اختر صورة الخلفية\nlauncher.background.classic=كلاسيكي\nlauncher.background.default=افتراضي\nlauncher.background.default.tooltip=أو \"background.png/.jpg/.gif/.webp\" والصور في مجلد \"bg\"\nlauncher.background.network=من رابط\nlauncher.background.paint=لون خالص\nlauncher.cache_directory=مجلد الذاكرة المؤقتة\nlauncher.cache_directory.clean=مسح الذاكرة المؤقتة\nlauncher.cache_directory.choose=اختر مجلد الذاكرة المؤقتة\nlauncher.cache_directory.default=افتراضي (\"%APPDATA%/.minecraft\" أو \"~/.minecraft\")\nlauncher.cache_directory.disabled=معطّل\nlauncher.cache_directory.invalid=فشل إنشاء مجلد الذاكرة المؤقتة، جارٍ الرجوع إلى الافتراضي.\nlauncher.contact=تواصل معنا\nlauncher.crash=واجه Hello Minecraft! Launcher خطأً فادحاً! يُرجى نسخ السجل التالي وطلب المساعدة على Discord أو مجموعة QQ أو GitHub أو أي منتدى Minecraft آخر.\nlauncher.crash.java_internal_error=واجه Hello Minecraft! Launcher خطأً فادحاً بسبب تلف Java. يُرجى إلغاء تثبيت Java وتنزيل إصدار مناسب من <a href=\"https://bell-sw.com/pages/downloads/#downloads\">هنا</a>.\nlauncher.crash.hmcl_out_dated=واجه Hello Minecraft! Launcher خطأً فادحاً! المشغّل قديم. يُرجى تحديثه!\nlauncher.update_java=يُرجى تحديث إصدار Java الخاص بك.\n\nlibraries.download=جارٍ تنزيل المكتبات\n\nlogin.empty_username=لم تحدد اسم المستخدم بعد!\nlogin.enter_password=يُرجى إدخال كلمة المرور.\n\nlogwindow.show_lines=إظهار أرقام الأسطر\nlogwindow.terminate_game=إنهاء عملية اللعبة\nlogwindow.title=السجل\nlogwindow.help=يمكنك الانتقال إلى مجتمع HMCL وإيجاد مساعدة من الآخرين.\nlogwindow.autoscroll=التمرير التلقائي\nlogwindow.export_game_crash_logs=تصدير سجلات التعطل\nlogwindow.export_dump=تصدير تفريغ مكدس اللعبة\nlogwindow.export_dump.no_dependency=لا يحتوي Java الخاص بك على المكتبات اللازمة لإنشاء تفريغ المكدس. يُرجى الانضمام إلى Discord أو مجموعة QQ للحصول على مساعدة.\n\nmain_page=الرئيسية\n\nmessage.cancelled=تم إلغاء العملية\nmessage.confirm=تأكيد\nmessage.copied=تم النسخ إلى الحافظة\nmessage.default=افتراضي\nmessage.doing=يُرجى الانتظار\nmessage.downloading=جارٍ التنزيل\nmessage.error=خطأ\nmessage.failed=فشلت العملية\nmessage.info=معلومة\nmessage.success=اكتملت العملية بنجاح\nmessage.unknown=غير معروف\nmessage.warning=تحذير\nmessage.question=سؤال\n\nmodpack=حزم المودات\nmodpack.choose=اختر حزمة مودات\nmodpack.choose.local=استيراد من ملف محلي\nmodpack.choose.local.detail=يمكنك سحب ملف حزمة المودات وإفلاته هنا.\nmodpack.choose.remote=تنزيل من رابط\nmodpack.choose.remote.detail=مطلوب رابط تنزيل مباشر لملف حزمة المودات البعيد.\nmodpack.choose.repository=تنزيل حزمة مودات من CurseForge أو Modrinth\nmodpack.choose.repository.detail=يمكنك اختيار حزمة المودات المطلوبة في الصفحة التالية.\nmodpack.choose.remote.tooltip=يُرجى إدخال رابط حزمة المودات\nmodpack.completion=جارٍ تنزيل التبعيات\nmodpack.desc=صف حزمة مودتك، بما في ذلك مقدمة وربما سجل تغييرات. يدعم حالياً Markdown والصور من الروابط.\nmodpack.description=وصف حزمة المودات\nmodpack.download=تنزيل حزم المودات\nmodpack.download.title=تنزيل حزمة المودات - %1s\nmodpack.enter_name=أدخل اسماً لهذه الحزمة.\nmodpack.export=تصدير كحزمة مودات\nmodpack.export.as=تصدير حزمة المودات باسم...\nmodpack.file_api=بادئة رابط حزمة المودات\nmodpack.files.blueprints=مخططات BuildCraft\nmodpack.files.config=ملفات إعداد المودات\nmodpack.files.dumps=ملفات تصحيح NEI\nmodpack.files.hmclversion_cfg=ملف إعداد المشغّل\nmodpack.files.liteconfig=ملفات LiteLoader\nmodpack.files.mods=المودات\nmodpack.files.mods.voxelmods=خيارات VoxelMods\nmodpack.files.options_txt=ملف خيارات Minecraft\nmodpack.files.optionsshaders_txt=ملف خيارات الظلال\nmodpack.files.resourcepacks=حزم الموارد/القوام\nmodpack.files.saves=العوالم\nmodpack.files.scripts=ملف إعداد MineTweaker\nmodpack.files.servers_dat=ملف قائمة الخوادم\nmodpack.installing=جارٍ تثبيت حزمة المودات\nmodpack.installing.given=جارٍ تثبيت حزمة المودات %s\nmodpack.introduction=يدعم حالياً حزم Curse وModrinth وMultiMC وMCBBS.\nmodpack.invalid=حزمة مودات غير صالحة، يمكنك محاولة تنزيلها مجدداً.\nmodpack.mismatched_type=نوع حزمة المودات غير متطابق، النسخة الحالية من نوع %1$s، لكن الحزمة المقدمة من نوع %2$s.\nmodpack.name=اسم حزمة المودات\nmodpack.not_a_valid_name=اسم حزمة مودات غير صالح.\nmodpack.origin=المصدر\nmodpack.origin.url=الموقع الرسمي\nmodpack.origin.mcbbs=MCBBS\nmodpack.origin.mcbbs.prompt=معرّف المنشور\nmodpack.scan=جارٍ تحليل فهرس حزمة المودات\nmodpack.task.install=استيراد حزمة مودات\nmodpack.task.install.error=فشل التعرف على هذه الحزمة. ندعم حالياً فقط حزم Curse وModrinth وMultiMC وMCBBS.\nmodpack.type.curse=Curse\nmodpack.type.curse.error=فشل تنزيل التبعيات. يُرجى المحاولة مجدداً أو استخدام خادم وكيل.\nmodpack.type.curse.not_found=بعض التبعيات لم تعد متاحة. يُرجى محاولة تثبيت إصدار أحدث من الحزمة.\nmodpack.type.manual.warning=هذه الحزمة معبّأة يدوياً من قِبل الناشر، وقد تحتوي بالفعل على مشغّل. يُنصح بمحاولة فك ضغط الحزمة وتشغيل اللعبة بمشغّلها الخاص. يستطيع HMCL استيرادها مع عدم ضمان قابليتها للاستخدام. هل تريد المتابعة؟\nmodpack.type.mcbbs=MCBBS\nmodpack.type.mcbbs.export=يمكن استيرادها بواسطة Hello Minecraft! Launcher\nmodpack.type.modrinth=Modrinth\nmodpack.type.modrinth.export=يمكن استيرادها بواسطة مشغّلات شائعة من أطراف ثالثة\nmodpack.type.multimc=MultiMC\nmodpack.type.multimc.export=يمكن استيرادها بواسطة Hello Minecraft! Launcher وMultiMC\nmodpack.type.server=تحديث تلقائي لحزمة المودات من الخادم\nmodpack.type.server.export=يتيح لمالك الخادم تحديث نسخة اللعبة عن بُعد\nmodpack.type.server.malformed=ملف بيانات الحزمة غير صالح. يُرجى التواصل مع صانع الحزمة لحل هذه المشكلة.\nmodpack.unsupported=صيغة حزمة مودات غير مدعومة\nmodpack.update=جارٍ تحديث حزمة المودات\nmodpack.wizard=دليل تصدير حزمة المودات\nmodpack.wizard.step.1=الإعدادات الأساسية\nmodpack.wizard.step.1.title=بعض المعلومات الأساسية لحزمة المودات.\nmodpack.wizard.step.2=اختيار الملفات\nmodpack.wizard.step.2.title=اختر الملفات التي تريد إضافتها إلى حزمة المودات.\nmodpack.wizard.step.3=نوع الحزمة\nmodpack.wizard.step.3.title=اختر نوع حزمة المودات الذي تريد التصدير إليه.\nmodpack.wizard.step.initialization.exported_version=نسخة اللعبة للتصدير\nmodpack.wizard.step.initialization.force_update=إجبار التحديث إلى أحدث إصدار من الحزمة (ستحتاج إلى خادم استضافة ملفات)\nmodpack.wizard.step.initialization.include_launcher=تضمين المشغّل\nmodpack.wizard.step.initialization.modrinth.info=سيطابق المشغّل موارد CurseForge/Modrinth البعيدة بدلاً من الملفات المحلية (بما في ذلك المودات وحزم الموارد وحزم الظلال) أثناء إنشاء الحزمة لتقليل حجمها، وسيحدد الملفات ذات امتداد \".disabled\" كاختيارية للتثبيت.\nmodpack.wizard.step.initialization.no_create_remote_files=عدم مطابقة الملفات البعيدة\nmodpack.wizard.step.initialization.save=تصدير إلى...\nmodpack.wizard.step.initialization.skip_curseforge_remote_files=عدم مطابقة موارد CurseForge البعيدة\nmodpack.wizard.step.initialization.warning=قبل إنشاء حزمة المودات، يُرجى التأكد من أن اللعبة تعمل بشكل طبيعي وأن Minecraft إصدار نهائي وليس تجريبياً. سيحفظ المشغّل إعدادات التنزيل الخاصة بك.\\n\\\n   \\n\\\n   ضع في اعتبارك أنه لا يُسمح بإضافة مودات وحزم موارد تنص صراحةً على عدم جواز توزيعها أو تضمينها في حزم المودات.\nmodpack.wizard.step.initialization.server=انقر هنا لمزيد من المعلومات حول كيفية إنشاء حزمة مودات خادم قابلة للتحديث التلقائي.\n\nmodrinth.category.adventure=مغامرة\nmodrinth.category.atmosphere=أجواء\nmodrinth.category.audio=صوتيات\nmodrinth.category.babric=Babric\nmodrinth.category.blocks=بلوكات\nmodrinth.category.bloom=إضاءة متوهجة\nmodrinth.category.bta-babric=BTA (Babric)\nmodrinth.category.bukkit=Bukkit\nmodrinth.category.bungeecord=BungeeCord\nmodrinth.category.canvas=Canvas\nmodrinth.category.cartoon=كرتوني\nmodrinth.category.challenging=تحدي\nmodrinth.category.colored-lighting=إضاءة ملونة\nmodrinth.category.combat=قتال\nmodrinth.category.core-shaders=ظلال أساسية\nmodrinth.category.cursed=ملعون\nmodrinth.category.datapack=حزمة بيانات\nmodrinth.category.decoration=ديكور\nmodrinth.category.economy=اقتصاد\nmodrinth.category.entities=كيانات\nmodrinth.category.environment=بيئة\nmodrinth.category.equipment=معدات\nmodrinth.category.fabric=Fabric\nmodrinth.category.fantasy=خيال\nmodrinth.category.folia=Folia\nmodrinth.category.foliage=أوراق الشجر\nmodrinth.category.fonts=خطوط\nmodrinth.category.food=طعام\nmodrinth.category.forge=Forge\nmodrinth.category.game-mechanics=ميكانيكيات اللعب\nmodrinth.category.gui=واجهة المستخدم\nmodrinth.category.high=عالي\nmodrinth.category.iris=Iris\nmodrinth.category.items=عناصر\nmodrinth.category.java-agent=Java Agent\nmodrinth.category.kitchen-sink=متنوع\nmodrinth.category.legacy-fabric=Legacy Fabric\nmodrinth.category.library=مكتبة\nmodrinth.category.lightweight=خفيف\nmodrinth.category.liteloader=LiteLoader\nmodrinth.category.locale=لغة\nmodrinth.category.low=منخفض\nmodrinth.category.magic=سحر\nmodrinth.category.management=إدارة\nmodrinth.category.medium=متوسط\nmodrinth.category.minecraft=Minecraft\nmodrinth.category.minigame=ألعاب صغيرة\nmodrinth.category.misc=متنوع\nmodrinth.category.mobs=مخلوقات\nmodrinth.category.modded=مع مودات\nmodrinth.category.models=نماذج\nmodrinth.category.modloader=محمّل مودات\nmodrinth.category.multiplayer=متعدد اللاعبين\nmodrinth.category.neoforge=NeoForge\nmodrinth.category.nilloader=NilLoader\nmodrinth.category.optifine=OptiFine\nmodrinth.category.optimization=تحسين الأداء\nmodrinth.category.ornithe=Ornithe\nmodrinth.category.paper=Paper\nmodrinth.category.path-tracing=تتبع المسار\nmodrinth.category.pbr=PBR\nmodrinth.category.potato=بطاطا (أجهزة ضعيفة)\nmodrinth.category.purpur=Purpur\nmodrinth.category.quests=مهام\nmodrinth.category.quilt=Quilt\nmodrinth.category.realistic=واقعي\nmodrinth.category.reflections=انعكاسات\nmodrinth.category.rift=Rift\nmodrinth.category.screenshot=لقطة شاشة\nmodrinth.category.semi-realistic=شبه واقعي\nmodrinth.category.shadows=ظلال\nmodrinth.category.simplistic=بسيط\nmodrinth.category.social=اجتماعي\nmodrinth.category.spigot=Spigot\nmodrinth.category.sponge=Sponge\nmodrinth.category.storage=تخزين\nmodrinth.category.technology=تكنولوجيا\nmodrinth.category.themed=ثيمات\nmodrinth.category.transportation=نقل\nmodrinth.category.tweaks=تعديلات\nmodrinth.category.utility=أدوات مساعدة\nmodrinth.category.vanilla=Vanilla\nmodrinth.category.vanilla-like=شبيه Vanilla\nmodrinth.category.velocity=Velocity\nmodrinth.category.waterfall=Waterfall\nmodrinth.category.worldgen=توليد العالم\nmodrinth.category.8x-=8x-\nmodrinth.category.16x=16x\nmodrinth.category.32x=32x\nmodrinth.category.48x=48x\nmodrinth.category.64x=64x\nmodrinth.category.128x=128x\nmodrinth.category.256x=256x\nmodrinth.category.512x+=512x+\n\nmods=المودات\nmods.add=إضافة\nmods.add.failed=فشل إضافة المود %s.\nmods.add.success=تمت إضافة %s بنجاح.\nmods.add.title=اختر ملف المود الذي تريد إضافته\nmods.broken_dependency.title=تبعية معطوبة\nmods.broken_dependency.desc=كانت هذه التبعية موجودة مسبقاً، لكنها لم تعد كذلك. جرّب استخدام مصدر تنزيل آخر.\nmods.category=الفئة\nmods.channel.alpha=ألفا\nmods.channel.beta=بيتا\nmods.channel.release=إصدار رسمي\nmods.check_updates=عملية تحديث المودات\nmods.check_updates.button=تحديث\nmods.check_updates.confirm=تحديث\nmods.check_updates.current_version=الإصدار الحالي\nmods.check_updates.empty=جميع المودات محدّثة\nmods.check_updates.failed_check=فشل التحقق من التحديثات.\nmods.check_updates.failed_download=فشل تنزيل بعض الملفات.\nmods.check_updates.file=الملف\nmods.check_updates.source=المصدر\nmods.check_updates.target_version=الإصدار المستهدف\nmods.curseforge=CurseForge\nmods.dependency.embedded=تبعيات مدمجة (مضمّنة مسبقاً في ملف المود من قِبل المطوّر، لا حاجة لتنزيلها)\nmods.dependency.optional=تبعيات اختيارية (إذا كانت مفقودة، ستعمل اللعبة بشكل طبيعي لكن بعض ميزات المود قد تكون غائبة)\nmods.dependency.required=تبعيات مطلوبة (يجب تنزيلها بشكل منفصل، غيابها قد يمنع تشغيل اللعبة)\nmods.dependency.tool=تبعيات مطلوبة (يجب تنزيلها بشكل منفصل، غيابها قد يمنع تشغيل اللعبة)\nmods.dependency.include=تبعيات مدمجة (مضمّنة مسبقاً في ملف المود من قِبل المطوّر، لا حاجة لتنزيلها)\nmods.dependency.incompatible=مودات غير متوافقة (تثبيت هذه المودات معاً سيمنع تشغيل اللعبة)\nmods.dependency.broken=تبعيات معطوبة (كان هذا المود موجوداً مسبقاً، لكنه لم يعد كذلك. جرّب استخدام مصدر تنزيل آخر.)\nmods.disable=تعطيل\nmods.download=تنزيل مود\nmods.download.title=تنزيل مود - %1s\nmods.download.recommend=إصدار المود المقترح - Minecraft %1s\nmods.enable=تفعيل\nmods.game.version=إصدار اللعبة\nmods.manage=المودات\nmods.mcbbs=MCBBS\nmods.mcmod=MCMod\nmods.mcmod.page=صفحة MCMod\nmods.mcmod.search=البحث في MCMod\nmods.modrinth=Modrinth\nmods.name=الاسم\nmods.not_modded=يجب تثبيت محمّل مودات (Forge أو NeoForge أو Fabric أو Legacy Fabric أو Quilt أو LiteLoader) أولاً لإدارة موداتك!\nmods.restore=استعادة\nmods.url=الصفحة الرسمية\nmods.update_modpack_mod.warning=تحديث المودات في حزمة مودات قد يؤدي إلى نتائج لا يمكن إصلاحها، وقد يتسبب في تلف الحزمة بحيث لا تعمل. هل أنت متأكد من التحديث؟\nmods.warning.loader_mismatch=عدم تطابق محمّل المودات\nmods.install=تثبيت\nmods.save_as=حفظ باسم\nmods.unknown=مود غير معروف\n\nmenu.undo=تراجع\nmenu.redo=إعادة\nmenu.cut=قص\nmenu.copy=نسخ\nmenu.paste=لصق\nmenu.deleteselection=حذف\nmenu.selectall=تحديد الكل\n\nnbt.entries=%s إدخال\nnbt.open.failed=فشل فتح الملف\nnbt.save.failed=فشل حفظ الملف\nnbt.title=عرض الملف - %s\n\ndatapack=حزم البيانات\ndatapack.add=إضافة\ndatapack.add.title=اختر أرشيف حزمة البيانات التي تريد إضافتها\ndatapack.reload.toast=اللعبة تعمل حالياً، يُرجى استخدام الأمر /reload لإعادة تحميل حزمة البيانات\ndatapack.title=العالم [%s] - حزم البيانات\n\nweb.failed=فشل تحميل الصفحة\nweb.open_in_browser=هل تريد فتح هذا العنوان في المتصفح:\\n%s\nweb.view_in_browser=عرض الكل في المتصفح\n\nworld=العوالم\nworld.add=إضافة\nworld.add.already_exists=هذا العالم موجود مسبقاً.\nworld.add.failed=فشل إضافة هذا العالم: %s\nworld.add.invalid=فشل تحليل العالم.\nworld.add.title=اختر أرشيف العالم الذي تريد إضافته\nworld.backup=نسخ احتياطي للعالم\nworld.backup.create.new_one=نسخة احتياطية جديدة\nworld.backup.create.failed=فشل إنشاء النسخة الاحتياطية.\\n%s\nworld.backup.create.success=تم إنشاء نسخة احتياطية جديدة بنجاح: %s\nworld.backup.delete=حذف هذه النسخة الاحتياطية\nworld.backup.processing=جارٍ النسخ الاحتياطي...\nworld.chunkbase=Chunk Base\nworld.chunkbase.end_city=مدينة النهاية\nworld.chunkbase.seed_map=خريطة البذرة\nworld.chunkbase.stronghold=القلعة\nworld.chunkbase.nether_fortress=قلعة الجحيم\nworld.duplicate=نسخ العالم\nworld.duplicate.prompt=يُرجى إدخال اسم العالم المنسوخ\nworld.duplicate.failed.already_exists=المجلد موجود مسبقاً\nworld.duplicate.failed.empty_name=لا يمكن أن يكون الاسم فارغاً\nworld.duplicate.failed.invalid_name=الاسم يحتوي على أحرف غير صالحة\nworld.duplicate.failed=فشل نسخ العالم\nworld.duplicate.success.toast=تم نسخ العالم بنجاح\nworld.datapack=حزم البيانات\nworld.datetime=آخر لعب في %s\nworld.delete=حذف العالم\nworld.delete.failed=فشل حذف العالم.\\n%s\nworld.download=تنزيل\nworld.download.title=تنزيل العالم - %1s\nworld.export=تصدير العالم\nworld.export.title=اختر المجلد لهذا العالم المُصدَّر\nworld.export.location=حفظ باسم\nworld.export.wizard=تصدير العالم \"%s\"\nworld.game_version=إصدار اللعبة\nworld.icon=أيقونة العالم\nworld.icon.change=تغيير أيقونة العالم\nworld.icon.change.fail.load.title=فشل تحليل الصورة\nworld.icon.change.fail.load.text=يبدو أن هذه الصورة تالفة ولا يستطيع HMCL تحليلها.\nworld.icon.change.fail.not_64x64.title=خطأ في حجم الصورة\nworld.icon.change.fail.not_64x64.text=دقة الصورة هي %d×%d بدلاً من 64×64. يُرجى توفير صورة 64×64 والمحاولة مجدداً.\nworld.icon.change.succeed.toast=تم تحديث أيقونة العالم بنجاح.\nworld.icon.change.tip=مطلوب صورة PNG بحجم 64×64. لا يستطيع Minecraft تحليل الصور ذات الدقة غير الصحيحة.\nworld.icon.choose.title=اختر أيقونة العالم\nworld.info=معلومات العالم\nworld.info.basic=المعلومات الأساسية\nworld.info.allow_cheats=السماح بالأوامر/الغش\nworld.info.dimension.the_nether=الجحيم\nworld.info.dimension.the_end=النهاية\nworld.info.difficulty=الصعوبة\nworld.info.difficulty.peaceful=سلمي\nworld.info.difficulty.easy=سهل\nworld.info.difficulty.normal=عادي\nworld.info.difficulty.hard=صعب\nworld.info.difficulty_lock=قفل الصعوبة\nworld.info.failed=فشل قراءة معلومات العالم\nworld.info.game_version=إصدار اللعبة\nworld.info.last_played=آخر لعب\nworld.info.generate_features=توليد المنشآت\nworld.info.player=معلومات اللاعب\nworld.info.player.food_level=مستوى الجوع\nworld.info.player.food_saturation_level=التشبع\nworld.info.player.game_type=وضع اللعب\nworld.info.player.game_type.adventure=مغامرة\nworld.info.player.game_type.creative=إبداعي\nworld.info.player.game_type.hardcore=صعوبة قصوى\nworld.info.player.game_type.spectator=مراقب\nworld.info.player.game_type.survival=بقاء\nworld.info.player.health=الصحة\nworld.info.player.last_death_location=موقع آخر وفاة\nworld.info.player.location=الموقع\nworld.info.player.spawn=موقع الظهور\nworld.info.player.xp_level=مستوى الخبرة\nworld.info.random_seed=البذرة\nworld.info.spawn=موقع ظهور العالم\nworld.info.time=وقت اللعب\nworld.info.time.format=%d يوم %d ساعة %d دقيقة\nworld.load.fail=فشل تحميل العالم\nworld.locked=قيد الاستخدام\nworld.locked.failed=العالم قيد الاستخدام حالياً. يُرجى إغلاق اللعبة والمحاولة مجدداً.\nworld.manage=العوالم\nworld.manage.button=إدارة العوالم\nworld.manage.title=العالم - %s\nworld.name=اسم العالم\nworld.name.enter=أدخل اسم العالم\nworld.show_all=عرض الكل\n\nprofile=مجلدات اللعبة\nprofile.already_exists=هذا الاسم موجود مسبقاً. يُرجى استخدام اسم مختلف.\nprofile.default=الحالي\nprofile.home=مشغّل Minecraft\nprofile.instance_directory=مجلد اللعبة\nprofile.instance_directory.choose=اختر مجلد اللعبة\nprofile.manage=قائمة مجلدات النسخ\nprofile.name=الاسم\nprofile.new=مجلد جديد\nprofile.title=مجلدات اللعبة\nprofile.selected=محدد\nprofile.use_relative_path=استخدام مسار نسبي لمجلد اللعبة إن أمكن\n\nrepositories.custom=مستودع Maven مخصص (%s)\nrepositories.maven_central=عالمي (Maven Central)\nrepositories.tencentcloud_mirror=مرآة البر الرئيسي الصيني (مستودع Tencent Cloud Maven)\nrepositories.chooser=يتطلب HMCL وجود JavaFX للعمل.\\n\\\n   \\n\\\n   يُرجى النقر على \"موافق\" لتنزيل JavaFX من المستودع المحدد، أو النقر على \"إلغاء\" للخروج.\\n\\\n   \\n\\\n   المستودعات:\nrepositories.chooser.title=اختر مصدر تنزيل JavaFX\n\nresourcepack=حزم الموارد\nresourcepack.add=إضافة\nresourcepack.add.title=اختر أرشيف حزمة الموارد التي تريد إضافتها\nresourcepack.manage=حزم الموارد\nresourcepack.download=تنزيل\nresourcepack.add.failed=فشل إضافة حزمة الموارد\nresourcepack.delete.failed=فشل حذف حزمة الموارد\nresourcepack.download.title=تنزيل حزمة موارد - %1s\n\nreveal.in_file_manager=فتح في مدير الملفات\n\nschematics=المخططات\nschematics.add=إضافة\nschematics.add.failed=فشل إضافة ملفات المخططات\nschematics.add.title=اختر ملف المخطط الذي تريد استيراده\nschematics.back_to=العودة إلى \"%s\"\nschematics.create_directory=مجلد جديد\nschematics.create_directory.prompt=يُرجى إدخال اسم المجلد الجديد\nschematics.create_directory.failed=فشل إنشاء المجلد\nschematics.create_directory.failed.already_exists=المجلد موجود مسبقاً\nschematics.create_directory.failed.empty_name=لا يمكن أن يكون الاسم فارغاً\nschematics.create_directory.failed.invalid_name=الاسم يحتوي على أحرف غير صالحة\nschematics.info.description=الوصف\nschematics.info.enclosing_size=الحجم المحيط\nschematics.info.name=الاسم\nschematics.info.region_count=المناطق\nschematics.info.schematic_author=المؤلف\nschematics.info.time_created=وقت الإنشاء\nschematics.info.time_modified=وقت التعديل\nschematics.info.total_blocks=إجمالي البلوكات\nschematics.info.total_volume=الحجم الكلي\nschematics.info.version=إصدار المخطط\nschematics.manage=المخططات\nschematics.sub_items=%d عنصر فرعي\n\nsearch=بحث\nsearch.hint.chinese=البحث بالإنجليزية والصينية\nsearch.hint.english=البحث بالإنجليزية فقط\nsearch.enter=أدخل النص هنا\nsearch.sort=ترتيب حسب\nsearch.first_page=الأولى\nsearch.previous_page=السابقة\nsearch.next_page=التالية\nsearch.last_page=الأخيرة\nsearch.page_n=%1$d / %2$s\n\nselector.choose=اختر\nselector.choose_file=اختر ملفاً\nselector.custom=مخصص\n\nsettings=الإعدادات\n\nsettings.advanced=الإعدادات المتقدمة\nsettings.advanced.modify=تعديل الإعدادات المتقدمة\nsettings.advanced.title=الإعدادات المتقدمة - %s\nsettings.advanced.custom_commands=أوامر مخصصة\nsettings.advanced.custom_commands.hint=المتغيرات البيئية التالية متاحة:\\n\\\n   \\  · $INST_NAME: اسم النسخة.\\n\\\n   \\  · $INST_ID: اسم النسخة.\\n\\\n   \\  · $INST_DIR: المسار المطلق لمجلد عمل النسخة.\\n\\\n   \\  · $INST_MC_DIR: المسار المطلق لمجلد اللعبة.\\n\\\n   \\  · $INST_JAVA: ملف Java المستخدم للتشغيل.\\n\\\n   \\  · $INST_FORGE: يُضبط إذا كان Forge مثبّتاً.\\n\\\n   \\  · $INST_NEOFORGE: يُضبط إذا كان NeoForge مثبّتاً.\\n\\\n   \\  · $INST_CLEANROOM: يُضبط إذا كان Cleanroom مثبّتاً.\\n\\\n   \\  · $INST_LITELOADER: يُضبط إذا كان LiteLoader مثبّتاً.\\n\\\n   \\  · $INST_OPTIFINE: يُضبط إذا كان OptiFine مثبّتاً.\\n\\\n   \\  · $INST_FABRIC: يُضبط إذا كان Fabric مثبّتاً.\\n\\\n   \\  · $INST_LEGACYFABRIC: يُضبط إذا كان Legacy Fabric مثبّتاً.\\n\\\n   \\  · $INST_QUILT: يُضبط إذا كان Quilt مثبّتاً.\nsettings.advanced.dont_check_game_completeness=عدم التحقق من سلامة اللعبة\nsettings.advanced.dont_check_jvm_validity=عدم التحقق من توافق JVM\nsettings.advanced.dont_patch_natives=عدم محاولة استبدال المكتبات الأصلية تلقائياً\nsettings.advanced.environment_variables=متغيرات البيئة\nsettings.advanced.game_dir.default=افتراضي (\".minecraft/\")\nsettings.advanced.game_dir.independent=معزول (\".minecraft/versions/<اسم النسخة>/\", باستثناء الأصول والمكتبات)\nsettings.advanced.java_permanent_generation_space=مساحة PermGen\nsettings.advanced.java_permanent_generation_space.prompt=بالميجابايت\nsettings.advanced.jvm=خيارات JVM\nsettings.advanced.jvm_args=وسيطات JVM\nsettings.advanced.jvm_args.prompt=\\  · إذا كانت الوسيطات المُدخلة في \"وسيطات JVM\" مطابقة للوسيطات الافتراضية، فلن تُضاف.\\n\\\n   \\  · أدخل أي وسيطات GC في \"وسيطات JVM\"، وسيتم تعطيل وسيطة G1 الافتراضية.\\n\\\n   \\  · فعّل \"عدم إضافة وسيطات JVM الافتراضية\" لتشغيل اللعبة بدون إضافة الوسيطات الافتراضية.\nsettings.advanced.launcher_visibility.close=إغلاق المشغّل بعد تشغيل اللعبة\nsettings.advanced.launcher_visibility.hide=إخفاء المشغّل بعد تشغيل اللعبة\nsettings.advanced.launcher_visibility.hide_and_reopen=إخفاء المشغّل وإظهاره عند إغلاق اللعبة\nsettings.advanced.launcher_visibility.keep=إبقاء المشغّل ظاهراً\nsettings.advanced.launcher_visible=ظهور المشغّل\nsettings.advanced.minecraft_arguments=وسيطات التشغيل\nsettings.advanced.minecraft_arguments.prompt=افتراضي\nsettings.advanced.natives_directory=مسار المكتبات الأصلية\nsettings.advanced.natives_directory.choose=اختر موقع المكتبة الأصلية المطلوبة\nsettings.advanced.natives_directory.custom=مخصص\nsettings.advanced.natives_directory.default=افتراضي\nsettings.advanced.natives_directory.default.version_id=<اسم النسخة>\nsettings.advanced.natives_directory.hint=هذا الخيار مخصص فقط لمستخدمي Apple silicon والمنصات غير المدعومة رسمياً. يُرجى عدم تعديل هذا الخيار إلا إذا كنت تعرف ما تفعله.\\n\\\n   \\n\\\n   قبل المتابعة، يُرجى التأكد من توفر جميع المكتبات (مثل lwjgl.dll وlibopenal.so) في المجلد المحدد.\\n\\\n   ملاحظة: يُنصح باستخدام مسار يحتوي على حروف إنجليزية فقط لملف المكتبة المحلية المحدد، وإلا قد يتسبب ذلك في فشل تشغيل اللعبة.\nsettings.advanced.no_jvm_args=عدم إضافة وسيطات JVM الافتراضية\nsettings.advanced.no_optimizing_jvm_args=عدم إضافة وسيطات تحسين JVM الافتراضية\nsettings.advanced.precall_command=أمر ما قبل التشغيل\nsettings.advanced.precall_command.prompt=أوامر تُنفَّذ قبل تشغيل اللعبة\nsettings.advanced.process_priority=أولوية العملية\nsettings.advanced.process_priority.low=منخفضة\nsettings.advanced.process_priority.low.desc=\nsettings.advanced.process_priority.below_normal=أقل من عادي\nsettings.advanced.process_priority.below_normal.desc=\nsettings.advanced.process_priority.normal=عادية\nsettings.advanced.process_priority.normal.desc=\nsettings.advanced.process_priority.above_normal=أعلى من عادي\nsettings.advanced.process_priority.above_normal.desc=\nsettings.advanced.process_priority.high=عالية\nsettings.advanced.process_priority.high.desc=\nsettings.advanced.post_exit_command=أمر ما بعد الإغلاق\nsettings.advanced.post_exit_command.prompt=أوامر تُنفَّذ بعد إغلاق اللعبة\nsettings.advanced.renderer=المُصيِّر\nsettings.advanced.renderer.default=افتراضي\nsettings.advanced.renderer.default.desc=OpenGL\nsettings.advanced.renderer.d3d12=Mesa D3D12\nsettings.advanced.renderer.d3d12.desc=DirectX 12 (أداء وتوافق ضعيفان)\nsettings.advanced.renderer.llvmpipe=Mesa LLVMpipe\nsettings.advanced.renderer.llvmpipe.desc=برمجي (أداء ضعيف، توافق أفضل)\nsettings.advanced.renderer.zink=Mesa Zink\nsettings.advanced.renderer.zink.desc=Vulkan (أفضل أداء، توافق ضعيف)\nsettings.advanced.server_ip=عنوان الخادم\nsettings.advanced.server_ip.prompt=الانضمام تلقائياً بعد تشغيل اللعبة\nsettings.advanced.unsupported_system_options=إعدادات غير مطبّقة على النظام الحالي\nsettings.advanced.use_native_glfw=[Linux/FreeBSD فقط] استخدام GLFW النظام\nsettings.advanced.use_native_openal=[Linux/FreeBSD فقط] استخدام OpenAL النظام\nsettings.advanced.workaround=حلول بديلة\nsettings.advanced.workaround.warning=خيارات الحلول البديلة مخصصة للمستخدمين المتقدمين فقط. قد يتسبب تعديل هذه الخيارات في تعطل اللعبة. يُرجى عدم تعديل هذه الخيارات إلا إذا كنت تعرف ما تفعله.\nsettings.advanced.wrapper_launcher=أمر التغليف\nsettings.advanced.wrapper_launcher.prompt=يتيح التشغيل باستخدام برنامج تغليف إضافي مثل \"optirun\" على Linux\n\nsettings.custom=مخصص\n\nsettings.game=الإعدادات\nsettings.game.copy_global=نسخ من الإعدادات العامة\nsettings.game.copy_global.copy_all=نسخ الكل\nsettings.game.copy_global.copy_all.confirm=هل أنت متأكد من الكتابة فوق إعدادات النسخة الحالية؟ لا يمكن التراجع عن هذا الإجراء!\nsettings.game.current=اللعبة\nsettings.game.dimension=الدقة\nsettings.game.exploration=استكشاف\nsettings.game.fullscreen=ملء الشاشة\nsettings.game.java_directory=Java\nsettings.game.java_directory.auto=اختيار تلقائي\nsettings.game.java_directory.auto.not_found=لم يتم تثبيت أي إصدار Java مناسب.\nsettings.game.java_directory.bit=%s بت\nsettings.game.java_directory.choose=اختر Java\nsettings.game.java_directory.invalid=مسار Java غير صحيح\nsettings.game.java_directory.version=تحديد إصدار Java\nsettings.game.java_directory.template=%1$s (%2$s)\nsettings.game.management=إدارة\nsettings.game.working_directory=مجلد العمل\nsettings.game.working_directory.choose=اختر مجلد العمل\nsettings.game.working_directory.hint=فعّل خيار \"معزول\" في \"مجلد العمل\" للسماح للنسخة الحالية بتخزين إعداداتها وعوالمها ومودتها في مجلد منفصل.\\n\\\n   \\n\\\n   يُنصح بتفعيل هذا الخيار لتجنب تعارض المودات، لكنك ستحتاج إلى نقل عوالمك يدوياً.\n\nsettings.icon=الأيقونة\n\nsettings.launcher=إعدادات المشغّل\nsettings.launcher.appearance=المظهر\nsettings.launcher.brightness=وضع السمة\nsettings.launcher.brightness.auto=متابعة إعدادات النظام\nsettings.launcher.brightness.dark=الوضع الداكن\nsettings.launcher.brightness.light=الوضع الفاتح\nsettings.launcher.common_path.tooltip=سيضع HMCL جميع أصول اللعبة وتبعياتها هنا. إذا كانت هناك مكتبات موجودة في مجلد اللعبة، فإن HMCL سيفضّل استخدامها أولاً.\nsettings.launcher.debug=تصحيح\nsettings.launcher.disable_auto_game_options=عدم تغيير لغة اللعبة تلقائياً\nsettings.launcher.download=التنزيل\nsettings.launcher.download.threads=الخيوط\nsettings.launcher.download.threads.auto=تحديد تلقائي\nsettings.launcher.download.threads.hint=قد يتسبب عدد كبير من الخيوط في تجميد النظام، وقد تتأثر سرعة التنزيل بمزوّد الخدمة وخوادم التنزيل. ليس دائماً أن المزيد من الخيوط يزيد سرعة التنزيل.\nsettings.launcher.download_source=مصدر التنزيل\nsettings.launcher.download_source.auto=اختيار مصادر التنزيل تلقائياً\nsettings.launcher.enable_game_list=عرض قائمة النسخ في الصفحة الرئيسية\nsettings.launcher.font=الخط\nsettings.launcher.font.anti_aliasing=مضاد التعرج\nsettings.launcher.font.anti_aliasing.auto=تلقائي\nsettings.launcher.font.anti_aliasing.gray=تدرج رمادي\nsettings.launcher.font.anti_aliasing.lcd=بكسل فرعي\nsettings.launcher.general=عام\nsettings.launcher.language=اللغة\nsettings.launcher.launcher_log.export=تصدير سجلات المشغّل\nsettings.launcher.launcher_log.export.failed=فشل تصدير السجلات.\nsettings.launcher.launcher_log.export.success=تم تصدير السجلات إلى \"%s\".\nsettings.launcher.launcher_log.reveal=فتح السجلات في مدير الملفات\nsettings.launcher.log=التسجيل\nsettings.launcher.log.font=الخط\nsettings.launcher.proxy=الوكيل\nsettings.launcher.proxy.authentication=يتطلب مصادقة\nsettings.launcher.proxy.default=استخدام وكيل النظام\nsettings.launcher.proxy.host=المضيف\nsettings.launcher.proxy.http=HTTP\nsettings.launcher.proxy.none=بدون وكيل\nsettings.launcher.proxy.password=كلمة المرور\nsettings.launcher.proxy.port=المنفذ\nsettings.launcher.proxy.socks=SOCKS\nsettings.launcher.proxy.username=اسم المستخدم\nsettings.launcher.theme=لون السمة\nsettings.launcher.title_transparent=شريط عنوان شفاف\nsettings.launcher.turn_off_animations=تعطيل الحركات\nsettings.launcher.version_list_source=قائمة الإصدارات\nsettings.launcher.background.settings.opacity=الشفافية\n\nsettings.memory=الذاكرة\nsettings.memory.allocate.auto=%1$.1f جيجابايت كحد أدنى / %2$.1f جيجابايت مخصص\nsettings.memory.allocate.auto.exceeded=%1$.1f جيجابايت كحد أدنى / %2$.1f جيجابايت مخصص (%3$.1f جيجابايت متاح)\nsettings.memory.allocate.manual=%1$.1f جيجابايت مخصص\nsettings.memory.allocate.manual.exceeded=%1$.1f جيجابايت مخصص (%3$.1f جيجابايت متاح)\nsettings.memory.auto_allocate=تخصيص تلقائي\nsettings.memory.lower_bound=الحد الأدنى للذاكرة\nsettings.memory.unit.mib=ميجابايت\nsettings.memory.used_per_total=%1$.1f جيجابايت مستخدم / %2$.1f جيجابايت إجمالي\nsettings.physical_memory=حجم الذاكرة الفعلية\nsettings.show_log=عرض السجلات\nsettings.enable_debug_log_output=إخراج سجل التصحيح\nsettings.tabs.installers=محمّلات المودات\nsettings.take_effect_after_restart=يُطبَّق بعد إعادة التشغيل\nsettings.type=نوع إعدادات النسخة\nsettings.type.global=الإعدادات العامة (مشتركة بين النسخ التي لم تُفعَّل فيها \"الإعدادات الخاصة بالنسخة\")\nsettings.type.global.manage=الإعدادات العامة\nsettings.type.global.edit=تعديل الإعدادات العامة\nsettings.type.special.enable=تفعيل الإعدادات الخاصة بالنسخة\nsettings.type.special.edit=تعديل إعدادات النسخة الحالية\nsettings.type.special.edit.hint=النسخة الحالية \"%s\" فعّلت \"الإعدادات الخاصة بالنسخة\". جميع الخيارات في هذه الصفحة لن تؤثر على تلك النسخة. انقر هنا لتعديل إعداداتها الخاصة.\n\nshaderpack.download.title=تنزيل ظلال - %1s\n\nsponsor=المتبرعون\nsponsor.bmclapi=تنزيلات البر الرئيسي الصيني مقدَّمة من BMCLAPI. انقر هنا للمزيد من المعلومات.\nsponsor.hmcl=Hello Minecraft! Launcher هو مشغّل Minecraft مفتوح المصدر يتيح للمستخدمين إدارة نسخ متعددة من Minecraft بسهولة. انقر هنا للمزيد من المعلومات.\n\nsystem.architecture=المعمارية\nsystem.operating_system=نظام التشغيل\n\nterracotta=متعدد اللاعبين\nterracotta.terracotta=Terracotta | متعدد اللاعبين\nterracotta.status=الردهة\nterracotta.back=خروج\nterracotta.feedback.title=ملء استمارة التقييم\nterracotta.feedback.desc=مع تحديث HMCL لنواة متعدد اللاعبين، نأمل أن تخصص 10 ثوانٍ لملء استمارة التقييم.\nterracotta.sudo_installing=يجب على HMCL التحقق من كلمة مرورك قبل تثبيت نواة متعدد اللاعبين\nterracotta.difficulty.easiest=شبكة ممتازة: الاتصال شبه مضمون!\nterracotta.difficulty.simple=شبكة جيدة: قد يستغرق الاتصال بعض الوقت\nterracotta.difficulty.medium=شبكة متوسطة: جارٍ تفعيل مسارات بديلة، وقد يفشل الاتصال\nterracotta.difficulty.tough=شبكة ضعيفة: جارٍ تفعيل مسارات بديلة، وقد يفشل الاتصال\nterracotta.difficulty.estimate_only=نسبة النجاح تقديرية بناءً على نوعي NAT للمضيف والعميل، للاستئناس فقط!\nterracotta.from_local.title=قنوات تنزيل بديلة لنواة متعدد اللاعبين\nterracotta.from_local.desc=في بعض المناطق، قد تكون قناة التنزيل الافتراضية المدمجة غير مستقرة.\nterracotta.from_local.guide=يُرجى تنزيل حزمة نواة متعدد اللاعبين باسم %s. بعد التنزيل، اسحب الملف إلى الصفحة الحالية لتثبيته.\nterracotta.from_local.file_name_mismatch=يجب تنزيل حزمة نواة متعدد اللاعبين باسم %1$s وليس %2$s\nterracotta.export_log=تصدير سجل نواة متعدد اللاعبين\nterracotta.export_log.desc=جمع مزيد من المعلومات للتحليل\nterracotta.status.bootstrap=جارٍ جمع المعلومات\nterracotta.status.uninitialized.not_exist=نواة متعدد اللاعبين: غير منزَّلة\nterracotta.status.uninitialized.not_exist.title=تنزيل نواة متعدد اللاعبين (~ 8 ميجابايت)\nterracotta.status.uninitialized.update=نواة متعدد اللاعبين: تحديث متاح\nterracotta.status.uninitialized.update.title=تحديث نواة متعدد اللاعبين (~ 8 ميجابايت)\nterracotta.status.uninitialized.desc=أنت تتعهد قانونياً بالالتزام الصارم بجميع قوانين وأنظمة بلدك أو منطقتك أثناء اللعب الجماعي.\nterracotta.confirm.title=إشعار للمستخدم\nterracotta.confirm.desc=Terracotta هو برنامج مفتوح المصدر ومجاني من طرف ثالث. يُرجى إرسال تقارير عن أي مشاكل تواجهها عبر القنوات المخصصة أثناء الاستخدام.\\n\\\n  يستخدم Terracotta تقنية P2P. بعد الاتصال الناجح، يتصل المستخدمون في الغرفة مباشرةً ببعضهم دون أي خادم وسيط لنقل البيانات. تعتمد تجربة اللعب الجماعي بشكل كبير على جودة الشبكة لدى المشاركين.\\n\\\n  طوال عملية اللعب الجماعي، يجب الالتزام الصارم بجميع قوانين وأنظمة بلدك ومنطقتك.\nterracotta.status.preparing=نواة متعدد اللاعبين: جارٍ التنزيل (لا تغلق HMCL)\nterracotta.status.launching=نواة متعدد اللاعبين: جارٍ التهيئة\nterracotta.status.unknown=نواة متعدد اللاعبين: جارٍ التهيئة\nterracotta.status.waiting=نواة متعدد اللاعبين: جاهزة\nterracotta.status.waiting.host.title=أريد استضافة جلسة\nterracotta.status.waiting.host.desc=أنشئ غرفة وأنشئ رمز دعوة للعب مع الأصدقاء\nterracotta.status.waiting.host.launch.title=يبدو أنك نسيت تشغيل اللعبة\nterracotta.status.waiting.host.launch.desc=لم يتم العثور على لعبة تعمل\nterracotta.status.waiting.host.launch.skip=اللعبة قيد التشغيل\nterracotta.status.waiting.guest.title=أريد الانضمام إلى جلسة\nterracotta.status.waiting.guest.desc=أدخل رمز الدعوة من اللاعب المضيف للانضمام إلى عالم اللعبة\nterracotta.status.waiting.guest.prompt.title=يُرجى إدخال رمز الدعوة من المضيف\nterracotta.status.waiting.guest.prompt.invalid=رمز دعوة غير صالح\nterracotta.status.scanning=جارٍ البحث عن عوالم الشبكة المحلية\nterracotta.status.scanning.desc=يُرجى <a href=\"hmcl://game/launch\">تشغيل اللعبة</a>، وفتح عالم، والضغط على ESC، واختيار \"فتح على الشبكة المحلية\"، ثم اختيار \"بدء عالم الشبكة المحلية\".\nterracotta.status.scanning.back=سيؤدي هذا أيضاً إلى إيقاف البحث عن عوالم الشبكة المحلية.\nterracotta.status.host_starting=جارٍ إنشاء الغرفة\nterracotta.status.host_starting.back=سيؤدي هذا إلى إيقاف إنشاء الغرفة.\nterracotta.status.host_ok=تم إنشاء الغرفة\nterracotta.status.host_ok.code=رمز الدعوة (تم النسخ)\nterracotta.status.host_ok.code.copy=نسخ رمز الدعوة\nterracotta.status.host_ok.code.copy.toast=تم نسخ رمز الدعوة إلى الحافظة\nterracotta.status.host_ok.code.desc=يُرجى تذكير أصدقائك باختيار وضع الضيف في HMCL - متعدد اللاعبين أو PCL CE وإدخال رمز الدعوة هذا.\nterracotta.status.host_ok.back=سيؤدي هذا أيضاً إلى إغلاق الغرفة، وسيغادر الضيوف الآخرون ولن يتمكنوا من إعادة الانضمام.\nterracotta.status.guest_starting=جارٍ الانضمام إلى الغرفة\nterracotta.status.guest_starting.back=لن يؤدي هذا إلى منع الضيوف الآخرين من الانضمام إلى الغرفة.\nterracotta.status.guest_ok=تم الانضمام إلى الغرفة\nterracotta.status.guest_ok.back=لن يؤدي هذا إلى منع الضيوف الآخرين من الانضمام إلى الغرفة.\nterracotta.status.guest_ok.title=يُرجى تشغيل اللعبة، واختيار متعدد اللاعبين، والنقر المزدوج على ردهة Terracotta.\nterracotta.status.guest_ok.desc=العنوان الاحتياطي: %s\nterracotta.status.exception.back=يُرجى إعادة المحاولة\nterracotta.status.exception.desc.ping_host_fail=فشل الانضمام إلى الغرفة: الغرفة مغلقة أو الشبكة غير مستقرة\nterracotta.status.exception.desc.ping_host_rst=انقطع الاتصال بالغرفة: الغرفة مغلقة أو الشبكة غير مستقرة\nterracotta.status.exception.desc.guest_et_crash=فشل الانضمام إلى الغرفة: تعطّل EasyTier، يُرجى إبلاغ المطوّرين بهذه المشكلة\nterracotta.status.exception.desc.host_et_crash=فشل إنشاء الغرفة: تعطّل EasyTier، يُرجى إبلاغ المطوّرين بهذه المشكلة\nterracotta.status.exception.desc.ping_server_rst=أُغلقت الغرفة: لقد خرجت من عالم اللعبة، أُغلقت الغرفة تلقائياً\nterracotta.status.exception.desc.scaffolding_invalid_response=بروتوكول غير صالح: أرسل المضيف استجابة غير صالحة، يُرجى إبلاغ المطوّرين بهذه المشكلة\nterracotta.status.fatal.retry=إعادة المحاولة\nterracotta.status.fatal.network=فشل تنزيل نواة متعدد اللاعبين. يُرجى التحقق من اتصالك بالإنترنت والمحاولة مجدداً.\nterracotta.status.fatal.install=خطأ فادح: تعذّر تثبيت نواة متعدد اللاعبين.\nterracotta.status.fatal.terracotta=خطأ فادح: تعذّر الاتصال بنواة متعدد اللاعبين.\nterracotta.status.fatal.unknown=خطأ فادح: غير معروف.\nterracotta.player_list=قائمة اللاعبين\nterracotta.player_anonymous=لاعب مجهول\nterracotta.player_kind.host=مضيف\nterracotta.player_kind.local=أنت\nterracotta.player_kind.guest=ضيف\nterracotta.unsupported=متعدد اللاعبين غير مدعوم على المنصة الحالية بعد.\nterracotta.unsupported.os.windows.old=يتطلب متعدد اللاعبين Windows 10 أو أحدث. يُرجى تحديث نظامك.\nterracotta.unsupported.arch.32bit=متعدد اللاعبين غير مدعوم على الأنظمة 32-bit. يُرجى الترقية إلى نظام 64-bit.\nterracotta.unsupported.arch.loongarch64_ow=متعدد اللاعبين غير مدعوم على توزيعات Linux LoongArch64 القديمة. يُرجى التحديث إلى توزيعة العالم الجديد (مثل AOSC OS).\nterracotta.unsupported.region=ميزة متعدد اللاعبين متاحة حالياً فقط لمستخدمي البر الرئيسي الصيني وقد لا تكون متاحة في منطقتك.\n\nunofficial.hint=أنت تستخدم إصداراً غير رسمي من HMCL. لا نستطيع ضمان أمانه.\n\nupdate=تحديث\nupdate.accept=تحديث\nupdate.changelog=سجل التغييرات\nupdate.channel.dev=تجريبي\nupdate.channel.dev.hint=أنت تستخدم حالياً إصدار القناة التجريبية من المشغّل. على الرغم من أنه قد يتضمن ميزات إضافية، إلا أنه أقل استقراراً أحياناً من إصدارات قناة الإصدار الرسمي.\\n\\\n   \\n\\\n   إذا واجهت أي أخطاء أو مشاكل، يُرجى إرسال تقرير عبر القنوات المتاحة في صفحة <a href=\"hmcl://settings/feedback\">الإبلاغ عن المشكلات</a>.\\n\\\n   \\n\\\n   تابع <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> على Bilibili للاطلاع على أهم أخبار HMCL، أو <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> لمتابعة تطور HMCL.\nupdate.channel.dev.title=إشعار القناة التجريبية\nupdate.channel.nightly=ليلي\nupdate.channel.nightly.hint=أنت تستخدم حالياً إصدار القناة الليلية من المشغّل. على الرغم من أنه قد يتضمن ميزات إضافية، إلا أنه دائماً أقل استقراراً من إصدارات القنوات الأخرى.\\n\\\n   \\n\\\n   إذا واجهت أي أخطاء أو مشاكل، يُرجى إرسال تقرير عبر القنوات المتاحة في صفحة <a href=\"hmcl://settings/feedback\">الإبلاغ عن المشكلات</a>.\\n\\\n   \\n\\\n   تابع <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> على Bilibili للاطلاع على أهم أخبار HMCL، أو <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> لمتابعة تطور HMCL.\nupdate.channel.nightly.title=إشعار القناة الليلية\nupdate.channel.stable=رسمي\nupdate.checking=جارٍ التحقق من التحديثات\nupdate.failed=فشل التحديث\nupdate.found=تحديث متاح!\nupdate.newest_version=أحدث إصدار: %s\nupdate.bubble.title=تحديث متاح: %s\nupdate.bubble.subtitle=انقر هنا للتحديث\nupdate.note=قد تحتوي القنوات التجريبية والليلية على مزيد من الميزات والإصلاحات، لكنها تأتي أيضاً بمزيد من المشاكل المحتملة.\nupdate.latest=هذا هو أحدث إصدار\nupdate.no_browser=لا يمكن الفتح في متصفح النظام. لكننا نسخنا الرابط إلى حافظتك، ويمكنك فتحه يدوياً.\nupdate.tooltip=تحديث\nupdate.preview=الاطلاع المبكر على إصدارات HMCL\nupdate.preview.subtitle=فعّل هذا الخيار لاستقبال إصدارات HMCL الجديدة مبكراً للاختبار قبل إصدارها الرسمي.\n\nversion=الألعاب\nversion.name=اسم النسخة\nversion.cannot_read=فشل تحليل نسخة اللعبة، لا يمكن متابعة التثبيت.\nversion.empty=لا توجد نسخ\nversion.empty.add=إضافة نسخة جديدة\nversion.empty.launch=لا توجد نسخ متاحة.\\nيمكنك الانتقال إلى صفحة \"التنزيل\" للحصول على اللعبة، أو تغيير مجلد اللعبة في صفحة \"جميع النسخ\".\nversion.empty.launch.goto_download=الانتقال إلى صفحة التنزيل\nversion.empty.hint=لا توجد نسخ Minecraft هنا.\\nيمكنك محاولة التبديل إلى مجلد لعبة آخر أو النقر هنا لتنزيل واحدة.\nversion.game.all=الكل\nversion.game.april_fools=كذبة أبريل\nversion.game.old=تاريخي\nversion.game.release=رسمي\nversion.game.releases=الإصدارات الرسمية\nversion.game.snapshot=تجريبي\nversion.game.snapshots=الإصدارات التجريبية\nversion.game.support_status.unsupported=غير مدعوم\nversion.game.support_status.untested=غير مختبر\nversion.game.type=النوع\nversion.launch=تشغيل اللعبة\nversion.launch_and_enter_world=لعب في العالم\nversion.launch.empty=تشغيل اللعبة\nversion.launch.empty.installing=جارٍ تثبيت اللعبة\nversion.launch.empty.tooltip=تثبيت وتشغيل أحدث إصدار رسمي\nversion.launch.test=تشغيل تجريبي\nversion.switch=تبديل النسخة\nversion.launch_script=تصدير سكريبت التشغيل\nversion.launch_script.failed=فشل تصدير سكريبت التشغيل.\nversion.launch_script.save=تصدير سكريبت التشغيل\nversion.launch_script.success=تم تصدير سكريبت التشغيل باسم %s.\nversion.manage=جميع النسخ\nversion.manage.clean=حذف ملفات السجلات\nversion.manage.clean.tooltip=حذف الملفات في مجلدي \"logs\" و\"crash-reports\".\nversion.manage.duplicate=نسخ النسخة\nversion.manage.duplicate.duplicate_save=نسخ العوالم\nversion.manage.duplicate.prompt=أدخل اسم النسخة الجديدة\nversion.manage.duplicate.confirm=ستحتوي النسخة المنسوخة على نسخة من جميع الملفات في مجلد النسخة (\".minecraft/versions/<اسم النسخة>\")، مع مجلد عمل وإعدادات معزولة.\nversion.manage.manage=تعديل النسخة\nversion.manage.manage.title=تعديل النسخة - %1s\nversion.manage.redownload_assets_index=تحديث أصول اللعبة\nversion.manage.remove=حذف النسخة\nversion.manage.remove.confirm.trash=هل أنت متأكد من إزالة النسخة \"%1$s\"؟ لا يزال بإمكانك إيجاد ملفاتها في سلة المحذوفات باسم \"%2$s\".\nversion.manage.remove.confirm.independent=نظراً لأن هذه النسخة مخزَّنة في مجلد معزول، فإن حذفها سيحذف أيضاً عوالمها وبياناتها الأخرى. هل تريد حذف النسخة \"%s\"؟\nversion.manage.remove.failed=فشل حذف النسخة. قد تكون بعض الملفات قيد الاستخدام.\nversion.manage.remove_assets=حذف جميع الأصول\nversion.manage.remove_libraries=حذف جميع المكتبات\nversion.manage.rename=إعادة تسمية النسخة\nversion.manage.rename.message=أدخل اسم النسخة الجديدة\nversion.manage.rename.fail=فشل إعادة تسمية النسخة. قد تكون بعض الملفات قيد الاستخدام، أو يحتوي الاسم على حرف غير صالح.\nversion.search=الاسم\nversion.search.prompt=أدخل اسم النسخة للبحث\nversion.settings=الإعدادات\nversion.update=تحديث حزمة المودات\n\nwarning.java_interpreted_mode=يعمل HMCL في بيئة Java مُترجمة تفسيرياً، مما سيؤثر بشكل كبير على الأداء.\\n\\\n  \\n\\\n  نوصي باستخدام Java يدعم JIT لفتح HMCL للحصول على أفضل تجربة.\nwarning.software_rendering=يستخدم HMCL حالياً المعالجة البرمجية، مما سيؤثر بشكل كبير على الأداء.\n\nwiki.tooltip=صفحة Minecraft Wiki\nwiki.version.game=https://minecraft.wiki/w/Java_Edition_%s\nwiki.version.game.snapshot=https://minecraft.wiki/w/Java_Edition_%s\n\nwizard.prev=< السابق\nwizard.failed=فشل\nwizard.finish=إنهاء\nwizard.next=التالي >\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/lang/I18N_es.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Contributors: dxNeil, machinesmith42\n# and Byacrya for basically retranslating it\n\nabout=Acerca de\nabout.copyright=Copyright\nabout.author=Autor\nabout.author.statement=bilibili @huanghongxun\nabout.claim=EULA\nabout.claim.statement=Haga clic en este enlace para ver el texto completo.\nabout.dependency=Bibliotecas de terceros\nabout.legal=Reconocimiento legal\nabout.thanks_to=Gracias a\nabout.thanks_to.bangbang93.statement=Por proporcionar el espejo de descarga de BMCLAPI, ¡por favor, considere hacer una donación!\nabout.thanks_to.burningtnt.statement=Aportar mucho apoyo técnico a HMCL.\nabout.thanks_to.contributors=Todos los colaboradores en GitHub\nabout.thanks_to.contributors.statement=Sin la impresionante comunidad de código abierto, HMCL no habría llegado tan lejos.\nabout.thanks_to.gamerteam.statement=Por proporcionar la imagen de fondo por defecto.\nabout.thanks_to.glavo.statement=Responsable del mantenimiento de HMCL.\nabout.thanks_to.zekerzhayard.statement=Aportar mucho apoyo técnico a HMCL.\nabout.thanks_to.zkitefly.statement=Responsable del mantenimiento de la documentación de HMCL.\nabout.thanks_to.mcbbs=MCBBS (Foro Chino Minecraft)\nabout.thanks_to.mcbbs.statement=Por proporcionar el espejo de descarga de mcbbs.net para los usuarios de China continental. (Ya no disponible)\nabout.thanks_to.mcim=mcmod-info-mirror\nabout.thanks_to.mcim.statement=Por proporcionar el servicio de aceleración de caché de información de mods para los usuarios de China continental.\nabout.thanks_to.mcmod=MCMod (mcmod.cn)\nabout.thanks_to.mcmod.statement=Por proporcionar las traducciones al chino simplificado y la wiki de varios mods.\nabout.thanks_to.red_lnn.statement=Por proporcionar la imagen de fondo por defecto.\nabout.thanks_to.shulkersakura.statement=Por proporcionar el logotipo de HMCL.\nabout.thanks_to.users=Miembros del grupo de usuarios de HMCL\nabout.thanks_to.users.statement=Gracias por las donaciones, los informes de errores, etc.\nabout.thanks_to.yushijinhun.statement=Por proporcionar el soporte relacionado con authlib-injector.\nabout.open_source=Código abierto\nabout.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)\n\naccount=Cuentas\naccount.cape=Capa\naccount.character=Jugador\naccount.choose=Elige un jugador\naccount.create=Añadir cuenta\naccount.create.microsoft=Añadir una cuenta Microsoft\naccount.create.offline=Añadir una cuenta sin conexión\naccount.create.authlibInjector=Añadir una cuenta authlib-injector\naccount.email=Correo electrónico\naccount.failed=Error al actualizar la cuenta.\naccount.failed.character_deleted=El jugador ya ha sido eliminado.\naccount.failed.connect_authentication_server=No se ha podido conectar con el servidor de autenticación. Es posible que su conexión de red no funcione.\naccount.failed.connect_injector_server=No se ha podido conectar con el servidor de autenticación. Por favor, comprueba su red y asegúrate de que has introducido la URL correcta.\naccount.failed.injector_download_failure=No se ha podido descargar authlib-injector. Por favor, compruebe su red, o intente cambiar a un espejo de descarga diferente.\naccount.failed.invalid_credentials=Contraseña incorrecta o tasa limitada, por favor, inténtelo más tarde.\naccount.failed.invalid_password=Contraseña no válida.\naccount.failed.invalid_token=Por favor, intente iniciar sesión de nuevo.\naccount.failed.migration=Su cuenta necesita ser migrada a una cuenta Microsoft. Si ya lo has hecho, debes volver a iniciar sesión con tu cuenta de Microsoft migrada.\naccount.failed.no_character=No hay personajes vinculados a esta cuenta.\naccount.failed.server_disconnected=No se ha podido conectar con el servidor de autenticación. Puedes conectarte utilizando el modo sin conexión o intentar conectarte de nuevo.\\n\\\n  Si lo intentas varias veces y sigue fallando, vuelve a intentar iniciar sesión en la cuenta.\naccount.failed.server_response_malformed=Respuesta del servidor no válida, el servidor de autenticación puede no estar funcionando.\naccount.failed.ssl=Se ha producido un error SSL al conectar con el servidor. Por favor, intente actualizar su Java.\naccount.failed.dns=Se produjo un error SSL al conectar con el servidor. Es posible que la resolución DNS sea incorrecta. Intente cambiar el servidor DNS o usar un servicio proxy.\naccount.failed.wrong_account=Ha iniciado sesión en la cuenta equivocada.\naccount.hmcl.hint=Debe hacer clic en «Iniciar sesión» y completar el proceso en la ventana abierta del navegador.\naccount.injector.add=Nuevo servidor Auth\naccount.injector.empty=Ninguno (Puedes hacer clic en el botón más de la derecha para añadir uno)\naccount.injector.http=Atención: Este servidor utiliza el protocolo HTTP inseguro. Cualquiera entre su conexión podrá ver sus credenciales en texto claro.\naccount.injector.link.homepage=Página de inicio\naccount.injector.link.register=Registrarse\naccount.injector.server=Servidor de autenticación\naccount.injector.server_url=URL del servidor\naccount.injector.server_name=Nombre del servidor\naccount.login=Iniciar sesión\naccount.login.hint=No almacenaremos su contraseña.\naccount.login.skip=Iniciar sesión sin conexión\naccount.login.retry=Reintentar\naccount.login.refresh=Iniciar sesión de nuevo\naccount.login.refresh.microsoft.hint=Debe volver a iniciar sesión en su cuenta Microsoft porque la autorización de la cuenta no es válida.\naccount.login.restricted=Iniciar sesión en su cuenta Microsoft para activar esta función\naccount.logout=Salir\naccount.register=Registrarse\naccount.manage=Lista de cuentas\naccount.copy_uuid=Copiar UUID de la cuenta\naccount.methods=Tipo de inicio de sesión\naccount.methods.authlib_injector=authlib-injector\naccount.methods.microsoft=Microsoft\naccount.methods.microsoft.birth=Cómo cambiar la fecha de nacimiento de su cuenta\naccount.methods.microsoft.close_page=La autorización de la cuenta de Microsoft ha finalizado.\\n\\\n  \\n\\\n  Hay algunos trabajos extra para nosotros, pero puedes cerrar esta pestaña con seguridad por ahora.\naccount.methods.microsoft.makegameidsettings=Crear perfil / Editar nombre del perfil\naccount.methods.microsoft.deauthorize=Desautorizar\naccount.methods.microsoft.logging_in=Iniciando sesión...\naccount.methods.microsoft.profile=Perfil de la cuenta\naccount.methods.microsoft.purchase=Comprar Minecraft\naccount.methods.microsoft.snapshot.website=Sitio web oficial\naccount.methods.offline=Sin conexión\naccount.methods.offline.name.special_characters=Utilice solo letras, números y guiones bajos (máximo 16 caracteres)\naccount.methods.offline.name.invalid.tip=Se recomienda utilizar sólo letras en inglés, números y guiones bajos para el nombre de usuario, y la longitud no debe superar los 16 caracteres.\naccount.methods.offline.name.invalid=Se recomienda utilizar sólo letras en inglés, números y guiones bajos para el nombre de usuario, y la longitud no debe superar los 16 caracteres.\\n\\\n  \\n\\\n  \\  · Algunos nombres de usuario legítimos: HuangYu, huang_Yu, Huang_Yu_123;\\n\\\n  \\  · Algunos nombres de usuario ilegales: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\\n\\\n  \\n\\\n  Usar un nombre de usuario ilegal te impedirá unirte a la mayoría de los servidores y puede entrar en conflicto con algunos mods, haciendo que el juego se cuelgue.\naccount.methods.offline.uuid=UUID\naccount.methods.offline.uuid.hint=UUID es un identificador único para los jugadores de Minecraft, y cada launcher puede generar UUID de manera diferente. Editarlo al generado por otros launchers te permite mantener tus objetos en el inventario de tu cuenta sin conexión.\\n\\\n  \\n\\\n  Esta opción es sólo para usuarios avanzados. No recomendamos editar esta opción a menos que sepas lo que estás haciendo.\naccount.methods.offline.uuid.malformed=Formato no válido.\naccount.methods.ban_query=Consulta de bloqueo de cuentas\naccount.missing=No hay cuentas\naccount.missing.add=Haga clic aquí para añadir una.\naccount.move_to_global=Convertir en cuenta global\\nLa información de la cuenta se guardará en un archivo de configuración del directorio de usuario actual del sistema.\naccount.move_to_portable=Convertir en cuenta portátil\\nLa información de la cuenta se guardará en un archivo de configuración en el mismo directorio que HMCL.\naccount.not_logged_in=No ha iniciado sesión\naccount.password=Contraseña\naccount.portable=Cuenta portátil\naccount.skin=Aspecto\naccount.skin.file=Archivo de aspecto\naccount.skin.model=Modelo\naccount.skin.model.default=Clásico\naccount.skin.model.slim=Slim\naccount.skin.type.alex=Alex\naccount.skin.type.csl_api=Blessing skin\naccount.skin.type.csl_api.location=URL\naccount.skin.type.csl_api.location.hint=CustomSkinAPI URL\naccount.skin.type.little_skin=LittleSkin\naccount.skin.type.little_skin.hint=Tienes que crear un jugador con el mismo nombre de jugador que tu cuenta sin conexión en el sitio web del proveedor de aspectos. Tu aspecto se ajustará ahora al aspecto asignado a tu jugador en el sitio web del proveedor de aspectos.\naccount.skin.type.local_file=Archivo local de aspecto\naccount.skin.type.steve=Steve\naccount.skin.upload=Subir/editar aspecto\naccount.skin.upload.failed=No se pudo subir el aspecto.\naccount.skin.invalid_skin=Archivo de aspecto inválido.\naccount.username=Nombre de usuario\n\narchive.author=Autor(es)\narchive.date=Fecha de publicación\narchive.file.name=Nombre de archivo\narchive.version=Versión\n\nassets.download=Descargando assets\nassets.download_all=Verificando la integridad de los archivos\nassets.index.malformed=Los archivos de índice de los activos descargados estaban dañados. Puede intentar utilizar «Actualizar activos del juego» en la configuración de su instancia del juego para solucionar este problema.\n\nbutton.cancel=Cancelar\nbutton.change_source=Cambiar fuente de descarga\nbutton.clear=Limpiar\nbutton.copy_and_exit=Copiar y salir\nbutton.delete=Borrar\nbutton.do_not_show_again=No volver a mostrar\nbutton.edit=Editar\nbutton.install=Instalar\nbutton.export=Exportar\nbutton.no=No\nbutton.ok=Aceptar\nbutton.ok.countdown=Aceptar (%d)\nbutton.reveal_dir=Carpeta\nbutton.refresh=Refrescar\nbutton.remove=Eliminar\nbutton.remove.confirm=¿Estás seguro de que deseas eliminarlo de forma permanente? ¡Esta acción no se puede deshacer!\nbutton.retry=Reintentar\nbutton.save=Guardar\nbutton.save_as=Guardar como\nbutton.select_all=Seleccionar todo\nbutton.view=Vista\nbutton.yes=Sí\n\ncontact=Comentarios\ncontact.chat=Chat de grupo\ncontact.chat.discord=Discord\ncontact.chat.discord.statement=¡Únase a nuestro servidor Discord!\ncontact.chat.qq_group=Grupo QQ de usuarios de HMCL\ncontact.chat.qq_group.statement=¡Únase a nuestro grupo QQ de usuarios!\ncontact.feedback=Canal de comentarios\ncontact.feedback.github=GitHub Issues\ncontact.feedback.github.statement=Envíe una propuesta en GitHub.\n\ncolor.recent=Recomendado\ncolor.custom=Color personalizado\n\ncrash.NoClassDefFound=Por favor, verifique la integridad de este software, o intente actualizar su Java.\ncrash.user_fault=El launcher se ha bloqueado debido a que Java o el entorno del sistema están dañados. Por favor, asegúrese de que su Java o sistema operativo está instalado correctamente.\n\ncurse.category.0=Todos\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4471\ncurse.category.4474=Ciencia Ficción\ncurse.category.4481=Pequeño / Ligero\ncurse.category.4483=Combate\ncurse.category.4477=Mini Juego\ncurse.category.4478=Misiones\ncurse.category.4484=Multijugador\ncurse.category.4476=Exploración\ncurse.category.4736=Skyblock\ncurse.category.4475=Aventura y RPG\ncurse.category.4487=FTB\ncurse.category.4480=Basado en mapa\ncurse.category.4479=Extremo\ncurse.category.4482=Extra grande\ncurse.category.4472=Tecnología\ncurse.category.4473=Magia\ncurse.category.5128=Vanilla+\ncurse.category.7418=Horror\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5299=Educación\ncurse.category.5232=Galacticraft\ncurse.category.5129=Vanilla+\ncurse.category.5189=Utilidad y calidad de vida\ncurse.category.5190=Calidad de vida\ncurse.category.6145=Skyblock\ncurse.category.6814=Rendimiento\ncurse.category.6954=Integrated Dynamics\ncurse.category.6484=Create\ncurse.category.6821=Corrección de errores\ncurse.category.5191=Utilidad y calidad de vida\ncurse.category.5192=Menú elegante\ncurse.category.423=Mapa e información\ncurse.category.426=Complementos\ncurse.category.434=Armaduras, herramientas y armas\ncurse.category.409=Estructuras\ncurse.category.4485=Blood Magic\ncurse.category.420=Almacenamiento\ncurse.category.429=Industrial Craft\ncurse.category.419=Magia\ncurse.category.412=Tecnología\ncurse.category.4557=Redstone\ncurse.category.428=Tinker's Construct\n# '\ncurse.category.414=Transporte de jugadores\ncurse.category.4486=Lucky Blocks\ncurse.category.432=Buildcraft\ncurse.category.418=Genética\ncurse.category.4671=Integración Twitch\ncurse.category.5314=KubeJS\ncurse.category.408=Recursos y minerales\ncurse.category.4773=CraftTweaker\ncurse.category.430=Thaumcraft\ncurse.category.422=Aventura y RPG\ncurse.category.413=Procesamiento\ncurse.category.417=Energía\ncurse.category.415=Transporte de energía, fluidos y objetos\ncurse.category.433=Silvicultura\ncurse.category.425=Varios\ncurse.category.4545=Applied Energistics 2\ncurse.category.416=Agricultura\ncurse.category.421=API y bibliotecas\ncurse.category.4780=Fabric\ncurse.category.424=Cosmética\ncurse.category.406=Generación de mundos\ncurse.category.435=Utilidades del servidor\ncurse.category.411=Criaturas\ncurse.category.407=Biomas\ncurse.category.427=Thermal Expansion\ncurse.category.410=Dimensiones\ncurse.category.436=Alimentos\ncurse.category.4558=Redstone\ncurse.category.4843=Automatización\ncurse.category.4906=MCreator\ncurse.category.7669=Twilight Forest\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/12\ncurse.category.5244=Paquete de fuentes\ncurse.category.5193=Paquete de datos\ncurse.category.399=Steampunk\ncurse.category.396=128x\ncurse.category.398=512x y superiores\ncurse.category.397=256x\ncurse.category.405=Miscelánea\ncurse.category.395=64x\ncurse.category.400=Fotorealista\ncurse.category.393=16x\ncurse.category.403=Tradicional\ncurse.category.394=32x\ncurse.category.404=Animado\ncurse.category.4465=Soporte de Mod\ncurse.category.402=Medieval\ncurse.category.401=Moderno\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/17\ncurse.category.4464=Mundo modificado\ncurse.category.250=Mapa del juego\ncurse.category.249=Creación\ncurse.category.251=Parkour\ncurse.category.253=Supervivencia\ncurse.category.248=Aventura\ncurse.category.252=Puzzle\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4546\ncurse.category.4551=Modo de búsqueda Hardcore\ncurse.category.4548=Lucky Blocks\ncurse.category.4556=Progresión\ncurse.category.4752=Artilugios de construcción\ncurse.category.4553=CraftTweaker\ncurse.category.4554=Recetas\ncurse.category.4549=Guías\ncurse.category.4547=Configuración\ncurse.category.4550=Quests\ncurse.category.4555=Generación del mundo\ncurse.category.4552=Scripts\n\ncurse.sort.author=Autor\ncurse.sort.date_created=Fecha de creación\ncurse.sort.last_updated=Última actualización\ncurse.sort.name=Nombre\ncurse.sort.popularity=Popularidad\ncurse.sort.total_downloads=Descargas totales\n\ndatetime.format=d MMM yyyy, H\\:mm\\:ss\n\ndownload=Descargar\ndownload.hint=Instalar juegos y modpacks o descargar mods, paquetes de recursos, sombreadores y mundos.\ndownload.code.404=Archivo no encontrado en el servidor remoto: %s\ndownload.content=Complementos\ndownload.shader=Sombreadores\ndownload.curseforge.unavailable=Esta versión de HMCL no permite acceder a CurseForge. Utilice la versión oficial para acceder a CurseForge.\ndownload.existing=El archivo no se puede guardar porque ya existe. Puedes hacer clic en «Guardar como» para guardar el archivo en otro lugar.\ndownload.external_link=Abrir sitio web\ndownload.failed=Falló la descarga de %1$s, código de respuesta: %2$d.\ndownload.failed.empty=No hay versiones disponibles, por favor haga clic aquí para volver.\ndownload.failed.no_code=No se ha podido descargar\ndownload.failed.refresh=No se ha podido obtener la lista de versiones. Por favor, haga clic aquí para volver a intentarlo.\ndownload.game=Nuevo juego\ndownload.provider.bmclapi=BMCLAPI\ndownload.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/\ndownload.provider.mojang=Oficial\ndownload.provider.mojang.desc=OptiFine es proporcionado por BMCLAPI\ndownload.provider.official=De fuentes oficiales\ndownload.provider.balanced=De la fuente más rápida disponible\ndownload.provider.mirror=Desde espejo\ndownload.java=Descargando Java\ndownload.java.process=Proceso de descarga Java\ndownload.javafx=Descargando dependencias para el launcher...\ndownload.javafx.notes=Estamos descargando dependencias para HMCL desde Internet.\\n\\\n  \\n\\\n  Puede hacer clic en «Cambiar fuente de descarga» para seleccionar el\\nespejo de descarga o hacer clic en «Cancelar» para detener y salir.\\n\\\n  Nota: Si su velocidad de descarga es demasiado lenta, puede intentar cambiar a otro espejo.\ndownload.javafx.component=Descargando módulo %s\ndownload.javafx.prepare=Preparando la descarga\n\nexception.access_denied=HMCL no puede acceder al archivo %s. Puede estar bloqueado por otro proceso.\\n\\\n  \\n\\\n  Para los usuarios de Windows, puede abrir el «Monitor de Recursos» para comprobar si otro proceso lo está utilizando actualmente. Si es así, puede intentarlo de nuevo después de cerrar ese proceso.\\n\\\n  Si no es así, comprueba si tu cuenta tiene permisos suficientes para acceder a ella.\nexception.artifact_malformed=No se puede verificar la integridad de los archivos descargados.\nexception.ssl_handshake=No se pudo establecer una conexión SSL porque falta el certificado SSL en la instalación actual de Java. Puede intentar abrir HMCL con otro Java y volver a intentarlo.\nexception.dns.pollution=No se pudo establecer una conexión SSL. Es posible que la resolución DNS sea incorrecta. Por favor, intente cambiar el servidor DNS o usar un servicio proxy.\n\nextension.bat=Archivo por lotes de Windows\nextension.png=Archivo de imagen\nextension.ps1=Windows PowerShell Script\nextension.sh=Shell Script\n\nextension.datapack=Paquete de datos\nextension.mod=Archivo mod\nextension.world=Archivo del mundo\n\nfatal.create_hmcl_current_directory_failure=El lanzador no puede crear el directorio HMCL (%s). Por favor, mueva el lanzador a otra ubicación y vuelva a abrirlo.\nfatal.javafx.incomplete=El entorno JavaFX está incompleto.\\n\\\n  Por favor, intente reemplazar su Java o reinstalar OpenJFX.\nfatal.javafx.missing=No se encontró un entorno JavaFX.\\n\\\n  Por favor, abra Hello Minecraft! Launcher con Java, que incluye OpenJFX.\nfatal.config_change_owner_root=Estás utilizando la cuenta root para abrir Hello Minecraft! Launcher. Esto puede hacer que no pueda abrir HMCL con otra cuenta en el futuro.\\n\\\n  ¿Todavía quieres continuar?\nfatal.config_in_temp_dir=Estás abriendo Hello Minecraft! Launcher en un directorio temporal. Tus ajustes y datos de juego pueden perderse.\\n\\\n  Se recomienda trasladar el HMCL a otro lugar y reabrirlo.\\n\\\n  ¿Todavía quieres continuar?\nfatal.config_loading_failure=No se pueden cargar los archivos de configuración.\\n\\\n  Por favor, asegúrese de que Hello Minecraft! Launcher tiene acceso de lectura y escritura a «%s» y a los archivos que contiene.\\n\\\n  Para macOS, intente colocar HMCL en un lugar con permiso que no sea «Escritorio», «Descargas» y «Documentos» y vuelva a intentarlo.\nfatal.config_loading_failure.unix=No se pudo cargar el archivo de configuración porque fue creado por el usuario «%1$s».\\n\\\n  Por favor, abra HMCL como usuario root (no recomendado), o ejecute el siguiente comando en el terminal para cambiar la propiedad del archivo de configuración al usuario actual:\\n%2$s\nfatal.config_unsupported_version=El archivo de configuración actual fue creado por una versión más reciente del lanzador, y esta versión del lanzador no puede cargarlo correctamente.\\n\\\n  Por favor, actualice y reinicie el lanzador.\\n\\\n  Antes de actualizar el lanzador, cualquier configuración que modifiques no se guardará.\nfatal.mac_app_translocation=El sistema operativo aísla Hello Minecraft! Launcher en un directorio temporal debido a los mecanismos de seguridad de macOS.\\n\\\n  Por favor, mueve HMCL a un directorio diferente antes de intentar abrirlo. De lo contrario, tus ajustes y datos de juego podrían perderse tras reiniciar.\\n\\\n  ¿Todavía quieres continuar?\nfatal.migration_requires_manual_reboot=Hello Minecraft! Launcher ha sido actualizado. Por favor, reinicie el launcher.\nfatal.apply_update_failure=Lo sentimos, pero Hello Minecraft! Launcher no puede actualizarse.\\n\\\n  \\n\\\n  Puedes actualizar manualmente descargando una versión más reciente del launcher desde %s.\\n\\\n  Si el problema persiste, por favor considere reportarlo a nosotros.\nfatal.apply_update_need_win7=Hello Minecraft! Launcher no puede actualizarse automáticamente en Windows XP/Vista.\\n\\\n  \\n\\\n  Puedes actualizar manualmente descargando una versión más reciente del launcher desde %s.\nfatal.deprecated_java_version=HMCL requerirá Java 17 o posterior para funcionar en el futuro, pero seguirá siendo compatible con el lanzamiento de juegos con Java 6~16.\\n\\\n\\n\\\nSe recomienda instalar la última versión de Java para garantizar el correcto funcionamiento de HMCL.\\n\\\n\\n\\\nPuedes conservar la versión antigua de Java. HMCL puede reconocer y gestionar varias versiones de Java y elegirá automáticamente la adecuada para usted en función de la versión del juego.\nfatal.deprecated_java_version.update=HMCL requerirá Java 17 o posterior para funcionar en el futuro. Por favor, instalar la última versión de Java para asegurarse de que el lanzador puede completar la actualización.\\n\\\n\\n\\\nPuedes conservar la versión antigua de Java.\\\nHMCL puede reconocer y gestionar varias versiones de Java \\\ny elegirá automáticamente la adecuada para usted en función de la versión del juego.\nfatal.deprecated_java_version.download_link=Descargar Java %d\nfatal.samba=Si ha abierto Hello Minecraft! Launcher desde una unidad de red Samba, es posible que algunas funciones no funcionen. Intenta actualizar tu Java o mover el launcher a otro directorio.\nfatal.illegal_char=Su ruta de usuario contiene un carácter ilegal «=», por lo que algunas características podrían no funcionar correctamente.\\n\\\n  Por ejemplo, no podrá utilizar authlib-injector o cambiar el skin de su cuenta offline.\nfatal.unsupported_platform=Minecraft aún no es totalmente compatible con tu plataforma, por lo que es posible que falten funciones o incluso que no puedas iniciar el juego.\\n\\\n   \\n\\\n   Si no puedes iniciar Minecraft 1.17 y versiones posteriores, puedes intentar cambiar el «Renderizador» a «Mesa LLVMpipe» en «Configuración global/específica de la instancia → Configuración avanzada» para utilizar el renderizado por CPU y mejorar la compatibilidad.\nfatal.unsupported_platform.loongarch=Hello Minecraft! Launcher ha prestado apoyo a la plataforma Loongson.\\n\\\n  Si tienes problemas al jugar, puedes visitar https://docs.hmcl.net/groups.html para obtener ayuda.\nfatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher ha proporcionado soporte para la plataforma de chips de Apple, utilizando Java nativo de ARM para ejecutar juegos y conseguir una experiencia de juego más fluida.\\n\\\n  Si tienes problemas al jugar a un juego, ejecutarlo con Java de arquitectura x86-64 puede tener mejor compatibilidad.\nfatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher ha proporcionado soporte nativo para la plataforma Windows en Arm. Si tiene problemas al jugar a un juego, intente iniciarlo con Java de arquitectura x86.\\n\\\n  \\n\\\n  Si utilizas la plataforma <b>Qualcomm</b>, es posible que tengas que instalar el <a href=\"ms-windows-store://pdp/?productid=9NQPSL29BFFF\">paquete de compatibilidad OpenGL</a> antes de jugar.\\n\\\n  Haz clic en el enlace para ir a Microsoft Store e instalar el paquete de compatibilidad.\n\nfile=Archivo\n\nfolder.config=Configuraciones de mod\nfolder.game=Directorio de trabajo\nfolder.logs=Registros\nfolder.mod=Mods\nfolder.resourcepacks=Paquetes de recursos\nfolder.shaderpacks=Paquetes de sombreados\nfolder.saves=Mundos\nfolder.schematics=Esquemas\nfolder.screenshots=Capturas de pantalla\nfolder.world=Directorio del mundo\n\ngame=Juegos\ngame.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla o fotos de esta interfaz con otras personas!</b> Si pide ayuda a otras personas, haga clic en <b>Exportar registros de colgado</b> y envíe el archivo exportado a otras personas para que lo analicen.\ngame.crash.info=Información de colgado\ngame.crash.reason=Motivo del colgado\ngame.crash.reason.analyzing=Analizando...\ngame.crash.reason.multiple=Se han detectado varias razones:\\n\\n\ngame.crash.reason.bootstrap_failed=El juego se ha colgado debido al mod «%1$s».\\n\\\n  \\n\\\n  Puedes intentar borrarlo o actualizarlo.\ngame.crash.reason.config=El juego se ha colgado debido a que el mod «%1$s» no ha podido analizar su archivo de configuración «%2$s».\ngame.crash.reason.debug_crash=El juego se ha colgado porque lo has activado manualmente. Así que probablemente sepas por qué :)\ngame.crash.reason.mixin_apply_mod_failed=El juego se ha colgado porque no se ha podido aplicar el mixin al mod «%1$s».\\n\\\n  \\n\\\n  Puedes probar a borrar o actualizar el mod para resolver el problema.\ngame.crash.reason.duplicated_mod=El juego no puede iniciarse debido a la existencia de mods duplicados: «%1$s».\\n\\\n  \\n\\\n  %2$s\\n\\\n  \\n\\\n  Cada mod sólo puede instalarse una vez, por favor, elimine el mod duplicado e inténtelo de nuevo.\ngame.crash.reason.fabric_version_0_12=La versión de Fabric Loader 0.12 o posterior es incompatible con los mods instalados actualmente. Necesitas bajarlo a la versión 0.11.7.\ngame.crash.reason.fabric_warnings=El cargador de Fabric Loader ha advertido:\\n\\\n  \\n\\\n  %1$s\ngame.crash.reason.file_already_exists=El juego se ha colgado porque el archivo «%1$s» ya existe.\\n\\\n  \\n\\\n  Puedes intentar hacer una copia de seguridad y eliminar ese archivo, y luego volver a ejecutar el juego.\ngame.crash.reason.file_changed=El juego se ha colgado porque ha fallado la verificación de archivos.\\n\\\n  \\n\\\n  Si has modificado el jar de Minecraft, tendrás que deshacer los cambios, o volver a descargar el juego.\ngame.crash.reason.gl_operation_failure=El juego se ha colgado por culpa de algunos mods, shaders o paquetes de recursos/texturas.\\n\\\n  \\n\\\n  Por favor, desactiva los mods, shaders o paquetes de recursos/texturas que estés utilizando y vuelve a intentarlo.\ngame.crash.reason.graphics_driver=El juego se ha colgado debido a un problema con el controlador de gráficos.\\n\\\n  \\n\\\n  Por favor, inténtelo de nuevo después de actualizar su controlador gráfico a la última versión.\\n\\\n  \\n\\\n  Si tu ordenador tiene una tarjeta gráfica discreta, tienes que comprobar si el juego utiliza gráficos integrados/núcleo. Si es así, inicie el launcher utilizando su tarjeta gráfica discreta. Si el problema persiste, probablemente debería considerar la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.\\n\\\n  \\n\\\n  Si utiliza su tarjeta gráfica integrada, tenga en cuenta que Minecraft 1.16.5 o anterior requiere <a href=\"https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html\">Java 1.8.0_51 o más antiguo</a> para la serie de procesadores Intel(R) Core(TM) 3000 o anteriores.\ngame.crash.reason.macos_failed_to_find_service_port_for_display=El juego no puede continuar debido a un fallo al inicializar la ventana OpenGL en la plataforma con chips de Apple.\\n\\\n  \\n\\\n  Para este problema, HMCL no tiene soluciones directas por el momento. Intente abrir cualquier navegador y ponerlo en pantalla completa, luego vuelva a HMCL, inicie el juego y <b>vuelva rápidamente a la página del navegador</b> antes de que aparezca la ventana del juego, espere a que aparezca la ventana del juego y luego vuelva a la ventana del juego.\ngame.crash.reason.illegal_access_error=El juego se ha colgado por culpa de algún(os) mod(s).\\n\\\n  \\n\\\n  Si sabes: %1$s, puedes actualizar o eliminar los mods y volver a intentarlo.\ngame.crash.reason.install_mixinbootstrap=El juego se ha colgado debido a la falta de MixinBootstrap.\\n\\\n  \\n\\\n  Puedes intentar instalar <a href=\"https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap\">MixinBootstrap</a> para resolver el problema. Si se cuelga después de la instalación, prueba a añadir un signo de exclamación (!) delante del nombre de archivo de este mod para intentar resolver el problema.\ngame.crash.reason.jdk_9=El juego se ha colgado porque la versión de Java es demasiado nueva para esta instancia.\\n\\\n  \\n\\\n  Tienes que descargar e instalar Java 8 y elegirlo en «Config. Global/Específica de instancia → Java».\ngame.crash.reason.need_jdk11=El juego se ha colgado debido a una versión inadecuada de Java.\\n\\\n  \\n\\\n  Debes descargar e instalar Java 11 y configurarlo en «Config. Global/Específica de instancia → Java».\ngame.crash.reason.jvm_32bit=El juego se ha colgado porque la asignación de memoria actual excedía el límite de 32 bits de la JVM.\\n\\\n  \\n\\\n  Si su sistema operativo es de 64 bits, instale y utilice una versión de Java de 64 bits. De lo contrario, es posible que tenga que volver a instalar un sistema operativo de 64 bits o adquirir un ordenador más moderno.\\n\\\n  \\n\\\n  O bien, puede desactivar la opción «Asignar automáticamente» en «Config. Global/Específica de instancia → Memoria» y establecer el tamaño máximo de asignación de memoria en 1024 MiB o menos.\ngame.crash.reason.loading_crashed_forge=El juego se ha colgado debido al mod «%1$s» (%2$s).\\n\\\n  \\n\\\n  Puedes intentar borrarlo o actualizarlo.\ngame.crash.reason.loading_crashed_fabric=El juego se ha colgado debido al mod «%1$s».\\n\\\n  \\n\\\n  Puedes intentar borrarlo o actualizarlo.\ngame.crash.reason.memory_exceeded=El juego se ha colgado debido a que se ha asignado demasiada memoria a un pequeño archivo de paginación.\\n\\\n  \\n\\\n  Puedes probar a desactivar la opción «Asignar automáticamente» en «Config. Global/Específica de instancia → Memoria» y ajustar el valor hasta que se inicie el juego.\\n\\\n  \\n\\  \n  También puede tratar de aumentar el tamaño del archivo de paginación en la configuración del sistema.\ngame.crash.reason.mod=El juego se ha colgado debido al mod «%1$s».\\n\\\n  \\n\\\n  Puedes actualizar o eliminar el mod y volver a intentarlo.\ngame.crash.reason.mod_resolution=El juego no puede seguir ejecutándose debido a problemas de dependencia de mods.\\n\\\n  \\n\\\n  Fabric proporcionó los siguientes detalles:\\n\\\n  \\n\\\n  %1$s\ngame.crash.reason.forgemod_resolution=El juego no puede seguir ejecutándose debido a problemas de dependencia de mods.\\n\\\n  \\n\\\n  Forge/NeoForge proporcionó los siguientes detalles:\\n\\\n  \\n\\\n  %1$s\ngame.crash.reason.forge_found_duplicate_mods=El juego se colgó debido a un problema de mod duplicado. Forge/NeoForge proporcionó la siguiente información:\\n\\\n  \\n\\\n  %1$s\ngame.crash.reason.night_config_fixes=El juego se colgó debido a algunos problemas con Night Config.\\n\\\n  \\n\\\n  Puedes intentar instalar el mod <a href=\"https://www.curseforge.com/minecraft/mc-mods/night-config-fixes\">Night Config Fixes</a>, que puede ayudarte con este problema.\\n\\\n  \\n\\\n  Para obtener más información, visite el <a href=\"https://www.github.com/Fuzss/nightconfigfixes\">repositorio de GitHub</a> del mod.\ngame.crash.reason.mod_resolution_collection=El juego se colgó porque la versión del mod no es compatible.\\n\\\n  \\n\\\n  «%1$s» requiere mod «%2$s».\\n\\\n  \\n\\\n  Necesitas actualizar o degradar «%3$s» antes de continuar.\ngame.crash.reason.mod_resolution_conflict=El juego se ha colgado debido a mods conflictivos.\\n\\\n  \\n\\\n  «%1$s» es incompatible con «%2$s».\ngame.crash.reason.mod_resolution_missing=El juego se ha colgado porque algunos mods dependientes no estaban instalados.\\n\\\n  \\n\\\n  «%1$s» requiere mod «%2$s».\\n\\\n  \\n\\\n  Esto significa que tienes que descargar e instalar «%2$s» primero para continuar jugando.\ngame.crash.reason.mod_resolution_missing_minecraft=El juego se ha colgado porque un mod es incompatible con la versión actual de Minecraft.\\n\\\n  \\n\\\n  «%1$s» requiere la versión de Minecraft %2$s.\\n\\\n  \\n\\\n  Si quieres jugar con esta versión del mod instalada, debes cambiar la versión del juego de tu instancia.\\n\\\n  \\n\\\n  En caso contrario, debes instalar una versión compatible con esta versión de Minecraft.\ngame.crash.reason.mod_resolution_mod_version=%1$s (Versión: %2$s)\ngame.crash.reason.mod_resolution_mod_version.any=%1$s (Cualquier versión)\ngame.crash.reason.forge_repeat_installation=El juego se ha colgado debido a una instalación duplicada de Forge. <a href=\"https://github.com/HMCL-dev/HMCL/issues/1880\">Este es un problema conocido</a>\\n\\\n  \\n\\\n  Se recomienda enviar comentarios en GitHub junto con este registro para que podamos encontrar más pistas y resolver el problema.\\n\\\n  \\n\\\n  Actualmente puede desinstalar Forge y volver a instalarlo en «Editar Instancia → Cargadores».\ngame.crash.reason.optifine_repeat_installation=El juego se ha colgado debido a una instalación duplicada de OptiFine.\\n\\\n  \\n\\\n  Elimine OptiFine del directorio «mods» o desinstálelo en «Editar instancia → Cargadores».\ngame.crash.reason.modmixin_failure=El juego se ha colgado debido a que algunos mods no se han inyectado.\\n\\\n  \\n\\\n  Esto generalmente significa que el mod tiene un error o no es compatible con el entorno actual. \\n\\\n  \\n\\\n  Puede consultar el registro para ver si hay un mod incorrecto.\ngame.crash.reason.mod_repeat_installation=El juego se ha colgado debido a los mods duplicados.\\n\\\n  \\n\\\n  Cada mod sólo puede instalarse una vez. Elimine el mod duplicado y vuelva a ejecutar el juego.\ngame.crash.reason.forge_error=Forge/NeoForge puede haber proporcionado información errónea.\\n\\\n  \\n\\\n  Puede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\\n\\\n  \\n\\\n  Si no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\\n\\\n  \\n\\\n  %1$s\ngame.crash.reason.mod_resolution0=El juego se ha colgado debido a algunos problemas con el mod. Puede consultar el registro para ver si hay un mod incorrecto.\ngame.crash.reason.java_version_is_too_high=El juego se ha colgado debido a que la versión de Java es demasiado nueva para seguir ejecutándose.\\n\\\n  \\n\\\n  Cambia la versión principal anterior de Java en «Config. Global/Específica de instancia → Java» y, a continuación, inicia el juego.\\n\\\n  \\n\\\n  Si no es así, puede descargarlo desde <a href=\"https://www.java.com/download/\">java.com (Java 8)</a> o <a href=\"https://bell-sw.com/pages/downloads/#downloads\">BellSoft Liberica Full JRE (Java 17)</a> y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación).\ngame.crash.reason.mod_name=El juego se ha colgado debido a problemas con el nombre de archivo del mod.\\n\\\n  \\n\\\n  Los nombres de los archivos Mod deben usar sólo letras inglesas (A~Z, a~z), números (0~9), líneas horizontales(-), subrayado (_) y puntos (.) a media altura.\\n\\\n  \\n\\\n  Por favor, vaya al directorio «mods» y cambie todos los nombres de archivo de mods no conformes utilizando los caracteres conformes anteriores.\ngame.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\\n\\\n  \\n\\\n  Reinstale o Forge/NeoForge em «Editar Instância → Carregadores».\ngame.crash.reason.modlauncher_8=El juego se ha colgado debido a que la versión actual de Forge no es compatible con tu instalación de Java. Intenta actualizar Forge.\ngame.crash.reason.no_class_def_found_error=El juego no puede ejecutarse debido a un código incompleto.\\n\\\n  \\n\\\n  A su instancia de juego le falta «%1$s», esto puede deberse a que falta un mod, a que hay un mod incompatible instalado o a que algunos archivos pueden estar dañados.\\n\\\n  \\n\\\n  Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.\ngame.crash.reason.no_such_method_error=El juego no puede ejecutarse debido a un código incompleto.\\n\\\n  \\n\\\n  Es posible que a su instancia de juego le falte un mod, que haya instalado un mod incompatible o que algunos archivos estén dañados.\\n\\\n  \\n\\\n  Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien.\ngame.crash.reason.opengl_not_supported=El juego se ha colgado porque tu controlador gráfico no es compatible con OpenGL.\\n\\\n  \\n\\\n  Si está transmitiendo el juego a través de Internet o utilizando un entorno de escritorio remoto, por favor, intente jugar el juego en su entorno local.\\n\\\n  O bien, puede intentar actualizar su controlador a la última versión y volver a intentarlo.\\n\\\n  \\n\\\n  Si tu ordenador tiene una tarjeta gráfica discreta, por favor, asegúrate de que el juego la está utilizando para el renderizado. Si el problema persiste, considera la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.\ngame.crash.reason.openj9=El juego no puede ejecutarse en una JVM OpenJ9. Por favor, cambia a un Java que utilice la JVM Hotspot en «Config. Global/Específica de instancia → Java» y vuelve a ejecutar el juego. Si no dispones de una, puedes descargarla.\ngame.crash.reason.out_of_memory=El juego se ha colgado porque el ordenador se ha quedado sin memoria.\\n\\\n  \\n\\\n  Puede que no haya suficiente memoria disponible o que haya demasiados mods instalados. Puedes intentar resolverlo aumentando la memoria asignada en «Config. Global/Específica de instancia → Memoria».\\n\\\n  \\n\\\n  Si sigues encontrando estos problemas, es posible que necesites una mejor computadora.\ngame.crash.reason.resolution_too_high=El juego se ha colgado porque la resolución del paquete de recursos/texturas es demasiado alta.\\n\\\n  \\n\\\n  Deberías cambiar a un paquete de recursos/texturas con menor resolución o considerar la compra de una tarjeta gráfica mejor con más VRAM.\ngame.crash.reason.shaders_mod=El juego se ha colgado porque OptiFine y Shaders mod están instalados al mismo tiempo.\\n\\\n  \\n\\\n  Simplemente elimine el mod Shader porque OptiFine tiene soporte incorporado para shaders.\ngame.crash.reason.rtss_forest_sodium=El juego se colgó porque el RivaTuner Statistical Server (RTSS) no es compatible con Sodium.\\n\\\n  \\n\\\n  Haz clic <a href=\"https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\">aquí</a> para obtener más detalles.\ngame.crash.reason.stacktrace=El motivo del cuelgue es desconocido. Puedes ver los detalles haciendo clic en el botón «Registros».\\n\\\n  \\n\\\n  Hay algunas palabras clave que pueden contener algunos nombres de mods. Puedes buscarlas en Internet para averiguar el problema tú mismo.\\n\\\n  \\n\\\n  %s\ngame.crash.reason.too_old_java=El juego se ha colgado porque estás utilizando una versión de Java obsoleta.\\n\\\n  \\n\\\n  Tienes que cambiar a una versión de Java más reciente (%1$s) en «Config. Global/Específica de instancia → Java» y, a continuación, volver a ejecutar el juego. Puede descargar Java desde <a href=\"https://learn.microsoft.com/java/openjdk/download\">aquí</a>.\ngame.crash.reason.unknown=No somos capaces de averiguar por qué se ha colgado el juego. Por favor, refiérase a los registros del juego.\ngame.crash.reason.unsatisfied_link_error=No se puede iniciar Minecraft porque faltan bibliotecas: %1$s.\\n\\\n  \\n\\\n  Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\\n\\\n  \\n\\\n  Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\\n\\\n  \\n\\\n  De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros.\ngame.crash.reason.mac_jdk_8u261=El juego se ha colgado porque tu versión actual de Forge u OptiFine es incompatible con tu instalación de Java.\\n\\\n  \\n\\\n  Por favor, intente actualizar Forge y OptiFine, o intente utilizar Java 8u251 o versiones anteriores.\ngame.crash.reason.mod_files_are_decompressed=El juego se ha colgado porque se ha extraído el archivo del mod.\\n\\\n  \\n\\\n  Por favor, ¡pon el archivo mod completo directamente en el directorio mod!\\n\\\n  \\n\\\n  Si la extracción provoca errores en el juego, elimine el mod extraído en el directorio de mods y, a continuación, inicie el juego.\ngame.crash.reason.optifine_causes_the_world_to_fail_to_load=Es posible que el juego no siga ejecutándose debido al OptiFine.\\n\\\n  \\n\\\n  Este problema solo se produce en una versión específica de OptiFine. Puede probar a cambiar la versión de OptiFine en «Editar instancia → Cargadores».\ngame.crash.reason.optifine_is_not_compatible_with_forge=El juego se ha colgado porque OptiFine no es compatible con la instalación actual de Forge.\\n\\\n  \\n\\\n  Por favor, navegue hasta la <a href=\"https://optifine.net/downloads\">página oficial de OptiFine</a>, compruebe si la versión de Forge es compatible con OptiFine, y reinstale la instancia en estricta conformidad con la versión correspondiente, o cambie la versión de OptiFine en «Editar Instancia → Cargadores».\\n\\\n  \\n\\\n  Tras realizar pruebas, creemos que las versiones de OptiFine demasiado altas o demasiado bajas pueden provocar fallos.\ngame.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=El juego se ha colgado porque has instalado demasiados mods y has superado el límite de ID del juego.\\n\\\n  \\n\\\n  Por favor, intenta instalar <a href=\"https://www.curseforge.com/minecraft/mc-mods/jeid\">JEID</a> o eliminar algunos mods grandes.\ngame.crash.title=Juego colgado\ngame.directory=Ruta del juego\ngame.version=Instancia del juego\n\nhelp=Ayuda\nhelp.doc=Documentación de Hello Minecraft! Launcher\nhelp.detail=Para los creadores de paquetes de datos y mods.\n\ninput.email=El nombre de usuario debe ser una dirección de correo electrónico.\ninput.number=La entrada debe ser un número.\ninput.not_empty=Este es un campo obligatorio.\ninput.url=La entrada debe ser una URL válida.\n\ninstall=Nueva instancia\ninstall.change_version=Cambiar versión\ninstall.change_version.confirm=¿Está seguro de querer cambiar %s de la versión %s a %s?\ninstall.change_version.process=Proceso de cambio de versión\ninstall.failed=Fallo en la instalación\ninstall.failed.downloading=No se han podido descargar algunos archivos necesarios.\ninstall.failed.downloading.detail=No se ha podido descargar el archivo: %s\ninstall.failed.downloading.timeout=Tiempo de espera de la descarga: %s\ninstall.failed.install_online=No se ha podido identificar el archivo proporcionado. Si está instalando un mod, vaya a la página «Mods».\ninstall.failed.malformed=Los archivos descargados están dañados. Puedes intentar resolver este problema cambiando a otra fuente de descarga en «Ajustes → Descarga → Fuente de descarga».\ninstall.failed.optifine_conflict=No se puede instalar tanto OptiFine como Fabric en Minecraft 1.13 o posterior.\ninstall.failed.optifine_forge_1.17=Para Minecraft 1.17.1, Forge sólo es compatible con OptiFine H1 pre2 o posterior. Puedes instalarlos marcando «Snapshots» al elegir una versión de OptiFine en HMCL.\ninstall.failed.version_mismatch=Este cargador requiere la versión del juego %s, pero la instalada es %s.\ninstall.installer.change_version=%s Incompatible\ninstall.installer.choose=Elija su versión %s\ninstall.installer.cleanroom=Cleanroom\ninstall.installer.depend=Requiere %s\ninstall.installer.do_not_install=No instalar\ninstall.installer.fabric=Fabric\ninstall.installer.fabric-api=Fabric API\ninstall.installer.fabric-quilt-api.warning=%1$s es un mod, y se instalará en el directorio de mods de la instancia del juego. Por favor, no cambies el directorio de trabajo del juego, o el %1$s no funcionará. Si quieres cambiar esta configuración, debes reinstalarlo.\ninstall.installer.forge=Forge\ninstall.installer.neoforge=NeoForge\ninstall.installer.game=Minecraft\ninstall.installer.incompatible=Incompatible con %s\ninstall.installer.install=Instalando %s\ninstall.installer.install_offline=Instalar/actualizar desde archivo local\ninstall.installer.install_offline.tooltip=Apoyamos el uso del instalador local de (Neo)Forge/Cleanroom/OptiFine.\ninstall.installer.install_online=Instalación en línea\ninstall.installer.install_online.tooltip=Actualmente soportamos Forge, NeoForge, Cleanroom, Fabric, Legacy Fabric, Quilt, LiteLoader y OptiFine.\ninstall.installer.liteloader=LiteLoader\ninstall.installer.not_installed=No está instalado\ninstall.installer.optifine=OptiFine\ninstall.installer.quilt=Quilt\ninstall.installer.quilt-api=QSL/QFAPI\ninstall.installer.version=%s\ninstall.installer.external_version=%s (Instalado por un proceso externo, que no se puede configurar)\ninstall.installing=Instalando\ninstall.modpack=Añadir modpack\ninstall.modpack.installation=Instalación de modpack\ninstall.name.invalid=El nombre contiene caracteres especiales (como emoji o caracteres CJK).\\n\\nSe recomienda cambiar el nombre para que sólo incluya letras, números y guiones bajos en inglés para evitar posibles problemas al iniciar el juego.\\n\\n¿Desea continuar con la instalación?\ninstall.new_game=Añadir instancia\ninstall.new_game.already_exists=Esta instancia ya existe. Por favor, utilice otro nombre.\ninstall.new_game.current_game_version=Versión actual de la instancia\ninstall.new_game.installation=Instalación de instancia\ninstall.new_game.malformed=Nombre no válido.\ninstall.select=Elegir una operación\ninstall.success=Instalado con éxito.\n\njava.add.failed=Este Java no es válido o es incompatible con la plataforma actual.\njava.disable=Deshabilitar este Java\njava.disable.confirm=¿Está seguro de que desea desactivar este Java?\njava.disabled.management=Java desactivado\njava.disabled.management.remove=Eliminar este Java de la lista\njava.disabled.management.restore=Activar este Java\njava.download.banshanjdk-8=Descargar Banshan JDK 8\njava.download.load_list.failed=No se ha podido cargar la lista de versiones\njava.download.more=Más distribuciones de Java\njava.download.prompt=Elija la versión de Java que desea descargar:\njava.download.distribution=Distribución\njava.download.version=Versión\njava.download.packageType=Tipo de paquete\njava.management=Gestión Java\njava.info.architecture=Arquitectura\njava.info.vendor=Proveedor\njava.info.version=Versión\njava.info.disco.distribution=Distribución\njava.install=Instalar Java\njava.install.archive=Fuente Path\njava.install.failed.exists=Este nombre ya tiene dueño\njava.install.failed.invalid=Este archivo no es un paquete de instalación de Java válido, por lo que no se puede instalar.\njava.install.failed.unsupported_platform=Este Java no es compatible con la plataforma actual, por lo que no puede instalarse.\njava.install.name=Nombre\njava.install.warning.invalid_character=Carácter ilegal en el nombre\njava.installing=Instalando Java\njava.uninstall=Desinstalar Java\njava.uninstall.confirm=¿Está seguro de que desea desinstalar este Java? ¡Esta acción no se puede deshacer!\n\nlang.default=Usar idioma del sistema\n\nlaunch.advice=%s ¿Todavía quieres continuar?\nlaunch.advice.multi=Se han detectado los siguientes problemas:\\n\\n%s\\n\\nEstos problemas pueden impedir que inicies el juego o afectar a la experiencia de juego.\\n\\n¿Todavía quieres continuar?\nlaunch.advice.java.auto=La versión actual de Java no es compatible con la instancia.\\n\\nHaga clic en «Sí» para elegir automáticamente la versión de Java más compatible. O bien, puede navegar hasta «Config. Global/Específica de instancia → Java» para elegir uno usted mismo.\nlaunch.advice.java.modded_java_7=Minecraft 1.7.2 y versiones anteriores requieren Java 7 o anterior.\nlaunch.advice.cleanroom=Cleanroom solo se puede ejecutar en Java 21 o versiones posteriores. Por favor, actualice su versión de Java.\nlaunch.advice.corrected=Hemos resuelto el problema de Java. Si aún desea utilizar la versión de Java que elija, puede desactivar «No comprobar la compatibilidad con JVM» en «Config. Global/Específica de instancia → Configuración avanzada».\nlaunch.advice.uncorrected=Si aún desea utilizar la versión de Java que elija, puede desactivar «No comprobar la compatibilidad con JVM» en «Config. Global/Específica de instancia → Configuración avanzada».\nlaunch.advice.different_platform=Se recomienda la versión de 64 bits de Java para tu dispositivo, pero has instalado una de 32 bits.\nlaunch.advice.forge2760_liteloader=La versión 2760 o posterior de Forge no es compatible con LiteLoader, por favor considere actualizar Forge a la versión 2773 o posterior.\nlaunch.advice.forge28_2_2_optifine=La versión 28.2.2 o posterior de Forge no es compatible con OptiFine. Considere la posibilidad de actualizar Forge a la versión 28.2.1 o anterior.\nlaunch.advice.forge37_0_60=Las versiones de Forge anteriores a la 37.0.60 no son compatibles con Java 17. Por favor, actualiza Forge a la versión 37.0.60 o posterior, o inicia el juego con Java 16.\nlaunch.advice.java8_1_13=Minecraft 1.13 y posteriores sólo se pueden ejecutar en Java 8 o posterior. Por favor, utilice Java 8 o versiones posteriores.\nlaunch.advice.java8_51_1_13=Minecraft 1.13 puede fallar en versiones de Java 8 anteriores a la 1.8.0_51. Por favor, instala la última versión de Java 8.\nlaunch.advice.java9=No puedes ejecutar Minecraft 1.12 o anterior con Java 9 o más reciente, por favor, utiliza Java 8 en su lugar.\nlaunch.advice.modded_java=Algunos mods pueden no ser compatibles con las nuevas versiones de Java. Se recomienda utilizar Java %s para iniciar Minecraft %s.\nlaunch.advice.modlauncher8=La versión de Forge que estás utilizando no es compatible con la versión actual de Java. Por favor, intenta actualizar Forge.\nlaunch.advice.newer_java=Estás utilizando una versión antigua de Java para iniciar el juego. Se recomienda actualizar a Java 8, de lo contrario algunos mods pueden hacer que el juego se bloquee.\nlaunch.advice.not_enough_space=Has asignado un tamaño de memoria mayor que los %d MiB reales de memoria instalados en tu máquina. Es posible que el rendimiento del juego se vea afectado, o incluso que no puedas iniciar el juego.\nlaunch.advice.require_newer_java_version=La versión actual del juego requiere Java %s, pero no hemos podido encontrar uno. ¿Quieres descargar uno ahora?\nlaunch.advice.too_large_memory_for_32bit=Has asignado un tamaño de memoria mayor que la limitación de memoria de la instalación de Java de 32 bits. Es posible que no puedas iniciar el juego.\nlaunch.advice.vanilla_linux_java_8=Minecraft 1.12.2 o inferior sólo admite Java 8 para la plataforma Linux x86-64, porque las versiones posteriores no pueden cargar las bibliotecas nativas de 32 bits como liblwjgl.so\\n\\nPor favor, descárguelo de java.com, o instale OpenJDK 8.\nlaunch.advice.vanilla_x86.translation=Minecraft no proporciona actualmente soporte oficial para arquitecturas distintas de x86 y x86-64.\\n\\nPor favor, instale Java para x86-64 para jugar a Minecraft a través del entorno de traducción Rosetta, o descargue sus bibliotecas nativas correspondientes y especifique su ruta.\nlaunch.advice.unknown=El juego no puede iniciarse por las siguientes razones:\nlaunch.failed=No se puede ejecutar\nlaunch.failed.cannot_create_jvm=No podemos crear una JVM. Puede deberse a un problema con los argumentos de ejecución proporcionados, puede intentar solucionarlo eliminando todos los argumentos que haya añadido en la configuración de la instancia.\nlaunch.failed.creating_process=No podemos crear un nuevo proceso, por favor compruebe la ruta de Java.\\n\nlaunch.failed.command_too_long=La longitud del comando excede la longitud máxima de un script bat. Por favor, intente exportarlo como un script de PowerShell.\nlaunch.failed.decompressing_natives=No se han podido descomprimir las bibliotecas nativas.\\n\nlaunch.failed.download_library=No se pudo descargar la biblioteca %s.\nlaunch.failed.executable_permission=No se pudo hacer ejecutable el script de ejecución.\nlaunch.failed.execution_policy=Configuración de la política de ejecución\nlaunch.failed.execution_policy.failed_to_set=No se pudo establecer la política de ejecución\nlaunch.failed.execution_policy.hint=La política de ejecución actual impide la ejecución de scripts de PowerShell.\\n\\nHaga clic en «Aceptar» para permitir que el usuario actual ejecute scripts de PowerShell, o haga clic en «Cancelar» para mantenerlo como está.\nlaunch.failed.exited_abnormally=El juego se ha bloqueado. Por favor, consulte el registro de errores para más detalles.\nlaunch.failed.java_version_too_low=La versión de Java que ha especificado es demasiado baja. Por favor, reajuste la versión de Java.\nlaunch.failed.no_accepted_java=No se ha podido encontrar una versión de Java compatible. Si crees que has descargado una compatible, puedes establecerla manualmente en los ajustes.\nlaunch.failed.sigkill=El juego fue terminado a la fuerza por el usuario o el sistema.\nlaunch.state.dependencies=Resolviendo dependencias\nlaunch.state.done=Completando el inicio\nlaunch.state.java=Comprobando la versión de Java\nlaunch.state.logging_in=Iniciando sesión\nlaunch.state.modpack=Descargando dependencias\nlaunch.state.waiting_launching=Esperando la ejecución del juego\nlaunch.invalid_java=Ruta Java inválida. Por favor, restablezca la ruta de Java.\n\nlauncher=Launcher\nlauncher.agreement=Términos de servicio y EULA\nlauncher.agreement.accept=Aceptar\nlauncher.agreement.decline=Rechazar\nlauncher.agreement.hint=Debe aceptar el EULA para utilizar este software.\nlauncher.background=Imagen de fondo\nlauncher.background.choose=Elige una imagen de fondo\nlauncher.background.classic=Clásico\nlauncher.background.default=Por defecto\nlauncher.background.default.tooltip=O «background.png/.jpg/.gif/.webp» y las imágenes en el directorio «bg».\nlauncher.background.network=Desde la URL\nlauncher.background.paint=Color sólido\nlauncher.cache_directory=Directorio de la caché\nlauncher.cache_directory.clean=Borrar caché\nlauncher.cache_directory.choose=Elegir el directorio de la caché\nlauncher.cache_directory.default=Por defecto («%APPDATA%/.minecraft» o «~/.minecraft»)\nlauncher.cache_directory.disabled=Desactivado\nlauncher.cache_directory.invalid=No se ha podido crear el directorio de la caché, volviendo a los valores por defecto.\nlauncher.contact=Contacta con nosotros\nlauncher.crash=Hello Minecraft! Launcher ha encontrado un error fatal. Por favor, copie el siguiente registro y pida ayuda en nuestra comunidad en Discord, GitHub o Minecraft Forums.\nlauncher.crash.java_internal_error=Hello Minecraft! Launcher ha encontrado un error fatal porque su Java está dañado. Por favor, desinstala tu Java y descarga un Java adecuado <a href=\"https://bell-sw.com/pages/downloads/#downloads\">aquí</a>.\nlauncher.crash.hmcl_out_dated=Hello Minecraft! Launcher ha encontrado un error fatal. Su launcher está desactualizado. Por favor, ¡actualícelo!\nlauncher.update_java=Por favor, actualice su versión de Java.\n\nlibraries.download=Descargando bibliotecas\n\nlogin.empty_username=¡Todavía no has puesto tu nombre de usuario!\nlogin.enter_password=Por favor, introduzca su contraseña.\n\nlogwindow.show_lines=Mostrar número de líneas\nlogwindow.terminate_game=Terminar proceso\nlogwindow.title=Registro\nlogwindow.help=Puede ir a la comunidad HMCL y encontrar a otros para ayudar\nlogwindow.autoscroll=Desplazamiento automático\nlogwindow.export_game_crash_logs=Exportar registros de colgado\nlogwindow.export_dump=Exportar volcado de pila de juegos\nlogwindow.export_dump.no_dependency=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a nuestro Discord o grupo QQ para obtener ayuda.\n\nmain_page=Inicio\n\nmessage.cancelled=La operación se ha cancelado\nmessage.confirm=Confirmar\nmessage.copied=Copiado al portapapeles\nmessage.default=Por defecto\nmessage.doing=Por favor, espere\nmessage.downloading=Descargando\nmessage.error=Error\nmessage.failed=Operación fallida\nmessage.info=Información\nmessage.success=Operación completada con éxito\nmessage.unknown=Desconocido\nmessage.warning=Atención\n\nmodpack=Modpacks\nmodpack.choose=Elija Modpack\nmodpack.choose.local=Importar desde un archivo local\nmodpack.choose.local.detail=Puede arrastrar y soltar el archivo modpack aquí\nmodpack.choose.remote=Descargar desde Internet\nmodpack.choose.remote.detail=Se requiere un enlace de descarga directa al archivo modpack remoto\nmodpack.choose.remote.tooltip=Por favor, introduzca la URL de su modpack\nmodpack.choose.repository=Descargar Modpack de CurseForge o Modrinth\nmodpack.choose.repository.detail=Puedes elegir el modpack que desees en la página siguiente.\nmodpack.completion=Descargando dependencias\nmodpack.desc=Describa su modpack, incluyendo una introducción y probablemente algún registro de cambios. Actualmente se admiten Markdown e imágenes desde URL.\nmodpack.description=Descripción\nmodpack.download=Descargar Modpacks\nmodpack.download.title=Descargar Modpack - %1s\nmodpack.enter_name=Introduzca un nombre para este modpack.\nmodpack.export=Exportar como modpack\nmodpack.export.as=Exportar Modpack como...\nmodpack.file_api=Prefijo de la URL del modpack\nmodpack.files.blueprints=Plantillas de BuildCraft\nmodpack.files.config=Archivos de configuración del mod\nmodpack.files.dumps=Archivos de salida de depuración NEI\nmodpack.files.hmclversion_cfg=Archivo de configuración del launcher\nmodpack.files.liteconfig=Archivos relacionados con LiteLoader\nmodpack.files.mods=Mods\nmodpack.files.mods.voxelmods=Opciones de VoxelMods\nmodpack.files.options_txt=Archivo de configuración de Minecraft\nmodpack.files.optionsshaders_txt=Archivo de configuración de shaders\nmodpack.files.resourcepacks=Paquetes de recursos/texturas\nmodpack.files.saves=Mundos\nmodpack.files.scripts=Archivo de configuración de MineTweaker\nmodpack.files.servers_dat=Archivo de lista de servidores\nmodpack.installing=Instalando modpack\nmodpack.installing.given=Instalando %s modpack\nmodpack.introduction=Actualmente se soportan los modpacks CurseForge, Modrinth, MultiMC y MCBBS.\nmodpack.invalid=Modpack inválido, puede intentar volver a descargarlo.\nmodpack.mismatched_type=Tipo de modpack erróneo, la instancia actual es del tipo %s, pero la proporcionada es del tipo %s.\nmodpack.name=Nombre del modpack\nmodpack.not_a_valid_name=Nombre de paquete no válido\nmodpack.origin=Fuente\nmodpack.origin.url=Sitio web oficial\nmodpack.origin.mcbbs=MCBBS\nmodpack.origin.mcbbs.prompt=ID de publicación\nmodpack.scan=Análisis del índice de modpacks\nmodpack.task.install=Instalar Modpack\nmodpack.task.install.error=No se ha podido identificar este modpack. Actualmente sólo soportamos los modpacks Curse, Modrinth, MultiMC y MCBBS.\nmodpack.type.curse=Curse\nmodpack.type.curse.error=No se han podido descargar las dependencias de CurseForge, inténtalo de nuevo o utiliza una conexión proxy.\nmodpack.type.curse.not_found=Algunas dependencias ya no están disponibles, por favor intenta instalar una versión más reciente del modpack.\nmodpack.type.manual.warning=Este archivo contiene una copia completa de una instancia de Minecraft. La mayoría de las veces, sólo tienes que descomprimirlo y ejecutar el juego usando su launcher incorporado. Pero, HMCL todavía puede importarlo, sin garantía de su utilidad, ¿aún continúa?\nmodpack.type.mcbbs=Tipo MCBBS\nmodpack.type.mcbbs.export=Puede ser importado por Hello Minecraft! Launcher\nmodpack.type.modrinth=Modrinth\nmodpack.type.modrinth.export=Puede ser importado por los principales lanzadores de terceros\nmodpack.type.multimc=MultiMC\nmodpack.type.multimc.export=Puede ser importado por Hello Minecraft! Launcher y MultiMC\nmodpack.type.server=Actualización automática del modpack desde el servidor\nmodpack.type.server.export=Permite al propietario del servidor actualizar la instancia del juego de forma remota\nmodpack.type.server.malformed=Manifiesto de modpack inválido. Por favor, ponte en contacto con el creador del modpack para resolver este problema.\nmodpack.unsupported=Hello Minecraft! Launcher no admite el formato del paquete de integración\nmodpack.update=Actualizando modpack\nmodpack.wizard=Asistente de exportación de modpack\nmodpack.wizard.step.1=Configuración básica\nmodpack.wizard.step.1.title=Algunos ajustes básicos para el modpack.\nmodpack.wizard.step.2=Elegir archivos\nmodpack.wizard.step.2.title=Elige los archivos que quieras añadir al modpack.\nmodpack.wizard.step.3=Tipo de modpack\nmodpack.wizard.step.3.title=Elija el tipo de modpack que desea exportar.\nmodpack.wizard.step.initialization.exported_version=Instancia del juego a exportar\nmodpack.wizard.step.initialization.force_update=Forzar la actualización del modpack a la última versión (necesitarás un servicio de alojamiento de archivos)\nmodpack.wizard.step.initialization.include_launcher=Incluir el launcher\nmodpack.wizard.step.initialization.modrinth.info=El lanzador comparará los recursos remotos de CurseForge/Modrinth en lugar de los archivos locales (incluidos mods, paquetes de recursos y paquetes de shaders) durante la creación del modpack para reducir su tamaño, y marcará los archivos con extensión «.disabled» como opcionales para la instalación.\nmodpack.wizard.step.initialization.no_create_remote_files=No utilice recursos remotos para sustituir archivos locales\nmodpack.wizard.step.initialization.save=Exportar a...\nmodpack.wizard.step.initialization.skip_curseforge_remote_files=No utilice los recursos remotos de CurseForge para sustituir archivos locales\nmodpack.wizard.step.initialization.warning=Antes de hacer un modpack, por favor asegúrate de que el juego puede ser lanzado normalmente y Minecraft es una versión de lanzamiento en lugar de una snapshot. El launcher guardará tu configuración de descarga.\\n\\\n  \\n\\\n  Ten en cuenta que no se te permite añadir mods y paquetes de recursos que se digan explícitamente que no se pueden distribuir o poner en un modpack.\nmodpack.wizard.step.initialization.server=Haga clic aquí para ver más tutoriales para hacer un modpack de servidor que se pueda actualizar automáticamente.\n\nmodrinth.category.adventure=Aventura\nmodrinth.category.atmosphere=Atmósfera\nmodrinth.category.audio=Audio\nmodrinth.category.babric=Babric\nmodrinth.category.blocks=Bloqueos\nmodrinth.category.bloom=Resplandor\nmodrinth.category.bta-babric=BTA (Babric)\nmodrinth.category.bukkit=Bukkit\nmodrinth.category.bungeecord=BungeeCord\nmodrinth.category.canvas=Canvas\nmodrinth.category.cartoon=Caricatura\nmodrinth.category.challenging=Desafío\nmodrinth.category.colored-lighting=Iluminación de color\nmodrinth.category.combat=Combate\nmodrinth.category.core-shaders=Sombreadores de núcleo\nmodrinth.category.cursed=Maldición\nmodrinth.category.datapack=Paquete de datos\nmodrinth.category.decoration=Decoración\nmodrinth.category.economy=Economía\nmodrinth.category.entities=Entidades\nmodrinth.category.environment=Medio ambiente\nmodrinth.category.equipment=Equipo\nmodrinth.category.fabric=Fabric\nmodrinth.category.fantasy=Fantasía\nmodrinth.category.folia=Folia\nmodrinth.category.foliage=Vegetación\nmodrinth.category.fonts=Fonts\nmodrinth.category.food=Alimentos\nmodrinth.category.forge=Forge\nmodrinth.category.game-mechanics=Mecánica de juego\nmodrinth.category.gui=GUI\nmodrinth.category.high=Alto\nmodrinth.category.iris=Iris\nmodrinth.category.items=Objetos\nmodrinth.category.java-agent=Java Agent\nmodrinth.category.kitchen-sink=Fregadero de cocina\nmodrinth.category.legacy-fabric=Legacy Fabric\nmodrinth.category.library=Biblioteca\nmodrinth.category.lightweight=Peso ligero\nmodrinth.category.liteloader=LiteLoader\nmodrinth.category.locale=Locale\nmodrinth.category.low=Bajo\nmodrinth.category.magic=Magia\nmodrinth.category.management=Gestión\nmodrinth.category.medium=Medio\nmodrinth.category.minecraft=Minecraft\nmodrinth.category.minigame=Minijuego\nmodrinth.category.misc=Misceláneo\nmodrinth.category.mobs=Creaturas\nmodrinth.category.modded=Modificado\nmodrinth.category.models=Modelos\nmodrinth.category.modloader=Cargador de mods\nmodrinth.category.multiplayer=Multijugador\nmodrinth.category.neoforge=NeoForge\nmodrinth.category.nilloader=NilLoader\nmodrinth.category.optifine=OptiFine\nmodrinth.category.optimization=Optimización\nmodrinth.category.ornithe=Ornithe\nmodrinth.category.paper=Paper\nmodrinth.category.path-tracing=Trazado de rutas\nmodrinth.category.pbr=PBR\nmodrinth.category.potato=Mínimo\nmodrinth.category.purpur=Purpur\nmodrinth.category.quests=Misiones\nmodrinth.category.quilt=Quilt\nmodrinth.category.realistic=Realista\nmodrinth.category.reflections=Reflexiones\nmodrinth.category.rift=Rift\nmodrinth.category.screenshot=Extremo\nmodrinth.category.semi-realistic=Semirrealista\nmodrinth.category.shadows=Sombras\nmodrinth.category.simplistic=Simplista\nmodrinth.category.social=Social\nmodrinth.category.spigot=Spigot\nmodrinth.category.sponge=Sponge\nmodrinth.category.storage=Almacenamiento\nmodrinth.category.technology=Tecnología\nmodrinth.category.themed=Temática\nmodrinth.category.transportation=Transporte\nmodrinth.category.tweaks=Ajustes\nmodrinth.category.utility=Utilidad\nmodrinth.category.vanilla=Vainilla\nmodrinth.category.vanilla-like=Similar a la vainilla\nmodrinth.category.velocity=Velocity\nmodrinth.category.waterfall=Waterfall\nmodrinth.category.worldgen=Generación de mundos\nmodrinth.category.8x-=8x-\nmodrinth.category.16x=16x\nmodrinth.category.32x=32x\nmodrinth.category.48x=48x\nmodrinth.category.64x=64x\nmodrinth.category.128x=128x\nmodrinth.category.256x=256x\nmodrinth.category.512x+=512x+\n\nmods=Mods\nmods.add.failed=No se ha podido añadir el mod %s.\nmods.add.success=%s se ha añadido correctamente.\nmods.broken_dependency.title=Dependencia rota\nmods.broken_dependency.desc=Esta dependencia existía antes, pero ahora no existe. Intente utilizar otra fuente de descarga.\nmods.category=Categoría\nmods.channel.alpha=Alpha\nmods.channel.beta=Beta\nmods.channel.release=Release\nmods.check_updates=Proceso de actualización de mods\nmods.check_updates.button=Actualizar\nmods.check_updates.confirm=Actualizar\nmods.check_updates.current_version=Versión actual\nmods.check_updates.empty=Todos los mods están actualizados\nmods.check_updates.failed_check=No se ha podido comprobar si hay actualizaciones.\nmods.check_updates.failed_download=No se han podido descargar algunos de los archivos.\nmods.check_updates.file=Archivo\nmods.check_updates.source=Fuente\nmods.check_updates.target_version=Versión de destino\nmods.curseforge=CurseForge\nmods.dependency.embedded=Dependencias incorporadas (Already packaged in the mod file by the author. No need to download separately)\nmods.dependency.optional=Dependencias opcionales (If missing, the game will run normally, but the mod features may be missing)\nmods.dependency.required=Dependencias necesarias (Must be downloaded separately. Missing may cause the game to fail to launch)\nmods.dependency.tool=Dependencias necesarias (Must be downloaded separately. Missing may cause the game to fail to launch)\nmods.dependency.include=Dependencias incorporadas (Already packaged in the mod file by the author, no need to download separately)\nmods.dependency.incompatible=Mods incompatibles (Installing these mods at the same time will cause the game to fail to launch)\nmods.dependency.broken=Dependencias rotas (This mod existed before, but it does not exist now. Try using another download source.)\nmods.disable=Desactivar\nmods.download=Descargar mod\nmods.download.title=Descargar mod - %1s\nmods.download.recommend=Versión recomendada - Minecraft %1s\nmods.enable=Activar\nmods.game.version=Versión del juego\nmods.manage=Mods\nmods.mcbbs=MCBBS\nmods.mcmod=MCMod\nmods.mcmod.page=Página de MCMod\nmods.mcmod.search=Búsqueda en MCMod\nmods.modrinth=Modrinth\nmods.name=Nombre\nmods.not_modded=¡Debes instalar primero un cargador de mods (Forge, NeoForge, Fabric, Legacy Fabric, Quilt o LiteLoader) para gestionar tus mods!\nmods.restore=Restaurar\nmods.url=Página oficial\nmods.update_modpack_mod.warning=Actualizar mods en un modpack puede generar resultados irreparables, posiblemente corrompiendo el modpack para que no pueda iniciarse. ¿Seguro que quieres actualizar?\nmods.install=Instalar\nmods.save_as=Guardar como\n\nnbt.entries=%s entradas\nnbt.open.failed=No se ha podido abrir el archivo\nnbt.save.failed=No se ha podido guardar el archivo\nnbt.title=Ver archivo - %s\n\ndatapack=Paquetes de datos\ndatapack.title=Mundo [%s] - Paquetes de datos\n\nweb.failed=No se ha podido cargar la página\nweb.open_in_browser=Desea abrir esta dirección en un navegador:\\n%s\nweb.view_in_browser=Ver en navegador\n\nworld=Mundos\nworld.add.already_exists=Este mundo ya existe.\nworld.add.title=Elija el archivo de mundo que desea importar\nworld.add.failed=No se ha podido importar este mundo: %s\nworld.add.invalid=No se ha podido analizar el mundo.\nworld.backup=Copia de seguridad\nworld.backup.create.failed=No se ha podido crear la copia de seguridad.\\n%s\nworld.backup.create.success=Creada con éxito una nueva copia de seguridad: %s\nworld.backup.delete=Eliminar esta copia de seguridad\nworld.backup.processing=Creando nueva copia de seguridad ...\nworld.chunkbase=Chunk Base\nworld.chunkbase.end_city=Ciudad del End\nworld.chunkbase.seed_map=Vista previa de la generación mundial\nworld.chunkbase.stronghold=Fortaleza\nworld.chunkbase.nether_fortress=Fortaleza del Nether\nworld.duplicate.failed.already_exists=El directorio ya existe\nworld.duplicate.failed.empty_name=El nombre no puede estar vacío\nworld.duplicate.failed.invalid_name=El nombre contiene caracteres no válidos\nworld.datapack=Paquetes de datos\nworld.datetime=Jugado por última vez en %s\nworld.delete=Eliminar este mundo\nworld.delete.failed=No se pudo eliminar el mundo.\\n%s\nworld.download.title=Descargar mundo - %1s\nworld.export=Exportar el mundo\nworld.export.title=Elija el directorio para este mundo exportado\nworld.export.location=Guardar como\nworld.export.wizard=Exportar Mundo %s\nworld.game_version=Versión del juego\nworld.info=Información del mundo\nworld.info.basic=Información básica\nworld.info.allow_cheats=Permitir comandos/trucos\nworld.info.dimension.the_nether=El Nether\nworld.info.dimension.the_end=El End\nworld.info.difficulty=Dificultad\nworld.info.difficulty.peaceful=Pacífico\nworld.info.difficulty.easy=Fácil\nworld.info.difficulty.normal=Normal\nworld.info.difficulty.hard=Difícil\nworld.info.failed=No se ha podido leer la información del mundo\nworld.info.game_version=Versión del juego\nworld.info.last_played=Jugado por última vez\nworld.info.generate_features=Generar estructuras\nworld.info.player=Información del jugador\nworld.info.player.food_level=Nivel de hambre\nworld.info.player.game_type=Modo de juego\nworld.info.player.game_type.adventure=Aventura\nworld.info.player.game_type.creative=Creativo\nworld.info.player.game_type.hardcore=Extremo\nworld.info.player.game_type.spectator=Espectador\nworld.info.player.game_type.survival=Supervivencia\nworld.info.player.health=Salud\nworld.info.player.last_death_location=Lugar de la última muerte\nworld.info.player.location=Ubicación\nworld.info.player.spawn=Ubicación de desove\nworld.info.player.xp_level=Nivel de experiencia\nworld.info.random_seed=Semilla\nworld.locked=En uso\nworld.locked.failed=El mundo está actualmente en uso. Por favor, cierra el juego e inténtalo de nuevo.\nworld.manage=Mundos\nworld.manage.button=Administrar\nworld.manage.title=Mundo - %s\nworld.name=Nombre del mundo\nworld.name.enter=Introducir el nombre del mundo\nworld.show_all=Mostrar todo\n\nprofile=Directorios del juego\nprofile.already_exists=Este nombre ya existe, por favor, utilice un nombre diferente.\nprofile.default=Directorio actual\nprofile.home=Minecraft Launcher\nprofile.instance_directory=Directorio ded juego\nprofile.instance_directory.choose=Elegir directorio de juegos\nprofile.manage=Lista de directorios de instancia\nprofile.name=Nombre\nprofile.new=Nuevo directorio\nprofile.title=Directorios del juego\nprofile.selected=Seleccionado\nprofile.use_relative_path=Utilizar ruta relativa para la ruta del juego si es posible\n\nrepositories.custom=Repositorio Maven personalizado (%s)\nrepositories.maven_central=Universal (Maven Central)\nrepositories.tencentcloud_mirror=Espejo de China continental (Repositorio Maven Tencent Cloud)\nrepositories.chooser=HMCL requiere JavaFX para funcionar.\\n\\\n  \\n\\\n  Por favor, haga clic en «Aceptar» para descargar JavaFX desde el repositorio especificado, o haga clic en «Cancelar» para salir.\\n\\\n  \\n\\\n  Repositorios:\nrepositories.chooser.title=Elija la fuente de descarga de JavaFX\n\nresourcepack=Paquetes de recursos\nresourcepack.download.title=Descargar paquete de recursos - %1s\n\nreveal.in_file_manager=Mostrar en el administrador de archivos\n\nschematics=Esquemas\nschematics.add.failed=No se han podido añadir archivos de esquema\nschematics.back_to=Atrás a «%s»\nschematics.create_directory.prompt=Introduzca el nuevo nombre del directorio\nschematics.create_directory.failed=No se ha podido crear el directorio\nschematics.create_directory.failed.already_exists=El directorio ya existe\nschematics.create_directory.failed.empty_name=El nombre no puede estar vacío\nschematics.create_directory.failed.invalid_name=El nombre contiene caracteres no válidos\nschematics.info.description=Descripción\nschematics.info.enclosing_size=Tamaño de Encierro\nschematics.info.name=Nombre\nschematics.info.region_count=Regiones\nschematics.info.schematic_author=Autor\nschematics.info.time_created=Creado\nschematics.info.time_modified=Modificado\nschematics.info.total_blocks=Bloques totales\nschematics.info.total_volume=Volumen total\nschematics.info.version=Versión de esquema\nschematics.manage=Esquemas\nschematics.sub_items=%d elemento(s)\n\nsearch=Búsqueda\nsearch.hint.chinese=Buscar en inglés y chino\nsearch.hint.english=Buscar sólo en inglés\nsearch.enter=Introduzca aquí el texto\nsearch.sort=Ordenar por\nsearch.first_page=Primera\nsearch.previous_page=Previo\nsearch.next_page=Siguiente\nsearch.last_page=Last\nsearch.page_n=%d / %s\n\nselector.choose=Elegir\nselector.choose_file=Elegir archivo\nselector.custom=Personalizar\n\nsettings=Configuración\n\nsettings.advanced=Configuración avanzada\nsettings.advanced.modify=Editar configuración avanzada\nsettings.advanced.title=Configuración avanzada - %s\nsettings.advanced.custom_commands=Comandos personalizados\nsettings.advanced.custom_commands.hint=Se proporcionan las siguientes variables de entorno:\\n\\\n  \\  · $INST_NAME: nombre de la instancia.\\n\\\n  \\  · $INST_ID: nombre de la instancia.\\n\\\n  \\  · $INST_DIR: ruta absoluta del directorio de trabajo de la instancia.\\n\\\n  \\  · $INST_MC_DIR: ruta absoluta del directorio del juego.\\n\\\n  \\  · $INST_JAVA: binario de java utilizado para la ejecución\\n\\\n  \\  · $INST_FORGE: disponible si Forge está instalado.\\n\\\n  \\  · $INST_NEOFORGE: disponible si NeoForge está instalado.\\n\\\n  \\  · $INST_CLEANROOM: disponible si Cleanroom está instalado.\\n\\\n  \\  · $INST_LITELOADER: disponible si LiteLoader está instalado.\\n\\\n  \\  · $INST_OPTIFINE: disponible si OptiFine está instalado.\\n\\\n  \\  · $INST_FABRIC: disponible si Fabric está instalado.\\n\\\n  \\  · $INST_QUILT: disponible si Quilt está instalado.\nsettings.advanced.dont_check_game_completeness=No chequear integridad del juego\nsettings.advanced.dont_check_jvm_validity=No comprobar la compatibilidad con JVM\nsettings.advanced.dont_patch_natives=No intente sustituir automáticamente las bibliotecas nativas\nsettings.advanced.environment_variables=Variables de entorno\nsettings.advanced.game_dir.default=Por defecto («.minecraft/»)\nsettings.advanced.game_dir.independent=Aislar («.minecraft/versions/<nombre de la instancia>/», excepto para los activos y las bibliotecas)\nsettings.advanced.java_permanent_generation_space=Espacio PermGen\nsettings.advanced.java_permanent_generation_space.prompt=en MiB\nsettings.advanced.jvm=Opciones de JVM\nsettings.advanced.jvm_args=Argumentos JVM\nsettings.advanced.jvm_args.prompt=\\  · Si los argumentos introducidos en «Argumentos JVM» son los mismos que los argumentos por defecto, no se añadirán.\\n\\\n  \\  · Introduzca cualquier argumento GC en «Argumentos JVM», y se desactivará el argumento G1 de los argumentos por defecto.\\n\\\n  \\  · Activa «No añadir argumentos JVM por defecto» para ejecutar el juego sin añadir argumentos por defecto.\nsettings.advanced.launcher_visibility.close=Cerrar el launcher después de ejecutar el juego.\nsettings.advanced.launcher_visibility.hide=Ocultar el launcher después de ejecutar el juego.\nsettings.advanced.launcher_visibility.hide_and_reopen=Ocultar el launcher y volver a abrirlo cuando se cierra el juego.\nsettings.advanced.launcher_visibility.keep=Mantener visible el launcher.\nsettings.advanced.launcher_visible=Visibilidad del launcher\nsettings.advanced.minecraft_arguments=Argumentos de lanzamiento\nsettings.advanced.minecraft_arguments.prompt=Por defecto\nsettings.advanced.natives_directory=Ruta de la biblioteca nativa\nsettings.advanced.natives_directory.choose=Elija la ubicación de la biblioteca nativa deseada\nsettings.advanced.natives_directory.custom=Personalizado\nsettings.advanced.natives_directory.default=Por defecto\nsettings.advanced.natives_directory.default.version_id=<Nombre de la instancia>\nsettings.advanced.natives_directory.hint=Esta opción está pensada sólo para usuarios de chips de Apple u otras plataformas no soportadas oficialmente. Por favor, no modifique esta opción a menos que sepa lo que está haciendo.\\n\\\n  \\n\\\n  Antes de proceder, por favor, asegúrese de que todas las bibliotecas (por ejemplo, lwjgl.dll, libopenal.so) se proporcionan en su directorio deseado.\nsettings.advanced.no_jvm_args=No añadir argumentos JVM por defecto\nsettings.advanced.no_optimizing_jvm_args=No añadir argumentos de optimización de JVM\nsettings.advanced.precall_command=Comando pre-lanzamiento:\nsettings.advanced.precall_command.prompt=El comando se ejecuta antes de que los juegos se lance\nsettings.advanced.process_priority=Prioridad del proceso\nsettings.advanced.process_priority.low=Bajo\nsettings.advanced.process_priority.below_normal=Por debajo de lo normal\nsettings.advanced.process_priority.normal=Normal\nsettings.advanced.process_priority.above_normal=Por encima de lo normal\nsettings.advanced.process_priority.high=Alta\nsettings.advanced.post_exit_command=Comando post-salida\nsettings.advanced.post_exit_command.prompt=El comando se ejecuta después de que el juego se detenga\nsettings.advanced.renderer=Renderizador\nsettings.advanced.renderer.default=Por defecto\nsettings.advanced.renderer.default.desc=OpenGL\nsettings.advanced.renderer.d3d12=Mesa D3D12\nsettings.advanced.renderer.d3d12.desc=DirectX 12 (Rendimiento y compatibilidad deficientes)\nsettings.advanced.renderer.llvmpipe=Mesa LLVMpipe\nsettings.advanced.renderer.llvmpipe.desc=Software (Bajo rendimiento, máxima compatibilidad)\nsettings.advanced.renderer.zink=Mesa Zink\nsettings.advanced.renderer.zink.desc=Vulkan (Máximo rendimiento, baja compatibilidad)\nsettings.advanced.server_ip=Dirección del servidor\nsettings.advanced.server_ip.prompt=Entrar automáticamente después de ejecutar el juego\nsettings.advanced.unsupported_system_options=Configuración no aplicable al sistema actual\nsettings.advanced.use_native_glfw=[Sólo Linux/FreeBSD] Utilizar GLFW nativo\nsettings.advanced.use_native_openal=[Sólo Linux/FreeBSD] Utilizar OpenAL nativo\nsettings.advanced.workaround=Métodos alternativos\nsettings.advanced.workaround.warning=Éstas opciones están pensadas sólo para usuarios expertos. Jugar con estas opciones puede romper el juego. A menos que sepas lo que estás haciendo, por favor no modifiques estas opciones.\nsettings.advanced.wrapper_launcher=Comando Wrapper\nsettings.advanced.wrapper_launcher.prompt=Permite ejecutar el juego usando un programa extra como 'optirun' en Linux.\n\nsettings.custom=Personalizado\n\nsettings.game=Configuración\nsettings.game.copy_global=Copiar desde Config. Global\nsettings.game.copy_global.copy_all=Copiar todo\nsettings.game.copy_global.copy_all.confirm=¿Está seguro de que desea sobrescribir la configuración actual de la instancia? Esta acción no se puede deshacer.\nsettings.game.current=Juego\nsettings.game.dimension=Resolución\nsettings.game.exploration=Explorar\nsettings.game.fullscreen=Pantalla completa\nsettings.game.java_directory=Java\nsettings.game.java_directory.auto=Elegir automáticamente\nsettings.game.java_directory.auto.not_found=No se ha instalado ninguna versión de Java adecuada.\nsettings.game.java_directory.bit=%s bit\nsettings.game.java_directory.choose=Elija Java\nsettings.game.java_directory.invalid=Ruta de Java incorrecta.\nsettings.game.java_directory.template=%s (%s)\nsettings.game.java_directory.version=Especifique la versión de Java\nsettings.game.management=Gestionar\nsettings.game.working_directory=Directorio de trabajo\nsettings.game.working_directory.choose=Elija el directorio de trabajo\n\nsettings.icon=Icono\n\nsettings.launcher=Configuración del launcher\nsettings.launcher.appearance=Apariencia\nsettings.launcher.common_path.tooltip=Esta aplicación pondrá todos los activos y dependencias del juego aquí. Si hay bibliotecas existentes en el directorio del juego, el launcher preferirá usarlas primero.\nsettings.launcher.debug=Depuración\nsettings.launcher.disable_auto_game_options=No cambiar el idioma del juego\nsettings.launcher.download=Descargas\nsettings.launcher.download.threads=Hilos\nsettings.launcher.download.threads.auto=Determinar automáticamente\nsettings.launcher.download.threads.hint=Demasiados hilos pueden hacer que tu sistema se congele, y tu velocidad de descarga puede verse afectada por tu ISP y servidores de descarga. No siempre se da el caso de que más hilos aumenten la velocidad de descarga.\nsettings.launcher.download_source=Fuente de descarga\nsettings.launcher.download_source.auto=Elegir automáticamente el espejo de descarga\nsettings.launcher.enable_game_list=Mostrar lista de versiones en la página de inicio\nsettings.launcher.font=Fuente\nsettings.launcher.font.anti_aliasing=Suavizado de bordes\nsettings.launcher.font.anti_aliasing.auto=Automático\nsettings.launcher.font.anti_aliasing.gray=Escala de grises\nsettings.launcher.font.anti_aliasing.lcd=Subpíxel\nsettings.launcher.general=General\nsettings.launcher.language=Idioma\nsettings.launcher.launcher_log.export=Exportar registros del launcher\nsettings.launcher.launcher_log.export.failed=No se han podido exportar los registros\nsettings.launcher.launcher_log.export.success=Los registros se han exportado a «%s»\nsettings.launcher.launcher_log.reveal=Abrir directorio de registro\nsettings.launcher.log=Registro\nsettings.launcher.log.font=Fuente\nsettings.launcher.proxy=Proxy\nsettings.launcher.proxy.authentication=Requiere autenticación\nsettings.launcher.proxy.default=Usar proxy del sistema\nsettings.launcher.proxy.host=Host\nsettings.launcher.proxy.http=HTTP\nsettings.launcher.proxy.none=Sin proxy\nsettings.launcher.proxy.password=Contraseña\nsettings.launcher.proxy.port=Puerto\nsettings.launcher.proxy.socks=SOCKS\nsettings.launcher.proxy.username=Nombre de usuario\nsettings.launcher.theme=Tema\nsettings.launcher.title_transparent=Barra de título transparente\nsettings.launcher.turn_off_animations=Desactivar animación\nsettings.launcher.version_list_source=Lista de versiones\nsettings.launcher.background.settings.opacity=Opacidad\n\nsettings.memory=Memoria\nsettings.memory.allocate.auto=%1$.1f GiB Mínimo / %2$.1f GiB Asignado\nsettings.memory.allocate.auto.exceeded=%1$.1f GiB Mínimo / %2$.1f GiB Asignado (%3$.1f GiB Disponible)\nsettings.memory.allocate.manual=%1$.1f GiB Asignados\nsettings.memory.allocate.manual.exceeded=%1$.1f GiB Asignados (%3$.1f GiB Disponibles)\nsettings.memory.auto_allocate=Asignar automáticamente\nsettings.memory.lower_bound=Memoria mínima\nsettings.memory.unit.mib=MiB\nsettings.memory.used_per_total=%1$.1f GiB Utilizados / %2$.1f GiB Totales\nsettings.physical_memory=Tamaño de la memoria física\nsettings.show_log=Mostrar registros\nsettings.tabs.installers=Cargadores\nsettings.take_effect_after_restart=Se aplica después de reiniciar\nsettings.type=Tipo de configuración de la instancia\nsettings.type.global=Config. Global (compartida)\nsettings.type.global.manage=Config. Global\nsettings.type.global.edit=Editar Config. Global\nsettings.type.special.enable=Activar Config. Específica de la instancia\nsettings.type.special.edit=Editar configuración de la instancia actual\nsettings.type.special.edit.hint=La instancia actual «%s» ha activado la «Configuración específica de la instancia». Todas las configuraciones de esta página NO afectarán a esa instancia. Haga clic aquí para editar la configuración de la instancia.\n\nsponsor=Donantes\nsponsor.bmclapi=Las descargas de China continental son proporcionadas por BMCLAPI. Haga clic aquí para obtener más información.\nsponsor.hmcl=Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Haga clic aquí para obtener más información.\n\nsystem.architecture=Arquitectura\nsystem.operating_system=Sistema operativo\n\nunofficial.hint=Está utilizando una versión no oficial de HMCL. No podemos garantizar su seguridad.\n\nupdate=Actualizar\nupdate.accept=Actualizar\nupdate.changelog=Registro de cambios\nupdate.channel.dev=Beta\nupdate.channel.dev.hint=Actualmente está utilizando una versión beta del launcher, que puede incluir algunas características adicionales, pero también es más inestable que las versiones de lanzamiento.\\n\\\n  \\n\\\n  Si encuentra algún error o problema, puede ir a la <a href=\"hmcl://settings/feedback\">página de comentarios</a> para informar de ello.\nupdate.channel.dev.title=Aviso de versión beta\nupdate.channel.nightly=Nocturna\nupdate.channel.nightly.hint=Estás utilizando una versión nocturna del launcher, que puede incluir algunas características adicionales, pero también es siempre más inestable que las otras versiones.\\n\\\n  \\n\\\n  Si encuentra algún error o problema, puede ir a la <a href=\"hmcl://settings/feedback\">página de comentarios</a> para informar de ello.\nupdate.channel.nightly.title=Aviso de versión nocturna\nupdate.channel.stable=Estable\nupdate.checking=Buscando actualizaciones\nupdate.failed=No se puede actualizar\nupdate.found=¡Actualización disponible!\nupdate.newest_version=Última versión: %s\nupdate.bubble.title=Actualización disponible: %s\nupdate.bubble.subtitle=Haga clic aquí para actualizar\nupdate.note=Advertencia: Las versiones beta y las versiones nocturnas pueden tener más funciones o correcciones, pero también tienen más problemas potenciales.\nupdate.latest=Esta es la última versión.\nupdate.no_browser=No se puede abrir en el navegador del sistema. Pero, hemos copiado el enlace a su portapapeles y puede abrirlo manualmente.\nupdate.tooltip=Actualización\nupdate.preview=Vista previa de actualizaciones anticipadas\nupdate.preview.subtitle=Activa esta opción para recibir nuevas versiones del lanzador antes de su lanzamiento oficial para probarlas\n\nversion=Juegos\nversion.name=Nombre de instancia\nversion.cannot_read=No se ha podido analizar la instancia del juego, la instalación no puede continuar.\nversion.empty=No hay instancias\nversion.empty.add=Añadir una instancia\nversion.empty.hint=No hay instancias de Minecraft aquí.\\nPuedes intentar cambiar a otro directorio del juego o hacer clic aquí para descargar una.\nversion.game.all=Todos\nversion.game.april_fools=Día de los Inocentes\nversion.game.old=Histórico\nversion.game.release=Lanzamiento\nversion.game.releases=Lanzamientos\nversion.game.snapshot=Snapshot\nversion.game.snapshots=Snapshots\nversion.game.type=Tipo\nversion.launch=Iniciar Minecraft\nversion.launch.empty=Iniciar Minecraft\nversion.launch.empty.installing=Instalando el juego\nversion.launch.empty.tooltip=Instala y ejecuta la última versión oficial del juego.\nversion.launch.test=Probar instalación\nversion.switch=Cambiar Instancia\nversion.launch_script=Exportar script de ejecución\nversion.launch_script.failed=No se ha podido exportar el script de ejecución.\nversion.launch_script.save=Exportar script de ejecución\nversion.launch_script.success=Exportado el script de ejecución como %s.\nversion.manage=Todas las instancias\nversion.manage.clean=Eliminar archivos de registros\nversion.manage.clean.tooltip=Eliminar los archivos de los directorios «logs» y «crash-reports».\nversion.manage.duplicate=Duplicar instancia\nversion.manage.duplicate.duplicate_save=Duplicar mundos\nversion.manage.duplicate.prompt=Introduzca el nombre de la nueva instancia\nversion.manage.duplicate.confirm=La instancia duplicada tendrá una copia de todos los archivos de esta instancia, con un directorio de juego y una configuración aislados.\nversion.manage.manage=Editar instancia\nversion.manage.manage.title=Editar instancia - %1s\nversion.manage.redownload_assets_index=Actualizar activos del juego\nversion.manage.remove=Eliminar instancia\nversion.manage.remove.confirm.trash=¿Estás seguro de que quieres eliminar la instancia «%s»? Todavía puedes encontrar sus archivos en tu papelera de reciclaje con el nombre de «%s».\nversion.manage.remove_assets=Borrar todas las activos del juego\nversion.manage.remove_libraries=Borrar todas las bibliotecas\nversion.manage.rename=Renombrar instancia\nversion.manage.rename.message=Introduzca el nombre de la nueva instancia\nversion.manage.rename.fail=No se ha podido renombrar la instancia, algunos archivos pueden estar en uso o el nombre contiene un carácter no válido.\nversion.search=Nombre\nversion.search.prompt=Introduzca el nombre de la versión para buscar.\nversion.settings=Configuración\nversion.update=Actualizar Modpack\n\nwarning.java_interpreted_mode=El lanzador se ejecuta en un entorno Java interpretado, lo que afectará considerablemente al rendimiento.\\n\\\n  \\n\\\n  Recomendamos utilizar Java con soporte JIT para ejecutar el lanzador y obtener la mejor experiencia.\n\nwarning.software_rendering=El lanzador utiliza actualmente renderización por software, lo que afectará considerablemente al rendimiento.\n\nwiki.tooltip=Página de Minecraft Wiki\nwiki.version.game=https://es.minecraft.wiki/w/Java_Edition_%s\nwiki.version.game.snapshot=https://es.minecraft.wiki/w/Java_Edition_%s\n\nwizard.prev=< Previo\nwizard.failed=Falló\nwizard.finish=Finalizar\nwizard.next=Siguiente >\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/lang/I18N_ja.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Contributors: zhixuan2333\n\nabout=について\nabout.copyright=著作権\nabout.author=著者\nabout.author.statement=bilibili @huanghongxun\nabout.claim=EULA\nabout.claim.statement=全文はこのリンクをクリック\nabout.dependency=依存関係\nabout.legal=法的承認\nabout.thanks_to=感謝します…\nabout.thanks_to.bangbang93.statement=BMCLAPIダウンロードミラープロバイダー。寄付をご検討ください！\nabout.thanks_to.burningtnt.statement=HMCLに多くのテクニカルサポートを提供\nabout.thanks_to.contributors=GitHubのすべての貢献者\nabout.thanks_to.contributors.statement=HMCLをサポートしてくれたオープンソースコミュニティに感謝する\nabout.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー\nabout.thanks_to.glavo.statement=HMCLのメンテナ\nabout.thanks_to.zekerzhayard.statement=HMCLに多くのテクニカルサポートを提供\nabout.thanks_to.zkitefly.statement=HMCLドキュメントメンテナ\nabout.thanks_to.mcbbs=MCBBS (Minecraft中国語フォーラム)\nabout.thanks_to.mcbbs.statement=中国本土ユーザー向けのmcbbs.netダウンロードミラープロバイダー（もう利用できません）\nabout.thanks_to.mcmod=MCMod (mcmod.cn)\nabout.thanks_to.mcmod.statement=様々なMODの簡体字中国語名前翻訳とウィキを提供する\nabout.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー\nabout.thanks_to.shulkersakura.statement=HMCLロゴプロバイダー\nabout.thanks_to.users=HMCLユーザーグループのメンバー\nabout.thanks_to.users.statement=寄付、バグレポートなどに感謝します\nabout.thanks_to.yushijinhun.statement=authlib-injector関連のサポートを提供する\nabout.open_source=オープンソース\nabout.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)\n\naccount=アカウント\naccount.cape=マント\naccount.character=キャラクター\naccount.choose=キャラクターを選択する\naccount.create=アカウントを追加\naccount.create.microsoft=Microsoft Login\naccount.create.offline=Offline\naccount.create.authlibInjector=サードパーティーアカウントを追加\naccount.email=メールアドレス\naccount.failed=ログインに失敗しました\naccount.failed.character_deleted=キャラクターが削除されました。\naccount.failed.connect_authentication_server=認証サーバーに接続できません。インターネット接続を確認してください。\naccount.failed.connect_injector_server=認証サーバーに接続できません。ネットワークをチェックして、URLが正しいことを確認してください。\naccount.failed.injector_download_failure=authlib-injectorのダウンロードに失敗しました。ネットワークを確認して、別のダウンロードソースに切り替えてみてください。\naccount.failed.invalid_credentials=パスワードが正しくないか、一時的にログインできません。\naccount.failed.invalid_password=無効なパスワード\naccount.failed.invalid_token=ログアウトし、パスワードを再入力してログインしてください。\naccount.failed.migration=アカウントをMicrosoftアカウントに移行する必要があります。すでに移行されている場合は、代わりに移行されたMicrosoftアカウントにログインする必要があります。\naccount.failed.no_character=このアカウントに文字がありません。\naccount.failed.server_response_malformed=無効なサーバー応答。認証サーバーでエラーが発生している可能性があります。\naccount.failed.wrong_account=不一致のアカウントでログインしました。\naccount.hmcl.hint=「ログイン」をクリックし、開いているページでログインを完了する必要があります。\naccount.injector.add=認証サーバーを追加します\naccount.injector.empty=Empty（追加するには右側のプラスボタンをクリックしてください）\naccount.injector.http=警告：このサーバーはHTTPを使用するため、パスワードはクリアテキストで送信されます。\naccount.injector.link.register=登録\naccount.injector.server=Authサーバー\naccount.injector.server_url=サーバーのURL\naccount.injector.server_name=サーバー名\naccount.login=ログイン\naccount.login.hint=パスワードは保存されません。\naccount.login.refresh=再ログイン\naccount.login.refresh.microsoft.hint=アカウント認証が無効なため、Microsoft アカウントを再度追加する必要があります\naccount.logout=ログアウト\naccount.register=登録\naccount.manage=アカウントリスト\naccount.methods=ログインタイプ\naccount.methods.authlib_injector=authlib-インジェクター\naccount.methods.microsoft=Microsoft Account\naccount.methods.microsoft.birth=誕生日の設定を編集する方法...\naccount.methods.microsoft.deauthorize=アカウントのバインドを解除\naccount.methods.microsoft.close_page=Microsoftアカウントの認証が終了しました。後で終了するログイン手順がいくつか残っています。このページを今すぐ閉じることができます。\naccount.methods.microsoft.logging_in=ログイン...\naccount.methods.microsoft.makegameidsettings=プロファイルを作成/プロフィール名を編集する\naccount.methods.microsoft.profile=アカウントプロファイル..\naccount.methods.microsoft.purchase=Minecraftを購入する\naccount.methods.offline=オフライン\naccount.methods.offline.uuid=UUID\naccount.methods.offline.uuid.hint=UUIDは、Minecraftのゲームキャラクターの一意の識別子です。UUIDの生成方法は、ゲームランチャーによって異なります。UUIDを他のランチャーによって生成されたものに変更すると、オフラインアカウントのバックパック内のゲームブロック/アイテムが残ることが約束されます。このオプションは専門家向けです。何をしているのかわからない限り、このオプションを変更することはお勧めしません。\\nこのオプションはサーバーに参加する場合には必要ありません。\naccount.methods.offline.uuid.malformed=Malformed\naccount.missing=アカウントなし\naccount.missing.add=追加するにはここをクリック\naccount.not_logged_in=ログインしていません\naccount.password=パスワード\naccount.skin=スキン\naccount.skin.file=スキンイメージファイル\naccount.skin.model=モデル\naccount.skin.model.default=Classic\naccount.skin.model.slim=Slim\naccount.skin.type.csl_api=Blessing Skin\naccount.skin.type.csl_api.location=アドレス\naccount.skin.type.csl_api.location.hint=CustomSkinAPI\naccount.skin.type.little_skin=LittleSkin\naccount.skin.type.little_skin.hint=スキンのWebサイトでキャラクターを作成する必要があります。そして、このオフラインアカウントのスキンは、同じ名前のスキンWebサイトのキャラクターのスキンにバインドされます。\naccount.skin.type.local_file=ローカルスキン画像ファイル\naccount.skin.upload=スキンをアップロードする\naccount.skin.upload.failed=スキンのアップロードに失敗しました\naccount.skin.invalid_skin=認識されないスキンファイル\naccount.username=ユーザー名\n\narchive.author=作成者\narchive.date=公開日\narchive.file.name=名前\narchive.version=バージョン\n\nassets.download=アセットのダウンロード\nassets.download_all=アセットの整合性チェック\nassets.index.malformed=アセットインデックスの形式が正しくありません。バージョン設定の[ゲームアセットファイルの更新]で再試行できます。\n\nbutton.cancel=キャンセル\nbutton.change_source=ダウンロードソースの変更\nbutton.clear=クリア\nbutton.copy_and_exit=コピーして終了\nbutton.delete=削除\nbutton.edit=編集\nbutton.install=インストール\nbutton.export=エクスポート\nbutton.no=いいえ\nbutton.ok=OK\nbutton.refresh=更新\nbutton.remove=削除\nbutton.remove.confirm=本当に完全に削除しますか？この操作は元に戻せません！\nbutton.retry=リトライ\nbutton.save=保存\nbutton.save_as=名前を付けて保存\nbutton.select_all=すべて選択\nbutton.view=読む\nbutton.yes=はい\n\ncontact=フィードバック\ncontact.chat=グループチャット\ncontact.chat.discord=Discord\ncontact.chat.discord.statement=Discordサーバーに参加してください！\ncontact.chat.qq_group=HMCLユーザーQQグループ\ncontact.chat.qq_group.statement=ユーザーQQグループに参加してください！\ncontact.feedback=フィードバックチャンネル\ncontact.feedback.github=GitHub Issues\ncontact.feedback.github.statement=GitHubで問題を送信します。\n\ncolor.recent=推奨\ncolor.custom=カスタムカラー\n\ncrash.NoClassDefFound=このソフトウェアの整合性を確認するか、Javaの更新をお試しください。\ncrash.user_fault=JavaまたはOSの環境が壊れているため、ランチャーがクラッシュしました。JavaまたはOSが正しくインストールされているかご確認ください。\n\ncurse.category.0=全て\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4471\ncurse.category.4474=Sci-Fi\ncurse.category.4481=小さい/軽い\ncurse.category.4483=戦闘\ncurse.category.4477=ミニゲーム\ncurse.category.4478=クエスト\ncurse.category.4484=マルチプレイヤー\ncurse.category.4476=探索\ncurse.category.4736=Skyblock\ncurse.category.4475=アドベンチャーとRPG\ncurse.category.4487=FTB\ncurse.category.4480=マップベース\ncurse.category.4479=ハードコア\ncurse.category.4482=特大\ncurse.category.4472=Tech\ncurse.category.4473=魔法\ncurse.category.7418=ホラー\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5129=Vanilla+\ncurse.category.5189=Utility & QOL\ncurse.category.5190=QoL\ncurse.category.5191=Utility & QoL\ncurse.category.5192=FancyMenu\ncurse.category.423=地図と情報\ncurse.category.6814=パフォーマンス\ncurse.category.6484=Create\ncurse.category.6954=Integrated Dynamics\ncurse.category.6145=Skyblock\ncurse.category.6821=バグ修正\ncurse.category.426=Addons\ncurse.category.434=武器、道具、武器\ncurse.category.409=構造\ncurse.category.4485=ブラッドマジック\ncurse.category.420=ストレージ\ncurse.category.429=インダストリアルクラフト\ncurse.category.419=魔法\ncurse.category.412=テクノロジー\ncurse.category.4557=レッドストーン\ncurse.category.428=Tinkerの構成\ncurse.category.414=プレイヤートランスポート\ncurse.category.4486=ラッキーブロック\ncurse.category.432=Buildcraft\ncurse.category.418=遺伝学\ncurse.category.4671=Twitch統合\ncurse.category.408=鉱石と資源\ncurse.category.4773=CraftTweaker\ncurse.category.430=Thaumcraft\ncurse.category.422=アドベンチャーとRPG\ncurse.category.413=処理中\ncurse.category.417=エネルギー\ncurse.category.415=エネルギー、流体、およびアイテムの輸送\ncurse.category.433=林業\ncurse.category.425=その他\ncurse.category.4545=Applied Energistics 2\ncurse.category.416=農業\ncurse.category.421=APIとライブラリ\ncurse.category.4780=ファブリック\ncurse.category.424=Cosmetic\ncurse.category.406=世界の世代\ncurse.category.435=サーバーユーティリティ\ncurse.category.411=モブ\ncurse.category.407=バイオーム\ncurse.category.427=熱膨張\ncurse.category.410=ディメンション\ncurse.category.436=食品\ncurse.category.4558=レッドストーン\ncurse.category.4843=自動化\ncurse.category.4906=MCreator\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5193=データパッケージ\ncurse.category.399=スチームパンク\ncurse.category.396=128x\ncurse.category.398=512x以上\ncurse.category.397=256x\ncurse.category.405=その他\ncurse.category.395=64x\ncurse.category.400=フォトリアリスティック\ncurse.category.393=16x\ncurse.category.403=従来型\ncurse.category.394=32x\ncurse.category.404=アニメーション\ncurse.category.4465=Modサポート\ncurse.category.402=中世\ncurse.category.401=モダン\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/17\ncurse.category.4464=変更された世界\ncurse.category.250=ゲームマップ\ncurse.category.249=作成\ncurse.category.251=パルクール\ncurse.category.253=サバイバル\ncurse.category.248=アドベンチャー\ncurse.category.252=パズル\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4546\ncurse.category.4551=ハードコアクエストモード\ncurse.category.4548=ラッキーブロック\ncurse.category.4556=進行\ncurse.category.4752=ガジェットの構築\ncurse.category.4553=CraftTweaker\ncurse.category.4554=レシピ\ncurse.category.4549=ガイドブック\ncurse.category.4547=構成\ncurse.category.4550=クエスト\ncurse.category.4555=World Gen\ncurse.category.4552=スクリプト\n\ncurse.sort.author=Author\ncurse.sort.date_created=作成日\ncurse.sort.last_updated=最終更新\ncurse.sort.name=名前\ncurse.sort.popularity=人気\ncurse.sort.total_downloads=合計ダウンロード数\n\ndatetime.format=yyyy/MM/dd H:mm:ss\n\ndownload=ダウンロード\ndownload.hint=ゲームや modpack をインストールするか、mod、リソース パック、マップ、シェーダーをダウンロードします\ndownload.code.404=リモートサーバーにファイルが見つかりません：%s\ndownload.content=ゲームコンテンツ\ndownload.shader=シェーダー\ndownload.existing=ファイルは既に存在するため、保存できません。「名前を付けて保存」を選択して、ファイルを別の場所に保存できます。\ndownload.external_link=ダウンロードサイトを開く\ndownload.failed=%1$s のダウンロードに失敗しました、応答コード：%2$d\ndownload.failed.empty=候補者なし。戻るにはここをクリックしてください。\ndownload.failed.refresh=バージョンリストをダウンロードできません。ここをクリックして再試行してください。\ndownload.game=ゲームのダウンロード\ndownload.provider.bmclapi=BMCLAPI\ndownload.provider.bmclapi.desc=ミラーソース\ndownload.provider.mojang=Mojang\ndownload.provider.mojang.desc=OptiFineはBMCLAPIにより提供されます\ndownload.provider.official=公式ソースからロード\ndownload.provider.balanced=mcbbsのソースからロード\ndownload.provider.mirror=ミラーソースからロード\ndownload.javafx=必要なランタイムコンポーネントのダウンロード\ndownload.javafx.notes=ネットワークを介したHMCLに必要なコンポーネントのダウンロード。\\n [ダウンロードソースの変更]ボタンをクリックして詳細を表示し、ダウンロードソースを選択するか、[キャンセル]ボタンをクリックして停止して終了します。\\n注：ダウンロード速度が遅すぎます。ダウンロードソースを変更してみてください。\ndownload.javafx.component=ダウンロード中のモジュール %s\ndownload.javafx.prepare=ダウンロードする準備ができました\n\nexception.access_denied=ファイル %s にアクセスできないので、HMCL がファイルにアクセスできないか、ファイルが他のプログラムによって開かれています。\\n\\\n  例えば、管理者でないユーザーは、他のアカウントの個人フォルダーにあるファイルにアクセスできない場合があります。\\n\\\n  Windowsユーザーの場合、リソースモニターでプログラムがファイルを占有しているかどうかを確認し、もしそうなら、このファイルを占有している関連プログラムを閉じるか、コンピュータを再起動して、もう一度試してみることも可能です。\nexception.artifact_malformed=ダウンロードしたファイルがチェックサムを通過していない。\nexception.ssl_handshake=現在のJava仮想マシンに該当するSSL証明書がないため、SSL接続を確立できませんでした。別のJava仮想マシンでHMCLを起動して、もう一度試してみてください。\nexception.dns.pollution=SSL 接続を確立できませんでした。DNS 解決が正しくない可能性があります。DNS サーバーを変更するか、プロキシサービスを使用してみてください。\n\nextension.bat=WindowsBatファイル\nextension.png=画像ファイル\nextension.ps1=PowerShellスクリプト\nextension.sh=Bashスクリプト\n\nextension.datapack=Datapack\nextension.mod=Modファイル\nextension.world=World zip\n\nfatal.javafx.missing=JavaFXがありません。\\nJava11以降を使用している場合は、Oracle JRE 8にダウングレードするか、BellSoft Liberica FullJREをインストールしてください。\\n他のOpenJDKビルドを使用している場合は、OpenJFXが含まれていることを確認してください。\nfatal.config_loading_failure=構成にアクセスできません。\\nHelloMinecraftを確認してください。Launcherには、「%s」とその中のファイルへの読み取りおよび書き込みアクセス権があります。\nfatal.migration_requires_manual_reboot=更新が完了しました。Hello Minecraftを再開してください！ランチャー。\nfatal.apply_update_failure=ごめんなさい、Hello Minecraft! Launcher 何か問題が発生したため、ランチャーは更新を完了できませんでした。\\nただし、Hello Minecraftをダウンロードすることで、手動で更新を終了できます。%s からのランチャー。\\nこの問題を報告することを検討してください。\nfatal.samba=If you are trying to run HMCL in a shared folder by Samba, HMCL may not working, please try updating your Java or running HMCL in a local folder.\nfatal.illegal_char=ユーザーフォルダーのパスに不正な文字'='が含まれています, ログインアカウントやオフラインログインではスキンの変更ができなくなり。\nfatal.unsupported_platform=現在、お使いのプラットフォームでは Minecraft が完全にはサポートされていないため、機能が欠けたり、ゲームを起動できない場合があります。\\n\\\n   \\n\\\n   Minecraft 1.17 以降を起動できない場合は、「グローバル/インスタンス固有の設定 → 詳細設定」で「レンダラー」を「Mesa LLVMpipe」に切り替え、CPU レンダリングを使用することで互換性が向上する可能性があります。\n\nfile=ファイル\n\nfolder.config=Config\nfolder.game=ゲームディレクトリ\nfolder.mod=Mod\nfolder.resourcepacks=リソースパック\nfolder.shaderpacks=シェーダーパックフォルダー\nfolder.screenshots=スクリーンショット\n\ngame=ゲーム\ngame.crash.feedback=<b>このインターフェースのスクリーンショットや写真を他の人と共有しないでください。</b> 他の人に助けを求める場合は、左下隅にあるゲーム クラッシュ情報をクリックしてエクスポートし、エクスポートされたファイルを分析のために他の人に送信してください。\ngame.crash.info=ゲームステータス\ngame.crash.reason=クラッシュアナライザー\ngame.crash.reason.analyzing=分析中..\ngame.crash.reason.multiple=複数の理由が検出されました：\\n\\n\ngame.crash.reason.bootstrap_failed=mod %1$s がクラッシュしたため、ゲームを実行できません。\\n削除または更新を試みることができます。\ngame.crash.reason.config=modが構成ファイルを解析できないため、ゲームを実行できません。\\nMod %1$s は構成ファイル %2$s を解析できません。\ngame.crash.reason.debug_crash=クラッシュが手動でトリガーされるため、ゲームを実行できません。\\n実際、ゲームは無実であり、すべてあなたの責任です。\ngame.crash.reason.duplicated_mod=Mod %1$s が重複してインストールされているため、現在のゲームを継続して実行できません。\\n%2$s\\n各Modは1つしかインストールできませんので、余分なModを削除して再度お試しください。\ngame.crash.reason.mixin_apply_mod_failed=Mixin が %1$s モジュールを適用できないため、現在のゲームを続行できません。\\n問題を解決するために、このモジュールを削除または更新してみてください。\ngame.crash.reason.fabric_version_0_12=Fabric 0.12以降は、現在インストールされているmodと互換性がありません。ファブリックを0.11.7にダウングレードする必要があります。\ngame.crash.reason.fabric_warnings=Fabricはいくつかの警告を出します：\\n%1$s\ngame.crash.reason.modmixin_failure=mod インジェクションの失敗により、現在のゲームを続行できません。\\nこれは通常、MOD にバグがあるか、現在の環境と互換性がないことを意味します。\\n間違った mod のログを確認できます。\ngame.crash.reason.mod_repeat_installation=現在のゲームには複数の同一の Mod が繰り返しインストールされており、各 Mod は 1 回しか表示できません。ゲームを開始する前に繰り返しの Mod を削除してください。\ngame.crash.reason.forge_error=Forge がエラー メッセージを表示した可能性があります。 \\nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \\nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。\\n%1$s\ngame.crash.reason.mod_resolution0=mod の問題により、現在のゲームを続行できません。 \\n間違った mod のログを確認できます。\ngame.crash.reason.java_version_is_too_high=Java のバージョンが高すぎて実行を継続できないため、現在のゲームがクラッシュしました。 \\nゲームを起動する前に、グローバル ゲーム設定またはゲーム固有の設定の Java パス タブで Java の以前のバージョンに切り替えてください。 \\nそうでない場合は、<a href=\"https://www.java.com/download/\">java.com (Java8)</a> または <a href=\"https://bell-sw.com/pages/downloads/#downloads\">BellSoft Liberica Full JRE (Java17)</a> およびその他のプラットフォームをダウンロードしてインストールします (インストール後にランチャーを再起動します)。\ngame.crash.reason.mod_name=現在のゲームはModファイル名の問題で続行できません。\\nModファイル名は、英文の全半角の大文字と小文字（Aa ~ Zz）、数字（0 ~ 9）、横線（-）、アンダースコア（_）、点（.）のみを使用してください。\\n上記のコンプライアンス文字をModフォルダに追加してください。\ngame.crash.reason.incomplete_forge_installation=Forge / NeoForge のインストールが不完全なため、現在のゲームを続行できません。\\nバージョン設定 - 自動インストールで Forge / NeoForge をアンインストールしてから再インストールしてください。\ngame.crash.reason.file_already_exists=ファイル %1$s が既に存在するので、現在のゲームは続行できません。\\このファイルを削除してもよいと思われる場合は、このファイルをバックアップして、ゲームを再起動してから削除してみてください。\ngame.crash.reason.need_jdk11=Java 仮想マシンのバージョンが不適切なため、現在のゲームは実行を続行できません。\\nJava 11 をダウンロードしてインストールし、グローバル (特定) ゲーム設定で Java を 11 で始まるバージョンに設定する必要があります。\ngame.crash.reason.file_changed=ファイルのチェックサムに失敗したため、現在のゲームを続行できません。\\nMinecraft.jar ファイルを手動で変更した場合、変更をロールバックするか、ゲームを再度ダウンロードする必要があります。\ngame.crash.reason.gl_operation_failure=一部のモッド、シェーダーパック、テクスチャパックが原因でゲームがクラッシュしました。\\n使用しているモッド/シェーダーパック/テクスチャパックを無効にして、再試行してください。\ngame.crash.reason.graphics_driver=現在、お使いのグラフィックカードのドライバに問題があるため、ゲームがクラッシュしています。\\n\\\n  ゲームを起動する前に、グラフィックカードのドライバーを最新バージョンにアップグレードしてみてください。\\n\\\n  ディスクリートグラフィックカードを搭載している場合、統合型/コアグラフィックカードを使用してゲームが起動するかどうかを確認する必要があります。その場合、ディスクリートグラフィックカードを使用したゲームでHMCLを起動してみてください。それでも問題がある場合は、新しいグラフィックカードや新しいコンピュータの購入を検討する必要があるかもしれません。\\n\\\n  コアグラフィックカードが必要な場合は、お使いのパソコンのCPUがIntel(R) Core(TM) 3000シリーズ以上であることをご確認ください。その場合、Minecraft 1.16.5以前の場合は、ゲームに使用しているJavaバージョンを1.8.0_51以下にダウングレードするか、ディスクリートグラフィックカードを交換するか、または、新しい コンピュータを使用します。\ngame.crash.reason.macos_failed_to_find_service_port_for_display=現在のゲームは、Apple silicon プラットフォームでの OpenGL ウィンドウの初期化に失敗したため、続行できません。\\nこの問題に対して、HMCL には直接的な解決策がありません。ブラウザを任意に開いてフルスクリーンにし、その後 HMCL に戻り、ゲームを起動し、ゲームウィンドウが表示される前<b>に素早くブラウザのページに戻ってください</b>。ゲームウィンドウが表示されたらゲームウィンドウに戻ってください。\ngame.crash.reason.illegal_access_error=一部のmodが原因でゲームがクラッシュしました。\\n認識している場合：%1$s、modを更新または削除して、再試行してください。\ngame.crash.reason.install_mixinbootstrap=MixinBootstrapが見つからないため、現在のゲームを続行できません。\\n<a href=\"https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap\">MixinBootstrap</a>をインストールしてみてください。インストール後にクラッシュする場合は、モジュールのファイル名の前に英語の「! をモジュールのファイル名の前につけてみてください。\ngame.crash.reason.jdk_9=Javaのバージョンが高すぎるため、ゲームを実行できません。\\nJava 8をダウンロードしてインストールし、ゲーム設定で新しくインストールしたJavaを選択する必要があります。\ngame.crash.reason.jvm_32bit=メモリ割り当てが32ビットJavaVMの制限を超えたため、ゲームがクラッシュしました。\\nOSが64ビットの場合は、64ビットJavaをインストールして使用してください。OSが32ビットの場合は、64ビットOSを再インストールするか、新しいコンピュータを変更できます。\\nまたは、自動メモリ割り当てを無効にして、最大メモリサイズを1024MiB以下に設定できます。\ngame.crash.reason.loading_crashed_forge=mod %1$s（%2$s）がクラッシュしたため、ゲームがクラッシュしました。\\n削除または更新を試みることができます。\ngame.crash.reason.loading_crashed_fabric=mod %1$s がクラッシュしたため、ゲームがクラッシュしました。\\n削除または更新を試みることができます。\ngame.crash.reason.memory_exceeded=JVMが割り当てるのに十分なメモリがないため、ゲームがクラッシュしました。\\nこの問題は、ページサイズが小さすぎることが原因です。\\nゲーム設定で自動メモリ割り当てをオフにし、メモリ割り当てを次のように調整する必要があります。システムが処理できる値。\\nシステムのページサイズを十分な大きさに調整することもできます。\ngame.crash.reason.mod=modが原因でゲームがクラッシュしました：%1$s。\\nmodを更新または削除して、再試行できます。\ngame.crash.reason.mod_resolution=modの解決に失敗したため、ゲームがクラッシュしました。\\nFabricは次の情報を提供します：\\n%s\ngame.crash.reason.forgemod_resolution=modの解決に失敗したため、ゲームがクラッシュしました。\\nForgeは次の情報を提供します：\\n%s\ngame.crash.reason.forge_found_duplicate_mods=現在のゲームは、モッズの重複の問題により、続行できません。Forge が次の情報を提供しました：\\n%1$s\ngame.crash.reason.mod_resolution_collection=改造前のバージョンと一致しないため、現在のゲームを続行できない。\\n%1$s Mod: %2$s が必要です。をクリックすると実行を継続します。\\nつまり、フロントエンドをアップデートするか、ダウングレードする必要があるのです。ダウンロードページからMODをダウンロードするか、ウェブから %3$s をダウンロードすることができます。\ngame.crash.reason.mod_resolution_conflict=modが競合しているため、ゲームがクラッシュしました。\\n%1$s が %2$s と競合しています。\ngame.crash.reason.mod_resolution_missing=Modプレフィックスがないため、現在のゲームを続けることができません。\\続行するにはMod: %2$s が必要です。\\これはMODがインストールされていないか、そのMODのバージョンが足りないことを意味します。ダウンロードページからMODをダウンロードするか、ウェブから %3$s をダウンロードすることができます。\ngame.crash.reason.mod_resolution_missing_minecraft=modが現在のMinecraftバージョンと互換性がないため、ゲームがクラッシュしました。\\n%1$s にはMinecraftバージョン %2$s が必要です。\\nインストールされているバージョンのmodを保持する場合は、変更する必要があります。ゲームバージョン。\\n現在のゲームバージョンを引き続き使用する場合は、適切なmodを再インストールする必要があります。\ngame.crash.reason.mod_resolution_mod_version=%1$s（バージョン：%2$s）\ngame.crash.reason.mod_resolution_mod_version.any=%1$s（任意のバージョン）\ngame.crash.reason.forge_repeat_installation=Forge が重複してインストールされているため、現在のゲームを続行できません。 <a href=\"https://github.com/HMCL-dev/HMCL/issues/1880\">これは既知の問題です</a>\\nログ フィードバックを GitHub にアップロードすることをお勧めします。この質問を修正してください。 \\n現在、自動インストールに移動して Forge をアンインストールし、再インストールできます。\ngame.crash.reason.shaders_mod=OptiFine と Shaders Mod の両方がインストールされているため、現在のゲームを続行できません。\\nOptiFine には Shaders Mod の機能が統合されているため、Shaders Mod を削除してください。\ngame.crash.reason.rtss_forest_sodium=現在のゲームは、RivaTuner Statistics Server (RTSS) と Sodium の互換性の問題によりクラッシュしました。\\n詳細については、<a href=\"https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\">こちら</a>をご覧ください。\ngame.crash.reason.optifine_repeat_installation=OptiFine を繰り返しインストールしたため、現在のゲームを続行できません。 \\nMod フォルダの下にある OptiFine を削除するか、[ゲーム管理] - [自動インストール] に移動して、自動的にインストールされる OptiFine をアンインストールしてください。\ngame.crash.reason.no_class_def_found_error=コードが不完全なためゲームを実行できません。\\nゲームにmodがないか、一部のmodファイルが不完全であるか、一部のmodが現在のゲームと互換性がない可能性があります。\\nゲームを再インストールする必要がある場合があります。およびmod、またはヘルプを要求します。\\n%1$s がありません\ngame.crash.reason.night_config_fixes=Night Config に問題があるため、現在のゲームを続行できません。\\n<a href=\"https://www.curseforge.com/minecraft/mc-mods/night-config-fixes\">Night Config Fixes</a> mod をインストールしてみると、この問題に役立つ可能性があります。\\n詳細については、mod の <a href=\"https://www.github.com/Fuzss/nightconfigfixes\">GitHub リポジトリ</a> にアクセスしてください。\ngame.crash.reason.no_such_method_error=コードが不完全なためゲームを実行できません。\\nゲームにmodがないか、一部のmodファイルが不完全であるか、一部のmodが現在のゲームと互換性がない可能性があります。\\nゲームを再インストールする必要がある場合があります。と改造、または助けを求める。\ngame.crash.reason.opengl_not_supported=グラフィックスドライバに問題があるため、ゲームを実行できません。\\nOpenGLがサポートされていないため、リモートデスクトップモードですか、それともストリーミングモードですか。はいの場合は、元のコンピューターを使用してゲームを開始してください。\\nまたは、グラフィックスドライバーを最新バージョンに更新して、ゲームを再起動してください。コンピューターに個別のグラフィックがある場合は、ゲームが統合/コアグラフィックを使用しているかどうかを確認する必要があります。その場合は、ディスクリートグラフィックカードを使用してHMCLとゲームを実行してみてください。それでもこれらの問題が発生する場合は、新しいグラフィックカードまたは新しいコンピュータの入手を検討してください。\ngame.crash.reason.openj9=ゲームはOpenJ9仮想マシンでは実行できません。ゲーム設定でHotspotJava仮想マシンに切り替えて、ゲームを再起動してください。そうでない場合は、オンラインでダウンロードできます。\ngame.crash.reason.out_of_memory=メモリ不足のためゲームを実行できません。\\nこれは、メモリ割り当てが少なすぎるか、modが多すぎることが原因である可能性があります。\\nゲーム設定でゲームメモリ割り当てを増やして、ゲームを実行できるようにすることができます。より大きなメモリ。\\nそれでもこれらの問題が発生する場合は、より良いコンピュータを変更する必要があるかもしれません。\ngame.crash.reason.resolution_too_high=解像度が高すぎるリソースパックを使用しているため、ゲームを実行できません\\n解像度の低いリソースパックを使用するか、メモリの大きいグラフィックカードを購入する必要があります。\ngame.crash.reason.stacktrace=不明。[ログ]ボタンをクリックすると詳細を確認できます。\\nModIDを表すキーワードがいくつかあります。オンラインで検索して理由を見つけることができます。\\n%s\ngame.crash.reason.too_old_java=Java仮想マシンのバージョンが低すぎるため、ゲームを実行できません。\\nゲーム設定でJava仮想マシンの新しいバージョン（%1$s）に切り替えて、ゲームを再起動する必要があります。そうでない場合は、オンラインでダウンロードできます。\ngame.crash.reason.unknown=不明。「ログ」ボタンをクリックすると詳細を確認できます。\ngame.crash.reason.unsatisfied_link_error=必要なネイティブライブラリがないため、ゲームを実行できません。\\n不足しているネイティブライブラリ：%1$s。\\nゲーム設定でネイティブライブラリパスオプションを変更した場合は、元に戻すことをお勧めします。標準モードに切り替えます。\\nすでに標準モードになっている場合は、不足しているネイティブライブラリがmodまたはゲームに属しているかどうかを確認してください。HMCLが原因であることが確実な場合は、フィードバックをお送りください。\\nネイティブライブラリパスを本当にカスタマイズする必要がある場合は、ゲームに必要なすべてのネイティブライブラリをディレクトリに配置する必要があります。\ngame.crash.title=ゲームがクラッシュしました\ngame.directory=ゲームディレクトリ\ngame.version=ゲームバージョン\n\nhelp=ヘルプ\nhelp.doc=ドキュメント\nhelp.detail=データパック、modpackなどのメーカー向け。\n\ninput.email=ユーザー名はメールである必要があります。\ninput.number=数値である必要があります。\ninput.not_empty=必須フィールド\ninput.url=有効なURLである必要があります。\n\ninstall=新規作成\ninstall.change_version=バージョンの変更\ninstall.change_version.confirm=%s をバージョン %s から %s に更新してもよろしいですか？\ninstall.failed=バージョンのインストールに失敗しました\ninstall.failed.downloading=一部のファイルが正常にダウンロードされなかったため、インストールに失敗しました\ninstall.failed.downloading.detail=ファイルのダウンロードに失敗しました：%s\ninstall.failed.downloading.timeout=ファイルのダウンロード中にタイムアウトしました：%s\ninstall.failed.install_online=提供されたインストーラーファイルを認識できません。modをインストールする場合は、「Mods」ページに移動します。\ninstall.failed.malformed=少し前にダウンロードしたファイルの形式が正しくありません。この問題を解決するには、他のダウンロードプロバイダーに切り替えることができます。\ninstall.failed.optifine_conflict=Fabric、OptiFine、ForgeがMinecraft1.13に同時にインストールされます\ninstall.failed.optifine_forge_1.17=Minecraft 1.17.1の場合、OptiFine H1 Pre2以降のバージョンのみがForgeと互換性があり、スナップショットバージョンでインストールできます。\ninstall.failed.version_mismatch=ライブラリにはゲームバージョン %s が必要ですが、実際のバージョンは %s です。\ninstall.installer.change_version=%s 、このバージョンは現在のゲームバージョンと互換性がありません。別のものを選択するには、ここをクリックしてください。\ninstall.installer.choose=%s バージョンを選択してください\ninstall.installer.depend=%s に依存\ninstall.installer.fabric=Fabric\ninstall.installer.fabric-api=Fabric API\ninstall.installer.fabric-quilt-api.warning=%1$sはmodであり、新しいゲームのmodディレクトリにインストールされます。新しいゲームの実行ディレクトリを変更しないでください。変更すると、%1$sのインストールが失われます。その設定を変更したい場合は、%1$sを再インストールする必要があります。\ninstall.installer.forge=Forge\ninstall.installer.game=Minecraft\ninstall.installer.incompatible=%s と互換性がありません\ninstall.installer.install=インストール %s\ninstall.installer.install_offline=ファイルからのインストール/更新\ninstall.installer.install_offline.tooltip=Forge / OptiFineインストールファイルのインポートをサポートします\ninstall.installer.install_online=オンラインでインストール\ninstall.installer.install_online.tooltip=Fabric、Forge、OptiFine、LiteLoaderのインストールをサポートします。\ninstall.installer.liteloader=LiteLoader\ninstall.installer.not_installed=インストールされていません\ninstall.installer.optifine=OptiFine\ninstall.installer.quilt=Quilt\ninstall.installer.quilt-api=QSL/QFAPI\ninstall.installer.version=%s\ninstall.modpack=modpackを導入\ninstall.new_game=新規作成\ninstall.new_game.already_exists=このバージョンはすでに存在します。\ninstall.new_game.current_game_version=現在のゲームバージョン\ninstall.new_game.malformed=無効な名前\ninstall.select=操作を選択します\ninstall.success=正常にインストールされました\n\njava.add.failed=このJavaは無効であるか、現在のプラットフォームと互換性がない。\njava.disable=無効化\njava.disable.confirm=本当にこのJavaを無効にしますか？\njava.disabled.management=無効なJava\njava.disabled.management.remove=このJavaをリストから削除する\njava.disabled.management.restore=有効化\njava.download.load_list.failed=バージョンリストの読み込みに失敗しました\njava.download.more=その他のJavaディストリビューション\njava.download.prompt=ダウンロードしたいJavaのバージョンを選択してください：\njava.download.distribution=配布の種類\njava.download.version=バージョン\njava.download.packageType=パッケージの種類\njava.management=Javaの管理\njava.info.architecture=アーキテクチャ\njava.info.vendor=発行元\njava.info.version=バージョン\njava.info.disco.distribution=配布の種類\njava.install=Javaのインストール\njava.install.archive=場所\njava.install.failed.exists=この名前はすでに使用されている\njava.install.failed.invalid=このアーカイブは有効なJavaインストール・パッケージではないため、インストールできません。\njava.install.failed.unsupported_platform=このJavaは現在のプラットフォームと互換性がないため、インストールできません。\njava.install.name=名前\njava.install.warning.invalid_character=名前に不正な文字が含まれています\njava.uninstall=このJavaをアンインストールする\njava.uninstall.confirm=本当にこのJavaをアンインストールしますか？この操作は元に戻せません！\n\nlang.default=システム言語を使用する\n\nlaunch.advice.java.auto=現在選択されているJavaVMは、ゲームの要件を満たしていません。適切なJavaVMを選択することを許可しますか？または、ゲーム設定で適切なJavaVMを選択することもできます。\nlaunch.advice.java.modded_java_7=Minecraft 1.7.2以前では、Java7以前のバージョンが必要です。\nlaunch.advice.corrected=JVMの選択はすでに修正されています。選択したJavaバージョンを維持したい場合は、ゲーム設定でJavaVMチェックを無効にすることができます。\nlaunch.advice.uncorrected=ゲームを正常に開始できることが確実な場合は、ゲーム設定でJavaVMチェックを無効にすることができます。\nlaunch.advice.different_platform=OSは64ビットですが、Javaは32ビットです。64ビットJavaをお勧めします。\nlaunch.advice.forge2760_liteloader=Forge 2760以降はLiteLoaderと互換性がないため、Forgeを2773以降にアップグレードすることを検討してください。\nlaunch.advice.forge28_2_2_optifine=Forge28.2.2以降のバージョンはOptiFineと互換性がありません。Forgeを28.2.1以前のバージョンにダウングレードすることを検討してください。\nlaunch.advice.java8_1_13=Minecraft 1.13以降は、Java8以降でのみ実行できます。\nlaunch.advice.java8_51_1_13=Minecraft 1.13は、1.8.0_51より前のJava8でクラッシュする可能性があります。最新バージョンのJava8をインストールしてください。\nlaunch.advice.java9=Java9以降のバージョンのJavaでMinecraft1.12以前を起動することはできません。ゲームを高速化するには、Java8をお勧めします。\nlaunch.advice.newer_java=多くのMinecraft1.12以降、およびほとんどのModには、Java8が必要です。\nlaunch.advice.not_enough_space=割り当てたメモリが多すぎます。物理メモリサイズが%dMiBであるため、ゲームがクラッシュする可能性があります。\nlaunch.advice.too_large_memory_for_32bit=32ビットJavaランタイム環境が原因で、割り当てたメモリが多すぎるため、ゲームがクラッシュする可能性があります。32ビットシステムの最大メモリ容量は1024MiBです。\nlaunch.advice.vanilla_linux_java_8=Linux x86-64の場合、Minecraft1.12.2以下はJava8でのみ実行できます。\\nJava9以降のバージョンでは、liblwjgl.soなどの32ビットネイティブライブラリをロードできません。\nlaunch.advice.vanilla_x86.translation=Minecraftは現在、x86およびx86-64以外のアーキテクチャの公式サポートを提供していません。\\nJava for x86-64を使用して、トランスレータを介してminecraftを実行するか、プラットフォームの対応するネイティブライブラリをダウンロードして指定してくださいその配置パス。\nlaunch.failed=起動できません\nlaunch.failed.cannot_create_jvm=Java仮想マシンを作成できませんでした。Java引数は問題を引き起こす可能性があります。JVM引数なしで再起動してください。\nlaunch.failed.creating_process=プロセスの作成に失敗しました。Javaパスを確認してください。\nlaunch.failed.command_too_long=コマンドの長さが制限を超えているため、batスクリプトを作成できません。PowerShellスクリプトとしてエクスポートしてください。\nlaunch.failed.decompressing_natives=ネイティブライブラリを解凍できません。\nlaunch.failed.download_library=ライブラリ %s をダウンロードできません。\nlaunch.failed.executable_permission=起動スクリプトにアクセス許可を追加できません。\nlaunch.failed.execution_policy=実行ポリシーを設定します\nlaunch.failed.execution_policy.failed_to_set=実行ポリシーの設定に失敗しました\nlaunch.failed.execution_policy.hint=現在の実行ポリシーにより、PowerShellスクリプトの実行が禁止されています。\\n現在のユーザーがPowerShellスクリプトを実行できるようにするには、[OK]をクリックするか、現状を維持するには[キャンセル]をクリックします。\nlaunch.failed.exited_abnormally=ゲームが異常終了しました。ログを確認するか、誰かに助けを求めてください。\nlaunch.failed.no_accepted_java=現在のゲームに適したJavaインストールが見つかりません。適切なJavaVMがインストールされていると思われる場合は、ゲーム設定で手動で選択できます。\nlaunch.state.dependencies=依存関係\nlaunch.state.done=完了\nlaunch.state.java=Javaバージョンの検出\nlaunch.state.logging_in=ログイン\nlaunch.state.modpack=modpackを読み込んでいます\nlaunch.state.waiting_launching=ゲームの起動\n\nlauncher=ランチャー\nlauncher.agreement=EULA\nlauncher.agreement.accept=同意します\nlauncher.agreement.decline=同意しません\nlauncher.agreement.hint=このソフトウェアを使用するには、EULAに同意する必要があります。\nlauncher.background=背景画像\nlauncher.background.classic=クラシック\nlauncher.background.choose=背景画像ファイルを選択してください\nlauncher.background.default=標準\nlauncher.background.default.tooltip=ランチャーと同じディレクトリにある background.png/.jpg/.gif/.webp と bg フォルダから自動的に画像を取得します。\nlauncher.background.network=ネットワーク\nlauncher.cache_directory=キャッシュ用のディレクトリ\nlauncher.cache_directory.clean=クリアランス\nlauncher.cache_directory.choose=キャッシュするディレクトリを選択します\nlauncher.cache_directory.default=標準(%APPDATA%/.minecraft または ~/.minecraft)\nlauncher.cache_directory.disabled=無効(常にゲームパスを使用する)\nlauncher.cache_directory.invalid=無効なディレクトリ。デフォルト設定の復元。\nlauncher.contact=お問い合わせ\nlauncher.crash=Hello Minecraft！ランチャーがクラッシュしました！次のコンテンツをコピーして、MCBBS、Baidu Tieba、GitHub、またはMinecraftForumを介してフィードバックを送信してください。\nlauncher.crash.hmcl_out_dated=Hello Minecraft！ランチャーがクラッシュしました！ランチャーが古くなっています。ランチャーを更新してください！\nlauncher.update_java=Javaを更新してください。\n\nlogin.empty_username=ユーザー名を設定していません！\nlogin.enter_password=パスワードを入力してください。\n\nlogwindow.show_lines=行を表示\nlogwindow.terminate_game=ゲームを終了する\nlogwindow.title=ログ\nlogwindow.help=HMCL コミュニティにアクセスして、他のユーザーからのヘルプを見つけることができます\nlogwindow.autoscroll=Autoscroll\nlogwindow.export_game_crash_logs=ゲームのクラッシュ情報をエクスポートする\n\nmain_page=ホーム\n\nmessage.cancelled=操作がキャンセルされました\nmessage.confirm=確認\nmessage.copied=クリップボードにコピーされました\nmessage.default=Default\nmessage.doing=お待ちください\nmessage.downloading=ダウンロード中...\nmessage.error=エラー\nmessage.failed=操作に失敗しました\nmessage.info=Info\nmessage.success=ジョブは正常に完了しました\nmessage.unknown=不明\nmessage.warning=警告\n\nmodpack=Modpack\nmodpack.choose=modpackを選択(zip)。\nmodpack.choose.local=ローカルからmodpackファイルをインポート。\nmodpack.choose.local.detail=modpackファイルをこのページにドラッグしてインストールできます\nmodpack.choose.remote=インターネットからmodpackをインポート。\nmodpack.choose.remote.detail=リモートmodpackファイルへの直接ダウンロードリンクが必要です\nmodpack.choose.remote.tooltip=リモートmodpackファイルへの直接ダウンロードリンク\nmodpack.desc=注意事項と変更ログを含めてmodpackを説明してください。マークダウンとオンライン写真がサポートされています。\nmodpack.description=説明\nmodpack.download=Modpackをダウンロード\nmodpack.enter_name=modpackの名前を入力。\nmodpack.export=Modpackをエクスポート\nmodpack.export.as=Export Modpack As...\nmodpack.file_api=Modpackダウンロードリンクプレフィックス\nmodpack.files.blueprints=BuildCraftブループリント\nmodpack.files.config=Modの構成\nmodpack.files.dumps=NEIデバッグ出力\nmodpack.files.hmclversion_cfg=ランチャー構成ファイル\nmodpack.files.liteconfig=Mod構成ファイル\nmodpack.files.mods=Mod\nmodpack.files.mods.voxelmods=VoxelMods（VoxelMapを含む）オプション\nmodpack.files.options_txt=ゲームオプション\nmodpack.files.optionsshaders_txt=シェーダーオプション\nmodpack.files.resourcepacks=リソース/テクスチャパック\nmodpack.files.saves=保存されたマップ\nmodpack.files.scripts=MineTweaker構成\nmodpack.files.servers_dat=サーバーリスト\nmodpack.install=%s modpackをインストールします\nmodpack.installing=modpackのインストール\nmodpack.introduction= Curse, Modrinth, MultiMC, MCBBS Modpackがサポートされています。\nmodpack.invalid=無効なmodpackファイル。\nmodpack.mismatched_type=不適切なmodpackタイプ、現在のゲームは %s modpackですが、更新ファイルは %s modpackです。\nmodpack.name=Modpack名\nmodpack.not_a_valid_name=無効なmodpack名\nmodpack.origin=Origin\nmodpack.origin.url=公式ウェブサイト\nmodpack.origin.mcbbs=MCBBS\nmodpack.origin.mcbbs.prompt=スレッドID\nmodpack.scan=このmodpackをスキャンしています\nmodpack.task.install=Modpackのインポート\nmodpack.task.install.error=このmodpackファイルは認識できません。CurseおよびMultiMCmodpackのみがサポートされています。\nmodpack.type.curse=Curse\nmodpack.type.curse.error=このmodpackをインストールできません。再試行してください。\nmodpack.type.curse.not_found=必要なリソースの一部が欠落しているため、ダウンロードできませんでした。最新バージョンまたは他のmodpackを検討してください。\nmodpack.type.manual.warning=このmodpackをインポートする代わりに、おそらくこのmodpackファイルを直接解凍する必要があります。そして、バンドルされたランチャーを使用してゲームを起動します。このmodpackは、ランチャーによってエクスポートされるのではなく、.minecraftディレクトリを圧縮することによって手動で作成されます。HMCLはこのmodpackのインポートを試みることができます、続行しますか？\nmodpack.type.mcbbs=MCBBS標準\nmodpack.type.mcbbs.export=Hello Minecraftでインポートできます！ランチャー\nmodpack.type.modrinth=Modrinth\nmodpack.type.modrinth.export=メインストリームのサードパーティ製イニシエータでインポート可能\nmodpack.type.multimc=MultiMC\nmodpack.type.multimc.export=Hello Minecraftでインポートできます！ランチャーとMultiMC\nmodpack.type.server=サーバー自動更新Modpack\nmodpack.type.server.export=サーバーマネージャーがゲームクライアントをリモートで更新できるようにする\nmodpack.type.server.malformed=不正なサーバーのmodpackマニフェスト\nmodpack.unsupported=Hello Minecraft! Launcher はこのmodpackフォーマットをサポートしていません\nmodpack.update=ゲームアップデート\nmodpack.wizard=modpackウィザードのエクスポート\nmodpack.wizard.step.1=基本オプション\nmodpack.wizard.step.1.title=modpackの基本オプションを設定します。\nmodpack.wizard.step.2=ファイルを選択します\nmodpack.wizard.step.2.title=modpackにファイルを追加します。\nmodpack.wizard.step.3=Modpackタイプ\nmodpack.wizard.step.3.title=modpackの形式を選択します。\nmodpack.wizard.step.initialization.exported_version=エクスポートされたゲームバージョン\nmodpack.wizard.step.initialization.force_update=可能であればmodpackを強制的に更新します\nmodpack.wizard.step.initialization.include_launcher=ランチャーを含める\nmodpack.wizard.step.initialization.modrinth.info=モッドパック作成プロセスにおいて、CurseForge/Modrinth のリモートリソースと一致するローカルファイルを置換して容量を削減し、.disabledサフィックス付きファイルをインストール時オプションとしてマーク\nmodpack.wizard.step.initialization.no_create_remote_files=リモートファイルとの一致を行わない\nmodpack.wizard.step.initialization.save=エクスポート先...\nmodpack.wizard.step.initialization.skip_curseforge_remote_files=CurseForge リモートリソースのマッチングをスキップ\nmodpack.wizard.step.initialization.warning=modpackを作成する前に、ゲームが正常に起動できること、および\\nMinecraftがリリースバージョンであることを確認する必要があります。\\n再配布できないmodを追加しないでください。\nmodpack.wizard.step.initialization.server=サーバーの自動更新modpackの詳細については、ここをクリックしてください\n\nmodrinth.category.adventure=Adventure\nmodrinth.category.cursed=Cursed\nmodrinth.category.decoration=装飾\nmodrinth.category.equipment=機器\nmodrinth.category.fabric=Fabric\nmodrinth.category.food=Food\nmodrinth.category.library=Library\nmodrinth.category.magic=Magic\nmodrinth.category.misc=Misc\nmodrinth.category.storage=ストレージ\nmodrinth.category.technology=テクノロジー\nmodrinth.category.utility=ユーティリティ\nmodrinth.category.worldgen=Worldgen\nmodrinth.category.datapack=Datapack\nmodrinth.category.folia=Folia\n\nmods=Mods\nmods.add.failed=mods %s の追加に失敗しました。\nmods.add.success=mods %s が正常に追加されました。\nmods.category=Category\nmods.check_updates=更新を確認\nmods.check_updates.button=更新\nmods.check_updates.confirm=更新\nmods.check_updates.current_version=Current\nmods.check_updates.failed_check=更新のチェックに失敗しました\nmods.check_updates.failed_download=一部のファイルのダウンロードに失敗しました\nmods.check_updates.file=ファイル\nmods.check_updates.source=Source\nmods.check_updates.target_version=Target\nmods.curseforge=CurseForge\nmods.disable=無効にする\nmods.download=Modのダウンロード\nmods.download.title=Modダウンロード- %1s\nmods.enable=有効にする\nmods.manage=Mods\nmods.mcbbs=MCBBS\nmods.mcmod=MCMOD\nmods.mcmod.page=MCMOD\nmods.mcmod.search=MCMODで検索\nmods.modrinth=Modrinth\nmods.name=Name\nmods.not_modded=最初にmodloaderをインストールする必要があります（Fabric、Forge、またはLiteLoader）\nmods.restore=Restore\nmods.url=公式ページ\nmods.update_modpack_mod.warning=modpack 内の mod を更新すると、修復不可能な結果につながる可能性があり、modpack が破損して起動できなくなる可能性があります。更新してもよろしいですか？\n\ndatapack=Datapacks\ndatapack.title=World %s -データパック\n\nworld=マップ\nworld.add.already_exists=このマップはすでに存在しています。\nworld.add.title=インポートするzipファイルを選択してください\nworld.add.failed=このマップをインポートできません：%s\nworld.add.invalid=無効なワールドzipファイル\nworld.datapack=データパックの管理\nworld.datetime=最終ゲーム時刻：%s\nworld.delete=このマップを削除\nworld.delete.failed=マップの削除に失敗しました\\n%s\nworld.export=このマップをエクスポートする\nworld.export.title=保存する場所を選択してください\nworld.export.location=エクスポート先\nworld.export.wizard=エクスポートワールド： %s\nworld.game_version=ゲームバージョン\nworld.manage=マップ\nworld.manage.title=マップ - %s\nworld.name=マップ名\nworld.name.enter=マップ名を入力してください\nworld.show_all=すべて表示\n\nprofile=ゲームディレクトリ\nprofile.already_exists=この名前はすでに存在します。別の名前を使用してください。\nprofile.default=現在のディレクトリ\nprofile.home=標準ディレクトリ\nprofile.instance_directory=ゲームディレクトリ\nprofile.instance_directory.choose=ゲームディレクトリを選択してください\nprofile.manage=ゲームディレクトリリスト\nprofile.name=名前\nprofile.new=新規作成\nprofile.title=ゲームディレクトリ\nprofile.selected=選択済み\nprofile.use_relative_path=可能であれば、ゲームディレクトリの相対パスを使用します\n\nrepositories.custom=カスタムMavenリポジトリ（%s）\nrepositories.maven_central=Universal（Maven Central）\nrepositories.tencentcloud_mirror=中国本土（Tencent Cloud Mavenリポジトリ）\nrepositories.chooser=JavaFXがないため、HMCLが機能するにはJavaFXが必要です。\\n [OK]をクリックして指定したダウンロードソースからJavaFXランタイムコンポーネントをダウンロードしてHMCLを起動するか、[キャンセル]をクリックして終了します。\\nダウンロードソースを選択：\nrepositories.chooser.title=ダウンロードソースを選択してJavaFXをダウンロードします\n\nresourcepack=リソースパック\n\nreveal.in_file_manager=ファイルマネージャーで表示する\n\nsearch=検索\nsearch.hint.chinese=中国語と英語での検索をサポート\nsearch.hint.english=英語でのみ検索をサポート\nsearch.enter=ここに入力してください\nsearch.sort=Sort\n\nselector.choose=選択\nselector.choose_file=ファイルを選択します\nselector.custom=カスタム\n\nsettings=設定\n\nsettings.advanced=詳細設定\nsettings.advanced.title=詳細設定 - %s\nsettings.advanced.custom_commands=カスタムコマンド\nsettings.advanced.custom_commands.hint=カスタムコマンドは、次の環境変数を使用して実行されます。\\n \\\n  \\-$ INST_NAME：バージョンの名前\\n \\\n  \\-$ INST_ID：バージョンの名前\\n \\\n  \\-$ INST_DIR：バージョンの絶対パス\\n \\\n  \\-$ INST_MC_DIR：Minecraftの絶対パス\\n \\\n  \\-$ INST_JAVA：起動に使用されるJavaバイナリ\\n \\\n  \\-$ INST_FORGE：Forgeがインストールされている場合に設定\\n \\\n  \\-$ INST_NEOFORGE：NeoForgeがインストールされている場合に設定\\n \\\n  \\-$ INST_LITELOADER：LiteLoaderがインストールされている場合に設定\\n \\\n  \\-$ INST_OPTIFINE：OptiFineがインストールされている場合に設定\\n \\\n  \\-$ INST_FABRIC：ファブリックがインストールされている場合に設定\\n \\\n  \\-$ INST_QUILT：ファブリックがインストールされている場合に設定\nsettings.advanced.dont_check_game_completeness=ゲームファイルをスキャンしない\nsettings.advanced.dont_check_jvm_validity=JVMがゲームを起動できるかどうかを確認しないでください\nsettings.advanced.game_dir.default=標準（.minecraft /）\nsettings.advanced.game_dir.independent=バージョン独立（.minecraft / versions / <バージョン名> /、アセットとライブラリを除く）\nsettings.advanced.java_permanent_generation_space=PermGen Space / MiB\nsettings.advanced.java_permanent_generation_space.prompt=Java 8以降のメタスペース、MiB\nsettings.advanced.jvm=Java仮想マシンの設定\nsettings.advanced.jvm_args=JavaVM引数\nsettings.advanced.jvm_args.prompt=- 入力パラメータがデフォルトパラメータと同じである場合、追加されません\\n\\\n- GCパラメータを入力すると、デフォルトパラメータのG1パラメータが無効になります\\n\\\n- 追加時にデフォルトの引数を追加しない場合は、下の[デフォルトのJVM引数を追加しない]をクリックしてください\nsettings.advanced.launcher_visibility.close=ゲームの起動時にランチャーを閉じます。\nsettings.advanced.launcher_visibility.hide=ゲームの起動時にランチャーを非表示にします。\nsettings.advanced.launcher_visibility.hide_and_reopen=ランチャーを非表示にし、ゲームが終了したら再度開きます。\nsettings.advanced.launcher_visibility.keep=ランチャーを表示したままにします。\nsettings.advanced.launcher_visible=ランチャーの設定\nsettings.advanced.minecraft_arguments=Minecraftの引数\nsettings.advanced.minecraft_arguments.prompt=デフォルト\nsettings.advanced.natives_directory=ネイティブライブラリパス（例： LWJGL）\nsettings.advanced.natives_directory.choose=ネイティブライブラリパスを選択してください\nsettings.advanced.natives_directory.custom=Custom（ゲームが実行時に必要とするネイティブライブラリを提供する必要があります）\nsettings.advanced.natives_directory.default=Standard（ネイティブライブラリは当社が提供します）\nsettings.advanced.natives_directory.hint=このオプションは、Appleシリコンまたはその他の公式にサポートされていないプラットフォーム/アーキテクチャでゲームをプレイする場合にのみ使用されます。\\nこのオプションを変更する意味がわからない場合は、このオプションを変更しないことをお勧めします。、またはゲームが起動しません。\\nこのオプションを変更する必要がある場合は、ゲームを実行するために必要なすべてのネイティブライブラリをカスタムネイティブディレクトリの下に配置する必要があります。lwjgl.dll（liblwjgl.so）、openal.dll（libopenal.so）。HMCLは、不足しているネイティブライブラリを提供しません。\nsettings.advanced.no_jvm_args=デフォルトのJVM引数なし\nsettings.advanced.precall_command=起動前コマンド\nsettings.advanced.precall_command.prompt=ゲーム開始前に実行されます\nsettings.advanced.process_priority=プロセスの優先度\nsettings.advanced.process_priority.low=低\nsettings.advanced.process_priority.below_normal=通常以下\nsettings.advanced.process_priority.normal=中\nsettings.advanced.process_priority.above_normal=通常より上\nsettings.advanced.process_priority.high=高\nsettings.advanced.post_exit_command=終了後のコマンド\nsettings.advanced.post_exit_command.prompt=ゲーム終了後に実行されます\nsettings.advanced.renderer=レンダラー\nsettings.advanced.renderer.default=デフォルト\nsettings.advanced.renderer.default.desc=OpenGL\nsettings.advanced.renderer.d3d12=Mesa D3D12\nsettings.advanced.renderer.d3d12.desc=DirectX 12 (低パフォーマンス、低互換性)\nsettings.advanced.renderer.llvmpipe=Mesa LLVMpipe\nsettings.advanced.renderer.llvmpipe.desc=ソフトウェア (低速だが、互換性は最高)\nsettings.advanced.renderer.zink=Mesa Zink\nsettings.advanced.renderer.zink.desc=Vulkan (最高パフォーマンス、低互換性)\nsettings.advanced.server_ip=サーバーアドレス\nsettings.advanced.server_ip.prompt=ゲームの起動時にサーバーに参加する\nsettings.advanced.unsupported_system_options=サポートされていないシステムオプション\nsettings.advanced.use_native_glfw=[Linux/FreeBSDのみ]システムGLFWを使用する\nsettings.advanced.use_native_openal=[Linux/FreeBSDのみ]システムOpenALを使用する\nsettings.advanced.workaround=デバッグ用オプション\nsettings.advanced.workaround.warning=デバッグオプションはプロフェッショナルのみ使用可能です。デバッグオプションにより、ゲームが起動しない場合があります。これらのオプションは、ご自分が何をしているのかが分からない限り、変更しないでください。\nsettings.advanced.wrapper_launcher=パッキングオーダー\nsettings.advanced.wrapper_launcher.prompt=optirunが記入されている場合、起動コマンドが \"java ... \"から変更されます。を「optirun java ...」に変更しました。\n\nsettings.custom=カスタム\n\nsettings.game=ゲーム\nsettings.game.current=ゲーム\nsettings.game.dimension=ゲームウィンドウのサイズ\nsettings.game.exploration=フォルダ\nsettings.game.fullscreen=フルスクリーン\nsettings.game.java_directory=Javaディレクトリ\nsettings.game.java_directory.auto=自動的に選択されます\nsettings.game.java_directory.auto.not_found=適切なJavaがない\nsettings.game.java_directory.bit=%s ビット\nsettings.game.java_directory.choose=Javaディレクトリを選択します。\nsettings.game.java_directory.invalid=Java のパスが正しくありません。\nsettings.game.java_directory.template=%s（%s）\nsettings.game.management=管理\nsettings.game.working_directory=作業ディレクトリ\nsettings.game.working_directory.choose=作業ディレクトリを選択してください\n\nsettings.icon=ゲームアイコン\n\nsettings.launcher=設定\nsettings.launcher.appearance=外観\nsettings.launcher.common_path.tooltip=このアプリは、すべてのダウンロードをここにキャッシュします。\nsettings.launcher.debug=デバッグ\nsettings.launcher.download=ダウンロード\nsettings.launcher.download.threads=スレッド\nsettings.launcher.download.threads.auto=自動\nsettings.launcher.download.threads.hint=同時実行性が大きすぎると、システムがフリーズする可能性があります。ダウンロード速度は、ICPと宛先サーバーの影響を受ける可能性があります。\nsettings.launcher.download_source=ソースのダウンロード\nsettings.launcher.download_source.auto=ダウンロードソースの自動選択\nsettings.launcher.enable_game_list=メインページにバージョンリストを表示する\nsettings.launcher.font=フォント\nsettings.launcher.general=全般的\nsettings.launcher.language=言語\nsettings.launcher.launcher_log.export=ランチャーログのエクスポート\nsettings.launcher.launcher_log.export.failed=ログのエクスポートに失敗しました\nsettings.launcher.launcher_log.export.success=ログが %s にエクスポートされました\nsettings.launcher.launcher_log.reveal=ファイルマネージャーでログを表示する\nsettings.launcher.log=ログ\nsettings.launcher.log.font=ログフォント\nsettings.launcher.proxy=プロキシ\nsettings.launcher.proxy.authentication=プロキシ認証\nsettings.launcher.proxy.default=システムプロキシを使用する\nsettings.launcher.proxy.host=Host\nsettings.launcher.proxy.http=HTTP\nsettings.launcher.proxy.none=プロキシなし\nsettings.launcher.proxy.password=パスワード\nsettings.launcher.proxy.port=ポート\nsettings.launcher.proxy.socks=SOCKS\nsettings.launcher.proxy.username=アカウント\nsettings.launcher.theme=テーマ\nsettings.launcher.title_transparent=タイトルの透明性\nsettings.launcher.version_list_source=バージョンリストソース\n\nsettings.memory=ゲームメモリ\nsettings.memory.allocate.auto=最小 %1$.1f GiB /実際の %2$.1f GiB\nsettings.memory.allocate.auto.exceeded=最小 %1$.1f GiB /実際の %2$.1f GiB（%3$.1f GiB使用可能）\nsettings.memory.allocate.manual=割り当て %1$.1f GiB\nsettings.memory.allocate.manual.exceeded=割り当て %1$.1f GiB（%3$.1f GiB使用可能）\nsettings.memory.auto_allocate=メモリサイズの自動割り当て\nsettings.memory.lower_bound=最小メモリ\nsettings.memory.unit.mib=MiB\nsettings.memory.used_per_total=%1$.1f GiB使用済み/%2$.1fGiB合計\nsettings.physical_memory=物理メモリサイズ\nsettings.show_log=ログを表示\nsettings.tabs.installers=Installers\nsettings.take_effect_after_restart=再起動後に有効になります\nsettings.type=バージョン設定タイプ\nsettings.type.global=グローバルゲーム設定（ゲーム間で共有されるすべての設定）\nsettings.type.global.manage=グローバルゲーム設定\nsettings.type.global.edit=グローバルゲーム設定を構成します\nsettings.type.special.enable=このゲームの特殊な設定を有効にします\nsettings.type.special.edit=現在のゲーム設定を構成します\nsettings.type.special.edit.hint=現在のゲームバージョン %s で特殊な設定が有効になっているため、現在のページのオプションはそのゲームバージョンに適用されません。リンクをクリックして、現在のゲーム設定を構成します。\n\nsponsor=寄付\nsponsor.bmclapi=中国本土向けのダウンロードサービスはBMCLAPIによって提供されています。詳細については、ここをクリックしてください。\nsponsor.hmcl=Hello Minecraft! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。詳細については、ここをクリックしてください。\n\nsystem.architecture=Arch\nsystem.operating_system=OS\n\nupdate=更新\nupdate.accept=更新\nupdate.changelog=変更\nupdate.channel.dev=ベータ\nupdate.channel.dev.hint=ベータ版を使用しています。ベータ版には、リリースバージョンと比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。ベータ版は不安定なはずです！\\n\\\n  \\n\\\n  問題が発生した場合は、<a href=\"hmcl://settings/feedback\">フィードバックページ</a>にアクセスして報告してください。\nupdate.channel.dev.title=ベータ版のヒント\nupdate.channel.nightly=アルファ\nupdate.channel.nightly.hint=アルファ版を使用しています。これには、ベータ版およびリリース版と比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。アルファ版は不安定です!\\n\\\n  \\n\\\n  問題が発生した場合は、<a href=\"hmcl://settings/feedback\">フィードバックページ</a>にアクセスして報告してください。\nupdate.channel.nightly.title=アルファ版のヒント\nupdate.channel.stable=リリース\nupdate.checking=更新の確認\nupdate.failed=更新の実行に失敗しました\nupdate.found=アップデートが利用可能です！\nupdate.newest_version=最新バージョン：%s\nupdate.bubble.title=利用可能な更新：%s\nupdate.bubble.subtitle=更新するにはここをクリックしてください\nupdate.note=警告：ベータ版には、より多くの機能とバグ修正が含まれている可能性がありますが、エラーが発生する可能性もあります。\nupdate.latest=これは最新バージョンです。\nupdate.no_browser=ブラウザを開くことができません。リンクがクリップボードにコピーされました。ブラウザのアドレスバーに貼り付けて更新します。\nupdate.tooltip=更新\n\nversion=ゲーム\nversion.cannot_read=ゲームのバージョンが見つかりません。自動インストールを続行できません。\nversion.empty=ゲームなし\nversion.empty.add=起動構成を作成\nversion.empty.hint=インストールされているゲームバージョンはありません。新しいゲームをダウンロードするには、ここをクリックしてください。\nversion.game.old=Old\nversion.game.release=リリース\nversion.game.releases=リリース\nversion.game.snapshot=スナップショット\nversion.game.snapshots=Snapshots\nversion.launch=プレイ\nversion.launch.test=テストプレイ\nversion.switch=バージョンを切り替える\nversion.launch_script=起動スクリプトを作成する\nversion.launch_script.failed=起動スクリプトを作成できません。\nversion.launch_script.save=起動スクリプトを保存する\nversion.launch_script.success=作成されたスクリプト: %s。\nversion.manage=起動構成\nversion.manage.clean=ゲームディレクトリをクリアする\nversion.manage.clean.tooltip=ログのクリア、クラッシュレポート\nversion.manage.duplicate=ゲームインスタンスをコピーする\nversion.manage.duplicate.duplicate_save=コピー保存\nversion.manage.duplicate.prompt=新しいバージョン名を入力してください\nversion.manage.duplicate.confirm=\nversion.manage.manage=バージョンの管理\nversion.manage.manage.title=バージョンの管理 - %1s\nversion.manage.redownload_assets_index=ゲームアセットファイルの更新\nversion.manage.remove=このバージョンを削除します\nversion.manage.remove.confirm.trash=このバージョン %s を削除してもよろしいですか？このバージョンは、システムのゴミ箱に %s という名前で復元できます。\nversion.manage.remove_assets=すべてのゲームリソースファイルの削除\nversion.manage.remove_libraries=ライブラリファイルを削除します\nversion.manage.rename=このバージョンの名前を変更します\nversion.manage.rename.message=このバージョンの新しい名前を入力してください\nversion.manage.rename.fail=バージョンの名前を変更できませんでした。\nversion.settings=設定\nversion.update=modpackを更新する\n\nwiki.version.game=https://ja.minecraft.wiki/w/Java_Edition_%s\nwiki.version.game.snapshot=https://ja.minecraft.wiki/w/Java_Edition_%s\n\nwizard.prev=< 前へ\nwizard.failed=失敗\nwizard.finish=終了\nwizard.next=次へ >\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/lang/I18N_lzh.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Contributors: Glavo\n\nabout=有涉\nabout.copyright=持權\nabout.author=作者\nabout.author.statement=嗶站 @huanghongxun\nabout.claim=客契\nabout.claim.statement=擊鏈以詳閱\nabout.dependency=所依\nabout.legal=告以律\nabout.thanks_to=致謝\nabout.thanks_to.bangbang93.statement=鑄 BMCLAPI 焉。望君資勉旃！\nabout.thanks_to.burningtnt.statement=多助於技\nabout.thanks_to.contributors=建議、撰案之諸君\nabout.thanks_to.contributors.statement=苟无開源之社，無以至今日\nabout.thanks_to.gamerteam.statement=供本繪圖\nabout.thanks_to.glavo.statement=常理之\nabout.thanks_to.zekerzhayard.statement=多助於技\nabout.thanks_to.zkitefly.statement=理案文焉\nabout.thanks_to.mcbbs=礦藝館肆\nabout.thanks_to.mcbbs.statement=鑄礦藝館肆引源 (今廢)\nabout.thanks_to.mcim=mcmod-info-mirror\nabout.thanks_to.mcim.statement=另遞改囊之訊於本朝\nabout.thanks_to.mcmod=礦藝改囊典\nabout.thanks_to.mcmod.statement=載改囊之漢名，並書其案文焉\nabout.thanks_to.red_lnn.statement=供本繪圖\nabout.thanks_to.shulkersakura.statement=鑄其徽\nabout.thanks_to.users=HMCL 群組諸員\nabout.thanks_to.users.statement=敬謝諸君之貲勉、報謬、謀案\nabout.thanks_to.yushijinhun.statement=有助於 authlib-injector\nabout.open_source=開源\nabout.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)\n\naccount=戶簿\naccount.cape=蓑衣\naccount.character=角\naccount.choose=擇一角\naccount.create=增戶簿\naccount.create.microsoft=增微軟之戶簿\naccount.create.offline=增離綫之戶簿\naccount.create.authlibInjector=增外載之戶簿 (authlib-injector)\naccount.email=電郵\naccount.failed=弗能新戶簿\naccount.failed.character_deleted=此角除訖\naccount.failed.connect_authentication_server=不可連所證之伺服器也，容有網謬。望查械之訪抑以代待也。\\n君可求助於右上之鈕。\naccount.failed.connect_injector_server=不可連所證之伺服器也，容有網謬。望查械之訪與 URL 之入，抑以代待也。\\n君可求助於右上之鈕。\naccount.failed.injector_download_failure=無可引 authlin-injector，容有網謬。望查械之訪、試更所引之源，抑以代待也。\\n君可求助於右上之鈕。\naccount.failed.invalid_credentials=君戶簿名、符節者有謬焉，抑入蕃而爲暫禁矣。待而復試也。\naccount.failed.invalid_password=無效符節\naccount.failed.invalid_token=望試出籍而復示符節以入\naccount.failed.migration=君戶簿宜遷微軟者也，既遷則需入以後者也。\naccount.failed.no_character=此戶簿失角\naccount.failed.server_disconnected=不可訪所入伺服器。戶簿訊更敗矣。\\n\\\n  君可擇「復更戶簿」以复試；\\n\\\n  抑擇「躍更戶簿」而續啟戲也。此可異步戶簿訊焉。\\n\\\n  近無更訊，恐致戶簿訊逾期無效；\\n\\\n  微軟抑外入之戶簿啟，逾期則恐不可入需證之伺服器耳；\\n\\\n  蕃試而不成更，則可復添之，或可矛盾自解。\\n\\\n  君可求助於右上之鈕。\naccount.failed.server_response_malformed=鑒權伺服器之訊有謬。伺服器或壞。\naccount.failed.ssl=將訪伺服器而 SSL 有謬。站證或舊，抑爪哇之版舊。宜新爪哇，抑廢代而再試之。\\n君可求助於右上之鈕。\naccount.failed.dns=將訪伺服器而 SSL 有謬。或由 DNS 解析有誤也。宜更 DNS 伺服，抑用代理以訪。\\n君可求助於右上之鈕。\naccount.failed.wrong_account=入謬戶簿\naccount.hmcl.hint=子須擊「登戶簿」之紐，並登簿於所見之頁。\naccount.injector.add=增鑒權伺服器\naccount.injector.empty=無 (擊右側「+」以增)\naccount.injector.http=誡甚：伺服器之網契弗安。君符節將明遞也。\naccount.injector.link.homepage=主葉\naccount.injector.link.register=立戶\naccount.injector.server=鑒權伺服器\naccount.injector.server_name=伺服器之名\naccount.injector.server_url=伺服器之址\naccount.login=登入\naccount.login.hint=余其不存乃符節\naccount.login.skip=略新戶簿\naccount.login.retry=復重整戶簿\naccount.login.refresh=復登入\naccount.login.refresh.microsoft.hint=鑒權既廢。君須復登微軟之戶簿。\naccount.login.restricted=登微軟戶簿以啟是\naccount.logout=出籍\naccount.register=立戶\naccount.manage=戶簿表\naccount.copy_uuid=鈔其戶簿之戶碼\naccount.methods=登入之法\naccount.methods.authlib_injector=外載之戶簿\naccount.methods.microsoft=微軟之戶簿\naccount.methods.microsoft.birth=何以改戶簿之生辰？\naccount.methods.microsoft.deauthorize=除戶簿\naccount.methods.microsoft.close_page=登微軟之簿畢。啟者自行之餘。頁可閉矣。\naccount.methods.microsoft.logging_in=方登入……\naccount.methods.microsoft.makegameidsettings=添檔 / 書檔之名\naccount.methods.microsoft.profile=纂戶簿訊息\naccount.methods.microsoft.purchase=買礦藝\naccount.methods.ban_query=檢戶簿羈否\naccount.methods.microsoft.snapshot.website=官網\naccount.methods.offline=離綫之式\naccount.methods.offline.name.special_characters=諫以英文、數、底綫爲名，凡十六字\naccount.methods.offline.name.invalid=諫以英文、數、底綫爲名，凡十六字。\\n\\\n  \\n\\\n  \\  · 堪名：HuangYu、huang_Yu、Huang_Yu_123；\\n\\\n  \\  · 諱名：黄鱼、Huang Yu、Huang-Yu_%%%、Huang_Yu_hello_world_hello_world。\\n\\\n  \\n\\\n  行諱名者，天下伺服器皆難入之，甚者剋改囊，潰其戲。\naccount.methods.offline.uuid=戶碼\naccount.methods.offline.uuid.hint=戶碼者，所以別礦藝之戲者也。諸啟者之成戶碼，其法或異。正戶碼以爲舊啟者之所取，可以換啟者而戲同角、持其行囊也。撰戶碼者，置設之深者也，苟不知之，不宜妄行也。\naccount.methods.offline.uuid.malformed=範式謬矣\naccount.missing=無戲戶簿\naccount.missing.add=擊此以增戶簿\naccount.move_to_global=更爲廣戶簿\\n戶簿諸文將廣而錄於械網戶夾\naccount.move_to_portable=更爲私戶簿\\n戶簿諸文並是啟者而錄\naccount.not_logged_in=未登入\naccount.password=符節\naccount.portable=私戶簿\naccount.skin=外觀\naccount.skin.file=外觀圖案\naccount.skin.model=形\naccount.skin.model.default=寬\naccount.skin.model.slim=纖\naccount.skin.type.alex=艾麗思\naccount.skin.type.csl_api=Blessing Skin 伺服器\naccount.skin.type.csl_api.location=伺服器之址\naccount.skin.type.csl_api.location.hint=CustomSkinAPI 之址\naccount.skin.type.little_skin=LittleSkin 外觀站\naccount.skin.type.little_skin.hint=君須立角之同名者於外觀站，爰傳外觀。離綫戶簿之外觀，其用外觀站之角也。\\n君可求助於右上之鈕。\naccount.skin.type.local_file=案頭之外觀圖案\naccount.skin.type.steve=史迪武\naccount.skin.upload=匯/纂外觀\naccount.skin.upload.failed=匯之外觀而未成\naccount.skin.invalid_skin=外觀之案有謬\naccount.username=戶名\n\narchive.author=作者\narchive.date=廣佈之期\narchive.file.name=案名\narchive.version=版\n\nassets.download=引源\nassets.download_all=驗資案之備\nassets.index.malformed=資案之目有謬。或至於是例「司例」之頁，擊左下之「司 → 新戲之資案」以正之。\\n君可求助於右上之鈕。\n\nbutton.cancel=罷\nbutton.change_source=迭引源\nbutton.clear=清\nbutton.copy_and_exit=鈔而辭\nbutton.delete=刪\nbutton.edit=改\nbutton.install=置\nbutton.export=錄出\nbutton.no=否\nbutton.ok=然\nbutton.ok.countdown=然 (%d)\nbutton.refresh=重整\nbutton.remove=刪\nbutton.remove.confirm=誠刪之？刪則不復！\nbutton.retry=復試\nbutton.save=存\nbutton.save_as=另存\nbutton.select_all=悉擇之\nbutton.view=覽\nbutton.yes=然\n\ncolor.recent=薦\ncolor.custom=自定色\n\ncontact=建言\ncontact.chat=會集\ncontact.chat.discord=齟齬\ncontact.chat.discord.statement=恭迎至齟齬伺服器，且循論議之規\ncontact.chat.qq_group=HMCL 群組\ncontact.chat.qq_group.statement=恭迎至 HMCL 群組，且循論議之規\ncontact.feedback=建言之徑\ncontact.feedback.github=Github 議題\ncontact.feedback.github.statement=舉一 Github 議題\n\ncrash.NoClassDefFound=宜驗 HMCL 之案全否，抑更迭爪哇。\\n君可求助於 https://docs.hmcl.net/help.html。\ncrash.user_fault=君之械網與爪哇或有謬，是以崩。宜驗爪哇與算機。\\n君可求助於 https://docs.hmcl.net/help.html。\n\ncurse.category.0=一覽無遺\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4471\ncurse.category.4474=行空\ncurse.category.4481=改囊輕集\ncurse.category.4483=戰\ncurse.category.4477=微戲\ncurse.category.4478=任務\ncurse.category.4484=眾戲\ncurse.category.4476=尋探\ncurse.category.4736=懸島\ncurse.category.4475=求索\ncurse.category.4482=改囊大集\ncurse.category.4472=天工\ncurse.category.4473=巫法\ncurse.category.4487=FTB 改囊集\ncurse.category.4480=有異圖\ncurse.category.4479=幾艱\ncurse.category.5128=壯之先版\ncurse.category.7418=悚\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5299=師教\ncurse.category.5232=天外新界\ncurse.category.5129=壯之先版\ncurse.category.5189=實用抑 QOL\ncurse.category.5190=QOL\ncurse.category.5191=實用抑 QOL\ncurse.category.5192=幻選\ncurse.category.6145=懸島\ncurse.category.6814=效能\ncurse.category.6954=聯動/集動\ncurse.category.6484=械力\ncurse.category.6821=勘誤\ncurse.category.423=公訊\ncurse.category.426=改囊拓展\ncurse.category.434=甲胄兵器\ncurse.category.409=天成\ncurse.category.4485=血巫法\ncurse.category.420=儲\ncurse.category.429=工業\ncurse.category.419=巫法\ncurse.category.412=天工\ncurse.category.4557=赤石\ncurse.category.428=匠魂\ncurse.category.414=交通\ncurse.category.4486=天運方石\ncurse.category.432=BuildCraft\ncurse.category.418=基因\ncurse.category.4671=推扺\ncurse.category.5314=KubeJS\ncurse.category.408=礦資\ncurse.category.4773=CraftTweaker\ncurse.category.430=秘\ncurse.category.422=勇 RPG\ncurse.category.413=理械\ncurse.category.417=源\ncurse.category.415=運物\ncurse.category.433=林業\ncurse.category.425=他者\ncurse.category.4545=Applied Energistics 2\ncurse.category.416=農業\ncurse.category.421=助庫\ncurse.category.4780=緞\ncurse.category.424=飾之\ncurse.category.406=生界新創\ncurse.category.435=伺服器\ncurse.category.411=生靈\ncurse.category.407=生態域\ncurse.category.427=漲熱\ncurse.category.410=幾維\ncurse.category.436=食\ncurse.category.4558=赤石\ncurse.category.4843=自動化\ncurse.category.4906=MCreator\ncurse.category.7669=暮森\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/12\ncurse.category.5244=書體集\ncurse.category.5193=錄囊\ncurse.category.399=三清遙獄\ncurse.category.396=像素一百二十八\ncurse.category.398=像素五百一十二及以晉\ncurse.category.397=像素二百五十六\ncurse.category.405=他者\ncurse.category.395=像素六十四\ncurse.category.400=仿實\ncurse.category.393=像素十六\ncurse.category.403=傳統\ncurse.category.394=像素三十二\ncurse.category.404=動效\ncurse.category.4465=助於改囊\ncurse.category.402=中世之風 (歐羅巴)\ncurse.category.401=今世之風\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/17\ncurse.category.4464=改囊\ncurse.category.250=戰戲\ncurse.category.249=創\ncurse.category.251=猋趫\ncurse.category.253=生\ncurse.category.248=勇\ncurse.category.252=智\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4546\ncurse.category.4551=莽\ncurse.category.4548=幸運方塊\ncurse.category.4556=計日程功\ncurse.category.4752=細物\ncurse.category.4553=CraftTweaker\ncurse.category.4554=製方表\ncurse.category.4549=指引\ncurse.category.4547=置設\ncurse.category.4550=任務\ncurse.category.4555=生界新創\ncurse.category.4552=角本\n\ncurse.sort.author=作者\ncurse.sort.date_created=創立之期\ncurse.sort.last_updated=近日迭更\ncurse.sort.name=名\ncurse.sort.popularity=幾熱\ncurse.sort.total_downloads=引數\n\ndatetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss\n\ndownload=引\ndownload.hint=裝戯事、改囊集，引改囊、資囊、光影與生界\ndownload.code.404=伺服器無案曰：%s\\n君可求助於右上之鈕。\ndownload.content=戲案\ndownload.shader=光影\ndownload.curseforge.unavailable=HMCL 是版不能訪 CurseForge。宜行官版以取之。\ndownload.existing=是案既存，無可貯之。宜移案他處。\ndownload.external_link=啟引之網葉\ndownload.failed=引之未成：%1$s，\\n謬碼：%2$d\\n君可求助於右上之鈕\ndownload.failed.empty=[無可裝之版，點此返]\\n(君可求助於右上之鈕)\ndownload.failed.no_code=引之未成\ndownload.failed.refresh=[載版列未成，點此重試]\\n(君可求助於右上之鈕)\ndownload.game=新戲\ndownload.provider.bmclapi=BMCLAPI\ndownload.provider.bmclapi.desc=bangbang93，https://bmclapi2.bangbang93.com\ndownload.provider.mojang=官版\ndownload.provider.mojang.desc=Optifine 猶取諸 BMCLAPI 也\ndownload.provider.official=先官版\ndownload.provider.official.desc=至新，容有艱\ndownload.provider.balanced=先其快者\ndownload.provider.balanced.desc=均，容有舊\ndownload.provider.mirror=先別源\ndownload.provider.mirror.desc=至快，容有舊\ndownload.java=引爪哇\ndownload.java.process=引爪哇\ndownload.javafx=甫引行需諸案……\ndownload.javafx.notes=方引 HMCL 所需諸案。\\n擊「迭引源」以閲詳，并迭之。擊「罷」以止而退。\\n誡：引之允滯，宜迭其源。\ndownload.javafx.component=方引件曰「%s」\ndownload.javafx.prepare=方備其引\n\nexception.access_denied=不能訪案「%s」。HMCL 或無權以訪之，抑既見用於他者。\\n\\\n  宜驗戶簿之權，蓋非有司而不能閲他戶簿之案。\\n\\\n  視窗之下，或啟司資臺以尋用案之物，有則稍抑之，抑復啟算機而再試。\\n\\\n  凡有謬，遽求助於右上之鈕。\nexception.artifact_malformed=所引之案未能經校。\\n君可求助於右上之鈕\nexception.ssl_handshake=無築 SSL 鏈。爪哇缺證。宜改爪哇，抑制廢爾代。\\n君可求助於右上之鈕。\nexception.dns.pollution=無築 SSL 鏈。或由 DNS 污染、解析有誤也。宜更 DNS 伺服，抑用代理以訪。\\n君可求助於右上之鈕。\n\nextension.bat=視窗角本\nextension.png=圖案\nextension.ps1=PowerShell 角本\nextension.sh=Bash 角本\n\nextension.datapack=錄囊\nextension.mod=改囊案\nextension.world=生界緊囊\n\nfatal.create_hmcl_current_directory_failure=HMCL 不能立其案夾 (%s)，宜稍遷而再啟之。\\n君可求助於 https://docs.hmcl.net/help.html。\nfatal.javafx.incomplete=JavaFX 損。宜更爪哇，抑復置 OpenJFX。\nfatal.javafx.missing=JavaFX 闕。君須行 HMCL 於爪哇之俻 OpenJFX 者。\\n君可求助於 https://docs.hmcl.net/help.html。\nfatal.config_change_owner_root=君方啟 HMCL 以根戶簿，是則他戶簿或不能啟之。\\n君可求助於 https://docs.hmcl.net/help.html。\\n猶啟諸？\nfatal.config_in_temp_dir=君啟 HMCL 於暫夾也。君之置設與戲事或佚焉。抑遷而再啟。\\n君可求助於 https://docs.hmcl.net/help.html。\\n猶啟諸？\nfatal.config_loading_failure=HMCL 不能閲置設之案。\\n宜驗 HMCL 訪案夾「%s」與其諸案之權。\\n林檎之下，或宜遷 HMCL 於「桌面」「引」「文案」外有權之處，然後再試之。\\n君可求助於 https://docs.hmcl.net/help.html。\nfatal.config_loading_failure.unix=HMCL 不能閲置設之案，以之見立於戶簿「%1$s」。\\n君可啟 HMCL 以根戶簿 (弗宜)，抑行令以遷案於君之戶簿：\\n%2$s\\n君可求助于 https://docs.hmcl.net/help.html。\nfatal.mac_app_translocation=林檎爲求安，乃遷 HMCL 与暫夾中。\\n宜遷 HMCL 於他夾，然後啟之。否，則置設與戲事或佚。\\n君可以求助於 https://docs.hmcl.net/help.html。\\n猶啟諸？\nfatal.migration_requires_manual_reboot=HMCL 將晉畢，宜復啟之。\\n君可求助於 https://docs.hmcl.net/help.html。\nfatal.apply_update_failure=嗚呼！HMCL 不能自晉，以有謬焉。\\n君可自取之於 %s ，以手晉之。\\n君可求助於 https://docs.hmcl.net/help.html。\nfatal.apply_update_need_win7=HMCL 不能自晉於視窗 XP/Vista 版。宜引諸 %s 以晉之。\nfatal.deprecated_java_version=HMCL 將須爪哇十七並後者，然猶可啟戲以爪哇六至十六也。宜置至新之爪哇，以保 HMCL 之行。\\n爪哇之舊者猶可留。HMCL 識理諸爪哇，以行適者以戲事之版也。\\n君可求助於 https://docs.hmcl.net/help.html。\nfatal.deprecated_java_version.download_link=引爪哇 %d\nfatal.deprecated_java_version.update=HMCL 之晉也，將須爪哇十七與新者。宜置至新之爪哇，以晉 HMCL。\\n爪哇之舊者猶可留。HMCL 識理諸爪哇，以行適者以戲事之版也。\nfatal.samba=誠行於案夾之同享以 Samba 者，啟者或不能行。宜新爪哇，抑遷於自案。\\n君可求助於 https://docs.hmcl.net/help.html。\nfatal.illegal_char=君之案夾名有諱文「=」，是以外載之戶簿、離綫戶簿之更外觀者弗行。\\n君可求助於 https://docs.hmcl.net/help.html。\nfatal.unsupported_platform=礦藝未盡適君之算機，是以戲事或損，至於不能啟。\\n\\\n  礦藝一點一七以晉之不能啟，或於「(全例/例殊) 戲設 → 進階置設 → 勘誤置設」改「繪製之器」以「Mesa LLVMpipe」，或能兼之。\\n君可求助於右上之鈕。\nfatal.unsupported_platform.loongarch=HMCL 既適龍芯。\\n凡有謬，遽求助於右上之鈕。\nfatal.unsupported_platform.macos_arm64=HMCL 既適蘋矽。宜啟以 ARM 之爪哇，以益君之戲事。\\n誠有謬，宜啟以 x86-64 之爪哇，以益其兼。\\n凡有謬，遽求助於右上之鈕。\nfatal.unsupported_platform.windows_arm64=HMCL 既適 ARM 之視窗。誠有謬，宜啟以 x86 之爪哇。\\n誠用<b>栝柑</b>，或須置<a href=\"ms-windows-store://pdp/?productid=9NQPSL29BFFF\">開圖庫兼囊</a>，而後可戯。擊鏈以置之於微軟貨舍。\\n君可求助於右上之鈕。\n\nfile=案\n\nfolder.config=置設案夾\nfolder.game=例案夾\nfolder.logs=誌案夾\nfolder.mod=改囊案夾\nfolder.resourcepacks=資囊案夾\nfolder.saves=記事案夾\nfolder.schematics=蜃圖案夾\nfolder.screenshots=畫案夾\nfolder.shaderpacks=光影案夾\nfolder.world=生界案夾\n\ngame=戲\ngame.crash.feedback=<b>無錄是頁以示人！</b>宜擊<b>「導崩記」</b>於左下，錄以詢焉。\\n<b>「助」</b>於下者，擊而至於言社，君可詢於是也。\ngame.crash.info=戲訊\ngame.crash.reason=潰退之由\ngame.crash.reason.analyzing=方求之焉……\ngame.crash.reason.block=塊謬而崩。\\n或操輿圖纂具修檔以去之，抑徑去改囊之有干者。\\n塊類：%1$s\\n塊位：%2$s\\n\\n輿圖纂具薦：<a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">英吉利語大典</a> | <a href=\"https://zh.minecraft.wiki/w/辅助程序与编辑器/地图工具?variant=zh-tw#地图编辑工具\">漢語大典</a>\ngame.crash.reason.bootstrap_failed=改囊「%1$s」謬而崩。\\n或抑廢之。\ngame.crash.reason.mixin_apply_mod_failed=「%1$s」不能旁嵌而崩。\\n或廢之，或新之。\ngame.crash.reason.config=置設之案謬而崩。\\n改囊「%1$s」之置設「%2$s」有謬。\ngame.crash.reason.multiple=其所由也衆：\\n\\n\ngame.crash.reason.debug_crash=手崩也。\\n戲事無誤，乃君致之！\ngame.crash.reason.duplicated_mod=改囊「%1$s」復而崩。\\n%2$s\\n改囊不能再載。宜廢冗者。\ngame.crash.reason.entity=實體謬而崩。\\n或操輿圖纂具修檔以去之，抑徑去改囊之有干者。\\n實體類：%1$s\\n實體位：%2$s\\n\\n輿圖纂具薦：<a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">英吉利語大典</a> | <a href=\"https://zh.minecraft.wiki/w/辅助程序与编辑器/地图工具?variant=zh-tw#地图编辑工具\">漢語大典</a>\ngame.crash.reason.fabric_version_0_12=改囊或弗見容於緞載之〇點一二與愈晉者。宜退乎〇點一一點七。\ngame.crash.reason.fabric_warnings=緞報曰：\\n%1$s\ngame.crash.reason.file_already_exists=案「%1$s」既存而謬。\\n誠以爲可去，宜另備而去之，然後復行。\ngame.crash.reason.file_changed=案弗驗，或見篡。\\n苟如是，宜復之，抑復備戲事。\ngame.crash.reason.gl_operation_failure=改囊、明滅囊、資囊謬而崩。\\n宜抑之而復行。\ngame.crash.reason.graphics_driver=繪牒有謬而崩。可試如次：\\n\\\n  \\  · 倘兼内外之牒，或可行外牒也。<a href=\"https://www.bing.com/search?q=使用独立显卡运行Minecraft\">有閲</a>\\n\\\n  \\  · <a href=\"https://minecrafthopper.net/help/pixel-format-not-accelerated/\">新牒之驅行</a>，抑復之如初。\\n\\\n  \\  · 誠須内牒，並用算牒 Intel(R) Core(TM) 3000 抑先者，則退爪哇之於一點八點〇之五一抑先者。否則略焉。<a href=\"https://github.com/frekele/oracle-java/releases/8u51-b16\">爪哇一點八點〇之故版</a>\\n\\\n  謬復如是，宜新之繪牒，抑新之算機。\ngame.crash.reason.macos_failed_to_find_service_port_for_display=蘋矽之下不能啟開圖庫，故崩。\\nHMCL 亦無方焉。或啟一瀏覽器，歸 HMCL 而啟戲，先其建匡，<b>遽返瀏覽器</b>，俟匡之既建二歸焉。\ngame.crash.reason.illegal_access_error=或有改囊謬而崩。\\n誠識「%1$s」，或廢或新之。\ngame.crash.reason.install_mixinbootstrap=闕 MixinBootstrap 而崩。\\n宜置 <a href=\"https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap\">MixinBootstrap</a> 以正之。置而猶崩，或補嘆號「!」於改囊名前以正之。\ngame.crash.reason.need_jdk11=爪哇版謬而崩。\\n宜置<a href=\"https://bell-sw.com/pages/downloads/#downloads\">爪哇十一</a>，然後至於「(全例/例殊) 戲設 → 戲之爪哇」，而取爪哇十一點某。\ngame.crash.reason.jdk_9=爪哇版謬而崩。\\n宜置<a href=\"https://bell-sw.com/pages/downloads/#downloads\">爪哇八</a>，然後至於「(全例/例殊) 戲設 → 戲之爪哇」，而取爪哇一點八點某。\ngame.crash.reason.jvm_32bit=分憶甚益，逾三十二位爪哇之限，是以崩。\\n誠行六十四位之械網，宜更以六十四位之爪哇。<a href=\"https://bell-sw.com/pages/downloads/#downloads\">引爪哇</a>\\n誠行三十二位之械網，或更以六十四位，抑新君之算機。\\n或至於「(全例/例殊) 戲設 → 戲憶」，廢「自調其憶」，并削所分於一千零二十四兆二進字節。\ngame.crash.reason.loading_crashed_forge=改囊「%1$s (%2$s)」謬而崩。\\n或廢之，或新之。\ngame.crash.reason.loading_crashed_fabric=改囊「%1$s」謬而崩。\\n或廢之，或新之。\ngame.crash.reason.memory_exceeded=分憶甚益而崩。\\n蓋械網之葉案微也。\\n宜至於「(全例/例殊) 戲設」而廢「自調其憶」，然後削憶之所分，至於能啟。\\n或改偽憶置設以「自司葉案之小大」，<a href=\"https://docs.hmcl.net/assets/img/hmcl/自动管理所有驱动器分页文件大小.webp\">閲詳</a>。\ngame.crash.reason.mac_jdk_8u261=鍛/Optifine 弗見容于爪哇而崩。\\n宜新鍛與 Optifine，抑啟以爪哇八更二五一與舊者。\ngame.crash.reason.mod=「%1$s」謬而崩。\\n宜廢「%1$s」，抑新之，然後復試。\ngame.crash.reason.mod_resolution=改囊之恃有謬，是以崩。緞報曰：\\n%1$s\ngame.crash.reason.mod_resolution_collection=改囊之所恃版謬，是以崩。\\n「%1$s」求改囊「%2$s」以行。\\n君宜新舊之。或取「%3$s」於「引 → 改囊」，抑自取之。\ngame.crash.reason.mod_resolution_conflict=改囊互斥而崩。\\n「%1$s」與「%2$s」交斥。\ngame.crash.reason.mod_resolution_missing=所恃闕而崩。\\n「%1$s」求改囊「%2$s」以行。\\n或有改囊須而弗置，抑置而版謬。君宜取「%3$s」於「引 → 改囊」，抑自取之。\ngame.crash.reason.mod_resolution_missing_minecraft=改囊弗適礦藝之版而崩。\\n「%1$s」須礦藝之版 %2$s 而行。\\n將持是改囊，宜改礦藝於是版。將持礦藝之是版，宜改改囊之版。\ngame.crash.reason.mod_resolution_mod_version=%1$s (版號 %2$s)\ngame.crash.reason.mod_resolution_mod_version.any=%1$s (任版)\ngame.crash.reason.forge_repeat_installation=鍛復置而崩。<a href=\"https://github.com/HMCL-dev/HMCL/issues/1880\">余既知之</a>\\n宜錄君之誌於 Github，以諸吾儕之尋蹤以補是頁。\\n君可至於「司例 → 自裝」，廢而後置鍛也。\ngame.crash.reason.optifine_repeat_installation=Optifine 復置而崩。\\n宜廢改囊案夾之 Optifine，抑至於「司例 → 自裝」以廢之。\ngame.crash.reason.forgemod_resolution=改囊之恃有謬，故崩。鍛/新鍛報曰：\\n%1$s\ngame.crash.reason.forge_found_duplicate_mods=改囊之恃有謬，故崩。鍛/新鍛報曰：\\n%1$s\ngame.crash.reason.modmixin_failure=某改囊不能旁嵌而崩。\\n蓋改囊有謬，抑復兼也。\\n宜尋之於誌也。\ngame.crash.reason.night_config_fixes=Night Config 有謬而崩。\\n宜置 <a href=\"https://www.curseforge.com/minecraft/mc-mods/night-config-fixes\">Night Config Fixes</a> 之改囊，庶能正之。\\n閲詳於其 <a href=\"https://github.com/Fuzss/nightconfigfixes\">GitHub 庫</a>\ngame.crash.reason.forge_error=鍛/新鍛或報謬矣。\\n宜閲誌，亦其訊而正之。\\n誠不見謬誌，或曰謬報以求其所自。\\n%1$s\ngame.crash.reason.mod_resolution0=某改囊謬而崩。\\n宜閲誌以尋之。\ngame.crash.reason.java_version_is_too_high=爪哇版逾而崩。\\n宜至於「(全例/例殊) 戲設 → 戲之爪哇」，削爪哇之版，然後復試。\\n誠無舊版，宜取乎 <a href=\"https://www.java.com/download/\">java.com (爪哇八)</a> 與 <a href=\"https://bell-sw.com/pages/downloads/#downloads\">BellSoft Liberica Full JRE (爪哇十七)</a>。(置而須復啟君之算機。)\ngame.crash.reason.mod_name=改囊案名謬而崩。\\n改囊案名但許洋文字母 (Aa-Zz) 、數 (0-9) 、橫 (-) 、下橫 (_) 與點 (.)。\\n宜正改囊之名，以去字之弗許者。\ngame.crash.reason.incomplete_forge_installation=鍛/新鍛半置而謬。\\n宜至於「司例 → 自裝」，廢而復置之。\ngame.crash.reason.optifine_is_not_compatible_with_forge=OptiFine 悖於鍛而崩。\\n擊<a href=\"https://zkitefly.github.io/optifine-forge-support-list\">是</a>以閲 Optifine 之可納，而必置斯版也，抑改之「司例 → 自裝」。\\n嘗驗之，崩即鍛之版不均也。\ngame.crash.reason.mod_files_are_decompressed=崩即解改囊案矣。\\n宜徑措改囊案於改囊之夾。\\n解案垂謬於戲。宜廢之解，然後復啟。\ngame.crash.reason.shaders_mod=OptiFine 與 Shaders 並，故崩。\\n蓋 OptiFine 既兼 Shaders 之功用，但去 Shaders 即可。\ngame.crash.reason.rtss_forest_sodium=RivaTuner Statistics Server (RTSS) 與鈉悖而崩。\\n擊<a href=\"https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\">是</a>以閲詳。\ngame.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=改囊眾而棨信逾限，故崩。\\n宜置改囊如 <a href=\"https://www.curseforge.com/minecraft/mc-mods/jeid\">JEID</a> 者以補之，抑廢改囊之巨者。\ngame.crash.reason.optifine_causes_the_world_to_fail_to_load=或戲以 Optifine 崩。\\n且但現於 Optifine 之某版，權且改之於「司例 → 自裝」。\ngame.crash.reason.modlauncher_8=鍛與爪哇悖而崩。宜晉鍛於三六點二點二六，抑退爪哇於一點八點〇點三二〇。<a href=\"https://bell-sw.com/pages/downloads/?version=java-8&package=jre-full\">Liberica JDK 8u312+7</a>\ngame.crash.reason.no_class_def_found_error=碼殘而崩。\\n或改囊闕，或有案殘，或改囊與戲版悖。\\n或宜子復置戲與改囊，抑尋助於他。\\n所闕：\\n%1$s\ngame.crash.reason.no_such_method_error=碼殘而崩。\\n或改囊闕，或有案殘，或改囊與戲版悖。\\n或宜子復置戲與改囊，抑尋助於他。\ngame.crash.reason.opengl_not_supported=繪牒有謬而崩。\\n蓋不兼開圖庫而崩。君其遠圖、流繪於算機歟？是，宜直啟戲於元算機。\\n抑新繪牒之驅行，而後再啟。内外之牒，宜查戲之所行內外，若爲內者，宜啟 HMCL 與戲於外牒。或可行外牒也。然猶有謬，抑宜新之繪牒，抑新之算機。\ngame.crash.reason.openj9=戲弗行於 OpenJ9 之虛機。宜至於「(全例/例殊) 戲設 → 戲之爪哇」，而改以爪哇之行於 Hotspot 之虛機者，而復啟之。苟無是，宜自取之。\ngame.crash.reason.out_of_memory=憶殆而崩。\\n或所分微，或改囊眾。\\n宜至於「(全例/例殊) 戲設 → 戲憶」，改所分宜擴戲之憶。\\n誠猶謬，或宜新君之算機。\ngame.crash.reason.resolution_too_high=資囊/紋囊之圖細甚而崩。\\n宜更以圖稍粗之囊，抑繪憶巨之繪牒。\ngame.crash.reason.stacktrace=所由不明。尋誌以詳之。\\n或有可探，悉列於次，或有改囊之名也。君亦可尋於是。\\n%s\ngame.crash.reason.too_old_java=爪哇版舊而崩。\\n宜至於「(全例/例殊) 戲設 → 戲之爪哇」而改以爪哇 %1$s 與新者，而復啟之。如弗置，可擊<a href=\"https://learn.microsoft.com/java/openjdk/download\">此</a>以取微軟 JDK。\ngame.crash.reason.unknown=所由不明。尋誌以詳之。\ngame.crash.reason.unsatisfied_link_error=闕庫而崩。\\n惟曰：%1$s。\\n苟更庫徑於「(全例/例殊) 戲設 → 進階置設」，宜復如初。\\n誠弗更是，宜查戲案夾之徑，尋非西文、數、下劃綫。\\n是，宜尋缺庫之所由，其改囊乎，抑 HMCL 乎；誠爲 HMCL，宜報謬焉。\\n<b>視窗之君，或於「控制面板 → 时钟和区域 → 区域 → 管理 → 更改系统区域设置」，改「当前系统区域设置」，且廢「Beta 版：使用 Unicode UTF-8 提供全球语言支持」；</b>\\n<b>抑於戲案夾之徑，去非英文者 (或漢字、空格) 而但存英文。</b>\\n固須另置庫徑，宜自錄闕庫於是！\ngame.crash.title=不期而退\ngame.directory=戲案夾之径\ngame.version=戲例\n\nhelp=助\nhelp.detail=所以閲錄囊、改囊集之司南與他者\nhelp.doc=Hello Minecraft! Launcher 之案文\n\ninput.email=戶名須爲電郵\ninput.not_empty=必填者\ninput.number=須數也\ninput.url=須爲堪用之鏈\n\ninstall=增例\ninstall.change_version=易版\ninstall.change_version.confirm=誠將易 %s 自 %s 於 %s 乎？\ninstall.change_version.process=易版\ninstall.failed=弗能裝之\ninstall.failed.downloading=弗能裝之，有案引之不成\ninstall.failed.downloading.detail=弗能引案：%s\ninstall.failed.downloading.timeout=所引逾時：%s\ninstall.failed.install_online=弗能辨組件所將裝。誠將裝改囊，須至於司改囊之葉。\ninstall.failed.malformed=案之所引既損。君可破此難以更他引源於「置設 → 引 → 引源」。\ninstall.failed.optifine_conflict=不能並置 Optifine 與緞於礦藝一點一三與晉者。\ninstall.failed.optifine_forge_1.17=於礦藝一點一七點一，但 Optifine H1 pre2 與晉者兼於鍛也。君可取至新版於 Optifine 預版 (Preview versions)。\ninstall.failed.version_mismatch=此組件須戲版乃 %s，然實戲版乃 %s。\ninstall.installer.change_version=%s 弗兼此戲版，須易他版\ninstall.installer.choose=擇 %s 版\ninstall.installer.cleanroom=Cleanroom\ninstall.installer.depend=請先裝 %s\ninstall.installer.do_not_install=不裝\ninstall.installer.fabric=緞\ninstall.installer.fabric-api=緞界\ninstall.installer.fabric-quilt-api.warning=%1$s者，改囊也，將措乎新戲之改囊案夾也。既裝而無改戲之「行徑」。將改，而須復置之。\ninstall.installer.forge=鍛\ninstall.installer.neoforge=新鍛\ninstall.installer.game=礦藝\ninstall.installer.incompatible=與 %s 不兼也\ninstall.installer.install=裝 %s\ninstall.installer.install_offline=取私案而置\ninstall.installer.install_offline.tooltip=可錄 (新) 鍛/Cleanroom/OptiFine 裝者\ninstall.installer.install_online=網取而置\ninstall.installer.install_online.tooltip=可置鍛、新鍛、Cleanroom、緞、褥、輕載與 OptiFine\ninstall.installer.liteloader=輕載\ninstall.installer.not_installed=未裝\ninstall.installer.optifine=OptiFine\ninstall.installer.quilt=褥\ninstall.installer.quilt-api=QSL/QFAPI\ninstall.installer.version=%s\ninstall.installer.external_version=%s (外置而不能廢改)\ninstall.installing=裝\ninstall.modpack=裝改囊集\ninstall.modpack.installation=裝改囊集\ninstall.name.invalid=名有諱字 (有如繪文字與漢字)。\\n諫易他名。諫名僅含英文、數、底綫，以免謬於啟戲之時。確續裝？\ninstall.new_game=裝新戲\ninstall.new_game.already_exists=既存是例，請易其名\ninstall.new_game.current_game_version=當即戲例\ninstall.new_game.installation=裝新戲\ninstall.new_game.malformed=逾矩之名\ninstall.select=擇一裝法\ninstall.success=裝成\n\njava.add=增爪哇\njava.add.failed=爪哇謬抑弗兼此算機。\njava.disable=禁此爪哇\njava.disable.confirm=確禁此爪哇乎？\njava.disabled.management=司既禁爪哇\njava.disabled.management.remove=除此爪哇于列中\njava.disabled.management.restore=複啟此爪哇\njava.download=引爪哇\njava.download.banshanjdk-8=引 Banshan JDK 8\njava.download.load_list.failed=載版列未成\njava.download.more=他廣布版\njava.download.prompt=須擇一爪哇版以引：\njava.download.distribution=廣布版\njava.download.version=版\njava.download.packageType=裝包類\njava.management=司爪哇\njava.info.architecture=大構\njava.info.vendor=供者\njava.info.version=版\njava.info.disco.distribution=廣布版\njava.install=裝爪哇\njava.install.archive=源徑\njava.install.failed.exists=是名既佔\njava.install.failed.invalid=是緊案非爪哇之裝者，故不能裝之。\njava.install.failed.unsupported_platform=此爪哇之不兼今算機，不之裝也。\njava.install.name=名\njava.install.warning.invalid_character=名未辟諱\njava.installing=裝爪哇\njava.uninstall=卸此爪哇\njava.uninstall.confirm=誠卸之？卸則不復！\n\nlang.default=隨械網之文\n\nlaunch.advice=%s 猶啟諸？\nlaunch.advice.multi=有謬如次：\\n\\n%s\\n\\n是謬也，或阻戲之啟，抑擾戲之行。猶啟諸？\\n君可求助於右上之鈕。\nlaunch.advice.java.auto=爪哇之所選不逮戲之所求。\\n擊「然」，HMCL 將取爪哇之適者。\\n抑於「(全例/例殊) 戲設 → 戲之爪哇」擇一適者。\nlaunch.advice.java.modded_java_7=礦藝一點七點二與舊者須爪哇七與舊者。\nlaunch.advice.cleanroom=Cleanroom 但行於爪哇二一與晉者。宜行於爪哇二一與晉者。\nlaunch.advice.corrected=爪哇版謬，既正之。誠將行自取之爪哇，宜至於「(全例/例殊) 戲設 → 進階置設」，啟「弗校爪哇虛機與戲之兼否」於下。\nlaunch.advice.uncorrected=誠將行自取之爪哇，宜至於「(全例/例殊) 戲設 → 進階置設」，啟「弗校爪哇虛機與戲之兼否」於下。\nlaunch.advice.different_platform=君方啟戲以三十二位之爪哇。宜更以六十四位之爪哇。\nlaunch.advice.forge2760_liteloader=鍛一四點二三點五點二七六〇弗兼輕載。宜新鍛於一四點二三點五點二七七三與晉者。\nlaunch.advice.forge28_2_2_optifine=鍛二八點二點二與晉者弗兼 Optifine。宜退鍛於二八點二點一與舊者。\nlaunch.advice.forge37_0_60=鍛三七點〇點五九與舊者弗兼爪哇十七。宜晉鍛於三七點〇點六〇，抑啟以爪哇十六。\nlaunch.advice.java8_1_13=礦藝一點一三以晉但行於爪哇八與晉者。宜行以爪哇八與晉者。\nlaunch.advice.java8_51_1_13=爪哇之前一點八點〇之五一者或崩礦藝一點一三。宜新爪哇於一點八點〇之五一以晉，然後復啟。\nlaunch.advice.java9=礦藝之前一點一三而有改囊者弗見兼於爪哇九與晉者。宜啟以爪哇八。\nlaunch.advice.modded_java=改囊或弗見兼於爪哇之新者。宜以爪哇 %s 啟礦藝 %s。\nlaunch.advice.modlauncher8=鍛版之所行弗兼於爪哇之所行。宜新鍛。\nlaunch.advice.newer_java=余識君之啟戲以舊爪哇也，蓋改囊或是以崩戲也。宜新爪哇於八，然後復啟。\nlaunch.advice.not_enough_space=君所分之憶巨，乃逾算機之總憶 %d 兆二進字節，庶戲崩。\nlaunch.advice.require_newer_java_version=戲之是版須爪哇 %s，然 HMCL 尋而弗得。擊「然」，而 HMCL 將取之。然否？\\n凡有謬，遽求助於右上之鈕。\nlaunch.advice.too_large_memory_for_32bit=分憶甚益，逾三十二位爪哇之限，戲或以崩。宜削於一千零二十四兆二進字節。\\n凡有謬，遽求助於右上之鈕。\nlaunch.advice.vanilla_linux_java_8=於磷若 x86-64 之械網，礦藝一點一二點二以降弗容於爪哇九以晉。宜啟以爪哇八。\\n凡有謬，遽求助於右上之鈕。\nlaunch.advice.vanilla_x86.translation=礦藝未完適子之算機，或會涉君之戲感，或無能啟戲。\\n君可於<a href=\"https://learn.microsoft.com/java/openjdk/download\">此</a>引 <b>x86-64</b> 架構之爪哇以更善之。\nlaunch.advice.unknown=戲不能啟，所由如次：\nlaunch.failed=未之啟也\nlaunch.failed.cannot_create_jvm=不能立爪哇虛機。或有謬於虛機之通弦。宜至於「(全例/例殊) 戲設 → 進階置設 → 爪哇虛機置設」，廢諸通弦而再啟。\nlaunch.failed.creating_process=未之啟也。將立新進程而謬，或有謬於爪哇之徑也。\nlaunch.failed.command_too_long=令長逾限，不能立集行角本。宜錄以 PowerShell 之角本。\nlaunch.failed.decompressing_natives=戲之府庫不能解。\nlaunch.failed.download_library=戲之所恃「%s」不能引之\nlaunch.failed.executable_permission=不能予行權於啟案。\nlaunch.failed.execution_policy=置行策\nlaunch.failed.execution_policy.failed_to_set=行策不能置\nlaunch.failed.execution_policy.hint=行策禁 PowerShell 角本之行。\\n擊「然」以許是戶之行私 PowerShell 角本，抑擊「罷」以弗更。\nlaunch.failed.exited_abnormally=戲有異而退。宜閲誌案，抑詢於他者。\nlaunch.failed.java_version_too_low=爪哇版次甚。宜改爪哇之版。\nlaunch.failed.no_accepted_java=爪哇之適戲者尋而弗得。啟以常爪哇乎？擊「然」則啟之，\\n抑至於「(全例/例殊) 戲設 → 戲之爪哇」而取爪哇之宜者。\\n君可求助於右上之鈕。\nlaunch.failed.sigkill=君抑械網強止戲也。\nlaunch.state.dependencies=理所恃\nlaunch.state.done=啟畢\nlaunch.state.java=檢爪哇之版\nlaunch.state.logging_in=登入\nlaunch.state.modpack=引之要案\nlaunch.state.waiting_launching=候戲之啟\nlaunch.invalid_java=爪哇之徑謬，宜復錄之。\n\nlauncher=啟者\nlauncher.agreement=客契與免責告示\nlauncher.agreement.accept=許\nlauncher.agreement.decline=拒\nlauncher.agreement.hint=將用而須許其客契與免責告示。\nlauncher.background=繪圖\nlauncher.background.choose=擇繪圖\nlauncher.background.classic=典\nlauncher.background.default=本\nlauncher.background.default.tooltip=自尋「background.png/.jpg/.webp」之與啟者並夾者，並「bg」夾之諸圖\nlauncher.background.network=網絡\nlauncher.background.paint=正色\nlauncher.cache_directory=取案之快取夾\nlauncher.cache_directory.clean=除快取\nlauncher.cache_directory.choose=擇取案之快取夾\nlauncher.cache_directory.default=本 (「%APPDATA%/.minecraft」或「~/.minecraft」)\nlauncher.cache_directory.disabled=禁 (恒用戲案夾之徑)\nlauncher.cache_directory.invalid=自訂之快取夾不能立。既復初。\nlauncher.contact=伏惟候告\nlauncher.crash=HMCL 有謬而弗能正。宜鈔下文而報謬于右下之鈕。\nlauncher.crash.java_internal_error=HMCL 不能行，以爪哇壞也。宜去是爪哇，而擊<a href=\"https://bell-sw.com/pages/downloads/#downloads\">此</a>以置爪哇之適者。\nlauncher.crash.hmcl_out_dated=HMCL 有謬而弗能正之。余識君之啟者舊矣，宜新之。\nlauncher.update_java=宜迭更爪哇。\\n君可求助於 https://docs.hmcl.net/help.html。\n\nlibraries.download=引之所依\n\nlogin.empty_username=君未置戶名！\nlogin.enter_password=告汝符節\n\nlogwindow.show_lines=示行次\nlogwindow.terminate_game=罷戲之進程\nlogwindow.title=誌\nlogwindow.help=君自可往 HMCL 之會，求於他人也。\nlogwindow.autoscroll=自滾\nlogwindow.export_game_crash_logs=導崩記\nlogwindow.export_dump=導行棧\nlogwindow.export_dump.no_dependency=君之爪哇殆闕創行棧之所憑。蓋赴 HMCL 之群組抑齟齬，庶得援手。\n\nmain_page=主葉\n\nmessage.cancelled=所行即止\nmessage.confirm=惕\nmessage.copied=既鈔於簿\nmessage.default=本\nmessage.doing=請少俟\nmessage.downloading=方引之\nmessage.error=謬\nmessage.failed=此舉未成\nmessage.info=注\nmessage.success=畢\nmessage.unknown=未明\nmessage.warning=誡\n\nmodpack=改囊集\nmodpack.choose=擇一欲裝之改囊集案\nmodpack.choose.local=錄改囊集於私案\nmodpack.choose.local.detail=君可徑曳改囊集於此而裝\nmodpack.choose.remote=引改囊集於網絡\nmodpack.choose.remote.detail=需改囊集引之鏈\nmodpack.choose.remote.tooltip=改囊集引之鏈\nmodpack.choose.repository=引改囊集於 CurseForge/Modrinth\nmodpack.choose.repository.detail=徑至所躍頁即裝改囊集\nmodpack.completion=引改囊集之案\nmodpack.desc=述欲製改囊集，蓋諸項與誌等，可行以 Markdown。(宜鏈圖以網)。\nmodpack.description=改囊集述\nmodpack.download=引改囊集\nmodpack.download.title=引改囊集 - %1s\nmodpack.enter_name=賜戲一寵名\nmodpack.export=錄出改囊集\nmodpack.export.as=擇改囊集之類\nmodpack.file_api=改囊集所引鏈前綴\nmodpack.files.blueprints=BuildCraft 藍圖\nmodpack.files.config=改囊置設之案\nmodpack.files.dumps=NEI 勘誤出案\nmodpack.files.hmclversion_cfg=啟者置設文之案\nmodpack.files.liteconfig=輕載之案\nmodpack.files.mods=改囊\nmodpack.files.mods.voxelmods=VoxelMods 置設\nmodpack.files.options_txt=戲設\nmodpack.files.optionsshaders_txt=光影置設\nmodpack.files.resourcepacks=資囊 (紋理囊)\nmodpack.files.saves=戲之範式\nmodpack.files.scripts=MineTweaker 置設\nmodpack.files.servers_dat=眾戲伺服器之列\nmodpack.installing=裝改囊集\nmodpack.installing.given=裝 %s 改囊集\nmodpack.introduction=可用自 Curse、Modrinth、MultiMC 啟者、礦藝館肆之改囊集。\nmodpack.invalid=無效之改囊集新之案。殆引之誤也。\nmodpack.mismatched_type=不合改囊集之類。夫戲爲「%s」改囊集，顧所供改囊集之案爲「%s」改囊集。\\n君可求助於右上之鈕。\nmodpack.name=改囊集名\nmodpack.not_a_valid_name=非可堪之改囊集名\nmodpack.origin=源\nmodpack.origin.url=官網\nmodpack.origin.mcbbs=礦藝館肆\nmodpack.origin.mcbbs.prompt=礦藝館肆之章號\nmodpack.scan=析改囊集\nmodpack.task.install=錄入改囊集\nmodpack.task.install.error=未可識此改囊集，迨今所納唯 Curse、Modrinth、MultiMC、礦藝館肆諸式改囊集耳。\\n君可求助於右上之鈕。\nmodpack.type.curse=Curse\nmodpack.type.curse.error=弗畢引改囊集之所依，宜復試之抑尋代。\\n君可求助於右上之鈕。\nmodpack.type.curse.not_found=有必需之改囊既見除於網無復引也。謹試此改囊集之至新版抑他改囊集。\\n君可求助於右上之鈕。\nmodpack.type.manual.warning=是改囊集也，蓋由發布者手自綰結，或自有啟者。姑解囊試之。\\n君可求助於右上之鈕。\\n迨試以 HMCL 導入斯改囊集，庸或可乎？\nmodpack.type.mcbbs=礦藝館肆改囊集 (薦)\nmodpack.type.mcbbs.export=可導於 HMCL\nmodpack.type.modrinth=Modrinth\nmodpack.type.modrinth.export=可導於諸坊常啟者\nmodpack.type.multimc=MultiMC\nmodpack.type.multimc.export=可導於 HMCL 與 MultiMC\nmodpack.type.server=伺服器自迭改囊集\nmodpack.type.server.export=准執事遙新戲之客端\nmodpack.type.server.malformed=伺服器之改囊集置設範式含謬，或可詢伺候器有司以助。\\n君可求助於右上之鈕。\nmodpack.unsupported=HMCL 弗識是改囊集之範式\nmodpack.update=方晉改囊集\nmodpack.wizard=錄出改囊集之教章\nmodpack.wizard.step.1=基之置設\nmodpack.wizard.step.1.title=書設改囊集之訊\nmodpack.wizard.step.2=擇案\nmodpack.wizard.step.2.title=擇君欲增入改囊集之案或案夾\nmodpack.wizard.step.3=改囊集類\nmodpack.wizard.step.3.title=擇改囊集錄出之類\nmodpack.wizard.step.initialization.exported_version=將錄出之戲例\nmodpack.wizard.step.initialization.force_update=強晉改囊集至新版 (需自建伺服器)\nmodpack.wizard.step.initialization.include_launcher=納啟者於集\nmodpack.wizard.step.initialization.modrinth.info=將築改囊集，啟者將尋 CurseForge/Modrinth 之案以代私案 (有如改囊、資囊、光影) 以小諸案，並取綴以「.disabled」之案，註以「裝時可擇項」\nmodpack.wizard.step.initialization.no_create_remote_files=不擇網案代私案\nmodpack.wizard.step.initialization.save=擇導改囊集案之處\nmodpack.wizard.step.initialization.skip_curseforge_remote_files=弗尋 CurseForge 之案\nmodpack.wizard.step.initialization.warning=夫欲製改囊集者，宜驗例之可啟，\\n並行以當版、非快照，\\n並弗改囊、資囊之不能轉曳者！\\n改囊集將錄引源置設。\\n君可求助於右上之鈕。\nmodpack.wizard.step.initialization.server=擊此覽教章：製伺服器自迭改囊集\n\nmodrinth.category.adventure=勇\nmodrinth.category.atmosphere=氛\nmodrinth.category.audio=聲樂\nmodrinth.category.babric=Babric\nmodrinth.category.blocks=塊方\nmodrinth.category.bloom=汎光\nmodrinth.category.bta-babric=BTA (Babric)\nmodrinth.category.bukkit=Bukkit\nmodrinth.category.bungeecord=BungeeCord\nmodrinth.category.canvas=Canvas\nmodrinth.category.cartoon=漫\nmodrinth.category.challenging=幾艱\nmodrinth.category.colored-lighting=照以彩\nmodrinth.category.combat=戰鬥\nmodrinth.category.core-shaders=繪事樞機\nmodrinth.category.cursed=Cursed\nmodrinth.category.datapack=錄囊\nmodrinth.category.decoration=飾之\nmodrinth.category.economy=經濟\nmodrinth.category.entities=實體\nmodrinth.category.environment=環境\nmodrinth.category.equipment=裝備\nmodrinth.category.fabric=緞\nmodrinth.category.fantasy=妄\nmodrinth.category.folia=Folia\nmodrinth.category.foliage=植被\nmodrinth.category.fonts=書體\nmodrinth.category.food=食\nmodrinth.category.forge=鍛\nmodrinth.category.game-mechanics=戲之所行\nmodrinth.category.gui=介面\nmodrinth.category.high=高\nmodrinth.category.iris=Iris\nmodrinth.category.items=物\nmodrinth.category.java-agent=爪哇 Agent\nmodrinth.category.kitchen-sink=諸雜\nmodrinth.category.legacy-fabric=舊緞\nmodrinth.category.library=助庫\nmodrinth.category.lightweight=輕囊\nmodrinth.category.liteloader=輕載\nmodrinth.category.locale=歸化\nmodrinth.category.low=低\nmodrinth.category.magic=巫法\nmodrinth.category.management=司界類\nmodrinth.category.medium=中\nmodrinth.category.minecraft=礦藝\nmodrinth.category.minigame=微戲\nmodrinth.category.misc=他者\nmodrinth.category.mobs=生靈類\nmodrinth.category.modded=既改\nmodrinth.category.models=形\nmodrinth.category.modloader=Modloader\nmodrinth.category.multiplayer=眾戲\nmodrinth.category.neoforge=新鍛\nmodrinth.category.nilloader=NilLoader\nmodrinth.category.optifine=OptiFine\nmodrinth.category.optimization=優之\nmodrinth.category.ornithe=Ornithe\nmodrinth.category.paper=Paper\nmodrinth.category.path-tracing=緣蹤\nmodrinth.category.pbr=PBR\nmodrinth.category.potato=至低\nmodrinth.category.purpur=Purpur\nmodrinth.category.quests=任務\nmodrinth.category.quilt=褥\nmodrinth.category.realistic=狀實\nmodrinth.category.reflections=反射\nmodrinth.category.rift=Rift\nmodrinth.category.screenshot=至高\nmodrinth.category.semi-realistic=半狀實\nmodrinth.category.shadows=陰影\nmodrinth.category.simplistic=易\nmodrinth.category.social=交際\nmodrinth.category.spigot=Spigot\nmodrinth.category.sponge=海綿\nmodrinth.category.storage=記\nmodrinth.category.technology=天工\nmodrinth.category.themed=主題\nmodrinth.category.transportation=運\nmodrinth.category.tweaks=優之\nmodrinth.category.utility=實用\nmodrinth.category.vanilla=元生\nmodrinth.category.vanilla-like=似元生\nmodrinth.category.velocity=Velocity\nmodrinth.category.waterfall=Waterfall\nmodrinth.category.worldgen=創世\n\nmods=改囊\nmods.add=增改囊\nmods.add.failed=增改囊「%s」未成。\\n君可求助於右上之鈕。\nmods.add.success=增改囊 %s 畢。\nmods.add.title=擇改囊\nmods.broken_dependency.title=所依之壞者\nmods.broken_dependency.desc=夫改囊素存於改囊庫，今闕矣，宜易他源。\nmods.category=類\nmods.channel.alpha=預版\nmods.channel.beta=試版\nmods.channel.release=當版\nmods.check_updates=檢改囊之新\nmods.check_updates.button=檢改囊之新\nmods.check_updates.confirm=迭更\nmods.check_updates.current_version=當版\nmods.check_updates.empty=無改囊可迭更\nmods.check_updates.failed_check=檢囊迭更未成\nmods.check_updates.failed_download=有引案未成\nmods.check_updates.file=案\nmods.check_updates.source=源\nmods.check_updates.target_version=將至之版\nmods.curseforge=CurseForge\nmods.dependency.embedded=既存之相依改囊 (既以內於改囊案，無須他引)\nmods.dependency.optional=可選之相依改囊 (设若阙如，戲亦能行)\nmods.dependency.required=所需之改囊 (須他引，失之則無啟戲也)\nmods.dependency.tool=相依之庫 (須他引，失之則無啟戲也)\nmods.dependency.include=既存之相依改囊 (既以內於改囊案，無須他引)\nmods.dependency.incompatible=難兼之改囊 (並載此改囊及所引改囊將無啟戲也)\nmods.dependency.broken=既損之相依改囊 (夫改囊素存於改囊庫，今闕矣，宜易他源)\nmods.disable=禁此改囊\nmods.download=引改囊\nmods.download.title=引改囊 - %1s\nmods.download.recommend=宜版 - 礦藝 %1s\nmods.enable=啟之\nmods.game.version=戲版\nmods.manage=司改囊\nmods.mcbbs=礦藝館肆\nmods.mcmod=礦藝改囊典\nmods.mcmod.page=礦藝改囊典頁\nmods.mcmod.search=礦藝改囊典查\nmods.modrinth=Modrinth\nmods.name=名\nmods.not_modded=君須先於「自裝」頁裝鍛、新鍛、緞、褥或輕載之屬，方司改囊。\nmods.restore=復\nmods.url=改囊官網\nmods.update_modpack_mod.warning=更迭改囊集之改囊或損之，以不能啟。行而不復，誠更迭乎？\nmods.install=裝至是例\nmods.save_as=引至私案夾\n\nnbt.entries=%s 元\nnbt.open.failed=閱案未成\nnbt.save.failed=存案未成\nnbt.title=覽案 - %s\n\ndatapack=錄囊\ndatapack.add=增錄囊\ndatapack.add.title=擇須導入之錄囊緊囊\ndatapack.title=生界 [%s] - 錄囊\n\nweb.failed=載網葉未成\nweb.open_in_browser=誠啟此鏈於瀏覽器乎：\\n%s\nweb.view_in_browser=覽全誌于瀏網器\n\nworld=生界\nworld.add=增生界\nworld.add.already_exists=生界既存\nworld.add.title=擇須導入之生界緊囊\nworld.add.failed=不能錄生界：「%s」\nworld.add.invalid=無可辨此緊囊\nworld.backup=司備\nworld.backup.create.new_one=建新備\nworld.backup.create.failed=建備未成。\\n%s\nworld.backup.create.locked=是生界方見用，須閉戲而後行。\nworld.backup.create.success=新備既成：%s\nworld.backup.delete=刪是備\nworld.backup.processing=方備……\nworld.chunkbase=生界輿圖\nworld.chunkbase.end_city=終界城輿圖\nworld.chunkbase.seed_map=種圖\nworld.chunkbase.stronghold=要塞輿圖\nworld.chunkbase.nether_fortress=焱界府輿圖\nworld.datapack=司錄囊\nworld.datetime=前戲之時辰: %s\nworld.delete=刪斯生界\nworld.delete.failed=刪斯生界未成\\n%s\nworld.download=引生界\nworld.download.title=引生界 - %1s\nworld.export=錄出生界\nworld.export.title=擇是生界之存處\nworld.export.location=存至\nworld.export.wizard=錄出生界「%s」\nworld.game_version=戲版\nworld.info=生界之訊\nworld.info.basic=基訊\nworld.info.allow_cheats=允令 (舞弊)\nworld.info.dimension.the_end=終界\nworld.info.dimension.the_nether=焱界\nworld.info.difficulty=難易\nworld.info.difficulty.peaceful=和\nworld.info.difficulty.easy=易\nworld.info.difficulty.normal=凡\nworld.info.difficulty.hard=難\nworld.info.failed=閱生界之訊敗\nworld.info.game_version=版\nworld.info.last_played=前戲之時辰\nworld.info.generate_features=天工生構\nworld.info.player=戲者之訊\nworld.info.player.food_level=飢值\nworld.info.player.game_type=法\nworld.info.player.game_type.adventure=勇\nworld.info.player.game_type.creative=創\nworld.info.player.game_type.hardcore=極\nworld.info.player.game_type.spectator=覽\nworld.info.player.game_type.survival=生\nworld.info.player.health=命數\nworld.info.player.last_death_location=先考之所\nworld.info.player.location=所\nworld.info.player.spawn=床/復生錨之所\nworld.info.player.xp_level=經驗之層\nworld.info.random_seed=種\nworld.locked=見用\nworld.manage=司生界\nworld.manage.button=司生界\nworld.manage.title=司生界 - %s\nworld.name=生界之名\nworld.name.enter=書生界之名\nworld.show_all=全顯\n\nprofile=戲案夾\nprofile.already_exists=名既存\nprofile.default=當案夾\nprofile.home=官之啟者置設文之案夾\nprofile.instance_directory=戲案夾之徑\nprofile.instance_directory.choose=擇戲案夾之徑\nprofile.manage=戲案夾列\nprofile.name=名\nprofile.new=另增戲案夾\nprofile.title=戲案夾\nprofile.selected=既擇\nprofile.use_relative_path=苟能是，戲案夾用相對之徑\n\nrepositories.custom=自定 Maven 倉 (%s)\nrepositories.maven_central=天下之倉 (Maven Central)\nrepositories.tencentcloud_mirror=本朝 (騰訊云轉寫 Maven 倉儲)\nrepositories.chooser=闕 JavaFX。HMCL 須之以行。\\n擊「善」而引 JavaFX 諸案於所擇之源，而後啟 HMCL。擊「罷」而離。\\n引源：\nrepositories.chooser.title=擇 JavaFX 之源\n\nresourcepack=資囊\nresourcepack.download.title=引資囊 - %1s\n\nreveal.in_file_manager=覽於理案臺\n\nschematics=蜃圖\nschematics.add=增蜃圖\nschematics.add.failed=增蜃圖未成\nschematics.back_to=退至「%s」\nschematics.create_directory=增案夾\nschematics.create_directory.prompt=書新案夾之名\nschematics.create_directory.failed=增案夾未成\nschematics.create_directory.failed.already_exists=案夾既存\nschematics.create_directory.failed.empty_name=名不可空\nschematics.create_directory.failed.invalid_name=諱字於名也\nschematics.info.description=述\nschematics.info.enclosing_size=圍盒方寸\nschematics.info.name=名\nschematics.info.region_count=域數\nschematics.info.schematic_author=作者\nschematics.info.time_created=創之時辰\nschematics.info.time_modified=改之時辰\nschematics.info.total_blocks=總塊數\nschematics.info.total_volume=總體積\nschematics.info.version=蜃圖之版\nschematics.manage=司蜃圖\nschematics.sub_items=%d 子项\n\nsearch=尋\nsearch.hint.chinese=以中英文尋\nsearch.hint.english=惟以英文尋\nsearch.enter=鍵於是\nsearch.sort=次\nsearch.first_page=首葉\nsearch.previous_page=嚮葉\nsearch.next_page=次葉\nsearch.last_page=尾葉\nsearch.page_n=%d / %s\n\nselector.choose=擇\nselector.choose_file=擇案\nselector.custom=自定\n\nsettings=置設\n\nsettings.advanced=進階置設\nsettings.advanced.modify=篡進階置設\nsettings.advanced.title=進階置設 - %s\nsettings.advanced.custom_commands=自定命令\nsettings.advanced.custom_commands.hint=自定之令見召，当含下列环境易数：\\n\\\n  \\  · $INST_NAME：例名；\\n\\\n  \\  · $INST_ID：例名；\\n\\\n  \\  · $INST_DIR：現例運行之徑；\\n\\\n  \\  · $INST_MC_DIR：現戲案夾之徑；\\n\\\n  \\  · $INST_JAVA：戲行所用之爪哇徑；\\n\\\n  \\  · $INST_FORGE：倘置鍛，则存此易数；\\n\\\n  \\  · $INST_NEOFORGE：倘置新鍛，则存此易数；\\n\\\n  \\  · $INST_CLEANROOM：倘置 Cleanroom，则存此易数；\\n\\\n  \\  · $INST_LITELOADER：倘置輕載，则存此易数；\\n\\\n  \\  · $INST_OPTIFINE：倘置 OptiFine，则存此易数；\\n\\\n  \\  · $INST_FABRIC：倘置緞，则存此易数；\\n\\\n  \\  · $INST_QUILT：倘置褥，则存此易数。\nsettings.advanced.dont_check_game_completeness=弗驗案之備否\nsettings.advanced.dont_check_jvm_validity=弗校爪哇虛機與戲之兼否\nsettings.advanced.dont_patch_natives=弗圖替府庫\nsettings.advanced.environment_variables=環境易量\nsettings.advanced.game_dir.default=本 (「.minecraft/」)\nsettings.advanced.game_dir.independent=例獨 (於「.minecraft/versions/<例名>/」，外 assets、libraries)\nsettings.advanced.java_permanent_generation_space=恆憶之域\nsettings.advanced.java_permanent_generation_space.prompt=位計兆二進字節\nsettings.advanced.jvm=爪哇虛機置設\nsettings.advanced.jvm_args=爪哇虛機之通弦\nsettings.advanced.jvm_args.prompt=\\  · 凡「爪哇虛機之通弦」之輸與默認通弦無異，則不復添之；\\n\\\n  \\  · 凡「爪哇虛機之通弦」內輸任一 GC 通弦，則本之 G1 通弦見廢；\\n\\\n  \\  · 啟下「無添爪哇虛機之本通弦」，則戲啟而不載本通弦。\nsettings.advanced.launcher_visibility.close=始戲而閉啟者\nsettings.advanced.launcher_visibility.hide=始戲而匿啟者\nsettings.advanced.launcher_visibility.hide_and_reopen=匿啟者而復現於戲訖\nsettings.advanced.launcher_visibility.keep=恆現啟者\nsettings.advanced.launcher_visible=啟者之隱現\nsettings.advanced.minecraft_arguments=戲通弦\nsettings.advanced.minecraft_arguments.prompt=本\nsettings.advanced.natives_directory=府庫之徑 (LWJGL)\nsettings.advanced.natives_directory.choose=擇府庫之徑\nsettings.advanced.natives_directory.custom=自定 (賴君所供之府庫也)\nsettings.advanced.natives_directory.default=本 (賴啟者所供之府庫)\nsettings.advanced.natives_directory.default.version_id=<例名>\nsettings.advanced.natives_directory.hint=供於械網之弗見適者，有如蘋矽，以自定府庫也。微其之知，毋改是，改則或弗啟。\\n\\n將改，而宜錄府庫於是，蓋 lwjgl.dll (liblwjgl.so)、openal.dll (libopenal.so) 之類。啟者弗補是府庫也！\\n\\n誡：府庫之徑，但宜書以英文、數、下綫，否則或弗啟。\nsettings.advanced.no_jvm_args=無添爪哇虛機之本通弦\nsettings.advanced.precall_command=令於戲前\nsettings.advanced.precall_command.prompt=將前戲啟而行\nsettings.advanced.process_priority=進程之次\nsettings.advanced.process_priority.low=低\nsettings.advanced.process_priority.low.desc=節戲所佔，容有滯焉\nsettings.advanced.process_priority.below_normal=略低\nsettings.advanced.process_priority.below_normal.desc=節戲所佔，容有滯焉\nsettings.advanced.process_priority.normal=中\nsettings.advanced.process_priority.normal.desc=權衡\nsettings.advanced.process_priority.above_normal=略高\nsettings.advanced.process_priority.above_normal.desc=以先礦藝之作動而後餘者也\nsettings.advanced.process_priority.high=高\nsettings.advanced.process_priority.high.desc=以先礦藝之作動而後餘者也\nsettings.advanced.post_exit_command=令於戲訖\nsettings.advanced.post_exit_command.prompt=將後戲訖而行\nsettings.advanced.renderer=繪器\nsettings.advanced.renderer.default=本\nsettings.advanced.renderer.default.desc=開圖庫\nsettings.advanced.renderer.d3d12=Mesa D3D12\nsettings.advanced.renderer.d3d12.desc=DirectX 12 (效與所適皆差，勘誤之用)\nsettings.advanced.renderer.llvmpipe=Mesa LLVMpipe\nsettings.advanced.renderer.llvmpipe.desc=軟繪器 (效差，所適至)\nsettings.advanced.renderer.zink=Mesa Zink\nsettings.advanced.renderer.zink.desc=Vulkan (效至，所適差)\nsettings.advanced.server_ip=伺服器之址\nsettings.advanced.server_ip.prompt=本之址，啟訖徑入是伺服器\nsettings.advanced.unsupported_system_options=不逮今之械網所選\nsettings.advanced.use_native_glfw=[惟磷若/FreeBSD] 用 GLFW\nsettings.advanced.use_native_openal=[惟磷若/FreeBSD] 用開聲庫\nsettings.advanced.workaround=勘誤置設\nsettings.advanced.workaround.warning=勘誤置設者，蓋專者之所行也。苟妄改之，戲或弗啟。微其之知，無復改焉！\nsettings.advanced.wrapper_launcher=包裝之令\nsettings.advanced.wrapper_launcher.prompt=若書「optirun」，啟時之命將從「java ...」更爲「optirun java ...」\n\nsettings.custom=自定\n\nsettings.game=戲設\nsettings.game.copy_global=鈔以全例戲設\nsettings.game.copy_global.copy_all=悉鈔之\nsettings.game.copy_global.copy_all.confirm=君誠覆當例之置設？覆則不復！\nsettings.game.current=戲\nsettings.game.dimension=匡之幾辨\nsettings.game.exploration=瀏覽\nsettings.game.fullscreen=全幕\nsettings.game.java_directory=戲之爪哇\nsettings.game.java_directory.auto=自擇適爪哇\nsettings.game.java_directory.auto.not_found=無適之爪哇\nsettings.game.java_directory.bit=%s 位\nsettings.game.java_directory.choose=擇爪哇\nsettings.game.java_directory.invalid=爪哇徑謬\nsettings.game.java_directory.version=定爪哇之版\nsettings.game.java_directory.template=%s (%s)\nsettings.game.management=司\nsettings.game.working_directory=運徑 (改囊當行「例獨」。既改，須移戲案，蓋檔、改囊之屬)\nsettings.game.working_directory.choose=擇戯運之處\nsettings.game.working_directory.hint=夫于「運徑」擇「例獨」者，可獨貯現例之置設、檔、改囊諸文。行改囊而宜啟，庶免相淆。既選，自移其案。\n\nsettings.icon=戲徵\n\nsettings.launcher=啟者置設\nsettings.launcher.appearance=外觀\nsettings.launcher.common_path.tooltip=啟者置戲資與府庫於是。倘戲有成案，則弗取公庫之案。\nsettings.launcher.debug=勘誤\nsettings.launcher.disable_auto_game_options=弗自改戲文\nsettings.launcher.download=引\nsettings.launcher.download.threads=綫程數\nsettings.launcher.download.threads.auto=自擇綫程數\nsettings.launcher.download.threads.hint=綫程數之高也，將滯械網。君之引速制於網賈抑伺服器。其數縱高，或弗速案之引。\nsettings.launcher.download_source=引源\nsettings.launcher.download_source.auto=自擇所引源\nsettings.launcher.enable_game_list=見版列於主頁\nsettings.launcher.font=書體\nsettings.launcher.font.anti_aliasing=抗鋸\nsettings.launcher.font.anti_aliasing.auto=自調\nsettings.launcher.font.anti_aliasing.gray=灰階\nsettings.launcher.font.anti_aliasing.lcd=子像素\nsettings.launcher.general=貫用\nsettings.launcher.language=文\nsettings.launcher.launcher_log.export=錄出啟者之誌\nsettings.launcher.launcher_log.export.failed=錄誌出而未成\nsettings.launcher.launcher_log.export.success=誌既存於「%s」\nsettings.launcher.launcher_log.reveal=啟誌案夾\nsettings.launcher.log=誌\nsettings.launcher.log.font=誌之書體\nsettings.launcher.proxy=代\nsettings.launcher.proxy.authentication=驗戶\nsettings.launcher.proxy.default=以械網代\nsettings.launcher.proxy.host=主機\nsettings.launcher.proxy.http=HTTP\nsettings.launcher.proxy.none=無代\nsettings.launcher.proxy.password=符節\nsettings.launcher.proxy.port=端口\nsettings.launcher.proxy.socks=SOCKS\nsettings.launcher.proxy.username=戶簿\nsettings.launcher.theme=主題\nsettings.launcher.title_transparent=通透題欄\nsettings.launcher.turn_off_animations=廢動效\nsettings.launcher.version_list_source=版列供者\nsettings.launcher.background.settings.opacity=陰翳\n\nsettings.memory=戲憶\nsettings.memory.allocate.auto=至低分之 %1$.1f GiB / 實分之 %2$.1f GiB\nsettings.memory.allocate.auto.exceeded=至低分之 %1$.1f GiB / 實分之 %2$.1f GiB (械祇 %3$.1f GiB)\nsettings.memory.allocate.manual=戲分之 %1$.1f GiB\nsettings.memory.allocate.manual.exceeded=戲分之 %1$.1f GiB (械祇 %3$.1f GiB)\nsettings.memory.auto_allocate=自調其憶\nsettings.memory.lower_bound=至低憶界\nsettings.memory.unit.mib=兆二進字節\nsettings.memory.used_per_total=械既佔 %1$.1f GiB / 械總憶 %2$.1f GiB\nsettings.physical_memory=實憶之小大\nsettings.show_log=察誌\nsettings.tabs.installers=自裝\nsettings.take_effect_after_restart=復啟而效\nsettings.type=戲例置設之類\nsettings.type.global=全例戲設 (凡未啟「例殊戲設」之諸例共此設)\nsettings.type.global.manage=全例戲設\nsettings.type.global.edit=纂全例戲設\nsettings.type.special.enable=啟例殊戲設 (不殃余者)\nsettings.type.special.edit=纂例殊戲設\nsettings.type.special.edit.hint=是戲例「%s」既啟「例殊戲設」，是故本頁諸置設弗行於斯。擊此鏈赴斯版之「戲設」頁。\n\nshaderpack.download.title=引光影 - %1s\n\nsponsor=資勉\nsponsor.bmclapi=本朝迅傳之功在 BMCLAPI 焉。BMCLAPI 久行善業，垂衆以慷慨解囊助作者兼濟天下也。[願聞其詳]\nsponsor.hmcl=Hello Minecraft! Launcher，乃一免費，自由，開源之礦藝啟者。[願聞其詳]\n\nsystem.architecture=大構\nsystem.operating_system=械網\n\nunofficial.hint=子方用非官築之 HMCL。余無能證其安，請君自辨。\n\nupdate=啟者迭更\nupdate.accept=迭更\nupdate.changelog=迭更誌\nupdate.channel.dev=開發版\nupdate.channel.dev.hint=君所用之 HMCL 乃開發版也。開發版納試功之未行於定版者，蓋所以驗新者也。預版曾不以全驗，安能穩也！<a href=\"https://hmcl.huangyuhui.net/download\">引定版</a>\\n\\\n  \\n\\\n  詎有疑焉，置設之<a href=\"hmcl://settings/feedback\">言事葉</a>徑達天聽。垂矚 B 站賬號 <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> 以伺要機，抑矚 <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> 以察 HMCL 之功。\nupdate.channel.dev.title=開發版告\nupdate.channel.nightly=預版\nupdate.channel.nightly.hint=君所用之 HMCL 乃預版也。預版之更迭常，納試功之未行於定版與開發版者，蓋所以驗新者也。預版曾不以全驗，安能穩也！<a href=\"https://hmcl.huangyuhui.net/download\">引定版</a>\\n\\\n  \\n\\\n  詎有疑焉，置設之<a href=\"hmcl://settings/feedback\">言事葉</a>徑達天聽。垂矚 B 站賬號 <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> 以伺要機，抑矚 <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> 以察 HMCL 之功。\nupdate.channel.nightly.title=預版告\nupdate.channel.stable=定版\nupdate.checking=甫察其新\nupdate.failed=迭更未成\nupdate.found=尋得新者\nupdate.newest_version=至新：%s\nupdate.bubble.title=尋得更新：%s\nupdate.bubble.subtitle=是擊以晉\nupdate.note=开發版與預版，虽增功、修阙，然亦恐生他患。\nupdate.latest=當版至新\nupdate.no_browser=無啟瀏覽器。址固鈔訖，君可自粘之以啟頁。\nupdate.tooltip=迭更\n\nversion=戲\nversion.name=戲例之名\nversion.cannot_read=不能閲戲例，無自裝矣\nversion.empty=無游戲例\nversion.empty.add=入引頁以裝戲\nversion.empty.hint=無畢裝者。\\n君且更他戲夾，抑擊斯以往戲之引頁。\nversion.game.all=全覽\nversion.game.april_fools=愚人節\nversion.game.old=古典版\nversion.game.release=當版\nversion.game.releases=當版\nversion.game.snapshot=預版\nversion.game.snapshots=預版\nversion.game.type=版類\nversion.launch=戲\nversion.launch.empty=啟戲\nversion.launch.empty.installing=裝戲\nversion.launch.empty.tooltip=裝啟至新之當版\nversion.launch.test=試其戲\nversion.switch=迭例\nversion.launch_script=成啟戲角本\nversion.launch_script.failed=弗能成啟戲角本\nversion.launch_script.save=存啟戲角本\nversion.launch_script.success=啟戲角本既成：%s\nversion.manage=例列\nversion.manage.clean=掃戲之案夾\nversion.manage.clean.tooltip=除空「logs」與「crash-reports」案夾\nversion.manage.duplicate=鈔戲例\nversion.manage.duplicate.duplicate_save=鈔生界\nversion.manage.duplicate.prompt=書新戲之名\nversion.manage.duplicate.confirm=新戲將鈔例夾 (「.minecraft/versions/<例名>」) 之案，並另立行案、置設。\nversion.manage.manage=司例\nversion.manage.manage.title=司例 - %1s\nversion.manage.redownload_assets_index=新戲之資案\nversion.manage.remove=除是例\nversion.manage.remove.confirm.trash=誠刪例「%s」乎？君猶可復之，蓋復案夾「%s」於棄案站也。\nversion.manage.remove.confirm.independent=以戲之用「(全例/例殊) 戲設 → 運徑 → 例獨」项，除之即咸除戲之檔與數矣！君其欲除之「%s」而後快邪？\nversion.manage.remove_assets=刪戲之資案\nversion.manage.remove_libraries=率刪庫案\nversion.manage.rename=更名是例\nversion.manage.rename.message=告新名\nversion.manage.rename.fail=更名未成，或名有諱，或案見用。\nversion.search=名\nversion.search.prompt=書版名以尋\nversion.settings=戲設\nversion.update=更迭改囊集\n\nwiki.tooltip=礦藝大典之葉\nwiki.version.game=https://lzh.minecraft.wiki/w/爪哇版%s\nwiki.version.game.snapshot=https://lzh.minecraft.wiki/w/%s\n\nwizard.prev=< 嚮\nwizard.failed=未成\nwizard.finish=成矣\nwizard.next=次 >\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/lang/I18N_ru.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Contributors: vanja-san\n\nabout=О лаунчере\nabout.copyright=Авторские права\nabout.author=Автор\nabout.author.statement=bilibili @huanghongxun\nabout.claim=EULA\nabout.claim.statement=Кликните на ссылку, чтобы увидеть весь текст.\nabout.dependency=Зависимости\nabout.legal=Юридическое подтверждение\nabout.thanks_to=Отдельная благодарность\nabout.thanks_to.bangbang93.statement=За предоставление зеркала загрузки BMCLAPI, пожалуйста, подумайте о пожертвовании!\nabout.thanks_to.burningtnt.statement=Оказал большую техническую поддержку HMCL.\nabout.thanks_to.contributors=Всем участникам на GitHub\nabout.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, HMCL не смог бы добраться так далеко.\nabout.thanks_to.gamerteam.statement=За предоставление фонового изображения.\nabout.thanks_to.glavo.statement=Отвечает за поддержание HMCL.\nabout.thanks_to.zekerzhayard.statement=Оказал большую техническую поддержку HMCL.\nabout.thanks_to.zkitefly.statement=Отвечает за поддержание документации HMCL.\nabout.thanks_to.mcbbs=MCBBS (Китайский форум Minecraft)\nabout.thanks_to.mcbbs.statement=За предоставление зеркала загрузки mcbbs.net для пользователей материкового Китая. (Больше недоступно)\nabout.thanks_to.mcim=mcmod-info-mirror\nabout.thanks_to.mcim.statement=За предоставление услуги ускорения кэширования информации о модах для пользователей из материкового Китая.\nabout.thanks_to.mcmod=MCMod (mcmod.cn)\nabout.thanks_to.mcmod.statement=За предоставление упрощенного китайского перевода и вики для различных модов.\nabout.thanks_to.red_lnn.statement=За предоставление фонового изображения.\nabout.thanks_to.shulkersakura.statement=За предоставление логотипа для HMCL.\nabout.thanks_to.users=Членам группы пользователей HMCL\nabout.thanks_to.users.statement=Спасибо за пожертвования, отчёты об ошибках и так далее.\nabout.thanks_to.yushijinhun.statement=За предоставление поддержки, связанной с authlib-injector.\nabout.open_source=Открытый исходный код\nabout.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)\n\naccount=Аккаунты\naccount.cape=Плащ\naccount.character=Игрок\naccount.choose=Выберите игрока\naccount.create=Добавить аккаунт\naccount.create.microsoft=Добавить аккаунт Microsoft\naccount.create.offline=Добавить аккаунт в режиме офлайн\naccount.create.authlibInjector=Добавить аккаунт authlib-injector\naccount.email=Почта\naccount.failed=Не удалось обновить аккаунт.\naccount.failed.character_deleted=Игрок уже был удален.\naccount.failed.connect_authentication_server=Не удалось связаться с серверами авторизации, проверьте свой интернет.\naccount.failed.connect_injector_server=Не удалось подключиться к серверу авторизации. Проверьте свой интернет и убедитесь, что вы ввели правильный URL-адрес.\naccount.failed.injector_download_failure=Не удалось скачать authlib-injector. Проверьте свой интернет или попробуйте переключиться на другое зеркало скачивания.\naccount.failed.invalid_credentials=Неверный пароль или ограничена скорость, повторите попытку позже.\naccount.failed.invalid_password=Неверный пароль\naccount.failed.invalid_token=Попробуйте выйти из аккаунта и повторно войти.\naccount.failed.migration=Ваш аккаунт должен быть перенесён в Microsoft. Если вы уже сделали это, вам следует войти в свой перенесенный аккаунт Microsoft.\naccount.failed.no_character=Аккаунт не имеет персонажей.\naccount.failed.server_disconnected=Невозможно получить доступ к серверу авторизации, не удалось проверить информацию о аккаунте.\\n\\\n  Вы можете выбрать «Повторить проверку аккаунта», чтобы попытаться снова.\\n\\\n  Также можно выбрать «Пропустить проверку аккаунта», чтобы продолжить запуск игры,\\n\\\n  но информация об аккаунте может быть не актуальной. Если срок действия информации\\n\\\n  о аккаунте истечёт, будет невозможно войти на сервер, который требует подтверждения\\n\\\n  аккаунта.\naccount.failed.server_response_malformed=Неверный ответ сервера, видимо сервер авторизации не работает.\naccount.failed.ssl=При подключении к серверу произошла ошибка SSL. Пожалуйста, попробуйте обновить Java.\naccount.failed.dns=При подключении к серверу произошла ошибка SSL. Возможно, проблемы с разрешением DNS. Попробуйте сменить DNS‑сервер или воспользоваться прокси-службой.\naccount.failed.wrong_account=Вы вошли в неверный аккаунт.\naccount.hmcl.hint=Необходимо нажать на «Войти» и завершить процесс в открывшейся вкладке вашего браузера.\naccount.injector.add=Новый сервер авторизации\naccount.injector.empty=Ничего (Вы можете нажать кнопку «плюс», чтобы добавить еще)\naccount.injector.http=Предупреждение\\: Этот сервер использует небезопасный протокол HTTP, и любой игрок, находящийся между вашими соединениями, сможет увидеть ваши учётные данные в открытом виде.\naccount.injector.link.homepage=Домашняя страница\naccount.injector.link.register=Зарегистрироваться\naccount.injector.server=Сервер авторизации\naccount.injector.server_url=URL-адрес сервера\naccount.injector.server_name=Имя сервера\naccount.login=Войти\naccount.login.hint=Мы не храним ваш пароль.\naccount.login.skip=Войти в режиме офлайн\naccount.login.retry=Повторить снова\naccount.login.refresh=Войти снова\naccount.login.refresh.microsoft.hint=Вам необходимо снова войти в аккаунт Microsoft, поскольку авторизация аккаунта недействительна.\naccount.login.restricted=Войти в свою аккаунт Microsoft, чтобы включить эту функцию\naccount.logout=Выйти\naccount.register=Зарегестрироваться\naccount.manage=Список аккаунтов\naccount.copy_uuid=Копирование UUID\naccount.methods=Способ входа\naccount.methods.authlib_injector=authlib-injector\naccount.methods.microsoft=Microsoft\naccount.methods.microsoft.birth=Как изменить настройки даты рождения...\naccount.methods.microsoft.close_page=Авторизация аккаунта Microsoft завершена. Осталось выполнить некоторые шаги, которые необходимо завершить позже. Вы можете закрыть эту страницу.\naccount.methods.microsoft.deauthorize=Отменить авторизацию аккаунта\naccount.methods.microsoft.logging_in=Авторизация...\naccount.methods.microsoft.makegameidsettings=Создать / редактировать профиль\naccount.methods.microsoft.profile=Профиль аккаунта...\naccount.methods.microsoft.purchase=Купить Minecraft\naccount.methods.microsoft.snapshot.website=Официальный сайт\naccount.methods.offline=Офлайн\naccount.methods.offline.name.special_characters=Использовать только буквы, цифры и подчеркивания (максимум 16 символов)\naccount.methods.offline.name.invalid.tip=Для имени пользователя рекомендуется использовать только английские буквы, цифры и подчеркивания, а его длина не должна превышать 16 символов.\naccount.methods.offline.name.invalid=Для имени пользователя рекомендуется использовать только английские буквы, цифры и подчеркивания, а его длина не должна превышать 16 символов.\\n\\\n  \\n\\\n  \\  · Законный: HuangYu, huang_Yu, Huang_Yu_123;\\n\\\n  \\  · Незаконный: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\\n\\\n  \\n\\\n  Использование незаконного имени пользователя не позволит вам присоединиться к большинству серверов и может конфликтовать с некоторыми модами, что приведет к отказу игры.\naccount.methods.offline.uuid=UUID\naccount.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных лаунчерах игр. Изменение UUID на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр. Эта опция не требуется для присоединения к серверам.\naccount.methods.offline.uuid.malformed=Недопустимый формат.\naccount.methods.ban_query=Просмотреть аккаунт заблокирован\naccount.missing=Нет аккаунтов\naccount.missing.add=Нажмите здесь, чтобы добавить.\naccount.move_to_global=Конвертировать в глобальный аккаунт\\nИнформация об аккаунте будет сохранена в файле конфигурации в папке текущего пользователя системы.\naccount.move_to_portable=Конвертировать в портативный аккаунт\\nИнформация об аккаунте будет сохранена в файле конфигурации в той же папке, что и HMCL.\naccount.not_logged_in=Вход не произведён\naccount.password=Пароль\naccount.portable=Портативный\naccount.skin=Скин\naccount.skin.file=Файл скина\naccount.skin.model=Модель\naccount.skin.model.default=Классическая\naccount.skin.model.slim=Стройная\naccount.skin.type.alex=Алекс\naccount.skin.type.csl_api=Blessing Skin\naccount.skin.type.csl_api.location=Адрес\naccount.skin.type.csl_api.location.hint=URL-адрес CustomSkinAPI\naccount.skin.type.little_skin=LittleSkin\naccount.skin.type.little_skin.hint=Вам нужно создать игрока с тем же именем, что и у вашего офлайн-аукнта на сайте поставщика скинов. Теперь ваш скин будет установлен на скин, назначенный вашему игроку на сайте поставщика скинов.\naccount.skin.type.local_file=Локальный файл скина\naccount.skin.type.steve=Стив\naccount.skin.upload=Загрузить скин\naccount.skin.upload.failed=Не удалось загрузить скин.\naccount.skin.invalid_skin=Недопустимый файл скина.\naccount.username=Имя пользователя\n\narchive.author=Автор(ы)\narchive.date=Дата публикации\narchive.file.name=Имя файла\narchive.version=Версия\n\nassets.download=Скачивание Assets\nassets.download_all=Проверка целостности assets\nassets.index.malformed=Индексные файлы загруженных assets повреждены. Можно попробовать использовать «Обновить игровые assets» в настройках игрового экземпляра, чтобы устранить эту проблему.\n\nbutton.cancel=Отмена\nbutton.change_source=Изменить источник скачивания\nbutton.clear=Очистить\nbutton.copy_and_exit=Скопировать и выйти\nbutton.delete=Удалить\nbutton.edit=Изменить\nbutton.install=Установить\nbutton.export=Экспорт\nbutton.no=Нет\nbutton.ok=ОК\nbutton.ok.countdown=ОК (%d)\nbutton.refresh=Обновить\nbutton.remove=Убрать\nbutton.remove.confirm=Удалить навсегда? Это действие невозможно отменить!\nbutton.retry=Повторить снова\nbutton.save=Сохранить\nbutton.save_as=Сохранить как\nbutton.select_all=Выбрать все\nbutton.view=Просмотреть\nbutton.yes=Да\n\ncontact=Обратная связь\ncontact.chat=Групповой чат\ncontact.chat.discord=Discord\ncontact.chat.discord.statement=Добро пожаловать в наш Discord.\ncontact.chat.qq_group=Группа QQ пользователя HMCL\ncontact.chat.qq_group.statement=Добро пожаловать в нашу группу QQ.\ncontact.feedback=Канал обратной связи\ncontact.feedback.github=Проблемы GitHub\ncontact.feedback.github.statement=Отправить проблему на GitHub.\n\ncolor.recent=Рекомендуемые\ncolor.custom=Пользовательский цвет\n\ncrash.NoClassDefFound=Проверьте целостность этого программного обеспечения или попробуйте обновить версию Java.\ncrash.user_fault=Лаунчер аварийно завершил работу из-за повреждения Java или системной среды. Убедитесь, что Java или ОС установлены правильно.\n\ncurse.category.0=Все\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4471\ncurse.category.4474=НФ\ncurse.category.4481=Маленький / Легкий\ncurse.category.4483=Бой\ncurse.category.4477=Мини-игра\ncurse.category.4478=Квесты\ncurse.category.4484=Сетевая игра\ncurse.category.4476=Разведка\ncurse.category.4736=Скайблок\ncurse.category.4475=Приключение и РПГ\ncurse.category.4487=FTB\ncurse.category.4480=Map Based\ncurse.category.4479=Хардкор\ncurse.category.4482=Очень большой\ncurse.category.4472=Технология\ncurse.category.4473=Магия\ncurse.category.5128=Ванильное+\ncurse.category.7418=Ужас\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5299=Образование\ncurse.category.5232=Galacticraft\ncurse.category.5129=Ванильное+\ncurse.category.5189=Утилита и качество жизни\ncurse.category.5190=Качество жизни\ncurse.category.5191=Утилита и качество жизни\ncurse.category.5192=FancyMenu\ncurse.category.6814=Производительность\ncurse.category.6954=Интегрированная динамика\ncurse.category.6484=Create\ncurse.category.6821=Исправления ошибок\ncurse.category.6145=Скайблок\ncurse.category.423=Карта и сведения\ncurse.category.426=Аддоны\ncurse.category.434=Броня, инструменты и оружие\ncurse.category.409=Строения\ncurse.category.4485=Магия крови\ncurse.category.420=Хранение\ncurse.category.429=Industrial Craft\ncurse.category.419=Магия\ncurse.category.412=Технология\ncurse.category.4557=Редстоун\ncurse.category.428=Tinker's Construct\n# '\ncurse.category.414=Транспорт игрока\ncurse.category.4486=Счастливые блоки\ncurse.category.432=Buildcraft\ncurse.category.418=Генетика\ncurse.category.4671=Интеграция с Twitch\ncurse.category.5314=KubeJS\ncurse.category.408=Руды и ресурсы\ncurse.category.4773=CraftTweaker\ncurse.category.430=Thaumcraft\ncurse.category.422=Приключение и РПГ\ncurse.category.413=Обработка\ncurse.category.417=Энергия\ncurse.category.415=Энергия, жидкость и транспорт предметов\ncurse.category.433=Лесоводство\ncurse.category.425=Разное\ncurse.category.4545=Applied Energistics 2\ncurse.category.416=Земледелие\ncurse.category.421=API и библиотека\ncurse.category.4780=Fabric\ncurse.category.424=Косметика\ncurse.category.406=Генерация карты\ncurse.category.435=Утилита сервера\ncurse.category.411=Мобы\ncurse.category.407=Биомы\ncurse.category.427=Thermal Expansion\ncurse.category.410=Размеры\ncurse.category.436=Еда\ncurse.category.4558=Редстоун\ncurse.category.4843=Автоматизация\ncurse.category.4906=MCreator\ncurse.category.7669=Twilight Forest\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/12\ncurse.category.5244=Пакеты шрифтов\ncurse.category.5193=Наборы данных\ncurse.category.399=Стимпанк\ncurse.category.396=128x\ncurse.category.398=512x и выше\ncurse.category.397=256x\ncurse.category.405=Разное\ncurse.category.395=64x\ncurse.category.400=Фотореализм\ncurse.category.393=16x\ncurse.category.403=Традиционный\ncurse.category.394=32x\ncurse.category.404=Анимированный\ncurse.category.4465=Поддержка модов\ncurse.category.402=Medieval\ncurse.category.401=Современный\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/17\ncurse.category.4464=Моддинг мир\ncurse.category.250=Игровой мир\ncurse.category.249=Создание\ncurse.category.251=Паркур\ncurse.category.253=Выживание\ncurse.category.248=Приключение\ncurse.category.252=Головоломка\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4546\ncurse.category.4551=Хардкорный режим квеста\ncurse.category.4548=Счастливые блоки\ncurse.category.4556=Прогрессия\ncurse.category.4752=Building Gadgets\ncurse.category.4553=CraftTweaker\ncurse.category.4554=Рецепты\ncurse.category.4549=Путеводитель\ncurse.category.4547=Конфигурация\ncurse.category.4550=Квесты\ncurse.category.4555=Генерация карты\ncurse.category.4552=Скрипты\n\ncurse.sort.author=Автор\ncurse.sort.date_created=Создан\ncurse.sort.last_updated=Обновлён\ncurse.sort.name=Название\ncurse.sort.popularity=Популярность\ncurse.sort.total_downloads=Скачиваний\n\ndatetime.format=d MMM yyyy г., HH\\:mm\\:ss\n\ndownload=Скачать\ndownload.hint=Установить игры и модпаки или скачать моды, пакеты ресурсов, шейдеры и миры.\ndownload.code.404=Файл «%s» не найден на удаленном сервере.\ndownload.content=Аддоны\ndownload.shader=Шейдеры\ndownload.curseforge.unavailable=Эта сборка HMCL не поддерживает доступ к CurseForge. Пожалуйста, используйте официальную сборку для доступа к CurseForge.\ndownload.existing=Файл существует и по этому не может быть сохранён. Можно использовать «Сохранить как», чтобы сохранить файл в другом месте.\ndownload.external_link=Открыть сайт\ndownload.failed=Не удалось скачать «%1$s», код ответа: %2$d.\ndownload.failed.empty=Версий нет. Нажмите здесь, чтобы вернуться назад.\ndownload.failed.no_code=Не удалось скачать\ndownload.failed.refresh=Не удалось получить список версий. Нажмите здесь, чтобы повторить попытку.\ndownload.game=Новая игра\ndownload.provider.bmclapi=BMCLAPI\ndownload.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/\ndownload.provider.mojang=Официальный\ndownload.provider.mojang.desc=OptiFine предоставляется BMCLAPI\ndownload.provider.official=Из официальных источников\ndownload.provider.balanced=Из самых быстрых доступных\ndownload.provider.mirror=Из зеркала\ndownload.java=Скачивание Java\ndownload.java.process=Процесс скачивания Java\ndownload.javafx=Скачивание зависимостей лаунчера...\ndownload.javafx.notes=Скачивание зависимостей для лаунчер из Интернета.\\n\\\n  \\n\\\n  Можно нажать «Изменить источник скачивания», чтобы выбрать зеркало загрузки, или нажать «Отмена», чтобы остановить и выйти.\\n\\\n  Примечание: Если ваша скорость скачивания слишком низкая, вы можете попробовать переключиться на другое зеркало.\ndownload.javafx.component=Скачивание модуля «%s»\ndownload.javafx.prepare=Подготовка к скачиванию\ndownload.speed.byte_per_second=%d Б/сек\ndownload.speed.kibibyte_per_second=%.1f КиБ/сек\ndownload.speed.megabyte_per_second=%.1f МиБ/сек\n\nexception.access_denied=Лаунчер не может получить доступ к файлу «%s», возможно он занят другим процессом.\\n\\\n  \\n\\\n  Пользователи Windows могут открыть «Монитор ресурсов», чтобы проверить, не использует ли его в данный момент другой процесс. Если использует, вы можете повторить попытку после закрытия этого процесса.\\n\\\n  Если нет, проверьте, достаточно ли привилегий у вашего аккаунта для доступа к нему. \nexception.artifact_malformed=Не удалось проверить целостность скачаных файлов.\nexception.ssl_handshake=Не удалось установить SSL-соединение из-за отсутствия SSL-сертификатов в текущей установке Java. Вы можете попробовать запустить лаунчер в другой версии Java, а затем повторить попытку.\nexception.dns.pollution=Не удалось установить SSL‑соединение. Возможно, некорректно разрешаются DNS‑имена. Попробуйте сменить DNS‑сервер или воспользоваться прокси-службой.\n\nextension.bat=Пакетный файл Windows\nextension.png=Файл изображения\nextension.ps1=Сценарий Windows PowerShell\nextension.sh=Сценарий оболочки Bash\n\nextension.datapack=Набор данных\nextension.mod=Файл мода\nextension.world=Архив мира\n\nfatal.create_hmcl_current_directory_failure=Лаунчер не может создать папку HMCL (%s). Пожалуйста, переместите лаунчер в другое место и откройте его снова.\nfatal.javafx.incomplete=Среда JavaFX является неполной.\\n\\\n  Попробуйте заменить Java или переустановить OpenJFX.\nfatal.javafx.missing=Отсутствует среда JavaFX.\\n\\\n  Откройте Лаунчер с помощью Java, которая включает OpenJFX.\nfatal.config_change_owner_root=Вы используете пользователя root для открытия Launcher. Это может помешать вам открыть HMCL под другим пользователем в будущем.\\n\\\n  Вы все еще хотите продолжить?\nfatal.config_in_temp_dir=Вы открываете лаунчер во временной папке. Ваши настройки и игровые данные могут быть потеряны.\\n\\\n  Рекомендуется перенести HMCL в другое место и открыть его заново.\\n\\\n  Вы все еще хотите продолжить?\nfatal.config_loading_failure=Не удалось прочитать файлы конфигурации.\\n\\\n  Предоставьте лаунчеру разрешение на чтение и запись «%s» и файлам в нём.\\n\\\n  Для пользователей macOS попробуйте поместить HMCL куда-нибудь с разрешениями, отличными от «Рабочий стол», «Загрузки» и «Документы», и повторите попытку.\nfatal.config_loading_failure.unix=Программа запуска не смогла прочитать файл конфигурации, так как он был создан пользователем «%1$s».\\n\\\n  Откройте HMCL от имени пользователя root (не рекомендуется) или выполните следующую команду в терминале, чтобы изменить право собственности на конфигурационный файл для текущего пользователя:\\n%2$s\nfatal.mac_app_translocation=Лаунчер изолируется ОС во временной папке благодаря механизмам безопасности macOS.\\n\\\n  Переместите HMCL в другую папку, прежде чем пытаться открыть. В противном случае ваши настройки и игровые данные могут быть потеряны после перезапуска.\\n\\\n  Вы все еще хотите продолжить?\nfatal.migration_requires_manual_reboot=Обновление завершено. Перезапустите лаунчер.\nfatal.apply_update_failure=Мы сожалеем, лаунчер не смог завершить обновление, потому что что-то пошло не так.\\n\\\n  Вы можете обновить программу вручную, скачав более новую версию с %s.\nfatal.apply_update_need_win7=Лаунчер не может автоматически обновляться на Windows XP/Vista.\\n\\\n  Вы можете обновить программу вручную, скачав более новую версию с %s.\nfatal.deprecated_java_version=В будущем для работы HMCL потребуется Java 17 или более поздняя версия, однако запуск игр будет поддерживаться и на Java 6~16.\\n\\\n\\n\\\nРекомендуется установить последнюю версию Java, чтобы обеспечить правильную работу HMCL.\\n\\\n\\n\\\nВы можете продолжать использовать старую версию Java. HMCL может распознавать и управлять несколькими версиями Java и автоматически выбирать подходящую Java в зависимости от версии игры.\nfatal.deprecated_java_version.update=В будущем для работы HMCL потребуется Java 17 или более поздняя версия. Пожалуйста, установить последнюю версию Java, чтобы убедиться, что приложение сможет завершить обновление.\\n\\\n\\n\\\nВы можете продолжать использовать старую версию Java.\\\nHMCL может распознавать и управлять несколькими версиями Java \\\nи автоматически выбирать подходящую Java в зависимости от версии игры.\nfatal.deprecated_java_version.download_link=Скачать Java %d\nfatal.samba=Если вы пытаетесь открыть лаунчер в общей папке Samba, он может не работать, попробуйте обновить Java или запустить лаунчер из локальной папки.\nfatal.illegal_char=Недопустимый символ «=» в пути к папке пользователя. Лаунчер может работать, но некоторые функции не будут работать.\\n\\\n  Вы не сможете использовать аккаунт authlib-injector или изменить скин для аккаунта в режиме офлайн.\nfatal.unsupported_platform=Minecraft еще не полностью поддерживается на вашей платформе, поэтому вы можете столкнуться с отсутствием функций или даже не сможете запустить игру.\\n\\\n   \\n\\\n   Если вы не можете запустить Minecraft версии 1.17 и новее, попробуйте переключить «Рендерер» на «Mesa LLVMpipe» в разделе «Глобальные/настройки экземпляра → Расширенные настройки», чтобы использовать рендеринг через CPU для лучшей совместимости.\nfatal.unsupported_platform.loongarch=Лаунчер обеспечил поддержку платформы Loongson.\\n\\\n  Если у вас возникнут проблемы во время игры, вы можете обратиться за помощью на сайт https://docs.hmcl.net/groups.html.\nfatal.unsupported_platform.macos_arm64=Лаунчер обеспечил поддержку платформы Apple silicon, используя родную ARM Java для запуска игр, чтобы получить более плавный игровой опыт.\\n\\\n  Если вы столкнулись с проблемами во время игры, запуск игры с Java на базе архитектуры x86-64 может обеспечить лучшую совместимость.\nfatal.unsupported_platform.windows_arm64=Лаунчер обеспечил нативную поддержку платформы Windows на архитектуре Arm. Если у вам возникли проблемы во время игры, попробуйте запустить игру с Java на базе архитектуры x86.\\n\\\n  \\n\\\n  Если вы используете платформу <b>Qualcomm</b>, вам может потребоваться установить <a href=\"ms-windows-store://pdp/?productid=9NQPSL29BFFF\">пакет совместимости OpenGL</a> перед началом игры.\\n\\\n  Щелкните ссылку, чтобы перейти в Microsoft Store и установить пакет совместимости.\n\nfile=Файл\n\nfolder.config=Конфигурация мод\nfolder.game=Рабочие папки\nfolder.logs=Журналы\nfolder.mod=Моды\nfolder.resourcepacks=Пакеты ресурсов\nfolder.shaderpacks=Пакеты шейдеров\nfolder.saves=Миры\nfolder.schematics=Схемы\nfolder.screenshots=Снимки экрана\nfolder.world=Папка мира\n\ngame=Игры\ngame.crash.feedback=<b>Пожалуйста, не делитесь с другими скриншотами или фотографиями этого интерфейса.</b> Если вы просите помощи у других, нажмите <b>«Экспорт журналов с ошибками»</b> и отправьте экспортированный файл другим для анализа.\ngame.crash.info=Сведения об отказе\ngame.crash.reason=Анализатор сбоев\ngame.crash.reason.analyzing=Анализирование...\ngame.crash.reason.multiple=Обнаружено несколько причин:\\n\\n\ngame.crash.reason.bootstrap_failed=Игра отказала из-за мода «%1$s».\\n\\\n  \\n\\\n  Попробуйте удалить или обновить его.\ngame.crash.reason.config=Игра отказала из-за того, что мод «%1$s» не смог разобрать свой файл конфигурации «%2$s».\ngame.crash.reason.debug_crash=Игра отказала из-за того, что вы инициировали ее вручную. Так что вы, вероятно, знаете, почему :)\ngame.crash.reason.duplicated_mod=Не удалось продолжить запуск игры из-за того, что дублируется мод «%1$s».\\n\\\n  \\n\\\n  %2$s\\n\\\n  \\n\\\n  Каждый мод должен быть установлен только один, удалите дублирующий мод и попробуйте снова.\ngame.crash.reason.fabric_version_0_12=Fabric 0.12 (и выше) несовместим с установленными модами. Следует понизить версию fabric до 0.11.7.\ngame.crash.reason.mixin_apply_mod_failed=Игра отказала из-за невозможности применить mixin к моду «%1$s».\\n\\\n  \\n\\\n  Вы можете попробовать удалить или обновить этот мод, чтобы решить проблему.\ngame.crash.reason.fabric_warnings=Предупреждения от Fabric Loader:\\n\\\n  \\n\\\n  %1$s\ngame.crash.reason.modmixin_failure=Игра отказала из-за невозможности ввести некоторые моды.\\n\\\n  \\n\\\n  Обычно это означает, что мод содержит ошибку или несовместим с текущей средой.\\n\\\n  \\n\\\n  Вы можете проверить журнал на наличие неправильного мода.\ngame.crash.reason.mod_repeat_installation=Игра отказала из-за дублирования модов.\\n\\\n  \\n\\\n  Каждый мод может быть установлен только один раз. Пожалуйста, удалите дублирующий мод, а затем заново запустите игру.\ngame.crash.reason.need_jdk11=Игра отказала из-за неподходящей версии Java.\\n\\\n  \\n\\\n  Вам необходимо загрузить и установить Java 11, а также указать ее в разделе «Глобальные настройки / Раздельные настройки для сборки → Java».\ngame.crash.reason.forge_error=Возможно, Forge/NeoForge предоставили информацию об ошибке.\\n\\\n  \\n\\\n  Вы можете просмотреть журнал и выполнить соответствующие действия в соответствии с информацией журнала в отчете об ошибках.\\n\\\n  \\n\\\n  Если вы не видите сообщения об ошибке, вы можете просмотреть отчет об ошибке, чтобы узнать, как она возникла.\\n\\\n  %1$s\ngame.crash.reason.mod_resolution0=Игра отказала из-за проблем с модом. Вы можете проверить журнал на наличие неправильного мода.\ngame.crash.reason.java_version_is_too_high=Игра отказала из-за того, что версия Java слишком нова для продолжения работы.\\n\\\n  \\n\\\n  Укажите предыдущую основную версию Java в разделе «Глобальные настройки / Раздельные настройки для сборки → Java», а затем запустите игру.\\n\\\n  \\n\\\n  Если нет, вы можете загрузить его с <a href=\"https://www.java.com/download/\">java.com (Java 8)</a> или <a href=\"https://bell-sw.com/pages/downloads\">BellSoft Liberica Full JRE (Java 17)</a> и другие дистрибутивов, чтобы загрузить и установить ее (перезапустите лаунчер после установки).\ngame.crash.reason.mod_name=Игра отказала из-за проблем с именем файла мода.\\n\\\n  \\n\\\n  В имени файла мода должны использоваться только английские буквы (A~Z, a~z), цифры (0~9), дефисы (-), подчеркивания (_) и точки (.) в половину ширины.\\n\\\n  \\n\\\n  Пожалуйста, откройте папку «mods» и измените имена всех несовместимых модов на совместимые символы, указанные выше.\ngame.crash.reason.incomplete_forge_installation=Игра не может быть запущена из-за неполной установки Forge/NeoForge.\\n\\\n  \\n\\\n  Переустановите Forge/NeoForge в разделе «Изменить сборку → Погрузчики».\ngame.crash.reason.file_already_exists=Игра отказала из-за того, что файл «%1$s» уже существует.\\n\\\n  \\n\\\n  Вы можете попробовать сделать резервную копию и удалить этот файл, а затем заново запустить игру.\ngame.crash.reason.file_changed=Игра отказала из-за сбоя проверки файла.\\n\\\n  \\n\\\n  Если вы изменили файл Minecraft.jar, вам придется вернуть модификацию или заново скачать игру.\ngame.crash.reason.gl_operation_failure=Игра отказала из-за некоторых модов, пакетов шейдеров/ресурсов.\\n\\\n  \\n\\\n  Отключите используемые моды, пакеты ресурсов/шейдеров и попробуйте снова.\ngame.crash.reason.graphics_driver=Игра отказала из-за проблемы с графическим драйвером. Обновите графический драйвер до последней версии.\\n\\\n  \\n\\\n  Если в вашем компьютере установлена дискретная видеокарта, необходимо проверить, использует ли игра интегрированную/ядер графику. Если это так, запустите HMCL и игру с использованием дискретной видеокарты. Если у вас по-прежнему возникают подобные проблемы, вы можете подумать о приобретении новой видеокарты или нового компьютера.\\n\\\n  \\n\\\n  Если вы хотите продолжать использовать интегрированную видеокарту, обратите внимание, что для Minecraft 1.16.5 или более ранней версии требуется <a href=\"https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html\">Java 1.8.0_51</a> или более ранняя версия для процессоров серии Intel(R) Core(TM) 3000 или более ранних.\ngame.crash.reason.macos_failed_to_find_service_port_for_display=Игра не может быть запущена из-за сбоя инициализации окна OpenGL на платформе Apple silicon.\\n\\\n  \\n\\\n  Для этой проблемы у HMCL временно нет прямых решений. Попробуйте открыть любой браузер и перейти в полноэкранный режим, затем вернуться в HMCL, запустить игру и <b>быстро вернуться на страницу браузера</b> перед появлением игрового окна, дождаться появления игрового окна, а затем вернуться в игровое окно.\ngame.crash.reason.illegal_access_error=Игра отказала из-за некоторых модов.\\n\\\n  \\n\\\n  Если вы знаете это: «%1$s», вы можете обновить или удалить мод(ы) и повторить попытку.\ngame.crash.reason.install_mixinbootstrap=Игра отказала из-за отсутствия MixinBootstrap.\\n\\\n  \\n\\\n  Для решения проблемы вы можете попробовать установить <a href=\"https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap\">MixinBootstrap</a>. Если игра отказала после установки, попробуйте добавить восклицательный знак (!) перед именем файла этого мода, чтобы попытаться решить проблему.\ngame.crash.reason.jdk_9=Игра отказала из-за того, что версия Java слишком новая для этой сборки.\\n\\\n  \\n\\\n  Вам необходимо загрузить и установить Java 8 и указать ее в разделе «Глобальные настройки / Раздельные настройки для сборки → Java».\ngame.crash.reason.jvm_32bit=Игра отказала из-за того, что текущее распределение памяти превысило 32-битный лимит JVM.\\n\\\n  \\n\\\n  Если ваша ОС 64-разрядная, установите и используйте 64-разрядная Java. Если ваша ОС 32-разрядная, вам может потребоваться переустановить 64-разрядную ОС или приобрести более современный компьютер.\\n\\\n  \\n\\\n  Или вы можете отключить опцию «Автовыделение» в разделе «Глобальные настройки / Раздельные настройки для сборки → Оперативная память» и указать максимальный размер выделяемой памяти 1024 МиБ или меньше.\ngame.crash.reason.loading_crashed_forge=Игра отказала из-за мода «%1$s» (%2$s).\\n\\\n  \\n\\\n  Вы можете попробовать удалить или обновить его.\ngame.crash.reason.loading_crashed_fabric=Игра отказала из-за мода «%1$s».\\n\\\n  \\n\\\n  Вы можете попробовать удалить или обновить его.\ngame.crash.reason.memory_exceeded=Игра отказала из-за того, что для маленького файла подкачки было выделено слишком много памяти.\\n\\\n  \\n\\\n  Вы можете попробовать отключить опцию «Автовыделение» в разделе «Глобальные настройки / Раздельные настройки для сборки → Оперативная память» и регулировать значение до тех пор, пока игра не запустится.\\n\\\n  \\n\\\n  Вы также можете попробовать увеличить размер файла подкачки в системных настройках.\ngame.crash.reason.mod=Игра отказала из-за мода «%1$s»\\n\\\n  \\n\\\n  Вы можете обновить или удалить мод и повторить попытку.\ngame.crash.reason.mod_resolution=Игра не может быть запущена из-за проблем с зависимостями модов.\\n\\\n  \\n\\\n  Fabric предоставила следующую информацию:\\n\\\n  \\n\\\n  %1$s\ngame.crash.reason.forgemod_resolution=Игра не может быть запущена из-за проблем с зависимостями модов.\\n\\\n  \\n\\\n  Forge/NeoForge предоставила следующую информацию:\\n\\\n  \\n\\\n  %1$s\ngame.crash.reason.forge_found_duplicate_mods=Игра отказала из-за проблемы с дублированием модов. Forge/NeoForge предоставила следующую информацию:\\n\\n%1$s\ngame.crash.reason.mod_resolution_collection=Игра отказала из-за несовместимости версии мода.\\n\\\n  \\n\\\n  «%1$s» требует «%2$s».\\n\\\n  \\n\\\n  Вам необходимо обновить или понизить версию «%3$s», прежде чем продолжить.\ngame.crash.reason.mod_resolution_conflict=Игра отказала из-за конфликтующих модов.\\n\\\n  \\n\\\n  «%1$s» конфликтуют с «%2$s».\ngame.crash.reason.mod_resolution_missing=Игра отказала из-за того, что не установлены некоторые зависимые моды.\\n\\\n  \\n\\\n  «%1$s» требует установки мода «%2$s».\\n\\\n  \\n\\\n  Это означает, что вам нужно сначала скачать и установить «%2$s», чтобы продолжить игру.\ngame.crash.reason.mod_resolution_missing_minecraft=Игра отказала из-за несовместимости мода с текущей версией Minecraft.\\n\\\n  \\n\\\n  «%1$s» требует Minecraft версии %2$s.\\n\\\n  \\n\\\n  Если вы хотите играть с установленной версией мода, вам следует изменить версию игры в вашей сборке.\\n\\\n  \\n\\\n  Если вы хотите играть в текущую версию игры, вам следует установить мод, совместимый с этой версией Minecraft.\ngame.crash.reason.mod_resolution_mod_version=%1$s (Версия: %2$s)\ngame.crash.reason.mod_resolution_mod_version.any=%1$s (Любая версия)\ngame.crash.reason.forge_repeat_installation=Игра отказала из-за дублирующей установки Forge. <a href=\"https://github.com/HMCL-dev/HMCL/issues/1880\">Это известная проблема</a>\\n\\\n  \\n\\\n  Рекомендуется загрузить журнал и отправить отзыв на GitHub, чтобы мы могли найти больше сведений и решить эту проблему.\\n\\\n  \\n\\\n  В настоящее время вы можете удалить Forge и установить его заново в разделе «Изменить сборку → Погрузчики».\ngame.crash.reason.optifine_repeat_installation=Игра отказала из-за дублирующей установки OptiFine.\\n\\\n  \\n\\\n  Пожалуйста, удалите OptiFine из папки «mods» или деинсталлируйте его в «Изменить сборку → Погрузчики».\ngame.crash.reason.modlauncher_8=Игра отказала из-за того, что текущая версия Forge несовместима с вашей установкой Java. Пожалуйста, попробуйте обновить Forge.\ngame.crash.reason.night_config_fixes=Игра отказала из-за некоторых проблем с Night Config.\\n\\\n  \\n\\\n  Вы можете попробовать установить мод <a href=\"https://www.curseforge.com/minecraft/mc-mods/night-config-fixes\">Night Config Fixes</a>, который может помочь вам справиться с этой проблемой.\\n\\\n  \\n\\\n  Для получения дополнительной информации посетите <a href=\"https://www.github.com/Fuzss/nightconfigfixes\">репозиторий мода на GitHub</a>.\ngame.crash.reason.no_class_def_found_error=Игра не может продолжить работу из-за неполного кода.\\n\\\n  \\n\\\n  В сборке вашей игры отсутствует «%1$s». Это может быть связано с отсутствием мода, установкой несовместимого мода или повреждением некоторых файлов.\\n\\\n  \\n\\\n  Возможно, вам придется переустановить игру и все моды или попросить кого-нибудь о помощи.\ngame.crash.reason.shaders_mod=Игра отказала из-за одновременной установки модов OptiFine и Shaders.\\n\\\n  \\n\\\n  Просто удалите мод Shaders, потому что OptiFine имеет встроенную поддержку шейдеров.\ngame.crash.reason.rtss_forest_sodium=Игра отказала из-за того, что RivaTuner Statistical Server (RTSS) не совместим с Sodium.\\n\\\n  \\n\\\n  Нажмите <a href=\"https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\">здесь</a>, чтобы узнать подробности.\ngame.crash.reason.no_such_method_error=Игра не может продолжить работу из-за неполного кода.\\n\\\n  \\n\\\n  В вашей сборке игры может отсутствовать мод, установлен несовместимый мод, или некоторые файлы могут быть повреждены.\\n\\\n  \\n\\\n  Возможно, вам придется переустановить игру и все моды или попросить кого-нибудь о помощи.\ngame.crash.reason.opengl_not_supported=Игра отказала, потому что ваш графический драйвер не поддерживает OpenGL.\\n\\\n  \\n\\\n  Если вы транслируете игру через Интернет или используете среду удаленного рабочего стола, пожалуйста, играйте на локальной машине.\\n\\\n  \\n\\\n  Или попробуйте обновить графический драйвер до последней версии и перезапустить игру.\\n\\\n  \\n\\\n  Если на вашем компьютере есть выделенная видaеокарта, убедитесь, что игра действительно использует ее для рендеринга. Если проблема не исчезнет, подумайте о приобретении новой видеокарты или нового компьютера.\ngame.crash.reason.openj9=Игра не может быть запущена на OpenJ9 JVM.\\n\\\n  \\n\\\n  Пожалуйста, переключитесь на Java, использующую Hotspot JVM, в разделе «Глобальные настройки / Раздельные настройки для сборки → Java» и запустите игру заново. Если у вас нет такого типа Java, вы можете скачать его.\ngame.crash.reason.out_of_memory=Игра отказала из-за того, что на компьютере закончилась память.\\n\\\n  \\n\\\n  Возможно, не хватает памяти или установлено слишком много модов. Вы можете попробовать решить эту проблему, увеличив объем выделенной памяти в разделе «Глобальные настройки / Раздельные настройки для сборки → Оперативная память».\\n\\\n  \\n\\\n  Если вы по-прежнему сталкиваетесь с этими проблемами, возможно, вам нужен более качественный компьютер.\ngame.crash.reason.resolution_too_high=Игра отказала из-за слишком высокого разрешения пакета ресурсов.\\n\\\n  \\n\\\n  Вам следует перейти на пакет ресурсов с меньшим разрешением или рассмотреть возможность покупки более качественной видеокарты с большим объемом VRAM.\ngame.crash.reason.stacktrace=Причина отказа неизвестна. Вы можете просмотреть его детали, нажав «Журналы».\\n\\\n  \\n\\\n  Есть несколько ключевых слов, которые могут содержать названия некоторых модов. Вы можете поискать их в Интернете, чтобы выяснить проблему самостоятельно.\\n\\\n  \\n\\\n  %s\ngame.crash.reason.too_old_java=Игра отказала из-за того, что вы используете устаревшую версию Java.\\n\\\n  \\n\\\n  Вам необходимо указать более новую версию Java (%1$s) в разделе «Глобальные настройки / Раздельные настройки для сборки → Java», а затем перезапустить игру. Скачать Java можно <a href=\"https://learn.microsoft.com/java/openjdk/download\">отсюда</a>.\ngame.crash.reason.unknown=Мы не можем выяснить причину отказа в игре. Пожалуйста, обратитесь к логам игры.\ngame.crash.reason.unsatisfied_link_error=Не удалось запустить Minecraft из-за отсутствия библиотек: %1$s.\\n\\\n  \\n\\\n  Если вы редактировали путь к родным библиотекам, убедитесь, что эти библиотеки существуют. Или попробуйте запустить игру снова после возврата к стандартным настройкам.\\n\\\n  \\n\\\n  Если вы этого не сделали, проверьте, нет ли у вас отсутствующих зависимых модов.\\n\\\n  \\n\\\n  В противном случае, если вы считаете, что это произошло по вине HMCL, пожалуйста, сообщите нам об этом.\ngame.crash.reason.mac_jdk_8u261=Игра отказала из-за того, что текущая версия Forge или OptiFine несовместима с установленной вами Java.\\n\\\n  \\n\\\n  Пожалуйста, попробуйте обновить Forge и OptiFine, или попробуйте использовать Java 8u251 или предыдущие версии.\ngame.crash.reason.mod_files_are_decompressed=Игра отказала из-за того, что файл мода был извлечен.\\n\\\n  \\n\\\n  Пожалуйста, поместите весь файл мода прямо в папку с модом!\\n\\\n  \\n\\\n  Если извлечение вызывает ошибки в игре, удалите извлеченный мод в папке мод, а затем запустите игру.\ngame.crash.reason.optifine_causes_the_world_to_fail_to_load=Игра может не запускаться из-за OptiFine.\\n\\\n  \\n\\\n  Эта проблема возникает только в определенной версии OptiFine. Вы можете попробовать изменить версию OptiFine в разделе «Изменить сборку → Погрузчики».\ngame.crash.reason.optifine_is_not_compatible_with_forge=Игра отказала из-за несовместимости OptiFine с текущей установкой Forge.\\n\\\n  \\n\\\n  Пожалуйста, перейдите на «официальный сайт OptiFine», проверьте, совместима ли версия Forge с OptiFine, и переустановите сборку в строгом соответствии с соответствующей версией, либо измените версию OptiFine в «Изменить сборку → Погрузчики».\\n\\\n  \\n\\\n  После тестирования мы пришли к выводу, что слишком высокие или слишком низкие версии OptiFine могут вызывать сбои.\ngame.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=Игра отказала из-за того, что вы установили слишком много модов и превысили лимит игровых идентификаторов.\\n\\\n  \\n\\\n  Попробуйте установить <a href=\"https://www.curseforge.com/minecraft/mc-mods/jeid\">JEID</a> или удалить несколько больших модов.\ngame.crash.title=Игра отказала!\ngame.directory=Путь игры\ngame.version=Сборка\n\nhelp=Справка\nhelp.doc=Документация лаунчера\nhelp.detail=Для создателей набора данных и модпака.\n\ninput.email=Имя пользователя должно быть адресом электронной почты.\ninput.number=Ввод должен быть числом.\ninput.not_empty=Это обязательное поле.\ninput.url=Ввод должен быть действительным URL-адресом.\n\ninstall=Новый сборник\ninstall.change_version=Изменить версию\ninstall.change_version.confirm=Переключить %s с версии %s на %s?\ninstall.change_version.process=Процесс изменения версии\ninstall.failed=Не удалось установить\ninstall.failed.downloading=Не удалось скачать некоторые необходимые файлы.\ninstall.failed.downloading.detail=Не удалось скачать файл: %s\ninstall.failed.downloading.timeout=Таймаут скачивания при получении данных: %s\ninstall.failed.install_online=Не удалось идентифицировать предоставленный файл. Если вы хотите установить мод, перейдите на страницу «Моды».\ninstall.failed.malformed=Скачанные файлы повреждены. Вы можете попробовать решить эту проблему, переключившись на другой источник загрузки в разделе «Настройки → Скачать → Источник скачивания».\ninstall.failed.optifine_conflict=Невозможно установить OptiFine и Fabric на Minecraft 1.13 или более поздней версии.\ninstall.failed.optifine_forge_1.17=Для Minecraft 1.17.1 Forge совместим только с OptiFine H1 pre2 или более поздней версией. Вы можете установить их, отметив «Снапшоты» при выборе версии OptiFine в HMCL.\ninstall.failed.version_mismatch=Этот загрузчик требует версию игры %s, но установленная версия %s.\ninstall.installer.change_version=%s (Несовместимо)\ninstall.installer.choose=Выберите версию %s\ninstall.installer.depend=Требуется %s\ninstall.installer.do_not_install=Не устанавливать\ninstall.installer.fabric=Fabric\ninstall.installer.fabric-api=Fabric API\ninstall.installer.fabric-quilt-api.warning=%1$s является модом и будет установлен в папку mod в сборке игры. Пожалуйста, не меняйте рабочую папку сборки, иначе %1$s не будет работать. Если вы все же хотите изменить папку, вам следует переустановить сборку.\ninstall.installer.forge=Forge\ninstall.installer.neoforge=NeoForge\ninstall.installer.game=Minecraft\ninstall.installer.incompatible=Несовместимо с %s\ninstall.installer.install=Установить %s\ninstall.installer.install_offline=Установить/обновить из локального файла\ninstall.installer.install_offline.tooltip=Поддерживаются установщики (Neo)Forge/Cleanroom/OptiFine.\ninstall.installer.install_online=Установить с интернета\ninstall.installer.install_online.tooltip=Поддерживаются Forge, NeoForge, Cleanroom, Fabric, Legacy Fabric, Quilt, LiteLoader и OptiFine.\ninstall.installer.liteloader=LiteLoader\ninstall.installer.not_installed=Не установлено\ninstall.installer.optifine=OptiFine\ninstall.installer.quilt=Quilt\ninstall.installer.quilt-api=QSL/QFAPI\ninstall.installer.version=%s\ninstall.installer.external_version=%s (Устанавливается внешним процессом, который не может быть изменен)\ninstall.installing=Установка\ninstall.modpack=Установить модпак\ninstall.modpack.installation=Установка модпак\ninstall.name.invalid=Имя содержит специальные символы (например, эмодзи или символы CJK).\\nРекомендуется изменить имя, включив в него только английские буквы, цифры и подчеркивание, чтобы избежать возможных проблем при запуске игры.\\nВы хотите продолжить процесс?\ninstall.new_game=Установить сборку\ninstall.new_game.already_exists=Это имя сборки уже существует. Пожалуйста, используйте другое имя.\ninstall.new_game.current_game_version=Текущая версия сборки\ninstall.new_game.installation=Установка экземпляра\ninstall.new_game.malformed=Недопустимое имя\ninstall.select=Выберите операцию\ninstall.success=Успешно установлено.\n\njava.add.failed=Этот Java недопустим или несовместим с текущей платформой.\njava.disable=Отключить Java\njava.disable.confirm=Вы уверены, что хотите отключить эту Java?\njava.disabled.management=Отключенная Java\njava.disabled.management.remove=Удалить эту Java из списка\njava.disabled.management.restore=Включить эту Java\njava.download.banshanjdk-8=Скачать Banshan JDK 8\njava.download.load_list.failed=Не удалось загрузить список версий\njava.download.more=Больше дистрибутивов Java\njava.download.prompt=Выберите версию Java, которую вы хотите скачать:\njava.download.distribution=Дистрибуция\njava.download.version=Версия\njava.download.packageType=Тип упаковки\njava.management=Управление Java\njava.info.architecture=Архитектура\njava.info.vendor=Поставщик\njava.info.version=Версия\njava.info.disco.distribution=Дистрибуция\njava.install=Установите Java\njava.install.archive=Расположение\njava.install.failed.exists=Это имя уже используется\njava.install.failed.invalid=Этот архив не является допустимым установочным пакетом Java, поэтому его установка невозможна.\njava.install.failed.unsupported_platform=Эта Java несовместима с текущей платформой, поэтому ее невозможно установить.\njava.install.name=Название\njava.install.warning.invalid_character=Неправильный символ в названии\njava.installing=Установка Java\njava.uninstall=Удалить Java\njava.uninstall.confirm=Вы уверены, что хотите удалить эту Java? Это действие нельзя отменить!\n\nlang.default=Использовать язык системы\n\nlaunch.advice=%s Вы все еще хотите продолжить запуск?\nlaunch.advice.multi=Были обнаружены следующие проблемы:\\n\\n%s\\n\\nЭти проблемы могут помешать вам запустить игру или повлиять на игровой процесс.\\nВы все еще хотите продолжить запуск?\nlaunch.advice.java.auto=Текущая версия Java несовместима со сборкой.\\n\\nНажмите «Да», чтобы автоматически выбрать наиболее совместимую версию Java. Или же вы можете перейти в раздел «Глобальные настройки / Раздельные настройки для сборки → Java», чтобы выбрать один из них самостоятельно.\nlaunch.advice.java.modded_java_7=Для Minecraft 1.7.2 или более ранней версии требуется Java 7 или более ранняя версия.\nlaunch.advice.corrected=Мы решили проблему с Java. Если вы все еще хотите использовать выбранную вами версию Java, вы можете отключить опцию «Не проверять совместимость JVM» в разделе «Глобальные настройки / Раздельные настройки для сборки → Расширенные настройки».\nlaunch.advice.uncorrected=Если вы все еще хотите использовать выбранную вами версию Java, вы можете отключить опцию «Не проверять совместимость JVM» в разделе «Глобальные настройки / Раздельные настройки для сборки → Расширенные настройки».\nlaunch.advice.different_platform=Ваша ОС - 64-разрядная, а Java - 32-разрядная. Рекомендуется использовать 64-разрядную версию Java.\nlaunch.advice.forge2760_liteloader=Forge 2760 и выше не совместим с LiteLoader. Обновите Forge до 2773 или более новой версии.\nlaunch.advice.forge28_2_2_optifine=Forge 28.2.2 и более поздние версии не совместимы с OptiFine. Смените Forge на версию 28.2.1 или более старую версию.\nlaunch.advice.forge37_0_60=Версии Forge ранее 37.0.60 не совместимы с Java 17. Обновите Forge или запустите игру с Java 16.\nlaunch.advice.java8_1_13=Minecraft 1.13 и выше могут работать только на Java 8 или более новых версиях. Используйте Java 8 или более поздние версии.\nlaunch.advice.java8_51_1_13=Minecraft 1.13 может аварийно завершить работу на более старой версии, чем Java 8 1.8.0_51. Установите последнюю версию Java 8.\nlaunch.advice.java9=Вы не сможете запустить Minecraft 1.12 и ниже с Java 9 и более новыми версиями Java. Используйте вместо этого Java 8.\nlaunch.advice.modded_java=Некоторые моды могут быть несовместимы с новыми версиями Java. Рекомендуется использовать Java %s для запуска Minecraft %s.\nlaunch.advice.newer_java=Вы используете старую версию Java для запуска игры. Рекомендуется обновить Java 8, иначе некоторые моды могут привести к сбою игры.\nlaunch.advice.not_enough_space=Вы выделили слишком много памяти, поскольку размер физической памяти составляет %d МиБ, ваша игра может рухнуть.\nlaunch.advice.require_newer_java_version=Для текущей версии игры требуется Java %s, но мы не смогли ее найти. Хотите скачать ее сейчас?\nlaunch.advice.too_large_memory_for_32bit=Вы выделили слишком много памяти, из-за 32-разрядной JRE ваша игра может рухнуть. Максимальный объем памяти для 32-разрядных систем составляет 1024 МиБ.\nlaunch.advice.vanilla_linux_java_8=На Linux x86-64, Minecraft 1.12.2 и ниже может работать только на Java 8.\\nВерсии Java 9 и выше не могут загружать 32-битные нативные библиотеки, такие как liblwjgl.so.\nlaunch.advice.modlauncher8=Используемая вами версия Forge несовместима с текущей версией Java. Попробуйте обновить Forge.\nlaunch.advice.vanilla_x86.translation=Minecraft еще не полностью поддерживается на вашей платформе, поэтому вы можете столкнуться с отсутствием функций или даже не сможете запустить игру.\\n\\\n  Пожалуйста, используйте Java для x86-64 для запуска minecraft через переводчик, или загрузите соответствующую нативную библиотеку платформы и укажите её путь размещения.\nlaunch.advice.unknown=Не удалось запустить игру по следующим причинам:\nlaunch.failed=Не удалось запустить\nlaunch.failed.cannot_create_jvm=Не удалось создать JVM. Это может быть вызвано неправильными аргументами JVM. Вы можете попробовать решить эту проблему, удалив все аргументы, добавленные в разделе «Глобальные настройки / Раздельные настройки для сборки → Расширенные настройки → Настройки JVM».\nlaunch.failed.creating_process=Не удалось создать процесс. Проверьте путь к Java.\\n\nlaunch.failed.command_too_long=Длина команды превышает максимальную длину пакетного сценария. Попробуйте экспортировать его как сценарий PowerShell.\nlaunch.failed.decompressing_natives=Не удалось извлечь нативные библиотеки.\nlaunch.failed.download_library=Не удалось скачать библиотеки «%s».\nlaunch.failed.executable_permission=Не удалось сделать сценарий запуска исполняемым.\nlaunch.failed.execution_policy=Установить политику выполнения\nlaunch.failed.execution_policy.failed_to_set=Не удалось установить политику выполнения\nlaunch.failed.execution_policy.hint=Текущая политика выполнения не позволяет выполнять сценарий PowerShell.\\n\\\n  \\n\\\n  Нажмите «ОК», чтобы разрешить текущему пользователю выполнять сценарий PowerShell, или нажмите «Отмена», чтобы оставить всё как есть.\nlaunch.failed.exited_abnormally=Игра отказала. Дополнительные сведения см. в журналах сбоев.\nlaunch.failed.java_version_too_low=Указанная вами версия Java слишком низкая. Измените версию Java.\nlaunch.failed.no_accepted_java=Не удалось найти совместимую версию Java, запустить игру с Java по умолчанию?\\nНажмите «Да», чтобы запустить игру с Java по умолчанию.\\nИли вы можете перейти в раздел «Глобальные настройки / Раздельные настройки для сборки → Java», чтобы выбрать один из них самостоятельно.\nlaunch.failed.sigkill=Игра была принудительно завершена пользователем или системой.\nlaunch.state.dependencies=Подготовка зависимостей\nlaunch.state.done=Завершение запуска\nlaunch.state.java=Проверка версии Java\nlaunch.state.logging_in=Вход в систему\nlaunch.state.modpack=Скачивание зависимостей\nlaunch.state.waiting_launching=Ожидание запуска игры\nlaunch.invalid_java=Неверный путь к Java. Переустановите путь к Java.\n\nlauncher=Лаунчер\nlauncher.agreement=Пользовательское соглашение\nlauncher.agreement.accept=Принять\nlauncher.agreement.decline=Отклонить\nlauncher.agreement.hint=Нужно принять пользовательское соглашение, чтобы использовать это программное обеспечение.\nlauncher.background=Фоновое изображение\nlauncher.background.choose=Выберите фоновое изображение\nlauncher.background.classic=Классическое\nlauncher.background.default=По умолчанию\nlauncher.background.default.tooltip=Или «background.png/.jpg/.gif/.webp» и изображения в папке «bg».\nlauncher.background.network=По ссылке\nlauncher.background.paint=Сплошной цвет\nlauncher.cache_directory=Папка с кэшем\nlauncher.cache_directory.clean=Очистить кэш\nlauncher.cache_directory.choose=Выберите папку для кэша\nlauncher.cache_directory.default=По умолчанию («%APPDATA%/.minecraft» или «~/.minecraft»)\nlauncher.cache_directory.disabled=Отключено\nlauncher.cache_directory.invalid=Не удалось создать папку для кэша, возвращаем к значению по умолчанию.\nlauncher.contact=Связаться с нами\nlauncher.crash=Лаунчер столкнулся с фатальной ошибкой! Скопируйте следующий журнал и попросите помощи в нашем Discord, группе QQ, GitHub или на другом форуме Minecraft.\nlauncher.crash.java_internal_error=Лаунчер столкнулся с фатальной ошибкой! Пожалуйста, удалите Java и скачайте подходящую Java <a href=\"https://bell-sw.com/pages/downloads/#downloads\">здесь</a>.\nlauncher.crash.hmcl_out_dated=Лаунчер столкнулся с фатальной ошибкой! Ваш лаунчер устарел. Обновите его!\nlauncher.update_java=Пожалуйста, обновите версию Java.\n\nlibraries.download=Скачивание библиотек\n\nlogin.empty_username=Имя пользователя не установлено!\nlogin.enter_password=Введите пароль.\n\nlogwindow.show_lines=Показать линии\nlogwindow.terminate_game=Завершить игру\nlogwindow.title=Журнал\nlogwindow.help=Вы можете обратиться к сообществу HMCL, чтобы получить помощь от других\nlogwindow.autoscroll=Автопрокрутка\nlogwindow.export_game_crash_logs=Экспорт журналов с ошибками\nlogwindow.export_dump=Экспорт дампа стека игры\nlogwindow.export_dump.no_dependency=Ваша Java не содержит зависимостей для создания дампа стека.\n\nmain_page=Главная\n\nmessage.cancelled=Операция была отменена\nmessage.confirm=Подтвердить\nmessage.copied=Скопировано в буфер обмена\nmessage.default=По умолчанию\nmessage.doing=Подождите\nmessage.downloading=Скачивание\nmessage.error=Ошибка\nmessage.failed=Не удалось завершить операцию\nmessage.info=Сведения\nmessage.success=Операция успешно завершена\nmessage.unknown=Неизвестно\nmessage.warning=Предупреждение\n\nmodpack=Модпаки\nmodpack.choose=Выберите модпак\nmodpack.choose.local=Импорт из файла\nmodpack.choose.local.detail=Можно перетащить файл модпака сюда\nmodpack.choose.remote=Скачать\nmodpack.choose.remote.detail=Необходима прямая ссылка на скачивание модпака\nmodpack.choose.remote.tooltip=Введите URL-адрес модпака\nmodpack.choose.repository=Скачать модпак с CurseForge или Modrinth\nmodpack.choose.repository.detail=Вы сможете выбрать нужный вам модпак на следующей странице.\nmodpack.completion=Скачивание зависимостей\nmodpack.desc=Опишите модпак, включая введение и журнал изменений. Поддерживаются Markdown и изображения по URL-адресу.\nmodpack.description=Описание\nmodpack.download=Скачать модпак\nmodpack.download.title=Скачать модпак - %1s\nmodpack.enter_name=Введите имя модпака.\nmodpack.export=Экспортировать модпак\nmodpack.export.as=Экспортировать модпак как...\nmodpack.file_api=Префикс URL модпака\nmodpack.files.blueprints=Чертежи BuildCraft\nmodpack.files.config=Файлы конфигурации мода\nmodpack.files.dumps=Выходные файлы отладки NEI\nmodpack.files.hmclversion_cfg=Файл конфигурации лаунчера\nmodpack.files.liteconfig=Связанные файлы LiteLoader\nmodpack.files.mods=Моды\nmodpack.files.mods.voxelmods=Настройки VoxelMods\nmodpack.files.options_txt=Файл настроек Minecraft\nmodpack.files.optionsshaders_txt=Файл настроек шейдера\nmodpack.files.resourcepacks=Пакеты ресурсов/текстур\nmodpack.files.saves=Миры\nmodpack.files.scripts=Файл конфигурации MineTweaker\nmodpack.files.servers_dat=Файл списка серверов\nmodpack.installing=Установка модпак\nmodpack.installing.given=Установка модпак %s\nmodpack.introduction=Поддерживаются модпаки CurseForge, Modrinth, MultiMC и MCBBS.\nmodpack.invalid=Неверный модпак, попробуйте скачать его заново.\nmodpack.mismatched_type=Несоответствие типа модпака, текущий сборка имеет тип %s, но предоставленный сборка имеет тип %s.\nmodpack.name=Имя модпака\nmodpack.not_a_valid_name=Неверное имя модпака\nmodpack.origin=Источник\nmodpack.origin.url=Официальный сайт\nmodpack.origin.mcbbs=MCBBS\nmodpack.origin.mcbbs.prompt=ID записи\nmodpack.scan=Разбор индекса модпака\nmodpack.task.install=Установить модпак\nmodpack.task.install.error=Не удалось идентифицировать этот модпак. Поддерживаются только модпаки Curse, Modrinth, MultiMC и MCBBS.\nmodpack.type.curse=Curse\nmodpack.type.curse.error=Невозможно установить этот модпак. Повторите ещё раз.\nmodpack.type.curse.not_found=Некоторые из необходимых ресурсов отсутствуют и поэтому не могут быть загружены. Скачайте последнюю версию или другие модпаки.\nmodpack.type.manual.warning=Возможно, вам нужно напрямую распаковать файл этого модпака, а не импортировать его. И запустите игру с помощью прилагаемого лаунчера. Этот модпак создаётся вручную путем сжатия директории .minecraft, а не экспортируется лаунчером. HMCL может попытаться импортировать этот модпак, продолжать?\nmodpack.type.mcbbs=MCBBS\nmodpack.type.mcbbs.export=Может быть импортирован Hello Minecraft! Launcher\nmodpack.type.modrinth=Modrinth\nmodpack.type.modrinth.export=Может быть импортирован популярными сторонними лаунчеры\nmodpack.type.multimc=MultiMC\nmodpack.type.multimc.export=Может быть импортирован Hello Minecraft! Launcher и MultiMC\nmodpack.type.server=Сервер автообновления модпака\nmodpack.type.server.export=Разрешить менеджеру сервера удаленно обновлять клиент игры\nmodpack.type.server.malformed=Некорректный манифест модпака сервера\nmodpack.unsupported=Hello Minecraft! Launcher не поддерживает этот формат пакетов\nmodpack.update=Обновление модпака\nmodpack.wizard=Мастер экспорта модпака\nmodpack.wizard.step.1=Основные настройки\nmodpack.wizard.step.1.title=Некоторые основные сведения о модпаке.\nmodpack.wizard.step.2=Выбрать файлы\nmodpack.wizard.step.2.title=Выберите файлы, которые вы хотите добавить в модпак.\nmodpack.wizard.step.3=Тип модпака\nmodpack.wizard.step.3.title=Выберите тип модпака, который вы хотите экспортировать.\nmodpack.wizard.step.initialization.exported_version=Сборка игры для экспорта\nmodpack.wizard.step.initialization.force_update=Принудительное обновление модпака до последней версии (вам понадобится служба хостинга файлов)\nmodpack.wizard.step.initialization.include_launcher=Включать лаунчер\nmodpack.wizard.step.initialization.modrinth.info=Лаунчер будет использовать удаленные ресурсы CurseForge/Modrinth вместо локальных файлов (включая моды, пакеты ресурсов и пакеты шейдеров) при создании модпака, чтобы уменьшить размер модпака, и помечать файлы с расширением «.disabled» как необязательные для установки.\nmodpack.wizard.step.initialization.no_create_remote_files=Не используйте удаленные файлы\nmodpack.wizard.step.initialization.save=Экспортировать в...\nmodpack.wizard.step.initialization.skip_curseforge_remote_files=Не используйте удаленные файлы CurseForge\nmodpack.wizard.step.initialization.warning=Перед созданием модпака убедитесь, что игра запускается нормально и Minecraft является релизной версией, а не снапшотом. Пусковая установка сохранит ваши настройки скачивания.\\n\\\n  \\n\\\n  Помните, что вам не разрешается добавлять моды и пакет ресурсов, в которых явно указано, что они не подлежат распространению или помещению в модпак.\nmodpack.wizard.step.initialization.server=Нажмите здесь для получения дополнительных руководств по созданию модпака на сервере, который может автообновляться.\n\nmodrinth.category.adventure=Приключение\nmodrinth.category.atmosphere=Атмосфера\nmodrinth.category.audio=Аудио\nmodrinth.category.babric=Babric\nmodrinth.category.blocks=Блоки\nmodrinth.category.bloom=Свечение\nmodrinth.category.bta-babric=BTA (Babric)\nmodrinth.category.bukkit=Bukkit\nmodrinth.category.bungeecord=BungeeCord\nmodrinth.category.canvas=Canvas\nmodrinth.category.cartoon=Мультяшный\nmodrinth.category.challenging=Вызов\nmodrinth.category.colored-lighting=Цветное освещение\nmodrinth.category.combat=Бой\nmodrinth.category.core-shaders=Ядро шейдеров\nmodrinth.category.cursed=Cursed\nmodrinth.category.datapack=Набор данных\nmodrinth.category.decoration=Украшения\nmodrinth.category.economy=Экономика\nmodrinth.category.entities=Сущности\nmodrinth.category.environment=Окружающая среда\nmodrinth.category.equipment=Оборудование\nmodrinth.category.fabric=Fabric\nmodrinth.category.fantasy=Фэнтези\nmodrinth.category.folia=Folia\nmodrinth.category.foliage=Растительность\nmodrinth.category.fonts=Шрифты\nmodrinth.category.food=Еда\nmodrinth.category.forge=Forge\nmodrinth.category.game-mechanics=Игровая механика\nmodrinth.category.gui=ГИП\nmodrinth.category.high=Высокий\nmodrinth.category.iris=Iris\nmodrinth.category.items=Предметы\nmodrinth.category.java-agent=Java Agent\nmodrinth.category.kitchen-sink=Kitchen-Sink\nmodrinth.category.legacy-fabric=Legacy Fabric\nmodrinth.category.library=Библиотека\nmodrinth.category.lightweight=Легкий\nmodrinth.category.liteloader=LiteLoader\nmodrinth.category.locale=Язык\nmodrinth.category.low=Низкий\nmodrinth.category.magic=Магия\nmodrinth.category.management=Управление\nmodrinth.category.medium=Средний\nmodrinth.category.minecraft=Minecraft\nmodrinth.category.minigame=Мини-игра\nmodrinth.category.misc=Разное\nmodrinth.category.mobs=Мобы\nmodrinth.category.modded=Модифицированный\nmodrinth.category.models=Модели\nmodrinth.category.modloader=Мод-загрузчик\nmodrinth.category.multiplayer=Сетевая игра\nmodrinth.category.neoforge=NeoForge\nmodrinth.category.nilloader=NilLoader\nmodrinth.category.optifine=OptiFine\nmodrinth.category.optimization=Оптимизация\nmodrinth.category.ornithe=Ornithe\nmodrinth.category.paper=Paper\nmodrinth.category.path-tracing=Трассировка пути\nmodrinth.category.pbr=PBR\nmodrinth.category.potato=Минимум\nmodrinth.category.purpur=Purpur\nmodrinth.category.quests=Квесты\nmodrinth.category.quilt=Quilt\nmodrinth.category.realistic=Реалистичный\nmodrinth.category.reflections=Отражения\nmodrinth.category.rift=Rift\nmodrinth.category.screenshot=Экстрим\nmodrinth.category.semi-realistic=Полуреалистичный\nmodrinth.category.shadows=Тени\nmodrinth.category.simplistic=Упрощенный\nmodrinth.category.social=Социальная\nmodrinth.category.spigot=Spigot\nmodrinth.category.sponge=Sponge\nmodrinth.category.storage=Хранилище\nmodrinth.category.technology=Технология\nmodrinth.category.themed=Тематический\nmodrinth.category.transportation=Транспорт\nmodrinth.category.tweaks=Усовершенствование\nmodrinth.category.utility=Утилиты\nmodrinth.category.vanilla=Ванильное\nmodrinth.category.vanilla-like=Ванильное\nmodrinth.category.velocity=Velocity\nmodrinth.category.waterfall=Waterfall\nmodrinth.category.worldgen=Генерация мира\nmodrinth.category.8x-=8x-\nmodrinth.category.16x=16x\nmodrinth.category.32x=32x\nmodrinth.category.48x=48x\nmodrinth.category.64x=64x\nmodrinth.category.128x=128x\nmodrinth.category.256x=256x\nmodrinth.category.512x+=512x+\n\nmods=Моды\nmods.add.failed=Не удалось установить мод %s.\nmods.add.success=%s был успешно добавлен.\nmods.broken_dependency.title=Сломанная зависимость\nmods.broken_dependency.desc=Эта зависимость существовала раньше, но теперь ее нет. Попробуйте использовать другой источник скачивания.\nmods.category=Категория\nmods.channel.alpha=Альфа\nmods.channel.beta=Бета\nmods.channel.release=Релиз\nmods.check_updates=Проверить обновления\nmods.check_updates.button=Обновить\nmods.check_updates.confirm=Обновить\nmods.check_updates.current_version=Текущая версия\nmods.check_updates.empty=Все моды новейшие\nmods.check_updates.failed_check=Не удалось проверить обновления.\nmods.check_updates.failed_download=Не удалось скачать некоторые файлы.\nmods.check_updates.file=Файл\nmods.check_updates.source=Источник\nmods.check_updates.target_version=Целевая версия\nmods.curseforge=CurseForge\nmods.dependency.embedded=Встроенные зависимости (Уже упакован в файл мода автором. Нет необходимости скачивать отдельно.)\nmods.dependency.optional=Необязательные зависимости (Если он отсутствует, игра будет работать нормально, но функции мода могут отсутствовать.)\nmods.dependency.required=Необходимые зависимости (Необходимо скачивать отдельно, отсутствие может помешать запуску игры.)\nmods.dependency.tool=Необходимые зависимости (Необходимо скачивать отдельно, отсутствие может помешать запуску игры.)\nmods.dependency.include=Встроенные зависимости (Уже упакован в файл мода автором. Нет необходимости скачивать отдельно.)\nmods.dependency.incompatible=Несовместимые моды (Одновременная установка этих модов не позволит запустить игру.)\nmods.dependency.broken=Сломанные зависимости (Этот мод существовал раньше, но теперь его нет. Попробуйте использовать другой источник скачивания.)\nmods.disable=Отключить\nmods.download=Скачать мод\nmods.download.title=Скачать мод - %1s\nmods.download.recommend=Рекомендуемая версия мода - Minecraft %1s\nmods.enable=Включить\nmods.game.version=Версия игры\nmods.manage=Моды\nmods.mcbbs=MCBBS\nmods.mcmod=MCMod\nmods.mcmod.page=Страница MCMod\nmods.mcmod.search=Искать на MCMod\nmods.modrinth=Modrinth\nmods.name=Название\nmods.not_modded=Для управления модами необходимо сначала установить загрузчик (Forge, NeoForge, Fabric, Legacy Fabric, Quilt или LiteLoader)!\nmods.restore=Восстановить\nmods.url=Официальная страница\nmods.update_modpack_mod.warning=Обновление модов в пакете модов может привести к непоправимым результатам, возможно, повредить пакет модов так, что он не сможет запуститься. Вы уверены, что хотите обновить?\nmods.install=Установить\nmods.save_as=Сохранить как\n\nnbt.entries=%s записи\nnbt.open.failed=Не удалось открыть файл\nnbt.save.failed=Не удалось сохранить файл\nnbt.title=Смотреть файл - %s\n\ndatapack=Наборы данных\ndatapack.title=Мир [%s] - Наборы данных\n\nweb.failed=Не удалось загрузить страницу\nweb.open_in_browser=Хотите ли вы открыть этот адрес в браузере?\\n\\n%s\nweb.view_in_browser=Смотреть в браузере\n\nworld=Миры\nworld.add.already_exists=Мир уже существует.\nworld.add.title=Выберите архив мира, который хотите импортировать\nworld.add.failed=Не удалось импортировать этот мир\\: %s\nworld.add.invalid=Не удалось разобрать мир.\nworld.backup=Резервный мир\nworld.backup.create.failed=Не удалось создать резервную копию.\\n%s\nworld.backup.create.success=Успешно создано новое резервное копирование: %s\nworld.backup.delete=Удалить эту резервную копию\nworld.backup.processing=Создание новой резервной копии ...\nworld.chunkbase=Chunk Base\nworld.chunkbase.end_city=Город Края\nworld.chunkbase.seed_map=Предпросмотр генерации мира\nworld.chunkbase.stronghold=Крепость\nworld.chunkbase.nether_fortress=Крепость Нижнего мира\nworld.duplicate.failed.already_exists=Папка уже существует\nworld.duplicate.failed.empty_name=Название не может быть пустым\nworld.duplicate.failed.invalid_name=Название содержит недопустимые символы\nworld.delete=Удалить этот мир\nworld.delete.failed=Не удалось удалить мир.\\n%s\nworld.datapack=Наборы данных\nworld.datetime=Последний запуск игры %s\nworld.download.title=Скачать мир - %1s\nworld.export=Экспорт мира\nworld.export.title=Выберите папку для экспорта мира\nworld.export.location=Экспорт в\nworld.export.wizard=Экспорт мира %s\nworld.game_version=Версия игры\nworld.info=Сведения о мире\nworld.info.basic=Основные сведения\nworld.info.allow_cheats=Разрешить команды/читы\nworld.info.dimension.the_nether=Нижний мир\nworld.info.dimension.the_end=Край\nworld.info.difficulty=Сложность\nworld.info.difficulty.peaceful=Мирная\nworld.info.difficulty.easy=Лёгкая\nworld.info.difficulty.normal=Нормальная\nworld.info.difficulty.hard=Сложная\nworld.info.failed=Не удалось прочитать сведения о мире\nworld.info.game_version=Версия игры\nworld.info.last_played=Последний запуск игры\nworld.info.generate_features=Генерация строений\nworld.info.player=Сведения о игроке\nworld.info.player.food_level=Уровень голода\nworld.info.player.game_type=Режим игры\nworld.info.player.game_type.adventure=Приключение\nworld.info.player.game_type.creative=Творчество\nworld.info.player.game_type.spectator=Наблюдение\nworld.info.player.game_type.survival=Выживание\nworld.info.player.health=Здоровье\nworld.info.player.last_death_location=Место последней смерти\nworld.info.player.location=Расположение\nworld.info.player.spawn=Точка возрождения\nworld.info.player.xp_level=Уровень опыта\nworld.info.random_seed=Ключ генератора мира\nworld.locked=В эксплуатации\nworld.locked.failed=В настоящее время мир находится в эксплуатации. Закройте игру и попробуйте снова.\nworld.manage=Миры\nworld.manage.button=Управлять\nworld.manage.title=Мир - %s\nworld.name=Название мира\nworld.name.enter=Введите название мира\nworld.show_all=Показать все\n\nprofile=Папки с играми\nprofile.already_exists=Имя уже существует, используйте другое имя.\nprofile.default=Текущая папка\nprofile.home=Лаунчер Minecraft\nprofile.instance_directory=Папка с игрой\nprofile.instance_directory.choose=Выберите папку с игрой\nprofile.manage=Список папок для сборки\nprofile.name=Имя\nprofile.new=Новая папка\nprofile.title=Папки с играми\nprofile.selected=Выбранный\nprofile.use_relative_path=По возможности используйте относительный путь для игры\n\nrepositories.custom=Пользовательский репозиторий Maven (%s)\nrepositories.maven_central=Универсальный (Maven Central)\nrepositories.tencentcloud_mirror=Зеркало Mainland China (Репозиторий Tencent Cloud Maven)\nrepositories.chooser=Для работы HMCL требуется JavaFX.\\n\\\n  \\n\\\n  Нажмите «ОК», чтобы скачать JavaFX из указанного репозитория, или нажмите «Отмена» чтобы выйти.\\n\\\n  \\n\\\n  Репозитории:\nrepositories.chooser.title=Выберите зеркало для скачивания JavaFX\n\nresourcepack=Пакеты ресурсов\nresourcepack.download.title=Скачать пакет ресурсов - %1s\n\nreveal.in_file_manager=Открыть в файловый менеджер\n\nschematics=Схемы\nschematics.add.failed=Не удалось добавить файлы схем\nschematics.back_to=Назад на «%s»\nschematics.create_directory.prompt=Введите новое имя папки\nschematics.create_directory.failed=Не удалось создать папку\nschematics.create_directory.failed.already_exists=Папка уже существует\nschematics.create_directory.failed.empty_name=Название не может быть пустым\nschematics.create_directory.failed.invalid_name=Название содержит недопустимые символы\nschematics.info.description=Описание\nschematics.info.enclosing_size=Суммарный размер\nschematics.info.name=Название\nschematics.info.region_count=Регионов\nschematics.info.schematic_author=Автор\nschematics.info.time_created=Создан\nschematics.info.time_modified=Изменен\nschematics.info.total_blocks=Всего блоков\nschematics.info.total_volume=Общий объём\nschematics.info.version=Версия схемы\nschematics.manage=Схемы\nschematics.sub_items=%d элемент(ов)\n\nsearch=Поиск\nsearch.hint.chinese=Поиск на китайском и английском языках\nsearch.hint.english=Поиск только на английском языке\nsearch.enter=Вводите здесь\nsearch.sort=Сортировка\nsearch.first_page=Первый\nsearch.previous_page=Пред.\nsearch.next_page=След.\nsearch.last_page=Последний\nsearch.page_n=%d / %s\n\nselector.choose=Выберите\nselector.choose_file=Выберите файл\nselector.custom=Свой\n\nsettings=Настройки\n\nsettings.advanced=Расширенные настройки\nsettings.advanced.modify=Изменить расширенные настройки\nsettings.advanced.title=Расширенные настройки - %s\nsettings.advanced.custom_commands=Пользовательские команды\nsettings.advanced.custom_commands.hint=Пользовательские команды выполняются со следующими переменными окружения:\\n\\\n  \\  · $INST_NAME: название сборки.\\n\\\n  \\  · $INST_ID: название сборки.\\n\\\n  \\  · $INST_DIR: абсолютный путь к рабочей папке сборки.\\n\\\n  \\  · $INST_MC_DIR: абсолютный путь к текущей папке игры.\\n\\\n  \\  · $INST_JAVA: двоичный файл java, используемый для запуска.\\n\\\n  \\  · $INST_FORGE: устанавливается, если установлен Forge.\\n\\\n  \\  · $INST_NEOFORGE: устанавливается, если установлен NeoForge.\\n\\\n  \\  · $INST_LITELOADER: устанавливается, если установлен LiteLoader.\\n\\\n  \\  · $INST_OPTIFINE: устанавливается, если установлен OptiFine.\\n\\\n  \\  · $INST_FABRIC: устанавливается, если установлен Fabric.\\n\\\n  \\  · $INST_QUILT: устанавливается, если установлен Quilt.\nsettings.advanced.dont_check_game_completeness=Не проверять целостность игры\nsettings.advanced.dont_check_jvm_validity=Не проверять совместимость JVM\nsettings.advanced.dont_patch_natives=Не пытайтесь автоматически заменить нативные библиотеки\nsettings.advanced.environment_variables=Переменные среды\nsettings.advanced.game_dir.default=По умолчанию («.minecraft/»)\nsettings.advanced.game_dir.independent=Раздельно («.minecraft/versions/<имя сборки>/», кроме assets и библиотек)\nsettings.advanced.java_permanent_generation_space=Пространство PermGen\nsettings.advanced.java_permanent_generation_space.prompt=в МиБ\nsettings.advanced.jvm=Настройки JVM\nsettings.advanced.jvm_args=Аргументы JVM\nsettings.advanced.jvm_args.prompt=\\  · Если параметр, введённый в «Аргументы JVM», совпадает с параметром по умолчанию, он не будет добавлен.\\n\\\n  \\  · Введите любые параметры GC в «Аргументы JVM», параметр G1 по умолчанию будет отключен.\\n\\\n  \\  · Нажмите «Не добавлять аргументы JVM по умолчанию» ниже, чтобы запустить игру без добавления аргументов по умолчанию.\nsettings.advanced.launcher_visibility.close=Закрывать после запуска игры\nsettings.advanced.launcher_visibility.hide=Скрыть после запуска игры\nsettings.advanced.launcher_visibility.hide_and_reopen=Скрыть после запуска и показать после закрытия игры\nsettings.advanced.launcher_visibility.keep=Оставлять видимым\nsettings.advanced.launcher_visible=Видимость лаунчера\nsettings.advanced.minecraft_arguments=Аргументы запуска\nsettings.advanced.minecraft_arguments.prompt=По умолчанию\nsettings.advanced.natives_directory=Папка с нативными библиотеками\nsettings.advanced.natives_directory.choose=Выберите расоложение нужных нативных библиотек\nsettings.advanced.natives_directory.custom=Пользовательское\nsettings.advanced.natives_directory.default=По умолчанию\nsettings.advanced.natives_directory.default.version_id=<Название сборки>\nsettings.advanced.natives_directory.hint=Параметр предназначен только для пользователей Apple silicon или других официально не поддерживаемых платформ. Не изменяйте, если не знаете что делаете.\\n\\\n  \\n\\\n  Прежде чем продолжить, убедитесь что все библиотеки (например, lwjgl.dll, libopenal.so) находятся в нужной папке.\\n\\\n  Примечание: Рекомендуется использовать полностью английские пути для указанных файлов локальных библиотек, иначе игра может не запуститься.\nsettings.advanced.no_jvm_args=Не добавлять аргументы JVM по умолчанию\nsettings.advanced.precall_command=Команда перед запуском\nsettings.advanced.precall_command.prompt=Команды, которые необходимо выполнить перед запуском игры\nsettings.advanced.process_priority=Приоритет процесса\nsettings.advanced.process_priority.low=Низкий\nsettings.advanced.process_priority.below_normal=Ниже обычного\nsettings.advanced.process_priority.normal=Обычный\nsettings.advanced.process_priority.above_normal=Выше обычного\nsettings.advanced.process_priority.high=Высокий\nsettings.advanced.post_exit_command=Команда после выхода\nsettings.advanced.post_exit_command.prompt=Команды, которые необходимо выполнить после выхода из игры\nsettings.advanced.renderer=Рендерер\nsettings.advanced.renderer.default=По умолчанию\nsettings.advanced.renderer.default.desc=OpenGL\nsettings.advanced.renderer.d3d12=Mesa D3D12\nsettings.advanced.renderer.d3d12.desc=DirectX 12 (Низкая производительность и совместимость)\nsettings.advanced.renderer.llvmpipe=Mesa LLVMpipe\nsettings.advanced.renderer.llvmpipe.desc=ПО (Низкая производительность, лучшая совместимость)\nsettings.advanced.renderer.zink=Mesa Zink\nsettings.advanced.renderer.zink.desc=Vulkan (Лучшая производительность, низкая совместимость)\nsettings.advanced.server_ip=Адрес сервера\nsettings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры\nsettings.advanced.unsupported_system_options=Настройки, не применимые к текущей системе\nsettings.advanced.use_native_glfw=[Только для Linux/FreeBSD] Использовать системный GLFW\nsettings.advanced.use_native_openal=[Только для Linux/FreeBSD] Использовать системный OpenAL\nsettings.advanced.workaround=Обходные пути\nsettings.advanced.workaround.warning=Варианты обхода предназначены только для опытных пользователей. Изменение этих параметров может привести к вылету игры. Если не знаете, что делаете, то не изменяйте эти параметры.\nsettings.advanced.wrapper_launcher=Команда-оболочка\nsettings.advanced.wrapper_launcher.prompt=Позволяет запускать с помощью дополнительной программы-оболочки, такой как «optirun» в Linux.\n\nsettings.custom=Пользовательское\n\nsettings.game=Настройки\nsettings.game.copy_global=Копировать из глобальные настройки\nsettings.game.copy_global.copy_all=Копировать все\nsettings.game.copy_global.copy_all.confirm=Вы уверены, что хотите переписать текущие настройки сборки? Это действие нельзя отменить!\nsettings.game.current=Игра\nsettings.game.dimension=Разрешение\nsettings.game.exploration=Расположение\nsettings.game.fullscreen=Полноэкранный режим\nsettings.game.java_directory=Java\nsettings.game.java_directory.auto=Автовыбор\nsettings.game.java_directory.auto.not_found=Совместимая версия Java не была установлена.\nsettings.game.java_directory.bit=%s-разрядная\nsettings.game.java_directory.choose=Выбрать папку с Java\nsettings.game.java_directory.invalid=Неверныая папка Java\nsettings.game.java_directory.version=Укажите версию Java\nsettings.game.java_directory.template=%s (%s)\nsettings.game.management=Инструменты\nsettings.game.working_directory=Рабочая папка\nsettings.game.working_directory.choose=Выберите рабочую папку\n\nsettings.icon=Значок\n\nsettings.launcher=Настройки лаунчера\nsettings.launcher.appearance=Внешний вид\nsettings.launcher.common_path.tooltip=Это приложение поместит сюда все игровые assets и зависимости. Если в папке с игрой есть существующие библиотеки, лаунчер предпочтёт использовать их в первую очередь.\nsettings.launcher.debug=Отладка\nsettings.launcher.download=Скачать\nsettings.launcher.download.threads=Потоки\nsettings.launcher.download.threads.auto=Автоопределение\nsettings.launcher.download.threads.hint=Слишком большое количество потоков может привести к зависанию вашей системы, а скорость скачивания может зависеть от вашего интернет-провайдера и серверов скачивания. Не всегда большее количество потоков увеличивает скорость скачивания.\nsettings.launcher.download_source=Источник скачивания\nsettings.launcher.download_source.auto=Автовыбор зеркала скачивания\nsettings.launcher.enable_game_list=Показывать список сборок на главной странице\nsettings.launcher.font=Шрифт\nsettings.launcher.font.anti_aliasing=Сглаживание\nsettings.launcher.font.anti_aliasing.auto=Автоматический\nsettings.launcher.font.anti_aliasing.gray=Оттенки серого\nsettings.launcher.font.anti_aliasing.lcd=Субпиксель\nsettings.launcher.general=Общие\nsettings.launcher.language=Язык\nsettings.launcher.launcher_log.export=Экспорт логов лаунчера\nsettings.launcher.launcher_log.export.failed=Не удалось экспортировать логи\nsettings.launcher.launcher_log.export.success=Логи экспортированы в %s\nsettings.launcher.launcher_log.reveal=Открыть папку журнала\nsettings.launcher.log=Запись логов\nsettings.launcher.log.font=Шрифт\nsettings.launcher.proxy=Прокси\nsettings.launcher.proxy.authentication=Требуется авторизация\nsettings.launcher.proxy.default=Использовать прокси системы\nsettings.launcher.proxy.host=Хост\nsettings.launcher.proxy.http=HTTP\nsettings.launcher.proxy.none=Без прокси\nsettings.launcher.proxy.password=Пароль\nsettings.launcher.proxy.port=Порт\nsettings.launcher.proxy.socks=SOCKS\nsettings.launcher.proxy.username=Имя пользователя\nsettings.launcher.theme=Тема\nsettings.launcher.title_transparent=Прозрачная строка заголовка\nsettings.launcher.turn_off_animations=Отключить анимацию\nsettings.launcher.version_list_source=Список версий\nsettings.launcher.background.settings.opacity=Непрозрачность\n\nsettings.memory=Оперативная память\nsettings.memory.allocate.auto=Минимум %1$.1f ГиБ / Выделено %2$.1f ГиБ\nsettings.memory.allocate.auto.exceeded=Минимум %1$.1f ГиБ / Выделено %2$.1f ГиБ (Доступно %3$.1f ГиБ)\nsettings.memory.allocate.manual=Выделено %1$.1f ГиБ\nsettings.memory.allocate.manual.exceeded=Выделено %1$.1f ГиБ (Доступно %3$.1f ГиБ)\nsettings.memory.auto_allocate=Автовыделение\nsettings.memory.lower_bound=Мин. объём памяти\nsettings.memory.unit.mib=МиБ\nsettings.memory.used_per_total=Использовано %1$.1f ГиБ / Всего %2$.1f ГиБ\nsettings.physical_memory=Размер физической памяти\nsettings.show_log=Показать логи\nsettings.tabs.installers=Погрузчики\nsettings.take_effect_after_restart=Применится после перезапуска\nsettings.type=Тип настроек экземпляра\nsettings.type.global=Глобальные настройки (совместно используется экземплярами)\nsettings.type.global.manage=Глобальные настройки\nsettings.type.global.edit=Изменить глобальные настройки\nsettings.type.special.enable=Включить раздельные настройки для сборки\nsettings.type.special.edit=Изменить текущие настройки сборки\nsettings.type.special.edit.hint=В текущей сборке «%s» включен параметр «Специфические для сборки настройки». Все настройки на этой странице НЕ влияют на эту сборку. Нажмите здесь, чтобы изменить его настройки.\n\nsponsor=Спонсоры\nsponsor.bmclapi=Загрузки для материкового Китая предоставляются BMCLAPI. Нажмите здесь для получения подробностей.\nsponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Для получения подробностей нажмите здесь.\n\nsystem.architecture=Архитектура\nsystem.operating_system=Операционная система\n\nunofficial.hint=Вы используете неофициальную версию HMCL. Мы не можем гарантировать безопасность.\n\nupdate=Обновление\nupdate.accept=Обновить\nupdate.changelog=Изменения\nupdate.channel.dev=Бета\nupdate.channel.dev.hint=Вы используете бета-версию, которая может включать некоторые дополнительные функции по сравнению с релизной версией, используемой только для тестирования. Бета-версия может быть нестабильной!\\n\\\n  \\n\\\n  Если вы встретили какие-то проблемы, вы можете зайти на <a href=\"hmcl://settings/feedback\">страницу обратной связи</a>.\nupdate.channel.dev.title=Подсказки для бета-версии\nupdate.channel.nightly=Альфа\nupdate.channel.nightly.hint=Вы используете альфа-версию, которая может включать некоторые дополнительные функциональные возможности по сравнению с бета-версией и версией релиза, используемой только для тестирования. Альфа-версия может быть нестабильной!\\n\\\n  \\n\\\n  Если вы встретили какие-то проблемы, вы можете зайти на <a href=\"hmcl://settings/feedback\">страницу обратной связи</a>.\nupdate.channel.nightly.title=Подсказки для альфа-версии\nupdate.channel.stable=Релиз\nupdate.checking=Проверка наличия обновлений\nupdate.failed=Не удалось выполнить обновление\nupdate.found=Доступно обновление!\nupdate.newest_version=Последняя версия: %s\nupdate.bubble.title=Доступно обновление: %s\nupdate.bubble.subtitle=Кликните здесь для обновления\nupdate.note=Предупреждение: Бета-версии могут иметь больше функциональных возможностей и исправлений ошибок, но также и больше возможных ошибок.\nupdate.latest=Это последняя версия.\nupdate.no_browser=Не удалось открыть браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.\nupdate.tooltip=Обновить\n\nversion=Игры\nversion.name=Название сборки\nversion.cannot_read=Невозможно найти версию игры. Невозможно продолжить автоматическую установку.\nversion.empty=Нет сборок\nversion.empty.add=Установить новую сборок\nversion.empty.hint=Нет сборок.\\nВы можете попробовать перейти в другую папку с игрой или нажать здесь, чтобы скачать игру.\nversion.game.all=Все\nversion.game.april_fools=День смеха\nversion.game.old=Исторические\nversion.game.release=Релиз\nversion.game.releases=Релизы\nversion.game.snapshot=Снапшот\nversion.game.snapshots=Снапшоты\nversion.game.type=Тип\nversion.launch=Играть\nversion.launch.empty=Играть\nversion.launch.empty.installing=Установка игры\nversion.launch.empty.tooltip=Установить и запустить последнюю официальную версию игры.\nversion.launch.test=Тестовая игра\nversion.switch=Сменить сборку\nversion.launch_script=Создать сценарий запуска\nversion.launch_script.failed=Невозможно создать сценарий запуска.\nversion.launch_script.save=Сохранить сценарий запуска\nversion.launch_script.success=Создан сценарий %s.\nversion.manage=Все сборки\nversion.manage.clean=Очистить журнал игры\nversion.manage.clean.tooltip=Очистить файлы в папках «logs» и «crash-reports».\nversion.manage.duplicate=Скопировать экземпляр сборки\nversion.manage.duplicate.duplicate_save=Скопировать миры\nversion.manage.duplicate.prompt=Введите новое название для этой сборки\nversion.manage.duplicate.confirm=Скопированная сборка будет иметь копию всех файлов в папке сборки(«.minecraft/versions/<имя сборки>»), с изолированной рабочей папкой и настройками.\nversion.manage.manage=Изменить сборку\nversion.manage.manage.title=Изменить сборку - %1s\nversion.manage.redownload_assets_index=Обновить файлы игровых активов\nversion.manage.remove=Удалить\nversion.manage.remove.confirm.trash=Удалить %s? Вы можете восстановить эту сборку с именем %s из корзины системы.\nversion.manage.remove.confirm.independent=Поскольку эта сборка находится в режиме разделения, удаление этой сборки приведет к удалению всех миров, принадлежащих этой сборке. Удалить %s?\nversion.manage.remove_assets=Удалить файлы игровых активов\nversion.manage.remove_libraries=Удалить файлы библиотек\nversion.manage.rename=Переименовать\nversion.manage.rename.message=Введите новое название для этой сборки\nversion.manage.rename.fail=Не удалось переименовать сборку. Возможно, некоторые файлы уже используются или в их имени содержится недопустимый символ.\nversion.search=Название\nversion.search.prompt=Введите название версии для поиска\nversion.settings=Настройки\nversion.update=Обновить модпак\n\nwiki.tooltip=Страница Minecraft Wiki\nwiki.version.game=https://ru.minecraft.wiki/w/%s_(Java_Edition)\nwiki.version.game.snapshot=https://ru.minecraft.wiki/w/%s_(Java_Edition)\n\nwizard.prev=< Пред.\nwizard.failed=Не удалось\nwizard.finish=Завершено\nwizard.next=След. >\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/lang/I18N_uk.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Contributors: BANSAFAn\n\nabout=Про програму\nabout.copyright=Авторське право\nabout.author=Автор\nabout.author.statement=bilibili @huanghongxun\nabout.claim=Ліцензійна угода з кінцевим користувачем (EULA)\nabout.claim.statement=Натисніть це посилання для перегляду повного тексту.\nabout.dependency=Сторонні бібліотеки\nabout.legal=Правове визнання\nabout.thanks_to=Подяки\nabout.thanks_to.bangbang93.statement=За надання дзеркала завантаження BMCLAPI. Будь ласка, подумайте про пожертвування!\nabout.thanks_to.burningtnt.statement=Надання великої технічної підтримки HMCL.\nabout.thanks_to.contributors=Усі учасники на GitHub\nabout.thanks_to.contributors.statement=Без чудового спільнотного руху з відкритим кодом, HMCL не дійшла б так далеко.\nabout.thanks_to.gamerteam.statement=За надання типового фонового зображення.\nabout.thanks_to.glavo.statement=Відповідальний за підтримку HMCL.\nabout.thanks_to.zekerzhayard.statement=Надання великої технічної підтримки HMCL.\nabout.thanks_to.zkitefly.statement=Відповідальний за підтримку документації HMCL.\nabout.thanks_to.mcbbs=MCBBS (Китайський форум Minecraft)\nabout.thanks_to.mcbbs.statement=За надання дзеркала завантаження mcbbs.net для користувачів материкового Китаю. (Більше не доступне)\nabout.thanks_to.mcim=mcmod-info-mirror\nabout.thanks_to.mcim.statement=За надання служби прискорення кешу інформації модів для користувачів материкового Китаю.\nabout.thanks_to.mcmod=MCMod (mcmod.cn)\nabout.thanks_to.mcmod.statement=За надання спрощених китайських перекладів та вікі для різних модів.\nabout.thanks_to.red_lnn.statement=За надання типового фонового зображення.\nabout.thanks_to.shulkersakura.statement=За надання логотипу для HMCL.\nabout.thanks_to.users=Учасники групи користувачів HMCL\nabout.thanks_to.users.statement=Дякуємо за пожертви, повідомлення про помилки тощо.\nabout.thanks_to.yushijinhun.statement=За надання підтримки, пов'язаної з authlib-injector.\nabout.open_source=Відкрите джерело\nabout.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)\n\naccount=Облікові записи\naccount.cape=Плащ\naccount.character=Гравець\naccount.choose=Оберіть гравця\naccount.create=Додати обліковий запис\naccount.create.microsoft=Додати обліковий запис Microsoft\naccount.create.offline=Додати автономний обліковий запис\naccount.create.authlibInjector=Додати обліковий запис authlib-injector\naccount.email=Електронна пошта\naccount.failed=Не вдалося оновити обліковий запис.\naccount.failed.character_deleted=Гравця вже видалено.\naccount.failed.connect_authentication_server=Не вдалося підключитися до сервера автентифікації, можливо, ваше мережеве з'єднання перервано.\naccount.failed.connect_injector_server=Не вдалося підключитися до сервера автентифікації. Перевірте мережу та переконайтеся, що ви ввели правильну URL-адресу.\naccount.failed.injector_download_failure=Не вдалося завантажити authlib-injector. Перевірте мережу або спробуйте переключитися на інше джерело завантаження.\naccount.failed.invalid_credentials=Неправильний пароль або перевищено ліміт запитів. Спробуйте пізніше.\naccount.failed.invalid_password=Недійсний пароль.\naccount.failed.invalid_token=Спробуйте увійти знову.\naccount.failed.migration=Ваш обліковий запис потрібно перенести на обліковий запис Microsoft. Якщо ви це вже зробили, вам потрібно знову увійти за допомогою перенесеного облікового запису Microsoft.\naccount.failed.no_character=До цього облікового запису не прив'язано жодного персонажа.\naccount.failed.server_disconnected=Не вдалося підключитися до сервера автентифікації. Ви можете увійти в автономному режимі або спробувати увійти знову.\\n\\\n   Якщо проблема не зникає після кількох спроб, спробуйте знову увійти до облікового запису.\naccount.failed.server_response_malformed=Недійсна відповідь сервера. Можливо, сервер автентифікації не працює.\naccount.failed.ssl=Під час підключення до сервера сталася помилка SSL. Спробуйте оновити Java.\naccount.failed.dns=Під час підключення до сервера сталася помилка SSL. Можливо, DNS неправильно розв'язується. Спробуйте змінити DNS-сервер або використовувати проксі-сервіс.\naccount.failed.wrong_account=Ви увійшли до неправильного облікового запису.\naccount.hmcl.hint=Вам потрібно натиснути \"Увійти\" і завершити процес у відкритому вікні браузера.\naccount.injector.add=Новий сервер автентифікації\naccount.injector.empty=Немає (Ви можете натиснути \"+\", щоб додати)\naccount.injector.http=Попередження: Цей сервер використовує небезпечний протокол HTTP. Будь-хто, хто перехопить ваше з'єднання, зможе побачити ваші облікові дані у відкритому вигляді.\naccount.injector.link.homepage=Домашня сторінка\naccount.injector.link.register=Реєстрація\naccount.injector.server=Сервер автентифікації\naccount.injector.server_url=URL сервера\naccount.injector.server_name=Назва сервера\naccount.login=Увійти\naccount.login.hint=Ми ніколи не зберігаємо ваш пароль.\naccount.login.skip=Увійти автономно\naccount.login.retry=Повторити\naccount.login.refresh=Увійти знову\naccount.login.refresh.microsoft.hint=Вам потрібно знову увійти до свого облікового запису Microsoft, оскільки авторизація облікового запису недійсна.\naccount.login.restricted=Увійдіть до свого облікового запису Microsoft, щоб увімкнути цю функцію\naccount.logout=Вийти\naccount.register=Зареєструватися\naccount.manage=Список облікових записів\naccount.copy_uuid=Копіювати UUID облікового запису\naccount.methods=Тип входу\naccount.methods.authlib_injector=authlib-injector\naccount.methods.microsoft=Microsoft\naccount.methods.microsoft.birth=Як змінити дату народження облікового запису\naccount.methods.microsoft.close_page=Авторизація облікового запису Microsoft тепер завершена.\\n\\\n   \\n\\\n   Нам залишилося виконати ще трохи роботи, але ви можете безпечно закрити цю вкладку зараз.\naccount.methods.microsoft.deauthorize=Скасувати авторизацію\naccount.methods.microsoft.logging_in=Вхід...\naccount.methods.microsoft.makegameidsettings=Створити профіль / Редагувати ім'я профілю\naccount.methods.microsoft.profile=Профіль облікового запису\naccount.methods.microsoft.purchase=Купити Minecraft\naccount.methods.microsoft.snapshot.website=Офіційний сайт\naccount.methods.offline=Автономно\naccount.methods.offline.name.special_characters=Використовуйте лише літери, цифри та підкреслення (макс. 16 символів)\naccount.methods.offline.name.invalid=Рекомендується використовувати лише англійські літери, цифри та підкреслення для імені користувача, довжина не повинна перевищувати 16 символів.\\n\\\n   \\n\\  · Допустимі: HuangYu, huang_Yu, Huang_Yu_123;\\n\\\n   \\n\\  · Недопустимі: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\\n\\\n   \\n\\\n   Використання недопустимого імені користувача не дозволить вам приєднатися до більшості серверів і може конфліктувати з деякими модами, що призведе до збою гри.\naccount.methods.offline.uuid=UUID\naccount.methods.offline.uuid.hint=UUID - це унікальний ідентифікатор для гравців Minecraft, і кожен лаунчер може генерувати UUID по-різному. Зміна його на той, що генерується іншими лаунчерами, дозволяє вам зберегти предмети у вашому інвентарі автономного облікового запису.\\n\\\n   \\n\\\n   Ця опція призначена лише для досвідчених користувачів. Ми не рекомендуємо змінювати цю опцію, якщо ви не знаєте, що робите.\naccount.methods.offline.uuid.malformed=Недійсний формат.\naccount.methods.ban_query=Запит блокування\naccount.missing=Немає облікових записів\naccount.missing.add=Натисніть тут, щоб додати.\naccount.move_to_global=Перетворити на глобальний обліковий запис\\n\\\n   Інформація про обліковий запис буде збережена у файлі конфігурації поточного каталогу користувача системи.\naccount.move_to_portable=Перетворити на портативний обліковий запис\\n\\\n   Інформація про обліковий запис буде збережена у файлі конфігурації в тому ж каталозі, що й HMCL.\naccount.not_logged_in=Не ввійшли\naccount.password=Пароль\naccount.portable=Портативний\naccount.skin=Скін\naccount.skin.file=Файл скіна\naccount.skin.model=Модель\naccount.skin.model.default=Класична\naccount.skin.model.slim=Тонка\naccount.skin.type.alex=Алекс\naccount.skin.type.csl_api=Blessing Skin\naccount.skin.type.csl_api.location=Адреса\naccount.skin.type.csl_api.location.hint=URL CustomSkinAPI\naccount.skin.type.little_skin=LittleSkin\naccount.skin.type.little_skin.hint=Вам потрібно створити гравця з тим же ім'ям гравця, що й у вашого автономного облікового запису, на сайті постачальника скінів. Ваш скін тепер буде встановлено на скін, призначений вашому гравцю на сайті постачальника скінів.\naccount.skin.type.local_file=Локальний файл скіна\naccount.skin.type.steve=Стів\naccount.skin.upload=Завантажити/Редагувати скін\naccount.skin.upload.failed=Не вдалося завантажити скін.\naccount.skin.invalid_skin=Недійсний файл скіна.\naccount.username=Ім'я користувача\n\narchive.author=Автор(и)\narchive.date=Дата публікації\narchive.file.name=Ім'я файлу\narchive.version=Версія\n\nassets.download=Завантаження ресурсів\nassets.download_all=Перевірка цілісності ресурсів\nassets.index.malformed=Індексні файли завантажених ресурсів пошкоджені. Ви можете вирішити цю проблему, натиснувши \"Керування → Оновити ресурси гри\" на сторінці \"Редагувати екземпляр\".\nbutton.cancel=Скасувати\nbutton.change_source=Змінити джерело завантаження\nbutton.clear=Очистити\nbutton.copy_and_exit=Копіювати та вийти\nbutton.delete=Видалити\nbutton.edit=Редагувати\nbutton.install=Встановити\nbutton.export=Експортувати\nbutton.no=Ні\nbutton.ok=ОК\nbutton.ok.countdown=ОК (%d)\nbutton.refresh=Оновити\nbutton.remove=Видалити\nbutton.remove.confirm=Ви впевнені, що хочете назавжди видалити це? Цю дію не можна скасувати!\nbutton.retry=Повторити\nbutton.save=Зберегти\nbutton.save_as=Зберегти як\nbutton.select_all=Вибрати все\nbutton.view=Переглянути\nbutton.yes=Так\n\ncontact=Зворотний зв'язок\ncontact.chat=Приєднатися до групового чату\ncontact.chat.discord=Discord\ncontact.chat.discord.statement=Ласкаво просимо приєднатися до нашого сервера Discord.\ncontact.chat.qq_group=Група користувачів QQ HMCL\ncontact.chat.qq_group.statement=Ласкаво просимо приєднатися до нашої групи користувачів QQ.\ncontact.feedback=Канал зворотного зв'язку\ncontact.feedback.github=GitHub Issues\ncontact.feedback.github.statement=Надіслати проблему на GitHub.\n\ncolor.recent=Рекомендовані\ncolor.custom=Власний колір\n\ncrash.NoClassDefFound=Перевірте цілісність цього програмного забезпечення або спробуйте оновити Java.\ncrash.user_fault=Лаунчер зазнав збою через пошкоджене середовище Java або системи. Переконайтеся, що ваша Java або ОС встановлені належним чином.\n\ncurse.category.0=Усі\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4471\ncurse.category.4474=Наукова фантастика\ncurse.category.4481=Малі / Легкі\ncurse.category.4483=Бойові\ncurse.category.4477=Міні-ігри\ncurse.category.4478=Квести\ncurse.category.4484=Багатокористувацькі\ncurse.category.4476=Дослідження\ncurse.category.4736=Skyblock\ncurse.category.4475=Пригоди та RPG\ncurse.category.4487=FTB\ncurse.category.4480=Базовані на карті\ncurse.category.4479=Хардкор\ncurse.category.4482=Дуже великі\ncurse.category.4472=Технології\ncurse.category.4473=Магія\ncurse.category.5128=Vanilla+\ncurse.category.7418=Жахи\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5299=Освіта\ncurse.category.5232=Galacticraft\ncurse.category.5129=Vanilla+\ncurse.category.5189=Утиліти та QOL\ncurse.category.6814=Продуктивність\ncurse.category.6954=Integrated Dynamics\ncurse.category.6484=Create\ncurse.category.6821=Виправлення помилок\ncurse.category.6145=Skyblock\ncurse.category.5190=QOL\ncurse.category.5191=Утиліти та QOL\ncurse.category.5192=FancyMenu\ncurse.category.423=Карти та інформація\ncurse.category.426=Додатки\ncurse.category.434=Броня, інструменти та зброя\ncurse.category.409=Структури\ncurse.category.4485=Blood Magic\ncurse.category.420=Сховище\ncurse.category.429=Industrial Craft\ncurse.category.419=Магія\ncurse.category.412=Технології\ncurse.category.4557=Redstone\ncurse.category.428=Tinker's Construct\n# '\ncurse.category.414=Транспорт гравців\ncurse.category.4486=Lucky Blocks\ncurse.category.432=Buildcraft\ncurse.category.418=Генетика\ncurse.category.4671=Інтеграція Twitch\ncurse.category.5314=KubeJS\ncurse.category.408=Руди та ресурси\ncurse.category.4773=CraftTweaker\ncurse.category.430=Thaumcraft\ncurse.category.422=Пригоди та RPG\ncurse.category.413=Обробка\ncurse.category.417=Енергія\ncurse.category.415=Транспорт енергії, рідин та предметів\ncurse.category.433=Forestry\ncurse.category.425=Різне\ncurse.category.4545=Applied Energistics 2\ncurse.category.416=Фермерство\ncurse.category.421=API та бібліотеки\ncurse.category.4780=Fabric\ncurse.category.424=Косметика\ncurse.category.406=Генерація світу\ncurse.category.435=Утиліти сервера\ncurse.category.411=Моби\ncurse.category.407=Біоми\ncurse.category.427=Thermal Expansion\ncurse.category.410=Виміри\ncurse.category.436=Їжа\ncurse.category.4558=Redstone\ncurse.category.4843=Автоматизація\ncurse.category.4906=MCreator\ncurse.category.7669=Twilight Forest\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/12\ncurse.category.5244=Шрифтові пакети\ncurse.category.5193=Пакети даних\ncurse.category.399=Стімпанк\ncurse.category.396=128x\ncurse.category.398=512x та вище\ncurse.category.397=256x\ncurse.category.405=Різне\ncurse.category.395=64x\ncurse.category.400=Фото реалістичні\ncurse.category.393=16x\ncurse.category.403=Традиційні\ncurse.category.394=32x\ncurse.category.404=Анімовані\ncurse.category.4465=Підтримка модів\ncurse.category.402=Середньовіччя\ncurse.category.401=Сучасність\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/17\ncurse.category.4464=Модифікований світ\ncurse.category.250=Карта гри\ncurse.category.249=Створення\ncurse.category.251=Паркур\ncurse.category.253=Виживання\ncurse.category.248=Пригоди\ncurse.category.252=Головоломки\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4546\ncurse.category.4551=Hardcore Questing Mode\ncurse.category.4548=Lucky Blocks\ncurse.category.4556=Прогресія\ncurse.category.4752=Building Gadgets\ncurse.category.4553=CraftTweaker\ncurse.category.4554=Рецепти\ncurse.category.4549=Посібник\ncurse.category.4547=Конфігурація\ncurse.category.4550=Квести\ncurse.category.4555=Генерація світу\ncurse.category.4552=Скрипти\n\ncurse.sort.author=Автор\ncurse.sort.date_created=Дата створення\ncurse.sort.last_updated=Останнє оновлення\ncurse.sort.name=Назва\ncurse.sort.popularity=Популярність\ncurse.sort.total_downloads=Всього завантажень\n\ndatetime.format=dd.MM.yyyy, HH:mm:ss\n\ndownload=Завантажити\ndownload.hint=Встановіть ігри та модпаки або завантажте моди, пакети ресурсів, шейдери та світи.\ndownload.code.404=Файл \"%s\" не знайдено на віддаленому сервері.\ndownload.content=Додатки\ndownload.shader=Шейдери\ndownload.curseforge.unavailable=Ця збірка HMCL не підтримує доступ до CurseForge. Використовуйте офіційну збірку для доступу до CurseForge.\ndownload.existing=Файл не може бути збережений, оскільки він вже існує. Ви можете натиснути \"Зберегти як\", щоб зберегти файл в іншому місці.\ndownload.external_link=Відвідати веб-сайт завантаження\ndownload.failed=Не вдалося завантажити \"%1$s\", код відповіді: %2$d.\ndownload.failed.empty=Немає доступних версій. Натисніть тут, щоб повернутися.\ndownload.failed.no_code=Не вдалося завантажити\ndownload.failed.refresh=Не вдалося отримати список версій. Натисніть тут, щоб повторити спробу.\ndownload.game=Нова гра\ndownload.provider.bmclapi=BMCLAPI\ndownload.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/\ndownload.provider.mojang=Офіційне\ndownload.provider.mojang.desc=OptiFine надається BMCLAPI\ndownload.provider.official=З офіційних джерел\ndownload.provider.balanced=З найшвидшого доступного\ndownload.provider.mirror=З дзеркала\ndownload.java=Завантаження Java\ndownload.java.process=Процес завантаження Java\ndownload.javafx=Завантаження залежностей для лаунчера...\ndownload.javafx.notes=Ми зараз завантажуємо залежності для HMCL з Інтернету. \\n\\\n   Ви можете натиснути \"Змінити джерело завантаження\", щоб вибрати джерело завантаження, або\\nнатиснути \"Скасувати\", щоб зупинити та вийти.\\n\\\n   Примітка: Якщо ваша швидкість завантаження занадто повільна, ви можете спробувати переключитися на інше дзеркало.\ndownload.javafx.component=Завантаження модуля \"%s\"\ndownload.javafx.prepare=Підготовка до завантаження\ndownload.speed.byte_per_second=%d Б/сек\ndownload.speed.kibibyte_per_second=%.1f КiБ/сек\ndownload.speed.megabyte_per_second=%.1f МiБ/сек\n\nexception.access_denied=HMCL не може отримати доступ до файлу \"%s\". Можливо, він заблокований іншим процесом.\\n\\\n   \\n\\\n   Для користувачів Windows ви можете відкрити \"Монітор ресурсів\", щоб перевірити, чи не використовується він іншим процесом. Якщо так, ви можете спробувати знову після завершення цього процесу.\\n\\\n   Якщо ні, перевірте, чи має ваш обліковий запис достатні дозволи для доступу до нього.\nexception.artifact_malformed=Не вдається перевірити цілісність завантажених файлів.\nexception.ssl_handshake=Не вдалося встановити SSL-з'єднання, оскільки SSL-сертифікат відсутній у поточній інсталяції Java. Ви можете спробувати відкрити HMCL іншою інсталяцією Java і спробувати знову.\nexception.dns.pollution=Не вдалося встановити SSL-з'єднання. Можливо, DNS неправильно розв'язується. Спробуйте змінити DNS-сервер або використовувати проксі-сервіс.\n\nextension.bat=Пакетний файл Windows\nextension.png=Файл зображення\nextension.ps1=Скрипт PowerShell Windows\nextension.sh=Скрипт оболонки\n\nextension.datapack=Datapack\nextension.mod=Файл мода\nextension.world=Архів світу\n\nfatal.create_hmcl_current_directory_failure=Hello Minecraft! Лаунчер не може створити каталог HMCL (%s). Перемістіть HMCL в інше місце та відкрийте його знову.\nfatal.javafx.incomplete=Середовище JavaFX неповне. \\n\\\n   Спробуйте замінити вашу Java або перевстановити OpenJFX.\nfatal.javafx.missing=Відсутнє середовище JavaFX. Відкрийте Hello Minecraft! Лаунчер за допомогою Java, яка включає OpenJFX.\nfatal.config_change_owner_root=Ви використовуєте обліковий запис root для відкриття Hello Minecraft! Лаунчера. Це може завадити вам відкрити HMCL іншим обліковим записом у майбутньому. \\n\\\n   Все одно продовжити?\nfatal.config_in_temp_dir=Ви відкриваєте Hello Minecraft! Лаунчер у тимчасовому каталозі. Ваші налаштування та ігрові дані можуть бути втрачені. \\n\\\n   Рекомендується перемістити HMCL в інше місце та відкрити його знову. \\n\\\n   Все одно продовжити?\nfatal.config_loading_failure=Не вдається завантажити файли конфігурації.\\n\\\n   Переконайтеся, що Hello Minecraft! Лаунчер має права на читання та запис до \"%s\" та файлів у ньому.\\n\\\n   Для macOS спробуйте помістити HMCL кудись із дозволами, відмінними від \"Робочий стіл\", \"Завантаження\" та \"Документи\", і спробуйте знову.\nfatal.config_loading_failure.unix=Hello Minecraft! Лаунчер не зміг завантажити файл конфігурації, оскільки він був створений користувачем \"%1$s\".\\n\\\n   Відкрийте HMCL як користувач root (не рекомендується) або виконайте наступну команду в терміналі, щоб змінити власника файлу конфігурації на поточного користувача:\\n%2$s\nfatal.mac_app_translocation=Hello Minecraft! Лаунчер ізольовано до тимчасового каталогу ОС через механізми безпеки macOS.\\n\\\n   Перемістіть HMCL в інший каталог перед спробою відкриття. Інакше ваші налаштування та ігрові дані можуть бути втрачені після перезавантаження.\\n\\\n   Все одно продовжити?\nfatal.migration_requires_manual_reboot=Hello Minecraft! Лаунчер було оновлено. Перезапустіть лаунчер.\nfatal.apply_update_failure=Вибачте, але Hello Minecraft! Лаунчер не може оновитися автоматично.\\n\\\n   Ви можете оновити вручну, завантаживши новішу версію лаунчера з %s.\\n\\\n   Якщо проблема не зникає, подумайте про те, щоб повідомити нас про це.\nfatal.apply_update_need_win7=Hello Minecraft! Лаунчер не може автоматично оновитися на Windows XP/Vista. \\n\\\n   Ви можете оновити вручну, завантаживши новішу версію лаунчера з %s.\nfatal.samba=Якщо ви відкрили Hello Minecraft! Лаунчер з мережевого диска Samba, деякі функції можуть не працювати. Спробуйте оновити вашу Java або перемістити лаунчер в інший каталог.\nfatal.illegal_char=Ваш шлях користувача містить недопустимий символ \"=\". Ви не зможете використовувати authlib-injector або змінювати скін вашого автономного облікового запису.\nfatal.unsupported_platform=Minecraft ще не повністю підтримується на вашій платформі, тому можливі відсутні функції або навіть неможливість запуску гри.\\n\\\n   \\n\\\n   Якщо ви не можете запустити Minecraft версії 1.17 і новіші, спробуйте змінити «Рендерер» на «Mesa LLVMpipe» у розділі «Глобальні/налаштування екземпляра → Розширені налаштування», щоб використовувати рендеринг через CPU для кращої сумісності.\nfatal.unsupported_platform.loongarch=Hello Minecraft! Лаунчер надав підтримку платформи Loongson.\\n\\\n   Якщо у вас виникли проблеми під час гри, ви можете відвідати https://docs.hmcl.net/groups.html для отримання допомоги.\nfatal.unsupported_platform.macos_arm64=Hello Minecraft! Лаунчер надав підтримку платформи Apple silicon, використовуючи нативну ARM Java для запуску ігор для отримання плавнішого ігрового досвіду.\\n\\\n   Якщо у вас виникли проблеми під час гри, запуск гри з Java на основі архітектури x86-64 може забезпечити кращу сумісність.\nfatal.unsupported_platform.windows_arm64=Hello Minecraft! Лаунчер надав нативну підтримку платформи Windows on Arm. Якщо у вас виникли проблеми під час гри, спробуйте запустити гру з Java на основі архітектури x86.\\n\\\n   Якщо ви використовуєте платформу <b>Qualcomm</b>, вам може знадобитися встановити <a href=\"ms-windows-store://pdp/?productid=9NQPSL29BFFF\">Пакет сумісності OpenGL</a> перед грою.\\n\\\n   Натисніть посилання, щоб перейти до Microsoft Store та встановити пакет сумісності.\n\nfile=Файл\n\nfolder.config=Конфігурації\nfolder.game=Робочий каталог\nfolder.logs=Журнали\nfolder.mod=Моди\nfolder.resourcepacks=Пакети ресурсів\nfolder.shaderpacks=Пакети шейдерів\nfolder.saves=Збереження\nfolder.schematics=Схематики\nfolder.screenshots=Скріншоти\nfolder.world=Каталог світу\n\ngame=Ігри\ngame.crash.feedback=<b>Будь ласка, не діліться скріншотами або фотографіями цього інтерфейсу з іншими!</b> Якщо ви просите допомоги в інших, натисніть <b>\"Експортувати журнали збоїв\"</b> і надішліть експортований файл іншим для аналізу.\ngame.crash.info=Інформація про збій\ngame.crash.reason=Причина збою\ngame.crash.reason.analyzing=Аналіз...\ngame.crash.reason.multiple=Виявлено кілька причин:\\n\\n\ngame.crash.reason.bootstrap_failed=Гра зазнала збою через мод \"%1$s\". \\n\\\n   \\n\\\n   Ви можете спробувати видалити або оновити його.\ngame.crash.reason.config=Гра зазнала збою, оскільки мод \"%1$s\" не зміг розібрати свій файл конфігурації \"%2$s\".\ngame.crash.reason.debug_crash=Гра зазнала збою, оскільки ви викликали її вручну. Тож ви, ймовірно, знаєте, чому :)\ngame.crash.reason.duplicated_mod=Гра не може продовжувати роботу через дублікати модів \"%1$s\". \\n\\\n   %2$s \\n\\\n   Кожен мод можна встановити лише один раз. Видаліть дублікат мода та спробуйте знову.\ngame.crash.reason.modmixin_failure=Гра зазнала збою, оскільки деякі моди не вдалося ввести. \\n\\\n   Це, як правило, означає, що мод має помилку або несумісний з поточним середовищем. \\n\\\n   Ви можете перевірити журнал, щоб знайти помилковий мод.\ngame.crash.reason.mod_repeat_installation=Гра зазнала збою через дублікати модів. \\n\\\n   Кожен мод можна встановити лише один раз. Видаліть дублікат мода та знову запустіть гру.\ngame.crash.reason.forge_error=Forge/NeoForge може надати інформацію про помилку. \\n\\\n   Ви можете переглянути журнал та відповідно обробити його відповідно до інформації про помилку в звіті. \\n\\\n   Якщо ви не бачите повідомлення про помилку, ви можете переглянути звіт про помилку, щоб зрозуміти, як сталася помилка. \\n\\\n   %1$s\ngame.crash.reason.mod_resolution0=Гра зазнала збою через проблеми з модами. Ви можете перевірити журнали, щоб знайти помилкові моди.\ngame.crash.reason.mixin_apply_mod_failed=Гра зазнала збою, оскільки міксин не вдалося застосувати до мода \"%1$s\". \\n\\\n   Ви можете спробувати видалити або оновити мод для вирішення проблеми.\ngame.crash.reason.java_version_is_too_high=Гра зазнала збою, оскільки версія Java занадто нова для продовження роботи. \\n\\\n   Використовуйте попередню основну версію Java у \"Глобальні/Специфічні налаштування екземпляра → Java\", а потім запустіть гру. \\n\\\n   Якщо ні, ви можете завантажити його з <a href=\"https://www.java.com/download/\">java.com (Java 8)</a> або <a href=\"https://bell-sw.com/pages/downloads/#downloads\">BellSoft Liberica Full JRE (Java 17)</a> та інших дистрибутивів для завантаження та встановлення (перезапустіть лаунчер після встановлення).\ngame.crash.reason.need_jdk11=Гра зазнала збою через неприйнятну версію Java. \\n\\\n   Вам потрібно завантажити та встановити Java 11 та встановити її у \"Глобальні/Специфічні налаштування екземпляра → Java\".\ngame.crash.reason.mod_name=Гра зазнала збою через проблеми з іменем файлу мода. \\n\\\n   Імена файлів модів повинні використовувати лише англійські літери (A~Z, a~z), цифри (0~9), дефіси (-), підкреслення (_) та крапки (.) у половинному розмірі. \\n\\\n   Перейдіть до каталогу модів і змініть усі несумісні імена файлів модів, використовуючи сумісні символи вище.\ngame.crash.reason.incomplete_forge_installation=Гра не може продовжувати роботу через неповне встановлення Forge/NeoForge. \\n\\\n   Перевстановіть Forge/NeoForge у \"Редагувати екземпляр → Завантажувачі\".\ngame.crash.reason.fabric_version_0_12=Завантажувач Fabric 0.12 або новіший несумісний з поточними встановленими модами. Вам потрібно понизити його до 0.11.7.\ngame.crash.reason.fabric_warnings=Завантажувач Fabric попередив: \\n\\\n   %1$s\ngame.crash.reason.file_already_exists=Гра зазнала збою, оскільки файл \"%1$s\" вже існує. \\n\\\n   Ви можете спробувати створити резервну копію та видалити цей файл, а потім знову запустити гру.\ngame.crash.reason.file_changed=Гра зазнала збою через невдалу перевірку файлу. \\n\\\n   Якщо ви змінили файл Minecraft.jar, вам потрібно скасувати зміну або повторно завантажити гру.\ngame.crash.reason.gl_operation_failure=Гра зазнала збою через деякі моди, шейдери або пакети ресурсів/текстур. \\n\\\n   Вимкніть моди, шейдери або пакети ресурсів/текстур, які ви використовуєте, а потім спробуйте знову.\ngame.crash.reason.graphics_driver=Гра зазнала збою через проблему з вашим графічним драйвером. \\n\\\n   Спробуйте знову після оновлення вашого графічного драйвера до останньої версії. \\n\\\n   Якщо у вашого комп'ютера є виділена графічна карта, вам потрібно перевірити, чи використовує гра інтегровану/основну графіку. Якщо так, відкрийте лаунчер, використовуючи вашу виділену графічну карту. Якщо проблема не зникає, вам, ймовірно, слід подумати про придбання нової графічної карти або нового комп'ютера. \\n\\\n   Якщо ви використовуєте інтегровану графічну карту, зверніть увагу, що Minecraft 1.16.5 або раніше вимагає <a href=\"https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html\">Java 1.8.0_51 або раніше</a> для процесора Intel(R) Core(TM) 3000 серії або раніше.\ngame.crash.reason.macos_failed_to_find_service_port_for_display=Гра не може продовжувати роботу, оскільки вікно OpenGL на платформі Apple silicon не вдалося ініціалізувати. \\n\\\n   Для цієї проблеми HMCL наразі не має прямих рішень. Спробуйте відкрити будь-який браузер та перейти в повноекранний режим, потім поверніться до HMCL, запустіть гру та <b>швидко поверніться на сторінку браузера</b> перед тим, як з'явиться вікно гри, зачекайте, поки з'явиться вікно гри, а потім переключіться назад на вікно гри.\ngame.crash.reason.illegal_access_error=Гра зазнала збою через деякі моди. \\n\\\n   Якщо ви знаєте це: \"%1$s\", ви можете оновити або видалити моди та спробувати знову.\ngame.crash.reason.install_mixinbootstrap=Гра зазнала збою через відсутність MixinBootstrap. \\n\\\n   Ви можете спробувати встановити <a href=\"https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap\">MixinBootstrap</a> для вирішення проблеми. Якщо вона зазнає збою після встановлення, спробуйте додати знак оклику (!) перед ім'ям файлу цього мода, щоб спробувати вирішити проблему.\ngame.crash.reason.optifine_is_not_compatible_with_forge=Гра зазнала збою, оскільки OptiFine несумісний з поточним встановленням Forge. \\n\\\n   Перейдіть на <a href=\"https://optifine.net/downloads\">офіційний сайт OptiFine</a>, перевірте, чи версія Forge сумісна з OptiFine, і перевстановіть екземпляр строго відповідно до відповідної версії, або змініть версію OptiFine у \"Редагувати екземпляр → Завантажувачі\". \\n\\\n   Після тестування ми вважаємо, що занадто високі або занадто низькі версії OptiFine можуть призвести до збоїв.\ngame.crash.reason.mod_files_are_decompressed=Гра зазнала збою, оскільки файл мода було розпаковано. \\n\\\n   Помістіть весь файл мода безпосередньо в каталог модів! \\n\\\n   Якщо розпакування викликає помилки в грі, видаліть розпакований мод у каталозі модів та запустіть гру.\ngame.crash.reason.shaders_mod=Гра зазнала збою, оскільки OptiFine та Shaders mod встановлені одночасно.\\n\\\n   Просто видаліть мод Shader, оскільки OptiFine має вбудовану підтримку шейдерів.\ngame.crash.reason.rtss_forest_sodium=Гра зазнала збою, оскільки RivaTuner Statistical Server (RTSS) несумісний з Sodium.\\n\\\n   Натисніть <a href=\"https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\">тут</a> для отримання додаткової інформації.\ngame.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=Гра зазнала збою, оскільки ви встановили занадто багато модів і перевищили ліміт ID гри.\\n\\\n   Спробуйте встановити <a href=\"https://www.curseforge.com/minecraft/mc-mods/jeid\">JEID</a> або видалити деякі великі моди.\ngame.crash.reason.night_config_fixes=Гра зазнала збою через деякі проблеми з Night Config.\\n\\\n   \\n\\\n   Ви можете спробувати встановити мод <a href=\"https://www.curseforge.com/minecraft/mc-mods/night-config-fixes\">Night Config Fixes</a>, який може допомогти вам з цією проблемою.\\n\\\n   \\n\\\n   Для отримання додаткової інформації відвідайте <a href=\"https://github.com/Fuzss/nightconfigfixes\">репозиторій GitHub</a> цього мода.\ngame.crash.reason.optifine_causes_the_world_to_fail_to_load=Гра може не продовжувати роботу через OptiFine.\\n\\\n   \\n\\\n   Ця проблема виникає лише в певній версії OptiFine. Ви можете спробувати змінити версію OptiFine у \"Редагувати екземпляр → Завантажувачі\".\ngame.crash.reason.jdk_9=Гра зазнала збою, оскільки версія Java занадто нова для цього екземпляра.\\n\\\n   \\n\\\n   Вам потрібно завантажити та встановити Java 8 та вибрати її у \"Глобальні/Специфічні налаштування екземпляра → Java\".\ngame.crash.reason.jvm_32bit=Гра зазнала збою, оскільки поточне виділення пам'яті перевищило ліміт 32-бітної JVM.\\n\\\n   \\n\\\n   Якщо ваша ОС 64-бітна, встановіть та використовуйте 64-бітну версію Java. Інакше вам може знадобитися перевстановити 64-бітну ОС або отримати новіший комп'ютер.\\n\\\n   \\n\\\n   Або ви можете вимкнути опцію \"Автоматично виділяти\" у \"Глобальні/Специфічні налаштування екземпляра → Пам'ять\" та встановити максимальний розмір виділення пам'яті до 1024 МіБ або менше.\ngame.crash.reason.loading_crashed_forge=Гра зазнала збою через мод \"%1$s\" (%2$s).\\n\\\n   \\n\\\n   Ви можете спробувати видалити або оновити його.\ngame.crash.reason.loading_crashed_fabric=Гра зазнала збою через мод \"%1$s\".\\n\\\n   \\n\\\n   Ви можете спробувати видалити або оновити його.\ngame.crash.reason.mac_jdk_8u261=Гра зазнала збою, оскільки ваша поточна версія Forge або OptiFine несумісна з вашою інсталяцією Java.\\n\\\n   \\n\\\n   Спробуйте оновити Forge та OptiFine або спробуйте використовувати Java 8u251 або попередні версії.\ngame.crash.reason.forge_repeat_installation=Гра зазнала збою через дублікат встановлення Forge. <a href=\"https://github.com/HMCL-dev/HMCL/issues/1880\">Це відома проблема</a>\\n\\\n   \\n\\\n   Рекомендується надіслати відгук на GitHub разом із цим журналом, щоб ми могли знайти більше підказок та вирішити проблему.\\n\\\n   \\n\\\n   Наразі ви можете видалити Forge та перевстановити його у \"Редагувати екземпляр → Завантажувачі\".\ngame.crash.reason.optifine_repeat_installation=Гра зазнала збою через дублікат встановлення OptiFine.\\n\\\n   \\n\\\n   Видаліть OptiFine у каталозі модів або видаліть його у \"Редагувати екземпляр → Завантажувачі\".\ngame.crash.reason.memory_exceeded=Гра зазнала збою, оскільки занадто багато пам'яті було виділено для малого файлу сторінки.\\n\\\n   \\n\\\n   Ви можете спробувати вимкнути опцію \"Автоматично виділяти\" у \"Глобальні/Специфічні налаштування екземпляра → Пам'ять\" та налаштувати значення до запуску гри.\\n\\\n   \\n\\\n   Ви також можете спробувати збільшити розмір файлу сторінки в налаштуваннях системи.\ngame.crash.reason.mod=Гра зазнала збою через мод \"%1$s\".\\n\\\n   \\n\\\n   Ви можете оновити або видалити мод та спробувати знову.\ngame.crash.reason.mod_resolution=Гра не може продовжувати роботу через проблеми з залежностями модів.\\n\\\n   \\n\\\n   Fabric надав наступні деталі: \\n\\\n   \\n\\\n   %1$s\ngame.crash.reason.forgemod_resolution=Гра не може продовжувати роботу через проблеми з залежностями модів.\\n\\\n   \\n\\\n   Forge/NeoForge надав наступні деталі: \\n\\\n   %1$s\ngame.crash.reason.forge_found_duplicate_mods=Гра зазнала збою через проблему з дублікатами модів. Forge/NeoForge надав наступну інформацію:\\n\\\n   %1$s\ngame.crash.reason.mod_resolution_collection=Гра зазнала збою, оскільки версія мода несумісна.\\n\\\n   \\n\\\n   \"%1$s\" вимагає мод \"%2$s\".\\n\\\n   \\n\\\n   Вам потрібно оновити або понизити \"%3$s\" перед продовженням.\ngame.crash.reason.mod_resolution_conflict=Гра зазнала збою через конфліктуючі моди.\\n\\\n   \\n\\\n   \"%1$s\" несумісний з \"%2$s\".\ngame.crash.reason.mod_resolution_missing=Гра зазнала збою, оскільки деякі залежні моди не були встановлені.\\n\\\n   \\n\\\n   \"%1$s\" вимагає мод \"%2$s\".\\n\\\n   \\n\\\n   Це означає, що вам потрібно завантажити та встановити \"%2$s\" для продовження гри.\ngame.crash.reason.mod_resolution_missing_minecraft=Гра зазнала збою, оскільки мод несумісний з поточною версією Minecraft.\\n\\\n   \\n\\\n   \"%1$s\" вимагає версію Minecraft %2$s.\\n\\\n   \\n\\\n   Якщо ви хочете грати з цією версією мода, вам слід змінити версію гри вашого екземпляра.\\n\\\n   \\n\\\n   Інакше вам слід встановити версію, сумісну з цією версією Minecraft.\ngame.crash.reason.mod_resolution_mod_version=%1$s (Версія: %2$s)\ngame.crash.reason.mod_resolution_mod_version.any=%1$s (Будь-яка версія)\ngame.crash.reason.modlauncher_8=Гра зазнала збою, оскільки ваша поточна версія Forge несумісна з вашою інсталяцією Java. Спробуйте оновити Forge.\ngame.crash.reason.no_class_def_found_error=Гра не може продовжувати роботу через неповний код.\\n\\\n   \\n\\\n   У вашому екземплярі гри відсутній \"%1$s\". Це може бути через те, що мод відсутній, встановлений несумісний мод або деякі файли пошкоджені.\\n\\\n   \\n\\\n   Можливо, вам потрібно перевстановити гру та всі моди або попросити когось про допомогу.\ngame.crash.reason.no_such_method_error=Гра не може продовжувати роботу через неповний код.\\n\\\n   \\n\\\n   У вашому екземплярі гри може бути відсутній мод, встановлений несумісний мод або деякі файли можуть бути пошкоджені.\\n\\\n   \\n\\\n   Можливо, вам потрібно перевстановити гру та всі моди або попросити когось про допомогу.\ngame.crash.reason.opengl_not_supported=Гра зазнала збою, оскільки ваш графічний драйвер не підтримує OpenGL.\\n\\\n   \\n\\\n   Якщо ви транслюєте гру через Інтернет або використовуєте середовище віддаленого робочого столу, будь ласка, грайте на вашому локальному комп'ютері.\\n\\\n   \\n\\\n   Або ви можете оновити ваш графічний драйвер до останньої версії та спробувати знову.\\n\\\n   \\n\\\n   Якщо у вашого комп'ютера є виділена графічна карта, переконайтеся, що гра дійсно використовує її для рендерингу. Якщо проблема не зникає, подумайте про придбання нової графічної карти або нового комп'ютера.\ngame.crash.reason.openj9=Гра не може працювати на JVM OpenJ9. Переключіться на Java, яка використовує JVM Hotspot у \"Глобальні/Специфічні налаштування екземпляра → Java\" та знову запустіть гру. Якщо у вас її немає, ви можете завантажити її.\ngame.crash.reason.out_of_memory=Гра зазнала збою, оскільки комп'ютер закінчився пам'ять.\\n\\\n   \\n\\\n   Можливо, недостатньо доступної пам'яті або встановлено занадто багато модів. Ви можете спробувати вирішити це, збільшивши виділену пам'ять у \"Глобальні/Специфічні налаштування екземпляра → Пам'ять\".\\n\\\n   \\n\\\n   Якщо ви все ще зіткнетеся з цими проблемами, вам може знадобитися кращий комп'ютер.\ngame.crash.reason.resolution_too_high=Гра зазнала збою, оскільки роздільна здатність пакета ресурсів/текстур занадто висока.\\n\\\n   \\n\\\n   Ви повинні переключитися на пакет ресурсів/текстур з нижчою роздільною здатністю або подумати про придбання кращої графічної карти з більшим обсягом VRAM.\ngame.crash.reason.stacktrace=Причина збою невідома. Ви можете переглянути її деталі, натиснувши \"Журнали\".\\n\\\n   \\n\\\n   Є деякі ключові слова, які можуть містити деякі імена модів. Ви можете пошукати їх в Інтернеті, щоб з'ясувати проблему самостійно.\\n\\\n   \\n\\\n   %s\ngame.crash.reason.too_old_java=Гра зазнала збою, оскільки ви використовуєте застарілу версію Java.\\n\\\n   \\n\\\n   Вам потрібно переключитися на новішу версію Java (%1$s) у \"Глобальні/Специфічні налаштування екземпляра → Java\" та знову запустити гру. Ви можете завантажити Java <a href=\"https://learn.microsoft.com/java/openjdk/download\">тут</a>.\ngame.crash.reason.unknown=Ми не можемо з'ясувати, чому гра зазнала збою. Зверніться до журналів гри.\ngame.crash.reason.unsatisfied_link_error=Не вдалося запустити Minecraft через відсутність бібліотек: %1$s. \\n\\\n   Якщо ви редагували шлях до нативних бібліотек, переконайтеся, що ці бібліотеки дійсно існують. Або спробуйте запустити знову після повернення до типового значення. \\n\\\n   Якщо ви цього не робили, перевірте, чи не відсутні у вас залежні моди. \\n\\\n   Інакше, якщо ви вважаєте, що це спричинено HMCL, надайте нам відгук.\ngame.crash.title=Гра зазнала збою\ngame.directory=Шлях до гри\ngame.version=Екземпляр гри\n\nhelp=Допомога\nhelp.doc=Документація Hello Minecraft! Лаунчера\nhelp.detail=Для творців datapack та модпаків.\n\ninput.email=Ім'я користувача має бути адресою електронної пошти.\ninput.number=Введення має бути числами.\ninput.not_empty=Це обов'язкове поле.\ninput.url=Введення має бути дійсною URL-адресою.\n\ninstall=Новий екземпляр\ninstall.change_version=Змінити версію\ninstall.change_version.confirm=Ви впевнені, що хочете переключити %s з версії %s на %s?\ninstall.change_version.process=Процес зміни версії\ninstall.failed=Не вдалося встановити\ninstall.failed.downloading=Не вдалося завантажити деякі необхідні файли.\ninstall.failed.downloading.detail=Не вдалося завантажити файл: %s\ninstall.failed.downloading.timeout=Час очікування завантаження при отриманні: %s\ninstall.failed.install_online=Не вдалося ідентифікувати наданий файл. Якщо ви встановлюєте мод, перейдіть на сторінку \"Моди\".\ninstall.failed.malformed=Завантажені файли пошкоджені. Ви можете спробувати вирішити цю проблему, переключившись на інше джерело завантаження у \"Налаштування → Завантаження → Джерело завантаження\".\ninstall.failed.optifine_conflict=Не можна встановити OptiFine та Fabric одночасно на Minecraft 1.13 або новішому.\ninstall.failed.optifine_forge_1.17=Для Minecraft 1.17.1 Forge сумісний лише з OptiFine H1 pre2 або новішою. Ви можете встановити їх, позначивши \"Снапшоти\" при виборі версії OptiFine в HMCL.\ninstall.failed.version_mismatch=Цей завантажувач вимагає версію гри %s, але встановлена %s.\ninstall.installer.change_version=%s Несумісний\ninstall.installer.choose=Виберіть вашу версію %s\ninstall.installer.depend=Вимагає %s\ninstall.installer.do_not_install=Не встановлювати\ninstall.installer.fabric=Fabric\ninstall.installer.fabric-api=Fabric API\ninstall.installer.fabric-quilt-api.warning=%1$s - це мод, і він буде встановлений у каталог модів екземпляра гри. Не змінюйте робочий каталог гри, інакше %1$s не працюватиме. Якщо ви все ж хочете змінити каталог, вам слід перевстановити його.\ninstall.installer.forge=Forge\ninstall.installer.neoforge=NeoForge\ninstall.installer.game=Minecraft\ninstall.installer.incompatible=Несумісний з %s\ninstall.installer.install=Встановлення %s\ninstall.installer.install_offline=Встановити/Оновити з локального файлу\ninstall.installer.install_offline.tooltip=Ми підтримуємо використання локального інсталятора (Neo)Forge/Cleanroom/OptiFine.\ninstall.installer.install_online=Онлайн встановлення\ninstall.installer.install_online.tooltip=Наразі ми підтримуємо Forge, NeoForge, Cleanroom, OptiFine, Fabric, Legacy Fabric, Quilt та LiteLoader.\ninstall.installer.liteloader=LiteLoader\ninstall.installer.not_installed=Не встановлено\ninstall.installer.optifine=OptiFine\ninstall.installer.quilt=Quilt\ninstall.installer.quilt-api=QSL/QFAPI\ninstall.installer.version=%s\ninstall.installer.external_version=%s (Встановлено зовнішнім процесом, який не можна налаштувати)\ninstall.installing=Встановлення\ninstall.modpack=Встановити модпак\ninstall.modpack.installation=Встановлення модпака\ninstall.name.invalid=Назва містить спеціальні символи (наприклад, емодзі або символи CJK). Рекомендується змінити назву, щоб включати лише англійські літери, цифри та підкреслення, щоб уникнути потенційних проблем під час запуску гри. Бажаєте продовжити встановлення?\ninstall.new_game=Встановити екземпляр\ninstall.new_game.already_exists=Ця назва екземпляра вже існує. Використовуйте іншу назву.\ninstall.new_game.current_game_version=Поточна версія екземпляра\ninstall.new_game.installation=Встановлення екземпляра\ninstall.new_game.malformed=Недійсна назва.\ninstall.select=Виберіть операцію\ninstall.success=Успішно встановлено.\n\njava.add.failed=Ця Java недійсна або несумісна з поточною платформою.\njava.disable=Вимкнути Java\njava.disable.confirm=Ви впевнені, що хочете вимкнути цю Java?\njava.disabled.management=Вимкнена Java\njava.disabled.management.remove=Видалити цю Java зі списку\njava.disabled.management.restore=Повторно ввімкнути цю Java\njava.download.banshanjdk-8=Завантажити Banshan JDK 8\njava.download.load_list.failed=Не вдалося завантажити список версій\njava.download.more=Більше дистрибутивів Java\njava.download.prompt=Будь ласка, виберіть версію Java, яку хочете завантажити:\njava.download.distribution=Дистрибутив\njava.download.version=Версія\njava.download.packageType=Тип пакета\njava.management=Керування Java\njava.info.architecture=Архітектура\njava.info.vendor=Постачальник\njava.info.version=Версія\njava.info.disco.distribution=Дистрибутив\njava.install=Встановити Java\njava.install.archive=Шлях до джерела\njava.install.failed.exists=Це ім'я вже використовується\njava.install.failed.invalid=Цей архів не є дійсним пакетом встановлення Java, тому його не можна встановити.\njava.install.failed.unsupported_platform=Ця Java несумісна з поточною платформою, тому її не можна встановити.\njava.install.name=Назва\njava.install.warning.invalid_character=Недопустимий символ у назві\njava.installing=Встановлення Java\njava.uninstall=Видалити Java\njava.uninstall.confirm=Ви впевнені, що хочете видалити цю Java? Цю дію не можна скасувати!\n\nlang.default=Використовувати системні локалі\n\nlaunch.advice=%s Все одно хочете продовжити запуск?\nlaunch.advice.multi=Було виявлено наступні проблеми: \\n\\n%s\\n\\n Ці проблеми можуть завадити запуску гри або вплинути на ігровий досвід. \\nВсе одно хочете продовжити запуск?\nlaunch.advice.java.auto=Поточна версія Java несумісна з екземпляром. Натисніть \"Так\", щоб автоматично вибрати найбільш сумісну версію Java. Або ви можете перейти до \"Глобальні/Специфічні налаштування екземпляра → Java\", щоб вибрати її самостійно.\nlaunch.advice.java.modded_java_7=Minecraft 1.7.2 та попередні версії вимагають Java 7 або раніше.\nlaunch.advice.corrected=Ми вирішили проблему Java. Якщо ви все ще хочете використовувати свій вибір версії Java, ви можете вимкнути \"Не перевіряти сумісність JVM\" у \"Глобальні/Специфічні налаштування екземпляра → Розширені налаштування\".\nlaunch.advice.uncorrected=Якщо ви все ще хочете використовувати свій вибір версії Java, ви можете вимкнути \"Не перевіряти сумісність JVM\" у \"Глобальні/Специфічні налаштування екземпляра → Розширені налаштування\".\nlaunch.advice.different_platform=Для вашого пристрою рекомендується 64-бітна версія Java, але ви встановили 32-бітну.\nlaunch.advice.forge2760_liteloader=Forge версії 2760 несумісний з LiteLoader. Розгляньте можливість оновлення Forge до версії 2773 або новішої.\nlaunch.advice.forge28_2_2_optifine=Forge версії 28.2.2 або новішої несумісний з OptiFine. Розгляньте можливість пониження Forge до версії 28.2.1 або ранішої.\nlaunch.advice.forge37_0_60=Forge версій до 37.0.60 несумісний з Java 17. Оновіть Forge до 37.0.60 або новішої, або запустіть гру з Java 16.\nlaunch.advice.java8_1_13=Minecraft 1.13 та новіші можна запускати лише на Java 8 або новішій. Використовуйте Java 8 або новіші версії.\nlaunch.advice.java8_51_1_13=Minecraft 1.13 може зазнати збою на версіях Java 8 до 1.8.0_51. Встановіть останню версію Java 8.\nlaunch.advice.java9=Ви не можете запустити Minecraft 1.12 або раніші з Java 9 або новішою. Використовуйте Java 8.\nlaunch.advice.modded_java=Деякі моди можуть бути несумісні з новішими версіями Java. Рекомендується використовувати Java %s для запуску Minecraft %s.\nlaunch.advice.modlauncher8=Версія Forge, яку ви використовуєте, несумісна з поточною версією Java. Спробуйте оновити Forge.\nlaunch.advice.newer_java=Ви використовуєте старішу версію Java для запуску гри. Рекомендується оновити до Java 8, інакше деякі моди можуть призвести до збою гри.\nlaunch.advice.not_enough_space=Ви виділили розмір пам'яті, більший за фактичні %d МіБ пам'яті, встановленої на вашому комп'ютері. Ви можете зіткнутися з погіршеною продуктивністю або навіть не зможете запустити гру.\nlaunch.advice.require_newer_java_version=Поточна версія гри вимагає Java %s, але ми не змогли знайти її. Бажаєте завантажити її зараз?\nlaunch.advice.too_large_memory_for_32bit=Ви виділили розмір пам'яті, більший за обмеження пам'яті 32-бітної інсталяції Java. Ви можете не змогти запустити гру.\nlaunch.advice.vanilla_linux_java_8=Minecraft 1.12.2 або раніші підтримують лише Java 8 для платформи Linux x86-64, оскільки новіші версії не можуть завантажити 32-бітні нативні бібліотеки, такі як liblwjgl.so. Завантажте її з java.com або встановіть OpenJDK 8.\nlaunch.advice.vanilla_x86.translation=Minecraft не повністю підтримується на вашій платформі, тому ви можете зіткнутися з відсутністю функцій або навіть не зможете запустити гру. Ви можете завантажити Java для архітектури <b>x86-64</b> <a href=\"https://learn.microsoft.com/java/openjdk/download\">тут</a> для повного ігрового досвіду.\nlaunch.advice.unknown=Гра не може бути запущена з наступних причин:\nlaunch.failed=Не вдалося запустити\nlaunch.failed.cannot_create_jvm=Ми не можемо створити JVM. Це може бути через неправильні аргументи JVM. Ви можете спробувати вирішити це, видаливши всі аргументи, які ви додали у \"Глобальні/Специфічні налаштування екземпляра → Розширені налаштування → Параметри JVM\".\nlaunch.failed.creating_processlaunch.advice=Ми не можемо створити новий процес. Перевірте ваш шлях до Java.\nlaunch.failed.command_too_long=Довжина команди перевищує максимальну довжину пакетного скрипта. Спробуйте експортувати його як скрипт PowerShell.\nlaunch.failed.decompressing_natives=Не вдалося витягти нативні бібліотеки.\nlaunch.failed.download_library=Не вдалося завантажити бібліотеки \"%s\".\nlaunch.failed.executable_permission=Не вдалося зробити скрипт запуску виконуваним.\nlaunch.failed.execution_policy=Встановити політику виконання\nlaunch.failed.execution_policy.failed_to_set=Не вдалося встановити політику виконання\nlaunch.failed.execution_policy.hint=Поточна політика виконання забороняє виконання скриптів PowerShell. Натисніть \"ОК\", щоб дозволити поточному користувачеві виконувати скрипти PowerShell, або натисніть \"Скасувати\", щоб залишити все як є.\nlaunch.failed.exited_abnormally=Гра зазнала збою. Зверніться до журналу збоїв для отримання додаткової інформації.\nlaunch.failed.java_version_too_low=Вказана вами версія Java занадто низька. Скиньте версію Java.\nlaunch.failed.no_accepted_java=Не вдалося знайти сумісну версію Java, бажаєте запустити гру з типовою Java? Натисніть \"Так\", щоб запустити гру з типовою Java. Або ви можете перейти до \"Глобальні/Специфічні налаштування екземпляра → Java\", щоб вибрати її самостійно.\nlaunch.failed.sigkill=Гру було примусово завершено користувачем або системою.\nlaunch.state.dependencies=Вирішення залежностей\nlaunch.state.done=Запущено\nlaunch.state.java=Перевірка версії Java\nlaunch.state.logging_in=Вхід\nlaunch.state.modpack=Завантаження необхідних файлів\nlaunch.state.waiting_launching=Очікування запуску гри\nlaunch.invalid_java=Недійсний шлях до Java. Скиньте шлях до Java.\n\nlauncher=Лаунчер\nlauncher.agreement=ToS та EULA\nlauncher.agreement.accept=Прийняти\nlauncher.agreement.decline=Відхилити\nlauncher.agreement.hint=Ви повинні погодитися з EULA, щоб використовувати це програмне забезпечення.\nlauncher.background=Фонове зображення\nlauncher.background.choose=Вибрати фонове зображення\nlauncher.background.classic=Класичний\nlauncher.background.default=Типовий\nlauncher.background.default.tooltip=Або \"background.png/.jpg/.gif/.webp\" та зображення в каталозі \"bg\"\nlauncher.background.network=З URL\nlauncher.background.paint=Суцільний колір\nlauncher.cache_directory=Каталог кешу\nlauncher.cache_directory.clean=Очистити кеш\nlauncher.cache_directory.choose=Вибрати каталог кешу\nlauncher.cache_directory.default=Типовий (\"%APPDATA%/.minecraft\" або \"~/.minecraft\")\nlauncher.cache_directory.disabled=Вимкнено\nlauncher.cache_directory.invalid=Не вдалося створити каталог кешу, повернення до типового.\nlauncher.contact=Зв'яжіться з нами\nlauncher.crash=Hello Minecraft! Лаунчер зіткнувся з фатальною помилкою! Скопіюйте наступний журнал та попросіть допомоги на нашому Discord, групі QQ, GitHub або іншому форумі Minecraft.\nlauncher.crash.java_internal_error=Hello Minecraft! Лаунчер зіткнувся з фатальною помилкою, оскільки ваша Java пошкоджена. Видаліть вашу Java та завантажте відповідну Java <a href=\"https://bell-sw.com/pages/downloads/#downloads\">тут</a>.\nlauncher.crash.hmcl_out_dated=Hello Minecraft! Лаунчер зіткнувся з фатальною помилкою! Ваш лаунчер застарів. Оновіть ваш лаунчер!\nlauncher.update_java=Оновіть вашу версію Java.\n\nlibraries.download=Завантаження бібліотек\n\nlogin.empty_username=Ви ще не встановили своє ім'я користувача!\nlogin.enter_password=Введіть свій пароль.\nlogwindow.show_lines=Показати номер рядка\nlogwindow.terminate_game=Вбити процес гри\nlogwindow.title=Журнал\nlogwindow.help=Ви можете перейти до спільноти HMCL та знайти інших для допомоги.\nlogwindow.autoscroll=Автопрокрутка\nlogwindow.export_game_crash_logs=Експортувати журнали збоїв\nlogwindow.export_dump=Експортувати дамп стека гри\nlogwindow.export_dump.no_dependency=Ваша Java не містить залежностей для створення дампу стека. Приєднуйтесь до нашого Discord або групи QQ для допомоги.\n\nmain_page=Головна\n\nmessage.cancelled=Операцію скасовано\nmessage.confirm=Підтвердити\nmessage.copied=Скопійовано до буфера обміну\nmessage.default=Типово\nmessage.doing=Будь ласка, зачекайте\nmessage.downloading=Завантаження\nmessage.error=Помилка\nmessage.failed=Операція не вдалася\nmessage.info=Інформація\nmessage.success=Операція успішно завершена\nmessage.unknown=Невідомо\nmessage.warning=Попередження\n\nmodpack=Модпаки\nmodpack.choose=Вибрати модпак\nmodpack.choose.local=Імпортувати з локального файлу\nmodpack.choose.local.detail=Ви можете перетягнути файл модпака сюди.\nmodpack.choose.remote=Завантажити з URL\nmodpack.choose.remote.detail=Потрібне пряме посилання для завантаження на віддалений файл модпака.\nmodpack.choose.repository=Завантажити модпак з CurseForge або Modrinth\nmodpack.choose.repository.detail=Ви можете вибрати бажаний модпак на наступній сторінці.\nmodpack.choose.remote.tooltip=Введіть URL вашого модпака\nmodpack.completion=Завантаження залежностей\nmodpack.desc=Опишіть свій модпак, включаючи вступ та, ймовірно, деякий журнал змін. Наразі підтримуються Markdown та зображення з URL.\nmodpack.description=Опис модпака\nmodpack.download=Завантажити модпаки\nmodpack.download.title=Завантажити модпак - %1s\nmodpack.enter_name=Введіть назву для цього модпака.\nmodpack.export=Експортувати як модпак\nmodpack.export.as=Експортувати модпак як...\nmodpack.file_api=Префікс URL модпака\nmodpack.files.blueprints=Схеми BuildCraft\nmodpack.files.config=Файли конфігурації модів\nmodpack.files.dumps=Файли виводу налагодження NEI\nmodpack.files.hmclversion_cfg=Файл конфігурації лаунчера\nmodpack.files.liteconfig=Файли, пов'язані з LiteLoader\nmodpack.files.mods=Моди\nmodpack.files.mods.voxelmods=Опції VoxelMods\nmodpack.files.options_txt=Файл опцій Minecraft\nmodpack.files.optionsshaders_txt=Файл опцій шейдерів\nmodpack.files.resourcepacks=Пакети ресурсів/текстур\nmodpack.files.saves=Світи\nmodpack.files.scripts=Файл конфігурації MineTweaker\nmodpack.files.servers_dat=Файл списку серверів\nmodpack.installing=Встановлення модпака\nmodpack.installing.given=Встановлення модпака %s\nmodpack.introduction=Наразі підтримуються модпаки Curse, Modrinth, MultiMC та MCBBS.\nmodpack.invalid=Недійсний модпак, ви можете спробувати завантажити його знову.\nmodpack.mismatched_type=Тип модпака не відповідає, поточний екземпляр має тип %s, але наданий має тип %s.\nmodpack.name=Назва модпака\nmodpack.not_a_valid_name=Недійсна назва модпака.\nmodpack.origin=Джерело\nmodpack.origin.url=Офіційний сайт\nmodpack.origin.mcbbs=MCBBS\nmodpack.origin.mcbbs.prompt=ID поста\nmodpack.scan=Аналіз індексу модпака\nmodpack.task.install=Імпортувати модпак\nmodpack.task.install.error=Не вдалося ідентифікувати цей модпак. Наразі ми підтримуємо лише модпаки Curse, Modrinth, MultiMC та MCBBS.\nmodpack.type.curse=Curse\nmodpack.type.curse.error=Не вдалося завантажити залежності. Спробуйте знову або використовуйте проксі-сервер.\nmodpack.type.curse.not_found=Деякі залежності більше не доступні. Спробуйте встановити новішу версію модпака.\nmodpack.type.manual.warning=Модпак вручну запаковано видавцем, який може вже містити лаунчер. Рекомендується спробувати розпакувати модпак та запустити гру з власним лаунчером. HMCL все ще може імпортувати його, без гарантії його працездатності. Все одно продовжити?\nmodpack.type.mcbbs=MCBBS\nmodpack.type.mcbbs.export=Може бути імпортовано Hello Minecraft! Лаунчером\nmodpack.type.modrinth=Modrinth\nmodpack.type.modrinth.export=Може бути імпортовано популярними сторонніми лаунчерами\nmodpack.type.multimc=MultiMC\nmodpack.type.multimc.export=Може бути імпортовано Hello Minecraft! Лаунчером та MultiMC\nmodpack.type.server=Автоматичне оновлення модпака з сервера\nmodpack.type.server.export=Дозволяє власнику сервера віддалено оновлювати екземпляр гри\nmodpack.type.server.malformed=Недійсний маніфест модпака. Зверніться до творця модпака, щоб вирішити цю проблему.\nmodpack.unsupported=Непідтримуваний формат модпака\nmodpack.update=Оновлення модпака\nmodpack.wizard=Посібник експорту модпака\nmodpack.wizard.step.1=Базові налаштування\nmodpack.wizard.step.1.title=Деяка базова інформація для модпака.\nmodpack.wizard.step.2=Вибір файлів\nmodpack.wizard.step.2.title=Виберіть файли, які ви хочете додати до модпака.\nmodpack.wizard.step.3=Тип модпака\nmodpack.wizard.step.3.title=Виберіть тип модпака, який ви хочете експортувати.\nmodpack.wizard.step.initialization.exported_version=Екземпляр гри для експорту\nmodpack.wizard.step.initialization.force_update=Примусове оновлення модпака до останньої версії (вам знадобиться сервер хостингу файлів)\nmodpack.wizard.step.initialization.include_launcher=Включити лаунчер\nmodpack.wizard.step.initialization.modrinth.info=Лаунчер буде відповідати віддаленим ресурсам CurseForge/Modrinth замість локальних файлів (включаючи моди, пакети ресурсів та пакети шейдерів) під час створення модпака для зменшення розміру модпака та позначати файли з розширенням \".disabled\" як необов'язкові для встановлення.\nmodpack.wizard.step.initialization.no_create_remote_files=Не відповідати віддаленим файлам\nmodpack.wizard.step.initialization.save=Експортувати до...\nmodpack.wizard.step.initialization.skip_curseforge_remote_files=Не відповідати віддаленим ресурсам CurseForge\nmodpack.wizard.step.initialization.warning=Перед створенням модпака переконайтеся, що гра може бути запущена нормально, і Minecraft є релізною версією, а не снапшотом. Лаунчер збереже ваші налаштування завантаження. \\n\\\n   \\n\\\n   Пам'ятайте, що вам заборонено додавати моди та пакети ресурсів, які явно вказують, що їх не можна розповсюджувати або вставляти в модпак.\nmodpack.wizard.step.initialization.server=Натисніть тут для отримання додаткової інформації про те, як створити серверний модпак, який може бути автоматично оновлений.\n\nmodrinth.category.adventure=Пригоди\nmodrinth.category.atmosphere=Атмосфера\nmodrinth.category.audio=Аудіо\nmodrinth.category.babric=Babric\nmodrinth.category.blocks=Блоки\nmodrinth.category.bloom=Bloom\nmodrinth.category.bta-babric=BTA (Babric)\nmodrinth.category.bukkit=Bukkit\nmodrinth.category.bungeecord=BungeeCord\nmodrinth.category.canvas=Canvas\nmodrinth.category.cartoon=Картоон\nmodrinth.category.challenging=Викликаючі\nmodrinth.category.colored-lighting=Кольорове освітлення\nmodrinth.category.combat=Бойові\nmodrinth.category.core-shaders=Основні шейдери\nmodrinth.category.cursed=Прокляті\nmodrinth.category.datapack=Datapack\nmodrinth.category.decoration=Декорації\nmodrinth.category.economy=Економіка\nmodrinth.category.entities=Сутності\nmodrinth.category.environment=Навколишнє середовище\nmodrinth.category.equipment=Обладнання\nmodrinth.category.fabric=Fabric\nmodrinth.category.fantasy=Фентезі\nmodrinth.category.folia=Folia\nmodrinth.category.foliage=Листя\nmodrinth.category.fonts=Шрифти\nmodrinth.category.food=Їжа\nmodrinth.category.forge=Forge\nmodrinth.category.game-mechanics=Механіки гри\nmodrinth.category.gui=GUI\nmodrinth.category.high=Високий\nmodrinth.category.iris=Iris\nmodrinth.category.items=Предмети\nmodrinth.category.java-agent=Java Agent\nmodrinth.category.kitchen-sink=Kitchen-Sink\nmodrinth.category.legacy-fabric=Legacy Fabric\nmodrinth.category.library=Бібліотека\nmodrinth.category.lightweight=Легкий\nmodrinth.category.liteloader=LiteLoader\nmodrinth.category.locale=Локаль\nmodrinth.category.low=Низький\nmodrinth.category.magic=Магія\nmodrinth.category.management=Управління\nmodrinth.category.medium=Середній\nmodrinth.category.minecraft=Minecraft\nmodrinth.category.minigame=Міні-гра\nmodrinth.category.misc=Різне\nmodrinth.category.mobs=Моби\nmodrinth.category.modded=Модифікований\nmodrinth.category.models=Моделі\nmodrinth.category.modloader=Завантажувач модів\nmodrinth.category.multiplayer=Багатокористувацький\nmodrinth.category.neoforge=NeoForge\nmodrinth.category.nilloader=NilLoader\nmodrinth.category.optifine=OptiFine\nmodrinth.category.optimization=Оптимізація\nmodrinth.category.ornithe=Ornithe\nmodrinth.category.paper=Paper\nmodrinth.category.path-tracing=Трасування променів\nmodrinth.category.pbr=PBR\nmodrinth.category.potato=Картопля\nmodrinth.category.purpur=Purpur\nmodrinth.category.quests=Квести\nmodrinth.category.quilt=Quilt\nmodrinth.category.realistic=Реалістичний\nmodrinth.category.reflections=Відображення\nmodrinth.category.rift=Rift\nmodrinth.category.screenshot=Скріншот\nmodrinth.category.semi-realistic=Напівреалістичний\nmodrinth.category.shadows=Тіні\nmodrinth.category.simplistic=Спрощений\nmodrinth.category.social=Соціальний\nmodrinth.category.spigot=Spigot\nmodrinth.category.sponge=Sponge\nmodrinth.category.storage=Сховище\nmodrinth.category.technology=Технології\nmodrinth.category.themed=Тематичний\nmodrinth.category.transportation=Транспорт\nmodrinth.category.tweaks=Твіки\nmodrinth.category.utility=Утиліта\nmodrinth.category.vanilla=Ваніль\nmodrinth.category.vanilla-like=Схожий на ваніль\nmodrinth.category.velocity=Velocity\nmodrinth.category.waterfall=Waterfall\nmodrinth.category.worldgen=Генерація світу\nmodrinth.category.8x-=8x-\nmodrinth.category.16x=16x\nmodrinth.category.32x=32x\nmodrinth.category.48x=48x\nmodrinth.category.64x=64x\nmodrinth.category.128x=128x\nmodrinth.category.256x=256x\nmodrinth.category.512x+=512x+\n\nmods=Моди\nmods.add.failed=Не вдалося додати мод %s.\nmods.add.success=%s успішно додано.\nmods.broken_dependency.title=Зламана залежність\nmods.broken_dependency.desc=Ця залежність існувала раніше, але тепер її немає. Спробуйте використовувати інше джерело завантаження.\nmods.category=Категорія\nmods.channel.alpha=Альфа\nmods.channel.beta=Бета\nmods.channel.release=Реліз\nmods.check_updates=Перевірити оновлення\nmods.check_updates.confirm=Оновити\nmods.check_updates.button=Оновити\nmods.check_updates.current_version=Поточна версія\nmods.check_updates.empty=Усі моди оновлені\nmods.check_updates.failed_check=Не вдалося перевірити оновлення.\nmods.check_updates.failed_download=Не вдалося завантажити деякі файли.\nmods.check_updates.file=Файл\nmods.check_updates.source=Джерело\nmods.check_updates.target_version=Цільова версія\nmods.curseforge=CurseForge\nmods.dependency.embedded=Вбудовані залежності (Вже запаковані в файл мода автором. Не потрібно завантажувати окремо)\nmods.dependency.optional=Необов'язкові залежності (Якщо відсутні, гра буде працювати нормально, але функції мода можуть бути відсутні)\nmods.dependency.required=Необхідні залежності (Потрібно завантажити окремо. Відсутність може завадити запуску гри)\nmods.dependency.tool=Необхідні залежності (Потрібно завантажити окремо. Відсутність може завадити запуску гри)\nmods.dependency.include=Вбудовані залежності (Вже запаковані в файл мода автором. Не потрібно завантажувати окремо)\nmods.dependency.incompatible=Несумісні моди (Встановлення цих модів одночасно завадить запуску гри)\nmods.dependency.broken=Зламані залежності (Цей мод існував раніше, але тепер його немає. Спробуйте використовувати інше джерело завантаження.)\nmods.disable=Вимкнути\nmods.download=Завантажити мод\nmods.download.title=Завантажити мод - %1s\nmods.download.recommend=Рекомендована версія мода - Minecraft %1s\nmods.enable=Увімкнути\nmods.game.version=Версія гри\nmods.manage=Моди\nmods.mcbbs=MCBBS\nmods.mcmod=MCMod\nmods.mcmod.page=Сторінка MCMod\nmods.mcmod.search=Пошук в MCMod\nmods.modrinth=Modrinth\nmods.name=Назва\nmods.not_modded=Ви повинні спочатку встановити завантажувач модів (Forge, NeoForge, Fabric, Legacy Fabric, Quilt або LiteLoader), щоб керувати своїми модами!\nmods.restore=Відновити\nmods.url=Офіційна сторінка\nmods.update_modpack_mod.warning=Оновлення модів у модпаку може призвести до непоправних наслідків, можливо, пошкодивши модпак так, що він не зможе запуститися. Ви впевнені, що хочете оновити?\nmods.install=Встановити\nmods.save_as=Зберегти як\n\nnbt.entries=%s записів\nnbt.open.failed=Не вдалося відкрити файл\nnbt.save.failed=Не вдалося зберегти файл\nnbt.title=Перегляд файлу - %s\n\ndatapack=Datapacks\ndatapack.title=Світ [%s] - Datapacks\n\nweb.failed=Не вдалося завантажити сторінку\nweb.open_in_browser=Бажаєте відкрити цю адресу в браузері: \\n%s\nweb.view_in_browser=Переглянути все в браузері\n\nworld=Світи\nworld.add.already_exists=Цей світ вже існує.\nworld.add.title=Виберіть архів світу, який ви хочете імпортувати\nworld.add.failed=Не вдалося імпортувати цей світ: %s\nworld.add.invalid=Не вдалося розібрати світ.\nworld.backup=Резервне копіювання світу\nworld.backup.create.failed=Не вдалося створити резервну копію. \\n%s\nworld.backup.create.success=Успішно створено нову резервну копію: %s\nworld.backup.delete=Видалити цю резервну копію\nworld.backup.processing=Резервне копіювання ...\nworld.chunkbase=Chunk Base\nworld.chunkbase.end_city=Кінцеве місто\nworld.chunkbase.seed_map=Карта насіння\nworld.chunkbase.stronghold=Фортеця\nworld.chunkbase.nether_fortress=Форт Незеру\nworld.duplicate.failed.already_exists=Каталог вже існує\nworld.duplicate.failed.empty_name=Назва не може бути порожньою\nworld.duplicate.failed.invalid_name=Назва містить недійсні символи\nworld.datapack=Datapacks\nworld.datetime=Останній раз грали %s\nworld.delete=Видалити цей світ\nworld.delete.failed=Не вдалося видалити світ.\\n%s\nworld.download.title=Завантажити світ - %1s\nworld.export=Експортувати світ\nworld.export.title=Виберіть каталог для цього експортованого світу\nworld.export.location=Зберегти як\nworld.export.wizard=Експортувати світ \"%s\"\nworld.game_version=Версія гри\nworld.info=Інформація про світ\nworld.info.basic=Базова інформація\nworld.info.allow_cheats=Дозволити команди/чіти\nworld.info.dimension.the_nether=Незер\nworld.info.dimension.the_end=Кінець\nworld.info.difficulty=Складність\nworld.info.difficulty.peaceful=Мирна\nworld.info.difficulty.easy=Легка\nworld.info.difficulty.normal=Нормальна\nworld.info.difficulty.hard=Важка\nworld.info.failed=Не вдалося прочитати інформацію про світ\nworld.info.game_version=Версія гри\nworld.info.last_played=Останній раз грали\nworld.info.generate_features=Генерувати структури\nworld.info.player=Інформація про гравця\nworld.info.player.food_level=Рівень голоду\nworld.info.player.game_type=Режим гри\nworld.info.player.game_type.adventure=Пригоди\nworld.info.player.game_type.creative=Творчий\nworld.info.player.game_type.hardcore=Хардкор\nworld.info.player.game_type.spectator=Спостерігач\nworld.info.player.game_type.survival=Виживання\nworld.info.player.health=Здоров'я\nworld.info.player.last_death_location=Місце останньої смерті\nworld.info.player.location=Місцезнаходження\nworld.info.player.spawn=Місце появи\nworld.info.player.xp_level=Рівень досвіду\nworld.info.random_seed=Насіння\nworld.locked=Використовується\nworld.locked.failed=Світ наразі використовується. Закрийте гру та спробуйте знову.\nworld.manage=Світи\nworld.manage.button=Керування світами\nworld.manage.title=Світ - %s\nworld.name=Назва світу\nworld.name.enter=Введіть назву світу\nworld.show_all=Показати все\n\nprofile=Ігрові каталоги\nprofile.already_exists=Це ім'я вже існує. Використовуйте інше ім'я.\nprofile.default=Поточний\nprofile.home=Лаунчер Minecraft\nprofile.instance_directory=Каталог гри\nprofile.instance_directory.choose=Вибрати каталог гри\nprofile.manage=Список каталогів екземплярів\nprofile.name=Назва\nprofile.new=Новий каталог\nprofile.title=Ігрові каталоги\nprofile.selected=Вибраний\nprofile.use_relative_path=Використовувати відносний шлях для каталогу гри, якщо можливо\n\nrepositories.custom=Власне сховище Maven (%s)\nrepositories.maven_central=Універсальне (Maven Central)\nrepositories.tencentcloud_mirror=Дзеркало материкового Китаю (Сховище Maven Tencent Cloud)\nrepositories.chooser=HMCL вимагає JavaFX для роботи. \\n\\\n   \\n\\\n   Натисніть \"ОК\", щоб завантажити JavaFX з вказаного сховища, або натисніть \"Скасувати\", щоб вийти. \\n\\\n   \\n\\\n   Сховища:\nrepositories.chooser.title=Виберіть джерело завантаження для JavaFX\n\nresourcepack=Пакети ресурсів\nresourcepack.download.title=Завантажити пакет ресурсів - %1s\n\nreveal.in_file_manager=Показати в менеджері файлів\n\nschematics=Схематики\nschematics.add.failed=Не вдалося додати файли схематик\nschematics.back_to=Назад до \"%s\"\nschematics.create_directory.prompt=Введіть нову назву каталогу\nschematics.create_directory.failed=Не вдалося створити каталог\nschematics.create_directory.failed.already_exists=Каталог вже існує\nschematics.create_directory.failed.empty_name=Назва не може бути порожньою\nschematics.create_directory.failed.invalid_name=Назва містить недійсні символи\nschematics.info.description=Опис\nschematics.info.enclosing_size=Розмір обмежувального паралелепіпеда\nschematics.info.name=Назва\nschematics.info.region_count=Регіони\nschematics.info.schematic_author=Автор\nschematics.info.time_created=Час створення\nschematics.info.time_modified=Час зміни\nschematics.info.total_blocks=Всього блоків\nschematics.info.total_volume=Загальний об'єм\nschematics.info.version=Версія схематики\nschematics.manage=Схематики\nschematics.sub_items=%d підпунктів\n\nsearch=Пошук\nsearch.hint.chinese=Пошук англійською та китайською\nsearch.hint.english=Пошук лише англійською\nsearch.enter=Введіть текст тут\nsearch.sort=Сортувати за\nsearch.first_page=Перша\nsearch.previous_page=Попередня\nsearch.next_page=Наступна\nsearch.last_page=Остання\nsearch.page_n=%d / %s\n\nselector.choose=Вибрати\nselector.choose_file=Вибрати файл\nselector.custom=Власний\n\nsettings=Налаштування\n\nsettings.advanced=Розширені налаштування\nsettings.advanced.modify=Редагувати розширені налаштування\nsettings.advanced.title=Розширені налаштування - %s\nsettings.advanced.custom_commands=Власні команди\nsettings.advanced.custom_commands.hint=Надано наступні змінні середовища: \\n\\\n   \\  · $INST_NAME: назва екземпляра. \\n\\\n   \\  · $INST_ID: назва екземпляра. \\n\\\n   \\  · $INST_DIR: абсолютний шлях до робочого каталогу екземпляра. \\n\\\n   \\  · $INST_MC_DIR: абсолютний шлях до каталогу гри. \\n\\\n   \\  · $INST_JAVA: двійковий файл java, що використовується для запуску. \\n\\\n   \\  · $INST_FORGE: встановлено, якщо Forge встановлено. \\n\\\n   \\  · $INST_NEOFORGE: встановлено, якщо NeoForge встановлено. \\n\\\n   \\  · $INST_LITELOADER: встановлено, якщо LiteLoader встановлено. \\n\\\n   \\  · $INST_OPTIFINE: встановлено, якщо OptiFine встановлено. \\n\\\n   \\  · $INST_FABRIC: встановлено, якщо Fabric встановлено. \\n\\\n   \\  · $INST_QUILT: встановлено, якщо Quilt встановлено.\nsettings.advanced.dont_check_game_completeness=Не перевіряти цілісність гри\nsettings.advanced.dont_check_jvm_validity=Не перевіряти сумісність JVM\nsettings.advanced.dont_patch_natives=Не намагатися автоматично замінити нативні бібліотеки\nsettings.advanced.environment_variables=Змінні середовища\nsettings.advanced.game_dir.default=Типово (\".minecraft/\")\nsettings.advanced.game_dir.independent=Ізольований (\".minecraft/versions/<назва екземпляра>/\", окрім ресурсів та бібліотек)\nsettings.advanced.java_permanent_generation_space=Простір PermGen\nsettings.advanced.java_permanent_generation_space.prompt=в МіБ\nsettings.advanced.jvm=Параметри JVM\nsettings.advanced.jvm_args=Аргументи JVM\nsettings.advanced.jvm_args.prompt=\\  · Якщо аргументи, введені в \"Аргументи JVM\", такі ж, як типові аргументи, вони не будуть додані. \\n\\\n   \\  · Введіть будь-які аргументи GC в \"Аргументи JVM\", і аргумент G1 типових аргументів буде вимкнено. \\n\\\n   \\  · Увімкніть \"Не додавати типові аргументи JVM\", щоб запустити гру без додавання типових аргументів.\nsettings.advanced.launcher_visibility.close=Закрити лаунчер після запуску гри\nsettings.advanced.launcher_visibility.hide=Приховати лаунчер після запуску гри\nsettings.advanced.launcher_visibility.hide_and_reopen=Приховати лаунчер та показати його, коли гра закривається\nsettings.advanced.launcher_visibility.keep=Залишити лаунчер видимим\nsettings.advanced.launcher_visible=Видимість лаунчера\nsettings.advanced.minecraft_arguments=Аргументи запуску\nsettings.advanced.minecraft_arguments.prompt=Типово\nsettings.advanced.natives_directory=Шлях до нативних бібліотек\nsettings.advanced.natives_directory.choose=Виберіть місце розташування бажаних нативних бібліотек\nsettings.advanced.natives_directory.custom=Власний\nsettings.advanced.natives_directory.default=Типово\nsettings.advanced.natives_directory.default.version_id=<Назва екземпляра>\nsettings.advanced.natives_directory.hint=Ця опція призначена лише для користувачів Apple silicon або інших неофіційно підтримуваних платформ. Не редагуйте цю опцію, якщо ви не знаєте, що робите. \\n\\\n   Перед продовженням переконайтеся, що всі бібліотеки (наприклад, lwjgl.dll, libopenal.so) надано у вашому бажаному каталозі. \\n\\\n   Примітка: Рекомендується використовувати повністю англійські літери для вказаного локального файлу бібліотеки. Інакше це може призвести до невдалого запуску гри.\nsettings.advanced.no_jvm_args=Не додавати типові аргументи JVM\nsettings.advanced.precall_command=Команда перед запуском\nsettings.advanced.precall_command.prompt=Команди для виконання перед запуском гри\nsettings.advanced.process_priority=Пріоритет процесу\nsettings.advanced.process_priority.low=Низький\nsettings.advanced.process_priority.below_normal=Нижче нормального\nsettings.advanced.process_priority.normal=Нормальний\nsettings.advanced.process_priority.above_normal=Вище нормального\nsettings.advanced.process_priority.high=Високий\nsettings.advanced.post_exit_command=Команда після виходу\nsettings.advanced.post_exit_command.prompt=Команди для виконання після виходу з гри\nsettings.advanced.renderer=Рендерер\nsettings.advanced.renderer.default=По умолчанию\nsettings.advanced.renderer.default.desc=OpenGL\nsettings.advanced.renderer.d3d12=Mesa D3D12\nsettings.advanced.renderer.d3d12.desc=DirectX 12 (Погана продуктивність та сумісність)\nsettings.advanced.renderer.llvmpipe=Mesa LLVMpipe\nsettings.advanced.renderer.llvmpipe.desc=Програмний (Погана продуктивність, найкраща сумісність)\nsettings.advanced.renderer.zink=Mesa Zink\nsettings.advanced.renderer.zink.desc=Vulkan (Найкраща продуктивність, погана сумісність)\nsettings.advanced.server_ip=Адреса сервера\nsettings.advanced.server_ip.prompt=Автоматично приєднатися після запуску гри\nsettings.advanced.unsupported_system_options=Налаштування, що не застосовуються до поточної системи\nsettings.advanced.use_native_glfw=[Лише Linux/FreeBSD] Використовувати системний GLFW\nsettings.advanced.use_native_openal=[Лише Linux/FreeBSD] Використовувати системний OpenAL\nsettings.advanced.workaround=Обхідні шляхи\nsettings.advanced.workaround.warning=Опції обхідних шляхів призначені лише для досвідчених користувачів. Налаштування цих опцій може призвести до збою гри. Якщо ви не знаєте, що робите, не редагуйте ці опції.\nsettings.advanced.wrapper_launcher=Команда обгортки\nsettings.advanced.wrapper_launcher.prompt=Дозволяє запуск за допомогою додаткової обгорткової програми, як-от \"optirun\" на Linux\n\nsettings.custom=Власний\n\nsettings.game=Налаштування\nsettings.game.copy_global=Копіювати з глобальних налаштувань\nsettings.game.copy_global.copy_all=Копіювати все\nsettings.game.copy_global.copy_all.confirm=Ви впевнені, що хочете перезаписати поточні налаштування екземпляра? Цю дію не можна скасувати!\nsettings.game.current=Гра\nsettings.game.dimension=Роздільна здатність\nsettings.game.exploration=Дослідити\nsettings.game.fullscreen=Повноекранний режим\nsettings.game.java_directory=Java\nsettings.game.java_directory.auto=Автоматично вибрати\nsettings.game.java_directory.auto.not_found=Не встановлено відповідну версію Java.\nsettings.game.java_directory.bit=%s біт\nsettings.game.java_directory.choose=Вибрати Java\nsettings.game.java_directory.invalid=Неправильний шлях до Java\nsettings.game.java_directory.version=Вказати версію Java\nsettings.game.java_directory.template=%s (%s)\nsettings.game.management=Керувати\nsettings.game.working_directory=Робочий каталог\nsettings.game.working_directory.choose=Вибрати робочий каталог\n\nsettings.icon=Іконка\n\nsettings.launcher=Налаштування лаунчера\nsettings.launcher.appearance=Зовнішній вигляд\nsettings.launcher.common_path.tooltip=HMCL розмістить усі ігрові ресурси та залежності тут. Якщо в каталозі гри вже є існуючі бібліотеки, тоді HMCL спочатку надасть перевагу їх використанню.\nsettings.launcher.debug=Налагодження\nsettings.launcher.download=Завантаження\nsettings.launcher.download.threads=Потоки\nsettings.launcher.download.threads.auto=Автоматично визначати\nsettings.launcher.download.threads.hint=Занадто багато потоків може призвести до зависання вашої системи, а ваша швидкість завантаження може бути вплинуто вашим інтернет-провайдером та серверами завантаження. Не завжди більше потоків збільшують вашу швидкість завантаження.\nsettings.launcher.download_source=Джерело завантаження\nsettings.launcher.download_source.auto=Автоматично вибирати джерела завантаження\nsettings.launcher.enable_game_list=Показувати список екземплярів на головній сторінці\nsettings.launcher.font=Шрифт\nsettings.launcher.font.anti_aliasing=Згладжування\nsettings.launcher.font.anti_aliasing.auto=Авто\nsettings.launcher.font.anti_aliasing.gray=Відтінки сірого\nsettings.launcher.font.anti_aliasing.lcd=Субпіксельне\nsettings.launcher.general=Загальні\nsettings.launcher.language=Мова\nsettings.launcher.launcher_log.export=Експортувати журнали лаунчера\nsettings.launcher.launcher_log.export.failed=Не вдалося експортувати журнали.\nsettings.launcher.launcher_log.export.success=Журнали було експортовано до \"%s\".\nsettings.launcher.launcher_log.reveal=Показати журнали в менеджері файлів\nsettings.launcher.log=Ведення журналу\nsettings.launcher.log.font=Шрифт\nsettings.launcher.proxy=Проксі\nsettings.launcher.proxy.authentication=Вимагає автентифікації\nsettings.launcher.proxy.default=Використовувати системний проксі\nsettings.launcher.proxy.host=Хост\nsettings.launcher.proxy.http=HTTP\nsettings.launcher.proxy.none=Без проксі\nsettings.launcher.proxy.password=Пароль\nsettings.launcher.proxy.port=Порт\nsettings.launcher.proxy.socks=SOCKS\nsettings.launcher.proxy.username=Ім'я користувача\nsettings.launcher.theme=Тема\nsettings.launcher.title_transparent=Прозорий заголовок\nsettings.launcher.turn_off_animations=Вимкнути анімацію\nsettings.launcher.version_list_source=Список версій\nsettings.launcher.background.settings.opacity=Непрозорість\n\nsettings.memory=Пам'ять\nsettings.memory.allocate.auto=%1$.1f ГіБ Мінімум / %2$.1f ГіБ Виділено\nsettings.memory.allocate.auto.exceeded=%1$.1f ГіБ Мінімум / %2$.1f ГіБ Виділено (%3$.1f ГіБ Доступно)\nsettings.memory.allocate.manual=%1$.1f ГіБ Виділено\nsettings.memory.allocate.manual.exceeded=%1$.1f ГіБ Виділено (%3$.1f ГіБ Доступно)\nsettings.memory.auto_allocate=Автоматично виділяти\nsettings.memory.lower_bound=Мінімальна пам'ять\nsettings.memory.unit.mib=МіБ\nsettings.memory.used_per_total=%1$.1f ГіБ Використано / %2$.1f ГіБ Всього\nsettings.physical_memory=Розмір фізичної пам'яті\nsettings.show_log=Показати журнали\nsettings.tabs.installers=Завантажувачі\nsettings.take_effect_after_restart=Застосовується після перезавантаження\nsettings.type=Тип налаштувань екземпляра\nsettings.type.global=Глобальні налаштування (Спільні між екземплярами без увімкнення \"Специфічні налаштування екземпляра\")\nsettings.type.global.manage=Глобальні налаштування\nsettings.type.global.edit=Редагувати глобальні налаштування\nsettings.type.special.enable=Увімкнути специфічні налаштування екземпляра\nsettings.type.special.edit=Редагувати поточні налаштування екземпляра\nsettings.type.special.edit.hint=Поточний екземпляр \"%s\" увімкнув \"Специфічні налаштування екземпляра\". Усі опції на цій сторінці НЕ впливатимуть на цей екземпляр. Натисніть тут, щоб редагувати його власні налаштування.\n\nsponsor=Спонсори\nsponsor.bmclapi=Завантаження для материкового Китаю надається BMCLAPI. Натисніть тут для отримання додаткової інформації.\nsponsor.hmcl=Hello Minecraft! Лаунчер - це FOSS лаунчер Minecraft, який дозволяє користувачам легко керувати кількома екземплярами Minecraft. Натисніть тут для отримання додаткової інформації.\n\nsystem.architecture=Архітектура\nsystem.operating_system=Операційна система\n\nunofficial.hint=Ви використовуєте неофіційну збірку HMCL. Ми не можемо гарантувати її безпеку.\n\nupdate=Оновлення\nupdate.accept=Оновити\nupdate.changelog=Журнал змін\nupdate.channel.dev=Бета\nupdate.channel.dev.hint=Ви зараз використовуєте збірку лаунчера каналу Бета. Хоча вона може включати деякі додаткові функції, іноді вона менш стабільна, ніж збірки каналу Реліз. \\n\\\n   \\n\\\n   Якщо ви зіткнетеся з будь-якими помилками або проблемами, надішліть відгук через канали, надані на сторінці <a href=\"hmcl://settings/feedback\">Зворотний зв'язок</a>. \\n\\\n   \\n\\\n   Підписуйтесь на <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> на Bilibili, щоб бути в курсі важливих новин HMCL, або на <a href=\"https://space.bilibili.com/20314891\">@Glavo</a>, щоб дізнатися про прогрес розробки HMCL.\nupdate.channel.dev.title=Повідомлення каналу Бета\nupdate.channel.nightly=Нічний\nupdate.channel.nightly.hint=Ви зараз використовуєте збірку лаунчера каналу Нічний. Хоча вона може включати деякі додаткові функції, вона завжди менш стабільна, ніж збірки інших каналів. \\n\\\n   \\n\\\n   Якщо ви зіткнетеся з будь-якими помилками або проблемами, надішліть відгук через канали, надані на сторінці <a href=\"hmcl://settings/feedback\">Зворотний зв'язок</a>. \\n\\\n   \\n\\\n   Підписуйтесь на <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> на Bilibili, щоб бути в курсі важливих новин HMCL, або на <a href=\"https://space.bilibili.com/20314891\">@Glavo</a>, щоб дізнатися про прогрес розробки HMCL.\nupdate.channel.nightly.title=Повідомлення каналу Нічний\nupdate.channel.stable=Реліз\nupdate.checking=Перевірка оновлень\nupdate.failed=Не вдалося оновити\nupdate.found=Доступне оновлення!\nupdate.newest_version=Остання версія: %s\nupdate.bubble.title=Доступне оновлення: %s\nupdate.bubble.subtitle=Натисніть тут, щоб оновити\nupdate.note=Канали Бета та Нічний можуть мати більше функцій або виправлень, але вони також мають більше потенційних проблем.\nupdate.latest=Це остання версія\nupdate.no_browser=Не вдається відкрити в системному браузері. Але ми скопіювали посилання до вашого буфера обміну, і ви можете відкрити його вручну.\nupdate.tooltip=Оновити\n\nversion=Ігри\nversion.name=Назва екземпляра\nversion.cannot_read=Не вдалося розібрати екземпляр гри, встановлення не може продовжуватися.\nversion.empty=Немає екземплярів\nversion.empty.add=Додати новий екземпляр\nversion.empty.hint=Тут немає екземплярів Minecraft. Ви можете спробувати переключитися на інший ігровий каталог або натиснути тут, щоб завантажити один.\nversion.game.all=Усі\nversion.game.april_fools=Перший квітня\nversion.game.old=Історичні\nversion.game.release=Реліз\nversion.game.releases=Релізи\nversion.game.snapshot=Снапшот\nversion.game.snapshots=Снапшоти\nversion.game.type=Тип\nversion.launch=Запустити гру\nversion.launch.empty=Почати гру\nversion.launch.empty.installing=Встановлення гри\nversion.launch.empty.tooltip=Встановити та запустити останній офіційний реліз\nversion.launch.test=Тестовий запуск\nversion.switch=Переключити екземпляр\nversion.launch_script=Експортувати скрипт запуску\nversion.launch_script.failed=Не вдалося експортувати скрипт запуску.\nversion.launch_script.save=Експортувати скрипт запуску\nversion.launch_script.success=Експортовано скрипт запуску як %s.\nversion.manage=Усі екземпляри\nversion.manage.clean=Видалити файли журналів\nversion.manage.clean.tooltip=Видалити файли в каталогах \"logs\" та \"crash-reports\".\nversion.manage.duplicate=Дублювати екземпляр\nversion.manage.duplicate.prompt=Введіть нову назву екземпляра\nversion.manage.duplicate.confirm=Дубльований екземпляр матиме копію всіх файлів у каталозі екземпляра (\".minecraft/versions/<назва екземпляра>\"), з ізольованим робочим каталогом та налаштуваннями.\nversion.manage.manage=Редагувати екземпляр\nversion.manage.manage.title=Редагувати екземпляр - %1s\nversion.manage.redownload_assets_index=Оновити ресурси гри\nversion.manage.remove=Видалити екземпляр\nversion.manage.remove.confirm.trash=Ви впевнені, що хочете видалити екземпляр \"%s\"? Ви все ще можете знайти його файли у своєму кошику за назвою \"%s\".\nversion.manage.remove_assets=Видалити всі ресурси\nversion.manage.remove_libraries=Видалити всі бібліотеки\nversion.manage.rename=Перейменувати екземпляр\nversion.manage.rename.message=Введіть нову назву екземпляра\nversion.manage.rename.fail=Не вдалося перейменувати екземпляр. Деякі файли можуть використовуватися або назва містить недійсний символ.\nversion.search=Назва\nversion.search.prompt=Введіть назву версії для пошуку\nversion.settings=Налаштування\nversion.update=Оновити модпак\n\nwiki.tooltip=Сторінка Minecraft Wiki\nwiki.version.game=https://uk.minecraft.wiki/w/%s_(Java_Edition)\nwiki.version.game.snapshot=https://uk.minecraft.wiki/w/%s_(Java_Edition)\n\nwizard.prev=< Попередній\nwizard.failed=Не вдалося\nwizard.finish=Завершити\nwizard.next=Наступний >\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/lang/I18N_zh.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Contributors: pan93412\n\nabout=關於\nabout.copyright=著作權\nabout.copyright.statement=著作權所有 © 2013-2026 huangyuhui 與貢獻者\nabout.author=作者\nabout.author.statement=bilibili @huanghongxun\nabout.claim=使用者協議\nabout.claim.statement=點擊連結以查看全文\nabout.dependency=相依元件\nabout.legal=法律宣告\nabout.thanks_to=鳴謝\nabout.thanks_to.bangbang93.statement=提供 BMCLAPI 下載來源。請贊助支援 BMCLAPI！\nabout.thanks_to.burningtnt.statement=為 HMCL 貢獻許多技術支援\nabout.thanks_to.contributors=所有透過 Issue、Pull Request 等管道參與本專案的貢獻者\nabout.thanks_to.contributors.statement=沒有開源社群的支援，HMCL 無法走到今天\nabout.thanks_to.gamerteam.statement=提供預設背景圖\nabout.thanks_to.glavo.statement=負責 HMCL 的日常維護\nabout.thanks_to.zekerzhayard.statement=為 HMCL 貢獻許多技術支援\nabout.thanks_to.zkitefly.statement=負責維護 HMCL 的文件\nabout.thanks_to.mcbbs=MCBBS (我的世界中文論壇)\nabout.thanks_to.mcbbs.statement=提供 MCBBS 下載源 (現已停止服務)\nabout.thanks_to.mcim=mcmod-info-mirror\nabout.thanks_to.mcim.statement=為中國大陸使用者提供模組資訊快取加速服務\nabout.thanks_to.mcmod=MC 百科 (mcmod.cn)\nabout.thanks_to.mcmod.statement=提供模組簡體中文名映射表與模組百科\nabout.thanks_to.red_lnn.statement=提供預設背景圖\nabout.thanks_to.shulkersakura.statement=提供 HMCL 的標誌\nabout.thanks_to.users=HMCL 使用者群組成員\nabout.thanks_to.users.statement=感謝使用者群組成員贊助充電、積極催更、回報問題、出謀劃策\nabout.thanks_to.yushijinhun.statement=提供 authlib-injector 相關支援\nabout.open_source=開放原始碼\nabout.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL/)\n\naccount=帳戶\naccount.cape=披風\naccount.character=角色\naccount.choose=請選取角色\naccount.create=建立帳戶\naccount.create.microsoft=新增 Microsoft 帳戶\naccount.create.offline=新增離線模式帳戶\naccount.create.authlibInjector=新增 authlib-injector 帳戶\naccount.email=電子信箱\naccount.empty=沒有帳戶\naccount.failed=帳戶重新整理失敗\naccount.failed.character_deleted=已刪除此角色\naccount.failed.connect_authentication_server=無法連線至認證伺服器。可能是網路問題，請檢查裝置能否正常上網或使用代理服務。\naccount.failed.connect_injector_server=無法連線至認證伺服器。可能是網路故障，請檢查裝置能否正常上網、檢查網址是否輸入錯誤或使用代理服務。\naccount.failed.injector_download_failure=無法下載 authlib-injector。請檢查網路或嘗試切換下載來源。\naccount.failed.invalid_credentials=你的使用者名稱或密碼錯誤，或者登入次數過多被暫時禁止登入。請稍後再試。\naccount.failed.invalid_password=密碼無效\naccount.failed.invalid_token=請嘗試登出並重新輸入密碼登入。\naccount.failed.migration=你的帳戶需要遷移至 Microsoft 帳戶。如果你已經遷移，你需要使用遷移後的 Microsoft 帳戶登入。\naccount.failed.no_character=該帳戶沒有角色。\naccount.failed.server_disconnected=無法訪問登入伺服器。帳戶資訊重新整理失敗。\\n\\\n  你可以選取「再次重新整理帳戶」重新嘗試。\\n\\\n  你也可以選取「跳過帳戶重新整理」繼續啟動遊戲，但可能會導致帳戶資訊未同步更新。\\n\\\n  若最近沒有重新整理帳戶資訊，則可能導致帳戶資訊過期失效。\\n\\\n  若使用 Microsoft/authlib-injector 帳戶啟動遊戲，帳戶資訊過期失效可能將無法進入需線上驗證的伺服器。\\n\\\n  若嘗試多次無法重新整理，可嘗試重新增加該帳戶，或許可以解決該問題。\naccount.failed.server_response_malformed=無法解析認證伺服器回應，可能是伺服器故障。\naccount.failed.ssl=連線伺服器時發生了 SSL 錯誤。可能網站證書已過期或你使用的 Java 版本過低。請嘗試更新 Java。\naccount.failed.dns=連線伺服器時發生了 SSL 錯誤。可能是 DNS 解析有誤。請嘗試更換 DNS 伺服器或使用代理服務。\naccount.failed.wrong_account=登入了錯誤的帳戶\naccount.hmcl.hint=你需要點擊「登入」按鈕，並在開啟的網頁中完成登入\naccount.injector.add=新增認證伺服器\naccount.injector.empty=無 (按一下右側 + 新增)\naccount.injector.http=警告: 此伺服器使用不安全的 HTTP 協定。你的密碼在登入時會被明文傳輸。\naccount.injector.link.homepage=首頁\naccount.injector.link.register=註冊\naccount.injector.server=認證伺服器\naccount.injector.server_url=伺服器位址\naccount.injector.server_name=伺服器名稱\naccount.login=登入\naccount.login.hint=我們不會儲存你的密碼\naccount.login.skip=跳過重新整理帳戶\naccount.login.retry=再次重新整理帳戶\naccount.login.refresh=重新登入\naccount.login.refresh.microsoft.hint=由於帳戶授權失效，你需要重新增加 Microsoft 帳戶\naccount.login.restricted=登入微軟帳戶以啟用此功能\naccount.logout=登出\naccount.register=註冊\naccount.manage=帳戶清單\naccount.copy_uuid=複製該帳戶的 UUID\naccount.methods=登入方式\naccount.methods.authlib_injector=外部登入\naccount.methods.microsoft=Microsoft 帳戶\naccount.methods.microsoft.birth=如何變更帳戶出生日期\naccount.methods.microsoft.code=程式碼 (已自動複製)\naccount.methods.microsoft.close_page=已完成 Microsoft 帳戶授權。啟動器將自動執行後續步驟。你現在可以關閉本頁面了。\naccount.methods.microsoft.deauthorize=移除應用存取權\naccount.methods.microsoft.error.add_family=請點擊 <a href=\"https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a\">此處</a> 更改你的帳戶出生日期，使年齡滿 18 歲以上，或將帳戶加入到家庭中。\naccount.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 Xbox Live 的支援。\naccount.methods.microsoft.error.missing_xbox_account=請點擊下方 <a href=\"https://www.minecraft.net/msaprofile/mygames/editprofile\">此處</a> 關聯 Xbox 帳戶。\naccount.methods.microsoft.error.no_character=請確認你已經購買了 Minecraft: Java 版。\\n若已購買，則可能未建立遊戲檔案。請點擊 <a href=\"https://www.minecraft.net/msaprofile/mygames/editprofile\">此處</a> 建立遊戲檔案。\naccount.methods.microsoft.error.banned=你的帳戶可能被 Xbox Live 封禁。\\n你可以點擊 <a href=\"https://enforcement.xbox.com/enforcement/showenforcementhistory\">此處</a> 按鈕查詢帳戶封禁狀態。\naccount.methods.microsoft.error.unknown=登入失敗。錯誤碼：%d。\naccount.methods.microsoft.error.wrong_verify_method=登入失敗。請在 Microsoft 帳戶登入頁面嘗試使用密碼登入，不要使用其他登入方式。\naccount.methods.microsoft.logging_in=登入中……\naccount.methods.microsoft.makegameidsettings=建立檔案 / 編輯檔案名稱\naccount.methods.microsoft.hint=點擊「登入」按鈕開始新增 Microsoft 帳戶。\naccount.methods.microsoft.methods.device=掃描 QR Code 登入\naccount.methods.microsoft.methods.device.hint=掃描 QR Code 或訪問 <a href=\"%s\">%s</a>，在開啟的頁面中輸入 <b>%s</b> 完成登入。\naccount.methods.microsoft.methods.browser=在瀏覽器中登入\naccount.methods.microsoft.methods.browser.hint=點擊「登入」按鈕或者<a href=\"%s\">複製連結</a>並在瀏覽器中貼上以登入。\naccount.methods.microsoft.manual=<b>若網路環境不佳，可能會導致網頁載入緩慢甚至無法載入，請稍後再試或更換網路環境後再試。</b>\naccount.methods.microsoft.profile=編輯帳戶個人資訊\naccount.methods.microsoft.purchase=購買 Minecraft\naccount.methods.ban_query=查詢帳戶是否被封禁\naccount.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。請下載 <a href=\"https://hmcl.huangyuhui.net/download\">官方版本</a> 進行登入。\naccount.methods.microsoft.snapshot.tooltip=你正在使用第三方提供的 HMCL。請下載官方版本來重新整理帳戶。\naccount.methods.microsoft.snapshot.website=官方網站\naccount.methods.offline=離線模式\naccount.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名，且長度不超過 16 個字元\naccount.methods.offline.name.invalid=遊戲使用者名稱建議僅使用英文字母、數字及底線，且長度不超過 16 個字元。\\n\\\n  \\n\\\n  \\  · 一些有效的使用者名稱：HuangYu、huang_Yu、Huang_Yu_123；\\n\\\n  \\  · 一些無效的使用者名稱：黃魚，Huang Yu、Huang-Yu_%%%、Huang_Yu_hello_world_hello_world。\\n\\\n  \\n\\\n  使用非法使用者名稱會導致你無法進入大部分伺服器，並可能與部分模組衝突而使遊戲崩潰。\naccount.methods.offline.uuid=UUID\naccount.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一標識符。每個啟動器生成 UUID 的方式可能不同。\\n透過修改 UUID 選項至原啟動器所生成的 UUID，你可以保證在切換啟動器後，遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色，從而保留原來角色的背包物品。\\nUUID 選項為進階選項。除非你知道你在做什麼，否則你不需要調整該選項。\naccount.methods.offline.uuid.malformed=格式錯誤\naccount.missing=沒有遊戲帳戶\naccount.missing.add=按一下此處新增帳戶\naccount.move_to_global=轉換為全域帳戶\\n該帳戶的資訊會儲存至系統目前使用者目錄的配置檔案中\naccount.move_to_portable=轉換為可攜式帳戶\\n該帳戶的資訊會儲存至與 HMCL 同目錄的配置檔案中\naccount.not_logged_in=未登入\naccount.password=密碼\naccount.portable=可攜式帳戶\naccount.skin=外觀\naccount.skin.file=外觀圖片檔案\naccount.skin.model=模型\naccount.skin.model.default=寬型\naccount.skin.model.slim=纖細\naccount.skin.type.alex=Alex\naccount.skin.type.csl_api=Blessing Skin 伺服器\naccount.skin.type.csl_api.location=伺服器位址\naccount.skin.type.csl_api.location.hint=CustomSkinAPI 位址\naccount.skin.type.little_skin=LittleSkin 皮膚站\naccount.skin.type.little_skin.hint=你需要在皮膚站中新增並使用和該離線帳戶同名角色。此時離線帳戶外觀將為皮膚站上對應角色所設定的外觀。\naccount.skin.type.local_file=本機外觀圖片檔案\naccount.skin.type.steve=Steve\naccount.skin.upload=上傳/編輯外觀\naccount.skin.upload.failed=外觀上傳失敗\naccount.skin.invalid_skin=無法識別的外觀檔案\naccount.username=使用者名稱\n\narchive.author=作者\narchive.date=發布日期\narchive.file.name=檔案名稱\narchive.version=版本\n\nassets.download=下載資源\nassets.download_all=驗證資源檔案完整性\nassets.index.malformed=資源檔案的索引檔案損壞。你可以在相應實例的「實例管理」頁面中，點擊頁面左下角的「設定 → 更新遊戲資源檔案」以修復該問題。\n\nbutton.cancel=取消\nbutton.change_source=切換下載源\nbutton.clear=清除\nbutton.copy_and_exit=複製並退出\nbutton.delete=刪除\nbutton.do_not_show_again=不再顯示\nbutton.edit=編輯\nbutton.install=安裝\nbutton.export=匯出\nbutton.no=否\nbutton.ok=確定\nbutton.ok.countdown=確定 (%d)\nbutton.reset=重設\nbutton.reveal_dir=開啟目錄\nbutton.refresh=重新整理\nbutton.remove=刪除\nbutton.remove.confirm=你確認要刪除嗎？該操作無法復原！\nbutton.retry=重試\nbutton.save=儲存\nbutton.save_as=另存新檔\nbutton.select_all=全選\nbutton.view=查看\nbutton.yes=是\n\ncontact=回報\ncontact.chat=官方群組\ncontact.chat.discord=Discord 伺服器\ncontact.chat.discord.statement=歡迎加入 Discord 伺服器，加入後請遵守討論區規則\ncontact.chat.qq_group=使用者 QQ 群組\ncontact.chat.qq_group.statement=歡迎加入 HMCL 使用者 QQ 群組，加入後請遵守群組規則\ncontact.feedback=回報管道\ncontact.feedback.github=GitHub Issues\ncontact.feedback.github.statement=提交一個 GitHub Issue\n\ncolor.recent=建議\ncolor.custom=自訂顏色\n\ncrash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整，或更新你的 Java。\ncrash.user_fault=你的系統或 Java 環境可能安裝不當導致本軟體當機，請檢查你的 Java 環境或你的電腦！可以嘗試重新安裝 Java。\n\ncurse.category.0=全部\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4471\ncurse.category.4474=科幻\ncurse.category.4481=輕量模組包\ncurse.category.4483=戰鬥 PvP\ncurse.category.4477=小遊戲\ncurse.category.4478=任務\ncurse.category.4484=多人\ncurse.category.4476=探索\ncurse.category.4736=空島\ncurse.category.4475=冒險 RPG\ncurse.category.4487=FTB 模組包\ncurse.category.4480=有特定地圖\ncurse.category.4479=高難度\ncurse.category.4482=大型模組包\ncurse.category.4472=科技\ncurse.category.4473=魔法\ncurse.category.5128=原版增強\ncurse.category.7418=恐怖\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5299=教育\ncurse.category.5232=額外行星\ncurse.category.5129=原版增強\ncurse.category.5189=實用與 QOL\ncurse.category.6814=性能\ncurse.category.6954=動態聯合/整合動力 (Integrated Dynamics)\ncurse.category.6484=機械動力 (Create)\ncurse.category.6821=錯誤修復\ncurse.category.6145=空島\ncurse.category.5190=QOL\ncurse.category.5191=實用與 QOL\ncurse.category.5192=夢幻選單\ncurse.category.423=訊息展示\ncurse.category.426=模組擴展\ncurse.category.434=裝備武器\ncurse.category.409=自然生成\ncurse.category.4485=血魔法\ncurse.category.420=儲存\ncurse.category.429=工業 (Industrialcraft)\ncurse.category.419=魔法\ncurse.category.412=科技\ncurse.category.4557=紅石\ncurse.category.428=匠魂\ncurse.category.414=交通運輸\ncurse.category.4486=幸運方塊 (Lucky Blocks)\ncurse.category.432=建築 (Buildcraft)\ncurse.category.418=基因\ncurse.category.4671=Twitch\ncurse.category.5314=KubeJS\ncurse.category.408=礦物資源\ncurse.category.4773=CraftTweaker\ncurse.category.430=神秘 (Thaumcraft)\ncurse.category.422=冒險 RPG\ncurse.category.413=機器處理\ncurse.category.417=能源\ncurse.category.415=物流運輸\ncurse.category.433=林業 (Forestry)\ncurse.category.425=其他\ncurse.category.4545=應用能源 2 (Applied Energistics 2)\ncurse.category.416=農業\ncurse.category.421=支援庫\ncurse.category.4780=Fabric\ncurse.category.424=裝飾\ncurse.category.406=世界生成\ncurse.category.435=伺服器\ncurse.category.411=生物\ncurse.category.407=生物群系\ncurse.category.427=熱力膨脹 (Thermal Expansion)\ncurse.category.410=維度\ncurse.category.436=食物\ncurse.category.4558=紅石\ncurse.category.4843=自動化\ncurse.category.4906=MCreator\ncurse.category.7669=暮色森林\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\n curse.category.5244=字體包\ncurse.category.5193=封包\ncurse.category.399=蒸汽朋克\ncurse.category.396=128x\ncurse.category.398=512x 及更高\ncurse.category.397=256x\ncurse.category.405=其他\ncurse.category.395=64x\ncurse.category.400=模擬\ncurse.category.393=16x\ncurse.category.403=傳統\ncurse.category.394=32x\ncurse.category.404=動態效果\ncurse.category.4465=模組支援\ncurse.category.402=中世紀風格\ncurse.category.401=現代風格\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/17\ncurse.category.4464=模組\ncurse.category.250=遊戲挑戰\ncurse.category.249=創造模式\ncurse.category.251=跑酷\ncurse.category.253=生存模式\ncurse.category.248=冒險模式\ncurse.category.252=解謎類\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4546\ncurse.category.4551=硬核任務模式\ncurse.category.4548=幸運方塊 (Lucky Blocks)\ncurse.category.4556=任務進度\ncurse.category.4752=小物件\ncurse.category.4553=CraftTweaker\ncurse.category.4554=合成表\ncurse.category.4549=指引書\ncurse.category.4547=配置\ncurse.category.4550=任務\ncurse.category.4555=世界生成\ncurse.category.4552=指令碼\n\ncurse.category.6553=寫實\ncurse.category.6554=幻想\ncurse.category.6555=原生\n\ncurse.sort.author=作者\ncurse.sort.date_created=建立日期\ncurse.sort.last_updated=最近更新\ncurse.sort.name=名稱\ncurse.sort.popularity=熱度\ncurse.sort.total_downloads=下載量\n\ndatetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss\n\ndownload=下載\ndownload.hint=安裝遊戲和模組包或下載模組、資源包、光影和世界\ndownload.code.404=遠端伺服器沒有需要下載的檔案：%s\ndownload.content=遊戲內容\ndownload.shader=光影\ndownload.curseforge.unavailable=這個 HMCL 版本不支援訪問 CurseForge。請使用官方版本進行下載。\ndownload.existing=檔案已存在，無法儲存。你可以將檔案儲存至其他地方。\ndownload.external_link=開啟下載網站\ndownload.failed=下載失敗：%1$s\\n錯誤碼：%2$d\ndownload.failed.empty=沒有可供安裝的版本，按一下此處返回。\ndownload.failed.no_code=下載失敗\ndownload.failed.refresh=載入版本清單失敗，按一下此處重試。\ndownload.game=新遊戲\ndownload.provider.bmclapi=BMCLAPI\ndownload.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com/\ndownload.provider.mojang=官方伺服器\ndownload.provider.mojang.desc=OptiFine 由 BMCLAPI 提供\ndownload.provider.official=盡量使用官方源\ndownload.provider.official.desc=最新，但可能載入慢\ndownload.provider.balanced=選取載入速度快的下載源\ndownload.provider.balanced.desc=平衡，但可能不是最新\ndownload.provider.mirror=盡量使用鏡像源\ndownload.provider.mirror.desc=載入快，但可能不是最新\ndownload.java=下載 Java\ndownload.java.process=下載 Java\ndownload.javafx=正在下載必要的執行時元件\ndownload.javafx.notes=正在透過網路下載 HMCL 必要的執行時元件。\\n點擊「切換下載源」按鈕查看詳情以及選取下載源。點擊「取消」按鈕停止並退出。\\n注意：如果下載速度過慢，請嘗試切換下載源。\ndownload.javafx.component=正在下載元件「%s」\ndownload.javafx.prepare=準備開始下載\ndownload.speed.byte_per_second=%d B/s\ndownload.speed.kibibyte_per_second=%.1f KiB/s\ndownload.speed.megabyte_per_second=%.1f MiB/s\n\nexception.access_denied=無法存取檔案「%s」。因為 HMCL 沒有對該檔案的訪問權限，或者該檔案已被其他程式開啟。\\n\\\n  請你檢查目前作業系統帳戶是否能訪存取檔案，比如非管理員使用者可能不能訪問其他帳戶的個人目錄內的檔案。\\n\\\n  對於 Windows 使用者，你還可以嘗試透過資源監視器查看是否有程式占用了該檔案。如果是，你可以關閉占用此檔案的程式，或者重啟電腦再試。\nexception.artifact_malformed=下載的檔案正確，但無法透過校驗。\nexception.ssl_handshake=無法建立 SSL 連線。目前 Java 缺少相關的 SSL 證書。你可以嘗試使用其他 Java 或關閉網路代理開啟 HMCL 再試。\nexception.dns.pollution=無法建立 SSL 連線。可能是 DNS 解析有誤。請嘗試更換 DNS 伺服器或使用代理服務。\n\nextension.bat=Windows 批次檔\nextension.png=圖片檔案\nextension.ps1=PowerShell 指令碼\nextension.sh=Bash 指令碼\nextension.command=macOS Shell 指令碼\n\nextension.datapack=資料包壓縮檔\nextension.mod=模組檔案\nextension.modloader.installer=模組載入器安裝檔\nextension.resourcepack=資源包壓縮檔\nextension.schematic=原理圖檔案\nextension.world=世界壓縮檔\n\nfatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 無法建立 HMCL 資料夾 (%s)，請將 HMCL 移動至其他位置再開啟。\nfatal.javafx.incomplete=JavaFX 執行環境不完整。請嘗試更換你的 Java 或者重新安裝 OpenJFX。\nfatal.javafx.missing=缺少 JavaFX 執行環境。請使用包含 OpenJFX 的 Java 執行環境開啟 Hello Minecraft! Launcher。\nfatal.config_change_owner_root=你正在使用 root 帳戶開啟 Hello Minecraft! Launcher，這可能導致你未來無法使用其他帳戶正常開啟 HMCL。\\n是否繼續開啟？\nfatal.config_in_temp_dir=你正在暫存目錄中開啟 Hello Minecraft! Launcher，你的設定和遊戲資料可能會遺失。建議將 HMCL 移動至其他位置再開啟。\\n是否繼續開啟？\nfatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案。\\n請確保 HMCL 對「%s」目錄及該目錄下的檔案擁有讀寫權限。\nfatal.config_loading_failure.unix=Hello Minecraft! Launcher 無法載入設定檔案，因為設定檔案是由使用者「%1$s」建立的。\\n請使用 root 帳戶開啟 HMCL (不推薦)，或在終端中執行以下指令將設定檔案的所有權變更為目前使用者：\\n%2$s\nfatal.config_unsupported_version=目前設定檔案是由更高版本的 Hello Minecraft! Launcher 建立的，目前版本的 HMCL 無法正常載入。請更新並重新啟動 HMCL。\\n在更新啟動器之前，你所做的所有設定更改都不會被儲存。\nfatal.mac_app_translocation=由於 macOS 的安全機制，Hello Minecraft! Launcher 被系統隔離至暫存目錄中。\\n請將 HMCL 移動到其他目錄後再嘗試開啟，否則你的設定和遊戲資料可能會在重啟後遺失。\\n是否繼續開啟？\nfatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成，請重新開啟 HMCL。\nfatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程式，因為出現了一些問題。\\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。\nfatal.apply_update_need_win7=Hello Minecraft! Launcher 無法在 Windows XP/Vista 上進行自動更新。請從 %s 處手動下載 HMCL 來完成升級。\nfatal.deprecated_java_version=HMCL 未來需要 Java 17 或更高版本才能執行，但依然支援使用 Java 6~16 啟動遊戲。建議安裝最新版本的 Java 以確保 HMCL 正常執行。\\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java，並會自動根據遊戲版本為你選取合適的 Java。\nfatal.deprecated_java_version.update=更高版本的 HMCL 需要 Java 17 或更高版本才能執行，請安裝最新版本的 Java 以確保 HMCL 能夠完成升級。\\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java，並會自動根據遊戲版本為你選取合適的 Java。\nfatal.deprecated_java_version.download_link=下載 Java %d\nfatal.samba=如果您正在透過 Samba 共亯的目錄中開啟 Hello Minecraft! Launcher，啟動器可能無法正常工作，請嘗試更新您的 Java 或在本機目錄內開啟 HMCL。\nfatal.illegal_char=由於您的執行路徑中存在無效字元『=』，您將無法使用外部登入帳戶以及離線登入更換外觀功能。\nfatal.unsupported_platform=Minecraft 尚未對您的平臺提供完善支援，所以可能影響遊戲體驗或無法啟動遊戲。\\n\\\n  若無法啟動 Minecraft 1.17 及更高版本，可以嘗試在「(全域/實例特定) 遊戲設定 → 進階設定 → 除錯選項」中將「繪製器」切換為「Mesa LLVMpipe」，以獲得更好的相容性。\nfatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已為龍芯提供支援。\\n如果遇到問題，你可以點擊右上角幫助按鈕進行求助。\nfatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher 已為 Apple Silicon 平臺提供支援。使用 ARM 原生 Java 啟動遊戲以獲得更流暢的遊戲體驗。\\n如果你在遊戲中遭遇問題，使用 x86-64 架構的 Java 啟動遊戲可能有更好的相容性。\nfatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已為 Windows on Arm 平臺提供原生支援。如果你在遊戲中遭遇問題，請嘗試使用 x86 架構的 Java 啟動遊戲。\\n\\n如果你正在使用<b>高通</b>平臺，你可能需要安裝 <a href=\"ms-windows-store://pdp/?productid=9NQPSL29BFFF\">OpenGL 相容包</a>後才能進行遊戲。點擊連結前往 Microsoft Store 安裝相容包。\n\nfile=檔案\n\nfolder.config=模組設定目錄\nfolder.game=實例執行目錄\nfolder.logs=日誌目錄\nfolder.mod=模組目錄\nfolder.resourcepacks=資源包目錄\nfolder.shaderpacks=著色器包目錄\nfolder.saves=遊戲世界目錄\nfolder.schematics=原理圖目錄\nfolder.screenshots=截圖目錄\nfolder.world=世界目錄\n\ngame=遊戲\ngame.crash.feedback=<b>請不要將本介面截圖或拍照給他人！</b>如果你要求助他人，請你點擊左下角「匯出遊戲崩潰資訊」後將匯出的檔案發送給他人以供分析。\\n你可以點擊下方的「幫助」前往社群尋求幫助。\ngame.crash.info=遊戲訊息\ngame.crash.reason=崩潰原因\ngame.crash.reason.analyzing=分析中……\ngame.crash.reason.multiple=檢測到多個原因：\\n\\n\ngame.crash.reason.block=目前遊戲由於某個方塊不能正常工作，無法繼續執行。\\n你可以嘗試使用地圖編輯工具編輯世界刪除該方塊，或者直接刪除對應的模組。\\n方塊類型：%1$s\\n方塊坐標：%2$s\\n\\n相關工具：<a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">英文 Wiki</a> | <a href=\"https://zh.minecraft.wiki/w/辅助程序与编辑器/地图工具?variant=zh-tw#地图编辑工具\">中文 Wiki</a>\ngame.crash.reason.bootstrap_failed=目前遊戲由於模組「%1$s」出現問題，無法繼續執行。\\n你可以嘗試刪除或更新該模組以解決問題。\ngame.crash.reason.mixin_apply_mod_failed=目前遊戲由於 Mixin 無法應用於「%1$s」模組，無法繼續執行。\\n你可以嘗試刪除或更新該模組以解決問題。\ngame.crash.reason.config=目前遊戲由於無法解析模組配置檔案，無法繼續執行\\n模組「%1$s」的配置檔案「%2$s」無法被解析。\ngame.crash.reason.debug_crash=目前遊戲由於手動觸發崩潰，無法繼續執行。\\n事實上遊戲並沒有問題，問題都是你造成的！\ngame.crash.reason.duplicated_mod=目前遊戲由於模組重複安裝，無法繼續執行。\\n%s\\n每種模組只能安裝一個，請你刪除多餘的模組再試。\ngame.crash.reason.entity=目前遊戲由於某個實體不能正常工作，無法繼續執行。\\n你可以嘗試使用地圖編輯工具編輯世界刪除該實體，或者直接刪除對應的模組。\\n實體類型：%1$s\\n實體坐標：%2$s\\n\\n相關工具：<a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">英文 Wiki</a> | <a href=\"https://zh.minecraft.wiki/w/辅助程序与编辑器/地图工具?variant=zh-tw#地图编辑工具\">中文 Wiki</a>\ngame.crash.reason.fabric_version_0_12=Fabric Loader 0.12 及更高版本與目前已經安裝的模組可能不相容，你需要將 Fabric Loader 降級至 0.11.7。\ngame.crash.reason.fabric_warnings=Fabric 提供了一些警告訊息：\\n%1$s\ngame.crash.reason.modmixin_failure=目前遊戲由於某些模組注入失敗，無法繼續執行。\\n這一般代表著該模組存在問題，或與目前環境不相容。\\n你可以查看日誌尋找出錯模組。\ngame.crash.reason.mod_repeat_installation=目前遊戲由於重複安裝了多個相同的模組，無法繼續執行。\\n每個模組只能出現一次，請刪除重複的模組，然後再啟動遊戲。\ngame.crash.reason.forge_error=Forge/NeoForge 可能已經提供了錯誤資訊。\\n你可以查看日誌，並根據錯誤報告中的日誌資訊進行對應處。\\n如果沒有看到報錯資訊，可以查看錯誤報告了解錯誤具體是如何發生的。\\n%1$s\ngame.crash.reason.mod_resolution0=目前遊戲由於一些模組出現問題，無法繼續執行。\\n你可以查看日誌尋找出錯模組。\ngame.crash.reason.need_jdk11=目前遊戲由於 Java 版本不合適，無法繼續執行。\\n你需要下載安裝 Java 11，並在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中將 Java 設定為 11.x 的版本。\ngame.crash.reason.java_version_is_too_high=目前遊戲由於使用的 Java 版本過高而崩潰了，無法繼續執行。\\n請在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中改用較低版本的 Java，然後再啟動遊戲。\\n如果沒有，可以從 <a href=\"https://www.java.com/download/\">java.com (Java 8)</a> 或 <a href=\"https://bell-sw.com/pages/downloads/#downloads\">BellSoft Liberica Full JRE (Java 17)</a> 等平台下載、安裝一個 (安裝完後需重啟啟動器)。\ngame.crash.reason.mod_name=目前遊戲由於模組檔案名稱問題，無法繼續執行。\\n模組檔案名稱應只使用半型的大小寫字母 (Aa~Zz)、數位 (0~9)、橫線 (-)、底線 (_)和點 (.)。\\n請到模組目錄中將所有不合規的模組檔案名稱修正為上述合規字元。\ngame.crash.reason.incomplete_forge_installation=目前遊戲由於 Forge 安裝不完整，無法繼續執行。\\n請在「實例管理 - 自動安裝」中移除 Forge 並重新安裝。\ngame.crash.reason.file_already_exists=目前遊戲由於檔案「%1$s」已經存在，無法繼續執行。\\n如果你認為這個檔案可以刪除，你可以在備份這個檔案後嘗試刪除它，並重新啟動遊戲。\ngame.crash.reason.file_changed=目前遊戲由於檔案校驗失敗，無法繼續執行。\\n如果你手動修改了 Minecraft.jar 檔案，你需要回退修改，或者重新下載遊戲。\ngame.crash.reason.gl_operation_failure=目前遊戲由於你使用的某些模組/光影包/資源包/紋理包出現問題，無法繼續執行。\\n請先嘗試停用你所使用的模組/光影包/資源包/紋理包再試。\ngame.crash.reason.graphics_driver=目前遊戲由於你的顯示卡驅動存在問題崩潰了。請嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。\\n\\\n  如果你的電腦同時存在獨立顯示卡與整合式顯示卡，你需要檢查遊戲是否使用整合式顯示卡啟動。如果是，請嘗試使用獨立顯示卡開啟 HMCL 與遊戲。如果仍有問題，你可能需要考慮換一個新顯示卡或新電腦。\\n\\\n  如果你確實需要使用整合式顯示卡，請檢查你電腦的 CPU 是否為 Intel(R) Core(TM) 3000 系列或更舊的處理器。如果是，對於 Minecraft 1.16.5 及更低版本，請你將遊戲所使用的 Java 版本降級至 1.8.0_51 及更低版本；否則你需要更換獨立顯示卡或新電腦。\ngame.crash.reason.macos_failed_to_find_service_port_for_display=目前遊戲由於 Apple Silicon 平台下初始化 OpenGL 視窗失敗，無法繼續執行。\\n對於該問題，HMCL 暫無直接性的解決方案。請你嘗試任意開啟一個瀏覽器並切換為全螢幕，然後再回到 HMCL 啟動遊戲。在彈出遊戲視窗前<b>迅速切回瀏覽器頁面</b>，等待遊戲視窗出現後再切回遊戲視窗。\ngame.crash.reason.illegal_access_error=目前遊戲由於某些模組的問題，無法繼續執行。\\n如果你認識「%1$s」，你可以更新或刪除對應模組再試。\ngame.crash.reason.install_mixinbootstrap=目前遊戲由於缺失 MixinBootstrap，無法繼續執行。\\n你可以嘗試安裝 <a href=\"https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap\">MixinBootstrap</a> 解決該問題。若安裝後崩潰，可以嘗試在該模組的檔案名前新增半形驚嘆號 (!) 解決。\ngame.crash.reason.jdk_9=目前遊戲由於 Java 版本過高，無法繼續執行。\\n你需要下載安裝 Java 8，並在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中將 Java 設定為 1.8.x 的版本。\ngame.crash.reason.jvm_32bit=目前遊戲由於記憶體分配過大，超過了 32 位 Java 虛擬機記憶體限制，無法繼續執行。\\n如果你的電腦是 64 位系統，請下載安裝並更換 64 位 Java。\\n如果你的電腦是 32 位系統，你或許可以重新安裝 64 位系統，或換一台新電腦。\\n或者，你可以關閉「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中的「自動分配」，並且把記憶體分配值調節為 1024 MiB 或以下。\ngame.crash.reason.loading_crashed_forge=目前遊戲由於模組「%1$s (%2$s)」錯誤，無法繼續執行。\\n你可以嘗試刪除或更新該模組以解決問題。\ngame.crash.reason.loading_crashed_fabric=目前遊戲由於模組「%1$s」錯誤，無法繼續執行。\\n你可以嘗試刪除或更新該模組以解決問題。\ngame.crash.reason.mac_jdk_8u261=目前遊戲由於你所使用的 Forge 或 OptiFine 與 Java 衝突而崩潰。\\n請嘗試更新 Forge 和 OptiFine，或使用 Java 8u251 及更早版本啟動。\ngame.crash.reason.memory_exceeded=目前遊戲由於記憶體分配過大，無法繼續執行。\\n該問題是由於系統分頁檔太小導致的。\\n你需要在「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中關閉「自動分配」，並將記憶體分配值調低至遊戲能正常啟動為止。\\n你還可以嘗試調大系統的分頁檔大小。\ngame.crash.reason.mod=目前遊戲由於 %1$s 的問題，無法繼續執行。\\n你可以更新或刪除已經安裝的「%1$s」再試。\ngame.crash.reason.mod_resolution=目前遊戲由於相依模組問題，無法繼續執行。Fabric 提供了如下訊息：\\n%1$s\ngame.crash.reason.forgemod_resolution=目前遊戲由於模組相依元件問題，無法繼續執行。Forge/NeoForge 提供了以下資訊：\\n%1$s\ngame.crash.reason.forge_found_duplicate_mods=目前遊戲由於模組重複的問題，無法繼續執行。Forge/NeoForge 提供了以下資訊：\\n%1$s\ngame.crash.reason.mod_resolution_collection=目前遊戲由於相依模組版本不匹配，無法繼續執行。\\n「%1$s」需要相依模組「%2$s」才能繼續執行。\\n這表示你需要更新或降級相依模組。你可以到「下載 → 模組」頁面或網路上下載「%3$s」。\ngame.crash.reason.mod_resolution_conflict=目前遊戲由於模組衝突，無法繼續執行。\\n「%1$s」與「%2$s」不能相容。\ngame.crash.reason.mod_resolution_missing=目前遊戲由於缺少相依模組，無法繼續執行。\\n「%1$s」需要相依模組「%2$s」才能繼續執行。\\n這表示你有一些必需的模組沒有安裝，或該模組版本不相配。你可以在「下載 → 模組」頁面或網路上下載「%2$s」。\ngame.crash.reason.mod_resolution_missing_minecraft=目前遊戲由於模組和 Minecraft 遊戲版本不匹配，無法繼續執行。\\n「%1$s」需要 Minecraft %2$s 才能執行。\\n如果你要繼續使用你已經安裝的模組，你可以選取安裝對應的 Minecraft 版本。如果你要繼續使用目前的 Minecraft 版本，你需要安裝對應版本的模組。\ngame.crash.reason.mod_resolution_mod_version=%1$s (版本號 %2$s)\ngame.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本)\ngame.crash.reason.forge_repeat_installation=目前遊戲由於 Forge 重複安裝，無法繼續執行。<a href=\"https://github.com/HMCL-dev/HMCL/issues/1880\">此為已知問題</a>\\n建議將日誌上傳並回報至 GitHub，以便我們找到更多線索並修復此問題。\\n目前你可以在「實例管理 → 自動安裝」中移除 Forge 並重新安裝。\ngame.crash.reason.optifine_repeat_installation=目前遊戲由於重複安裝 OptiFine，無法繼續執行。\\n請刪除模組目錄下的 OptiFine 或前往「實例管理 → 自動安裝」移除安裝的 OptiFine。\ngame.crash.reason.optifine_is_not_compatible_with_forge=目前遊戲由於 OptiFine 與目前版本的 Forge 不相容，導致了遊戲崩潰。\\n請前往 <a href=\"https://optifine.net/downloads\">OptiFine 官網</a>查看 OptiFine 所相容的 Forge 版本，並嚴格按照對應版本重新安裝遊戲，或在「實例管理 → 自動安裝」中更換版本。\\n經測試，Forge 版本過高或過低都可能導致崩潰。\ngame.crash.reason.night_config_fixes=目前遊戲由於 Night Config 庫的一些問題，無法繼續執行。\\n你可以嘗試安裝 <a href=\"https://www.curseforge.com/minecraft/mc-mods/night-config-fixes\">Night Config Fixes</a> 模組，這或許能幫助你解決這個問題。\\n了解更多，可訪問該模組的 <a href=\"https://github.com/Fuzss/nightconfigfixes\">GitHub 儲存庫</a>。\ngame.crash.reason.mod_files_are_decompressed=目前遊戲由於模組檔案被解壓了，無法繼續執行。\\n請直接把整個模組檔案放進模組目錄中即可。\\n解壓模組檔案會導致遊戲出錯。請刪除模組目錄中已被解壓的模組，然後再啟動遊戲。\ngame.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=目前遊戲由於你所安裝的模組過多，超出了遊戲的 ID 限制，無法繼續執行。\\n請嘗試安裝<a href=\"https://www.curseforge.com/minecraft/mc-mods/jeid\">JEID</a>等修正模組，或刪除部分大型模組。\ngame.crash.reason.optifine_causes_the_world_to_fail_to_load=目前遊戲可能由於 OptiFine 而無法繼續執行。\\n該問題只在特定 OptiFine 版本中出現，你可以嘗試在「實例管理 → 自動安裝」中更換 OptiFine 的版本。\ngame.crash.reason.modlauncher_8=目前遊戲由於你所使用的 Forge 版本與目前使用的 Java 衝突崩潰。請嘗試更新 Forge。\ngame.crash.reason.shaders_mod=目前遊戲由於同時安裝了 OptiFine 和 Shaders 模組，無法繼續執行。 \\n由於 OptiFine 已整合 Shaders 模組的功能，只需刪除 Shaders 模組即可。\ngame.crash.reason.rtss_forest_sodium=目前遊戲由於 RivaTuner Statistics Server (RTSS) 與 Sodium 不相容，導致遊戲崩潰。\\n點擊 <a href=\"https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\">此處</a> 查看詳情。\ngame.crash.reason.no_class_def_found_error=目前遊戲由於程式碼不完整，無法繼續執行。\\n你的遊戲可能缺失了某個模組，或者某些模組檔案不完整，或者模組與遊戲的版本不匹配。\\n你可能需要重新安裝遊戲和模組，或請求他人幫助。\\n缺失：%1$s\ngame.crash.reason.no_such_method_error=目前遊戲由於程式碼不完整，無法繼續執行。\\n你的遊戲可能缺失了某個模組，或者某些模組檔案不完整，或者模組與遊戲的版本不匹配。\\n你可能需要重新安裝遊戲和模組，或請求他人幫助。\ngame.crash.reason.opengl_not_supported=目前遊戲由於你的顯示卡驅動存在問題，無法繼續執行。\\n原因是 OpenGL 不受支援，你現在是否在遠端桌面或者串流模式下？如果是，請直接使用原電腦啟動遊戲。\\n或者嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。如果你的電腦同時存在獨立顯示卡與整合式顯示卡，你需要檢查遊戲是否使用整合式顯示卡啟動。如果是，請嘗試使用獨立顯示卡開啟 HMCL 與遊戲。如果仍有問題，你可能需要考慮換一個新顯示卡或新電腦。\ngame.crash.reason.openj9=目前遊戲無法執行在 OpenJ9 虛擬機上。請你在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中更換為使用 Hotspot 虛擬機的 Java，並重新啟動遊戲。如果沒有下載安裝，你可以在網路上自行下載。\ngame.crash.reason.out_of_memory=目前遊戲由於記憶體不足，無法繼續執行。\\n這可能是記憶體分配過小，或者模組數量過多導致的。\\n你可以在「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中調整遊戲記憶體分配值以允許遊戲在更大的記憶體下執行。\\n如果仍然出現該錯誤，你可能需要換一台更好的電腦。\ngame.crash.reason.resolution_too_high=目前遊戲由於資源包/紋理包解析度過高，無法繼續執行。\\n你可以更換一個解析度更低的資源包/紋理包，或者更換一個視訊記憶體更大的顯示卡。\ngame.crash.reason.stacktrace=原因未知。請點擊日誌按鈕查看詳細訊息。\\n下面是一些關鍵字，其中可能包含模組名稱，你可以透過搜尋的方式尋找有關訊息。\\n%s\ngame.crash.reason.too_old_java=目前遊戲由於 Java 版本過低，無法繼續執行。\\n你需要在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中更換 %1$s 或更高版本的 Java，並重新啟動遊戲。如果沒有下載安裝，你可以點擊 <a href=\"https://learn.microsoft.com/java/openjdk/download\">此處</a> 下載 Microsoft JDK。\ngame.crash.reason.unknown=原因未知。請點擊日誌按鈕查看詳細訊息。\ngame.crash.reason.unsatisfied_link_error=目前遊戲由於缺少本機庫，無法繼續執行。\\n這些本機庫缺失：%1$s。\\n如果你在「(全域/實例特定) 遊戲設定 → 進階設定」中修改了本機庫路徑選項，請你修改回預設模式。\\n如果你正在使用預設模式，請檢查遊戲目錄路徑是否只包含英文字母、數字和底線。\\n如果是，那麼請檢查是否為模組或 HMCL 導致了本機庫缺失的問題。如果你確定是 HMCL 引起的，建議你向我們回報。\\n<b>對於 Windows 使用者，你還可以嘗試在「控制台 → 時鐘和區域 → 地區 → 系統管理 → 變更系統區域設定」中，關閉「Beta：使用 Unicode UTF-8 提供全球語言支援」選項；</b>\\n<b>或將遊戲目錄路徑中的所有非英文字母的名稱 (例如中文、空格等) 修改為英文字母。</b>\\n如果你確實需要自訂本機庫路徑，你需要保證其中包含缺失的本機庫！\ngame.crash.title=遊戲意外退出\ngame.directory=遊戲目錄路徑\ngame.version=遊戲實例\n\nhelp=說明\nhelp.doc=Hello Minecraft! Launcher 說明文件\nhelp.detail=可查閱資料包、模組包製作教學等內容\n\ninput.email=[使用者名稱] 必須是電子信箱格式\ninput.number=必須是數字\ninput.not_empty=必填\ninput.url=必須是有效連結\n\ninstall=新增實例\ninstall.change_version=變更版本\ninstall.change_version.confirm=你確定要將 %s 從 %s 更新到 %s 嗎？\ninstall.change_version.process=變更版本\ninstall.failed=安裝失敗\ninstall.failed.downloading=安裝失敗，部分檔案未能完成下載\ninstall.failed.downloading.detail=未能下載檔案: %s\ninstall.failed.downloading.timeout=下載逾時: %s\ninstall.failed.install_online=無法識別要安裝的載入器。如果你要安裝模組，你需要在模組管理頁面安裝模組。\ninstall.failed.malformed=剛才下載的檔案已損壞。你可以在「設定 → 下載 → 下載來源」中切換其他下載來源以解決此問題。\ninstall.failed.optifine_conflict=暫不支援 OptiFine 與 Fabric 同時安裝在 Minecraft 1.13 上。\ninstall.failed.optifine_forge_1.17=對於 Minecraft 1.17.1 版本，僅 OptiFine H1 pre2 及更高版本與 Forge 相容。你可以從 OptiFine 預覽版 (Preview versions) 中選取最新版本。\ninstall.failed.version_mismatch=該載入器需要的遊戲版本為 %s，但實際的遊戲版本為 %s。\ninstall.installer.change_version=%s 與目前遊戲不相容，請更換版本\ninstall.installer.choose=選取 %s 版本\ninstall.installer.cleanroom=Cleanroom\ninstall.installer.depend=需要先安裝 %s\ninstall.installer.do_not_install=不安裝\ninstall.installer.fabric=Fabric\ninstall.installer.fabric-api=Fabric API\ninstall.installer.legacyfabric=Legacy Fabric\ninstall.installer.legacyfabric-api=Legacy Fabric API\ninstall.installer.fabric-quilt-api.warning=%1$s 是一個模組，將會被安裝到新遊戲的模組目錄。請你在安裝遊戲後不要修改目前遊戲的「執行路徑」設定。如果你在之後修改了相關設定，則需要重新安裝 %1$s。\ninstall.installer.forge=Forge\ninstall.installer.neoforge=NeoForge\ninstall.installer.game=Minecraft\ninstall.installer.incompatible=與 %s 不相容\ninstall.installer.install=安裝 %s\ninstall.installer.install_offline=從本機檔案安裝或升級\ninstall.installer.install_offline.tooltip=支援匯入已經下載好的 (Neo)Forge、Cleanroom 及 OptiFine 安裝器\ninstall.installer.install_online=線上安裝\ninstall.installer.install_online.tooltip=支援安裝 Forge、NeoForge、Cleanroom、Fabric、Legacy Fabric、Quilt、LiteLoader 和 OptiFine\ninstall.installer.liteloader=LiteLoader\ninstall.installer.not_installed=未安裝\ninstall.installer.optifine=OptiFine\ninstall.installer.quilt=Quilt\ninstall.installer.quilt-api=QSL/QFAPI\ninstall.installer.version=%s\ninstall.installer.external_version=%s [由外部安裝的版本，無法解除安裝或更換]\ninstall.installing=安裝\ninstall.modpack=安裝模組包\ninstall.modpack.installation=安裝模組包\ninstall.name.invalid=名稱中包含特殊字元 (如 Emoji 表情或中文字元)。\\n建議修改名稱。名稱建議僅包含英文字母、數字和底線，以防啟動遊戲時出現問題。是否繼續安裝？\ninstall.new_game=安裝新實例\ninstall.new_game.already_exists=此實例已經存在，請重新命名\ninstall.new_game.current_game_version=目前遊戲實例\ninstall.new_game.installation=安裝新實例\ninstall.new_game.malformed=名稱無效\ninstall.select=請選取安裝方式\ninstall.success=安裝成功\n\njava.add=新增 Java\njava.add.failed=Java 無效或與目前平臺不相容\njava.disable=停用此 Java\njava.disable.confirm=你確定要停用此 Java 嗎？\njava.disabled.management=管理已停用的 Java\njava.disabled.management.remove=從清單中移除此 Java\njava.disabled.management.restore=重新啟用此 Java\njava.download=下載 Java\njava.download.banshanjdk-8=下載 Banshan JDK 8\njava.download.load_list.failed=載入版本清單失敗\njava.download.more=更多發行版\njava.download.title=下載 Java\njava.download.prompt=請選擇你要下載的 Java 版本：\njava.download.distribution=發行版\njava.download.version=版本\njava.download.packageType=包類型\njava.management=Java 管理\njava.info.architecture=架構\njava.info.vendor=供應商\njava.info.version=版本\njava.info.disco.distribution=發行版本\njava.install=安裝 Java\njava.install.archive=源路徑\njava.install.failed.exists=該名稱已被使用\njava.install.failed.invalid=該檔案不是合法的 Java 安裝包，無法繼續安裝。\njava.install.failed.unsupported_platform=此 Java 與目前平臺不相容，無法安裝。\njava.install.name=名稱\njava.install.warning.invalid_character=名稱中包含無效字元\njava.installing=安裝 Java\njava.uninstall=移除此 Java\njava.uninstall.confirm=你確定要移除此 Java 嗎？此操作無法復原！\n\nlang.default=使用系統語言\n\nlaunch.advice=%s 是否繼續啟動？\nlaunch.advice.multi=檢測到以下問題：\\n\\n%s\\n\\n這些問題可能導致遊戲無法正常啟動或影響遊戲體驗，是否繼續啟動？\nlaunch.advice.java.auto=目前選取的 Java 版本不滿足遊戲要求，是否自動選取合適的 Java 版本？\\n或者你可以到「(全域/實例特定) 遊戲設定 → 遊戲 Java」中選取一個合適的 Java 版本。\nlaunch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。\nlaunch.advice.cleanroom=Cleanroom %2$s 只能在 Java %1$s 或更高版本上運行。請使用 Java %1$s 或最新版本。\nlaunch.advice.corrected=我們已經修正了 Java 版本問題。如果你確實希望使用你自訂的 Java，你可以在「(全域/實例特定) 遊戲設定 → 進階設定」中往下滑，開啟「不檢查 Java 虛擬機與遊戲的相容性」。\nlaunch.advice.uncorrected=如果你確實希望使用你自訂的 Java，你可以在「(全域/實例特定) 遊戲設定 → 進階設定」中往下滑，開啟「不檢查 Java 虛擬機與遊戲的相容性」。\nlaunch.advice.different_platform=你正在使用 32 位元 Java 啟動遊戲。建議更換至 64 位元 Java。\nlaunch.advice.forge2760_liteloader=Forge 14.23.5.2760 與 LiteLoader 不相容。請更新 Forge 至 14.23.5.2773 或更高版本。\nlaunch.advice.forge28_2_2_optifine=Forge 28.2.2 及更高版本與 OptiFine 不相容，請降級 Forge 至 28.2.1 或更低版本。\nlaunch.advice.forge37_0_60=Forge 37.0.59 及更低版本與 Java 17 不相容。請更新 Forge 到 37.0.60 或更高版本，或者使用 Java 16 啟動遊戲。\nlaunch.advice.java8_1_13=Minecraft 1.13 及更高版本僅支援 Java 8 或更高版本。請使用 Java 8 或最新版本。\nlaunch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議你到 https://java.com 安裝最新版的 Java 8。\nlaunch.advice.java9=低於 (包含) 1.13 的有安裝模組的 Minecraft 版本不支援 Java 9 或更高版本。請使用 Java 8。\nlaunch.advice.modded_java=部分模組可能與高版本 Java 不相容。建議使用 Java %s 啟動 Minecraft %s。\nlaunch.advice.modlauncher8=你所使用的 Forge 版本與目前使用的 Java 不相容。請更新 Forge。\nlaunch.advice.newer_java=偵測到你正在使用舊版本 Java 啟動遊戲，這可能導致部分模組引發遊戲崩潰。建議更新至 Java 8 後再次啟動。\nlaunch.advice.not_enough_space=你設定的記憶體分配值過大，超過了系統記憶體大小 %d MiB，可能影響遊戲體驗或無法啟動遊戲。\nlaunch.advice.require_newer_java_version=目前遊戲版本需要 Java %s，但 HMCL 未能找到該 Java 版本。你可以點擊「是」，HMCL 會自動下載它。是否下載？\nlaunch.advice.too_large_memory_for_32bit=你設定的記憶體分配值過大，由於可能超過了 32 位元 Java 的記憶體分配限制，所以可能無法啟動遊戲。請將記憶體分配值調至低於 1024 MiB 的值。\nlaunch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台，Minecraft 1.12.2 及更低版本與 Java 9+ 不相容，請使用 Java 8 啟動遊戲。\nlaunch.advice.vanilla_x86.translation=Minecraft 尚未為你的平臺提供完善支援，所以可能影響遊戲體驗或無法啟動遊戲。\\n你可以在 <a href=\"https://learn.microsoft.com/java/openjdk/download\">這裡</a> 下載 <b>x86-64</b> 架構的 Java 以獲得更完整的體驗。\\n是否繼續啟動？\nlaunch.advice.unknown=由於以下原因，無法繼續啟動遊戲：\nlaunch.failed=啟動失敗\nlaunch.failed.cannot_create_jvm=無法建立 Java 虛擬機。可能是虛擬機參數有問題。可以在「(全域/實例特定) 遊戲設定 → 進階設定 → Java 虛擬機設定」中移除所有虛擬機參數後，嘗試再次啟動遊戲。\nlaunch.failed.creating_process=啟動失敗，在建立新處理程式時發生錯誤。可能是 Java 路徑錯誤。\nlaunch.failed.command_too_long=指令長度超過限制，無法建立批次檔指令碼，請匯出為 PowerShell 指令碼。\nlaunch.failed.decompressing_natives=無法解壓縮遊戲本機庫。\nlaunch.failed.download_library=無法下載遊戲相依元件「%s」。\nlaunch.failed.executable_permission=無法為啟動檔案新增執行權限。\nlaunch.failed.execution_policy=設定執行策略\nlaunch.failed.execution_policy.failed_to_set=設定執行策略失敗\nlaunch.failed.execution_policy.hint=目前執行策略封鎖你執行 PowerShell 指令碼。\\n點擊「確定」允許目前使用者執行本機 PowerShell 指令碼，或點擊「取消」保持現狀。\nlaunch.failed.exited_abnormally=遊戲非正常退出。請查看日誌檔案，或聯絡他人尋求幫助。\nlaunch.failed.java_version_too_low=你所指定的 Java 版本過低。請重新設定 Java 版本。\nlaunch.failed.no_accepted_java=找不到適合目前遊戲使用的 Java。是否使用預設 Java 啟動遊戲？點擊「是」使用預設 Java 繼續啟動遊戲，\\n或者請到「(全域/實例特定) 遊戲設定 → 遊戲 Java」中選取一個合適的 Java 版本。\nlaunch.failed.sigkill=遊戲被使用者或系統強制終止。\nlaunch.state.dependencies=處理遊戲相依元件\nlaunch.state.done=啟動完成\nlaunch.state.java=檢測 Java 版本\nlaunch.state.logging_in=登入\nlaunch.state.modpack=下載必要檔案\nlaunch.state.waiting_launching=等待遊戲啟動\nlaunch.invalid_java=目前設定的 Java 路徑無效，請重新設定 Java 路徑。\n\nlauncher=啟動器\nlauncher.agreement=使用者協議與免責宣告\nlauncher.agreement.accept=同意\nlauncher.agreement.decline=拒絕\nlauncher.agreement.hint=同意本軟體的使用者協議與免責宣告以使用本軟體。\nlauncher.april_fools.switch_lzh=HMCL 啟動器現已支援文言文，我們誠邀你參與體驗。是否要將介面語言切換至中文 (文言)？\\n在切換語言後，你可以在「置設 → 貫用 → 文」中將語言切換回中文 (繁體)。\nlauncher.april_fools.switch_lzh.confirm=確認切換語言？\\n點擊「確定」後 HMCL 將自動重新啟動並將介面語言切換至中文 (文言)；點擊「取消」將繼續使用中文 (繁體) 進入 HMCL 主頁。\nlauncher.background=背景圖片\nlauncher.background.choose=選取背景圖片\nlauncher.background.classic=經典\nlauncher.background.default=預設\nlauncher.background.default.tooltip=自動尋找啟動器同目錄下的「background.png/.jpg/.gif/.webp」及「bg」目錄內的圖片\nlauncher.background.network=網路\nlauncher.background.paint=純色\nlauncher.cache_directory=檔案下載快取目錄\nlauncher.cache_directory.clean=清理\nlauncher.cache_directory.choose=選取檔案下載快取目錄\nlauncher.cache_directory.default=預設 (\"%APPDATA%/.minecraft\" 或 \"~/.minecraft\")\nlauncher.cache_directory.disabled=停用\nlauncher.cache_directory.invalid=無法建立自訂的快取目錄。已還原至預設設定。\nlauncher.contact=聯絡我們\nlauncher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤。請複製下列內容並透過 GitHub、Discord 或 HMCL QQ 群回報問題。\nlauncher.crash.java_internal_error=Hello Minecraft! Launcher 由於目前 Java 損壞而無法繼續執行。請移除目前 Java，點擊 <a href=\"https://bell-sw.com/pages/downloads/#downloads\">此處</a> 安裝合適的 Java 版本。\nlauncher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤。已偵測到你的啟動器不是最新版本，請更新後重試！\nlauncher.update_java=請更新你的 Java\n\nlibraries.download=下載依賴庫\n\nlogin.empty_username=你還未設定使用者名稱！\nlogin.enter_password=請輸入你的密碼\n\nlogwindow.show_lines=顯示行數\nlogwindow.terminate_game=結束遊戲處理程式\nlogwindow.title=日誌\nlogwindow.help=你可以前往 HMCL 社群，向他人尋求幫助\nlogwindow.autoscroll=自動滾動\nlogwindow.export_game_crash_logs=匯出遊戲崩潰資訊\nlogwindow.export_dump=匯出遊戲執行堆疊\nlogwindow.export_dump.no_dependency=你的 Java 不包含用於建立遊戲執行堆疊的相依元件。請前往 Discord 或 HMCL QQ 群尋求幫助。\n\nmain_page=首頁\n\nmessage.cancelled=操作被取消\nmessage.confirm=提示\nmessage.copied=已複製到剪貼簿\nmessage.default=預設\nmessage.doing=請耐心等待\nmessage.downloading=正在下載……\nmessage.error=錯誤\nmessage.failed=操作失敗\nmessage.info=提示\nmessage.success=完成\nmessage.unknown=未知\nmessage.warning=警告\nmessage.question=確認\n\nmodpack=模組包\nmodpack.choose=選取要安裝的遊戲模組包檔案\nmodpack.choose.local=匯入本機模組包檔案\nmodpack.choose.local.detail=你可以直接將模組包檔案拖入本頁面以安裝\nmodpack.choose.remote=從網路下載模組包\nmodpack.choose.remote.detail=需要提供模組包的下載連結\nmodpack.choose.repository=從 CurseForge/Modrinth 下載模組包\nmodpack.choose.repository.detail=可直接在跳轉到的頁面安裝模組包\nmodpack.choose.remote.tooltip=要下載的模組包的連結\nmodpack.completion=下載模組包相關檔案\nmodpack.desc=描述你要製作的模組包，比如模組包注意事項和更新紀錄。支援 Markdown (圖片請上傳至網路)。\nmodpack.description=模組包描述\nmodpack.download=下載模組包\nmodpack.download.title=模組包下載 - %1s\nmodpack.enter_name=給遊戲取個你喜歡的名稱\nmodpack.export=匯出模組包\nmodpack.export.as=請選取模組包類型\nmodpack.file_api=模組包下載連結前綴\nmodpack.files.blueprints=BuildCraft 藍圖\nmodpack.files.config=模組設定檔案\nmodpack.files.dumps=NEI 除錯輸出檔案\nmodpack.files.hmclversion_cfg=啟動器設定檔案\nmodpack.files.liteconfig=LiteLoader 相關檔案\nmodpack.files.mods=模組\nmodpack.files.mods.voxelmods=VoxelMods 設定，如小地圖\nmodpack.files.options_txt=遊戲設定\nmodpack.files.optionsshaders_txt=光影設定\nmodpack.files.resourcepacks=資源包 (紋理包)\nmodpack.files.saves=遊戲世界\nmodpack.files.scripts=MineTweaker 設定\nmodpack.files.servers_dat=多人遊戲伺服器清單\nmodpack.installing=安裝模組包\nmodpack.installing.given=安裝 %s 模組包\nmodpack.introduction=支援 Curse、Modrinth、MultiMC、MCBBS 模組包。\nmodpack.invalid=無效的模組包升級檔案。可能是下載時出現問題。\nmodpack.mismatched_type=模組包類型不符。目前遊戲是「%s」模組包，但是提供的模組包更新檔案是「%s」模組包。\nmodpack.name=模組包名稱\nmodpack.not_a_valid_name=模組包名稱無效\nmodpack.origin=來源\nmodpack.origin.url=官方網站\nmodpack.origin.mcbbs=MCBBS\nmodpack.origin.mcbbs.prompt=帖子 ID\nmodpack.scan=解析模組包\nmodpack.task.install=匯入模組包\nmodpack.task.install.error=無法識別該模組包，目前僅支援匯入 Curse、Modrinth、MultiMC 和 MCBBS 模組包。\nmodpack.type.curse=Curse\nmodpack.type.curse.error=無法完成該模組包所需的相依元件下載，請多次重試或設定代理……\nmodpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載。請嘗試該模組包的最新版本或者安裝其他模組包。\nmodpack.type.manual.warning=該模組包由發佈者手動打包，其中可能已經包含啟動器。建議嘗試解壓後使用其內建的啟動器執行遊戲。\\nHMCL 可以嘗試匯入該模組包，但不保證可用性。是否繼續？\nmodpack.type.mcbbs=MCBBS 模組包\nmodpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher 匯入\nmodpack.type.modrinth=Modrinth\nmodpack.type.modrinth.export=可以被主流第三方啟動器匯入\nmodpack.type.multimc=MultiMC\nmodpack.type.multimc.export=可以被 Hello Minecraft! Launcher 和 MultiMC 匯入\nmodpack.type.server=伺服器自動更新模組包\nmodpack.type.server.export=允許伺服器管理員遠端更新遊戲用戶端\nmodpack.type.server.malformed=伺服器模組包配置格式錯誤，請聯絡伺服器管理員解決此問題\nmodpack.unsupported=Hello Minecraft! Launcher 不支援該模組包格式\nmodpack.update=正在升級模組包\nmodpack.wizard=匯出模組包引導\nmodpack.wizard.step.1=基本設定\nmodpack.wizard.step.1.title=設定模組包的主要訊息\nmodpack.wizard.step.2=選取檔案\nmodpack.wizard.step.2.title=選中你想加到模組包中的檔案或目錄\nmodpack.wizard.step.3=模組包類型\nmodpack.wizard.step.3.title=選取模組包匯出類型\nmodpack.wizard.step.initialization.exported_version=要匯出的遊戲實例\nmodpack.wizard.step.initialization.force_update=強制升級模組包至最新版本 (需要自建伺服器)\nmodpack.wizard.step.initialization.include_launcher=包含啟動器\nmodpack.wizard.step.initialization.modrinth.info=在模組包建立過程中，啟動器將匹配 CurseForge/Modrinth 遠端資源替代本機檔案 (包括模組、資源包和光影包) 以縮減模組包大小，並將副檔名為「.disabled」的檔案標註為「安裝時可選項」。\nmodpack.wizard.step.initialization.no_create_remote_files=不匹配遠端檔案\nmodpack.wizard.step.initialization.save=選取要匯出到的遊戲模組包位置\nmodpack.wizard.step.initialization.skip_curseforge_remote_files=不匹配 CurseForge 遠端資源\nmodpack.wizard.step.initialization.warning=在製作模組包前，請你確認你選取的實例可以正常啟動，\\n並保證你的 Minecraft 是正式版而非快照，\\n而且不應將不允許非官方途徑傳播的模組、資源 (紋理) 包等納入模組包。\\n模組包會儲存你目前的下載來源設定。\nmodpack.wizard.step.initialization.server=點選此處查看有關伺服器自動更新模組包的製作教學\n\nmodrinth.category.adventure=冒險\nmodrinth.category.atmosphere=氛圍\nmodrinth.category.audio=聲音\nmodrinth.category.babric=Babric\nmodrinth.category.blocks=方塊\nmodrinth.category.bloom=泛光\nmodrinth.category.bta-babric=BTA (Babric)\nmodrinth.category.bukkit=Bukkit\nmodrinth.category.bungeecord=BungeeCord\nmodrinth.category.canvas=Canvas\nmodrinth.category.cartoon=卡通\nmodrinth.category.challenging=高難度\nmodrinth.category.colored-lighting=彩色光照\nmodrinth.category.combat=戰鬥\nmodrinth.category.core-shaders=核心著色器\nmodrinth.category.cursed=Cursed\nmodrinth.category.datapack=資料包\nmodrinth.category.decoration=裝飾\nmodrinth.category.economy=經濟\nmodrinth.category.entities=實體\nmodrinth.category.environment=環境\nmodrinth.category.equipment=裝備\nmodrinth.category.fabric=Fabric\nmodrinth.category.fantasy=幻想\nmodrinth.category.folia=Folia\nmodrinth.category.foliage=植被\nmodrinth.category.fonts=字體\nmodrinth.category.food=食物\nmodrinth.category.forge=Forge\nmodrinth.category.game-mechanics=遊戲機制\nmodrinth.category.gui=GUI\nmodrinth.category.high=高\nmodrinth.category.iris=Iris\nmodrinth.category.items=物品\nmodrinth.category.java-agent=Java Agent\nmodrinth.category.kitchen-sink=大雜燴\nmodrinth.category.legacy-fabric=Legacy Fabric\nmodrinth.category.library=支援庫\nmodrinth.category.lightweight=輕量\nmodrinth.category.liteloader=LiteLoader\nmodrinth.category.locale=在地化\nmodrinth.category.low=低\nmodrinth.category.magic=魔法\nmodrinth.category.management=管理\nmodrinth.category.medium=中\nmodrinth.category.minecraft=Minecraft\nmodrinth.category.minigame=小遊戲\nmodrinth.category.misc=其他\nmodrinth.category.mobs=生物\nmodrinth.category.modded=Modded\nmodrinth.category.models=模型\nmodrinth.category.modloader=ModLoader\nmodrinth.category.multiplayer=多人\nmodrinth.category.neoforge=NeoForge\nmodrinth.category.nilloader=NilLoader\nmodrinth.category.optifine=OptiFine\nmodrinth.category.optimization=最佳化\nmodrinth.category.ornithe=Ornithe\nmodrinth.category.paper=Paper\nmodrinth.category.path-tracing=路徑追蹤\nmodrinth.category.pbr=PBR\nmodrinth.category.potato=極低\nmodrinth.category.purpur=Purpur\nmodrinth.category.quests=任務\nmodrinth.category.quilt=Quilt\nmodrinth.category.realistic=寫實\nmodrinth.category.reflections=反射\nmodrinth.category.rift=Rift\nmodrinth.category.screenshot=極高\nmodrinth.category.semi-realistic=半寫實\nmodrinth.category.shadows=陰影\nmodrinth.category.simplistic=簡單\nmodrinth.category.social=社交\nmodrinth.category.spigot=Spigot\nmodrinth.category.sponge=Sponge\nmodrinth.category.storage=儲存\nmodrinth.category.technology=科技\nmodrinth.category.themed=主題\nmodrinth.category.transportation=運輸\nmodrinth.category.tweaks=最佳化\nmodrinth.category.utility=實用\nmodrinth.category.vanilla=原生\nmodrinth.category.vanilla-like=類原生\nmodrinth.category.velocity=Velocity\nmodrinth.category.waterfall=Waterfall\nmodrinth.category.worldgen=世界生成\n\nmods=模組\nmods.add=新增模組\nmods.add.failed=新增模組「%s」失敗。\nmods.add.success=成功新增模組「%s」。\nmods.add.title=選取要新增的模組檔案\nmods.broken_dependency.title=損壞的相依模組\nmods.broken_dependency.desc=該相依模組曾經存在於模組儲存庫中，但現在已被刪除，請嘗試其他下載源。\nmods.category=類別\nmods.channel.alpha=Alpha\nmods.channel.beta=Beta\nmods.channel.release=Release\nmods.check_updates=模組更新檢查\nmods.check_updates.button=檢查更新\nmods.check_updates.confirm=更新\nmods.check_updates.current_version=目前版本\nmods.check_updates.empty=沒有需要更新的模組\nmods.check_updates.failed_check=檢查更新失敗\nmods.check_updates.failed_download=部分檔案下載失敗\nmods.check_updates.file=檔案\nmods.check_updates.source=來源\nmods.check_updates.target_version=目標版本\nmods.curseforge=CurseForge\nmods.dependency.embedded=內建相依模組 (作者已經打包在模組檔中，無需單獨下載)\nmods.dependency.optional=可選相依模組 (如果不安裝，遊戲可以執行，但模組功能可能缺失)\nmods.dependency.required=必需相依模組 (必須單獨下載，缺少可能會導致遊戲無法啟動)\nmods.dependency.tool=相依庫 (必須單獨下載，缺少可能會導致遊戲無法啟動)\nmods.dependency.include=內建相依模組 (作者已經打包在模組檔中，無需單獨下載)\nmods.dependency.incompatible=不相容模組 (同時安裝此模組和正在下載的模組會導致遊戲無法啟動)\nmods.dependency.broken=損壞的相依模組 (該模組曾經存在於模組儲存庫中，但現在已被刪除，請嘗試其他下載源)\nmods.disable=停用\nmods.download=模組下載\nmods.download.title=模組下載 - %1s\nmods.download.recommend=推薦版本 - Minecraft %1s\nmods.enable=啟用\nmods.game.version=遊戲版本\nmods.manage=模組管理\nmods.mcbbs=MCBBS\nmods.mcmod=MC 百科\nmods.mcmod.page=MC 百科頁面\nmods.mcmod.search=MC 百科檢索\nmods.modrinth=Modrinth\nmods.name=名稱\nmods.not_modded=你需要先在「自動安裝」頁面安裝 Forge、NeoForge、Fabric、Legacy Fabric、Quilt 或 LiteLoader 才能管理模組。\nmods.restore=回退\nmods.url=官方頁面\nmods.update_modpack_mod.warning=更新模組包中的模組可能導致模組包損壞，使模組包無法正常啟動。該操作不可逆，確定要更新嗎？\nmods.warning.loader_mismatch=模組載入器不匹配\nmods.install=安裝到目前實例\nmods.save_as=下載到本機目錄\nmods.unknown=未知模組\n\nmenu.undo=還原\nmenu.redo=重做\nmenu.cut=剪下\nmenu.copy=複製\nmenu.paste=貼上\nmenu.deleteselection=刪除\nmenu.selectall=全選\n\nnbt.entries=%s 個條目\nnbt.open.failed=開啟檔案失敗\nnbt.save.failed=儲存檔案失敗\nnbt.title=查看檔案 - %s\n\ndatapack=資料包\ndatapack.add=新增資料包\ndatapack.add.title=選取要新增的資料包壓縮檔\ndatapack.reload.toast=Minecraft 正在執行，請使用 /reload 指令重新載入資料包\ndatapack.title=世界 [%s] - 資料包\n\nweb.failed=載入頁面失敗\nweb.open_in_browser=是否要在瀏覽器中開啟此連結：\\n%s\nweb.view_in_browser=在瀏覽器中查看完整日誌\n\nworld=世界\nworld.add=新增世界\nworld.add.already_exists=此世界已經存在\nworld.add.failed=無法新增此世界: %s\nworld.add.invalid=無法識別的世界壓縮檔\nworld.add.title=選取要新增的世界壓縮檔\nworld.backup=世界備份\nworld.backup.create.new_one=建立新備份\nworld.backup.create.failed=建立備份失敗。\\n%s\nworld.backup.create.success=成功建立新備份：%s\nworld.backup.delete=刪除此備份\nworld.backup.processing=正在備份中……\nworld.chunkbase=世界地圖\nworld.chunkbase.end_city=終界城地圖\nworld.chunkbase.seed_map=種子地圖\nworld.chunkbase.stronghold=要塞地圖\nworld.chunkbase.nether_fortress=地獄要塞地圖\nworld.duplicate=複製此世界\nworld.duplicate.prompt=輸入複製後的世界名稱\nworld.duplicate.failed.already_exists=目錄已存在\nworld.duplicate.failed.empty_name=名稱不能為空\nworld.duplicate.failed.invalid_name=名稱中包含無效字元\nworld.duplicate.failed=複製世界失敗\nworld.duplicate.success.toast=複製世界成功\nworld.datapack=資料包管理\nworld.datetime=上一次遊戲時間: %s\nworld.delete=刪除此世界\nworld.delete.failed=刪除世界失敗。\\n%s\nworld.download=下載世界\nworld.download.title=世界下載 - %1s\nworld.export=匯出此世界\nworld.export.title=選取該世界的儲存位置\nworld.export.location=儲存到\nworld.export.wizard=匯出世界「%s」\nworld.icon=世界圖示\nworld.icon.change=修改世界圖示\nworld.icon.change.fail.load.title=圖片解析失敗\nworld.icon.change.fail.load.text=該圖片似乎已損毀，HMCL 無法解析它\nworld.icon.change.fail.not_64x64.title=圖片大小錯誤\nworld.icon.change.fail.not_64x64.text=該圖片的解析度為 %d×%d，而不是 64×64，請提供一張 64×64 解析度的圖片再次嘗試\nworld.icon.change.succeed.toast=世界圖示修改成功\nworld.icon.change.tip=請提供一張 64×64 PNG 格式的圖片。錯誤解析度的圖片將無法被 Minecraft 解析。\nworld.icon.choose.title=選擇世界圖示\nworld.info=世界資訊\nworld.info.basic=基本資訊\nworld.info.allow_cheats=允許指令(作弊)\nworld.info.dimension.the_nether=地獄\nworld.info.dimension.the_end=末地\nworld.info.difficulty=難易度\nworld.info.difficulty.peaceful=和平\nworld.info.difficulty.easy=簡單\nworld.info.difficulty.normal=普通\nworld.info.difficulty.hard=困難\nworld.info.difficulty_lock=鎖定難易度\nworld.info.failed=讀取世界資訊失敗\nworld.info.game_version=遊戲版本\nworld.info.last_played=上一次遊戲時間\nworld.info.generate_features=生成建築\nworld.info.player=玩家資訊\nworld.info.player.food_level=饑餓值\nworld.info.player.food_saturation_level=飽食度\nworld.info.player.game_type=遊戲模式\nworld.info.player.game_type.adventure=冒險\nworld.info.player.game_type.creative=創造\nworld.info.player.game_type.hardcore=極限\nworld.info.player.game_type.spectator=旁觀者\nworld.info.player.game_type.survival=生存\nworld.info.player.health=生命值\nworld.info.player.last_death_location=上次死亡位置\nworld.info.player.location=位置\nworld.info.player.spawn=床/重生錨位置\nworld.info.player.xp_level=經驗等級\nworld.info.random_seed=種子碼\nworld.info.spawn=世界重生點\nworld.info.time=遊戲時間\nworld.info.time.format=%d 天 %d 小時 %d 分鐘\nworld.load.fail=世界載入失敗\nworld.locked=使用中\nworld.locked.failed=該世界正在使用中，請關閉遊戲後重試。\nworld.game_version=遊戲版本\nworld.manage=世界管理\nworld.manage.button=世界管理\nworld.manage.title=世界管理 - %s\nworld.name=世界名稱\nworld.name.enter=輸入世界名稱\nworld.show_all=全部顯示\n\nprofile=遊戲目錄\nprofile.already_exists=該名稱已存在\nprofile.default=目前目錄\nprofile.home=官方啟動器目錄\nprofile.instance_directory=遊戲目錄路徑\nprofile.instance_directory.choose=選取遊戲目錄路徑\nprofile.manage=遊戲目錄清單\nprofile.name=名稱\nprofile.new=建立新目錄\nprofile.title=遊戲目錄\nprofile.selected=已選取\nprofile.use_relative_path=如可行，則對於遊戲目錄使用相對路徑\n\nrepositories.custom=自訂 Maven 儲存庫 (%s)\nrepositories.maven_central=全球 (Maven Central)\nrepositories.tencentcloud_mirror=中國大陸 (騰訊雲 Maven 儲存庫)\nrepositories.chooser=缺少 JavaFX 執行環境。HMCL 需要 JavaFX 才能正常執行。\\n點擊「確認」從指定下載源下載 JavaFX 執行時元件並開啟 HMCL。點擊「取消」退出程式。\\n選取下載源：\nrepositories.chooser.title=選取 JavaFX 下載源\n\nresourcepack=資源包\nresourcepack.add=新增資源包\nresourcepack.add.title=選取要新增的資源包壓縮檔\nresourcepack.manage=資源包管理\nresourcepack.download=下載資源包\nresourcepack.add.failed=新增資源包失敗\nresourcepack.delete.failed=刪除資源包失敗\nresourcepack.download.title=資源包下載 - %1s\n\nreveal.in_file_manager=在檔案管理員中查看\n\nschematics=原理圖\nschematics.add=新增原理圖\nschematics.add.failed=新增原理圖失敗\nschematics.add.title=選取要新增的原理圖檔案\nschematics.back_to=返回到「%s」\nschematics.create_directory=建立目錄\nschematics.create_directory.prompt=請輸入新目錄名稱\nschematics.create_directory.failed=建立目錄失敗\nschematics.create_directory.failed.already_exists=目錄已存在\nschematics.create_directory.failed.empty_name=名稱不能為空\nschematics.create_directory.failed.invalid_name=名稱中包含無效字元\nschematics.info.description=描述\nschematics.info.enclosing_size=框大小\nschematics.info.name=名稱\nschematics.info.region_count=區域數量\nschematics.info.schematic_author=作者\nschematics.info.time_created=建立時間\nschematics.info.time_modified=修改時間\nschematics.info.total_blocks=總方塊數\nschematics.info.total_volume=總體積\nschematics.info.version=原理圖版本\nschematics.manage=原理圖管理\nschematics.sub_items=%d 個子項\n\nsearch=搜尋\nsearch.hint.chinese=支援中英文搜尋\nsearch.hint.english=僅支援英文搜尋\nsearch.enter=請在此處輸入\nsearch.sort=排序\nsearch.first_page=第一頁\nsearch.previous_page=上一頁\nsearch.next_page=下一頁\nsearch.last_page=最後一頁\nsearch.page_n=%d / %s\n\nselector.choose=選取\nselector.choose_file=選取檔案\nselector.custom=自訂\n\nsettings=設定\n\nsettings.advanced=進階設定\nsettings.advanced.modify=編輯進階設定\nsettings.advanced.title=進階設定 - %s\nsettings.advanced.custom_commands=自訂指令\nsettings.advanced.custom_commands.hint=自訂指令被呼叫時將包含如下的環境變數：\\n\\\n  \\  · $INST_NAME: 實例名稱；\\n\\\n  \\  · $INST_ID: 實例名稱；\\n\\\n  \\  · $INST_DIR: 目前實例執行路徑；\\n\\\n  \\  · $INST_MC_DIR: 目前遊戲目錄路徑；\\n\\\n  \\  · $INST_JAVA: 遊戲執行使用的 Java 路徑；\\n\\\n  \\  · $INST_FORGE: 若安裝了 Forge，將會存在本環境變數；\\n\\\n  \\  · $INST_NEOFORGE: 若安裝了 NeoForge，將會存在本環境變數；\\n\\\n  \\  · $INST_CLEANROOM: 若安裝了 Cleanroom，將會存在本環境變數；\\n\\\n  \\  · $INST_LITELOADER: 若安裝了 LiteLoader，將會存在本環境變數；\\n\\\n  \\  · $INST_OPTIFINE: 若安裝了 OptiFine，將會存在本環境變數；\\n\\\n  \\  · $INST_FABRIC: 若安裝了 Fabric，將會存在本環境變數；\\n\\\n  \\  · $INST_LEGACYFABRIC: 若安裝了 Legacy Fabric，將會存在本環境變數；\\n\\\n  \\  · $INST_QUILT: 若安裝了 Quilt，將會存在本環境變數。\nsettings.advanced.dont_check_game_completeness=不檢查遊戲完整性\nsettings.advanced.dont_check_jvm_validity=不檢查 Java 虛擬機與遊戲的相容性\nsettings.advanced.dont_patch_natives=不嘗試自動取代本機庫\nsettings.advanced.environment_variables=環境變數\nsettings.advanced.game_dir.default=預設 (\".minecraft/\")\nsettings.advanced.game_dir.independent=各實例獨立 (\".minecraft/versions/<實例名>/\"，除 assets、libraries 外)\nsettings.advanced.java_permanent_generation_space=記憶體永久儲存區域\nsettings.advanced.java_permanent_generation_space.prompt=單位 MiB\nsettings.advanced.jvm=Java 虛擬機設定\nsettings.advanced.jvm_args=Java 虛擬機參數\nsettings.advanced.jvm_args.prompt=\\  · 若在「Java 虛擬機參數」中輸入的參數與預設參數相同，則不會新增；\\n\\\n  \\  · 若在「Java 虛擬機參數」輸入任何 GC 參數，預設參數的 G1 參數會被停用；\\n\\\n  \\  · 啟用下方「不新增預設的 Java 虛擬機參數」可在啟動遊戲時不新增預設參數。\nsettings.advanced.launcher_visibility.close=遊戲啟動後結束啟動器\nsettings.advanced.launcher_visibility.hide=遊戲啟動後隱藏啟動器\nsettings.advanced.launcher_visibility.hide_and_reopen=隱藏啟動器並在遊戲結束後重新開啟\nsettings.advanced.launcher_visibility.keep=不隱藏啟動器\nsettings.advanced.launcher_visible=啟動器可見性\nsettings.advanced.minecraft_arguments=Minecraft 額外參數\nsettings.advanced.minecraft_arguments.prompt=預設\nsettings.advanced.natives_directory=本機庫路徑 (LWJGL)\nsettings.advanced.natives_directory.choose=選取本機庫路徑\nsettings.advanced.natives_directory.custom=自訂 (由你提供遊戲需要的本機庫)\nsettings.advanced.natives_directory.default=預設 (由啟動器提供遊戲本機庫)\nsettings.advanced.natives_directory.default.version_id=<實例名稱>\nsettings.advanced.natives_directory.hint=本選項提供給 Apple Silicon 等未受遊戲官方支援的平台來自訂遊戲本機庫。如果你不知道本選項的含義，請你不要修改本選項，否則會導致遊戲無法啟動。\\n\\n如果你要修改本選項，你需要保證自訂目錄下有遊戲所需的本機庫檔案，如 lwjgl.dll (liblwjgl.so), openal.dll (libopenal.so) 等檔案。啟動器不會幫你補全缺少的本機庫檔案。\\n\\n注意：建議指定的本機庫檔案路徑使用全英文字元，否則可能導致遊戲啟動失敗。\nsettings.advanced.no_jvm_args=不新增預設的 Java 虛擬機參數\nsettings.advanced.no_optimizing_jvm_args=不自動新增 Java 虛擬機最佳化參數\nsettings.advanced.precall_command=遊戲啟動前執行指令\nsettings.advanced.precall_command.prompt=將在遊戲啟動前呼叫使用\nsettings.advanced.process_priority=處理程式優先度\nsettings.advanced.process_priority.low=低\nsettings.advanced.process_priority.low.desc=節省遊戲占用資源，可能會造成遊戲卡頓\nsettings.advanced.process_priority.below_normal=較低\nsettings.advanced.process_priority.below_normal.desc=節省遊戲占用資源，可能會造成遊戲卡頓\nsettings.advanced.process_priority.normal=中\nsettings.advanced.process_priority.normal.desc=平衡\nsettings.advanced.process_priority.above_normal=較高\nsettings.advanced.process_priority.above_normal.desc=優先保證遊戲執行，但可能會導致其他程式卡頓\nsettings.advanced.process_priority.high=高\nsettings.advanced.process_priority.high.desc=優先保證遊戲執行，但可能會導致其他程式卡頓\nsettings.advanced.post_exit_command=遊戲結束後執行指令\nsettings.advanced.post_exit_command.prompt=將在遊戲結束後呼叫使用\nsettings.advanced.renderer=繪製器\nsettings.advanced.renderer.default=預設\nsettings.advanced.renderer.default.desc=OpenGL\nsettings.advanced.renderer.d3d12=Mesa D3D12\nsettings.advanced.renderer.d3d12.desc=DirectX 12 (效能與相容性較差，用於除錯)\nsettings.advanced.renderer.llvmpipe=Mesa LLVMpipe\nsettings.advanced.renderer.llvmpipe.desc=軟繪製器 (效能較差，相容性最好)\nsettings.advanced.renderer.zink=Mesa Zink\nsettings.advanced.renderer.zink.desc=Vulkan (效能最好，相容性較差)\nsettings.advanced.server_ip=伺服器位址\nsettings.advanced.server_ip.prompt=預設，啟動遊戲後直接進入對應伺服器\nsettings.advanced.unsupported_system_options=不適用於目前系統的選項\nsettings.advanced.use_native_glfw=[僅限 Linux/FreeBSD] 使用系統 GLFW\nsettings.advanced.use_native_openal=[僅限 Linux/FreeBSD] 使用系統 OpenAL\nsettings.advanced.workaround=除錯選項\nsettings.advanced.workaround.warning=除錯選項僅提供給專業玩家使用。修改除錯選項可能會導致遊戲無法啟動。除非你知道你在做什麼，否則請不要修改這些選項。\nsettings.advanced.wrapper_launcher=前置指令\nsettings.advanced.wrapper_launcher.prompt=如填寫「optirun」後，啟動指令將從「java ...」變為「optirun java ...」\n\nsettings.custom=自訂\n\nsettings.game=遊戲設定\nsettings.game.copy_global=複製全域遊戲設定\nsettings.game.copy_global.copy_all=複製全部\nsettings.game.copy_global.copy_all.confirm=你確定要覆寫目前實例特定遊戲設定嗎？該操作無法復原！\nsettings.game.current=遊戲\nsettings.game.dimension=遊戲介面解析度大小\nsettings.game.exploration=瀏覽\nsettings.game.fullscreen=全螢幕\nsettings.game.java_directory=遊戲 Java\nsettings.game.java_directory.auto=自動選取合適的 Java\nsettings.game.java_directory.auto.not_found=沒有合適的 Java\nsettings.game.java_directory.bit=%s 位\nsettings.game.java_directory.choose=選取 Java\nsettings.game.java_directory.invalid=Java 路徑不正確\nsettings.game.java_directory.version=指定 Java 版本\nsettings.game.java_directory.template=%s (%s)\nsettings.game.management=管理\nsettings.game.working_directory=執行路徑 (建議使用模組時選取「各實例獨立」。修改後請自行移動相關遊戲檔案，如世界、模組設定等)\nsettings.game.working_directory.choose=選取執行目錄\nsettings.game.working_directory.hint=在「執行路徑」選項中選取「各實例獨立」使目前實例獨立存放設定、世界、模組等資料。使用模組時建議開啟此選項以避免不同實例模組衝突。修改此選項後需自行移動世界等檔案。\n\nsettings.icon=遊戲圖示\n\nsettings.launcher=啟動器設定\nsettings.launcher.appearance=外觀\nsettings.launcher.brightness=主題模式\nsettings.launcher.brightness.auto=跟隨系統設定\nsettings.launcher.brightness.dark=深色模式\nsettings.launcher.brightness.light=淺色模式\nsettings.launcher.common_path.tooltip=啟動器將所有遊戲資源及相依元件庫檔案放於此集中管理。如果遊戲目錄內有現成的將不會使用公共庫檔案。\nsettings.launcher.debug=除錯\nsettings.launcher.disable_april_fools=不啟用愚人節功能\nsettings.launcher.disable_auto_game_options=不自動切換遊戲語言\nsettings.launcher.download=下載\nsettings.launcher.download.threads=執行緒數\nsettings.launcher.download.threads.auto=自動選取執行緒數\nsettings.launcher.download.threads.hint=執行緒數過高可能導致系統卡頓。你的下載速度會受到網際網路運營商、下載來源伺服器等方面的影響。調高下載執行緒數不一定能大幅提升總下載速度。\nsettings.launcher.download_source=下載來源\nsettings.launcher.download_source.auto=自動選取下載來源\nsettings.launcher.enable_game_list=在首頁內顯示遊戲清單\nsettings.launcher.font=字體\nsettings.launcher.font.anti_aliasing=反鋸齒\nsettings.launcher.font.anti_aliasing.auto=自動\nsettings.launcher.font.anti_aliasing.gray=灰階\nsettings.launcher.font.anti_aliasing.lcd=子像素\nsettings.launcher.general=一般\nsettings.launcher.language=語言\nsettings.launcher.launcher_log.export=匯出啟動器日誌\nsettings.launcher.launcher_log.export.failed=無法匯出日誌。\nsettings.launcher.launcher_log.export.success=日誌已儲存到「%s」。\nsettings.launcher.launcher_log.reveal=開啟日誌目錄\nsettings.launcher.log=日誌\nsettings.launcher.log.font=日誌字體\nsettings.launcher.proxy=代理\nsettings.launcher.proxy.authentication=身份驗證\nsettings.launcher.proxy.default=使用系統代理\nsettings.launcher.proxy.host=IP 位址\nsettings.launcher.proxy.http=HTTP\nsettings.launcher.proxy.none=不使用代理\nsettings.launcher.proxy.password=密碼\nsettings.launcher.proxy.port=連線埠\nsettings.launcher.proxy.socks=SOCKS\nsettings.launcher.proxy.username=帳戶\nsettings.launcher.theme=主題色\nsettings.launcher.title_transparent=標題欄透明\nsettings.launcher.turn_off_animations=關閉動畫\nsettings.launcher.version_list_source=版本清單來源\nsettings.launcher.background.settings.opacity=不透明度\n\nsettings.memory=遊戲記憶體\nsettings.memory.allocate.auto=最低分配 %1$.1f GiB / 實際分配 %2$.1f GiB\nsettings.memory.allocate.auto.exceeded=最低分配 %1$.1f GiB / 實際分配 %2$.1f GiB (%3$.1f GiB 可用)\nsettings.memory.allocate.manual=遊戲分配 %1$.1f GiB\nsettings.memory.allocate.manual.exceeded=遊戲分配 %1$.1f GiB (%3$.1f GiB 可用)\nsettings.memory.auto_allocate=自動分配\nsettings.memory.lower_bound=最低分配\nsettings.memory.unit.mib=MiB\nsettings.memory.used_per_total=已使用 %1$.1f GiB / 總記憶體 %2$.1f GiB\nsettings.physical_memory=實體記憶體大小\nsettings.show_log=查看日誌\nsettings.enable_debug_log_output=輸出除錯日誌\nsettings.tabs.installers=自動安裝\nsettings.take_effect_after_restart=重啟後生效\nsettings.type=實例遊戲設定類型\nsettings.type.global=全域遊戲設定 (未啟用「實例特定遊戲設定」的實例共用一套設定)\nsettings.type.global.manage=全域遊戲設定\nsettings.type.global.edit=編輯全域遊戲設定\nsettings.type.special.enable=啟用實例特定遊戲設定 (不影響其他實例)\nsettings.type.special.edit=編輯實例特定遊戲設定\nsettings.type.special.edit.hint=目前實例「%s」啟用了「實例特定遊戲設定」，本頁面選項不對目前實例生效。點擊連結以修改目前實例設定。\n\nshaderpack.download.title=光影下載 - %1s\n\nsponsor=贊助\nsponsor.bmclapi=中國大陸下載源由 BMCLAPI 提供高速下載服務。點選此處查閱詳細訊息。\nsponsor.hmcl=Hello Minecraft! Launcher 是一個免費、自由、開源的 Minecraft 啟動器，允許玩家方便快捷地安裝、管理、執行遊戲。點選此處查閱詳細訊息。\n\nsystem.architecture=架構\nsystem.operating_system=作業系統\n\nterracotta=多人遊戲\nterracotta.terracotta=Terracotta | 陶瓦聯機\nterracotta.status=聯機大廳\nterracotta.back=退出\nterracotta.feedback.title=填寫回饋表\nterracotta.feedback.desc=在 HMCL 更新聯機核心時，我們歡迎您用 10 秒時間填寫聯機品質回饋收集表。\nterracotta.sudo_installing=HMCL 需要驗證您的密碼才能安裝線上核心\nterracotta.difficulty.easiest=目前網路狀況極佳：稍等一下就成功！\nterracotta.difficulty.simple=目前網路狀況較好：建立連線需要一段時間…\nterracotta.difficulty.medium=目前網路狀態中等：已啟用抗干擾備用線路，連線可能失敗\nterracotta.difficulty.tough=目前網路狀態極差：已啟用抗干擾備用線路，連線可能失敗\nterracotta.difficulty.estimate_only=連線成功率由房主和房客的 NAT 類型推算得到，僅供參考。\nterracotta.from_local.title=線上核心第三方下載管道\nterracotta.from_local.desc=在部分地區，內建的預設下載管道可能不穩定或連線緩慢\nterracotta.from_local.guide=您應下載名為 %s 的線上核心套件。下載完成後，請將檔案拖曳到目前介面來安裝。\nterracotta.from_local.file_name_mismatch=您應該下載名為 %1$s 的線上核心包，而非 %2$s\nterracotta.export_log=匯出線上核心日誌\nterracotta.export_log.desc=為分析錯誤提供更多資訊\nterracotta.status.bootstrap=正在收集資訊\nterracotta.status.uninitialized.not_exist=未下載聯機核心\nterracotta.status.uninitialized.not_exist.title=下載聯機核心 (約 8MiB)\nterracotta.status.uninitialized.update=需更新聯機核心\nterracotta.status.uninitialized.update.title=更新聯機核心 (約 8MiB)\nterracotta.status.uninitialized.desc=您承諾，在多人聯機全過程中，您將嚴格遵守您所在國家或地區的全部法律法規\nterracotta.confirm.title=使用者須知\nterracotta.confirm.desc=陶瓦聯機是第三方開源自由軟體，您在使用過程中所遇到的問題請透過相關管道進行回饋。\\n\\\n  陶瓦聯機使用 P2P 技術，連線成功後房間內使用者之間將直接連接，不會使用第三方伺服器對您的流量進行轉發。最終聯機體驗和參與連線者的網路情況有較大關係。\\n\\\n  在多人聯機全過程中，您必須嚴格遵守您所在國家與地區的全部法律法規。\nterracotta.status.preparing=正在下載聯機核心 (請勿退出啟動器)\nterracotta.status.launching=正在初始化聯機核心\nterracotta.status.unknown=正在初始化聯機核心\nterracotta.status.waiting=聯機核心已就緒\nterracotta.status.waiting.host.title=我想當房主\nterracotta.status.waiting.host.desc=建立房間並產生邀請碼，與好友一起暢玩\nterracotta.status.waiting.host.launch.title=您似乎忘記啟動遊戲了\nterracotta.status.waiting.host.launch.desc=未能找到正在執行的遊戲\nterracotta.status.waiting.host.launch.skip=遊戲已啟動\nterracotta.status.waiting.guest.title=我想當房客\nterracotta.status.waiting.guest.desc=輸入房主提供的邀請碼加入遊戲世界\nterracotta.status.waiting.guest.prompt.title=請輸入房主提供的邀請碼\nterracotta.status.waiting.guest.prompt.invalid=邀請碼錯誤\nterracotta.status.scanning=正在掃描區域網路世界\nterracotta.status.scanning.desc=請<a href=\"hmcl://game/launch\">啟動遊戲</a>，進入單人世界，按 ESC 鍵，選擇「在區網上公開」，點擊「開始區網世界」。\nterracotta.status.scanning.back=這將同時停止掃描區域網路世界。\nterracotta.status.host_starting=正在建立房間\nterracotta.status.host_starting.back=這將會取消建立房間。\nterracotta.status.host_ok=已建立房間\nterracotta.status.host_ok.code=邀請碼 (已自動複製到剪貼簿)\nterracotta.status.host_ok.code.copy=複製邀請碼\nterracotta.status.host_ok.code.copy.toast=已將邀請碼複製到剪貼簿\nterracotta.status.host_ok.code.desc=請提醒您的朋友在 HMCL 或 PCL CE 多人遊戲功能中選擇房客模式，並輸入該邀請碼。\nterracotta.status.host_ok.back=這將同時徹底關閉房間，其他房客將退出並不再能重新加入該房間。\nterracotta.status.guest_starting=正在加入房間\nterracotta.status.guest_starting.back=這不會影響其他房客加入目前房間。\nterracotta.status.guest_ok=已加入房間\nterracotta.status.guest_ok.back=這不會影響其他房客加入目前房間。\nterracotta.status.guest_ok.title=請啟動遊戲，選擇多人遊戲，雙擊進入陶瓦聯機大廳。\nterracotta.status.guest_ok.desc=備用連線位址：%s\nterracotta.status.exception.back=可再試一次\nterracotta.status.exception.desc.ping_host_fail=加入房間失敗：房間已關閉或網路不穩定\nterracotta.status.exception.desc.ping_host_rst=房間連線中斷：房間已關閉或網路不穩定\nterracotta.status.exception.desc.guest_et_crash=加入房間失敗：EasyTier 已崩潰，請向開發者回報該問題\nterracotta.status.exception.desc.host_et_crash=建立房間失敗：EasyTier 已崩潰，請向開發者回報問題\nterracotta.status.exception.desc.ping_server_rst=房間已關閉：您已退出遊戲世界，房間已自動關閉\nterracotta.status.exception.desc.scaffolding_invalid_response=協議錯誤：房主發送了錯誤的回應資料，請向開發者回報該問題\nterracotta.status.fatal.retry=重試\nterracotta.status.fatal.network=未能下載線上核心。請檢查網路連接，然後再試一次\nterracotta.status.fatal.install=嚴重錯誤：無法安裝線上核心\nterracotta.status.fatal.terracotta=嚴重錯誤：無法與線上核心通訊\nterracotta.status.fatal.unknown=嚴重錯誤：原因未知\nterracotta.player_list=玩家清單\nterracotta.player_anonymous=匿名玩家\nterracotta.player_kind.host=房主\nterracotta.player_kind.local=你\nterracotta.player_kind.guest=房客\nterracotta.unsupported=多人聯機功能尚未支援目前平台。\nterracotta.unsupported.os.windows.old=多人聯機功能需要 Windows 10 或更高版本。請更新系統。\nterracotta.unsupported.arch.32bit=多人聯機功能不支援 32 位元系統。請更新至 64 位元系統。\nterracotta.unsupported.arch.loongarch64_ow=多人聯機功能不支援 Linux LoongArch64 舊世界發行版，請更新至新世界發行版 (如 AOSC OC)。\nterracotta.unsupported.region=多人聯機功能目前僅為中國內地使用者提供服務，在您所在地區可能不可用。\n\nunofficial.hint=你正在使用第三方提供的 HMCL。我們無法保證其安全性，請注意甄別。\n\nupdate=啟動器更新\nupdate.accept=更新\nupdate.changelog=更新日誌\nupdate.channel.dev=開發版\nupdate.channel.dev.hint=你正在使用 HMCL 開發版。開發版包含一些未在穩定版中包含的測試性功能，僅用於體驗新功能。開發版功能未受充分驗證，使用起來可能不穩定！\\n\\\n  \\n\\\n  如果你遇到了使用問題，可以透過設定中 <a href=\"hmcl://settings/feedback\">回報頁面</a> 提供的管道進行回報。歡迎關注 Bilibili <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> 以關注 HMCL 的重要動態，或關注 <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> 以瞭解 HMCL 的開發進展。\nupdate.channel.dev.title=開發版提示\nupdate.channel.nightly=預覽版\nupdate.channel.nightly.hint=你正在使用 HMCL 預覽版。預覽版更新較為頻繁，包含一些未在穩定版和開發版中包含的測試性功能，僅用於體驗新功能。預覽版功能未受充分驗證，使用起來可能不穩定！\\n\\\n  \\n\\\n  如果你遇到了使用問題，可以透過設定中 <a href=\"hmcl://settings/feedback\">回報頁面</a> 提供的管道進行回報。歡迎關注 Bilibili <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> 以關注 HMCL 的重要動態，或關注 <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> 以瞭解 HMCL 的開發進展。\nupdate.channel.nightly.title=預覽版提示\nupdate.channel.stable=穩定版\nupdate.checking=正在檢查更新\nupdate.disable_auto_show_update_dialog=不自動顯示更新彈窗\nupdate.disable_auto_show_update_dialog.subtitle=啟用此選項，HMCL 將不會自動彈出更新彈窗。\nupdate.failed=更新失敗\nupdate.found=發現到更新\nupdate.newest_version=最新版本為：%s\nupdate.bubble.title=發現更新：%s\nupdate.bubble.subtitle=點選此處進行升級\nupdate.note=開發版與預覽版包含更多的功能以及錯誤修復，但也可能會包含其他的問題。\nupdate.latest=目前版本為最新版本\nupdate.no_browser=無法開啟瀏覽器。網址已經複製到剪貼簿了，你可以手動複製網址開啟頁面。\nupdate.tooltip=更新\nupdate.preview=提前測試 HMCL 預覽版本\nupdate.preview.subtitle=啟用此選項，你將可以提前取得 HMCL 的新版本，以便在正式發布前進行測試。\n\nversion=遊戲\nversion.name=遊戲實例名稱\nversion.cannot_read=讀取遊戲實例失敗，無法進行自動安裝\nversion.empty=沒有遊戲實例\nversion.empty.add=進入下載頁安裝遊戲\nversion.empty.launch=沒有可啟動的遊戲。\\n你可以前往「下載」頁面下載新遊戲，或在「實例清單」切換遊戲目錄。\nversion.empty.launch.goto_download=前往下載頁面\nversion.empty.hint=沒有已安裝的遊戲。\\n你可以切換其他遊戲目錄，或者點擊此處進入遊戲下載頁面。\nversion.game.all=全部\nversion.game.april_fools=愚人節\nversion.game.old=遠古版\nversion.game.release=正式版\nversion.game.releases=正式版\nversion.game.snapshot=快照\nversion.game.snapshots=快照\nversion.game.support_status.unsupported=不支援\nversion.game.support_status.untested=未經測試\nversion.game.type=版本類型\nversion.launch=啟動遊戲\nversion.launch_and_enter_world=進入世界\nversion.launch.empty=開始遊戲\nversion.launch.empty.installing=安裝遊戲\nversion.launch.empty.tooltip=安裝並啟動最新正式版遊戲\nversion.launch.test=測試遊戲\nversion.switch=切換實例\nversion.launch_script=生成啟動指令碼\nversion.launch_script.failed=生成啟動指令碼失敗\nversion.launch_script.save=儲存啟動指令碼\nversion.launch_script.success=啟動指令碼已生成完畢: %s\nversion.manage=實例清單\nversion.manage.clean=清理遊戲目錄\nversion.manage.clean.tooltip=清理「logs」與「crash-reports」目錄\nversion.manage.duplicate=複製遊戲實例\nversion.manage.duplicate.duplicate_save=複製世界\nversion.manage.duplicate.prompt=請輸入新遊戲實例名稱\nversion.manage.duplicate.confirm=新的遊戲將複製該實例目錄 (\".minecraft/versions/<實例名>\") 下的檔案，並帶有獨立的執行目錄和設定。\nversion.manage.manage=實例管理\nversion.manage.manage.title=實例管理 - %1s\nversion.manage.redownload_assets_index=更新遊戲資源檔案\nversion.manage.remove=刪除該實例\nversion.manage.remove.confirm.trash=真的要刪除實例「%s」嗎? 你可以在系統的資源回收筒 (或垃圾桶) 中還原目錄「%s」來找回該實例。\nversion.manage.remove.confirm.independent=由於該實例啟用了「(全域/實例特定) 遊戲設定 → 執行路徑 → 各實例獨立」設定，刪除該實例將導致該遊戲的世界等資料一同被刪除！真的要刪除實例「%s」嗎?\nversion.manage.remove.failed=刪除實例失敗。可能檔案被占用。\nversion.manage.remove_assets=刪除所有遊戲資源檔案\nversion.manage.remove_libraries=刪除所有支援庫檔案\nversion.manage.rename=重新命名該實例\nversion.manage.rename.message=請輸入新名稱\nversion.manage.rename.fail=重新命名實例失敗，可能檔案被佔用或者名稱有特殊字元。\nversion.search=名稱\nversion.search.prompt=輸入版本名稱進行搜尋\nversion.settings=遊戲設定\nversion.update=更新模組包\n\nwarning.java_interpreted_mode=HMCL 正在執行於直譯器模式的 Java 環境中，效能將會受到很大影響。\\n我們建議你使用支援 JIT 的 Java 開啟 HMCL 以獲得最佳體驗。\nwarning.software_rendering=HMCL 正在使用軟體繪製，效能將會受到很大影響。\n\nwiki.tooltip=Minecraft Wiki 頁面\nwiki.version.game=https://zh.minecraft.wiki/w/Java版%s\nwiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s\n\nwizard.prev=< 上一步\nwizard.failed=失敗\nwizard.finish=完成\nwizard.next=下一步 >\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Contributors: huangyuhui\n\nabout=关于\nabout.copyright=版权\nabout.copyright.statement=版权所有 © 2013-2026 huangyuhui 及贡献者\nabout.author=作者\nabout.author.statement=bilibili @huanghongxun\nabout.claim=用户协议\nabout.claim.statement=点击链接以查看全文\nabout.dependency=依赖\nabout.legal=法律声明\nabout.thanks_to=鸣谢\nabout.thanks_to.bangbang93.statement=提供 BMCLAPI 下载源。请赞助支持 BMCLAPI！\nabout.thanks_to.burningtnt.statement=为 HMCL 贡献许多技术支持\nabout.thanks_to.contributors=所有通过 Issue、Pull Request 等方式参与本项目的贡献者\nabout.thanks_to.contributors.statement=没有开源社区的支持，HMCL 就无法走到今天\nabout.thanks_to.gamerteam.statement=提供默认背景图\nabout.thanks_to.glavo.statement=负责 HMCL 的日常维护\nabout.thanks_to.zekerzhayard.statement=为 HMCL 贡献许多技术支持\nabout.thanks_to.zkitefly.statement=负责维护 HMCL 的文档\nabout.thanks_to.mcbbs=MCBBS (我的世界中文论坛)\nabout.thanks_to.mcbbs.statement=提供 MCBBS 下载源 (现已停止服务)\nabout.thanks_to.mcim=mcmod-info-mirror\nabout.thanks_to.mcim.statement=为中国大陆用户提供模组信息缓存加速服务\nabout.thanks_to.mcmod=MC 百科 (mcmod.cn)\nabout.thanks_to.mcmod.statement=提供模组简体中文名映射表与模组百科\nabout.thanks_to.red_lnn.statement=提供默认背景图\nabout.thanks_to.shulkersakura.statement=提供 HMCL 的徽标\nabout.thanks_to.users=HMCL 用户群成员\nabout.thanks_to.users.statement=感谢用户群成员赞助充电、积极催更、反馈问题、出谋划策\nabout.thanks_to.yushijinhun.statement=提供 authlib-injector 相关支持\nabout.open_source=开源\nabout.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)\n\naccount=账户\naccount.cape=披风\naccount.character=角色\naccount.choose=选择一个角色\naccount.create=添加账户\naccount.create.microsoft=添加微软账户\naccount.create.offline=添加离线模式账户\naccount.create.authlibInjector=添加外置登录账户 (authlib-injector)\naccount.email=邮箱\naccount.empty=没有账户\naccount.failed=账户刷新失败\naccount.failed.character_deleted=此角色已被删除\naccount.failed.connect_authentication_server=无法连接认证服务器。可能是网络问题，请检查设备能否正常上网或使用代理服务。\\n你可以点击右上角帮助按钮进行求助。\naccount.failed.connect_injector_server=无法连接认证服务器。可能是网络问题，请检查设备是否能正常上网、URL 是否输入错误，或使用代理服务。\\n你可以点击右上角帮助按钮进行求助。\naccount.failed.injector_download_failure=无法下载 authlib-injector。可能是网络问题，请检查设备是否能正常上网、尝试切换下载源或使用代理服务。\\n你可以点击右上角帮助按钮进行求助。\naccount.failed.invalid_credentials=你的用户名或密码错误，或登录次数过多被暂时禁止登录。请稍后再试。\naccount.failed.invalid_password=无效的密码\naccount.failed.invalid_token=请尝试登出并重新输入密码登录\naccount.failed.migration=你的账户需要迁移至微软账户。如果你已经迁移，你需要使用迁移后的微软账户登录。\naccount.failed.no_character=该账户没有角色\naccount.failed.server_disconnected=无法访问登录服务器。账户信息刷新失败。\\n\\\n  你可以选择“再次刷新账户”重新尝试。\\n\\\n  你也可以选择“跳过账户刷新”继续启动游戏，但可能会导致账户信息未同步更新。\\n\\\n  若最近没有刷新账户信息，则可能导致账户信息过期失效。\\n\\\n  若使用微软账户或外置登录账户启动游戏，账户信息过期失效可能将无法进入需在线验证的服务器。\\n\\\n  若尝试多次无法成功刷新，可尝试重新添加该账户，或许会解决该问题。\\n\\\n  你可以点击右上角帮助按钮进行求助。\naccount.failed.server_response_malformed=无法解析认证服务器响应。可能是服务器故障。\naccount.failed.ssl=连接服务器时发生了 SSL 错误。可能网站证书已过期或你使用的 Java 版本过低。请尝试更新 Java，或关闭网络代理后再试。\\n你可以点击右上角帮助按钮进行求助。\naccount.failed.dns=连接服务器时发生了 SSL 错误。可能是 DNS 解析有误。请尝试更换 DNS 服务器或使用代理服务。\\n你可以点击右上角帮助按钮进行求助。\naccount.failed.wrong_account=登录了错误的账户\naccount.hmcl.hint=你需要点击“登录”按钮，并在弹出的网页中完成登录。\naccount.injector.add=添加认证服务器\naccount.injector.empty=无 (点击右侧加号添加)\naccount.injector.http=警告：此服务器使用不安全的 HTTP 协议。你的密码在登录时会被明文传输。\naccount.injector.link.homepage=主页\naccount.injector.link.register=注册\naccount.injector.server=认证服务器\naccount.injector.server_url=服务器地址\naccount.injector.server_name=服务器名称\naccount.login=登录\naccount.login.hint=我们不会保存你的密码\naccount.login.skip=跳过账户刷新\naccount.login.retry=再次刷新账户\naccount.login.refresh=重新登录\naccount.login.refresh.microsoft.hint=由于账户授权失效，你需要重新添加微软账户。\naccount.login.restricted=登录微软账户以启用此功能\naccount.logout=登出\naccount.register=注册\naccount.manage=账户列表\naccount.copy_uuid=复制该账户的 UUID\naccount.methods=登录方式\naccount.methods.authlib_injector=外置登录\naccount.methods.microsoft=微软账户\naccount.methods.microsoft.birth=如何更改账户出生日期\naccount.methods.microsoft.code=代码 (已自动复制)\naccount.methods.microsoft.close_page=已完成微软账户授权。其余登录步骤将由启动器自动执行。你现在可以关闭本页面了。\naccount.methods.microsoft.deauthorize=解除账户授权\naccount.methods.microsoft.error.add_family=请点击 <a href=\"https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a\">此处</a> 更改你的账户出生日期，使年龄满 18 岁以上，或将账户加入到家庭中。\\n你可以点击右上角帮助按钮进行求助。\naccount.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 Xbox Live 的支持。\naccount.methods.microsoft.error.missing_xbox_account=请点击 <a href=\"https://www.minecraft.net/msaprofile/mygames/editprofile\">此处</a> 关联 Xbox 账户。\\n你可以点击右上角帮助按钮进行求助。\naccount.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\\n若已购买，则可能未创建游戏档案。请点击 <a href=\"https://www.minecraft.net/msaprofile/mygames/editprofile\">此处</a> 创建游戏档案。\\n你可以点击右上角帮助按钮进行求助。\naccount.methods.microsoft.error.banned=你的账户可能被 Xbox Live 封禁。\\n你可以点击 <a href=\"https://enforcement.xbox.com/enforcement/showenforcementhistory\">此处</a> 按钮查询账户封禁状态。\naccount.methods.microsoft.error.unknown=未知问题。错误码：%d。\\n你可以点击右上角帮助按钮进行求助。\naccount.methods.microsoft.error.wrong_verify_method=登录失败。请在微软账户登录页面使用密码登录，不要使用其他登录方式。\\n你可以点击右上角帮助按钮进行求助。\naccount.methods.microsoft.logging_in=登录中……\naccount.methods.microsoft.makegameidsettings=创建档案 / 编辑档案名称\naccount.methods.microsoft.hint=点击“登录”按钮开始添加微软账户。\naccount.methods.microsoft.methods.device=扫描二维码登录\naccount.methods.microsoft.methods.device.hint=扫描二维码或访问 <a href=\"%s\">%s</a>，在打开的页面中输入 <b>%s</b> 完成登录。\naccount.methods.microsoft.methods.browser=在浏览器登录\naccount.methods.microsoft.methods.browser.hint=点击“登录”按钮或者<a href=\"%s\">复制链接</a>并在浏览器中粘贴以登录。\naccount.methods.microsoft.manual=<b>若网络环境不佳，可能会导致网页加载缓慢甚至无法加载，请使用网络代理并重试。</b>\\n\\\n  如遇到问题，你可以点击右上角帮助按钮进行求助。\naccount.methods.microsoft.profile=编辑账户个人信息\naccount.methods.microsoft.purchase=购买 Minecraft\naccount.methods.ban_query=检测账户是否被封禁\naccount.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。请下载 <a href=\"https://hmcl.huangyuhui.net/download\">官方版本</a> 来登录微软账户。\naccount.methods.microsoft.snapshot.tooltip=你正在使用第三方提供的 HMCL。请下载官方版本来刷新账户。\naccount.methods.microsoft.snapshot.website=官方网站\naccount.methods.offline=离线模式\naccount.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名，且长度不超过 16 个字符\naccount.methods.offline.name.invalid=游戏用户名建议仅使用英文字母、数字及下划线，且长度不超过 16 个字符。\\n\\\n  \\n\\\n  \\  · 一些合法用户名：HuangYu、huang_Yu、Huang_Yu_123；\\n\\\n  \\  · 一些非法用户名：黄鱼、Huang Yu、Huang-Yu_%%%、Huang_Yu_hello_world_hello_world。\\n\\\n  \\n\\\n  使用非法用户名会导致你无法加入大部分服务器，并可能与部分模组冲突而使游戏崩溃。\naccount.methods.offline.uuid=UUID\naccount.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一标识符。每个启动器生成 UUID 的方式可能不同。通过将 UUID 修改为原启动器所生成的 UUID，你可以保证在切换启动器后，游戏还能将你的游戏角色识别为给定 UUID 所对应的角色，从而保留原角色的背包物品。UUID 选项为高级选项。除非你知道你在做什么，否则你不需要调整该选项。\naccount.methods.offline.uuid.malformed=格式错误\naccount.missing=没有游戏账户\naccount.missing.add=点击此处添加账户\naccount.move_to_global=转换为全局账户\\n该账户的信息会保存至系统当前用户文件夹的配置文件中\naccount.move_to_portable=转换为便携账户\\n该账户的信息会保存至与 HMCL 同文件夹的配置文件中\naccount.not_logged_in=未登录\naccount.password=密码\naccount.portable=便携账户\naccount.skin=皮肤\naccount.skin.file=皮肤图片文件\naccount.skin.model=模型\naccount.skin.model.default=宽型\naccount.skin.model.slim=纤细\naccount.skin.type.alex=Alex\naccount.skin.type.csl_api=Blessing Skin 服务器\naccount.skin.type.csl_api.location=服务器地址\naccount.skin.type.csl_api.location.hint=CustomSkinAPI 地址\naccount.skin.type.little_skin=LittleSkin 皮肤站\naccount.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线账户同名的角色。此时离线账户皮肤将显示为皮肤站上对应角色所设置的皮肤。\\n你可以点击右上角帮助按钮进行求助。\naccount.skin.type.local_file=本地皮肤图片文件\naccount.skin.type.steve=Steve\naccount.skin.upload=上传/编辑皮肤\naccount.skin.upload.failed=皮肤上传失败\naccount.skin.invalid_skin=无法识别的皮肤文件\naccount.username=用户名\n\narchive.author=作者\narchive.date=发布日期\narchive.file.name=文件名\narchive.version=版本\n\nassets.download=下载资源\nassets.download_all=检查资源文件完整性\nassets.index.malformed=资源文件的索引文件损坏。你可以在相应实例的“实例管理”页面中，点击左下角“管理 → 更新游戏资源文件”以修复该问题。\\n你可以点击右上角帮助按钮进行求助。\n\nbutton.cancel=取消\nbutton.change_source=切换下载源\nbutton.clear=清除\nbutton.copy_and_exit=复制并退出\nbutton.delete=删除\nbutton.do_not_show_again=不再显示\nbutton.edit=修改\nbutton.install=安装\nbutton.export=导出\nbutton.no=否\nbutton.ok=确定\nbutton.ok.countdown=确定 (%d)\nbutton.reset=重置\nbutton.reveal_dir=打开文件夹\nbutton.refresh=刷新\nbutton.remove=删除\nbutton.remove.confirm=你确定要删除吗？此操作无法撤销！\nbutton.retry=重试\nbutton.save=保存\nbutton.save_as=另存为\nbutton.select_all=全选\nbutton.view=查看\nbutton.yes=是\n\ncolor.recent=推荐\ncolor.custom=自定义颜色\n\ncontact=反馈\ncontact.feedback=提交反馈\ncontact.feedback.github=GitHub Issue\ncontact.feedback.github.statement=提交一个 GitHub Issue\ncontact.chat=官方群组\ncontact.chat.qq_group=用户 QQ 群\ncontact.chat.qq_group.statement=欢迎加入 HMCL 官方 QQ 群，加入后请遵守群规\ncontact.chat.discord=Discord\ncontact.chat.discord.statement=欢迎加入 Discord 服务器，加入后请遵守讨论区规定\n\ncrash.NoClassDefFound=请确认 Hello Minecraft! Launcher 本体是否完整，或更新你的 Java。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\ncrash.user_fault=你的系统或 Java 环境可能安装不当导致本软件崩溃，请检查你的 Java 环境或你的电脑。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n\ncurse.category.0=全部\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4471\ncurse.category.4474=科幻\ncurse.category.4481=轻量整合包\ncurse.category.4483=战斗 PvP\ncurse.category.4477=小游戏\ncurse.category.4478=任务\ncurse.category.4484=多人\ncurse.category.4476=探索\ncurse.category.4736=空岛\ncurse.category.4475=冒险 RPG\ncurse.category.4487=FTB 整合包\ncurse.category.4480=有特定地图\ncurse.category.4479=高难度\ncurse.category.4482=大型整合包\ncurse.category.4472=科技\ncurse.category.4473=魔法\ncurse.category.5128=原版增强\ncurse.category.7418=恐怖\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/6\ncurse.category.5299=教育\ncurse.category.5232=额外行星\ncurse.category.5129=原版增强\ncurse.category.5189=实用与 QOL\ncurse.category.5190=QOL\ncurse.category.5191=实用与 QOL\ncurse.category.5192=梦幻菜单\ncurse.category.6145=空岛\ncurse.category.6814=性能\ncurse.category.6954=动态联合/集成动力 (Integrated Dynamics)\ncurse.category.6484=机械动力 (Create)\ncurse.category.6821=错误修复\ncurse.category.423=信息展示\ncurse.category.426=模组扩展\ncurse.category.434=装备武器\ncurse.category.409=自然生成\ncurse.category.4485=血魔法\ncurse.category.420=存储\ncurse.category.429=工业 (Industrialcraft)\ncurse.category.419=魔法\ncurse.category.412=科技\ncurse.category.4557=红石\ncurse.category.428=匠魂\ncurse.category.414=交通运输\ncurse.category.4486=幸运方块 (Lucky Blocks)\ncurse.category.432=建筑 (Buildcraft)\ncurse.category.418=基因\ncurse.category.4671=Twitch\ncurse.category.5314=KubeJS\ncurse.category.408=矿物资源\ncurse.category.4773=CraftTweaker\ncurse.category.430=神秘 (Thaumcraft)\ncurse.category.422=冒险 RPG\ncurse.category.413=机器处理\ncurse.category.417=能源\ncurse.category.415=物流运输\ncurse.category.433=林业 (Forestry)\ncurse.category.425=其他\ncurse.category.4545=应用能源 2 (Applied Energistics 2)\ncurse.category.416=农业\ncurse.category.421=支持库\ncurse.category.4780=Fabric\ncurse.category.424=装饰\ncurse.category.406=世界生成\ncurse.category.435=服务器\ncurse.category.411=生物\ncurse.category.407=生物群系\ncurse.category.427=热力膨胀 (Thermal Expansion)\ncurse.category.410=维度\ncurse.category.436=食物\ncurse.category.4558=红石\ncurse.category.4843=自动化\ncurse.category.4906=MCreator\ncurse.category.7669=暮色森林\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/12\ncurse.category.5244=字体包\ncurse.category.5193=数据包\ncurse.category.399=蒸汽朋克\ncurse.category.396=128x\ncurse.category.398=512x 及更高\ncurse.category.397=256x\ncurse.category.405=其他\ncurse.category.395=64x\ncurse.category.400=仿真\ncurse.category.393=16x\ncurse.category.403=传统\ncurse.category.394=32x\ncurse.category.404=动态效果\ncurse.category.4465=模组支持\ncurse.category.402=中世纪风格\ncurse.category.401=现代风格\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/17\ncurse.category.4464=模组\ncurse.category.250=游戏挑战\ncurse.category.249=创造模式\ncurse.category.251=跑酷\ncurse.category.253=生存模式\ncurse.category.248=冒险模式\ncurse.category.252=解谜类\n\n# https://addons-ecs.forgesvc.net/api/v2/category/section/4546\ncurse.category.4551=硬核任务模式\ncurse.category.4548=幸运方块 (Lucky Blocks)\ncurse.category.4556=任务进度\ncurse.category.4752=小物件\ncurse.category.4553=CraftTweaker\ncurse.category.4554=合成表\ncurse.category.4549=指引书\ncurse.category.4547=配置\ncurse.category.4550=任务\ncurse.category.4555=世界生成\ncurse.category.4552=脚本\n\ncurse.category.6553=写实\ncurse.category.6554=幻想\ncurse.category.6555=原生\n\ncurse.sort.author=作者\ncurse.sort.date_created=创建日期\ncurse.sort.last_updated=最近更新\ncurse.sort.name=名称\ncurse.sort.popularity=热度\ncurse.sort.total_downloads=下载量\n\ndatetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss\n\ndownload=下载\ndownload.hint=安装游戏和整合包或下载模组、资源包、光影和世界\ndownload.code.404=远程服务器不包含需要下载的文件: %s\\n你可以点击右上角帮助按钮进行求助。\ndownload.content=游戏内容\ndownload.shader=光影\ndownload.curseforge.unavailable=此 HMCL 版本不支持访问 CurseForge。请使用官方版本进行下载。\ndownload.existing=文件已存在，无法保存。你可以将文件保存至其他地方。\ndownload.external_link=打开下载网站\ndownload.failed=下载失败: %1$s，\\n错误码：%2$d\\n你可以点击右上角帮助按钮进行求助。\ndownload.failed.empty=[没有可供安装的版本，点击此处返回]\\n(你可以点击右上角帮助按钮进行求助)\ndownload.failed.no_code=下载失败\ndownload.failed.refresh=[加载版本列表失败，点击此处重试]\\n(你可以点击右上角帮助按钮进行求助)\ndownload.game=新游戏\ndownload.provider.bmclapi=BMCLAPI\ndownload.provider.bmclapi.desc=bangbang93, https://bmclapi2.bangbang93.com\ndownload.provider.mojang=官方\ndownload.provider.mojang.desc=OptiFine 自动安装使用 BMCLAPI 下载源\ndownload.provider.official=尽量使用官方源\ndownload.provider.official.desc=最新，但可能加载慢\ndownload.provider.balanced=选择加载速度快的下载源\ndownload.provider.balanced.desc=平衡，但可能不是最新\ndownload.provider.mirror=尽量使用镜像源\ndownload.provider.mirror.desc=加载快，但可能不是最新\ndownload.java=下载 Java\ndownload.java.process=下载 Java\ndownload.javafx=正在下载必要的运行时组件……\ndownload.javafx.notes=正在通过网络下载 HMCL 必要的运行时组件。\\n点击“切换下载源”按钮查看详情以及选择下载源。点击“取消”按钮停止并退出。\\n注意：若下载速度过慢，请尝试切换下载源。\ndownload.javafx.component=正在下载模块“%s”\ndownload.javafx.prepare=准备开始下载\ndownload.speed.byte_per_second=%d B/s\ndownload.speed.kibibyte_per_second=%.1f KiB/s\ndownload.speed.megabyte_per_second=%.1f MiB/s\n\nexception.access_denied=无法访问文件“%s”。HMCL 没有对该文件的访问权限，或者该文件已被其他程序打开。\\n\\\n  请你检查当前操作系统账户是否能访问该文件，比如非管理员用户可能无法访问其他账户的个人文件夹内的文件。\\n\\\n  对于 Windows 用户，你还可以尝试通过资源监视器查看是否有程序占用了该文件。如果是，请关闭占用该文件的程序，或者重启电脑再试。\\n\\\n  如遇到问题，你可以点击右上角帮助按钮进行求助。\nexception.artifact_malformed=下载的文件无法通过校验。\\n你可以点击右上角帮助按钮进行求助。\nexception.ssl_handshake=无法建立 SSL 连接。当前 Java 缺少相关的 SSL 证书。你可以尝试使用其他 Java 启动 HMCL 再试。\\n你可以点击右上角帮助按钮进行求助。\nexception.dns.pollution=无法建立 SSL 连接。可能是 DNS 解析有误。请尝试更换 DNS 服务器或使用代理服务。\\n你可以点击右上角帮助按钮进行求助。\n\nextension.bat=Windows 脚本\nextension.png=图片文件\nextension.ps1=PowerShell 脚本\nextension.sh=Bash 脚本\nextension.command=macOS Shell 脚本\n\nextension.datapack=数据包压缩包\nextension.mod=模组文件\nextension.modloader.installer=模组加载器安装包\nextension.resourcepack=资源包压缩包\nextension.schematic=原理图文件\nextension.world=世界压缩包\n\nfatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 无法创建 HMCL 文件夹 (%s)，请将 HMCL 移动至其他位置再启动。\\n如遇到问题，你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nfatal.javafx.incomplete=JavaFX 运行环境不完整。请尝试更换你的 Java 或者重新安装 OpenJFX。\nfatal.javafx.missing=缺少 JavaFX 运行环境。请使用包含 OpenJFX 的 Java 运行环境启动 Hello Minecraft! Launcher。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nfatal.config_change_owner_root=你正在使用 root 账户启动 Hello Minecraft! Launcher，这可能导致你未来无法正常使用其他账户正常启动 HMCL。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\\n是否继续启动？\nfatal.config_in_temp_dir=你正在临时文件夹中启动 Hello Minecraft! Launcher，你的设置和游戏数据可能会丢失。建议将 HMCL 移动至其他位置再启动。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\\n是否继续启动？\nfatal.config_loading_failure=Hello Minecraft! Launcher 无法加载配置文件。\\n请确保 HMCL 对“%s”文件夹及该文件夹下的文件拥有读写权限。\\n对于 macOS，尝试将 HMCL 放在除“桌面”“下载”“文稿”之外的有权限的地方再试。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nfatal.config_loading_failure.unix=Hello Minecraft! Launcher 无法加载配置文件，因为配置文件是由用户“%1$s”创建的。\\n请使用 root 账户启动 HMCL (不推荐)，或在终端中执行以下命令将配置文件的所有权变更为当前用户：\\n%2$s\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nfatal.config_unsupported_version=当前配置文件是由更高版本的 Hello Minecraft! Launcher 创建的，当前版本的 HMCL 无法正常加载。请更新并重启 HMCL。\\n在更新启动器之前，你修改的所有设置都不会被保存。\\n如果遇到问题，你可以点击右上角帮助按钮进行求助。\nfatal.mac_app_translocation=由于 macOS 的安全机制，Hello Minecraft! Launcher 被系统隔离至临时文件夹中。\\n请将 HMCL 移动到其他文件夹后再尝试启动，否则你的设置和游戏数据可能会在重启后丢失。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\\n是否继续启动？\nfatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级，请重新打开 HMCL。\\n如遇到问题，你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nfatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级，因为出现了一些问题。\\n但你依可以从 %s 手动下载 HMCL 来完成升级。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nfatal.apply_update_need_win7=Hello Minecraft! Launcher 无法在 Windows XP/Vista 上进行自动更新。请从 %s 手动下载 HMCL 来完成升级。\nfatal.deprecated_java_version=HMCL 未来需要 Java 17 或更高版本才能运行，但依然支持使用 Java 6~16 启动游戏。建议安装最新版本的 Java 以确保 HMCL 能正常工作。\\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java，并会自动根据游戏版本为你选择合适的 Java。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nfatal.deprecated_java_version.update=更高版本的 HMCL 需要 Java 17 或更高版本才能运行，请安装最新版本的 Java 以确保 HMCL 能够完成升级。\\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java，并会自动根据游戏版本为你选择合适的 Java。\nfatal.deprecated_java_version.download_link=下载 Java %d\nfatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher，启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 HMCL。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nfatal.illegal_char=由于你的运行路径中存在非法字符“=”，你将无法使用外置登录账户以及离线登录更换皮肤功能。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nfatal.unsupported_platform=Minecraft 尚未对你的平台提供完善支持，所以可能影响游戏体验或无法启动游戏。\\n\\\n  若无法启动 Minecraft 1.17 及更高版本，可以尝试在“(全局/实例特定) 游戏设置 → 高级设置 → 调试选项”中将“渲染器”切换为“Mesa LLVMpipe”，以获得更好的兼容性。\\n\\\n  如遇到问题，你可以点击右上角帮助按钮进行求助。\nfatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已为龙芯提供支持。\\n如果遇到问题，你可以点击右上角帮助按钮进行求助。\nfatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher 已为 Apple Silicon 平台提供支持。使用 ARM 原生 Java 启动游戏以获得更流畅的游戏体验。\\n如果你在游戏中遇到问题，使用 x86-64 架构的 Java 启动游戏可能有更好的兼容性。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\nfatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已为 Windows on Arm 平台提供原生支持。如果你在游戏中遇到问题，请尝试使用 x86 架构的 Java 启动游戏。\\n如果你正在使用 <b>高通</b> 平台，你可能需要安装 <a href=\"ms-windows-store://pdp/?productid=9NQPSL29BFFF\">OpenGL 兼容包</a> 后才能进行游戏。点击链接前往 Microsoft Store 安装兼容包。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\n\nfile=文件\n\nfolder.config=配置文件夹\nfolder.game=实例运行文件夹\nfolder.logs=日志文件夹\nfolder.mod=模组文件夹\nfolder.resourcepacks=资源包文件夹\nfolder.shaderpacks=光影包文件夹\nfolder.saves=世界文件夹\nfolder.schematics=原理图文件夹\nfolder.screenshots=截图文件夹\nfolder.world=世界文件夹\n\ngame=游戏\ngame.crash.feedback=<b>请不要将本界面截图或拍照给他人！</b>如果你要向他人求助，请你点击左下角<b>“导出游戏崩溃信息”</b>后将导出的文件发送给他人以供分析。\\n你可以点击下方的<b>“帮助”</b>前往交流群寻求帮助。\ngame.crash.info=游戏信息\ngame.crash.reason=崩溃原因\ngame.crash.reason.analyzing=分析中……\ngame.crash.reason.block=当前游戏由于某个方块不能正常工作，无法继续运行。\\n你可以尝试使用地图编辑工具编辑世界删除该方块，或者直接删除对应的模组。\\n方块类型：%1$s\\n方块坐标：%2$s\\n\\n相关工具：<a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">英文 Wiki</a> | <a href=\"https://zh.minecraft.wiki/w/辅助程序与编辑器/地图工具?variant=zh-cn#地图编辑工具\">中文 Wiki</a>\ngame.crash.reason.bootstrap_failed=当前游戏由于模组“%1$s”出现问题，无法继续运行。\\n你可以尝试删除或更新该模组以解决问题。\ngame.crash.reason.mixin_apply_mod_failed=当前游戏由于 Mixin 无法应用于“%1$s”模组，无法继续运行。\\n你可以尝试删除或更新该模组以解决问题。\ngame.crash.reason.config=当前游戏由于无法解析模组配置文件，无法继续运行。\\n无法解析模组“%1$s”的配置文件“%2$s”。\ngame.crash.reason.multiple=检测到多个原因：\\n\\n\ngame.crash.reason.debug_crash=当前游戏由于手动触发崩溃，无法继续运行。\\n事实上游戏并没有问题，问题都是你造成的！\ngame.crash.reason.duplicated_mod=当前游戏由于模组“%1$s”重复安装，无法继续运行。\\n%2$s\\n每种模组只能安装一个，请你删除多余的模组再试。\ngame.crash.reason.entity=当前游戏由于某个实体不能正常工作，无法继续运行。\\n你可以尝试使用地图编辑工具编辑世界删除该实体，或者直接删除对应的模组。\\n实体类型：%1$s\\n实体坐标：%2$s\\n\\n相关工具：<a href=\"https://minecraft.wiki/w/Tutorial:Programs_and_editors/Mapping#Map_editors\">英文 Wiki</a> | <a href=\"https://zh.minecraft.wiki/w/辅助程序与编辑器/地图工具?variant=zh-cn#地图编辑工具\">中文 Wiki</a>\ngame.crash.reason.fabric_version_0_12=Fabric Loader 0.12 及更高版本与当前已经安装的模组可能不兼容，你需要将 Fabric Loader 降级至 0.11.7。\ngame.crash.reason.fabric_warnings=Fabric 提供了一些警告信息：\\n%1$s\ngame.crash.reason.file_already_exists=当前游戏由于文件“%1$s”已经存在，无法继续运行。\\n如果你认为这个文件可以删除，你可以在备份这个文件后尝试删除它，并重新启动游戏。\ngame.crash.reason.file_changed=当前游戏由于文件校验失败，无法继续运行。\\n如果你手动修改了 Minecraft.jar 文件，你需要回退修改，或者重新下载游戏。\ngame.crash.reason.gl_operation_failure=当前游戏由于你使用的某些模组/光影包/资源包/纹理包有问题，导致无法继续运行。\\n请先尝试禁用你所使用的模组/光影包/资源包/纹理包再试。\ngame.crash.reason.graphics_driver=当前游戏由于显卡驱动问题而崩溃，请尝试以下操作：\\n\\\n  \\  · 如果你的电脑同时存在独立显卡和集成显卡，请尝试使用独立显卡启动 HMCL 与游戏。<a href=\"https://www.bing.com/search?q=使用独立显卡运行Minecraft\">详情</a>\\n\\\n  \\  · <a href=\"https://minecrafthopper.net/help/pixel-format-not-accelerated/\">尝试升级你的显卡驱动到最新版本</a>，或回退到出厂版本。\\n\\\n  \\  · 如果你确实需要使用集成显卡，请检查你电脑的 CPU 是否为 Intel(R) Core(TM) 3000 系列或更旧的处理器。如果是，对于 Minecraft 1.16.5 及更旧版本，请你将游戏所使用的 Java 降级至 1.8.0_51 及更低版本，否则请跳过。<a href=\"https://github.com/frekele/oracle-java/releases/8u51-b16\">Java 1.8.0 历史版本</a>\\n\\\n  如果仍有问题，你可能需要考虑换一张新显卡或一台新电脑。\ngame.crash.reason.macos_failed_to_find_service_port_for_display=当前游戏由于 Apple Silicon 平台下初始化 OpenGL 窗口失败，无法继续运行。\\n对于该问题，HMCL 暂无直接性的解决方案。请你尝试任意打开一个浏览器并全屏，然后再回到 HMCL 启动游戏。在弹出游戏窗口前<b>迅速切回浏览器页面</b>，等待游戏窗口出现后再切回游戏窗口。\ngame.crash.reason.illegal_access_error=当前游戏由于某些模组的问题，无法继续运行。\\n如果你认识“%1$s”，你可以更新或删除对应模组再试。\ngame.crash.reason.install_mixinbootstrap=当前游戏由于缺失 MixinBootstrap，无法继续运行。\\n你可以尝试安装 <a href=\"https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap\">MixinBootstrap</a> 解决该问题。若安装后崩溃，可以尝试在该模组的文件名开头添加半角感叹号 (!) 解决。\ngame.crash.reason.need_jdk11=当前游戏由于 Java 版本不合适，无法继续运行。\\n你需要下载安装 <a href=\"https://bell-sw.com/pages/downloads/#downloads\">Java 11</a>，并在“(全局/实例特定) 游戏设置 → 游戏 Java”中将 Java 设置为 11.x 的版本。\ngame.crash.reason.jdk_9=当前游戏由于 Java 版本过高，无法继续运行。\\n你需要下载安装 <a href=\"https://bell-sw.com/pages/downloads/#downloads\">Java 8</a>，并在“(全局/实例特定) 游戏设置 → 游戏 Java”中将 Java 设置为 1.8.x 的版本。\ngame.crash.reason.jvm_32bit=当前游戏由于内存分配过大，超过了 32 位 Java 虚拟机内存限制，无法继续运行。\\n如果你的电脑是 64 位系统，请下载安装并更换 64 位 Java。<a href=\"https://bell-sw.com/pages/downloads/#downloads\">下载 Java</a>\\n如果你的电脑是 32 位系统，你或许可以重新安装 64 位系统，或换一台新电脑。\\n或者，你可以在“(全局/实例特定) 游戏设置 → 游戏内存”中关闭“自动分配内存”，并把内存分配值调整为 1024 MiB 或以下。\ngame.crash.reason.loading_crashed_forge=当前游戏由于模组“%1$s (%2$s)”错误，无法继续运行。\\n你可以尝试删除或更新该模组以解决问题。\ngame.crash.reason.loading_crashed_fabric=当前游戏由于模组“%1$s”错误，无法继续运行。\\n你可以尝试删除或更新该模组以解决问题。\ngame.crash.reason.memory_exceeded=当前游戏由于内存分配过大，无法继续运行。\\n该问题是由于系统页面文件太小导致的。\\n你需要在“(全局/实例特定) 游戏设置”中关闭“自动分配内存”，并将内存分配值调低至游戏能正常启动为止。\\n你还可以尝试将虚拟内存设置调整为“自动管理所有驱动器分页文件大小”，<a href=\"https://docs.hmcl.net/assets/img/hmcl/自动管理所有驱动器分页文件大小.webp\">详情</a>。\ngame.crash.reason.mac_jdk_8u261=当前游戏由于你所使用的 Forge/OptiFine 与 Java 冲突而崩溃。\\n请尝试更新 Forge/OptiFine，或使用 Java 8u251 及更早版本启动。\ngame.crash.reason.mod=当前游戏由于“%1$s”的问题，无法继续运行。\\n你可以更新或删除已经安装的“%1$s”再试。\ngame.crash.reason.mod_resolution=当前游戏由于模组依赖问题，无法继续运行。Fabric 提供了如下信息：\\n%1$s \ngame.crash.reason.mod_resolution_collection=当前游戏由于前置模组版本不匹配，无法继续运行。\\n“%1$s”需要前置模组“%2$s”才能继续运行。\\n这表示你需要更新或降级前置模组。你可以前往“下载 → 模组”页面或网上下载“%3$s”。\ngame.crash.reason.mod_resolution_conflict=当前游戏由于模组冲突，无法继续运行。\\n“%1$s”与“%2$s”不兼容。\ngame.crash.reason.mod_resolution_missing=当前游戏由于缺少前置模组，无法继续运行。\\n“%1$s”需要前置模组“%2$s”才能继续运行。\\n这表示有一些必需的模组没有安装，或该模组版本不匹配。你可以前往“下载 → 模组”页面或网上下载“%3$s”。\ngame.crash.reason.mod_resolution_missing_minecraft=当前游戏由于模组和 Minecraft 游戏版本不匹配，无法继续运行。\\n“%1$s”需要 Minecraft %2$s 才能运行。\\n如果你要继续使用你已经安装的模组，你可以选择安装对应的 Minecraft 版本。如果你要继续使用当前 Minecraft 版本，你需要安装对应版本的模组。\ngame.crash.reason.mod_resolution_mod_version=%1$s (版本号 %2$s)\ngame.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本)\ngame.crash.reason.forge_repeat_installation=当前游戏由于 Forge 重复安装，无法继续运行。<a href=\"https://github.com/HMCL-dev/HMCL/issues/1880\">此为已知问题</a>\\n建议将日志上传并反馈至 GitHub，以便我们找到更多线索并修复此问题。\\n目前你可以在“实例管理 → 自动安装”中卸载 Forge 并重新安装。\ngame.crash.reason.optifine_repeat_installation=当前游戏由于 OptiFine 重复安装，无法继续运行。\\n请删除模组文件夹下的 OptiFine 或前往“实例管理 → 自动安装”卸载安装的 OptiFine。\ngame.crash.reason.forgemod_resolution=当前游戏由于模组依赖问题，无法继续运行。Forge/NeoForge 提供了如下信息：\\n%1$s \ngame.crash.reason.forge_found_duplicate_mods=当前游戏由于模组重复安装，无法继续运行。Forge/NeoForge 提供了如下信息：\\n%1$s \ngame.crash.reason.modmixin_failure=当前游戏由于某些模组注入失败，无法继续运行。\\n这一般代表着该模组存在问题，或与当前环境不兼容。\\n你可以查看日志寻找出错模组。\ngame.crash.reason.night_config_fixes=当前游戏由于 Night Config 库的一些问题，无法继续运行。\\n你可以尝试安装 <a href=\"https://www.curseforge.com/minecraft/mc-mods/night-config-fixes\">Night Config Fixes</a> 模组，这或许能帮助你解决这个问题。\\n更多详情，可访问该模组的 <a href=\"https://github.com/Fuzss/nightconfigfixes\">GitHub 仓库</a>。\ngame.crash.reason.forge_error=Forge/NeoForge 可能已经提供了错误信息。\\n你可以查看日志，并根据错误报告中的日志信息进行对应处理。\\n如果没有看到报错信息，可以查看错误报告了解错误具体是如何发生的。\\n%1$s\ngame.crash.reason.mod_resolution0=当前游戏由于一些模组出现问题，无法继续运行。\\n你可以查看日志寻找出错模组。\ngame.crash.reason.java_version_is_too_high=当前游戏由于 Java 版本过高，无法继续运行。\\n请在“(全局/实例特定) 游戏设置 → 游戏 Java”中改用较低版本的 Java，然后再启动游戏。\\n如果没有，可以从 <a href=\"https://www.java.com/download/\">java.com (Java 8)</a> 或 <a href=\"https://bell-sw.com/pages/downloads/#downloads\">BellSoft Liberica Full JRE (Java 17)</a> 等平台下载、安装一个 (安装完后需重启启动器)。\ngame.crash.reason.mod_name=当前游戏由于模组文件名称问题，无法继续运行。\\n模组文件名称应只使用半角的大小写字母 (Aa~Zz)、数字 (0~9)、横线 (-)、下划线 (_)和点 (.)。\\n请到模组文件夹中将所有不合规的模组文件名修改为上述合规字符。\ngame.crash.reason.incomplete_forge_installation=当前游戏由于 Forge/NeoForge 安装不完整，无法继续运行。\\n请在“实例管理 → 自动安装”中卸载 Forge 并重新安装。\ngame.crash.reason.optifine_is_not_compatible_with_forge=当前游戏由于 OptiFine 与当前版本的 Forge 不兼容，导致游戏崩溃。\\n点击 <a href=\"https://zkitefly.github.io/optifine-forge-support-list\">此处</a> 查看 OptiFine 所兼容的 Forge 版本，并严格按照对应版本重新安装游戏或在“实例管理 → 自动安装”中更换版本。\\n经测试，Forge 版本过高或过低都可能导致崩溃。\ngame.crash.reason.mod_files_are_decompressed=当前游戏由于模组文件被解压了，无法继续运行。\\n请直接把整个模组文件放进模组文件夹中即可。\\n解压模组文件会导致游戏出错。请删除模组文件夹中已被解压的模组，然后再启动游戏。\ngame.crash.reason.shaders_mod=当前游戏由于同时安装了 OptiFine 和 Shaders 模组，无法继续运行。\\n由于 OptiFine 已集成 Shaders 模组的功能，所以只需删除 Shaders 模组即可。\ngame.crash.reason.rtss_forest_sodium=当前游戏由于 RivaTuner Statistics Server (RTSS) 与 Sodium 不兼容，导致游戏崩溃。\\n点击 <a href=\"https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\">此处</a> 查看详情。\ngame.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=当前游戏由于你所安装的模组过多，超出了游戏的 ID 限制，无法继续运行。\\n请尝试安装 <a href=\"https://www.curseforge.com/minecraft/mc-mods/jeid\">JEID</a> 等修复模组，或删除部分大型模组。\ngame.crash.reason.optifine_causes_the_world_to_fail_to_load=当前游戏可能由于 OptiFine 而无法继续运行。\\n该问题只在特定 OptiFine 版本中出现，你可以尝试在“实例管理 → 自动安装”中更换 OptiFine 的版本。\ngame.crash.reason.modlauncher_8=当前游戏由于你所使用的 Forge 版本与当前使用的 Java 冲突而崩溃。请尝试更新 Forge 到 36.2.26 或更高版本或换用版本低于 1.8.0.320 的 Java。<a href=\"https://bell-sw.com/pages/downloads/?version=java-8&package=jre-full\">Liberica JDK 8u312+7</a>\ngame.crash.reason.no_class_def_found_error=当前游戏由于代码不完整，无法继续运行。\\n你的游戏可能缺失了某个模组，或者某些模组文件不完整，或者模组与游戏的版本不匹配。\\n你可能需要重新安装游戏和模组，或寻求他人帮助。\\n缺失：\\n%1$s \ngame.crash.reason.no_such_method_error=当前游戏由于代码不完整，无法继续运行。\\n你的游戏可能缺失了某个模组，或者某些模组文件不完整，或者模组与游戏的版本不匹配。\\n你可能需要重新安装游戏和模组，或寻求他人帮助。\ngame.crash.reason.opengl_not_supported=当前游戏由于你的显卡驱动存在问题，无法继续运行。\\n原因是 OpenGL 不受支持，你现在是否在远程桌面或者串流模式下？如果是，请直接使用原电脑启动游戏。\\n或者尝试升级你的显卡驱动到最新版本后再尝试启动游戏。如果你的电脑同时存在独立显卡和集成显卡，你需要检查游戏是否使用集成显卡启动。如果是，请尝试使用独立显卡启动 HMCL 与游戏。如果仍有问题，你可能需要考虑换一个新显卡或新电脑。\ngame.crash.reason.openj9=当前游戏无法在 OpenJ9 虚拟机上运行。请你在“(全局/实例特定) 游戏设置 → 游戏 Java”中更换为使用 Hotspot 虚拟机的 Java，并重新启动游戏。如果没有下载安装，你可以在网上自行下载。\ngame.crash.reason.out_of_memory=当前游戏由于内存不足，无法继续运行。\\n这可能是内存分配过小，或者模组数量过多导致的。\\n你可以在“(全局/实例特定) 游戏设置 → 游戏内存”中调整内存分配值以允许游戏在更大内存下运行。\\n如果仍然出现该错误，你可能需要换一台更好的电脑。\ngame.crash.reason.resolution_too_high=当前游戏由于资源包/纹理包分辨率过高，无法继续运行。\\n你可以更换一个分辨率更低的资源包/纹理包，或者更换一个显存更大的显卡。\ngame.crash.reason.stacktrace=原因未知。请点击日志按钮查看详细信息。\\n下面是一些关键词，其中可能包含模组名称，你可以通过搜索的方式查找有关信息。\\n%s\ngame.crash.reason.too_old_java=当前游戏由于 Java 版本过低，无法继续运行。\\n你需要在“(全局/实例特定) 游戏设置 → 游戏 Java”中更换 Java %1$s 或更新版本的 Java，并重新启动游戏。如果没有下载安装，你可以点击 <a href=\"https://learn.microsoft.com/java/openjdk/download\">此处</a> 下载 Microsoft JDK。\ngame.crash.reason.unknown=原因未知。请点击日志按钮查看详细信息。\ngame.crash.reason.unsatisfied_link_error=当前游戏由于缺少本地库，无法继续运行。\\n这些本地库缺失：%1$s。\\n如果你在“(全局/实例特定) 游戏设置 → 高级设置”中修改了本地库路径选项，请你修改回默认模式。\\n如果你正在使用默认模式，请检查游戏文件夹路径是否只包含英文字母、数字和下划线。\\n如果是，那么请检查是否为模组或 HMCL 导致了本地库缺失的问题。如果你确定是 HMCL 引起的，建议你向我们反馈。\\n<b>对于 Windows 用户，你还可以尝试在“控制面板 → 时钟和区域 → 区域 → 管理 → 更改系统区域设置”中将“当前系统区域设置”修改为“中文(简体，中国大陆)”，并关闭“Beta 版：使用 Unicode UTF-8 提供全球语言支持”选项；</b>\\n<b>或将游戏文件夹路径中的所有非英文字符的名称 (例如中文、空格等) 修改为英文字符。</b>\\n如果你确实需要自定义本地库路径，你需要保证其中包含缺失的本地库！\ngame.crash.title=游戏意外退出\ngame.directory=游戏文件夹路径\ngame.version=游戏实例\n\nhelp=帮助\nhelp.doc=Hello Minecraft! Launcher 帮助文档\nhelp.detail=可查阅数据包、整合包制作指南等内容\n\ninput.email=用户名必须是邮箱\ninput.number=必须是数字\ninput.not_empty=必填项\ninput.url=必须是合法的链接\n\ninstall=添加实例\ninstall.change_version=更换版本\ninstall.change_version.confirm=你确定要将 %s 从 %s 更换成 %s 吗？\ninstall.change_version.process=更换版本\ninstall.failed=安装失败\ninstall.failed.downloading=安装失败，部分文件未能完成下载\ninstall.failed.downloading.detail=未能下载文件：%s\ninstall.failed.downloading.timeout=下载超时：%s\ninstall.failed.install_online=无法识别要安装的组件。如果你要安装模组，你需要在模组管理页面安装模组。\ninstall.failed.malformed=下载的文件已损坏。你可以在“设置 → 下载 → 下载源”中切换其他下载源来尝试解决此问题。\ninstall.failed.optifine_conflict=暂不支持在 Minecraft 1.13 及更高版本同时安装 OptiFine 与 Fabric。 \ninstall.failed.optifine_forge_1.17=对于 Minecraft 1.17.1 版本，仅 OptiFine H1 pre2 及更高版本与 Forge 兼容。你可以在 OptiFine 预览版 (Preview versions) 中选择最新版本。\ninstall.failed.version_mismatch=该组件需要的游戏版本为 %s，但实际的游戏版本为 %s。\ninstall.installer.change_version=%s 与当前游戏不兼容，请更换版本\ninstall.installer.choose=选择 %s 版本\ninstall.installer.cleanroom=Cleanroom\ninstall.installer.depend=需要先安装 %s\ninstall.installer.do_not_install=不安装\ninstall.installer.fabric=Fabric\ninstall.installer.legacyfabric=Legacy Fabric\ninstall.installer.legacyfabric-api=Legacy Fabric API\ninstall.installer.fabric-api=Fabric API\ninstall.installer.fabric-quilt-api.warning=%1$s 是一个模组，将会被安装到新游戏的模组文件夹。请你在安装游戏后不要修改当前游戏的“版本隔离”设置。如果你在之后修改了相关设置，则需要重新安装 %1$s。\ninstall.installer.forge=Forge\ninstall.installer.neoforge=NeoForge\ninstall.installer.game=Minecraft\ninstall.installer.incompatible=与 %s 不兼容\ninstall.installer.install=安装 %s\ninstall.installer.install_offline=从本地文件安装/升级\ninstall.installer.install_offline.tooltip=支持导入已经下载好的 (Neo)Forge、Cleanroom 和 OptiFine 安装器\ninstall.installer.install_online=在线安装\ninstall.installer.install_online.tooltip=支持安装 Forge、NeoForge、Cleanroom、Fabric、Legacy Fabric、Quilt、LiteLoader 和 OptiFine\ninstall.installer.liteloader=LiteLoader\ninstall.installer.not_installed=未安装\ninstall.installer.optifine=OptiFine\ninstall.installer.quilt=Quilt\ninstall.installer.quilt-api=QSL/QFAPI\ninstall.installer.version=%s\ninstall.installer.external_version=%s (由外部安装的版本，无法卸载或更换)\ninstall.installing=安装\ninstall.modpack=安装整合包\ninstall.modpack.installation=安装整合包\ninstall.name.invalid=名称中包含特殊字符 (如 Emoji 表情或中文字符)。\\n建议修改名称。名称建议仅包含英文字母、数字和下划线，以防启动游戏时出现问题。是否继续安装？\ninstall.new_game=安装新游戏\ninstall.new_game.already_exists=此实例已经存在，请换一个名字\ninstall.new_game.current_game_version=当前游戏实例\ninstall.new_game.installation=安装新游戏\ninstall.new_game.malformed=名字不合法\ninstall.select=请选择安装方式\ninstall.success=安装成功\n\njava.add=添加 Java\njava.add.failed=Java 无效或与当前平台不兼容。\njava.disable=禁用此 Java\njava.disable.confirm=你确定要禁用此 Java 吗？\njava.disabled.management=管理已禁用的 Java\njava.disabled.management.remove=从列表中移除此 Java\njava.disabled.management.restore=重新启用此 Java\njava.download=下载 Java\njava.download.banshanjdk-8=下载 Banshan JDK 8\njava.download.load_list.failed=加载版本列表失败\njava.download.more=更多发行版\njava.download.title=下载 Java\njava.download.prompt=请选择你要下载的 Java 版本：\njava.download.distribution=发行版\njava.download.version=版本\njava.download.packageType=包类型\njava.management=Java 管理\njava.info.architecture=架构\njava.info.vendor=供应商\njava.info.version=版本\njava.info.disco.distribution=发行版\njava.install=安装 Java\njava.install.archive=源路径\njava.install.failed.exists=该名称已被使用\njava.install.failed.invalid=该压缩包不是合法的 Java 安装包，无法继续安装。\njava.install.failed.unsupported_platform=此 Java 与当前平台不兼容，无法安装。\njava.install.name=名称\njava.install.warning.invalid_character=名称中包含非法字符\njava.installing=安装 Java\njava.uninstall=卸载此 Java\njava.uninstall.confirm=你确定要卸载此 Java 吗？此操作无法撤销！\n\nlang.default=跟随系统语言\n\nlaunch.advice=%s 是否继续启动？\nlaunch.advice.multi=检测到以下问题：\\n\\n%s\\n\\n这些问题可能导致游戏无法正常启动或影响游戏体验，是否继续启动？\\n你可以点击右上角帮助按钮进行求助。\nlaunch.advice.java.auto=当前选择的 Java 版本不满足游戏要求。\\n点击“是”即可由 HMCL 来自动选取合适的 Java 版本。\\n或者你可以在“(全局/实例特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 版本。\nlaunch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。\nlaunch.advice.cleanroom=Cleanroom %2$s 只能在 Java %1$s 或更高版本上运行。请使用 Java %1$s 或最新版本。\nlaunch.advice.corrected=我们已经修复了 Java 版本问题。如果你确实希望使用你自定义的 Java，你可以在“(全局/实例特定) 游戏设置 → 高级设置”中往下滑，启用“不检查 Java 虚拟机与游戏的兼容性”。\nlaunch.advice.uncorrected=如果你确实希望使用你自定义的 Java，你可以在“(全局/实例特定) 游戏设置 → 高级设置”中往下滑，启用“不检查 Java 虚拟机与游戏的兼容性”。\nlaunch.advice.different_platform=你正在使用 32 位 Java 启动游戏。建议更换至 64 位 Java。\nlaunch.advice.forge2760_liteloader=Forge 14.23.5.2760 与 LiteLoader 不兼容。请更新 Forge 至 14.23.5.2773 或更高版本。\nlaunch.advice.forge28_2_2_optifine=Forge 28.2.2 及更高版本与 OptiFine 不兼容。请降级 Forge 至 28.2.1 或更低版本。\nlaunch.advice.forge37_0_60=Forge 37.0.59 及更低版本与 Java 17 不兼容。请更新 Forge 至 37.0.60 或更高版本，或使用 Java 16 启动游戏。\nlaunch.advice.java8_1_13=Minecraft 1.13 及更高版本只能在 Java 8 或更高版本上运行。请使用 Java 8 或最新版本。\nlaunch.advice.java8_51_1_13=低于 1.8.0_51 的 Java 版本可能会导致 Minecraft 1.13 崩溃。建议更新 Java 至 1.8.0_51 或更高版本后再次启动。\nlaunch.advice.java9=低于 1.13 的有安装模组的 Minecraft 版本与 Java 9 或更高版本不兼容。请使用 Java 8。\nlaunch.advice.modded_java=部分模组可能与高版本 Java 不兼容。建议使用 Java %s 启动 Minecraft %s。\nlaunch.advice.modlauncher8=你所使用的 Forge 版本与当前使用的 Java 不兼容。请更新 Forge。\nlaunch.advice.newer_java=检测到你正在使用旧版本 Java 启动游戏，这可能导致部分模组引发游戏崩溃。建议更新至 Java 8 后再次启动。\nlaunch.advice.not_enough_space=你设置的内存分配值过大，超过了系统内存容量 %d MiB，可能导致游戏无法启动。\nlaunch.advice.require_newer_java_version=当前游戏版本需要 Java %s，但 HMCL 未能找到该 Java 版本。你可以点击“是”，HMCL 会自动下载它。是否下载？\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\nlaunch.advice.too_large_memory_for_32bit=你设置的内存分配值过大，由于可能超过了 32 位 Java 的内存分配限制，所以可能无法启动游戏。请将内存分配值调至 1024 MiB 或更小。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\nlaunch.advice.vanilla_linux_java_8=对于 Linux x86-64 平台，Minecraft 1.12.2 及更低版本与 Java 9+ 不兼容，请使用 Java 8 启动游戏。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\nlaunch.advice.vanilla_x86.translation=Minecraft 尚未为你的平台提供完善支持，所以可能影响游戏体验或无法启动游戏。\\n你可以在 <a href=\"https://learn.microsoft.com/java/openjdk/download\">这里</a> 下载 <b>x86-64</b> 架构的 Java 以获得更完整的体验。\nlaunch.advice.unknown=由于以下原因，无法继续启动游戏：\nlaunch.failed=启动失败\nlaunch.failed.cannot_create_jvm=无法创建 Java 虚拟机。可能是虚拟机参数有问题。你可以在“(全局/实例特定) 游戏设置 → 高级设置 → Java 虚拟机设置”中移除所有虚拟机参数后，尝试再次启动游戏。\nlaunch.failed.creating_process=启动失败。在创建新进程时发生错误，可能是 Java 路径错误。\nlaunch.failed.command_too_long=命令长度超过限制，无法创建批处理脚本。请导出为 PowerShell 脚本。\nlaunch.failed.decompressing_natives=未能解压游戏本地库。\nlaunch.failed.download_library=未能下载游戏依赖“%s”\nlaunch.failed.executable_permission=未能为启动文件添加执行权限。\nlaunch.failed.execution_policy=设置执行策略\nlaunch.failed.execution_policy.failed_to_set=设置执行策略失败\nlaunch.failed.execution_policy.hint=当前执行策略阻止你执行 PowerShell 脚本。\\n点击“确定”允许当前用户执行本地 PowerShell 脚本，或点击“取消”保持现状。\nlaunch.failed.exited_abnormally=游戏非正常退出。请查看日志文件，或联系他人寻求帮助。\nlaunch.failed.java_version_too_low=你所指定的 Java 版本过低。请重新设置 Java 版本。\nlaunch.failed.no_accepted_java=找不到适合当前游戏使用的 Java。是否使用默认 Java 启动游戏？点击“是”使用默认 Java 继续启动游戏，\\n或者在“(全局/实例特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 版本。\\n你可以点击右上角帮助按钮进行求助。\nlaunch.failed.sigkill=游戏被用户或系统强制终止。\nlaunch.state.dependencies=处理游戏依赖\nlaunch.state.done=启动完成\nlaunch.state.java=检测 Java 版本\nlaunch.state.logging_in=登录\nlaunch.state.modpack=下载必要文件\nlaunch.state.waiting_launching=等待游戏启动\nlaunch.invalid_java=当前设置的 Java 路径无效，请重新设置 Java 路径。\n\nlauncher=启动器\nlauncher.agreement=用户协议与免责声明\nlauncher.agreement.accept=同意\nlauncher.agreement.decline=拒绝\nlauncher.agreement.hint=同意本软件的用户协议与免责声明以使用本软件。\nlauncher.april_fools.switch_lzh=HMCL 启动器现已支持文言文，我们诚邀你参与体验。是否要将界面语言切换至中文 (文言)？\\n在切换语言后，你可以在“置設 → 貫用 → 文”中将语言切换回中文 (简体)。\nlauncher.april_fools.switch_lzh.confirm=确认切换语言？\\n点击“确定”后 HMCL 将自动重启并将界面语言切换至中文 (文言)；点击“取消”将继续使用中文 (简体) 进入 HMCL 主页。\nlauncher.background=背景图片\nlauncher.background.choose=选择背景图片\nlauncher.background.classic=经典\nlauncher.background.default=默认\nlauncher.background.default.tooltip=自动检索启动器同文件夹下的“background.png/.jpg/.gif/.webp”及“bg”文件夹内的图片\nlauncher.background.network=网络\nlauncher.background.paint=纯色\nlauncher.cache_directory=文件下载缓存文件夹\nlauncher.cache_directory.clean=清理缓存\nlauncher.cache_directory.choose=选择文件下载缓存文件夹\nlauncher.cache_directory.default=默认 (\"%APPDATA%/.minecraft\" 或 \"~/.minecraft\")\nlauncher.cache_directory.disabled=禁用 (总是使用游戏文件夹路径)\nlauncher.cache_directory.invalid=无法创建自定义的缓存文件夹。已经恢复到默认设置。\nlauncher.contact=联系我们\nlauncher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误。请复制下列内容并点击右下角的按钮反馈问题。\nlauncher.crash.java_internal_error=Hello Minecraft! Launcher 由于当前 Java 损坏而无法继续运行。请卸载当前 Java，点击 <a href=\"https://bell-sw.com/pages/downloads/#downloads\">此处</a> 安装合适的 Java 版本。\nlauncher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误。已检测到你的启动器不是最新版本，请更新后再试。\nlauncher.update_java=请更新你的 Java。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n\nlibraries.download=下载依赖库\n\nlogin.empty_username=你还未设置用户名！\nlogin.enter_password=请输入你的密码\n\nlogwindow.show_lines=显示行数\nlogwindow.terminate_game=结束游戏进程\nlogwindow.title=日志\nlogwindow.help=你可以前往 HMCL 社区，寻找他人帮助\nlogwindow.autoscroll=自动滚动\nlogwindow.export_game_crash_logs=导出游戏崩溃信息\nlogwindow.export_dump=导出游戏运行栈\nlogwindow.export_dump.no_dependency=你的 Java 不包含用于创建游戏运行栈的依赖。请前往 HMCL QQ 群或 Discord 寻求帮助。\n\nmain_page=主页\n\nmessage.cancelled=操作被取消\nmessage.confirm=提示\nmessage.copied=已复制到剪贴板\nmessage.default=默认\nmessage.doing=请耐心等待\nmessage.downloading=正在下载\nmessage.error=错误\nmessage.failed=操作失败\nmessage.info=提示\nmessage.success=完成\nmessage.unknown=未知\nmessage.warning=警告\nmessage.question=确认\n\nmodpack=整合包\nmodpack.choose=选择要安装的游戏整合包文件\nmodpack.choose.local=导入本地整合包文件\nmodpack.choose.local.detail=你可以直接将整合包文件拖入本页面以安装\nmodpack.choose.remote=从互联网下载整合包\nmodpack.choose.remote.detail=需要提供整合包的下载链接\nmodpack.choose.repository=从 CurseForge/Modrinth 下载整合包\nmodpack.choose.repository.detail=可直接在跳转到的页面安装整合包\nmodpack.choose.remote.tooltip=要下载的整合包的链接\nmodpack.completion=下载整合包相关文件\nmodpack.desc=描述你要制作的整合包，比如整合包注意事项和更新记录。支持 Markdown (图片请用网络链接)。\nmodpack.description=整合包描述\nmodpack.download=下载整合包\nmodpack.download.title=整合包下载 - %1s\nmodpack.enter_name=给游戏起个你喜欢的名字\nmodpack.export=导出整合包\nmodpack.export.as=请选择整合包类型\nmodpack.file_api=整合包下载链接前缀\nmodpack.files.blueprints=BuildCraft 蓝图\nmodpack.files.config=模组配置文件\nmodpack.files.dumps=NEI 调试输出文件\nmodpack.files.hmclversion_cfg=启动器配置文件\nmodpack.files.liteconfig=LiteLoader 相关文件\nmodpack.files.mods=模组\nmodpack.files.mods.voxelmods=VoxelMods 配置，如小地图\nmodpack.files.options_txt=游戏设置\nmodpack.files.optionsshaders_txt=光影设置\nmodpack.files.resourcepacks=资源包 (纹理包)\nmodpack.files.saves=游戏世界\nmodpack.files.scripts=MineTweaker 配置\nmodpack.files.servers_dat=多人游戏服务器列表\nmodpack.installing=安装整合包\nmodpack.installing.given=安装 %s 整合包\nmodpack.introduction=支持 Curse、Modrinth、MultiMC、MCBBS 整合包。\nmodpack.invalid=无效的整合包升级文件。可能是下载时出现问题。\nmodpack.mismatched_type=整合包类型不匹配。当前游戏为“%s”整合包，但是提供的整合包更新文件为“%s”整合包。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\nmodpack.name=整合包名称\nmodpack.not_a_valid_name=不是一个有效的整合包名称\nmodpack.origin=来源\nmodpack.origin.url=官方网站\nmodpack.origin.mcbbs=MCBBS\nmodpack.origin.mcbbs.prompt=帖子 ID\nmodpack.scan=解析整合包\nmodpack.task.install=导入整合包\nmodpack.task.install.error=无法识别该整合包，目前仅支持导入 Curse、Modrinth、MultiMC、MCBBS 整合包。\\n你可以点击右上角帮助按钮进行求助。\nmodpack.type.curse=Curse\nmodpack.type.curse.error=未能完成该整合包所需的依赖下载，请再次尝试或设置代理。\\n你可以点击右上角帮助按钮进行求助。\nmodpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载。请尝试该整合包的最新版本或者安装其他整合包。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\nmodpack.type.manual.warning=该整合包由发布者手动打包，其中可能已经包含启动器。建议尝试解压后使用其自带的启动器运行游戏。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\\nHMCL 可以尝试导入该整合包，但不保证可用性。是否继续？\nmodpack.type.mcbbs=MCBBS 整合包 (推荐)\nmodpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher 导入\nmodpack.type.modrinth=Modrinth\nmodpack.type.modrinth.export=可以被主流第三方启动器导入\nmodpack.type.multimc=MultiMC\nmodpack.type.multimc.export=可以被 Hello Minecraft! Launcher 和 MultiMC 导入\nmodpack.type.server=服务器自动更新整合包\nmodpack.type.server.export=允许服务器管理员远程更新游戏客户端\nmodpack.type.server.malformed=服务器整合包配置格式错误，请联系服务器管理员解决此问题。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\nmodpack.unsupported=Hello Minecraft! Launcher 不支持该整合包格式\nmodpack.update=正在升级整合包\nmodpack.wizard=导出整合包向导\nmodpack.wizard.step.1=基本设置\nmodpack.wizard.step.1.title=设置整合包的主要信息\nmodpack.wizard.step.2=文件选择\nmodpack.wizard.step.2.title=选中你想添加到整合包中的文件或文件夹\nmodpack.wizard.step.3=整合包类型\nmodpack.wizard.step.3.title=选择整合包导出类型\nmodpack.wizard.step.initialization.exported_version=要导出的游戏实例\nmodpack.wizard.step.initialization.force_update=强制升级整合包至最新版本 (需要自建服务器)\nmodpack.wizard.step.initialization.include_launcher=包含启动器\nmodpack.wizard.step.initialization.modrinth.info=在整合包创建过程中，启动器将匹配 CurseForge/Modrinth 远程资源替代本地文件 (包括模组、资源包和光影包) 以缩减整合包体积，并将扩展名为“.disabled”的文件标注为“安装时可选项”。\nmodpack.wizard.step.initialization.no_create_remote_files=不匹配远程文件\nmodpack.wizard.step.initialization.save=选择要导出到的游戏整合包位置\nmodpack.wizard.step.initialization.skip_curseforge_remote_files=不匹配 CurseForge 远程资源\nmodpack.wizard.step.initialization.warning=在制作整合包前，请你确认你选择的实例可以正常启动，\\n并保证你的 Minecraft 是正式版而非快照，\\n而且不应当将不允许非官方途径传播的模组、资源 (纹理) 包等纳入整合包。\\n整合包会保存你目前的下载源设置。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\nmodpack.wizard.step.initialization.server=点击此处查看有关服务器自动更新整合包的制作教程\n\nmodrinth.category.adventure=冒险\nmodrinth.category.atmosphere=氛围\nmodrinth.category.audio=声音\nmodrinth.category.babric=Babric\nmodrinth.category.blocks=方块\nmodrinth.category.bloom=泛光\nmodrinth.category.bta-babric=BTA (Babric)\nmodrinth.category.bukkit=Bukkit\nmodrinth.category.bungeecord=BungeeCord\nmodrinth.category.canvas=Canvas\nmodrinth.category.cartoon=卡通\nmodrinth.category.challenging=高难度\nmodrinth.category.colored-lighting=彩色光照\nmodrinth.category.combat=战斗\nmodrinth.category.core-shaders=核心着色器\nmodrinth.category.cursed=Cursed\nmodrinth.category.datapack=数据包\nmodrinth.category.decoration=装饰\nmodrinth.category.economy=经济\nmodrinth.category.entities=实体\nmodrinth.category.environment=环境\nmodrinth.category.equipment=装备\nmodrinth.category.fabric=Fabric\nmodrinth.category.fantasy=幻想\nmodrinth.category.folia=Folia\nmodrinth.category.foliage=植被\nmodrinth.category.fonts=字体\nmodrinth.category.food=食物\nmodrinth.category.forge=Forge\nmodrinth.category.game-mechanics=游戏机制\nmodrinth.category.gui=GUI\nmodrinth.category.high=高\nmodrinth.category.iris=Iris\nmodrinth.category.items=物品\nmodrinth.category.java-agent=Java Agent\nmodrinth.category.kitchen-sink=大杂烩\nmodrinth.category.legacy-fabric=Legacy Fabric\nmodrinth.category.library=支持库\nmodrinth.category.lightweight=轻量\nmodrinth.category.liteloader=LiteLoader\nmodrinth.category.locale=本地化\nmodrinth.category.low=低\nmodrinth.category.magic=魔法\nmodrinth.category.management=管理\nmodrinth.category.medium=中\nmodrinth.category.minecraft=Minecraft\nmodrinth.category.minigame=小游戏\nmodrinth.category.misc=其他\nmodrinth.category.mobs=生物\nmodrinth.category.modded=Modded\nmodrinth.category.models=模型\nmodrinth.category.modloader=Modloader\nmodrinth.category.multiplayer=多人\nmodrinth.category.neoforge=NeoForge\nmodrinth.category.nilloader=NilLoader\nmodrinth.category.optifine=OptiFine\nmodrinth.category.optimization=优化\nmodrinth.category.ornithe=Ornithe\nmodrinth.category.paper=Paper\nmodrinth.category.path-tracing=路径追踪\nmodrinth.category.pbr=PBR\nmodrinth.category.potato=极低\nmodrinth.category.purpur=Purpur\nmodrinth.category.quests=任务\nmodrinth.category.quilt=Quilt\nmodrinth.category.realistic=写实\nmodrinth.category.reflections=反射\nmodrinth.category.rift=Rift\nmodrinth.category.screenshot=极高\nmodrinth.category.semi-realistic=半写实\nmodrinth.category.shadows=阴影\nmodrinth.category.simplistic=简单\nmodrinth.category.social=社交\nmodrinth.category.spigot=Spigot\nmodrinth.category.sponge=Sponge\nmodrinth.category.storage=存储\nmodrinth.category.technology=科技\nmodrinth.category.themed=主题\nmodrinth.category.transportation=运输\nmodrinth.category.tweaks=优化\nmodrinth.category.utility=实用\nmodrinth.category.vanilla=原生\nmodrinth.category.vanilla-like=类原生\nmodrinth.category.velocity=Velocity\nmodrinth.category.waterfall=Waterfall\nmodrinth.category.worldgen=世界生成\n\nmods=模组\nmods.add=添加模组\nmods.add.failed=添加模组“%s”失败。\\n如遇到问题，你可以点击右上角帮助按钮进行求助。\nmods.add.success=成功添加模组 %s。\nmods.add.title=选择要添加的模组文件\nmods.broken_dependency.title=损坏的前置模组\nmods.broken_dependency.desc=该前置模组曾经在该模组仓库上存在过，但现在被删除了。换个下载源试试吧。\nmods.category=类别\nmods.channel.alpha=快照版本\nmods.channel.beta=测试版本\nmods.channel.release=稳定版本\nmods.check_updates=模组更新检查\nmods.check_updates.button=检查更新\nmods.check_updates.confirm=更新\nmods.check_updates.current_version=当前版本\nmods.check_updates.empty=没有需要更新的模组\nmods.check_updates.failed_check=检查更新失败\nmods.check_updates.failed_download=部分文件下载失败\nmods.check_updates.file=文件\nmods.check_updates.source=来源\nmods.check_updates.target_version=目标版本\nmods.curseforge=CurseForge\nmods.dependency.embedded=内置的前置模组 (已经由作者打包在模组文件中，无需另外下载)\nmods.dependency.optional=可选的前置模组 (若缺失游戏能够正常运行，但模组功能可能缺失)\nmods.dependency.required=必须的前置模组 (必须另外下载，缺失可能会导致游戏无法启动)\nmods.dependency.tool=前置库 (必须另外下载，缺失可能会导致游戏无法启动)\nmods.dependency.include=内置的前置模组 (已经由作者打包在模组文件中，无需另外下载)\nmods.dependency.incompatible=不兼容的模组 (同时安装该模组和正在下载的模组会导致游戏无法启动)\nmods.dependency.broken=损坏的前置模组 (该前置模组曾经在该模组仓库上存在过，但现在被删除了，换个下载源试试吧)\nmods.disable=禁用\nmods.download=模组下载\nmods.download.title=模组下载 - %1s\nmods.download.recommend=推荐版本 - Minecraft %1s\nmods.enable=启用\nmods.game.version=游戏版本\nmods.manage=模组管理\nmods.mcbbs=MCBBS\nmods.mcmod=MC 百科\nmods.mcmod.page=MC 百科页面\nmods.mcmod.search=MC 百科搜索\nmods.modrinth=Modrinth\nmods.name=名称\nmods.not_modded=你需要先在“自动安装”页面安装 Forge、NeoForge、Fabric、Legacy Fabric、Quilt 或 LiteLoader 才能管理模组。\nmods.restore=回退\nmods.url=官方页面\nmods.update_modpack_mod.warning=更新整合包中的模组可能导致整合包损坏，使整合包无法正常启动。该操作不可逆，确定要更新吗？\nmods.warning.loader_mismatch=模组加载器不匹配\nmods.install=安装到当前实例\nmods.save_as=下载到本地文件夹\nmods.unknown=未知模组\n\nmenu.undo=撤销\nmenu.redo=重做\nmenu.cut=剪切\nmenu.copy=复制\nmenu.paste=粘贴\nmenu.deleteselection=删除\nmenu.selectall=全选\n\nnbt.entries=%s 个条目\nnbt.open.failed=打开文件失败\nnbt.save.failed=保存文件失败\nnbt.title=查看文件 - %s\n\ndatapack=数据包\ndatapack.add=添加数据包\ndatapack.add.title=选择要添加的数据包压缩包\ndatapack.reload.toast=Minecraft 正在运行，请使用 /reload 命令重新加载数据包\ndatapack.title=世界 [%s] - 数据包\n\nweb.failed=加载页面失败\nweb.open_in_browser=是否要在浏览器中打开此链接：\\n%s\nweb.view_in_browser=在浏览器中查看完整日志\n\nworld=世界\nworld.add=添加世界\nworld.add.already_exists=此世界已经存在\nworld.add.failed=无法添加此世界：%s\nworld.add.invalid=无法识别该世界压缩包\nworld.add.title=选择要添加的世界压缩包\nworld.backup=备份管理\nworld.backup.create.new_one=创建新备份\nworld.backup.create.failed=创建备份失败。\\n%s\nworld.backup.create.success=成功创建新备份：%s\nworld.backup.delete=删除此备份\nworld.backup.processing=正在备份中……\nworld.chunkbase=世界地图\nworld.chunkbase.end_city=末地城地图\nworld.chunkbase.seed_map=种子地图\nworld.chunkbase.stronghold=要塞地图\nworld.chunkbase.nether_fortress=下界要塞地图\nworld.duplicate=复制此世界\nworld.duplicate.prompt=输入复制后的世界名称\nworld.duplicate.failed.already_exists=文件夹已存在\nworld.duplicate.failed.empty_name=名称不能为空\nworld.duplicate.failed.invalid_name=名称中包含非法字符\nworld.duplicate.failed=复制世界失败\nworld.duplicate.success.toast=复制世界成功\nworld.datapack=数据包管理\nworld.datetime=上一次游戏时间: %s\nworld.delete=删除此世界\nworld.delete.failed=删除世界失败。\\n%s\nworld.download=下载世界\nworld.download.title=世界下载 - %1s\nworld.export=导出此世界\nworld.export.title=选择该世界的存储位置\nworld.export.location=保存到\nworld.export.wizard=导出世界“%s”\nworld.game_version=游戏版本\nworld.icon=世界图标\nworld.icon.change=修改世界图标\nworld.icon.change.fail.load.title=图片解析出错\nworld.icon.change.fail.load.text=该图片似乎已损坏，HMCL 无法解析它\nworld.icon.change.fail.not_64x64.title=图片大小错误\nworld.icon.change.fail.not_64x64.text=该图片的分辨率为 %d×%d，而不是 64×64，请提供一张 64×64 分辨率的图片再次尝试\nworld.icon.change.succeed.toast=世界图标修改成功\nworld.icon.change.tip=请提供一张 64×64 PNG 格式的图片。错误分辨率的图片将无法被 Minecraft 解析。\nworld.icon.choose.title=选择世界图标\nworld.info=世界信息\nworld.info.basic=基本信息\nworld.info.allow_cheats=允许命令(作弊)\nworld.info.dimension.the_nether=下界\nworld.info.dimension.the_end=末地\nworld.info.difficulty=难度\nworld.info.difficulty.peaceful=和平\nworld.info.difficulty.easy=简单\nworld.info.difficulty.normal=普通\nworld.info.difficulty.hard=困难\nworld.info.difficulty_lock=锁定难度\nworld.info.failed=读取世界信息失败\nworld.info.game_version=游戏版本\nworld.info.last_played=上一次游戏时间\nworld.info.generate_features=生成建筑\nworld.info.player=玩家信息\nworld.info.player.food_level=饥饿值\nworld.info.player.food_saturation_level=饱和度\nworld.info.player.game_type=游戏模式\nworld.info.player.game_type.adventure=冒险\nworld.info.player.game_type.creative=创造\nworld.info.player.game_type.hardcore=极限\nworld.info.player.game_type.spectator=旁观者\nworld.info.player.game_type.survival=生存\nworld.info.player.health=生命值\nworld.info.player.last_death_location=上次死亡位置\nworld.info.player.location=位置\nworld.info.player.spawn=床/重生锚位置\nworld.info.player.xp_level=经验等级\nworld.info.random_seed=种子\nworld.info.spawn=世界出生点\nworld.info.time=游戏时长\nworld.info.time.format=%d 天 %d 小时 %d 分钟\nworld.load.fail=世界加载失败\nworld.locked=使用中\nworld.locked.failed=该世界正在使用中，请关闭游戏后重试。\nworld.manage=世界管理\nworld.manage.button=世界管理\nworld.manage.title=世界管理 - %s\nworld.name=世界名称\nworld.name.enter=输入世界名称\nworld.show_all=显示全部\n\nprofile=游戏文件夹\nprofile.already_exists=该名称已存在\nprofile.default=当前文件夹\nprofile.home=官方启动器文件夹\nprofile.instance_directory=游戏文件夹路径\nprofile.instance_directory.choose=选择游戏文件夹路径\nprofile.manage=游戏文件夹列表\nprofile.name=名称\nprofile.new=添加游戏文件夹\nprofile.title=游戏文件夹\nprofile.selected=已选中\nprofile.use_relative_path=若可能，游戏文件夹使用相对路径\n\nrepositories.custom=自定义 Maven 仓库 (%s)\nrepositories.maven_central=全球 (Maven Central)\nrepositories.tencentcloud_mirror=中国大陆 (腾讯云镜像源 Maven 仓库)\nrepositories.chooser=缺少 JavaFX 运行环境。HMCL 需要 JavaFX 才能正常运行。\\n点击“确认”从指定源下载 JavaFX 运行时组件并启动 HMCL。点击“取消”退出程序。\\n选择下载源：\nrepositories.chooser.title=选择 JavaFX 下载源\n\nresourcepack=资源包\nresourcepack.add=添加资源包\nresourcepack.add.title=选择要添加的资源包压缩包\nresourcepack.manage=资源包管理\nresourcepack.download=下载资源包\nresourcepack.add.failed=添加资源包失败\nresourcepack.delete.failed=删除资源包失败\nresourcepack.download.title=资源包下载 - %1s\n\nreveal.in_file_manager=在文件管理器中查看\n\nschematics=原理图\nschematics.add=添加原理图\nschematics.add.failed=添加原理图失败\nschematics.add.title=选择要添加的原理图文件\nschematics.back_to=返回到“%s”\nschematics.create_directory=创建文件夹\nschematics.create_directory.prompt=请输入新文件夹名称\nschematics.create_directory.failed=创建文件夹失败\nschematics.create_directory.failed.already_exists=文件夹已存在\nschematics.create_directory.failed.empty_name=名称不能为空\nschematics.create_directory.failed.invalid_name=名称中包含非法字符\nschematics.info.description=描述\nschematics.info.enclosing_size=包围盒尺寸\nschematics.info.name=名称\nschematics.info.region_count=区域数量\nschematics.info.schematic_author=作者\nschematics.info.time_created=创建时间\nschematics.info.time_modified=修改时间\nschematics.info.total_blocks=总方块数\nschematics.info.total_volume=总体积\nschematics.info.version=原理图版本\nschematics.manage=原理图管理\nschematics.sub_items=%d 个子项\n\nsearch=搜索\nsearch.hint.chinese=支持中英文搜索\nsearch.hint.english=仅支持英文搜索\nsearch.enter=可在此处输入\nsearch.sort=排序\nsearch.first_page=第一页\nsearch.previous_page=上一页\nsearch.next_page=下一页\nsearch.last_page=最后一页\nsearch.page_n=%d / %s\n\nselector.choose=选择\nselector.choose_file=选择文件\nselector.custom=自定义\n\nsettings=设置\n\nsettings.advanced=高级设置\nsettings.advanced.modify=编辑高级设置\nsettings.advanced.title=高级设置 - %s\nsettings.advanced.custom_commands=自定义命令\nsettings.advanced.custom_commands.hint=自定义命令被调用时将包含如下的环境变量：\\n\\\n  \\  · $INST_NAME: 实例名称；\\n\\\n  \\  · $INST_ID: 实例名称；\\n\\\n  \\  · $INST_DIR: 当前实例运行路径；\\n\\\n  \\  · $INST_MC_DIR: 当前游戏文件夹路径；\\n\\\n  \\  · $INST_JAVA: 游戏运行使用的 Java 路径；\\n\\\n  \\  · $INST_FORGE: 若安装了 Forge，将会存在本环境变量；\\n\\\n  \\  · $INST_NEOFORGE: 若安装了 NeoForge，将会存在本环境变量；\\n\\\n  \\  · $INST_CLEANROOM: 若安装了 Cleanroom，将会存在本环境变量；\\n\\\n  \\  · $INST_LITELOADER: 若安装了 LiteLoader，将会存在本环境变量；\\n\\\n  \\  · $INST_OPTIFINE: 若安装了 OptiFine，将会存在本环境变量；\\n\\\n  \\  · $INST_FABRIC: 若安装了 Fabric，将会存在本环境变量；\\n\\\n  \\  · $INST_LEGACYFABRIC: 若安装了 Legacy Fabric，将会存在本环境变量；\\n\\\n  \\  · $INST_QUILT: 若安装了 Quilt，将会存在本环境变量。\nsettings.advanced.dont_check_game_completeness=不检查游戏完整性\nsettings.advanced.dont_check_jvm_validity=不检查 Java 虚拟机与游戏的兼容性\nsettings.advanced.dont_patch_natives=不尝试自动替换本地库\nsettings.advanced.environment_variables=环境变量\nsettings.advanced.game_dir.default=默认 (\".minecraft/\")\nsettings.advanced.game_dir.independent=各实例独立 (存放在 \".minecraft/versions/<实例名>/\"，除 assets、libraries 外)\nsettings.advanced.java_permanent_generation_space=内存永久保存区域\nsettings.advanced.java_permanent_generation_space.prompt=单位 MiB\nsettings.advanced.jvm=Java 虚拟机设置\nsettings.advanced.jvm_args=Java 虚拟机参数\nsettings.advanced.jvm_args.prompt=\\  · 若在“Java 虚拟机参数”输入的参数与默认参数相同，则不会添加；\\n\\\n  \\  · 若在“Java 虚拟机参数”输入任何 GC 参数，默认参数的 G1 参数会被禁用；\\n\\\n  \\  · 开启下方“不添加默认的 Java 虚拟机参数”可在启动游戏时不添加默认参数。\nsettings.advanced.launcher_visibility.close=游戏启动后结束启动器\nsettings.advanced.launcher_visibility.hide=游戏启动后隐藏启动器\nsettings.advanced.launcher_visibility.hide_and_reopen=隐藏启动器并在游戏结束后重新打开\nsettings.advanced.launcher_visibility.keep=保持启动器可见\nsettings.advanced.launcher_visible=启动器可见性\nsettings.advanced.minecraft_arguments=游戏参数\nsettings.advanced.minecraft_arguments.prompt=默认\nsettings.advanced.natives_directory=本地库路径 (LWJGL)\nsettings.advanced.natives_directory.choose=选择本地库路径\nsettings.advanced.natives_directory.custom=自定义 (由你提供游戏需要的本地库)\nsettings.advanced.natives_directory.default=默认 (由启动器提供游戏本地库)\nsettings.advanced.natives_directory.default.version_id=<实例名称>\nsettings.advanced.natives_directory.hint=本选项提供给 Apple Silicon 等未受游戏官方支持的平台来自定义游戏本地库。如果你不知道本选项的含义，请不要修改本选项，否则会导致游戏无法启动！\\n\\n如果你要修改本选项，你需要保证自定义文件夹下有游戏所需的本地库文件，如 lwjgl.dll (liblwjgl.so)、openal.dll (libopenal.so) 等文件。启动器不会帮你补全缺少的本地库文件！\\n\\n注意：指定的本地库文件路径建议只包含英文大小写字母、数字和下划线，否则可能会导致启动游戏失败。\nsettings.advanced.no_jvm_args=不添加默认的 Java 虚拟机参数\nsettings.advanced.no_optimizing_jvm_args=不自动添加 Java 虚拟机优化参数\nsettings.advanced.precall_command=游戏启动前执行命令\nsettings.advanced.precall_command.prompt=将在游戏启动前调用\nsettings.advanced.process_priority=进程优先级\nsettings.advanced.process_priority.low=低\nsettings.advanced.process_priority.low.desc=节省游戏占用资源，可能会造成游戏卡顿\nsettings.advanced.process_priority.below_normal=较低\nsettings.advanced.process_priority.below_normal.desc=节省游戏占用资源，可能会造成游戏卡顿\nsettings.advanced.process_priority.normal=中\nsettings.advanced.process_priority.normal.desc=平衡\nsettings.advanced.process_priority.above_normal=较高\nsettings.advanced.process_priority.above_normal.desc=优先保证游戏运行，但可能会导致其他程序卡顿\nsettings.advanced.process_priority.high=高\nsettings.advanced.process_priority.high.desc=优先保证游戏运行，但可能会导致其他程序卡顿\nsettings.advanced.post_exit_command=游戏结束后执行命令\nsettings.advanced.post_exit_command.prompt=将在游戏结束后调用\nsettings.advanced.renderer=渲染器\nsettings.advanced.renderer.default=默认\nsettings.advanced.renderer.default.desc=OpenGL\nsettings.advanced.renderer.d3d12=Mesa D3D12\nsettings.advanced.renderer.d3d12.desc=DirectX 12 (性能与兼容性较差，用于调试)\nsettings.advanced.renderer.llvmpipe=Mesa LLVMpipe\nsettings.advanced.renderer.llvmpipe.desc=软渲染器 (性能较差，兼容性最好)\nsettings.advanced.renderer.zink=Mesa Zink\nsettings.advanced.renderer.zink.desc=Vulkan (性能最好，兼容性较差)\nsettings.advanced.server_ip=服务器地址\nsettings.advanced.server_ip.prompt=默认，启动游戏后可以直接进入对应服务器\nsettings.advanced.unsupported_system_options=不适用于当前系统的选项\nsettings.advanced.use_native_glfw=[仅 Linux/FreeBSD] 使用系统 GLFW\nsettings.advanced.use_native_openal=[仅 Linux/FreeBSD] 使用系统 OpenAL\nsettings.advanced.workaround=调试选项\nsettings.advanced.workaround.warning=调试选项仅提供给专业玩家使用。调试选项可能会导致游戏无法启动。除非你知道你在做什么，否则请不要修改这些选项！\nsettings.advanced.wrapper_launcher=包装命令\nsettings.advanced.wrapper_launcher.prompt=如填写“optirun”后，启动命令将从“java ...”变为“optirun java ...”\n\nsettings.custom=自定义\n\nsettings.game=游戏设置\nsettings.game.copy_global=复制全局游戏设置\nsettings.game.copy_global.copy_all=复制全部\nsettings.game.copy_global.copy_all.confirm=你确定要覆盖当前实例特定游戏设置吗？此操作无法撤销！\nsettings.game.current=游戏\nsettings.game.dimension=游戏窗口分辨率\nsettings.game.exploration=浏览\nsettings.game.fullscreen=全屏\nsettings.game.java_directory=游戏 Java\nsettings.game.java_directory.auto=自动选择合适的 Java\nsettings.game.java_directory.auto.not_found=没有合适的 Java\nsettings.game.java_directory.bit=%s 位\nsettings.game.java_directory.choose=选择 Java\nsettings.game.java_directory.invalid=Java 路径不正确\nsettings.game.java_directory.version=指定 Java 版本\nsettings.game.java_directory.template=%s (%s)\nsettings.game.management=管理\nsettings.game.working_directory=版本隔离 (建议使用模组时选择“各实例独立”。改后需移动世界、模组等相关游戏文件)\nsettings.game.working_directory.choose=选择运行文件夹\nsettings.game.working_directory.hint=在“版本隔离”中选择“各实例独立”使当前实例独立存放设置、世界、模组等数据。使用模组时建议启用此选项以避免不同实例模组冲突。修改此选项后需自行移动世界等文件。\n\nsettings.icon=游戏图标\n\nsettings.launcher=启动器设置\nsettings.launcher.appearance=外观\nsettings.launcher.brightness=主题模式\nsettings.launcher.brightness.auto=跟随系统设置\nsettings.launcher.brightness.dark=深色模式\nsettings.launcher.brightness.light=浅色模式\nsettings.launcher.common_path.tooltip=启动器将所有游戏资源及依赖库文件存放于此集中管理。如果游戏文件夹内有现成的将不会使用公共库文件。\nsettings.launcher.debug=调试\nsettings.launcher.disable_april_fools=不启用愚人节功能\nsettings.launcher.disable_auto_game_options=不自动切换游戏语言\nsettings.launcher.download=下载\nsettings.launcher.download.threads=线程数\nsettings.launcher.download.threads.auto=自动选择线程数\nsettings.launcher.download.threads.hint=线程数过高可能导致系统卡顿。你的下载速度会受到互联网运营商、下载源服务器等方面的影响。调高下载线程数不一定能大幅提升总下载速度。\nsettings.launcher.download_source=下载源\nsettings.launcher.download_source.auto=自动选择下载源\nsettings.launcher.enable_game_list=在主页内显示版本列表\nsettings.launcher.font=字体\nsettings.launcher.font.anti_aliasing=抗锯齿\nsettings.launcher.font.anti_aliasing.auto=自动\nsettings.launcher.font.anti_aliasing.gray=灰度\nsettings.launcher.font.anti_aliasing.lcd=子像素\nsettings.launcher.general=通用\nsettings.launcher.language=语言\nsettings.launcher.launcher_log.export=导出启动器日志\nsettings.launcher.launcher_log.export.failed=无法导出日志\nsettings.launcher.launcher_log.export.success=日志已保存到“%s”\nsettings.launcher.launcher_log.reveal=打开日志文件夹\nsettings.launcher.log=日志\nsettings.launcher.log.font=日志字体\nsettings.launcher.proxy=代理\nsettings.launcher.proxy.authentication=身份验证\nsettings.launcher.proxy.default=使用系统代理\nsettings.launcher.proxy.host=主机\nsettings.launcher.proxy.http=HTTP\nsettings.launcher.proxy.none=不使用代理\nsettings.launcher.proxy.password=密码\nsettings.launcher.proxy.port=端口\nsettings.launcher.proxy.socks=SOCKS\nsettings.launcher.proxy.username=账户\nsettings.launcher.theme=主题色\nsettings.launcher.title_transparent=标题栏透明\nsettings.launcher.turn_off_animations=关闭动画\nsettings.launcher.version_list_source=版本列表源\nsettings.launcher.background.settings.opacity=不透明度\n\nsettings.memory=游戏内存\nsettings.memory.allocate.auto=最低分配 %1$.1f GiB / 实际分配 %2$.1f GiB\nsettings.memory.allocate.auto.exceeded=最低分配 %1$.1f GiB / 实际分配 %2$.1f GiB (%3$.1f GiB 可用)\nsettings.memory.allocate.manual=游戏分配 %1$.1f GiB\nsettings.memory.allocate.manual.exceeded=游戏分配 %1$.1f GiB (设备仅 %3$.1f GiB 可用)\nsettings.memory.auto_allocate=自动分配内存\nsettings.memory.lower_bound=最低内存分配\nsettings.memory.unit.mib=MiB\nsettings.memory.used_per_total=已使用 %1$.1f GiB / 总内存 %2$.1f GiB\nsettings.physical_memory=物理内存大小\nsettings.show_log=查看日志\nsettings.enable_debug_log_output=输出调试日志\nsettings.tabs.installers=自动安装\nsettings.take_effect_after_restart=重启后生效\nsettings.type=实例游戏设置类型\nsettings.type.global=全局游戏设置 (未启用“实例特定游戏设置”的实例共用此设置)\nsettings.type.global.manage=全局游戏设置\nsettings.type.global.edit=编辑全局游戏设置\nsettings.type.special.enable=启用实例特定游戏设置 (不影响其他游戏实例)\nsettings.type.special.edit=编辑实例特定游戏设置\nsettings.type.special.edit.hint=当前游戏实例“%s”启用了“实例特定游戏设置”，因此本页面选项不对该实例生效。点击链接前往该实例的“游戏设置”页。\n\nshaderpack.download.title=光影下载 - %1s\n\nsponsor=赞助\nsponsor.bmclapi=国内下载源由 BMCLAPI 提供高速下载服务。BMCLAPI 为公益服务，赞助 BMCLAPI 可以帮助作者更好地提供稳定高速的下载服务。[点击此处查阅详细信息]\nsponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。[点击此处查阅详细信息]\n\nsystem.architecture=架构\nsystem.operating_system=操作系统\n\nterracotta=多人联机\nterracotta.terracotta=Terracotta | 陶瓦联机\nterracotta.status=联机大厅\nterracotta.back=退出\nterracotta.feedback.title=填写反馈表\nterracotta.feedback.desc=在 HMCL 更新联机核心时，我们欢迎您用 10 秒时间填写联机质量反馈收集表。\nterracotta.sudo_installing=HMCL 需要验证您的密码才能安装联机核心\nterracotta.difficulty.easiest=当前网络状态极好：稍等一下就成功！\nterracotta.difficulty.simple=当前网络状态较好：建立连接需要一段时间……\nterracotta.difficulty.medium=当前网络状态中等：已启用抗干扰备用线路，连接可能失败\nterracotta.difficulty.tough=当前网络状态极差：已启用抗干扰备用线路，连接可能失败\nterracotta.difficulty.estimate_only=连接成功率由房主和房客的 NAT 类型推算得到，仅供参考。\nterracotta.from_local.title=联机核心第三方下载渠道\nterracotta.from_local.desc=在部分地区，HMCL 内置的默认下载渠道可能不稳定或连接缓慢\nterracotta.from_local.guide=您应当下载名为 %s 的联机核心包。下载完成后，请将文件拖入当前界面来安装。\nterracotta.from_local.file_name_mismatch=您应当下载名为 %1$s 的联机核心包，而非 %2$s\nterracotta.export_log=导出联机核心日志\nterracotta.export_log.desc=为分析错误提供更多信息\nterracotta.status.bootstrap=正在收集信息\nterracotta.status.uninitialized.not_exist=未下载联机核心\nterracotta.status.uninitialized.not_exist.title=下载联机核心 (约 8MiB)\nterracotta.status.uninitialized.update=需更新联机核心\nterracotta.status.uninitialized.update.title=更新联机核心 (约 8MiB)\nterracotta.status.uninitialized.desc=您承诺，在多人联机全过程中，您将严格遵守您所在国家或地区的全部法律法规\nterracotta.confirm.title=用户须知\nterracotta.confirm.desc=陶瓦联机是第三方开源自由软件，您在使用过程中所遇到的问题请通过相关渠道进行反馈。\\n\\\n  陶瓦联机使用 P2P 技术，联机成功后房间内用户之间将直接连接，不会使用第三方服务器对您的流量进行转发。最终联机体验和参与联机者的网络情况有较大关系。\\n\\\n  在多人联机全过程中，您必须严格遵守您所在国家与地区的全部法律法规。\nterracotta.status.preparing=正在下载联机核心 (请勿退出启动器)\nterracotta.status.launching=正在初始化联机核心\nterracotta.status.unknown=正在初始化联机核心\nterracotta.status.waiting=联机核心已就绪\nterracotta.status.waiting.host.title=我想当房主\nterracotta.status.waiting.host.desc=创建房间并生成邀请码，与好友一起畅玩\nterracotta.status.waiting.host.launch.title=您似乎忘记启动游戏了\nterracotta.status.waiting.host.launch.desc=未能找到正在运行的游戏\nterracotta.status.waiting.host.launch.skip=游戏已启动\nterracotta.status.waiting.guest.title=我想当房客\nterracotta.status.waiting.guest.desc=输入房主提供的邀请码加入游戏世界\nterracotta.status.waiting.guest.prompt.title=请输入房主提供的邀请码\nterracotta.status.waiting.guest.prompt.invalid=邀请码错误\nterracotta.status.scanning=正在扫描局域网世界\nterracotta.status.scanning.desc=请<a href=\"hmcl://game/launch\">启动游戏</a>，进入单人世界，按下 ESC 键，选择对局域网开放，点击创建局域网世界。\nterracotta.status.scanning.back=这将同时停止扫描局域网世界。\nterracotta.status.host_starting=正在启动房间\nterracotta.status.host_starting.back=这将会取消创建房间。\nterracotta.status.host_ok=已启动房间\nterracotta.status.host_ok.code=邀请码 (已自动复制到剪贴板)\nterracotta.status.host_ok.code.copy=复制邀请码\nterracotta.status.host_ok.code.copy.toast=已将邀请码复制到剪贴板\nterracotta.status.host_ok.code.desc=请提醒您的朋友在 HMCL 或 PCL CE 多人联机功能中选择房客模式，并输入该邀请码。\nterracotta.status.host_ok.back=这将同时彻底关闭房间，其他房客将退出并不再能重新加入该房间。\nterracotta.status.guest_starting=正在加入房间\nterracotta.status.guest_starting.back=这不会影响其他房客加入当前房间。\nterracotta.status.guest_ok=已加入房间\nterracotta.status.guest_ok.back=这不会影响其他房客加入当前房间。\nterracotta.status.guest_ok.title=请启动游戏，选择多人游戏，双击进入陶瓦联机大厅。\nterracotta.status.guest_ok.desc=备用联机地址：%s\nterracotta.status.exception.back=可再试一次\nterracotta.status.exception.desc.ping_host_fail=加入房间失败：房间已关闭或网络不稳定\nterracotta.status.exception.desc.ping_host_rst=房间连接断开：房间已关闭或网络不稳定\nterracotta.status.exception.desc.guest_et_crash=加入房间失败：EasyTier 已崩溃，请向开发者反馈该问题\nterracotta.status.exception.desc.host_et_crash=创建房间失败：EasyTier 已崩溃，请向开发者反馈该问题\nterracotta.status.exception.desc.ping_server_rst=房间已关闭：您已退出游戏世界，房间已自动关闭\nterracotta.status.exception.desc.scaffolding_invalid_response=协议错误：房主发送了错误的响应数据，请向开发者反馈该问题\nterracotta.status.fatal.retry=重试\nterracotta.status.fatal.network=未能下载联机核心。请检查网络连接，然后再试一次\nterracotta.status.fatal.install=严重错误：无法安装联机核心\nterracotta.status.fatal.terracotta=严重错误：无法与联机核心通讯\nterracotta.status.fatal.unknown=严重错误：原因未知\nterracotta.player_list=玩家列表\nterracotta.player_anonymous=匿名玩家\nterracotta.player_kind.host=房主\nterracotta.player_kind.local=你\nterracotta.player_kind.guest=房客\nterracotta.unsupported=多人联机功能尚不支持当前平台。\nterracotta.unsupported.os.windows.old=多人联机功能需要 Windows 10 或更高版本。请更新系统。\nterracotta.unsupported.arch.32bit=多人联机功能不支持 32 位系统。请更新至 64 位系统。\nterracotta.unsupported.arch.loongarch64_ow=多人联机功能不支持 Linux LoongArch64 旧世界发行版，请更新至新世界发行版 (如 AOSC OC)。\nterracotta.unsupported.region=多人联机功能目前仅为中国内地用户提供服务，在您所在地区可能不可用。\n\nunofficial.hint=你正在使用非官方构建的 HMCL。我们无法保证其安全性，请注意甄别。\n\nupdate=启动器更新\nupdate.accept=更新\nupdate.changelog=更新日志\nupdate.channel.dev=开发版\nupdate.channel.dev.hint=你正在使用 HMCL 开发版。开发版包含一些未在稳定版中包含的测试性功能，仅用于体验新功能。开发版功能未受充分验证，使用起来可能不稳定！<a href=\"https://hmcl.huangyuhui.net/download\">下载稳定版</a>\\n\\\n  \\n\\\n  如果你使用时遇到了问题，可以通过设置中<a href=\"hmcl://settings/feedback\">反馈页面</a>提供的渠道进行反馈。欢迎关注 B 站账号 <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> 以关注 HMCL 的重要动态，或关注 <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> 以了解 HMCL 的开发进展。\nupdate.channel.dev.title=开发版提示\nupdate.channel.nightly=预览版\nupdate.channel.nightly.hint=你正在使用 HMCL 预览版。预览版更新较为频繁，包含一些未在稳定版和开发版中包含的测试性功能，仅用于体验新功能。预览版功能未受充分验证，使用起来可能不稳定！<a href=\"https://hmcl.huangyuhui.net/download\">下载稳定版</a>\\n\\\n  \\n\\\n  如果你使用时遇到了问题，可以通过设置中<a href=\"hmcl://settings/feedback\">反馈页面</a>提供的渠道进行反馈。欢迎关注 B 站账号 <a href=\"https://space.bilibili.com/1445341\">@huanghongxun</a> 以关注 HMCL 的重要动态，或关注 <a href=\"https://space.bilibili.com/20314891\">@Glavo</a> 以了解 HMCL 的开发进展。\nupdate.channel.nightly.title=预览版提示\nupdate.channel.stable=稳定版\nupdate.checking=正在检查更新\nupdate.disable_auto_show_update_dialog=不自动显示更新弹窗\nupdate.disable_auto_show_update_dialog.subtitle=启用此选项，HMCL 将不会自动弹出更新弹窗。\nupdate.failed=更新失败\nupdate.found=发现更新\nupdate.newest_version=最新版本为：%s\nupdate.bubble.title=发现更新：%s\nupdate.bubble.subtitle=点击此处进行升级\nupdate.note=开发版与预览版包含更多的功能以及错误修复，但也可能会包含其他的问题。\nupdate.latest=当前版本为最新版本\nupdate.no_browser=无法打开浏览器。网址已经复制到剪贴板，你可以手动粘贴网址打开页面。\nupdate.tooltip=更新\nupdate.preview=提前预览 HMCL 版本\nupdate.preview.subtitle=启用此选项，你将可以提前获取 HMCL 的新版本，以便在正式发布前进行测试。\n\nversion=游戏\nversion.name=游戏实例名称\nversion.cannot_read=读取游戏实例失败，无法进行自动安装\nversion.empty=没有游戏实例\nversion.empty.add=进入下载页安装游戏\nversion.empty.launch=没有可启动的游戏。\\n你可以前往下载页面下载新游戏，或在“实例列表”中切换游戏文件夹。\nversion.empty.launch.goto_download=前往下载页面\nversion.empty.hint=没有已安装的游戏。\\n你可以切换其他游戏文件夹，或者点击此处进入游戏下载页面。\nversion.game.all=全部\nversion.game.april_fools=愚人节\nversion.game.old=远古版\nversion.game.release=正式版\nversion.game.releases=正式版\nversion.game.snapshot=快照\nversion.game.snapshots=快照\nversion.game.support_status.unsupported=不支持\nversion.game.support_status.untested=未经测试\nversion.game.type=版本类型\nversion.launch=启动游戏\nversion.launch_and_enter_world=进入世界\nversion.launch.empty=开始游戏\nversion.launch.empty.installing=安装游戏\nversion.launch.empty.tooltip=安装并启动最新正式版游戏\nversion.launch.test=测试游戏\nversion.switch=切换实例\nversion.launch_script=生成启动脚本\nversion.launch_script.failed=生成启动脚本失败\nversion.launch_script.save=保存启动脚本\nversion.launch_script.success=启动脚本已生成完毕：%s\nversion.manage=实例列表\nversion.manage.clean=清理游戏文件夹\nversion.manage.clean.tooltip=清理“logs”和“crash-reports”文件夹\nversion.manage.duplicate=复制游戏实例\nversion.manage.duplicate.duplicate_save=复制世界\nversion.manage.duplicate.prompt=请输入新游戏名称\nversion.manage.duplicate.confirm=新的游戏实例将复制该实例文件夹 (\".minecraft/versions/<实例名>\") 下的文件，并带有独立的运行文件夹和设置。\nversion.manage.manage=实例管理\nversion.manage.manage.title=实例管理 - %1s\nversion.manage.redownload_assets_index=更新游戏资源文件\nversion.manage.remove=删除该实例\nversion.manage.remove.confirm.trash=真的要删除实例“%s”吗？你可以在系统的回收站中还原“%s”文件夹来找回该实例。\nversion.manage.remove.confirm.independent=由于该游戏启用了“(全局/实例特定) 游戏设置 → 版本隔离 → 各实例独立”选项，删除该实例将导致该游戏的世界等数据一同被删除！真的要删除实例“%s”吗？\nversion.manage.remove.failed=删除实例失败。可能文件被占用。\nversion.manage.remove_assets=删除所有游戏资源文件\nversion.manage.remove_libraries=删除所有库文件\nversion.manage.rename=重命名该实例\nversion.manage.rename.message=请输入要修改的名称\nversion.manage.rename.fail=重命名实例失败，可能文件被占用或者名字有特殊字符。\nversion.search=名称\nversion.search.prompt=输入版本名称进行搜索\nversion.settings=游戏设置\nversion.update=更新整合包\n\nwarning.java_interpreted_mode=HMCL 正在运行在解释器模式的 Java 环境中，性能将会受到很大影响。\\n我们建议你使用支持 JIT 的 Java 启动 HMCL 以获取最佳体验。\nwarning.software_rendering=HMCL 正在使用软件渲染，性能将会受到很大影响。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n\nwiki.tooltip=Minecraft Wiki 页面\nwiki.version.game=https://zh.minecraft.wiki/w/Java版%s\nwiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s\n\nwizard.prev=< 上一步\nwizard.failed=失败\nwizard.finish=完成\nwizard.next=下一步 >\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/microsoft_auth.html",
    "content": "<!--\nHello Minecraft! Launcher\nCopyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <https://www.gnu.org/licenses/>.\n-->\n<!DOCTYPE html>\n<html lang=\"%lang%\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Hello Minecraft! Launcher</title>\n    <style>\n        %style%\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            background-color: var(--monet-surface);\n            color: var(--monet-on-surface);\n            min-height: 100vh;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            overflow: hidden;\n            user-select: none;\n            cursor: default;\n        }\n\n        .dialog-card {\n            background-color: var(--monet-surface-container-high);\n            padding: 24px;\n            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.02);\n            max-width: 560px;\n            width: 90%;\n            display: flex;\n            flex-direction: row;\n            gap: 16px;\n        }\n\n        .icon-column {\n            flex-shrink: 0;\n            padding-top: 4px;\n        }\n\n        .icon-column svg {\n            width: 40px;\n            height: 40px;\n            display: block;\n            fill: var(--monet-on-surface);\n        }\n\n        .content-column {\n            flex: 1;\n            display: flex;\n            flex-direction: column;\n            min-width: 0;\n        }\n\n        .title {\n            font-size: 20px;\n            line-height: 1.4;\n            font-weight: 500;\n            color: var(--monet-on-surface);\n            margin-bottom: 8px;\n        }\n\n        .subtitle {\n            font-size: 14px;\n            line-height: 1.5;\n            color: var(--monet-on-surface-variant);\n            word-wrap: break-word;\n            margin-bottom: 24px;\n        }\n\n        .actions {\n            display: flex;\n            justify-content: flex-end;\n            gap: 8px;\n        }\n\n        .btn-confirm {\n            background-color: transparent;\n            color: var(--monet-primary);\n            border: none;\n            padding: 0 16px;\n            height: 40px;\n            font-size: 14px;\n            font-weight: 500;\n            font-family: inherit;\n            cursor: pointer;\n            transition: background-color 0.2s;\n            outline: none;\n            min-width: 64px;\n            display: inline-flex;\n            align-items: center;\n            justify-content: center;\n        }\n\n        .btn-confirm:hover {\n            background-color: rgba(128, 128, 128, 0.08);\n        }\n\n        .btn-confirm:active {\n            background-color: rgba(128, 128, 128, 0.12);\n        }\n    </style>\n</head>\n\n<body>\n<div class=\"dialog-card\">\n    <div class=\"icon-column\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n            <path d=\"m10.6 16.6l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8\"/>\n        </svg>\n    </div>\n\n    <div class=\"content-column\">\n        <div class=\"title\">%success%</div>\n        <div class=\"subtitle\">%close_page%</div>\n\n        <div class=\"actions\">\n            <button class=\"btn-confirm\" onclick=\"closeWindow()\">%ok%</button>\n        </div>\n    </div>\n</div>\n\n<script>\n    function closeWindow() {\n        window.close();\n        window.open(\"about:blank\", \"_self\").close();\n    }\n\n    setTimeout(closeWindow, 3000);\n</script>\n</body>\n</html>"
  },
  {
    "path": "HMCL/src/main/resources/assets/mod_data.txt",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# mcmod.cn\n# Copyright (C) 2025. All Rights Reserved.\n#\nindustrial-craft;2;IC2,ic2;工业时代2;Industrial Craft 2;IC2\n;3;RedPowerCore,RedPowerBase;红石力量2;RedPower2;RP2\nbuildcraft;4;BuildCraft|Core,buildcraftlib,buildcraftcore,buildcraftbuilders,buildcrafttransport,buildcraftsilicon,buildcraftfactory,buildcraftrobotics,buildcraftenergy,BuildMod,kamenridercraft4th,BuildCraft|Energy,BuildCraft|Transport,BuildCraft|Factory,BuildCraft|Silicon,BuildCraft|Builders,BuildCraft|Robotics;建筑;BuildCraft;BC\nforestry;5;Forestry,forestry;林业;Forestry;FR\nrailcraft;6;Railcraft,railcraft;铁路;Railcraft;RC\n;7;mod_MineFactory,MFReloaded;我的工厂重置版;MineFactory Reloaded;MFR\n;9;mod_EE;等价交换2;Equivalent Exchange 2;EE2\n;10;ForgottenNature;被遗忘的大自然;Forgotten Nature;FN\ntouhou-item-mod;11;THKaguyaMod;辉夜姬的五难题;五つの難題MOD+ ~ Touhou Items;\nsecretroomsmod;12;secretroomsmod;密室;SecretRoomsMod;SR\nthe-camping-mod;13;camping;野营;The Camping Mod;\nusefulfood;14;UsefulFood;有用的食物;UsefulFood;UF\npowerconverters-portablejims-fork;15;PowerConverters;能源转换;PowerConverters;PC\n;16;snyke7_Transformers;变压器;Transformers;\n;17;jammyfurniture,JammyFurniture;吉米的家具;Jammy Furniture;JF\nmodular-forcefield-system;18;modularforcefieldsystem;模块化力场系统;Modular Forcefield System;MFFS\nminechem-recompile;19;minechem;化学;MineChem;CH\niron-chests;20;IronChest,ironchest;更多箱子;Iron Chests;\nbalkons-weaponmod;21;weaponmod;更多武器;Balkon's WeaponMod;BWM\nadvanced-machines;22;AdvancedMachines,advanced_machines;高级机械;AdvancedMachines;\nadvanced-solar-panels;23;AdvancedSolarPanel,advanced_solar_panels;高级太阳能;Advanced Solar Panels;ASP\nnuclear-control-2;24;IC2NuclearControl;核电控制2;Nuclear Control 2;IC2NC\n;25;;魔法艺术;Ars Magica;AM\nbetter-dungeons;26;chocolateQuest;更好的地牢/寻找巧克力;Better Dungeons / Chocolate Quest;\nflans-mod-5-5-2;27;flansmod;Flan 的枪械;Flan's Mod;\n;29;Thaumcraft;神秘时代3;Thaumcraft 3;TC3\n;30;Forge,forge,FML,fml,mcp;Minecraft Forge;;Forge\nee3;31;EE3;等价交换3;Equivalent Exchange 3;EE3\nminefactory-reloaded;32;MineFactoryReloaded,MineFactoryReloaded|CompatVanilla,MineFactoryReloaded|CompatAppliedEnergistics,MineFactoryReloaded|CompatForgeMicroblock,MineFactoryReloaded|CompatThaumcraft,MineFactoryReloaded|CompatThermalExpansion,MineFactoryReloaded|CompatIC2,MineFactoryReloaded|CompatForestry,MineFactoryReloaded|CompatMagicalCrops,MineFactoryReloaded|CompatBuildCraft,MineFactoryReloaded|CompatRailcraft,minefactoryreloaded,MineFactoryReloaded|CompatAtum,MineFactoryReloaded|CompatBackTools,MineFactoryReloaded|CompatChococraft,MineFactoryReloaded|CompatExtraBiomes,MineFactoryReloaded|CompatForestryPre,MineFactoryReloaded|CompatMystcraft,MineFactoryReloaded|CompatPams,MineFactoryReloaded|CompatRP2,MineFactoryReloaded|CompatSufficientBiomes;我的工厂2;MineFactoryReloaded 2;MFR2\n;33;ModLoader;Risugami's ModLoader;;RML\nrender-player-api;34;RenderPlayerAPI;玩家渲染API;Render Player API;\nplayer-api;35;PlayerAPI;玩家API;Player API;\n;36;optifine;高清修复;OptiFine;OF\nnot-enough-items-1-8;41;NotEnoughItems,nei;NEI物品管理器;Not Enough Items;NEI\n;42;TooManyItems;TMI内置修改器;TooManyItems;TMI\n;43;;中文输入修复补丁;Minecraft Language InputFix;\n;44;MoLanguage;更多语言;MoLanguage;\ncustom-npcs;45;customnpcs,AdvNPCs;自定义NPC;Custom NPCs;\nmillenaire;46;millenaire;千年村庄;Millénaire;\nclay-soldiers-mod;47;claysoldiers;粘土士兵;Clay Soldiers Mod;\nmo-creatures;48;mocreatures;更多生物;Mo' Creatures;MOC\n;49;pixelmon;精灵宝可梦;Pixelmon;PM\nextrabiomesxl;50;ExtrabiomesXL,extrabiomesxl;更多生态群系;ExtraBiomesXL;EBXL\n;51;gregtech;格雷科技4;GregTech 4;GT4\ntale-of-kingdoms;55;taleofkingdoms;王国;Tale Of Kingdoms;\n;56;FamiliarsAPI;守护精灵;Familiars;\n;58;;正版皮肤显示修正;AntiSteve;\ntfcraft;59;terrafirmacraft;群峦传说;TerraFirmaCraft;TFC\n;60;SoulShards;灵魂碎片;Soul Shards;SS\nthe-twilight-forest;61;twilightforest;暮色森林;The Twilight Forest;TF\nbinnies-mods;62;ExtraBees,extrabees;更多蜜蜂;Extra Bees;EB\nportal-gun;63;PortalGun,portalgun;传送枪;Portal Gun;\nsimpleores;64;simpleores;简单矿石;SimpleOres;\nsmart-moving;65;SmartMoving,smartmoving;灵活动作;Smart Moving;\neasycrafting;66;EasyCrafting;轻松合成;Easy Crafting;\nthermal-expansion;67;ThermalExpansion;热力膨胀3;Thermal Expansion 3;TE3\n;68;FerullosGunsMod;费如罗的枪械;Ferullo's Guns;\n;69;compactkineticgenerators;压缩风电阵列;Compact Windmills;\n;70;manasys;元素魔法;Manasys;\ncomputercraft;71;ComputerCraft;电脑;Computer Craft;CC\n;72;SpecialArmor;特殊防具;Special Armor;\ninventory-tweaks;73;inventorytweaks;R键整理;Inventory Tweaks;\n;74;More Bows;更多弓;More Bows;\nminecraft-minions;75;minions;召唤奴仆;Minecraft Minions;\nender-storage;76;enderstorage;末影存储;Ender Storage;\n;77;DQMV;勇者斗恶龙;Dragon Quest Monster;DQM\n;78;mcmp101;内置音乐播放器;MCMP-1;\nforge-nbtedit-for-1-7-10;80;NBTEdit,nbtedit;内置NBT编辑器;In-Game NBTEdit;IGN\nextra-utilities;81;ExtraUtilities;更多实用设备;Extra Utilities;ExU\n;84;ssHookShot,HookShot;立体机动装置;Hookshot;\nrotarycraft;85;RotaryCraft;旋转工艺;RotaryCraft;RoC\n;86;;万用工具;OmniTools;\napplied-energistics-1;87;AppliedEnergistics;应用能源;Applied Energistics 1;AE\n;88;;奥法卷轴;Arcane Scroll;\n;89;HarkenScythe_Core;死神之镰;Harken Scythes;\n;90;AsgardShield;盾与巨剑;Asgard Shield;\n;91;Chainz_Core;锁链;Chainz;\nUniversal-Electricity;92;UniversalElectricity;通用电力;Universal Electricity;UE\n;93;btw;比狼好;Better Than Wolves;BTW\naether;94;aether,aether_legacy;天境;The Aether;AE\n;95;miscperipherals;电脑外设;MiscPeripherals;\n;96;AdvancedReactors;高级核电;Advanced Reactors;AR\nassassincraft-assassins-creed-mod;97;AssassinCraft;刺客信条;AssassinCraft;\n;98;RWG;更真实的地图生成;Realistic World Gen;RWG\ncustom-recipes;99;customrecipes;自定义合成表;Custom Recipes;\nminecraft-comes-alive-mca;100;mca;虚拟人生/凡家物语;Minecraft Comes Alive;MCA\ntinkers-construct;101;TConstruct;匠魂;Tinkers' Construct;TiC\n;102;BukkitForge;BukkitForge;;\nforge-essentials-74735;103;ForgeEssentials,forgeessentials;ForgeEssentials;;FE\n;104;palaria;帕拉瑞亚;Palaria;\ntranslocators-1-8;105;Translocator,translocators;转运器;Translocators;\ncraftguide;106;craftguide;G键合成表;CraftGuide;\n;107;mobdictionary;生物图鉴;Mob Dictionary;\nbiomes-o-plenty;108;biomesoplenty;超多生物群系;Biomes O' Plenty;BOP\n;109;BambooMod;竹;Bamboo;\n;110;tdmg;立体机动装置 (Ibemu版);立体機動装置MOD / Three-Dimensional Maneuver Gear;TDMG\nenhanced-portals-3;111;enhancedportals;增强传送门3;Enhanced Portals 3;\ncompact-solars;112;compactsolars,CompactSolars;压缩太阳能阵列;CompactSolars / Compact Solar Arrays;\nbibliocraft;113;BiblioCraft,bibliocraft;收藏馆;BiblioCraft;\nnatura;114;Natura,natura;自然;Natura;\natomic-science;115;atomicscience;原子科学;Atomic Science;AS\n;116;shouldersurfing;越肩视角;Shoulder Surfing;\natum;117;atum;阿图姆：沙漠之旅/亚图姆：金沙之旅;Atum 2: Return to the Sands;ATUM2\nblockphysics_by_id_miner;118;BlockPhysics;方块物理学;BlockPhysics;\n;119;RocketScience,mod_ZZZMissile,mod_RocketScience;火箭科技;Rocket Science;RS\ngrimoire-of-gaia;120;grimoireofgaia;盖亚魔典2;GRIMOIRE OF GAIA 2;GOG2\nremain-in-motion;121;JAKJ_RedstoneInMotion;运动红石机构;Remain in Motion;\n;122;OCS;开放式电脑传感器;OpenCCSensors;OpenCCS\nopenperipheralcore;123;OpenPeripheralCore,OpenPeripheralIntegration,OpenPeripheralAddons;开放式电脑外设;OpenPeripheral;\nmodular-powersuits;124;powersuits;模块化动力套装;Modular Powersuits;MPS\n;129;immibis_microblocks,ImmibisMicroblocks;Immibis的小方块;Immibis's Microblocks;\n;132;RedLogic;红石逻辑;RedLogic;\npowercraft-2;133;PowerCraft,mod_PowerCraft;能量工厂;Power Craft;PC\nobsidiplates;134;ObsidiPlates;黑曜石压力板;ObsidiPlates;\n;135;InfiniTubes;终极管道;InfiniTubes;\nflatsigns;136;FlatSigns;平置牌子;FlatSigns;\nswitches;137;switches;按钮开关;Switches;\nartifice;138;Artifice;工匠的手艺;Artifice;\narchimedes-ships;140;ArchimedesShipsPlus,ArchimedesShips;阿基米德之舟;Archimedes' Ships;\ncubebots;141;cubebots;立方体机器人;CubeBots;CB\ntruss-mod;142;TrussMod;桁架;Truss Mod;\nmutant-creatures-mod;143;MutantCreatures;突变生物;Mutant Creatures;\n;144;minegicka;魔能2;Minegicka II;\nlogistics-pipes;145;LogisticsPipes,logisticspipes;物流管道;Logistics Pipes;LP\nbinnies-mods;146;ExtraTrees,extratrees;更多树;Extra Trees;\nmagic-bees;148;MagicBees,magicbees;魔法蜜蜂;Magic Bees;MB\ncarpenters-blocks;149;CarpentersBlocks;木匠方块;Carpenter's Blocks;\nmystcraft;150;mystcraft;神秘岛;Mystcraft;\n;151;CowboyHat;牛仔帽;Cowboy Hat Mod;\n;152;fontsfix;英文显示默认字体 / 字体修复;FontsFix;\n;153;dartcraft;达特工艺;DartCraft;DC\nminesweeper-mod;154;MinesweeperMod;扫雷;Minesweeper;\nminechess;155;MineChess;国际象棋;MineChess;\n;156;MineCharacter;职业;MineCharacter;\n;157;tennox_bacteria;细菌;Bacteria;\n;158;Beargrylls;贝爷;BearGrylls;BG\n;159;factorization;工厂化/因式分解;Factorization;FZ\n;160;MineFantasy;我的幻想;MineFantasy;MF\n;161;UgoCraft;机关;UgoCraft;\n;162;ColoredWool;真彩色羊毛;Full RGB Colored Wool;\n;163;YATC;你是苦力怕;You Are The Creeper;YATC\nproject-red-base;164;ProjRed|Core,projectred-integration,projectred-transmission,projectred-expansion,projectred-relocation,projectred-transportation,projectred-fabrication,projectred-exploration,projectred-compat,projectred-illumination,ProjRed|Transmission,ProjRed|Integration,ProjRed|Illumination,ProjRed|Expansion,ProjRed|Exploration,ProjRed|Transportation,ProjRed|Fabrication,ProjRed|Compatibility,projectred-core;红石计划;ProjectRed;PR\n;165;OpenXP;开放式经验;OpenXP;\ndimensionaldoors;166;dimdoors;次元门;Dimensional Doors;\nchickenchunks;167;chickenchunks;区块载入器;ChickenChunks;\ndlt2;168;dynamictanks;动态蓄水槽;Dynamic Liquid Tanks;DLT\n;169;TechnicalVanilla;海绵科技;Technical Vanilla;\nwebdisplays-remasterd;170;webdisplays,WebDisplay;内置网页浏览器;WebDisplays;WD\nmorph;171;Morph;变身;Morph;\n;172;custom_flags;自定义旗帜;Custom Flags;\n;173;terraria3d;泰拉瑞亚3D;Terraria 3D;T3D\nextracells2;174;extracells;更多存储单元2;Extra Cells 2;EC2\n;175;itemrender;渲染图片导出;Item Render;IR\n;176;Zeppelin,mod_Zeppelin;齐柏林;Zeppelin;\nbillund-223258;177;Billund;乐高;Billund;\nalchemyplusplus;178;AlchemyPlusPlus;炼金++;AlchemyPlusPlus;\nbetter-bows;179;QuiverMod,BetterArcheryReborn;更好弓箭系统;Better Archery;\nmumblelink;180;mumblelink;Mumble语音链接;Mumblelink;\nender-io;181;enderioconduitsappliedenergistics,enderiointegrationforestry,enderioconduitsopencomputers,enderioconduitsrefinedstorage,enderioendergy,enderiobase,enderioconduits,enderiointegrationticlate,enderiomachines,enderiopowertools,enderiointegrationtic,enderio,EnderIO;末影接口;Ender IO;EIO\n;184;LevelStorage;等级存储;LevelStorage;\nelectrodynamics;185;electrodynamics;电动力学;Electrodynamics;\nelite-armageddon;186;genuine-ea,SolarApoc;Elite Armageddon / Solar Apocalypse;;\nmekanism;187;Mekanism,mekanism;通用机械;Mekanism;Mek\n;188;blowme;吹熄火焰！;Blow Me!;\n;189;Somnia;梦短人生长 / 真实睡眠;Somnia;\nmagic-crusade-rpg-mod;190;;魔法远征:魔法十字军;Magic Crusade;\ninvasions;191;Invasion,mod_Invasion;入侵;Invasion;\nmb-battlegear-2;192;battlegear2;挖矿与砍杀：双持与战备 2;Mine & Blade: Battlegear 2;\n;193;FrogCraft,mod_FrogCraft;青蛙化工;FrogCraft;FC\ngalacticraft-legacy;194;GalacticraftCore,galacticraftplanets,GalacticraftMars,galacticraftcore;星系;GalactiCraft;GC\n;195;dungeonpack;地牢拓展;DungeonPack;\n;196;LittleBlocks;小方块;Little Blocks;\n;197;CustomStuff2;自定义物品2;Custom Stuff 2;CS2\njourneymap;198;JourneyMapServer,journeymap;旅行地图;JourneyMap;JM\n;199;tickthreading;线程优化;TickThreading;\n;200;EEC;等价交换经典版;Equivalent Exchange Classic;EEC\nunderground-biomes;201;undergroundbiomes;科学地质;Underground Biomes;UB\nfpsplus-lagfixes;202;fpsplus;FPS优化;FPS Plus;\nars-magica-2;203;arsmagica2;魔法艺术2;Ars Magica 2;AM2\nthaumcraft;204;Thaumcraft;神秘时代4;Thaumcraft 4;TC4\nhighlands;205;Highlands,highlands;高地;Highlands;\ndoggy-talents;206;doggytalents;小狗天才;Doggy Talents;DT\nplasmacraft;207;plasmacraft;电浆工艺;PlasmaCraft;PC\n;208;morehealth;更多生命;More Health ENHANCED;\nfossils;209;HungerOverhaul;考古与化石;Fossils and Archeology Revival;F/A\nglenns-gases;210;gasesFrameworkCore,gasesFramework,gases;格林的气体;Glenn's Gases;\nthe-lord-of-the-rings-mod-legacy;211;lotr;魔戒：传承;The Lord of the Rings Mod: Legacy;LotR\nthaumic-tinkerer;212;ThaumicTinkerer,ThaumicTinkerer-preloader,thaumictinkerer;神秘工匠;Thaumic Tinkerer;TT\n;213;Paleocraft;古生代;Paleocraft;\nmetallurgy;214;Metallurgy3Core,Metallurgy3Machines,Metallurgy3Base,Metallurgy3Vanilla;冶金3;MetallurgyIII;\nxycraft;216;xycraft;XyCraft;;\nchococraft;217;chococraft;陆行鸟;ChocoCraft;\npams-harvestcraft;218;harvestcraft;潘马斯农场;Pam's HarvestCraft;PHC\nblood-magic;219;AWWayofTime,bloodmagic;血魔法;Blood Magic;BM\nopis;220;Opis;Opis;;\n;221;Yggdrasill;世界树/巨树;Yggdrasill;\nopenblocks;222;OpenBlocks,openblocks;开放式方块;OpenBlocks;OB\n;223;TinyCarts;微型矿车;TinyCarts;\n;225;adventurecraft;冒险;AdventureCraft;AC\n;226;MITE;MC实在是太简单了;Minecraft Is Too Easy;MITE\nrandomcraft-v0-1-0;227;RandomCraft;随机工艺;RandomCraft;\ndragon-mounts;228;DragonMounts;龙坐骑/龙骑士;Dragon Mounts;DM\nanimal-bikes;229;animalbikes;动物坐骑;Animal Bikes;AB\n;230;BetterGrassAndLeavesMod;更好的草地和树叶;Better Grass & Leaves;\nelectro-magic-tools;232;EMT;电力魔法工具;Electro-Magic Tools;EMT\nforbidden-magic;233;ForbiddenMagic;禁忌魔法;Forbidden Magic;FM\nmobtalker2;234;mobtalker2;怪物交谈2;MobTalker 2;\nicbm-classic;235;;洲际导弹;ICBM;ICBM\nthaumic-infusion;236;thaumicinfusion;神秘注魔;Thaumic Infusion;TI\nthaumic-exploration;237;ThaumicExploration;神秘探险;Thaumic Exploration;TX\ndeconstruction-table;238;deconstruction;解构工作台;Deconstruction Table;\n;240;magmamod;岩浆工具;The Magma Items;\nkcs-growable-ores;241;B0bGrowsOre,growores;可生长矿石;Kc's Growable Ores;BGO\n;242;Idfix;基础ID修复工具;Idfix;\n;243;;高级ID修复工具;IdfixMinus;\n;244;fle;远陆时代2;Far Land Era2;FLE2\n;245;CompactEngine;高效率引擎;Compact Engine;\nwaila;246;Waila,waila;高亮信息拓展;What am I looking at;Waila\nchaoscraft-2;247;Chaos_Technology;混沌工艺;ChaosCraft;CC\nscenter-mod;248;scenter;矿物追踪;Scenter;\n;249;otaku_craft;御宅工艺;Otaku Craft;OtC\nex-nihilo;250;exnihilo;无中生有;Ex Nihilo;ExN\nreactorcraft;251;ReactorCraft;反应堆工艺;ReactorCraft;ReC\nbctools;252;BCTools;BC扩展：更多工具;Buildcraft Tools;\nadditional-buildcraft-objects;253;Additional-Buildcraft-Objects;更多管道;Additional Buildcraft Objects;ABO\nfoundry;254;foundry;金属工坊;Foundry;\ngravitation-suite;255;GraviSuite,gravisuite;重力装甲;Gravitation Suite;\nredstone-arsenal;256;redstonearsenal,RedstoneArsenal,redstone_arsenal;红石兵工厂;Redstone Arsenal;RSA\nbig-reactors;257;BigReactors;大型反应堆;Big Reactors;BR\n;258;ResonantInduction,ResonantInduction|Core,ResonantInduction|Archaic,ResonantInduction|Atomic,ResonantInduction|Electrical,ResonantInduction|Mechanical,resonantinduction;谐振感应;Resonant Induction;RI\nendertanks;259;endertanks;末影水槽;Ender Tanks;\napplied-energistics-2;260;appliedenergistics2,appliedenergistics2-core,ae2;应用能源2;Applied Energistics 2;AE2\nhopper-ducts;261;HopperDuctMod,hopperducts;漏斗管道;Hopper Ducts;\nautofish;262;autofish;自动钓鱼;Autofish;\nmrcrayfish-furniture-mod;263;cfm;MrCrayfish 的家具;MrCrayfish's Furniture Mod;CFM\nwireless-redstone-cbe;264;WR-CBE,WR-CBE|Logic,WR-CBE|Addons,WR-CBE|Core,wrcbe;无线红石;Wireless Redstone;WR-CBE\n;265;ModTools;终极农业;Agent's Agriculture;\n;266;;异次元物品存储;Extradimensional Item Storage;EIS\nper-fabrica-ad-astra-geologica;267;PFAAGeologica;自合成至星辰：真实地理;Per Fabrica Ad Astra: Geologica;PFAA\n;268;bettermining;更好的矿物挖掘;Better Mining;BM\nredstone-furnace;269;redstonefurnace;红石炉;Redstone Furnace;\npneumaticcraft;270;PneumaticCraft,PneumaticCrap,pneumaticcraft;气动工艺;PneumaticCraft: Repressurized;PnC\n;271;TransducersIC2;能源转换器;Transducers;\nthirst-mod;272;thirstmod;饮水;Thirst Mod;\ntubes;273;tubes;管道;Tubes!;\npchan3-steamship-steamboat-pirates;274;pchan3;飞艇与汽船;SteamShip - SteamBoat and Pirates;PChan3\n;275;powersuits;模块化动力套装扩展;Modular Powersuits Addons;MPSA\nbetterfarming;276;BetterFarming;农业增强;Better Farming;\n;277;AdvancedTools;IC扩展：高级工具;Advanced Tools;\nlots-of-food;278;LotsOfFood;更多食物;Lots of Food;\n;279;FoodPlus;食物Plus;Food Plus;\naquaculture;281;aquaculture;水产业2/水产品2;Aquaculture 2;\nproject-bench;282;bau5_ProjectBench,projectbench;高级工作台;Project Bench;\n;283;extendedWorkbench;扩展工作台;Extended Workbench;\napples-plus;284;apples;更多苹果;Apples+;\n;285;BeerMod;啤酒;Beer Mod;\nqcraft;286;qCraft;量子物理;QCraft;QC\n;287;enderCraft;末影工艺;EnderCraft;\nmad-science;288;madscience;疯狂科学;Mad Science;\n;289;QuarryPlus;采石场增强;Quarry Plus;\nenviromine;290;enviromine;更多生存要素;EnviroMine;\n;291;ExtraFirma;群峦扩展;ExtraFirma;EF\nthe-erebus;292;erebus;混沌之地;The Erebus;\n;293;emeraldmod;绿宝石 Mod;Emerald Mod;\nlucky-block;294;lucky;幸运方块;Lucky Block;LB\ndynamic-lights;295;DynamicLights,DynamicLights_onFire,DynamicLights_creepers,DynamicLights_dropItems,DynamicLights_entityClasses,DynamicLights_mobEquipment,DynamicLights_thePlayer,DynamicLights_otherPlayers,DynamicLights_flameArrows,dynamiclights;动态光源;Dynamic Lights;DL\nyarr-cute-mob-models-remake-abandoned;296;yarr_cutemobmodels;怪物娘化;Yarr Cute Mob Models - Remake;\nmonster-hunter-frontier-craft;297;mhfc;怪物猎人边境工艺;Monster Hunter Frontier Craft;MHFC\nzombie-awareness;298;zombieawareness;僵尸意识;Zombie Awareness;ZA\nremote-io;299;RIO;远程接口;Remote IO;RIO\nelectrical-age;300;Eln;电力时代;Electrical Age;ELN\n;301;upgradableMachines;可升级机器;Upgradable Machines;\n;302;PowerUtils;电力转换;Power Utilites;\nminor-alchemy;303;MA,MinorAlchemy;小型炼金术;Minor Alchemy;MA\n;304;TooMuchTNT;更多TNT;TooMuchTNT;\n;305;mod_MoreExplosives,MoreExplosives;更多爆炸物;More Explosives Mod;\n;306;infection;生物病毒;MineInfection;\n;307;advancedfluxtools;高级能量工具;Advanced Flux Tools;\n;308;naturalcraft;自然工艺;Natural Craft;NC\nmcicraft;309;mcicraft,mcicraft_machines,mcicraft_redstone,mcicraft_farming;MCI工艺;MCI Craft;\n;310;EnergyManipulator;能源操纵;Energy Manipulation;\n;311;Lantern;手电与照明;Lanterns and Flashlights;\nengineers-toolbox;312;eng_toolbox;工程师工具箱;Engineer's Toolbox;\ne-tools-redstone-powered-tools;313;mod_EnergyTools;电子工具：红石供电工具;E-Tools: Redstone Powered Tools;\n;314;AppliedThermodynamics;应用热力学;Applied Thermodynamics;\nmodular-systems;315;modularsystems;模块化系统;Modular Systems;\n;316;;原子科技;Atomicraft;\ntraincraft;317;tc;火车工业;Traincraft;TC\nhydraulicraft;318;HydCraft;液压工艺;Hydraulicraft;HC\nutilities3;319;utilities3;流体能量;Utilities³;\n;320;;科学;The Science Mod;TSM\n;321;;和平工艺;InspiritCraft;\nmetallurgy;322;MetallurgyCore,Metallurgy,metallurgycm,metallurgy;冶金4;Metallurgy 4;ML4\nschool-mod-forge-1-14;324;schoolmod;学校;School Mod;\nwitchery;325;witchery;巫术;Witchery;\ngrowthcraft;326;Growthcraft,growthcraft_rice,growthcraft_milk,growthcraft_hops,growthcraft_grapes,growthcraft_fishtrap,growthcraft_bees,growthcraft_cellar,growthcraft_bamboo,growthcraft_apples,Growthcraft|Rice,Growthcraft|Milk,Growthcraft|Hops,Growthcraft|Grapes,Growthcraft|Fishtrap,Growthcraft|Bees,Growthcraft|Bamboo,Growthcraft|Apples,Growthcraft|Cellar;生长工艺;GrowthCraft;GrC\ngregtech-5-unofficial;327;gregtech_addon;格雷科技5;GregTech 5;GT5\nmantle;329;Mantle,mantle;地幔;Mantle;\ncustomoregen;330;CustomOreGen;自定义矿物生成;Custom Ore Generation;COG\nlittlemaidmobx;331;MMMLibX,lmmx;女仆;LittleMaidMob;LMM\nbotania;332;botania,Botania;植物魔法;Botania;BOT\nalternate-terrain-generation;333;atg;交替性地形生成;Alternate Terrain Generation;ATG\ngendustry;334;gendustry;基因工业;Gendustry;\n;336;;更多的配方;NotEnoughRecipes;NER\n;337;CrossbowMod2;十字弩2;Crossbow2;\n;338;mario,mod_Mario;超级马里奥;Super Mario Mod;\nbetterfonts;339;BetterFonts;更好的字体;BetterFonts;\nzelda-sword-skills;340;zeldaswordskills;塞尔达剑技;ZELDA SWORD SKILLS;ZSS\nminemenu;342;minemenu;我的菜单;MineMenu;\nsymcalc;343;symcalc;对称度运算仪;Symcalc;\n;344;mod_ecru_MapleTree;枫树;MapleTree;MT\n;345;IguanaTweaksTConstruct;匠魂强化;TiC Tweaks;\njabba;346;JABBA;更好的储物桶;Just Another Better Barrel Attempt;JABBA\nmcheli-minecraft-helicopter-mod;347;mcheli;MC直升机;MC Helicopter Mod;MCheli\nex-aliquo;348;exaliquo;Ex Aliquo;;\nsteves-carts-2;350;StevesCarts;史蒂夫矿车2;Steve's Carts 2;SC2\nmariculture;351;Mariculture;海洋物语;Mariculture;\nevilcraft;352;evilcraft,evilcraftcompat;邪恶工艺;EvilCraft;EC\nprojecte;353;ProjectE,projecte;等价交换重制版;ProjectE;PE\nfalling-meteors-mod;354;meteors;陨石;Falling Meteors;\nthermal-expansion;357;ThermalExpansion;热力膨胀4;Thermal Expansion 4;TE4\n;358;ControlPack;控制包/更方便快捷的控制;ControlPack;CP\nglacia-dimension;360;greenapple_glacia;冰河时代;Glacia;\nwater-power;361;WaterPower,waterpower;水力科技;Water Power;WP\nrealtrainmod;362;rtm;真实火车;RealTrainMod;RTM\napplemilktea2;363;DCsAppleMilk;苹果奶茶2;AppleMilkTea2;AMT2\n;364;ihouse;我的房屋;iHouse;\nflaxbeards-steam-power;365;Steamcraft;FSP蒸汽动力;Flaxbeard's Steam Power;FSP\nslashblade;366;flammpfeil.slashblade;拔刀剑;SlashBlade;\nsteves-factory-manager;367;StevesFactoryManager;史蒂夫工厂管理;Steve's Factory Manager;SFM\n;369;thaumicpipes;神秘管道;Thaumic Pipes;\nthaumcraftgates;370;thaumiumPipe;奥法管道;ThaumcraftGates;\nthaumcraft-recipes;371;thaumcraftrecipes;神秘合成;Thaumcraft Recipes;\nplayer-heads-forge;372;PlayerHeads;玩家死亡掉头;Player Heads;\nnotenoughkeys;373;notenoughkeys;更好的按键控制;Not Enough Keys;NEK\n;374;SnowsDeeper;更深的雪;Snow Deeper;\npet-bats;375;petbat;宠物蝙蝠;Pet Bats;\n;376;LightBridgesAndDoors;光桥和门;Light Bridges and Doors;\ncrafting-pillar-mod;377;craftingpillars;合成柱;Crafting Pillar Mod;CPM\nultratech;378;UltraTech;次元科技;UltraTech;UT\n;379;AdvancedRepulsionSystems;高级排斥系统;Advanced Repulsion Systems;ARS\natomicstrykers-infernal-mobs;380;InfernalMobs,infernalmobs;稀有精英怪;AtomicStryker's Infernal Mobs;\napplecore;381;AppleCore,applecore;苹果核;AppleCore;\nthaumic-energistics;385;thaumicenergistics;神秘能源;Thaumic Energistics;\n;386;TofuCraft;豆腐工艺;TofuCraft;\nchisel;387;chisel;凿子;Chisel;\nnuclearcraft-mod;388;nuclearcraft;核电工艺;NuclearCraft;NC\nopencomputers;389;OpenComputers,OpenComputers|Core,opencomputers,opencomputers|core;开放式电脑;Open Computers;OC\nelemental-stones;390;ElementalStones;元素之石;Elemental Stones;ES\nwaila-nbt;392;WailaNBT;万用高亮显示;Waila NBT;\nthaumcraft-node-tracker;393;tcnodetracker;神秘节点追踪;Thaumcraft Node Tracker;\ngeochests;394;Geochests;箱载世界;Geochests;\nparachutemod;395;parachutemod;降落伞;Parachute;\ngem-buffs-infinite-potion-effects;396;;宝石效果/增益宝石;Gem Buffs;GB\nrftools;397;rftools;RF工具箱;RFTools;\natomicstrykers-magic-yarn;398;MagicYarn;AtomicStryker的魔法线;AtomicStryker's Magic Yarn;\ngrimoire-of-gaia;399;GrimoireOfGaia,grimoireofgaia;盖亚魔典3;Grimoire of Gaia 3;GOG3\nteds-old-world-gen-mod;400;OWG;怀旧地形;Old World Gen;OWG\nadvent-of-ascension-nevermine;401;nevermine;虚无世界2;Nevermine 2: Advent of Ascension;AoA2\nacademycraft;402;academy-craft,academy;超能力/学园都市;AcademyCraft;AC\nthe-spice-of-life;404;SpiceOfLife,spiceoflife;生活调味料;The Spice of Life;SOL\n;405;minecraftloader;更好的载入界面;Minecraft Loader;\nautomagy;407;Automagy;自动化魔法;Automagy;\nstorage-drawers;408;StorageDrawers,storagedrawers;储物抽屉;Storage Drawers;\nwintercraft-gruntpie224;409;;冬天的世界;Wintercraft;\n;410;FoodCraft;食物工艺;FoodCraft;FC\n;411;PaneInTheGlasses;缝合玻璃;Pane In The Glasses;\nshincollecraft;412;shincolle;深海舰队收藏;ShinColle;\n;413;handofcats;猫之手;猫の手 ～Hand of Cats～;\nunicode-font-fixer;414;UnicodeFontFixer;通用字体修复 / Unicode 字体修复;Unicode Font Fixer;UFF\nlanteacraft;415;LanteaCraft;星际之门;LanteaCraft;\nmatteroverdrive-17-edition;416;matteroverdrive,mo;超能物质;Matter Overdrive;MO\ntainted-magic;417;TaintedMagic;污秽魔法;Tainted Magic;TM\nminetweaker3;418;MineTweaker3;MineTweaker 3;;MT\ntreecapitator;419;Treecapitator;砍树;Treecapitator;\ninsta-house;420;instahouse;即时建造;Insta House;\nmulti-page-chest;421;MultiPageChest,multipagechest;多页箱子;Multi-Page Chest;MPC\ndraconic-evolution;423;draconicevolution;龙之进化/龙之研究;Draconic Evolution;DE\ngalacticraft-add-on-more-planets;424;MorePlanet,moreplanets;更多行星;More Planets;\nthermal-foundation;425;ThermalFoundation,thermalfoundation,thermal;热力基本;Thermal Foundation;TF\n;426;colourfulsurvivalmod;多姿多彩的生存;Colourful Survival Mod;\n;427;;基岩破坏法杖;Bedrock Breaker;\nding;428;ding;界面进入提醒;Ding;\nender-utilities;429;enderutilities;末影设备;Ender Utilities;\nmagical-crops;430;magicalcrops,magicalcropsarmour;魔法作物;Magical Crops;\nheadcrumbs;431;headcrumbs;更多头颅;Headcrumbs;\nex-astris;432;exastris;星辉生万物;Ex Astris;EA\nxelitez-frostcraft;433;XEZFrostcraft;冰冻工艺;FrostCraft;\nhardcore-ender-expansion;434;HardcoreEnderExpansion;极限末地生存;Hardcore Ender Expansion;HEE\nmore-fun-quicksand-mod;435;MFQM;更多流沙;More Fun Quicksand Mod;MFQM\nfastcraft;436;FastCraft;快速工艺;FastCraft;\nforgeautoshutdown-1-12-2;437;forgeautoshutdown;Forge Auto Shutdown;;\n;438;MovementAssistant;行动助手;Movement Assistant;\nsao-ui;439;saoui;刀剑神域UI;Sword Art Online UI / SAO UI;SAOUI\nextratic;440;ExtraTiC;匠魂扩展;Extra TiC;\ndimensional-pockets;441;dimensionalPockets;四次元口袋;DimensionalPockets;DP\njoint-block-1-7-10;442;modJ_JBRobot,modJ_JointBlock,modj_jointblock;机器方块;JointBlock;\nsecurity-craft;443;securitycraft;安全工艺;Security Craft;SC\nbuildcraft-additions;444;bcadditions;建筑扩充;Buildcraft Additions;\nbedrockium-mod;445;be;基岩工艺;Bedrockium Mod;BE\nblocksteps;446;blocksteps;Blocksteps;;\nthaumic-horizons;447;ThaumicHorizons;神秘视界;Thaumic Horizons;TH\nmodtweaker;448;modtweaker2,modtweaker;ModTweaker;;MoT\nlootbags;449;enhancedlootbags,lootbags;战利品;Loot Bags;LB\ncompact-lava-generator;450;CLG;高级地热发电机;Compact Lava Generator;CLG\nproject-zulu;451;ProjectZulu|Core;祖鲁;Project Zulu;PZ\n;452;lom;许多生物;LotsOMobs;LOM\nchinacraft;453;chinacraft;华夏文明;ChinaCraft;CC\nthaumcraft;454;Thaumcraft;神秘时代5;Thaumcraft 5;TC5\n;455;gregtech;格雷科技6;GregTech 6;GT6\nbetterbeginnings-mod;456;betterbeginnings;更好的开局;Better Beginnings;BB\nutility-mobs;457;UtilityMobs;工具怪物;Utility Mobs;\nhunger-overhaul;458;hungeroverhaul;饥饿改革;Hunger Overhaul;HO\njei;459;jei,JEI;JEI物品管理器;Just Enough Items;JEI\njeiaddons;460;JEIAddons;JEI Addons;;\nmagneticraft;461;Magneticraft,magneticraft;磁场工艺;MagnetiCraft;MAGC\nchromaticraft;462;ChromatiCraft;缤纷纪元;ChromatiCraft;ChC\nimmersive-engineering;463;ImmersiveEngineering,immersiveengineering;沉浸工程;Immersive Engineering;IE\narmourers-workshop;464;armourersWorkshop,armourers_workshop,plushieWrapper;时装工坊;Armourer's Workshop;\nplants-to-ores;465;plo;植物矿石;Plants To Ores;\nslash;466;Slash;命令强化;Slash;\net-futurum;467;etfuturum;把未来带回现实;Et Futurum;\ncooking-for-blockheads;468;cookingforblockheads,cookingbook;傻瓜烹饪/懒人厨房;Cooking for Blockheads;\n;469;nstd;不要饿死;No Starve To Death;\npsi;470;psi,Psi;Psi;;\ngood-nights-sleep;471;good_nights_sleep,goodnightsleep;夜眠梦境/晚安的梦境;Good Night's Sleep;GNS\nbinnies-mods;472;BinnieCore,ExtraBees,ExtraTrees,Botany,Genetics;Binnie's Mods;;\nbaubles;473;Baubles,baubles;饰品栏;Baubles;\nmore-swords-mod;474;MSM3;更多剑;MoreSwords;\nthaumic-bases;475;thaumicbases;神秘基础学;Thaumic Bases;TB\nessentialcraft-3;476;essentialcraft;源质魔法3;EssentialCraft 3;EC3\n;477;Wa;和风;和風;Wa\nhardcore-questing-mode;478;HardcoreQuesting,hardcorequesting;困难任务;Hardcore Questing Mode;HQM\nunidict;479;unidict;矿辞统一;UniDict;UD\nthermal-dynamics;480;ThermalDynamics,thermaldynamics;热动力学;Thermal Dynamics;TD\nzoology;481;zoology;动物学;Zoology;\nplant-mega-pack-reupload;482;pmp,plantmegapack;植物世界;Plant Mega Pack;PMP\nthaumic-revelations;483;trevelations;神秘启示录;Thaumic Revelations;\n;484;morefuel;更多燃料;MoreFuel;\nwarp-book;485;warpbook;传送书;Warp Book;WB\nex-compressum;486;excompressum;压缩工具;Ex Compressum;\nsimply-jetpacks;487;simplyjetpacks;简易喷气背包;Simply Jetpacks;SJ\nblood-arsenal;488;BloodArsenal,bloodarsenal;血液兵工厂;Blood Arsenal;\nsim-u-kraft-old;489;ashjacksimukraftreloaded;模拟城市;Sim-U-Kraft;\nsuperheroes-unlimited;490;sus,sum;超级英雄无限;Superheroes Unlimited;SU\noceancraft-mod;491;Oceancraft;海洋世界;Oceancraft;\n;492;;能源链接;BC-IC2 Crossover;\n;493;AencEx,MoreEnchants,me;更多附魔;More Enchantments;\nwitching-gadgets;494;WitchingGadgets,WitchingGadgetsCore;巫师宝具;Witching Gadgets;WG\nbuildcraft-compat;496;BuildCraft|Compat,buildcraftcompat;建筑兼容;Buildcraft Compat;\nextrabotany;497;ExtraBotany,extrabotany;额外植物学;ExtraBotany;EXBOT\nbotania-unofficial;498;;植物魔法非官方版;Botania Unoffical;\nangry-pixel-the-betweenlands-mod;499;thebetweenlands;交错次元;The Betweenlands;TBL\nreal-first-person-render;500;rfp2,realrender;真实的第一人称;Real First Person 2;\n;501;totem,PillNote;丹药笔记;PillNote;PN\n;502;TW-Info;TW-信息;TW-Info;TW-Info\ncustom-stuff-3;503;CustomStuff3;自定义物品3;Custom Stuff 3;CS3\nlockdown;504;lockdown;地图生成锁定;Lockdown;\navaritia;505;Avaritia,avaritia;无尽贪婪;Avaritia;\nminecraft-flight-simulator;506;mfs;飞行模拟;Minecraft Flight Simulator;MFS\nessential-thaumaturgy;507;essenthaum;源质神秘学;Essential Thaumaturgy;\nabyssalcraft;508;abyssalcraft;深渊国度;AbyssalCraft;AC\ncalculator;509;calculator;运算工艺;Calculator;CC\nsextiarysector2;510;SextiarySector;Sextiary Sector 2;;SS2\nwarpdrive;511;WarpDrive;曲率驱动;WarpDrive;WD\nfuturepack;512;fp.api;飞向未来;Futurepack Mod;FP\nhbms-nuclear-tech-mod;513;hbm;HBM的核科技;HBM's Nuclear Tech Mod;NTM/HBM\nagricraft;514;AgriCraft,agricraft;农业工艺;AgriCraft;AC\narcane-engineering;515;arcane_engineering;奥法工程学;Arcane Engineering;\nimmersive-integration;516;immersiveintegration;Immersive Integration;;\nae2-stuff;517;ae2stuff;AE2 Stuff;;\nminetweaker-recipemaker;518;MTRM;可视化合成表编辑器;MineTweaker RecipeMaker;MTRM\nthaumic-dyes;519;thaumicdyes;神秘染料;Thaumic Dyes;\ngadomancy;520;gadomancy;魔像秘经;Gadomancy;\ninventory-pets;521;inventorypets;背包宠物;Inventory Pets;\nlevels;522;levels;装备等级;Levels;\nkeeping-inventory;523;keepinginventory;Keeping Inventory;;\norespawn;524;orespawn;矿石菌种;OreSpawn;OS\nreliquary-reincarnations;525;xreliquary;圣遗物;Reliquary Reincarnations (Reliquary) / Xeno's Reliquary;\nmo-bends;526;mobends;更多弯曲;Mo' Bends;\nquark;527;quark,Quark;夸克;Quark;\nblood-magic;528;bloodmagic,BloodMagic;血魔法2;Blood Magic 2;BM2\nofficial-divinerpg;529;divinerpg;神圣RPG;DivineRPG;DRPG\nharvest-festival;530;harvestfestival;丰收物语;Harvest Festival;HF\ntough-as-nails;531;toughasnails;意志坚定;Tough As Nails;TAN\nnon-update;532;non_update;不再有更新;Non Update;NU\nclayium;533;clayium;粘土工业;Clayium;\nbetter-storage;534;betterstorage;更好的储存;Better Storage;\ndavincis-vessels;535;davincisvessels;达芬奇之舟/达芬奇的船;Davincis Vessels;\nbspkrscore;536;bspkrsCore;bspkrs核心;bspkrsCore;\ntechguns;537;Techguns,techguns_core;科技枪;Techguns;TG\niron-backpacks;538;ironbackpacks;特殊背包;Iron Backpacks;\nopenmodularturrets;539;openmodularturrets;开放式炮台;OpenModularTurrets;OMT\ntechnomancy;540;technom;量子魔法;Technomancy;\nquantumflux;541;quantumflux;量子通量;QuantumFlux;QF\nblocksniffer;542;sniffer;方块探测;Block Sniffer;\ndragonapi;543;DragonAPI;Dragon API;;\nelectricraft;544;ElectriCraft;电力工艺;ElectriCraft;EC\ncandycraft;545;candycraftmod;糖果世界;CandyCraft;\n;546;ihl;IHL Tools & Machines;;IHL\nminefantasy2;547;minefantasy2;我的幻想2;MineFantasy2;MF2\n;548;tallworlds;更高的世界;TallWorlds;TW\nrails-of-war;549;;铁路战争;Rails of War;RoW\nactually-additions;550;ActuallyAdditions,actuallyadditions;实用拓展;Actually Additions;AA\ngalaxy-space-addon-for-galacticraft;551;GalaxySpace,galaxyspace;星空;Galaxy Space;GS\nlatiao-craft;552;latiaocraft;辣条工艺;Latiao Craft;LTC\n;553;TofuFactory;豆腐工厂重置版;Tofu Factory Reloaded;TFR\nmeteorcraft;554;MeteorCraft;陨石工艺;MeteorCraft;\niguanas-tinker-tweaks;555;IguanaTweaksTConstruct;匠魂增强;Iguanas Tinker Tweaks;\narrow-cam;556;arrowcammod;箭矢视角;Arrow Cam Mod;ACM\ntea-the-story;557;teastory;茶风·纪事;Tea the Story;TS\ntechreborn;558;techreborn,techreborn_compat;科技复兴;Tech Reborn;TR\ninfusinbrewing;559;InfusionBrewing;注魔酿造;Infusion Brewing;IB\njurassicraft;560;jurassicraft;侏罗纪时代;JurassiCraft;JC\neternal-singularity;561;eternalsingularity;永恒奇点;Eternal Singularity;ES\ncodechicken-lib-1-8;562;codechickenlib,ccl-entityhook,CodeChickenLib;CodeChicken Lib;;CCL\ncodechicken-core-1-8;563;CodeChickenCore;鸡块核心;CodeChickenCore;CCC\nautomagy;564;Automagy;自动化魔法2;Automagy 2;\naobd-singularities;565;aobdsingularities;AOBD的奇点;AOBD Singularities;\nuniversal-singularities;566;universalsingularities;通用奇点;Universal Singularities;US\nacademy-monster;567;academy-monster;超能力怪物;Academy Monster;AM\nsonar-core;568;sonarcore;声呐核心;Sonar Core;\narmorstatushud;569;armorstatushud;耐久信息显示;ArmorStatusHUD;\nwanionlib;570;wanionlib;Wanion Lib;;\nllibrary;571;llibrary;LLibrary;;\n;575;gtextras;GTExtras;;\nenvironmental-tech;583;environmentaltech;环境科技;Environmental Tech;ET\nmagic-cookies;585;MagicCookie;魔法饼干;Magic Cookies;\nthaumic-potatoes;586;shibeid;神秘土豆;Thaumic Potatoes;\ntorcherino;587;Torcherino,torcherino;加速火把;Torcherino;\ncraftable-nether-star;588;craftnstar;Craftable Nether Star +;;\nveinminer;589;veinminer,VeinMinerModSupport;连锁采矿/矿脉矿工;Vein Miner;\n;590;iridiummod;铱矿;Iridium Mod;\nanother-one-bites-the-dust;591;aobd;Another One Bites the Dust;;AOBD\n;592;MCEF;Minecraft Chromium嵌入式框架;Minecraft Chromium Embedded Framework;MCEF\nmine-mine-no-mi;593;mineminenomi;恶魔果实;Mine Mine no Mi;\nadvanced-rocketry;594;advancedRocketry,advancedrocketrycore,advancedrocketry,AdvancedRocketryCore;高级火箭;Advanced Rocketry;AR\nvics-modern-warfare-mod;595;mw;维克的现代战争;Vic's Modern Warfare Mod;VMW\nchance-cubes;596;chancecubes;机会方块;Chance Cubes;\ngun-customization;597;guncus;Gun Customization;;GunCus\nendercore;598;endercore;末影核心;EnderCore;\nbrandons-core;599;brandonscore;Brandon's Core;;\ncofh-core;600;CoFHCore,cofh_core,cofhcore;CoFH核心;CoFH Core;\nstatuseffecthud;601;StatusEffectHUD,statuseffecthud;状态信息显示;StatusEffectHUD;\nlambdalib;602;LambdaLib,LambdaLib|Core,lambdalib2;LambdaLib;;\nredstonic;603;Redstonic,redstonic;红石电子;Redstonic;\nlibvulpes;604;libVulpes,libvulpes;Libvulpes;;\nye-gamol-chattels;605;yegamolchattels;伊加墨的财产;Ye Gamol Chattels;YGC\nivtoolkit;606;ivtoolkit;IvToolkit;;\nwolf-armor-and-storage;607;wolfarmor;狼铠;Wolf Armor and Storage;\nbetter-records;608;betterrecords;更好的播放器;Better Records;\nworldedit;609;worldedit;创世神;WorldEdit;WE\n;610;liteloader;LiteLoader;;LL\n;611;WorldEditWrapper;单机创世神;WorldEdit Wrapper;\nworldeditcui;612;worldeditcui;WorldEditCUI;;WECUI\nztones;613;Ztones;Ztones;;\n;614;ppapmod;笔菠萝苹果笔;Pen Pineapple Apple Pen Mod;PPAP\ndummycore;615;DummyCore,fc-dummy;DummyCore;;\nextra-utilities;616;extrautils2;更多实用设备2;Extra Utilities 2;ExU2\nopenmodslib;617;OpenMods,OpenModsCore,openmodscore,openmods;开放式模组库;OpenModsLib;OML\nembers;618;embers;余烬;Embers;\nneat;619;neat;极简血量显示;Neat;\nmob-properties;620;MobProperties,mob_properties;怪物属性;Mob Properties;MP\nganys-surface;621;ganyssurface;Gany的世界;Gany's Surface;\n;622;;遗忘的火光;Amnesia Lights;AL\nezstorage;623;ezstorage;EZ存储;EZStorage;EZ\nichunutil;624;ichunutil,iChunUtil;iChunUtil;;\nintangible;625;intangible;Intangible;;\n;626;soulforest;灵魂森林;Soul Forest;\nforge-multipart-cbe;627;ForgeMultipart,forgemultipartcbe,microblockcbe,minecraftmultipartcbe;CB Multipart / Forge Multipart CBE / ForgeMultipart;;\nthe-titans-mod;628;TitansAnimationAPI,thetitans;泰坦生物;The Titans;\nenchiridion;629;Enchiridion,Enchiridion2;Enchiridion;;\nstimmedcow-nomorerecipeconflict;630;recipehandler;合成冲突消除;NoMoreRecipeConflict;\ntinker-i-o;631;tinker_io;工匠接口;Tinker I/O;\nwoot;632;woot,Woot;生物工厂;Woot;\n;633;huaji;滑稽纪元;Huaji Age;\nthermal-expansion;634;thermalexpansion;热力膨胀5;Thermal Expansion 5;TE5\nrealistic-terrain-generation;635;RTG,rtg;真实地形生成;Realistic Terrain Generation;RTG\nmob-rebirth;636;mobrebirth;怪物重生;Mob Rebirth;\ndiseasecraft;637;DiseaseCraft;疾病;Disease Craft;\nzombie-infection;638;ZombieInfection;僵尸感染;Zombie Infection;ZI\nastral-sorcery;639;astralsorcery;星辉魔法;Astral Sorcery;AS\nneotech;640;neotech;全新科技;NeoTech;NT\napocalypse-mod;641;Apocalypse;启示录;Apocalypse;\nbookshelf-api-library;642;bookshelfapi,bookshelf;Bookshelf API Library;;\n;643;;以太;The Ether;\nmovingworld;644;MovingWorld;MovingWorld;;\nbotania-garden-of-glass;645;GardenOfGlass,gardenofglass;水晶花园;Garden of Glass;\n;646;Tubestuff;RP增强;Tube Stuff;\ntiny-progressions;647;tp;微型自动化;Tiny Progressions;\nthaumic-additions;648;thaumadditions;神秘领域;Thaumic Additions: Reconstructed;\nbaubles-stuff;649;baublesstuff;更多首饰;Baubles Stuff;BS\nthaumaturgical-knowledge;650;benway_knowledge;神秘奥义;Thaumaturgical Knowledge;TK\nadvanced-thaumaturgy-2;651;AdvancedThaumaturgy;高等神秘学;Advanced Thaumaturgy;AT\nINpureCore;652;inpure|core;INpureCore;;\natomicstrykers-battle-towers;653;battletowers;战斗高塔;AtomicStryker's Battle Towers;\nreborncore;654;reborncore,reborncore-mcmultipart;重生核心;Reborn Core;\nsteves-carts-reborn;655;stevescarts;史蒂夫矿车：重生;Steve's Carts Reborn;SCR\nrefined-relocation;656;ForgeRelocation,RefinedRelocation;简单分类;Refined Relocation;\ngilded-games-util;657;gilded-games-util;Gilded Games Util;;GGU\ndangerrpg;658;dangerrpg;危险RPG;Danger RPG;\nmalisisdoors;659;malisisdoors;更多门;MalisisDoors;MD\n;660;;YMC科技公司的录像室;[YMC.Inc]Video Studio Room;YMC-VSR\njaffa-foods-a-harvestcraft-addon;661;jaffa;更多有趣的食物;Just Another Fun Food Addon;JAFFA\nthermal-smeltery;662;ThermalSmeltery,thermalsmeltery;热力冶炼厂;Thermal Smeltery;\n;663;torchLevers;火把拉杆;Torch Levers;\nmalisiscore;664;malisiscore;MalisisCore;;\nvalkyrielib;665;valkyrielib,universalmodifiers,valkyrie;ValkyrieLib;;\nwaila-harvestability;666;WailaHarvestability,wailaharvestability;Waila Harvestability;;\nmixed-magic;667;mixedmagic;混合魔法;Mixed Magic;\nhwyla;668;waila;Here's What You're Looking At;;Hwyla\ncrafttweaker;669;crafttweaker,ctgui,crafttweakerjei;CraftTweaker;;CrT\n;670;plustic;匠魂扩充;PlusTiC;\nreal-time-chat-translation-mod;671;translationmod;实时聊天翻译;Real Time Chat Translation Mod;RTCT\n;672;customsteve;自定义史蒂夫;CustomSteve;CS\npractical-logistics-2;673;practicallogistics2;实用物流监控2;Practical Logistics 2;PL2\nenergyadditions;674;EnergyAdditions;高级能源;EnergyAdditions;\nadvanced-generators;675;advgenerators;高级发电机;Advanced Generators;ADVG\nbdlib;676;bdlib;BDLib;;\n;677;FLabsBF;更好的熔炉;Better Furnace;\npressure-pipes;678;pressure;压力管道;Pressure Pipes;PP\nonlinepictureframe;679;opframe;在线相框显示;OnlinePictureFrame;\nsuper-circuit-maker;680;rscircuits,supercircuitmaker;超级电路;Super Circuit Maker;SCM\nswitch-bow;681;switchbow;Switch-Bow;;\nminecolonies;682;minecolonies;模拟殖民地;MineColonies;\ntinkers-construct;683;TConstruct,tconstruct;匠魂2;Tinkers' Construct 2;TiC2\nmcjtylib;684;mcjtylib_ng,mcjtylib;McJtyLib;;\nmcmultipart;685;mcmultipart;MCMultiPart;;MCMP\naroma1997s-dimensional-world;686;aroma1997sdimension,Aroma1997sDimension;Aroma1997的维度世界;Aroma1997's Dimensional World;\nproject-73926;687;supercraftingframe;超级合成框架;Super Crafting Frame;SCF\nakashic-tome;688;akashictome,AkashicTome;阿卡什宝典;Akashic Tome;\nmorph-o-tool;689;morphtool,Morphtool;变形工具;Morph-o-Tool;\ndecocraft;690;props,decocraft;装饰工艺;Decocraft;\nrefined-storage;691;refinedstorage,refinedreborn;精致存储;Refined Storage;RS\ndeep-resonance;692;deepresonance;深度共振;Deep Resonance;\n;693;GollumCoreLib;Gollum Core Lib;;\n;694;MorePistons;更多活塞;More Pistons;\n;695;itemsaver;物品存储器;ItemSaver;\nclockwork-phase;696;;时之沙;Clockwork Phase;\n;697;;镐子养成;Growable Pickaxe;GP\nautoreglib;698;autoreglib,AutoRegLib;AutoRegLib;;ARL\nroots;699;roots;根源魔法;Roots;\nguide-api;700;guideapi;指南;Guide-Api;\nfraghappy-reborn;701;fraghappy;FragHappy Reborn;;\ngenetics-reborn;702;geneticsreborn;基因重生;Genetics Reborn;\nrouter-reborn;704;routerreborn;路由重生;Router Reborn;\n;705;neieasysearch;NEI中文快捷搜索;NEI Easy Search;\ncoroutil;706;coroutil,CoroAI,CoroPets;CoroUtil;;\nvanilla-magic;707;vanillamagic2;原生魔法;Vanilla Magic;\ntaoism;708;taoism;御灵;Taoism;\nessentialcraft-4-unofficial;709;essentialcraft;源质魔法4非官方版;EssentialCraft 4 Unofficial;\ncelestial-craft;710;celestialcraft;天体;Celestial Craft;\nlost-thaumaturgy;711;lostthaumaturgy;失落神秘;Pengu's Lost Thaumaturgy;\ncompact-machines;712;compactmachines3,cm2,compactmachines,CompactMachines;更多空间/压缩空间;Compact Machines;CM\n;713;uc;实用煤炭;UsefulCoals;UfC\nex-nihilo-omnia;714;exnihiloomnia;无中生有-世间万物;Ex Nihilo Omnia;\nlast-sword-you-will-ever-need-mo;715;theonlychaosmodforyou;最终之剑;The Last Sword You Will Ever Need Mod;\nthe-last-sword-you-will-ever-need-reboot;716;tlsywen;最终之剑：重生;The Last Sword You Will Ever Need Reboot;\nftb-library-legacy-forge;718;ftblib,FTBL,ftbl;FTB Library (旧版);FTB Library (Forge) (Legacy);FTBL\n;719;InventoryScan;库存扫描;InventoryScan;IS\n;720;godzillamod;哥斯拉;Godzilla;\nthaumic-potatoes-2;721;thaumicpotatoes;神秘土豆2;Thaumic Potatoes 2;TP2\nextraplanets;722;extraplanets;额外行星;Extra Planets;EP\nxtracraft-mod;724;xtracraftmod;X工艺;XtraCraft Mod;XC\n;725;DefenseTech;防御科技;DefenseTech;DT\nformivore-generators-walled-city-great-wall-ca;726;CARuins,GreatWallMod,WalledCityMod;城墙城市生成;Cellular Automata Generator/Great Wall Mod/Walled City Generator;\nicbm-classic;727;icbmclassic;洲际导弹经典版;ICBM - Classic;\nvoltz-engine;728;VoltzEngine;Voltz Engine;;VE\nblocklings;729;blocklings;方块酱;Blocklings;\n;730;ThaumicMachina;神秘力学;Thaumic Machina;\n;731;shamanry;巫媒;Shamanry;\n;732;ThaumicDarkness;黑暗神秘学;Thaumic Darkness;TD\nmagia-naturalis;733;magianaturalis;自然魔法;Magia Naturalis;\nsbm-wooden-shears;734;woodenshears;木剪刀;Wooden Shears;\n;735;;神秘史记;Thaumic History;\nschools-of-magic;736;SchoolsMagic;魔法学院;Schools of Magic;\naether-aspects;737;AetherAspects;天境要素扫描;Aether Aspects;\narcane-arteries;738;arcanearteries;奥法献祭学;Arcane Arteries;\nthaumic-palmistry;739;thaumicpalmistry;Thaumic Palmistry;;\none-click-crafting;740;oneclickcrafting;一键合成;One click crafting;OCC\nessential-thaumaturgy-unofficial;741;;源质神秘学 (非官方版);Essential Thaumaturgy Unofficial;\nwireless-crafting-terminal;742;wct,ae2wct;无线合成终端;Wireless Crafting Terminal;WCT\nwaila-plugins;743;wailaplugins;Waila 插件;Waila Plugins;WP\nappleskin;744;appleskin;苹果皮;AppleSkin;AS\ntcbotaniaexoflame;745;TCBotaniaExoflame;奥术投射;TCBotaniaExoflame;TCBE\nthaumic-equivalence;746;;奥法置换;Thaumic Equivalence;\nthaumcraft-research-helper;747;;神秘研究小助手;Thaumcraft Research Helper;\nthaumic-upholstry;748;;神秘装饰;Thaumic Upholstry;\nthaumic-expansion;749;;神秘膨胀;Thaumic Expansion;\nnodal-mechanics;750;NodalMechanics;节点力学;Nodal Mechanics;NM\ntransmute-liquids-addon-for-thaumcraft-4;751;transmuteliquids;液体嬗变;Transmute Liquids;\nthaumores;752;thaumores;奥能矿物;ThaumOres;TO\nnotenoughthaumcrafttabs;753;bt;无限神秘标签页;Not Enough Thaumcraft Tabs;NETCT\nnatures-compass;754;naturescompass;自然罗盘/生物群系指南针;Nature's Compass;\nwarp-theory;755;WarpTheory;神秘扭曲学;Warp Theory;\nhammer-lib;756;hammercore,hammerlib;锤子核心;Hammer Core/Hammer Lib;\n;757;MiningTools;采掘工具;Mining Tools;\nthermal-casting;758;ThermalCasting;热力铸造室;Thermal Casting;\nthermal-smeltery-redux;759;;热力冶炼厂重置版;Thermal Smeltery Redux;\nbrcore;760;BRCore;BRCore;;\nindustrial-expansion-te-addon;761;industrialexpansion;工业扩充 (TE附属);Industrial Expansion [TE Addon];\n;762;gtveinlocator,detravscannermod;格雷科技矿脉定位器;GTVeinLocator;\n;763;gtneioreplugin;格雷矿物NEI插件;GTNEIOrePlugin;GTNOP\nspmodapi;764;spmodapi;SpmodAPI;;\nquantum-pack;765;quantumpack;量子工具包;Quantum Pack;\nnetherores;766;NetherOres;下界矿石;NetherOres;\ntesla;767;tesla;特斯拉;TESLA;TESLA\nvanillafoodpantry-mod;768;vanillafoodpantry;食品储藏室;VanillaFoodPantry Mod;VFP\nthe-elysium;769;elysium;极乐世界;The Elysium;\nice-and-fire-dragons;770;iceandfire;冰火传说;Ice and Fire;IAF\nyabba;772;yabba;Yet Another Better Barrels Attempt;;YABBA\nuseful-nullifiers;773;usefulnullifiers;实用销毁器;Useful Nullifiers;\nxtones;774;xtones;Xtones;;\nxnet;775;xnet;XNet;;\nwireless-crafting-grid;776;wcg;无线合成网络;Wireless Crafting Grid;WCG\ntop-addons;778;topaddons;TOP Addons;;TOPA\ntorchmaster;779;torchmaster;火炬大师;Torchmaster;\nzerocore;780;zerocore;ZeroCore;;\nsleeping-bag;781;SleepingBag;睡袋;Sleeping Bag;\nthe-one-probe;782;theoneprobe;检测器;The One Probe;TOP\nsimply-jetpacks-2;784;simplyjetpacks;简易喷气背包2;Simply Jetpacks 2;SJ2\nsnad;785;snad;子沙;Snad;\nstorage-drawers-extras;786;storagedrawersextra;Storage Drawers Extras;;\nsolar-flux-reborn;787;solarflux;日光通量：重制版;Solar Flux Reborn;SFR\nsimple-void-world;788;simplevoidworld;简单虚空世界;Simple Void World;SVW\nshetiphiancore;789;shetiphiancore;ShetiPhianCore;;SC\nshadowmc;790;shadowmc;ShadowMC;;\nscannable;791;scannable;扫描器;Scannable;\nrebornstorage;792;rebornstorage;存储重置;Reborn Storage;\nquantumstorage;793;quantumstorage;量子存储;QuantumStorage;\np455w0rds-library;794;p455w0rdslib;p455w0rd's Library;;\nmtlib;795;mtlib;MTLib;;\n;796;noCubes;光滑的地形/平滑地形;No Cubes Mod;\nopen-glider;797;openglider;滑翔翼;Open Glider;\nmputils;798;mputils;MPUtils;;\nshadowfacts-forgelin;799;forgelin;Shadowfacts' Forgelin;;\neleccore;800;eleccore;ElecCore | Rendering Library;;\ninfinitylib;801;infinitylib;Infinity Lib;;\ndark-utilities;802;darkutils;Dark Utilities;;\nflux-networks;803;fluxnetworks;通量网络;Flux Networks;FN\nflat-colored-blocks-forge;804;flatcoloredblocks;平滑色块;Flat Colored Blocks;FCB\njei-bees;805;jeibees;JEI 蜜蜂;JEI Bees;\nchisels-bits;806;chiselsandbits;雕凿工艺;Chisels & Bits;C&B\narchitecturecraft;807;ArchitectureCraft;建筑师工艺;Architecture Craft;\nchameleon;808;chameleon,Chameleon;变色龙;Chameleon;\nmrtjpcore;809;MrTJPCoreMod,mrtjpcore;MrTJPCore;;\ncyclops-core;810;cyclopscore;Cyclops Core;;\nnetherportalfix;811;netherportalfix;下界传送门修正;NetherPortalFix;\nrftools-dimensions;812;rftoolsdim;RF工具：维度;RFTools Dimensions;\nsignals;813;signals,Signals;信号;Signals;\nextreme-reactors;814;bigreactors;极限反应堆;Extreme Reactors;ER\nenvironmental-lunartech;815;etlunar;ET：月光发电;Environmental Lunar Tech;\nangel-ring-to-bauble;816;flyringbaublemod;可装备的天使指环;Angel Ring To Bauble;\nmob-grinding-utils;817;mob_grinding_utils;刷怪塔实用设备;Mob Grinding Utils;\nrftools-control;818;rftoolscontrol;RF工具：控制;RFTools Control;\nimmersive-petroleum;819;immersivepetroleum;沉浸原油;Immersive Petroleum;\nmore-shearables;820;shear;更多的剪刀用途;More Shearables;\ngregtech-gt-gtplusplus;821;miscutils,GT++_Preloader;Gregtech++;;GT++\n;822;GTTweaker;GTTweaker;;\n;823;TGregworks;Tinkers' Gregworks;;\n;824;galacticgreg;GalacticGreg;;\n;825;tectech;TecTech;;\nyam-core;826;YAMCore;YAMCore;;\n;828;;自然灾害;NaturalDisasters;ND\nteletoro;829;teletoro;远程传送;TeleToro;\n;830;touhou_alice_core,touhou_alice_dolls,touhou_alice_extras;爱丽丝人偶;Alice's Doll;\nre-zero-kara-hajimeru-isekai-seikatsu;831;ReZero;RE:从零开始的异世界;Re: Zero kara hajimeru isekai seikatsu;Re0\nminewatch;832;minewatch;Minewatch;;\nthaumcraft-inventory-scanning;833;tcinventoryscan;神秘时代物品栏扫描;Thaumcraft Inventory Scanning;TCIS\ncyclic;834;cyclicmagic,cyclic;循环;Cyclic;\nredstoneplusplus;836;redstoneplusplus;红石++;Redstone++;\nctm;837;ctm;连接纹理;ConnectedTexturesMod;CTM\nsimulated-nights;838;simulatednights;夜晚模拟;Simulated Nights;\nftb-utilities;839;FTBU,ftbutilities,ftbu;FTB 实用工具;FTB Utilities;FTBU\njust-enough-characters;840;jecharacters,je_characters;通用拼音搜索;Just Enough Characters;JECh\nredstone-flux;842;redstoneflux;Redstone Flux API;;RFAPI\ncofh-world;843;cofhworld;CoFH World;;\nancient-warfare-2;844;AncientWarfareAutomation,AncientWarfare,ancientwarfare,ancientwarfarestructure,ancientwarfarevehicle,ancientwarfarenpc,ancientwarfareautomation;古代战争2;Ancient Warfare2;AW2\nintegrated-dynamics;845;integrateddynamics,integrateddynamicscompat;动态联合/集成动力;Integrated Dynamics;ID\ncommon-capabilities;846;commoncapabilities;Common Capabilities;;\njingames-dragon-block-c;847;jinryuudragonblockc;龙珠C;Dragon Block C;DBC\ntan-campfire-spit;848;tanspit;意志坚定-篝火烤架;TAN Campfire Spit;\nsuper-solar-panels;849;supersolarpanel;超级太阳能发电机;Super Solar Panels;SSP\nngtlib;850;ngtlib;NGTLib;;\ngeomastery;851;geomastery;征服大地;Geomastery;\nafsu-mod;852;AFSU;AFSU;;\n;853;NyankoMod;猫;Nyanko;\nlightningcraft;854;LightningCraft,lightningcraft;雷电工艺;LightningCraft;LC\njust-enough-resources-jer;855;jeresources;Just Enough Resources;;JER\nmorpheus;856;Morpheus,morpheus;Morpheus;;\nwall-jump;857;walljump;飞檐走壁;Wall Jump;\ncondensed-block-mod-reborn;858;CBMReborn;凝结成块;Condensed Block Mod Reborn;\nrefined-avaritia;859;refined_avaritia;Refined Avaritia;;\nweather-storms-tornadoes;860;weather2;局部气候&风暴;Weather, Storms & Tornadoes;\nnew-things;861;newthings;New Things;;\nsurvivalist;862;survivalist;生存主义;Survivalist;\nmystic-divination;863;eangel;秘契占星术;Mystic Divination;\ncofh-lib;864;CoFHLib;CoFH Lib;;\ntabula-rasa;865;TabulaRasa;Tabula Rasa;;\ncofhtweaks;866;;CoFHTweaks;;\nender-io-zoo;867;EnderZoo;末影动物园;Ender IO Zoo/Ender Zoo;\nsimple-usage-log;868;simpleusagelog;简易日志;Simple Usage Log;SUL\nomnis-core;869;OmnisCore;Omnis Core;;\nauto-paths;870;AutoPaths;自动路径;Auto Paths;\nreact;871;react;React;;\nshared-resource-packs;872;sharedresourcepacks;共享资源包;Shared Resource Packs;SRP\ni-know-what-im-doing;873;ikwid;我知道我在干什么;I Know What I'm Doing;IKWID\nleather-works;874;leatherworks;皮革加工厂;Leather Works;\ncyberware;875;cyberware;机械改造;Cyberware;\nthermal-cultivation;876;thermalcultivation,thermal_cultivation;热力农业;Thermal Cultivation;\nbetter-builders-wands;877;betterbuilderswands;更好的建筑之杖;Better Builder's Wands;\njaopca;878;jaopca;Just A Ore Processing Compatibility Attempt;;JAOPCA\nchickens;879;chickens;更多鸡;Chickens;\ntragicmc2;880;TragicMC;悲惨世界2;TragicMC 2;TMC2\nmobdrip;881;mobdrip;MobDrip;;\n;882;custommc;自定义MC;CustomMc;\ncustomskinloader;883;customskinloader;万用皮肤补丁;CustomSkinLoader;CSL\nsaltymod;884;saltmod;盐;SaltyMod;\nimmersive-tech;885;immersivetech;沉浸科技;Immersive Technology;\n;886;DQMIIINext;勇者斗恶龙R;Dragon Quest Respect;DQR\nfunky-locomotion;887;funkylocomotion;Funky Locomotion;;FL\ncompatlayer;888;compatlayer;版本兼容;CompatLayer;\nwopper;889;wopper;木制漏斗;Wopper;\ngorgecore;890;gorgecore;食用强化;GorgeCore;\nchineseworkshop;891;chineseworkshop;中式工坊;Chinese Workshop;CW\nmodular-routers;892;modularrouters;模块化路由器;Modular Routers;\ndarkcore;893;darkcore;暗黑核心;Darkcore;\ntardis-mod;894;TardisMod;时间和空间的相对维度;TARDIS MOD;\n;895;ProjectBlue;蓝石计划;Project Blue;\n;896;herowatch;守望先锋;HeroWatch;\nanother-one-bites-the-dust-berry-bushes;897;aobdbb;AOBD Berry Bushes;Another One Bites the Dust: Berry Bushes;AOBDBB\ngokistats;898;gokistats,gokiStats;天赋技能树;GokiStats;\n;899;scraft;SC科学创造;Scientific Craft;SC\ntropicraft;900;tropicraft;热带世界;Tropicraft;\n;901;DanmakuCraft;弹幕工艺;DanmakuCraft;\n;902;exastrisrebirth;星辉生万物·重制;Ex Astris Rebirth;\nsumsang;903;sumsang;亖圼;Sumsang;\nmoblocks;904;moblocksmodtwo;Mo' Blocks;;\ncreepypasta-1-0-0;905;creepypasta;蠕动意面;CreepyPasta;\ngrue;906;grue;格鲁;Grue;\n263385-the-dead-brother;907;TheDeadBrother;亡兄;The Dead Brother;\ngooglyeyes;908;googlyeyes;金鱼眼;GooglyEyes;\nlycanites-mobs;909;lycanitesmobs,swampmobs,saltwatermobs,plainsmobs,mountainmobs,junglemobs,infernomobs,freshwatermobs,forestmobs,desertmobs;恐怖生物;Lycanites Mobs;LM\nback-to-eco;910;backtoeco;生态回归;Back to Eco;\nworld-of-warcraft;911;wow;魔兽世界;World Of Warcraft;\nthe-witcher-adventure;912;thewitcheradventure;猎魔人;The Witcher Adventure;\nfancy-battleaxes;913;fancy_battleaxes;华丽战斧;Fancy Battleaxes;\nzora-no-densha;914;zoranodensha;Zora的电车;Zora no Densha;ZnD\nroguelike-dungeons;915;roguelike;冒险地牢;Roguelike Dungeons;\nfirst-aid;916;firstaid;急救/医疗护理;First Aid;\nbonfires;917;bonfires;篝火;Bonfires;\nimmersive-craft;918;immcraft;沉浸工艺;Immersive Craft;\naroma1997core;919;Aroma1997Core,Aroma1997CoreHelper,aroma1997core;Aroma1997Core;;\ntatw;920;tatw;TATW;;\nhostile-mobs-and-girls;921;hmag;敌对生物和女孩们;Hostile Mobs and Girls;HMaG\nbetter-hud;922;hud,betterhud;更好的HUD;Better HUD;\ncustom-stuff-4;923;customstuff4;自定义物品4;Custom Stuff 4;CS4\npowered-thingies;924;teslathingies;特斯拉科技;Tesla Powered Thingies;TPT\n;925;xaerobetterpvp;更好的 PVP;Better PVP;\nmcmusicplayer;926;;MCMusic player;;\nobsidian-suite;927;obsidian_api,obsidian_animator;Obsidian Animation Suite;;OAS\nwizards-of-lua;928;wol;Lua魔导师;Wizards of Lua;WoL\nmystical-agriculture;929;mysticalagriculture;神秘农业;Mystical Agriculture;\nvampirism-become-a-vampire;930;vampirism;吸血鬼/血族传说;Vampirism;\nplants-vs-zombies-mod;931;plants2,pvz;植物大战僵尸;Plants VS Zombies;PVZ\nitemphysic;932;itemphysic;物品物理掉落;ItemPhysic;\nminenautica;933;Minenautica;Minenautica;;\nimproved-extraction;934;improvedextraction;矿物获取改进;Improved Extraction;\necology-mod;935;ecomod;生态;Ecology Mod;\ncakeisalie-mod;936;CakeIsALie;蛋糕是个谎言;CakeIsALie;\nex-nihilo-adscensio;937;exnihiloadscensio;无中生有：Adscensio;Ex Nihilo Adscensio;\nbookshelf;938;bookshelf,bookshelfapi;Bookshelf;;\n;939;mooncakecraft;月饼工坊;Mooncake Craft;\nrandom-things;940;randomthings,RandomThings;随意作品;Random Things;RT\n;941;scprein;SCP基金会2;SCP Craft 2;SCPC2\nnetherex;942;nex,netherex;下界拓展;NetherEX;NEX\nengender-mod;943;ageofminecraft,ageofabyssalcraft,ageofchaos,ageofmutants;召唤师;The Engender Mod;\nbetter-questing;944;betterquesting;更好的任务;Better Questing;BQ\n;945;;石头工艺;Stone craft;\n;946;DonnotSlay;不要杀戮;Don't Slay;\nct-mortar;947;ctmortar;CT Mortar;;\nct-watercan;948;watercan;CT Watercan;;\ntotemic;949;totemic;图腾;Totemic;\ngrimoire-of-alice;950;grimoireofalice;爱丽丝的魔法书;Grimoire of Alice;GoA\njourney-to-gensokyo;951;journeytogensokyo;幻想乡之旅;Journey to Gensokyo;JTG\ndanmakucore;952;danmakucore;弹幕核心;DanmakuCore;\nangel-of-vengeance;953;aov;复仇天使;Angel of Vengeance;AoV\ntammodized;954;tammodized;TamModized;;\nex-nihilo-creatio;955;exnihilocreatio;无中生有：创世;Ex Nihilo: Creatio;ExNC\nthaumcraft;956;thaumcraft;神秘时代6;Thaumcraft 6;TC6\ncolossal-chests;957;colossalchests;巨型箱子;Colossal Chests;\nauto-pickup;958;autopickup;自动拾取;Auto Pickup;\nall-u-want;959;alluwant;背包编辑器;All-U-Want;AUW\npotion-core;960;PotionExtensionCore,potioncore;药水核心;Potion Core;\nsolar-expansion;961;SolarExpansion;Solar Expansion;;\nblue-power;962;bluepower;蓝石力量;Blue Power;\nredstone-armory;963;RArm;红石军械库;Redstone Armory;RArm\nqmunitylib;964;qmunitylib;QmunityLib;;\nquest-for-blue-name;965;questforbluename;Quest Name For Blue;;\nthe-last-smith;966;lastsmith;最后的太刀匠人;The Last Smith;TLS\nmineamp;967;MineAmp,jukebox;我的音乐;MineAmp;\nquiverbow;968;quiverchevsky;Quiver Bow;;\ncookiecore;969;cookiecore;Cookie Core;;\nfarseek;970;farseek;Farseek;;\nmeecreeps;971;meecreeps;使命必达先生;MeeCreeps;\nstreams;972;streams;溪流;Streams;\n;973;noteblockdisplay;音符盒显示器;Note Block Display;\nblockcraftery;974;blockcraftery;方块工匠;Blockcraftery;\nbedrockores;975;bedrockores;基岩矿簇;Bedrock Ores;\nchat-bubbles;976;chatbubbles;聊天泡泡;Chat Bubbles;\nrefined-storage-addons;977;refinedstorageaddons;精致存储附属;Refined Storage Addons;\nfoamfix-optimization-mod;978;foamfix,foamfixcore;泡沫修复;FoamFix;\nindustrial-foregoing;979;industrialforegoing;工业先锋;Industrial Foregoing;IF\nexpanded-armory;980;exparmory;扩展装备;Expanded Armory;EA\nvoxelmap;981;VolexMap,voxelmap,mod_ZanMinimap;体素地图;VoxelMap;VM\n;982;rwbyCraft;四色战记;RWBY Craft;RWBYC\n;983;grim3212core;Grim3212Core;;\nmowzies-mobs;984;mowziesmobs;Mowzie的生物;Mowzie's Mobs;MMobs\nthaumcraft-mob-aspects;985;ThaumcraftMobAspects;Thaumcraft Mob Aspects;;\nmagical-psi;986;magipsi;Magical Psi;;\nstarcraft-mod-scmc;987;Starcraft;星际争霸2;StarCraft II in MineCraft;SCMC\ncovens-reborn;988;covens;Covens Reborn;;\ngrappling-hook-mod;989;grapplemod;抓钩;Grappling Hook Mod;\ntabbychat-2;990;tabbychat;TabbyChat 2;;\n;991;MineCameraDummy,minecamera;摄影工艺;MineCamera;\nyoyos;992;yoyos;悠悠球;Yoyos;\ncd4017be-library;993;cd4017be_lib;CD4017BE Library;;\niberia;994;iberia;Iberia;;\nreforged;995;reforged;重铸;Reforged;\ncavern;996;cavern;洞穴;Cavern;\nblock-drops-jei-addon;997;blockdrops;Block Drops;;\niron-tanks;998;irontank,irontanks;更多储罐;Iron Tanks;\ncolorutility;999;ColorUtility;颜色代码;ColorUtility;\ndigimobs;1000;digimobs;数码宝贝;Digimobs;DM\nstorage-boats-mod;1001;storageboats;存储船;Storage Boat Mod;\nmo-withers;1002;mowithers;更多凋灵;Mo' Withers;\njingames-naruto-c;1003;;火影忍者C;Naruto C;\nthe-autologin-mod-for-authme-server;1004;autologin;自动登录;AutoLogin;\nroost;1005;roost;鸡窝;Roost;\ncroparia;1006;croparia,Croparia;矿石作物/魔种之咏;Croparia;\nsimple-corn;1007;simplecorn;简单玉米;Simple Corn;\nthaumic-jei;1008;thaumicjei;Thaumic JEI;;\ntinkers-steelworks;1009;TSteelworks;匠魂炼钢厂;Tinkers' Steelworks;TiS\nunicode-font-extension;1010;unicodefontextension;Unicode 字体扩展;Unicode Font Extension;UFEX\nplaceable-items;1011;placeableitems;可放置物品;Placeable Items;\ncompendium;1012;tinkerscompendium,tinkersdefense,compendium;工匠防御;Compendium / Tinkers' Defense;\nsg-craft;1013;SGCraft;格雷的星门;Greg's SG Craft;GSGC\ndamage-indicators-mod;1014;DamageIndicatorsMod,damageindicatorsmod;伤害显示;Damage Indicators;\ntorohealth-damage-indicators;1015;torohealth;ToroHealth 伤害显示;ToroHealth Damage Indicators;\nomni-ocular;1016;OmniOcular;Omni Ocular;;OO\nwrapup;1017;wrapup;WrapUp;;\nnuclear-physics;1018;nuclearphysics;Nuclear Physics;;\nwither-skeleton-tweaks;1019;witherskelefix,wstweaks;凋灵骷髅调整;Wither Skeleton Tweaks;\nheat-and-climate;1020;dcs_climate;热量与气候;Heat And Climate;HaC\ncustomthings;1021;customthings;自定义物品;CustomThings;\nexgregilo;1022;exgregilo;格雷矿筛;ExGregilo;\nplacebo;1023;placebo;Placebo;;\nplants;1024;plants2,plants;Plants;;\nchinjufumod;1026;chinjufumod;镇守府;ChinjufuMod +JapaneseBlock;\ntrack-api;1027;trackapi;Track API;;\nimmersive-railroading;1028;immersiverailroading;沉浸铁路;Immersive Railroading;IR\nfoodcraft-reloaded;1029;foodcraft;食物工艺重置版;FoodCraft Reloaded;FCR\ncustom-main-menu;1030;custommainmenu;自定义主菜单;Custom Main Menu;CMM\n;1031;TacticalFrame;战术框架;Tactical Frame;\nwildycraft;1032;nolpfij_wildycraft;Wildycraft;;\n;1033;minecreed;我的信条;MineCreed;\n;1034;heartwork;心血结晶;The Heartwork;THW\nmy-little-mob-grinder;1035;mylittlemobgrinder;小怪物粉碎机;My Little Mob Grinder;\nresource-loader;1036;resourceloader,ResourceLoader;资源加载;Resource Loader;RL\nno-sleeping-allowed;1037;nsa;禁止睡觉;No Sleeping Allowed;\nplayersdropheads;1038;playersdropheads;玩家掉落头颅;PlayersDropHeads;\nmemory-bar;1039;memorybar;Memory Bar;;\ntoxicrain;1040;toxicrain;有毒的雨;ToxicRain;\n;1041;happypvp;快乐的PVP;Happy PVP;\n;1042;molten;炽热基岩;Molten Bedrockium;\ncannibalism;1043;cannibalism;割肉小刀/同类相食;Cannibalism;\nquick-consume;1044;;快速消耗;Quick Consume;\nplanting-dirt-for-saplings;1045;autoplanter;自动植树机;Auto Planter / Planting dirt for saplings;\n;1046;phtage;幻影时代;PhantomAge;PHTAGE\ncustom-crosshair-mod;1047;custom-crosshair-mod;自定义准心;Custom Crosshair Mod;CCM\nbetter-blink;1048;betterblink;更好的传送;Better Blink;\nbiobomb;1049;biobomb;生物炸弹;BioBomb;\nender-hopper;1050;enderhopper;末影漏斗;Ender Hopper;\nmine-souls;1051;minesouls;黑魂战斗;Mine Souls;MS\nradixcore;1052;radixcore;RadixCore;;\nfamiliar-fauna;1054;familiarfauna;熟悉的动物;Familiar Fauna;\ntinkers-tool-leveling;1056;tinkertoolleveling;匠魂工具升级;Tinkers' Tool Leveling;\n;1057;gibby_dungeons;神秘RPG;Arcana RPG;\ningame-info-xml;1058;ingameinfoxml,InGameInfoXML;游戏信息显示XML版;InGame Info XML;IGI\n;1059;peachwoodsword,PeachWoodSword;桃木剑;PeachWoodSword;PeachWS\nlovelyrobot;1060;LovelyRobot,lovely_robot;萌化机器人;LovelyRobot;\ntameable-arachne;1061;TameableArachneMod,tameable_arachne;萌化女妖;Tameable Arachne;\nskillable;1062;skillable;Skillable;;\nlunatriuscore;1063;lunatriuscore,LunatriusCore;LunatriusCore;;\nspin-to-win;1064;rafradek_spin;旋转剑技;Spin to Win;\nsoft-blocks;1065;softblocks;柔软方块;Soft Blocks;\nheartblock;1066;heartmod;生命方块;HeartBlock;\nspawncontroller;1067;spawncontroller;自定义怪物生成;SpawnController;\nwearable-backpacks;1068;wearablebackpacks,Backpack;可穿戴背包;Wearable Backpacks;\nheart-drop;1069;heartdrop;生命汲取;Heart Drop;\nomlib;1070;omlib;开放式炮台库;OMLib;\ndynamictrees;1071;dynamictrees,dynamictreestc,growingtrees;动态的树 / 有活力的树;Dynamic Trees;DT\nmerry-christmas;1072;merrychristmas;圣诞箱子;Merry Christmas!;\ncloud-control;1073;cloudcontrol;可控制的云;Cloud Control;\nzombie-ore;1074;zombieore;矿石僵尸;Zombie Ore;\nmeanmobs;1075;meanmobs;残忍怪物;MeanMobs;\nzyins-hud;1076;zyinhud;Zyin's HUD;;\n;1077;negorerouse;尼格洛兹·无尽曈曚;NegoreRouse;NR\nrough-tweaks;1078;roughtweaks;难度调整;Rough Tweaks;\n;1079;;厨艺大师;Master Chef;\nthe-disenchanter-mod;1080;disenchanter;祛魔台;The Disenchanter;\ncarry-on;1081;carryon;搬运;Carry On;\nbetter-than-llamas;1082;betterthanllamas;更好看的羊驼;Better Than Llamas;\ndynamic-surroundings;1083;dsurround,dsurroundcore;动态环境/动态环绕;Dynamic Surroundings;DS\nalfheim;1084;alfheim;亚尔夫海姆;Alfheim;\nmob-battle-mod;1085;mobbattle;生物大乱斗;Mob Battle Mod;\nsmooth-font;1086;smoothfont,smoothfontcore;平滑字体;Smooth Font;SF\nreskillable;1088;reskillable;Reskillable;;\nbetteroffhand;1089;betteroffhand;更好的副手;BetterOffhand;\nentity-spring;1090;entityspring;实体弹簧;Entity Spring;\ncompatskills;1091;compatskills;CompatSkills;;\navaritia-tweaks;1092;avaritiatweaks;Avaritia Tweaks;;\nmjrlegendslib;1093;mjrlegendslib;MJRLegends Lib;;\ndye-it-yourself;1094;diy;自动染色;Dye It Yourself;\npiston-expansion;1095;pistonexpansion;活塞拓展;Piston Expansion;\nunderwater-rails;1096;underwaterrails;水下铁轨;Underwater Rails;\ntrebuchet;1097;trebuchet;Trebuchet;;\nparticlelib;1098;particlelib;粒子库;ParticleLib;\nstar-miner;1099;modJ_StarMiner;星际矿工;Starminer;\n;1100;flammpfeil.slashblade.zxsa;迷之刀技;AbbySA;\njglrxavpoks-uncrafting-table;1101;uncraftingTable,jordy141minecraftmagicwrench;jglrxavpok的拆解台;jglrxavpok's Uncrafting Table;\nrustic;1102;rustic;乡村;Rustic;\nnetherutils;1103;netherutils;​NetherUtils;;\nbonsai-trees;1104;bonsaitrees;盆栽;Bonsai Trees;\n;1105;;量子工程;Quantum Engineering;QE\nbinnies-mods;1106;Genetics,genetics;基因工程;Genetics;\nmisty-world;1107;mist;朦胧世界;Misty World;MW\n;1108;binniecore;Binnie核心;Binnie Core;\nbinnies-mods;1109;Botany,botany;植物学;Botany;\nbeta-kathairis;1110;kathairis;卡塞瑞斯;Kathairis / Kether;\nmanametal;1111;manametalmod;魔法金属;ManaMetalMod;M3\navaritiaddons;1112;avaritiaddons;无尽收容 / 更多空间箱子;Avaritiaddons;\nbase;1113;base;B.A.S.E;;\nimmersive-cables;1114;immersivecables;沉浸线缆;Immersive Cables;\n;1115;darksword;黑暗剑;Darksword;\nbetter-title-screen;1116;bettertitlescreen;更好的标题页;Better Title Screen;BTS\nsound-filters;1117;soundfilters;声音滤波器;Sound Filters;\nlambda-craft;1118;lambdacraft;半条命;LambdaCraft;λC\nvivecraft;1119;vivecraftforgeextensionscore,vivecraftforgeextensions,vivecraft;Vivecraft;;\nsuper-sound-muffler;1120;supersoundmuffler;超级消音器;Super Sound Muffler;\narmorplus;1121;armorplus;装备扩充;ArmorPlus;\ninspirations;1122;inspirations;灵感;Inspirations;\ntree-growing-simulator;1123;treeGrowingSimulator;跳舞树成长;Tree Growing Simulator;TGS\n;1124;flammpfeil.extrascepterstaff;独角兽权杖;ExtraScepterStaff;\npersonal-cars;1125;personalcars;Personal Cars;;\nthermal-innovation;1126;thermalinnovation,thermal_innovation;热力革新;Thermal Innovation;TI\nbewitchment-legacy;1127;bewitchment;巫师之路;Bewitchment;\nbetter-foliage;1128;betterfoliage;更好的树叶;Better Foliage;BF\ncreative-plus;1129;creativeplus;创造+;Creative Plus;\njust-a-few-fish;1130;jaff;只是一些鱼;Just a Few Fish;JAFF\nynot;1131;ynot;YNot;;\nserene-seasons;1132;sereneseasons;静谧四季/季节;Serene Seasons;\nrealm-of-the-dragons-rotd;1133;rotd;龙之领域;Realm Of The Dragons;ROTD\nrockhounding-mod-core;1134;rockhounding_oretiers,rockhounding_rocks,rockhounding_surface,globbypotato_rockhounding,rockhounding_chemistry,rockhounding_core;地质探秘/岩石狩猎;RockHounding;RH\nfancy-block-particles;1135;fbp;梦幻方块效果;Fancy Block Particles;FBP\nthaumic-katana;1136;thaumkatana;神秘之日本刀;Thaumic Katana;\nthe-aether-ii;1137;aether,aether_ii;天境二;The Aether II;\nvisibleraygenerator;1138;VisibleRayGenerator,vrgenerator;永久光发电机;VisibleRay Generator;\nmmmmmmmmmmmm;1139;testdummy,dummmmmmy;试验假人;MmmMmmMmmMmm / Target Dummy;\naromabackup;1140;AromaBackup,aromabackuprecovery,aromabackup;存档备份;AromaBackup;\n;1141;name-wakander,NameWakander;NameWakander;;\norbis-lib;1142;orbis-lib;Orbis Library;;\nthe-eight-fabled-blades;1143;theeightfabledblades;八大传说之刃;The Eight Fabled Blades;\nmalisisblocks;1144;malisisblocks;Malisisblocks;;\nuseful-interior;1145;of;有用的内饰;Useful Interior;\ntaiga-tinkers-alloying-addon;1146;taiga;匠魂合金附加;Tinkers Alloying Addon;TAIGA\nversion-checker;1147;VersionChecker;模组更新检测;Version Checker;\nplanar-artifice;1148;planarartifice;镜面艺术: 传承;Planar Artifice: Continuation;PA\n;1149;wop;沃普综合;WOP Mixed;WOP\nRequiem;1150;requiem;安魂曲;Requiem/Dissolution/Tartaros;\nslashblade-in-tfc;1151;slashblade_tfc;古刀传;Slash Blade in TFC;SITFC\nmlp-mythical-creatures-updated-to-1-7-10;1152;MLPMod;神话生物;Mythical Creatures;MLP\nmetamorph;1153;metamorph;变形;Metamorph;\nreactioncraft-3-rebirth;1154;reactioncraft;世界反应3：重置;Reactioncraft 3: Rebirth;\npewter;1155;pewter;魔法匠魂;Pewter;\nredstone-repository-revolved;1156;redstonerepository;红石军械库重制版;Redstone Repository Revolved;RRR\nspawncommands-spawn-commands-teleport;1157;spawncommands;传送指令;Spawn Commands Teleport;\nserver-properties-for-lan;1158;splan;简单联机;Server.Properties for LAN;\njaopcaadditions;1160;;JAOPCA附属;JAOPCAAdditions;\ncloche-call;1161;;Cloche Call;;\nmouse-tweaks;1162;mousetweaks;鼠标手势;Mouse Tweaks;\njargca;1163;jaopca,oredictinit;JAOPCA神秘农业附属;Just A Resource Generation Compatibility Attempt;JARGCA\nsky-resources;1164;skyresources;空岛资源2;Sky Resources2;SR2\nhexxit-gear;1165;hexxitgear;Hexxit 装备;Hexxit Gear;\n;1166;itemrender;渲染图片导出续作;Item Render Rebirth;IRR\nmagic-clover;1167;magicclover;幸运四叶草;Magic Clover;\ninventory-crafting-grid;1168;invcrafting,samsinvcrafting;背包工作台;Inventory Crafting Grid;\nkleeslabs;1169;kleeslabs;更好地破坏半砖;KleeSlabs;\ngregtechce;1171;gregtech;格雷科技社区版;GregTech Community Edition;GTCE\nblur;1172;blur;界面背景模糊;Blur;\nfast-leaf-decay;1173;fastleafdecay;树叶快速腐烂;Fast Leaf Decay;\nenable-cheats;1174;enablecheats;开启作弊模式;Enable Cheats;\nfindit;1175;findit;找到它;Findit!;\nwearable-blocks;1176;wearableblocks;可穿戴的方块;Wearable Blocks;\n;1177;weaponmod;更多武器·改造版;Balkon's WeaponMod;\n;1178;item_search;MC百科资料搜索;MCMOD Item Search;\nswissarmyknife;1179;;瑞士军刀;SwissArmyKnife;SAK\nender-io-addons;1180;enderioaddons;末影接口拓展;Ender IO Addons;\nhistoricized-medicine;1181;historicizedmedicine;历史化医学;Historicized Medicine;\ninfinity-item-editor;1182;infinityeditor;无尽物品编辑器;Infinity Item Editor;IIE\nextra-rails;1183;extrarails,ExtraRails;更多功能铁轨;Extra Rails;\ngregtania;1184;;Gregtania;;\norigin;1185;origin;Origin;;\nminecraft-transport-simulator;1186;mts,mtsofficialpack;沉浸车辆;Immersive Vehicles;IV/MTS\nchest-transporter;1187;ChestTransporter,chesttransporter;搬箱器;Chest Transporter;\ni18nupdatemod;1188;i18nmod,i18nupdatemod;自动汉化更新;I18nUpdateMod;i18n\nrealistic-item-drops;1189;realdrops;真实物品掉落;Realistic Item Drops;\npixelmon;1190;pixelmon;像素宝可梦 重铸;Pixelmon Reforged;\ncontrolling;1191;controlling;键位冲突显示;Controlling;\n;1192;saligia;炼金重铸计划-七宗罪;PROJECT_saligia;\n;1193;MemoryCleaner;内存清理;Memory Cleaner;\nsimpleleather;1194;simpleleather;腐肉烧皮革;SimpleLeather;\nsync;1195;sync,Sync;克隆;Sync;\nchiseled-me;1196;chiseled_me;超级变变变;Chiseled Me;\nprogressive-automation-early-miner;1197;progressiveautomation;进阶自动化;Progressive Automation;\nmatmos;1198;matmos;真实环境音效;MAtmos;\nye-olde-tanks;1199;YeOldeTanks;水箱;Ye Olde Tanks;\nimproving-minecraft;1200;imc;改善的世界;Improving Minecraft;IPMC\nwawla;1201;wawla;Wawla高亮显示;What Are We Looking At;Wawla\nlightarea;1202;lightarea;点亮区域;LightArea;\n;1203;replaymod;录像回放;Replay Mod;\nstellar-sky;1204;stellarium;群星璀璨;Stellar Sky;\nstellar-api;1205;StellarAPI,stellarapi;恒星API;Stellar API;\nnei-addons;1206;NEIAddons,NEIAddons|Developer,NEIAddons|AppEng,NEIAddons|Botany,NEIAddons|Forestry,NEIAddons|CraftingTables,NEIAddons|ExNihilo;NEI扩充;NEI Addons;\ncucumber;1207;cucumber;Cucumber Library;;\nhunting-dimension;1208;huntingdim;狩猎维度;Hunting Dimension;\nrunes-of-wizardry;1209;runesofwizardry;符文魔法;Runes-of-Wizardry;\ncoffee-workshop;1211;coffeework;咖啡工坊;Coffee Workshop;\nin-game-account-switcher;1212;ias;游戏内账号切换;In-Game Account Switcher;IAS\nconsecration;1213;consecration;圣化;Consecration;\nignite-hud;1214;ignitehud;燃点HUD;Ignite HUD;\nmrcrayfishs-gun-mod;1215;cgm;MrCrayfish的枪;MrCrayfish's Gun Mod;CGM\nbloodmoon;1216;bloodmoon;血月;Bloodmoon;BM\nobfuscate;1217;obfuscate;Obfuscate;;\nrunic-dungeons;1218;runicdungeons;符文地牢;Runic Dungeon;\npoop;1219;shit;屎;Poop Mod;\nslurp;1220;slurp;啜饮;Slurp;\ntough-expansion;1221;tanaddons;意志坚定：热力附属;Tough Expansion;\nmystical-agradditions;1222;mysticalagradditions;神秘农业扩展;Mystical Agradditions;\nvanillafix;1223;vanillafix;原版修复;VanillaFix;VF\njust-enough-harvestcraft;1224;jehc;更多潘马斯配方查询;Just Enough HarvestCraft;JEHC\ncharcoal-pit-4;1225;charcoal_pit;Charcoal Pit;;\nuncomplication;1226;Uncomplication;IC2exp还原;Uncomplication;\nthe-wings-of-alfheim-1-7-10;1227;alfheimwings;亚尔夫海姆之翼;The Wings of Alfheim;\n;1228;reversecraft;逆向合成台;ReverseCraft;\n;1229;HariboteAirCraft,mod_HariboteAirCraft;飞行器;HariboteAirCraft;\nmubble;1230;mubble;Mubble;;\nlibraryex;1231;libraryex;MineEx 通用库;LibraryEx;LibEx\nindustrial-wires;1232;industrialwires;工业线缆;Industrial Wires;IW\nmystagrad-cloche-compat;1233;mystagradcompat;MystAgrad Cloche Compat​;;\nwaitingtime;1234;waitingtime;加载页面游戏;WaitingTime;\ndragontech;1235;dragontech,DragonScalesEX;龙之科技/龙鳞实验版;DragonTech / Dragon Scales EX / DragonScales;\ngravityfalls-mod;1237;;怪诞小镇;Gravity Falls;GF\ntfcbotania;1238;;TFC植物魔法;TFCBotania;\nno-recipe-guide;1239;;没有合成指南;No Recipe Guide;\ntakumi-craft;1240;takumicraft;爬行者世界/苦力怕世界;Takumi Craft;\nthe-mists-of-riov;1241;RioV;神秘世界;The Mists of RioV Mod;RioV\n;1242;kyokainokanata;境界的彼方;KyokaiNoKanata;KNK\nnsr-no-respawn-screen;1243;norespawnscreen;无重生屏幕;No Respawn Screen;NRS\nmore-bees;1244;morebees;蜜蜂拓展;More Bees;\nevilnotchlib;1245;evilnotchlib;EvilNotch Lib;;\ntpalette-forge;1246;TPaletteF;调色板;TPalette;\n;1247;gtweapons;格雷武器扩展;GregTech WeaponWorks;GTWW\ncareer-bees;1248;careerbees;职业蜜蜂;Career Bees;\n;1249;levelup;升级！;Level Up!;\nlevel-up;1250;levelup2;升级！重置;Level Up!Reloaded;\nlevel-up-legacy;1251;levelup;升级！前版;Level Up!Legacy;\nmoar-boats;1252;moarboats;模块化船;Moar Boats;\nnumina;1253;numina;模块化动力装甲前置;Numina;\ntinkers-complement;1254;tcomplement;匠魂补充;Tinkers' Complement;\neverlastingabilities;1255;everlastingabilities;永恒能力;Everlasting Abilities;\nabyssalcraft-heads;1256;acheads;深渊国度头颅;AbyssalCraft Heads;\noverlord;1257;overlord;不死者之王;Overlord;\nmultifarm-crops;1258;multifarmcrops;多功能农场兼容;Multifarm Crops;\nforestry-fermenter-addon;1259;ForestryFermenterAddon-1.7.10;更多生物质;Forestry Fermenter Addon;\ndakimakura-mod;1260;dakimakuramod;抱枕;Dakimakura Mod;\nrift;1262;rift;Rift;;\ncrop-eating-animals;1263;ceanimals;动物自动喂食;Crop-Eating Animals;\nrefraction;1264;refraction;折射;Refraction;\n;1265;;真实火车附加包：上海地铁包;;\nlibrarianlib;1266;librarianlib;图书馆;LibrarianLib;\ncreeper-confetti;1267;creeperconfetti;烟花苦力怕;Creeper Confetti;\nfunnels;1268;funnels;液体漏斗;Funnels;\nadvent-of-ascension-nevermine;1269;aoa3;虚无世界3;Advent of Ascension 3;AoA3\ntoroquest;1270;toroquest;托罗探索;Toro Quest;TQ\n;1271;nutrition;营养学;Nutrition;\nrikmulds-core-mod;1272;corerm;Rikmulds Core Mod;;\njust-enough-pattern-banners;1273;jepb;Just Enough Pattern Banners;;JEPB\nprimitive-mobs;1274;primitivemobs;远古生物 / 原始生物;Primitive Mobs;\nrealistic-cobwebs;1275;cobwebs;Realistic Cobwebs;;\nsimpleharvest;1276;simpleharvest;简单收获;Simple Harvest;\nmrcrayfishs-device-mod;1277;cdm;MrCrayfish 的设备;MrCrayfish's Device Mod;CDM\nblock-armor;1278;blockarmor;方块盔甲;Block Armor;\nstockpile;1279;stockpile;储物桶;Stockpile;\nloot-slash-conquer;1280;lsc;Loot Slash Conquer;;LSC\ncandy-world;1281;candymod;糖果世界;Candy World;CW\n;1282;slimecraft;史莱姆工艺;SlimeCraft;\ncreativecore;1283;creativecoredummy,creativecore;CreativeCore;;\ninstantunify;1284;instantunify;瞬间统一;InstantUnify;\n;1285;lostblade;遗忘之刃;LostBlade;\nmobdrops;1286;mobdrops,MobDrops;更多掉落;MobDrops;\nstates;1287;states;城邦;States;\nmodular-machinery;1288;modularmachinery;模块化机械;Modular Machinery;MM\ncompositegear;1289;compositegear;复合齿轮/复合装甲;Composite Gear;\nkiwi;1290;kiwi;Kiwi 🥝;;\ncuisine;1291;cuisine;料理工艺 🍳;Cuisine 🍳;\nworld-control;1292;worldcontrol;世界控制;World Control;\ntree-chopper;1294;treechopper;砍树;Tree Chopper;TCH\nthe-lost-cities;1295;lostcities;失落的城市;The Lost Cities;\n;1296;usernamemod;游戏内更改玩家名称;Ingame Username Changer;\n;1297;cpp;更多的合成;Crafting++;CPP\n;1298;dmp;装饰品合集 / 超多装饰世界;Decorations Mega Pack;DMP\nsimple-underground-biomes;1299;simpleundergroundbiomes;简单地下生物群系;Simple Underground Biomes;\nzen-foundry;1300;foundry;禅：金属工坊 🌋;Zen: Foundry 🌋;\n;1301;ExtendedPlanets;扩展行星;Extended Planets;\n;1302;custom_sword;TheCatacyst's Palindel;;\nbeyondspace;1303;beyondspace,galaxyadditions,ProjectAndromeda;Beyond Space / Galaxy Additions;;\nanimationapi;1304;AnimationAPI;AnimationAPI;;\nrealbench;1305;realbench;更好的工作台;RealBench;RB\nprimalcore;1306;primal;原始核心;PrimalCore;\nprimal-tech;1307;primal_tech;原始科技;Primal Tech;\nantique-atlas;1308;antiqueatlas;古式地图;Antique Atlas;\n;1309;hmggirlfront;少女前线;Girls' Frontline / ドールズフロントライン/ HMGDollsFrontLine;GF\nxray-1-13-rift-modloader;1310;atianxray;Xray Mod;;\noverloaded;1311;overloaded;超限存储;Overloaded;\npower-tools;1312;cuchaz.powerTools;现代工具;Power Tools;\nenchanting-plus;1314;eplus;高级附魔台;Enchanting Plus;EP\nstick-of-death;1315;stickofdeath;死亡之棍;The Stick Of Death;\ngodweapons;1316;godweapons;神的武器;Gods' Weapons;\nsekwah41s-naruto-mod;1317;narutomod;sekwah41的火影忍者;sekwah41's Naruto Mod;\nconstructs-armory;1318;conarm;匠魂盔甲;Construct's Armory;ConArm\n;1319;rivalrebels;未来战争;Rival Rebels;RR\nundertale-mod;1320;testenvironmentmod;传说之下;Undertale Mod;UTM\nglobe;1321;globe;水晶球;Globe;\nsuper-stick-sword;1322;supersticksword;超级木棍剑;Super Stick sword;\nmekanism-generators;1323;mekanismgenerators;通用机械发电机;Mekanism Generators;MekG\nspider-queen;1324;SQ;蜘蛛女王;Spider Queen;SQ\nbaublelicious;1325;baublelicious;美妙饰品;Baublelicious;\nvoid-monster;1326;VoidMonster;Void Monster;;VM\nshields-plus;1327;sp;更多盾牌;Shields Plus;SP\nghost-buster;1328;ghost_buster;Ghost Buster;;\nculinary-construct;1329;culinaryconstruct;自定义三明治;Culinary Construct;\narmoreablemobs;1330;armoreablemobs;怪物装备自定义;ArmoreableMobs;\n;1332;xos;矿物嗅探器;Ore Sniffer;\nadvanced-inventory;1333;advInv;Advanced Inventory;;\n;1335;Unsheathe;利刃出鞘;Sword Unsheathe;SU\npollution-of-the-realms;1336;adpother;环境污染/领域的污染;Pollution of the Realms;\nheat-and-climate-lib;1338;;Heat And Climate Lib;;\nwaystones;1339;waystones;传送石碑/指路石;Waystones;\nthaumic-calculations;1340;thaumic_calculator;神秘计算器;Thaumic Calculations;\npams-weee-flowers;1341;weeeflowers;潘马斯花园;Pam's Weee! Flowers;\n;1342;mistraven;寒鸦;MistRaven;M.R\n;1343;;Gamemode 4;;GM4\nparagliders;1344;paraglider;滑翔伞;Paragliders;\nender-io-endergy;1345;;末影接口：管道拓展;Ender IO:Endergy;\nbwm-suite;1346;betterwithmods;Better With Mods;;BWM\nambience-music-mod;1347;ambience;环境音乐;Ambience;\nalmost-enough-items;1348;almostenoughitems;AEI物品管理器;Almost Enough Items;AEI\nmtutils;1349;;MTUtils;;\ngravestone-mod;1350;gravestone;墓碑;GraveStone;\natlas-extras;1351;atlasextras;Atlas Extras;;\nhorse-power;1352;horsepower;马力;Horse Power;HP\nbetter-with-addons;1353;betterwithaddons;Better with Addons;;\ntesla-core-lib;1354;teslacorelib,teslacorelib_registries;特斯拉核心;Tesla Core Lib;TCL\nvoidscape;1355;voidcraft,voidscape;虚空工艺;Voidscape/VoidCraft;VC\nstygian-end-biome-expansion;1356;stygian;末地：生物群系扩展;Stygian End: Biome Expansion;\nwooden-furnace;1357;woodenfurnace;木制熔炉;Wooden Furnace;\nmoreplates;1358;moreplates;更多金属板;More Plates;\nmorelibs;1359;morelibs;More Libs;;\ngame-stages;1360;gamestages;游戏阶段;Game Stages;GS\nsilents-gems;1362;silentgems;寂静宝石;Silent's Gems;\nsilent-lib;1363;silentlib;Silent Lib;;\nsilents-gems-tic-support;1364;silentgemstic;寂静宝石：匠魂支持;Silent's Gems: TiC Support;\nlit-little-insignificant-things;1365;lit;Little Insignificant Things;;LIT\ngemulation;1366;;Gemulation;;\nsilents-gems-extra-parts;1367;sgextraparts;寂静宝石：额外部件;Silent's Gems: Extra Parts;\nfun-ores;1368;funores;趣味矿石;Fun Ores;\nsolar-furnaces;1369;simplesolarpanels;简易太阳能;Simple Solar Panels;\nvanilladeathchest;1370;vanilladeathchest;原版死亡箱子;Vanilla Death Chest;\narachnophobia;1371;arachnophobia;蜘蛛恐惧症;Arachnophobia;\nabyssalcraft-integration;1372;acintegration;深渊国度联动;AbyssalCraft Integration;\nprefab;1373;prefab;预制建筑;Prefab;\nhaunch-hud;1374;haunchhud;Haunch HUD;;\nabsent-by-design;1375;absentbydesign;Absent by Design;;\nno-nv-flash;1376;no_nv_flash;无夜视闪烁;No Night Vision Flashing;\narcade-mod;1377;arcademod;街机;Arcade Mod;\nasmodeuscore;1378;asmodeuscore;Asmodeus Core;;\nbuilding-gadgets;1379;buildinggadgets,buildinggadgets2;建筑小帮手;Building Gadgets;\ndooglamoo-worlds;1380;dooglamooworlds;Dooglamoo的世界;Dooglamoo Worlds;\nscp-lockdown;1381;scp;SCP基金会：封锁;SCP: Lockdown;SCPL\nmiddle-earth-thaumaturgy;1382;mett;中土神秘学;Middle-Earth Thaumaturgy;METT\nstructpro-mod-fast-schematic-spawning-system;1383;structpro;自然神殿;Structpro;\nbetterfps;1384;betterfps;更好的FPS;BetterFPS;\nsky-compression;1385;sc;Sky Compression;;SC\nreap-mod;1386;reap;收获;Reap;\ngeolosys;1387;geolosys;地质矿脉;Geolosys;\npatchouli;1388;patchouli;帕秋莉手册;Patchouli;\njeid;1389;jeid;JustEnoughIDs;;JEID\ngases-framework;1390;gasesframework;Gases Framework;;\nranged-pumps;1391;rangedpumps;范围泵;Ranged Pumps;\nbagginses;1392;bagginses;背包;Bagginses;\nic2-classic;1393;IC2-Classic-Spmod;工业时代2经典版;IC2 Classic;IC2C\nopencubicchunks;1394;cubicchunks;Cubic Chunks;;CC\ncubicworldgen;1395;;CubicWorldGen;;CWG\nrally-health;1396;rallyhealth;Rally Health;;\nopen-terrain-generator;1397;openterraingenerator;开放式地形生成器;Open Terrain Generator;OTG\n;1398;elementtimes;元素时代;ElementTimes;ET\nnoshelter;1399;noshelter;无庇护所;NoShelter;\nepic-siege-mod;1400;epicsiegemod;史诗攻城;Epic Siege;\nfurnace-overhaul;1401;furnaceoverhaul;熔炉改革;Furnace Overhaul;\n;1402;emberwither;灰烬凋零;EmberWither;\narchitecturecraft-tridev;1403;architecturecraft;建筑师工艺重置版;ArchitectureCraft - TridentMC Version;\nore-farm;1404;orefarm;矿石农场;Ore Farm;\nsound-physics-legacy;1406;soundphysics;物理声效;Sound Physics;\nplethora-peripherals;1407;plethora;Plethora Peripherals;;\nsimple-flight;1408;DP_SimpleFlight;简单的飞行;Simple Flight;\nfactory-tech;1409;factorytech;工厂科技;FactoryTech;\n;1411;fabricloader;Fabric Loader;;\nmysticallib;1412;mysticallib,elulib;MysticalLib;;\ncrissaegrim;1413;crissaegrim;Crissaegrim;;\n;1414;xcustomizedblade;可视化 X-客制化拔刀剑;XCustomizedBlade;XCB\ntraverse-reforged;1416;traverse;遍历;Traverse;\nwinter-wonder-land;1417;winterwonderland;霏雪寄语之地;Winter Wonder Land;WWL\nroad-stuff;1418;roadstuff;道路;Road Stuff;RS\nae2wtlib;1419;ae2wtlib;AE2 Wireless Terminal Library;;AE2WTLib\nreal-filing-cabinet;1420;realfilingcabinet;仿真档案柜;Real Filing Cabinet;\nhycrafthds-wtf-ic2-addon;1421;wtfic2addon;WTF 工业拓展;WTF Ic2 Addon;\nu-team-core;1422;uteamcore;U Team Core;;\nftb-quests-forge;1423;ftbquests;FTB任务;FTB Quests;FTBQ\ntexfix;1424;texfix;材质修复;TexFix;\nmapmakers-gadgets;1425;mapgadgets;地图作者小帮手;Mapmaker's Gadgets;MG\ntravellers-gear;1426;TravellersGear;旅者之器;Traveller's Gear;\nceramics;1427;ceramics;陶瓷器;Ceramics;\nsjap;1428;slashblade_addon,slashblade.addonpack,flammpfeil.slashblade;拔刀剑日系附属包;SlashBlade Japanese Addon Pack;SJAP\n;1429;4space;4-Space;;4S\nplanet-progression;1430;planetprogression;星球研究;Planet Progression;\nspace-advanced-addon-for-galactic-craft-3;1431;spaceadvanced;Space Advanced;;\ndecimation-zombie-apocalypse;1432;deci;僵尸启示录;Decimation;DECI\nrift-mod-list;1433;riftmodlist;Rift 模组列表;Rift Mod List;\nforgeendertech;1434;EnderTech,forgeendertech;ForgeEndertech;;\nnew-tardis-mod;1435;tardis;New TARDIS Mod;;NTM\n;1436;timezone;时区;Time Zone;\nadvanced-chimneys;1437;adchimneys;高级烟囱;Advanced Chimneys;\nregeneration;1438;regeneration;神秘博士 - 再生;Doctor Who - Regeneration​;\nstevekungs-lib;1439;stevekung's_lib;SteveKunG's Lib;;\nmake-zoom-zoom;1440;makezoomzoom;更快的加载;MakeZoomZoom;MZZ\nminecraft-virtual-machines;1441;mcvm;Minecraft虚拟机;Minecraft Virtual Machines;MCVM\ncavern2;1442;cavern;洞穴 II;Cavern II;\nnotenoughids;1443;neid;增加ID上限;NotEnoughIDs;NEID\navaritia-recipe-generator;1444;avaritiarecipemaker;可视化无尽配方编辑器;Avaritia Recipe Generator;ARG\ntrapcraft;1445;trapcraft;陷阱;Trapcraft;\nmod-name-tooltip;1446;modnametooltip;模组名称显示;Mod Name Tooltip;\ngpick-2;1447;gpick;进化之镐2;GPickaxe 2;GPick2\ncxlibrary;1448;cxlibrary;CXLibrary;;\n;1449;glasshearts;玻璃心;GlassHearts;\nintegrated-tunnels;1450;integratedtunnels,integratedtunnelscompat;集成管道;Integrated Tunnels;\nunmending;1451;unmending;经验不修补;Unmending;\nthe-creeping-nether;1452;creepingnether;蔓延的下界;The Creeping Nether;\nrandom-psideas;1453;rpsideas;Psi随想;Random PSIdeas;\nexpetrum;1454;exp;真实世界;ExPetrum;EXP\nequivalent-energistics;1455;equivalentenergistics;等价能源学;Equivalent Energistics / Equivalent Energistics (LTS);\nthedragonlib;1456;thedragonlib;TheDragonLib;;\nrepose;1457;repose;自动上坡;Repose;\nbotanic-additions;1458;botanicadds;植物学拓展;Botanic Additions;BA\n;1459;spongeforge,spongeapi,sponge;海绵端插件支持 Forge 版;SpongeForge;SF\nveggieway-fabric;1460;veggie_way;素食之道;The Veggie Way;\ngargoyles-mod;1461;gargoyles;石像鬼;Gargoyles;\nhydrophobia;1462;hydrophobia;恐水症;Hydrophobia;\ndimensional-edibles;1463;dimensionaledibles;维度食物;Dimensional Edibles;\npassable-leaves;1464;passableleaves,passableleavescore;叶间穿行;Passable Leaves;\nswingthroughgrass;1465;swingthroughgrass;穿草攻击;SwingThroughGrass;\nsteves-bizarre-adventure;1466;jojobadv;史蒂夫的奇妙冒险;Steve's Bizarre Adventure;\nanimania;1467;animania;动物谷：基础;Animania Base;\nendertweaker;1468;endertweaker;Ender Tweaker;;\ncofh-vanilla-tools;1469;vanillatools;CoFH: Vanilla+ Tools;;\n;1470;Yamazakura;山樱之刃;Yamazakura;\nrarmor;1471;rarmor;功能装甲;Rarmor;\nimmersive-io;1472;immersive_io;沉浸接口;Immersive IO;\ntickratechanger;1473;tickratechanger;运算变速;TickrateChanger;\nbotania-needs-these-things;1474;botanianeedsit;植物魔法需要它！;Botania Needs These Things!;\nnocubes;1475;nocubes;NoCubes;;\norelib;1476;orelib;OreLib;;\ntick-dynamic;1477;tickdynamic;动态Tick;Tick-Dynamic;\nsurge;1478;surge;潮涌;Surge;\nmore-avaritia;1479;moreavaritia;More Avaritia;;\nai-improvements;1480;aiimprovements;AI改进;AI Improvements;\nproject-ex;1481;projectex,extendedexchange;等价交换升级;FTB Project EX/ExtendedExchange;\nxaeros-world-map;1483;xaeroworldmap_core,xaeroworldmap;Xaero的世界地图;Xaero's World Map;XWM\nwizardry-mod;1484;wizardry;巫师艺术;Wizardry;\nfastfurnace;1485;fastfurnace;熔炉性能优化;FastFurnace;\nfastworkbench;1486;fastbench;工作台性能优化;FastWorkbench;\nunbreaken;1487;unbreaken;工具保护;UnbreakEn;FS\nbitcoin;1488;bitcoin;比特币;Bitcoin;\nhungering-darkness;1489;hungeringdarkness;Hungering Darkness;;\nroots-classic;1490;rootsclassic;根源魔法经典版;Roots Classic;\nembers-rekindled;1491;embers;余烬复刻版;Embers Rekindled;\nvending-machines-revamped;1492;aziasvendingmachine;自动贩卖机;Vending Machines Revamped;\nthaumic-alchemy;1493;thaumicalchemy;神秘炼金学;Thaumic Alchemy;\n;1494;thebestchristmas;最好的圣诞节;THE BEST CHRISTMAS MOD;\nmove-plus;1495;moveplus;移动方式增强;Move Plus;\nfuture-minecraft;1496;futureminecraf;未来的版本;FutureVersions;FV\ncontenttweaker;1497;contenttweaker;ContentTweaker;;CoT\nfrom-the-ground-up;1498;ftgumod;白手起家;From The Ground Up;FTGU\nclumps;1499;clumps;经验机制改革;Clumps;\ndynamic-surroundings-huds;1500;dshuds;动态环境：信息显示;Dynamic Surroundings: HUDs;\ncrafting-tweaks;1501;craftingtweaks;合成辅助;Crafting Tweaks;\nlimelib;1502;limelib;LimeLib;;\nsparks-hammers;1503;sparkshammers;重锤火花;Sparks Hammers;\ndeadly-monsters;1504;dmonsters;致命怪物;Deadly Monsters;\nvanillaautomation;1505;va;原版自动化;VanillaAutomation;VA\ntickprofiler;1506;tickprofiler;Tick分析器;TickProfiler;\nvanilla-immersion;1507;vimmersion;立体工具方块;Vanilla Immersion;VI\nearthworks;1508;earthworks;土方工程;Earthworks;\nterraqueous;1509;terraqueous;地灵云心/海树山花;Terraqueous;\ninitial-inventory;1510;initialinventory;自定义初始物品;Initial Inventory;\ninteraction-wheel;1511;intwheel;Interaction Wheel;;\nwater-strainer;1512;waterstrainer;滤水器;Water Strainer;\ntinkers-skyblock;1513;tinkerskyblock;匠魂空岛;Tinkers' Skyblock;\ndiet-hoppers;1514;diethopper;漏斗碰撞箱修复;Diet Hoppers;\nxl-food-mod;1515;xlfoodmod;超多食物;XL Food Mod;\nsoot;1516;soot;烟尘;Soot;\nhp-spells;1517;hpspells;哈利·波特：法术;Harry Potter Spells;HP Spells\nrecurrent-complex;1518;reccomplex;自然建筑生成;Recurrent Complex;ReC\ncapabilityproxy;1519;capabilityproxy;功能代理;CapabilityProxy;\nreauth;1520;reauth;ReAuth;;\nautoverse;1521;autoverse;Autoverse;;\ntransprot;1522;transprot;运输;Transprot;\ndefault-world-generator-port;1523;defaultworldgenerator-port;Default World Generator (port);;\nbetter-questing-standard-expansion;1524;bq_standard;更好的任务-基础扩展;Better Questing - Standard Expansion;\nexchangers;1525;exchangers;方块交换器;Exchangers;\nflexible-tools;1526;flexibletools;灵活的工具;Flexible Tools;\ndimensional-control;1527;dimensionalcontrol;维度控制;Dimensional Control;\nvalkyrien-skies;1528;valkyrienskies,vs_world,vs_control;瓦尔基里天空;Valkyrien Skies;VS\nitem-scroller;1529;itemscroller;物品滚轮;Item Scroller;\nbetter-advancements;1530;betteradvancements;更好的进度;Better Advancements;\nlucraft-core;1531;lucraftcore;Lucraft: Core;;\nprogressive-bosses;1532;progressivebosses;进化的 BOSS;Progressive Bosses;\npersistent-bits;1533;persistentbits;Persistent Bits;;\nrc-roads;1534;rcroads;真实公路;RC Roads;\nemoticons;1535;emoticons;动作表情;Emoticons;\nsimple-diving-gear;1536;simpledivegear;简单的潜水装备;Simple Diving Gear;\njust-enough-energistics-jee;1537;jee;Just Enough Energistics;;JEE\nironman;1538;ironman;钢铁侠;IronMan;\nchunk-animator;1539;chunkanimator;区块加载动画;Chunk Animator;CA\ncraftstudio-api;1540;craftstudioapi;CraftStudio API;;\nfisks-superheroes-2;1542;fiskheroes;菲斯克的超级英雄;Fisk's Superheroes;\n;1543;ultimatestack,ultimatestackplugin;终极堆叠;UltimateStack;\naiot-botania;1544;aiotbotania;植物魔法全能工具;AIOT Botania;\nbotania-tweaks;1545;botania_tweaks,botania_tweaks_core;植物魔法调整;Botania Tweaks;\nalbedo;1546;albedo;Albedo;;\nnatures-aura;1547;naturesaura;自然灵气;Nature's Aura;\n;1548;;RF的原版工艺;RFTR's Craft;RFC\nvoid-island-control;1549;voidislandcontrol;Void Island Control;;\nic2-nei-crop-plugin;1550;;IC2育种模拟器/杂交模拟器;IC2 Crop-Breeding Plugin;\nbetter-storage-too;1551;betterstorage;更好的储存2;Better Storage Too;\nmanaita-plus;1552;ManaitaPlus;更好的砧板;Manaita Plus;MP\nbartworks;1553;bartworks;巴特的作品;bartworks;\n;1554;rtj;放射性同位素温差供能喷气背包;Radioisotope Thermoelectric Jetpack;RTJ\nplaceable-motor;1555;;可放置马达;Placeable Motor;PM\nexo-craft;1556;exocraft;EXO-Craft;;\nsimple-login;1557;simplelogin;简单登录;Simple Login;\ngrim-pack;1558;grimpack;Grim的Mod整合;Grim Pack;\nartisan-worktables;1559;artisanworktables;工匠之作/工匠工作台;Artisan Worktables;AW\nptrlib;1560;PTRModelLib;PTRLib;;\ncreeper-awareness;1561;creeperawareness;爬行者意识;Creeper Awareness;\nwhat-fluid-fix;1562;what;WHAT - Fluid Fix;;\nblue-skies;1563;blue_skies;蔚蓝浩空;Blue Skies;\nproject-vibrant-journeys;1564;projectvibrantjourneys,pvj;活力之旅;Project: Vibrant Journeys;\nbed-bugs;1565;bedbugs;修复卡床;Bed Bugs;\nbiometweaker;1566;biometweaker;生物群系修改器;BiomeTweaker;\nactuallycomputers;1567;actuallycomputers;实用电脑;ActuallyComputers;\nfence-overhaul;1568;fenceoverhaul;斜栅栏;Fence Overhaul;\nzombie-players;1569;zombie_players;僵尸玩家;Zombie Players;ZP\nexotic-birds;1570;exoticbirds;珍奇鸟类;Exotic Birds;\ncharset;1571;charset;Charset;;\nbiometweakercore;1572;biometweakercore;生物群系修改器扩展;BiomeTweakerCore;\ntrashcans-reborn;1573;trashcansreborn;Garbage Bins;Trash Cans Reborn;\nCompactDrawers;1574;compactdrawers;Compact Drawers;;\nblockdispenser;1575;;发射器放置方块;BlockDispenser;\nredstone-paste;1576;redstonepaste;爬墙红石/粘性红石;Redstone Paste;\ndynamic-trees-traverse-compat;1577;dttraverse;动态的树：遍历附属;Dynamic Trees - Traverse Compat;\ncharset-lib;1578;charset;Charset Lib;;\nbetternether;1579;betternether;更好的下界;BetterNether;\ntime-machine-mod;1580;timemachine;时光机;Time Machine;\n;1581;AnotherStarAntiCheat,AnotherStar,anotheranticheat,AnotherStarByMiracle,AntiCheat3;另一个反作弊;AnotherAntiCheat;AAC\nupsizer-mod;1582;upsizer;更大的堆叠数量;Upsizer Mod;\nfence-jumper;1583;fencejumper;越过栅栏;Fence Jumper;\nenchantments-exchanger;1584;enchxchg;便携附魔替换台;Enchantments Exchanger;\nadditional-enchanted-miner;1585;quarryplus;Additional Enchanted Miner;;AEM\ntreasure-bags;1586;treasurebags;宝藏袋;Treasure Bags;\nstevecraft-by-owenpsteve2;1587;stevecraft;史蒂夫工艺;SteveCraft;\nhunger-games;1588;foundations;饥饿游戏;The Hunger Games;\ntfctech-addon;1589;tfctech;群峦工业;TFCTech Addon;TFCA\nyunomakegoodmap;1590;YUNoMakeGoodMap,yunomakegoodmap;YUNoMakeGoodMap;;\norganic-creepers;1591;organiccreepers;苦力怕蕨;Organic Creepers;\n;1592;flammpfeil.nihil;似蛭;Nihil;\n;1593;flammpfeil.slashblade.zephyr;西风太刀;Blade of the Zephyr;\n;1594;flammpfeil.toyako;洞爷湖;Toyako;\nindustrial-renewal;1595;industrialrenewal;工业复兴;Industrial Renewal;\ndraconicadditions;1596;draconicadditions;龙之进化拓展;Draconic Additions;\n;1597;sky_lanterns;孔明灯/天灯;Sky Lanterns;\nhated-mobs;1598;hatedmobs;令人讨厌的生物;Hated Mobs;\n;1599;tfcprimitivetech;原始技术;Primitive Technology;\nmekanica;1600;;通用机械：DZ版;Mekanica;\nlittletiles;1601;littletiles;LT小方块;LittleTiles;LT\nextended-crafting;1602;extendedcrafting;合成拓展;Extended Crafting;\nprodigy-tech;1603;prodigytech;奇才妙械;Prodigy Tech;PT\n;1604;RPG,RPGEx;RPGapi;;\nitem-filters;1605;itemfilters;物品过滤器;Item Filters​;\neblib;1606;net.blacklab.lib;EBlib;;\nnotenoughcodecs;1607;notenoughcodecs,NotEnoughCodecs;NotEnoughCodecs;;\nfuture-mc;1608;futuremc,minecraftfuture;未来的MC;Future MC;FMC\nrope-bridge;1609;ropebridge;索桥;Rope Bridge;\nsbm-oil-ore;1610;sbmoilore;油矿石;Oil Ore;\nredstone-minus-redstone;1611;redstoneminusredstone;红石减红石;Redstone Minus Redstone;\nmore-furnaces;1612;morefurnaces;更多熔炉;More Furnaces;\nintegrated-crafting;1613;integratedcrafting;集成合成学;Integrated Crafting;\nintegrated-terminals;1614;integratedterminals;集成终端;Integrated Terminals;\nmekanism-tools;1615;mekanismtools,MekanismTools;通用机械工具;Mekanism Tools;MekT\nroboticparts;1616;cyberware;机械改造1.12+;Robotic Parts;\nrewired;1617;rewired;ReWIRED;;\noh-the-biomes-youll-go;1618;byg;你将去的生物群系;Oh The Biomes You'll Go;BYG\nvertically-stacked-dimensions;1619;dimstack;垂直堆叠的世界;Vertically Stacked Dimensions;\n;1620;UraniumPlus;UraniumPlus;;\nmalisisswitches;1621;malisisswitches;MalisisSwitches;;\nautomated-redstone;1622;circuits;自动化红石;Automated Redstone;\nthuts-elevators;1623;thuttech;Thutmose的电梯;Thut's Elevators;\nadditional-resources;1624;additionalresources;Additional Resources;;\nsky-orchards;1625;sky_orchards;天空果园;Sky Orchards;\nmdxlib;1626;mdxlib;Minecraft Development Library X;;MDXLib\nwireless-utilities;1627;wirelessutils;无线设备;Wireless Utilities;\ntometinkers;1628;tometinkers;ToMeTinkers;;\ntinkers-slashblade;1629;slashbladetic;刀锻冶匠魂;Tinker's SlashBlade;\naudio-death;1630;audiodeath;死亡音效;Audio Death;\ndeep-mob-learning;1631;deepmoblearning;深度怪物学习;Deep Mob Learning;\n;1632;;Automated Bellows Addon;;\ntofucraftreload;1633;tofucraft;豆腐工艺重制版;TofuCraftReload;\nelectroblobs-wizardry;1634;ebwizardry;巫术学;Electroblob's Wizardry;EBWizardry\nmoarsigns;1635;moarsigns;更多告示牌;MoarSigns;\nkamen-rider-craft;1636;kamenridercraft4th;假面骑士工艺;Kamen Rider Craft;KRC\navaritia-io;1637;avaritiaio;无尽贪婪：接口;Avaritia IO;\ndense-neutron-collectors;1638;denseneutroncollectors;致密中子态素收集器;Dense Neutron Collectors;\npubgmc-mod;1639;pubgmc;PUBGMC;;\nmobultion;1640;mobultion;进化怪物;mobultion;\nwaddles;1641;waddles;Waddles;;\nproject-intelligence;1642;projectintelligence;PI文档;Project Intelligence;PI\nthutcore;1643;thutcore_compat,thutcore;ThutCore;;\nresource-pack-organizer;1644;resourcepackorganizer;资源包管理器;Resource Pack Organizer;\nmorcant-ore;1645;morcant_mod;莫桑矿石;Morcant Ore;\nforce-void-world;1646;FVW,fvw;强制虚空世界;Force Void World;FVW\ni-am-very-smart-forge;1647;iamverysmart;我很聪明;I Am Very Smart;\nfar-from-home;1648;farfromhome;背井离乡;Far From Home;FFH\ncotton;1649;cotton;Cotton;;\noxygen-teleportation;1650;oxygen_teleportation;Oxygen: Teleportation;;\noxygen-core;1651;oxygen_core;氧气核心;Oxygen Core;\nlead-villagers;1652;leadvillagers;带领村民;Lead Villagers;\ndelta-hard-mode;1653;deltahardmode;Delta 困难模式;Delta Hard Mode;\nid-squeezer-tweak;1654;squeezerpatch;ID Squeezer Tweak;;\nfood-ores;1655;;食物矿石;Food Ores;\nsit;1656;sit;席地而坐;Sit;\n;1657;;DIO招式还原;;\n;1658;;新世代生存助手;Miobot;\n;1659;youxinjiyi;优辛记忆;YOUXIN Memory;YXM\ncplpibalds-tweaks;1660;pitweaks;CplPibald的微调;CplPibald's Tweaks;\nore-biome;1661;;矿石生态群系;Ore Biome;\nfarm-adventure;1662;farm_adventure_ii,farm_adventure;农场冒险;Farm Adventure;FA\nthe-shadow-world;1663;shadowworld,DynamicLights;暗影世界;Shadow World;\nproject-306141;1664;japaricraftmod;加帕里公园;JapariCraftMod;\nmchorses-mclib;1665;mclib;McHorse's McLib;;\nmusiccraft-mod;1666;musiccraft;音乐工艺;MusicCraft mod;\nhardcore-darkness;1667;hardcoredarkness;真实的黑夜;Hardcore Darkness;HD\ncola-craft;1668;colacraft;可乐工艺;Cola Craft;\n;1669;ifaz;粒法杖;Particle Wands;\nbackstab;1670;backstab;背刺;Backstab;\ndynamic-stealth;1671;dynamicstealth;动态潜行;Dynamic Stealth;\n;1673;infinitesimalzeros;Infinitesimal Zeros;;\nroughly-enough-items;1674;roughlyenoughitems;REI物品管理器;Roughly Enough Items;REI\nmodmenu;1675;modmenu;模组菜单;Mod Menu;\narchitectury-quilt;1676;reiaddons;REI Addons;;\nilluminations;1677;illuminations;Illuminations 🔥;;\nmappy;1678;mappy;Mappy;;\nmouse-wheelie;1679;mouse-wheelie;急速滚轮;Mouse Wheelie;\nmrcrayfishs-vehicle-mod;1680;vehicle;MrCrayfish的载具;MrCrayfish's Vehicle Mod;CVM\ncc-tweaked;1681;cctweaked,computercraft;CC: Tweaked;;CC:T\naliensvspredator;1682;;异形大战铁血战士;AliensVsPredator;AVP\ndragon-murder;1683;dragonmurder;Dragon Murder;;\nbirds-nests;1685;birdsnests;鸟巢;Birds Nests;BN\nminecraft-animated;1686;mcanm;Minecraft Animated;;MCAnm\nrefined-machinery;1687;;精致机械;Refined Machinery;RM\nmagicalsculpture;1689;;魔法雕像;MagicalSculpture;MaS\nash-another-simple-hud;1690;umollu_ash;另一个简单的HUD;Another Simple HUD;ASH\ncustom-selection-box-port;1691;csb;自定义选择框;Custom Selection Box;CSB\nlibrikka-api;1692;librikka;Librikka API;;\nnot-enough-wands;1693;notenoughwands;更多魔杖;Not Enough Wands;\ndeep-mob-learning-blood-magic-addon;1694;deepmoblearningbm;深度怪物知识-血魔法插件;Deep Mob Learning - Blood Magic Addon;\ninformed-load;1695;informedload;更多加载信息;Informed Load;\nhuajiage-infinite-galaxy;1696;huajiage;滑稽纪元II:无尽星河;Huaji Age II:Infinite Galaxy;\n;1697;anothercommonbugfix;另一个通用Bug修复;Another Common Bug Fix;ACBF\ncqrepoured;1698;cqrepoured;寻找巧克力重铸版;Chocolate Quest Repoured;CQR\nfabricproxy;1699;fabricproxy;Fabric 群组支持;FabricProxy;\nex-sartagine;1700;exsartagine;锅具;Ex Sartagine;\nxaeros-minimap;1701;xaerominimap,xaerominimap_core;Xaero的小地图;Xaero's Minimap;XMM\neverlastingabilities-potioncore;1702;eapotioncore;永恒能力-药水核心;EverlastingAbilities-PotionCore;\noptifabric;1703;optifabric;OptiFabric;;\nthe-emerald-haven;1704;the_emerald_haven;翡翠天堂;The Emerald Haven;TEH\nsimple-grinder;1706;simplegrinder;简易研磨机;Simple Grinder;\nredstone-gauges-and-switches;1707;rsgauges;红石测量与开关;Gauges and Switches;\napotheosis;1708;apotheosis;神化;Apotheosis;\nlighting-wand;1709;lightingwand;照明魔杖 🌟;Lighting Wand 🌟;\nterrarium;1710;earth;地球;Terrarium;\nclimatic-biomes;1711;climaticbiomesjbg;Climatic Biomes;;\nhowling-moon;1712;howlingmoon;狼人;Howling Moon;\nhostile-worlds-invasions;1713;hw_inv;敌对世界-入侵;Hostile Worlds - Invasions;HW-INV\nsimple-storage-network;1714;storagenetwork;简单存储;Simple Storage Network;SSN\nadvent-of-ascension-nevermine;1715;EternIsles;虚无世界1;Eternal Isles;\nscaling-health;1716;scalinghealth;Scaling Health;;\nminecoprocessors;1717;minecoprocessors;微处理器;Minecoprocessors;\n;1718;maplelib;挖矿争霸;Dig Craft;DC\ndifficult-life;1719;difficultlife;Difficult Life;;\nmekanismores;1720;mekores;通用机械矿石;MekanismOres;\npyrotech;1721;pyrotech;火种科技;Pyrotech;PT\nrats;1722;rats;老鼠;Rats;\nserversync;1723;serversync;服务器同步;ServerSync;SS\n;1724;fisma;渔师与渔火;Fisma;\nauxilium-equivalence;1725;auxiliumequivalence,auxilium;等价交换辅助;Auxilium Equivalence / Auxilium;\nbionisation-4;1726;bionisation3,bionisation4;细菌病毒;Bionisation;\ntiny-mob-farm;1727;tinymobfarm;迷你刷怪场;Tiny Mob Farm;\nsoulus;1728;soulus;Soulus;;\nsanlib;1729;sanlib;SanLib;;\n;1730;bloodblade;炼狱刀「血腥」;BloodBlade;\n;1731;CameraStudio;延时摄影;Camera Studio;\ntravelers-backpack;1732;travellersbackpack,travelersbackpack;旅行者背包;Traveler's Backpack;\ngregtech-classic;1733;gtclassic;格雷科技经典版;GregTech Classic;GTC\naperture;1734;aperture;Aperture;;\ncombo-armors;1735;IC2CA;组合装甲;IC2ComboArmors;IC2CA\n;1736;;源质罐子统计;Thaumcraft Jar Checker;\nnonausea;1737;nonausea;没有反胃;NoNausea;NN\ncommunism-mod;1738;communism;苏维埃工坊;Communism Mod;\nmirror;1739;mirror;Mirror;;\nping;1740;ping;Ping;;\nthe-legends-mod;1741;legends;Legends;;\nbad-wither-no-cookie-reloaded;1742;badwithernocookie,bwncr;Bad Wither No Cookie - Reloaded;;BWNCR\ngiacomos-fishing-net;1743;giacomos_fishing_net;Giacomo's Fishing Net;;\nthe-hospital-mod;1744;;医院Mod;The Hospital Mod;THM\nmore-charcoal;1745;morecharcoal;更多木炭;More Charcoal;\nborn-in-a-barn;1746;biab;Born in a Barn;;biab\nbetter-animations-collection;1748;betteranimationscollection;更好的动物动作;Better Animations Collection;BAC\n;1749;flysword;御剑飞行;Fly Sword;\nquality-tools;1750;qualitytools;工具品质;Quality Tools;\ntoolscombine;1751;stc;工具组合;Tools Combine;\npollutant-pump;1752;pollutantpump;污染泵;Pollutant Pump;\ninfinite-pollution-filter;1753;infilter;无限污染过滤器;Infinite Pollution Filter;\njei-hider;1754;jeihider;JEI Hider;;\nfastflyblockbreaking;1755;fastflyblockbreaking;飞行速破;FastFlyBlockBreaking;\nplanttech-2;1757;planttech2;植物科技2;PlantTech 2;PT2\ntoast-control;1758;toastcontrol;消息框控制;Toast Control;\n;1759;itemrender;渲染图片导出暗黑版;Item Render Dark;IRD\ncocoa-magic;1760;cbcraft;可可工艺;Cocoa Magic;\nbetterportals;1761;;更好的传送门;Better Portals;\nclassic-combat;1762;classiccombat;经典战斗;Classic Combat;\n;1763;AM;火影忍者;NarutoAnimeMod;\nless-lag;1764;lesslag;较少的滞后;Less Lag;\ngoodbye-grass;1765;goodbyegrass;没有草丛;Grassta la Vista;\nphosphor;1766;phosphor-lighting,phosphor;磷;Phosphor;\nclay-bucket;1767;claybucket;粘土桶;Clay Bucket;\nextra-anvils;1768;extraanvils;更多铁砧 / 更多砧;Extra Anvils;\nglassential;1769;glassential;精致玻璃;Glassential;\ntinkers-addons;1770;tinkersaddons,TinkersAddons;匠魂拓展;Tinkers' Addons;\nbarrels-drums-storage-more;1771;bdsandm;桶，圆桶，存储 & 更多;Barrels, Drums, Storage & More;BDSM\nwings;1772;wings;翅膀;Wings;\nup-and-down-and-all-around;1773;mysttmtgravitymod;颠倒世界;Up And Down And All Around;\nlost-souls;1774;lostsouls;失落的灵魂;Lost Souls;\nintelligent-energistics;1775;intellie;智能能源;Intelligent Energistics;\nender-compass;1776;endercompass;末影指南针;Ender Compass;\nic2-tweaker;1777;ic2_tweaker;IC2 Tweaker;;\nthaumic-gadgets;1778;tg;神秘宝具;Thaumic Gadgets;TG\nmulti-mine;1779;multimine;多人开采;Multi Mine;\nextra-foam-for-liteloader;1780;EFFLL (LiteLoader ObjectHolder fix);Liteloader 修复;Extra Foam For LiteLoader;EFFLL\nembersified;1781;embersified;余烬能量转换;Embersified;\nthaumic-grid;1783;thaumicgrid;神秘网络;Thaumic Grid;\nback-tools;1784;backtools;工具后置 / 后背工具展示;Back Tools;\nmapwriter-2;1785;MapWriter;地图作者2;Mapwriter 2;MW2\ndartcraft-reloaded;1786;dartcraftreloaded;达特工艺重置版;Dartcraft Reloaded;DR\n;1787;mi;更多物品;More Item;MI\nceu;1788;ceu;CEU;;\nfantastic-lib;1789;fantasticlib;Fantastic Lib;;\ndm2;1790;;龙骑士2;Dragon Mounts 2;DM2\ntiquality;1791;tiquality;Tiquality;;\nloot-capacitor-tooltips;1792;lootcapacitortooltips;电容信息显示;Loot Capacitor Tooltips;\nclassic-bars;1793;classicbar;经典状态条;Classic Bars;\noverground-ores;1795;oo;地表矿石;Overground Ores;\ntouhou-little-maid;1796;touhou_little_maid;车万女仆;Touhou Little Maid;TLM\n;1797;allweapon;万物皆可为兵刃;Allweapon;\neureka;1798;eureka;Eureka;;\nlazy-ae2;1799;threng;懒人AE2;Lazy AE2;LAE\nlibnine;1800;libnine;LibNine;;\nequaldragons;1801;;EqualDragons;;\nberries;1802;berriespp;Crops++;;\ninfinity-gauntlet-mod;1803;infinitygauntlet;无限手套;Infinity Gauntlet;\nmighty-ender-chicken;1804;mightyenderchicken;强大的末影鸡;Mighty Ender Chicken;\nmalisisadvert;1805;malisisadvert;广告牌;MalisisAdvert;MA\nido;1806;ido;移动;Idō;\nbat-happy-mod;1807;bathappymod;🦇蝙蝠快乐MOD;🦇 Bat Happy Mod;\nviescraft-airships;1808;vc,viescraftmachines;维斯工艺-飞艇！;ViesCraft - Airships!;\nthe-ultimate-dimension;1809;rt;究极次元;The Ultimate Dimension;TUD\nadvanced-mortars;1810;advancedmortars;更好的研钵;Advanced Mortars;\n;1811;epic_journey;史诗征程;Epic Journey;EJ\ncrafttweaker-utils;1812;ctutils;CraftTweaker Utils;;\n;1814;elementcore;元素核心;ElementCore;EC\nfps-reducer;1815;fpsreducer;FPS减速器;FPS Reducer;\nfreelook;1816;freelook;自由视角;FreeLook;\nspacecraftx;1817;spacex;航天高科X;SpaceCraftX;\n;1818;micdoodlecore;MicdoodleCore;;\nbamboo-everything;1819;bambooeverything;竹子制品;Bamboo Everything;\nnofog;1820;nofog;没有雾;NoFog;\nessentia-brazier;1821;brazier;源质火盆;Essentia Brazier;\nantipotionmod-1-12-2;1822;antipotionmod;无药水效果;AntiPotionMod;APM\nquark-oddities;1823;quarkoddities;夸克-奇思妙想;Quark Oddities;\nfast-food;1824;fastfood;快餐;Fast Food;\nmore-carrots;1825;more_carrots;更多的萝卜;More Carrots;\nd-maneuver-gear;1826;3DManeuverGear;3D立体机动装置;3D Maneuver Gear;\nweapon-craftery;1827;weapon_craftery;武器工匠;Weapon Craftery;\nbobos-supermarket;1828;bobossupermarket;Bobo的超市;Bobo's supermarket;\neven-more-apples;1829;mam;更多的苹果;More Apples;\nlucky-beans;1830;luckybeans;幸运豆;Lucky Beans;\ncooked;1831;cooked;Cooked!;;\nafaoe;1832;afaoe,afaoe2019;Amazing FoodStuffs;;AFAOE\nbirds-foods;1833;birdsfoods;Bird's Foods;;\nmorexfood;1834;morexfood;More XFood;;\nyummy;1835;yummy;美味;Yummy;\nspice-of-life-carrot-edition;1836;solcarrot;生活调味料：胡萝卜版;Spice of Life: Carrot Edition;\nrefined-exchange;1837;refinedexchange;Refined Exchange;;\nrs-requestify;1838;refinedstoragerequestify,rsrequestify;精致存储：请求者;Refined Storage: Requestify;RSR\npizzacraft;1839;pizzacraft;披萨工艺;PizzaCraft;\nsuper-factory-manager;1840;superfactorymanager,sfm;超级工厂管理器;Super Factory Manager;SFM\nsakura;1841;sakura;樱;Sakura;\nsips;1842;sips;更多液体容器;Sips;\ngregs-construct;1843;gtconstruct;格雷与匠魂;Greg's Construct;GTCon\nultra-awesome-tools-mod;1844;stm;超级工具;Super Tools / Ultra Awesome Tools Mod;STM\n;1845;;Galacticraft Planets;;\nlocks;1846;locks;锁;Locks;\nspecial-mobs;1847;specialmobs;特殊怪物;Special Mobs;\nadorn-for-forge;1848;adorn;Adorn;;\nmissingbits;1849;missingbits;MissingBits;;\n;1850;quickcarpet;QuickCarpet;;\n;1851;cli;物品分类器;Classifier for Items;\nlocky;1852;locky;Locky;;\nbaubleshud;1853;baubleshud;BaublesHud;;\n;1855;landcore;陆地工艺核心;LandCore;\n;1856;landcraft;陆地工艺;Land Craft;\nextra-golems;1858;golems;更多傀儡;Extra Golems;\ncraftablecreativemodifier;1859;ccm;可合成的创造模式强化头颅;CraftableCreativeModifier;CCM\nruins-structure-spawning-system;1860;ruins;遗迹;Ruins;\narmory-expansion;1861;armoryexpansion,armoryexpansion-iceandfire,armoryexpansion-conarm,armoryexpansion-custommaterials;匠魂盔甲扩展;Armory Expansion;\nwg-block-replacer;1862;wgblockreplacer;方块生成替换器;WorldGen Block Replacer;\nmulti-mob-core;1863;multimob;Multi Mob Library;;\nstructurize;1865;structurize;结构化;Structurize;\nimprovable-skills;1867;improvableskills;技能进阶;Improvable Skills 3;\n;1868;morecrafting;更多合成;MoreCrafting;MC++\nbounding-box-outline-reloaded;1869;bbor;自然生成建筑结构显示;Bounding Box Outline Reloaded;BBOR\nsauceconstruct;1870;sauceconstruct;Sauce Construct;;\ncolorful-hearts;1871;healthoverlay,colorfulhearts;更美观的血条;Health Overlay / Colorful Hearts;\nauto-feeder-helmet;1872;feederhelmet;自动进食头盔;Auto Feeder Helmet;\ncc-tweaked-fabric;1873;;CC: Tweaked for Fabric;;\nsimple-farming;1874;simplefarming;简单农业;Simple Farming;\nbigger-packets-please;1875;biggerpacketsplz;Bigger Packets Please;;\nlockyz-extra-dimensions-mod;1876;lockyzextradimensionsmod;更多维度世界;Lockyz Extra Dimensions Mod;LEDM\npsi-combat-magic;1877;psiaddons;Psi:Combat Magic;;\ndtphc;1878;dynamictreesphc;动态的树：潘马斯附属;Dynamic Trees - Pam's Harvestcraft Compat;\nlargefluidtank;1879;fluidtank;大型储罐;Large Fluid Tank;LFT\ntumbleweed;1880;tumbleweed;风滚草;Tumbleweed;\ncubicdynamictreescompat;1881;dynamictreees-cubicaddon;Cubic Chunks Dynamic Trees Addon;;\nthermallogistics;1882;thermallogistics;热力物流学;Thermal Logistics;\nbithop;1883;bithop;BitHop;;\nswordskillsapi;1884;swordskillsapi;SwordSkillsAPI;;\ndynamic-sword-skills;1885;dynamicswordskills;动态剑技;Dynamic Sword Skills;DSS\nmagical-instruments;1886;minst;魔法乐器;Magical Instruments;\nenderspawn;1887;enderspawn;末影龙再生成;EnderSpawn;\nanimus;1888;animus;Animus;;\nscootys-scp-lockdown-extras;1889;scootys_scp_mod;SCP重型收容区扩展;SCP: Lockdown Heavy Containment Zone Expansion;\nmodern-glass-doors;1890;glassdoor;时尚玻璃门;Modern Glass Doors;\nlemonlib;1891;lemonlib;柠檬库;LemonLib;\nstatues-rechiseled;1892;statues;雕像：重置;Statues: Rechiseled;\ntrashslot;1893;trashslot;垃圾槽;TrashSlot;\nplayer-plates-forge;1894;playerplates;更多压力板;Player Plates;\njust-sleep;1895;justsleep;我就睡个觉;Just Sleep;\nauto-network-lib;1896;autonetworklib;Auto Network Lib;;\nclick-machine;1897;clickmachine;自动连点器;Click Machine;\nmystical-adaptations;1898;mysticaladaptations;更好的神秘农业;Mystical Adaptations;\nreinforcedtools;1899;reinforcedtools;强化工具;ReinforcedTools;\nkilling-fall;1900;killing_fall;Killing Fall;;\nmore-dogs;1901;;更多狗狗;More Dogs;\nsnow-variants;1902;snowvariants;更好的雪;Snow Variants;\nsearchable-chests;1903;searchablechests;箱子搜索栏;Searchable Chests;\nbad-mobs;1904;badmobs;Bad Mobs;;\nmc-paint;1905;mcpaint;涂鸦;MC Paint;\ngoprone;1906;goprone;匍匐前进;GoProne;\nceiling-torch;1907;ceilingtorch;倒置火把;Ceiling Torch;\nair-hop;1908;airhop;多段跳;Air Hop;\nbetter-combat-mod;1909;bettercombatmod;更好的战斗;Better Combat Mod;\nftb-money;1910;ftbmoney;FTB 交易;FTB Money;FTBM\nrestricted-portals;1911;restrictedportals;传送门限制;Restricted Portals;\nvampirelib;1912;vampirelib;VampireLib;;\ningameconfigmanager;1913;igcm;游戏内配置器;InGameConfigManager;IGCM\nplayerrevive;1914;playerrevive;玩家救援;PlayerRevive;\nfarlands;1915;farlanders;边境之地;FarLands;\ninworldcrafting;1916;inworldcrafting;世界合成;InWorldCrafting;IWC\nfood-editor;1917;;食物编辑器;Food Editor;\ncustom-starter-gear;1918;customstartinggear;自定义初始装备;Custom Starter Gear;CSG\nct-tinycoal;1919;cttinycoal;CT 小型煤炭;CT TinyCoal;\ntweaker-gui;1920;tweakergui;TweakerGUI;;\ncrafttweaker-editor;1921;cteditor;CraftTweaker 编辑器;CraftTweaker Editor;CrTE\nunforgiving-void;1922;unforgivingvoid;不那么仁慈的虚空;Unforgiving Void;\nmoretweaker;1923;moretweaker;MoreTweaker;;\nriteclicker-mod;1924;riteclicker;小石子;RiteClicker Mod;\nforgiving-void;1926;forgivingvoid;仁慈的虚空;Forgiving Void;\nbread-craft;1927;mrgw_bread_craft_mod;面包工艺;Bread Craft;BrC\nalchemistry;1928;alchemistry;炼金化学;Alchemistry;\ndamage-control;1929;dmgcontrol;伤害控制;Damage Control;\nprestige;1930;prestige;成就点;Prestige;\n;1931;mmo,mcmmoplus;原版技能升级;Vanilla MMO+;VM\nenergy-converters;1933;energyconverters;能量桥接器;Energy Converters;\nsignpost;1934;signpost;路标;Signpost;\ndiagonal-panes;1935;fabric-diagonal-panes;对角线玻璃板;Diagonal Panes;\nforgelin;1936;Forgelin;Forgelin;;\nalchemylib;1937;alib,alchemylib;AlchemyLib;;AL\nexp-ore-block-mod;1938;exp_ore;经验矿石;Exp Ore Mod;\nmore-glowstone;1939;moreglowstone;更多荧石;More Glowstone;\nyukarilib;1940;;YukariLib;;\ngregic-additions;1941;gtadditions;Gregic Additions;;\ninstantlava;1942;instantlava;无限岩浆;InstantLava;\n;1944;snowpack;积雪;Snowpack;\nenchantment-descriptions;1945;enchdesc;附魔描述;Enchantment Descriptions;\nstreak;1946;streak;残影;Streak;\ndttc;1947;dynamictreestc;动态的树：神秘时代附属;Dynamic Trees - Thaumcraft Compat;\ndtbop;1948;dynamictreesbop;动态的树：超多生物群系附属;Dynamic Trees - Biomes O' Plenty Compat;\ndynamic-trees-compatibility-for-climatic-biomes;1949;climaticbiomesdt;动态的树：气候生态群系附属;Dynamic Trees Compatibility for Climatic Biomes;\ncapability-adapter;1950;rf-capability-adapter;ME功能适配器;ME Capability Adapter;MECA\ntf2-stuff-mod;1951;rafradek_tf2_weapons;军团要塞2;TF2 stuff mod;\nmob-control-wands;1952;micwands;怪物控制魔杖;Mob Control Wands;\n;1953;statues;雕像模组;Statues Mod;\nathenaeum;1954;athenaeum;Athenaeum;;\nore-excavation;1955;oreexcavation;矿石挖掘;Ore Excavation;OE\nstone-chests;1956;stonechests;石箱子;Stone Chests;\nkjlib;1957;kjlib;KJ库;KJLib;\niron-chest-minecarts;1958;ironchestminecarts;更多箱子矿车;Iron Chest Minecarts;ICM\nnether-chest;1959;netherchest;下界箱子;Nether Chest;\nmob-kill-messages;1960;mkmmod;击杀信息通知;Mob Kill Messages;\nbetterdispenser;1962;betterdispenser;能喂食的发射器;BetterDispenser;\nshulkerboxdisplay;1963;;潜影盒显示器;ShulkerBoxDisplay;\nmycommands;1964;mycommands;我的命令;MyCommands;\n;1966;miecraft;羊羊工艺;Miecraft;\nhearth-well;1968;hwell;Hearth Well;;\n;1969;;苹果科技;Apple Tech;AT\ntea-and-biscuits;1970;teaandbiscuits;茶和饼干;Tea And Biscuits;TAB\ncontrollable;1971;controllable;手柄控制;Controllable;\ngenetic-animals;1972;eanimod;遗传动物;Genetic Animals;\nstone-chest;1973;stonechest;石头箱子;Stone Chest;\niron-shulker-boxes;1974;ironshulkerbox;更多潜影盒;Iron Shulker Boxes;\nmob-dismemberment;1975;mobdismemberment;怪物肢解;Mob Dismemberment;\nzoo-wild-animals-rebuild;1976;zawa;野生动物园：重制版;Zoo & Wild Animals Rebuilt;ZAWA\nkottle;1977;kottle;Kottle;;\ncustomdrones;1978;drones;自定义无人机;CustomDrones;\ncall-to-battle-2-authentic-world-war-2-experience;1979;ctb;战争召唤2;Call To Battle 2;CtB2\nsome-like-it-dry;1980;somelikeitdry;Some Like It Dry;;\nyungs-better-caves;1981;bettercaves;YUNG 的洞穴优化;YUNG's Better Caves;\nresynth;1982;resynth;神农科技;Resynth;\nflatworld;1983;com.ch1a.flatworld,flatworld;开山棒;Flat World;\npymtech;1984;pymtech;皮姆科技;PymTech;\ncorail-tombstone;1985;tombstone;Corail 的墓碑;Corail's Tombstone;\n;1986;missing;消失的物品;Missing Things;\n;1987;maplelib;Maple Lib;;\narcane-archives;1988;arcanearchives;奥术档案馆;Arcane Archives;AA\ndivineweapon;1989;dweapon;神兵;DivineWeapon;DW\ncamera-obscura;1990;cameraobscura;朝花夕拾;Camera Obscura;\nanimales;1991;tfa;动物扩充;Animales;TFA\narmor-underwear-mod;1992;armorunder;盔甲内衬;Armor Underwear Mod;\nassisted-progression;1993;nucleus_teambr;进度小助手;Assisted Progression;\nmore-buckets;1994;morebuckets;更多桶;More Buckets;\nlaggoggles;1995;laggoggles;延迟监视;LagGoggles;\nthe-weirding-gadget;1996;weirdinggadget;古怪装置;The Weirding Gadget;\nenergysynergy;1997;energysynergy;能源协同;EnergySynergy;ES\nmine-and-slash-reloaded;1998;mmorpg;挖矿与砍杀;Mine and Slash;MS\n;1999;tfccellarsaddon;群峦冰窖;TFC Cellars Addon;\nmonster-swarm;2000;monsterswarm;怪物云集;Monster Swarm;\nSimplePipes;2001;simple_pipes_dep_container;Simple BC Pipes;;\n;2002;fabric_wool;Wool;;\namecs;2003;amecs;Amecs;;\nworld-primer;2004;worldprimer;World Primer;;\n;2005;autoharvest;自动收获;AutoHarvest;\nbookworm;2006;bookworm;Bookworm;;\nopeneye;2007;OpenEye;错误报告器;OpenEye;OE\nlet-sleeping-dogs-lie;2008;dogslie;嘘，别吵醒狗狗;Let Sleeping Dogs Lie;\nbspkrscore-updated;2009;bspkrscore;bspkrsCore Updated;;\nsuper-ores;2010;superores;超级矿石;Super Ores;\ngravelminer;2011;gravelminer;沙砾终结者;GravelMiner;\nclient-tweaks;2012;clienttweaks;客户端微调;Client Tweaks;\nsingle-use-crafting-table-mod;2013;otc;一次性工作台;Single-Use Crafting Table Mod;OTC\nget-back-to-home;2014;GetBackToHome;回家;Get Back to Home;\nexpanded-equivalence;2015;expequiv;等价扩展;Expanded Equivalence;\nschematica;2016;schematica;Schematica;;\nscp-craft-decline;2017;scpcraft;SCPCraft - 下降;SCPCraft - Decline;\ntetra;2018;tetra;Tetra;;\nminestrappolation-5;2019;minestrapp;Minestrappolation 5;;M5\nunderwater-biome;2020;underwaterbiome;深海生态;Underwater Biome;\ncreate;2021;create;机械动力;Create;\n;2022;;超级TNT;Super TNT;\n;2023;;坦桑石;Tanzanite;TZ\nenergeticsheep;2024;energeticsheep;脉冲羊;EnergeticSheep;\nensorcellation;2025;ensorcellation;万象;Ensorcellation;\ninventory-sorter;2026;inventorysorter;物品分拣;Inventory Sorter;\nspeedster-heroes;2027;speedsterheroes;闪电侠;Speedster Heroes;\n;2028;solidcmd;地狱门输入补丁;Solid Command UI;\ncurios;2029;curios;Curios API;;\nvillage-spawn-point;2030;villagesp,villagespawnpoint,villagespawnpoint-fabric;村庄出生点;Village Spawn Point;\nspawnercraft;2031;spawnercraft;刷怪笼合成;SpawnerCraft;\nopenfm;2032;openfm;开放电台;OpenFM;\nthe-dalek-mod;2033;thedalekmod;Dalek Mod;;\nmodular-diversity;2034;modulardiversity;模块化多样性;Modular Diversity;\nbotanic-bonsai;2035;botanicbonsai;植物魔法盆栽;Botanic Bonsai;\nmodular-magic;2036;modularmagic;模块化魔法;Modular Magic;\n;2037;portalsupercache;传送门超级缓存;Portal Super Cache;\nmagic-kingdoms-mod;2038;som;魔法王国;Magic Kingdoms Mod/Schools of Magic Mod;MK/SOM\nzollern-galaxy;2039;zollerngalaxy;卓伦星系;Zollern Galaxy;ZG\nmacaws-bridges;2040;mcwbridges;Macaw的桥梁;Macaw's Bridges;\ntrample-stopper;2042;tramplestopper;防止踩踏;Trample Stopper;\nmacaws-roofs;2043;mcwroofs;Macaw的屋顶;Macaw's Roofs;\nshadows-of-greg;2044;gtadditions,gregtech;格雷之影;Shadows of Greg;SOG\nloottabletweaker;2045;lttweaker;LootTableTweaker;;\nrockycore;2046;rockycore;RockyCore;;RC\ntree-tweaker;2047;treetweaker;Tree Tweaker;;\nworleys-caves;2048;worleycaves;沃利的洞穴;Worley's Caves;\nadditional-structures;2049;additionalstructures;失落废墟;Additional Structures/Rex's Additional Structures;AS\n;2050;customtrees;自定义树木;Custom Trees;\n;2051;mcmooncake;MC牌月饼;MC Moon Cake;\n;2052;dao_za_vanillaplus;稻砸原版增强;Dao_Za_Vanillaplus;\n;2053;foml;Fabric OBJ Model Loader;;FOML\nenhancedvisuals;2054;enhancedvisuals;增强视觉效果/拓展视觉效果;EnhancedVisuals;\npotion-fingers-redux;2055;potionfingers;药剂环/药水指环;Potion Rings;\nballoon-sheep;2056;balloonsheep;气球羊;Balloon Sheep;\nfarming-for-blockheads;2057;farmingforblockheads;农场贸易;Farming for Blockheads;\noreexcavation-integration;2058;oeintegration;矿物开凿;Ore Excavation Integration;\nengineers-workshop;2059;engineersworkshop;工程师工作站;Engineers Workshop;\nthe_legend_of_the_brave;2060;thelegendofthebraveii;勇者传说;The Legend of The Brave;\ntwerk-sim-2k16;2061;ts2k16;Twerk Sim 2K16;;\nfart-generator;2062;fartgen;屁能发电机;Fart Energy Generator;\nex-nihilo-fabrico;2063;exnihilofabrico;无中生有：Fabrico;Ex Nihilo Fabrico;\nholoinventory;2064;holoinventory;物品显示/物品清单;HoloInventory;\nreactor-stuff;2065;reactor_stuff;反应堆材料;Reactor Stuff;\ntraitors-better-swampland-mod;2066;swampland;更好的沼泽;Traitor's Better Swamplands Mod;\nsoviet-abandoned-lab-mod;2067;soviet;苏维埃风格装饰;Soviet Era;SL\n;2068;;生存必备;Survival Essentials;SuE\ncharm;2069;charm;Charm;;\nvillage-names;2070;villagenames;村庄名字;Village Names;VN\nadvanced-rocketry-tweaker;2071;art;Advanced Rocketry Tweaker;;ART\nalternating-flux;2072;alternatingflux;交变磁通;Alternating Flux;AF\nprojectextended;2073;projectextended;等价交换扩展;ProjectExtended;\ntfc-metallum;2074;tfcmetallum;群峦合金;TFC：Metallum;TFCM\nreactor-turbines-mod-for-ic2;2075;reactor_turbines;Reactor Turbines Mod for IC2;;RTM\nmutant-beasts;2076;mutantbeasts;突变生物重置版;Mutant Beasts;\njei-integration;2077;jeiintegration;JEI扩展;JEI Integration;\nfunny-fruit;2078;funnyfruit;滑稽果;FunnyFruit;\nlife-tech;2079;lifetech;生命工艺;Life Tech;LT\nexponential-power;2080;exponentialpower;指数能源;Exponential Power;\ngirl-armor-mod;2081;vgirlarmor;女式盔甲;V-Girl Armor Mod;\n;2082;theworlddisintegratespickaxe;瓦解世界的镐子;The World Disintegrates Pickaxe;TWDP\nnomadic-tents;2083;yurtmod,nomadictents;游牧帐篷;Nomadic Tents;NT\nzetta-industries;2084;zettaindustries;泽它重工;Zetta Industries;ZI\nkekztech;2085;kekztech;KekzTech;;\nby-the-gods;2086;btg;神迹;By The Gods;BTG\nbee-angry-est;2087;beeangry-est;愤怒的蜜蜂;Bee Angry-est;\ntitanium;2088;titanium;钛;Titanium;\nchemlib;2089;chemlib;Chem Lib;;\ncarrots-lib;2090;carrots;胡萝卜库;Carrots Library;\nno-hostiles-around-campfire;2091;nhacampfire,nohostilesaroundcampfire,nohostilesaroundcampfire-fabric;围火无怪;No Hostiles Around Campfire;\nhealing-campfire;2092;regrowth,healingcampfire,healingcampfire-fabric;治愈营火;Healing Campfire;\nminetunes;2093;mineTunes;MineTunes;;\njetif;2094;jetif;物品丢入流体;Just Enough Throwing In Fluids;JETIF\nrftools-base;2095;rftoolsbase;RF工具：基础;RFTools Base;\nflopper;2096;flopper;流体漏斗;Flopper;\nexhausted-stamina;2097;exhaustedstamina;竭力攻击;Exhausted Stamina;\nstaminaplus;2098;staminaplus;耐力值;StaminaPlus;\nenhanced-armaments;2099;enhancedarmaments;增强装备;Enhanced Armaments;\nspatialcompat;2100;spatialservermod;空间兼容;SpatialCompat;\nextended-reach;2101;;Extended Reach;;\nno-recipe-book;2102;norecipebook;没有配方书;No Recipe Book;\ndarknesslib;2103;darknesslib;DarknessLib;;\nterrafirmacraft;2104;tfc;群峦传说：次世代;TerraFirmaCraft: The Next Generation;TFC:TNG\nredstone-control;2105;rs_ctr;红石控制;Redstone Control;\nsnow-real-magic;2106;snowrealmagic;雪！真实的魔法！⛄;Snow! Real Magic! ⛄;\ncomforts;2107;comforts;舒适用品;Comforts;\nplasmacannon;2108;plasmacannons;等离子炮;PlasmaCannon;\nplasmaengines;2109;;等离子引擎;PlasmaEngines;\nbonny-food;2110;;Bonny Food;;\nemerging-technology-hydroponics;2111;emergingtechnology;新兴技术;Emerging Technology;\n;2112;ej2,epicjourney;史诗征程2;Epic Journey2;EJ2\ntwilightopia-phantasy;2114;twilightopia;暮托邦之梦;Twilightopia;TTP\nengineers-decor;2115;engineersdecor;工程师的装饰;Engineer's Decor;\nengineers-tools;2116;engineerstools;工程师的工具;Engineer's Tools;\nwesleys-roguelike-dungeons;2117;wrd;Wesley的冒险地牢;Wesley's Roguelike Dungeons;WRD\nxp-tome;2118;xpbook;经验之书;Xp Tome;\nvaried-commodities;2119;variedcommodities;Varied Commodities;;\ntinkers-things;2120;tiths;匠魂扩增;Tinkers' Things;TITHS\njust-enough-buttons;2121;justenoughbuttons;更多的按钮;Just Enough Buttons;JEB\nclock-hud;2122;clockhud;时间显示器;Clock HUD;\ntektopia;2123;tektopia;桃花源记/特克托皮亚;TekTopia;TTP\n;2125;lolipickaxe;氪金萝莉;LoliPickaxe;\nfabric-language-kotlin;2126;fabric-language-kotlin;Fabric Language Kotlin;;FLK\nftb-backups-forge;2127;ftbbackups;FTB备份;FTB Backups;\ncyclopstek;2128;cyclopstek;独眼巨人;CyclopsTek;\n;2129;;健康生活;Stay Healthy;SH\nchampions;2130;champions;冠军/强敌;Champions;\ndanger-lies-ahead;2131;dangerliesahead;前路危机;Danger Lies Ahead;\ncroploadcore;2132;croploadcore;CropLoadCore;;\ndefiled-lands;2133;defiledlands;污秽之地;Defiled Lands;\narcanum;2134;arcanum;奥秘;Arcanum;\nrandom-enchants;2135;randomenchants;随机附魔;Random Enchants;\nmuch-more-spiders-v2;2136;muchmorespiders;更多种类的蜘蛛;Much More Spiders;\ncompressed-items;2137;ci;Compressed Items;;\nno-tree-punching;2138;notreepunching;无树可撸;No Tree Punching;NTP\nharvest-festival-legacy;2139;harvestfestival;丰收物语移植版;Harvest Festival Legacy;HFL\nfriendlymobs;2140;friendlymobs;友好的怪物;FriendlyMobs;\nsimplecore-api;2141;simplecorelib;简单前置;SimpleCore API / SimpleCoreLib;\ntopography;2142;topography;地形;Topography;\njei-villagers;2143;jeivillagers;JEI村民交易查看;JEI Villagers;\nbnbgamingcore;2144;bnbgamingcore;BnBGamingCore;;\nigauntlet;2145;;灭霸手套;IGauntlet;\nalcatrazcore;2146;alcatrazcore;Alcatraz Core;;\ndebugserverinfo;2147;debugserverinfo;DebugServerInfo;;DSI\nthe-midnight;2148;midnight;午夜;The Midnight;\nmoolands;2149;moolands;奶牛世界;Moolands;\negg-craft;2150;egg_craft;蛋工艺;Egg Craft;\ntinkersextras;2151;tinkersextras;Tinkers Extras;;\npoke-lucky;2152;pokelucky;宝可梦幸运方块;PokeLucky;\nusefultnt;2153;UsefulTNT;更有用的TNT;UsefulTNT;\n;2154;betterfeatures;更好的特性;Better Features;BF\ncrow-flight;2155;crowflight;乌鸦坐飞机;Crow Flight;\nfindme;2156;findme;东寻西觅;FindMe;\nminecraft-transit-railway;2157;mtr;我的世界铁路;Minecraft Transit Railway;MTR\nrandom-loot-mod;2158;randomloot;随机战利品;Random Loot Mod;\n;2159;erc;超级过山车;ExRollerCoaster;ERC\ntinkers-aether;2160;tinkersaether;天境工匠;Tinkers Aether;TA\nmine-and-slash-auto-compatibility;2161;azurecompat;挖矿与砍杀自动兼容;Mine and Slash Auto Compatibility;M&S Auto\nmetallurgy-core;2162;MetallurgyCore;冶金核心;MetallurgyCore;\nblank-planet;2163;BlankPlanet;BlankPlanet;;\nbetterachievements;2164;BetterAchievements;更好的成就;Better Achievements;\nextra-bows;2165;extrabows;更多的弓;Extra Bows;\nforgotten-relics;2166;ForgottenRelics;失落遗物学;Forgotten Relics;\nore-core;2167;orecore;矿石核心;Ore Core;\nordinary-coins;2168;ordinarycoins;普通硬币;Ordinary Coins;\npeanut-mod;2169;peanutmod;花生！;Peanuts!;\n;2170;;蝙蝠耳;Bat's Ear;\nin-game-wiki-mod;2171;igwmod;In Game Wiki;;IGW\nbetterbedrockgen;2172;betterbedrockgen,BBG;更好的基岩;Better Bedrock Gen;BBG\nwsbim-legacy-blocks-and-items;2173;wsbim_legacy;WSBIM: Legacy Blocks and Items;;\nstorage-items-mod;2174;sim;Storage Items Mod;;SIM\nwhat-should-be-in-mc-wsbim;2175;;What Should Be in MC;;WSBIM\nbluestone;2176;;Bluestone;;\neffortless-building;2177;effortlessbuilding;Effortless Building;;\nic2-classic-tweaker;2178;ic2_classic_tweaker;IC2 Classic Tweaker;;\nIc2c-Crop-Overrides;2179;ic2c_crop_overrides;IC2经典版作物覆盖;IC2C Crop Overrides;\nic2c-extras;2180;ic2c_extras;工业时代2经典版拓展;IC2C Extras;\nadvanced-solars-classic;2181;advancedsolars;高级太阳能经典版;Advanced Solars Classic;\npower-chisels;2182;;能量凿子;Power Chisels;\nic2c-custom-soils;2183;ic2c_custom_soils;IC2C Custom Soils;;\nhoming-exp-orbs;2184;homingexporbs;经验球归航;Homing Exp Orbs;\nenchantmentglint;2185;enchantmentglint;自定义附魔光效;Jabelar's Truly Magical Enchantment Glints;\nfloricraft;2186;floricraft;花卉工艺;Floricraft;FC\nweisscore;2187;WeissCore;WeissCore;;\nglibys-physics;2188;glibysphysics;Gliby的物理;Gliby's Physics;\nelectro-magic-tools-ic2-classic;2189;;电子神秘工具;Electro-Thaumic Tools - IC2 Classic;\nforceasciifont;2190;forceasciifont;强制ASCII字体;ForceASCIIFont;\nskewers;2191;;烤串;Skewers;\nget-in-the-bucket-mod;2192;getinthebucketmod;进入水桶;Get In The Bucket Mod;GITBM\nfertilization;2193;fertilization;施肥;Fertilization;\nadditional-banners;2194;additionalbanners;更多旗帜;Additional Banners;\ndropt;2195;dropt;Dropt;;\nthe-elven-forest;2196;the_elven_forest;精灵森林;The Elven Forest​;EF\nthe-beneath;2197;beneath;深渊世界;The Beneath;\nnot-enough-characters;2198;nechar;NEI 拼音搜索;Not Enough Characters;\n;2199;cookingwithtfc;Cooking with TFC;;\nmine-addons-2;2200;mineaddons;Mine Addons 2;;\nterrafirmacraftplus;2201;terrafirmacraftplus;群峦传说 +;Terrafirmacraft +;TFC+\nquick-consume-2;2202;;快速摄入2;Quick Consume 2;\nmctitle;2203;MCTitle;MCTitle;;\nmgui;2204;mutil,mgui;mutil / mGui;;\nsublib;2205;subcommonlib;SubLib;;\ndense-ores;2206;denseores;致密矿石;Dense Ores;\narchisections;2207;ArchiSections;ArchiSections;;\ncavecontrol;2208;CaveControl;洞穴控制;Cave Control;\nexpandedredstone;2209;ExpandedRedstone;红石扩展;Expanded Redstone;\nlegacycraft;2210;LegacyCraft;传统工艺;LegacyCraft;\ngeostrata;2211;GeoStrata;GeoStrata;;\nterritoryzone;2212;TerritoryZone;TerritoryZone;;\nloottweaks;2213;LootTweaks;LootTweaks;;\ncritterpet;2214;CritterPet;CritterPet;;\ncondensedores;2215;CondensedOres;Condensed Ores;;\ntreeclimbing;2216;TreeClimbing;Tree Climbing;;\nmore-electric-tools;2217;mets;更多电力装置;More Electric Tools;METS\nwireless-fluid-terminal;2218;wft;无线流体终端;AE2 Wireless Fluid Terminal;WFT\nwireless-interface-terminal;2219;wit;无线接口终端;AE2 Wireless Interface Terminal;WIT\nwireless-pattern-terminal;2220;wpt;无线样板终端;Wireless Pattern Terminal;WPT\nbetter-questing-rf-expansion;2221;bq_rf;更好的任务-RF拓展;Better Questing - RF Expansion;\nhurt-animation-remover;2222;hurtanimationremover;Hurt Animation Remover;;\nmore-cauldrons;2223;morecauldrons;更多炼药锅;More Cauldrons;\nparabox;2224;parabox;Parabox;;\nsky-grid;2225;skygrid;Sky Grid;;\nforbidden-arcanus;2226;forbidden_arcanus;禁忌与奥秘;Forbidden and Arcanus;FAA/F&A\nmysticalmechanics;2227;mysticalmechanics;奇械学;Mystical Mechanics API;\nbackpacked;2228;backpacked;背包;Backpacked;\nstarship;2229;;星舰;StarShip;\ntweakeroo;2230;tweakeroo;Tweakeroo;;\nintegrated-rest;2231;integratedrest;Integrated REST;;\ncitadel;2232;citadel;Citadel;;\nscreenshot-browser;2233;screenbrowser;内置截图管理器;Screenshot Browser;\n;2235;;R键整理复兴版;Inventory Tweaks Reborn;\ncurio-of-undying;2236;curioofundying,charmofundying;不死图腾插槽/不死图腾槽位;Curio of Undying / Trinket of Undying / Charm of Undying;\nexplorercraft-worldexpansion;2237;explorercraft;探险者;Explorercraft;\nenigmatic-legacy;2239;enigmaticlegacy;神秘遗物;Enigmatic Legacy;\nend-reborn;2240;endreborn;末地：重生;Endorium;ER\n;2241;eternalfrost;永恒的冰霜;Eternal Frost;EF\nezstorage-2;2242;;EZ存储2;EZStorage 2;EZ2\nmore-chickens;2243;morechickens;更多鸡扩展;More Chickens;\neoa-fa;2244;lotrfa;魔戒：第一纪元;Eras of Arda: The First Age Legacy submod;LOTR:FA\nthe-rc-mod;2245;thercmod;The RC Mod;;\nrubybyimxiaoanag;2246;ruby;红宝石;Ruby;\njojo-hamon-era;2247;jojohe;JoJo: 波纹时代;JoJo: Hamon Era;\n;2248;horodimensionweapons;次元装备;Horo's Dimension Weapons;\nOpenSecurity;2249;opensecurity;开放式安全;OpenSecurity;\nthe-legend-era;2250;legendera;传说纪元;The Legend Era;LE\nlevel-up-hp-forge;2251;leveluphp;生命提升;Level Up HP;\nmore-planet-extras;2252;moreplanetsextras;More Planets Extras;;\nrandompatches;2253;randompatches;随意修复;RandomPatches;\nships-mod;2254;ships;船;Ships Mod;\ntfc-metric;2255;tfcmetric;公制重量转换;TFC Metric;\n;2256;tfcm;TerraMisc;;\n;2257;lwstfc;群峦水袋;Leather Water Sac for TFC;\n;2258;;群峦贸易;Merchants Addon for TFC;\ntfc-additions;2259;tfcadditions;群峦附加;TFC-Additions;\nraolcraft-w;2260;raolcraft;RaolCraft Ω;;\nlitematica;2261;litematica;投影;Litematica;\ntelepastries;2262;telepastries;传送蛋糕;TelePastries;\nheroesexpansion;2263;heroesexpansion;超级英雄拓展;HeroesExpansion;HE\nattributefix;2264;attributefix;属性修复;AttributeFix;\ndynamic-transport;2265;dynamictransport;动态运输;Dynamic Transport;\ntool-builder;2266;toolbuilder;铸器师;Tool Builder;\nbountifulbaubles;2267;bountifulbaubles;丰富的饰品;Bountiful Baubles;\n;2268;;生命的真谛;The meaning of life;TMOL\nmanacraft;2269;;魔能工艺;ManaCraft;\n;2270;;明月合成;MoreCraftingMoon;MCM\n;2271;monsterlegend;怪物传说;MonsterLegend;\n;2273;ic2_money;真·工业货币;ICMoney;ICM\nforge-backpacks;2274;backpack;更多背包;Backpacks;\nrepurpose;2275;repurpose;Repurpose;;\nunlimited-chisel-works;2276;unlimitedchiselworks;Unlimited Chisel Works;;\nkfc;2277;thekfcmod;The KFC Mod;;\nrainbow-oak-trees;2278;rainboaks;彩虹橡树;Rainbow Oak Trees;\nspawntabletweaker;2279;spawntabletweaker;生成调整;SpawnTableTweaker;STT\nlarge-ore-deposits;2280;adlods;大型矿床;Large Ore Deposits;LOD\ngregtech6-unofficial;2281;gregtech;格雷科技6非官方版;GregTech 6 Unofficial;GT6U\nmahou-tsukai;2282;mahoutsukai;魔法使;Mahou Tsukai;MT\n;2283;customevents;自定义事件;Custom Events;\nmoar-tinkers;2284;moartinkers;Moar Tinkers;;\nbaubley-heart-canisters;2285;bhc;心之容器;Baubley Heart Canisters;BHC\njaopcasingularities;2286;jaopcasingularities;JAOPCA奇点;JAOPCA Singularities;\nlabymod;2288;labymod;LabyMod;;\n;2289;mcty;MC牌汤圆;Tangyuan In MineCraft;MCTY\nguidebook;2291;gbook;Guidebook;;\nlithium;2292;lithium;锂;Lithium;\nleft-2-mine;2293;;求生之路;Left 2 Mine;L2M\nworldeditcui-forge-edition-2;2294;worldeditcuife2;WorldEditCUI Forge Edition 2;;\nworld-book;2295;worldbook;World Book;;\nuppers;2296;uppers;倒置漏斗;Uppers;\nspartan-weaponry;2297;spartanweaponry;斯巴达的武器;Spartan Weaponry;\nmalilib;2298;malilib;Masa's Litemod Library;;MaLiLib\ninfraredstone;2299;infraredstone;InfraRedstone;;\nincorporeal;2300;incorporeal;幻想多媒体;Incorporeal;\namun-ra;2301;GalacticraftAmunRa;Amun-Ra;Pra's Galacticraft Mod;AR\n;2302;VocaloidMod;虚拟歌姬;Vocaloid Mod;\nmysterious-mountain-lib;2303;mmlib,mysterious_mountain_lib;妖怪之山通用库;MysteriousMountainLib;MMLib\nloottweaker;2304;loottweaker;LootTweaker;;\nrediscovered;2305;rediscovered;特性重现;Rediscovered;\n;2307;gvclib;GVCLib;;\nbaubles-reborn;2308;baubles;饰品栏重置版;Baubles Reborn;\npams-brewcraft;2309;pambrewcraft;潘马斯酿造工艺;Pam's Brewcraft;\ndirtcraft;2310;dirtcraft;泥土工艺;DirtCraft;\nminihud;2311;minihud;迷你HUD;MiniHUD;MH\npotato-craft;2312;pc;土豆工艺;Potato Craft;\n;2313;;自定义矿脉生成器;Ore Generator;OreGe\nbed-patch;2314;bedpatch;Bed Patch;;\n;2315;crafttweakersync;CT脚本同步;CraftTweakerSync;CTS\ndata-loader-forge;2316;dataloader;Data Loader;;\nlevel-up-hp-easy-start;2317;leveluphp;Level Up HP Easy Start;;\nsimply-light;2318;simplylight;更好的灯;Simply Light;\nlightning-bolt-mod;2319;lightning_bolt;雷电攻击;Lightning Bolt Mod;\nspartan-and-fire;2320;spartanfire;斯巴达之冰与火;Spartan and Fire;\n;2321;shadersmod;光影模组核心;Shaders Mod Core;GLSL\nbeacons-for-all;2322;beaconsforall;Beacons For All;;\n;2323;chaoswither;混沌凋灵;Chaos Wither;\nmore-flowers;2324;moreflowers;更多功能花;More Flowers;\nbuzzier-bees;2326;buzzier_bees;Buzzier Bees;;\n;2327;plne;原版一键整理;Vanilla Mod Neat;VMN\nlollipop;2328;lollipop;棒棒糖;Lollipop;\nenemyz;2329;enemyz;敌意显示;Enemyz;\nthaumic-restoration;2330;thaumicrestoration;神秘复辟;Thaumic Restoration;\n;2331;vimi;连锁矿工数据包;Vein Miner Datapack;\n;2332;;快速落叶 (伪);Fast Leaf Decay;\nprimal-boat;2333;primalboat;原始船舶;Primal Boat;\neyes-in-the-darkness;2334;eyesinthedarkness;Eyes in the Darkness;;\nthaumic-augmentation;2335;thaumicaugmentation;神秘进阶;Thaumic Augmentation;TA\nlost-magic;2336;lostmagic;失落的魔法;Lost Magic;\nthaumic-arcana;2337;thaumic_arcana;Thaumic Arcana;;\nthaumic-wonders;2338;thaumicwonders;神秘奇境;Thaumic Wonders;TWOND\ncrimson-warfare;2339;crimsonwarfare;Crimson Warfare;;\nterra-1-to-1-minecraft-world-project;2340;terra121;Terra 1 to 1;;\nmultiverse-pouch;2341;qwebnm_multiversepouch;多元宇宙袋;Multiverse Pouch;\nelectroblobs-wizardry-twilight-forest-spell-pack;2343;tfspellpack;巫术学：暮色森林法术包;Electroblob's Wizardry: Twilight Forest Spell Pack;\nblockbuster;2344;blockbuster;Blockbuster;;BB\nfishs-undead-rising;2345;mod_lavacow;Fish的不死崛起/Fish的亡灵崛起;Fish's Undead Rising;\ncloth-config;2346;cloth_config,cloth-config;Cloth Config API;;\njust-map;2347;justmap;Just Map;;\ntoo-realistic-inventory;2348;noinv;真实背包;Realistic Inventory;\nmob-farm;2349;mobfarm;生物农场;Mob Farm;\nacceleratorcraft;2350;acceleratorcraft;加速器工艺;AcceleratorCraft;AC\nminecraft-spongebob-squarepants-mod-for-the-1-7-10;2351;SpongebobModAddONTwo;Minecraft Spongebob Mod 2018 New;;SBM\nutility-worlds;2352;utilityworlds;实用世界;Utility Worlds;UW\ngoblin-traders;2353;goblintraders;哥布林商人;Goblin Traders;\ndivine-favor;2354;divinefavor;神的恩惠;Divine Favor;\nvolumetric-flask;2355;volumetricflask;量瓶;Volumetric Flask;\nloot-bag-mod;2356;lootbagmod;战利品包;Loot Bag Mod;\n;2357;customnpcsfix;自定义NPC补丁;CustomNPCsFix;NPCFix\nminecraft-dungeons-mod;2358;duneons;我的世界地下城;Minecraft Dungeons Mod;MCD\n;2359;musicsetting;音乐控制;MusicSetting;\nseasonal-bees;2360;seasonal_bees;季节性蜜蜂;Seasonal Bees;\ncarpet;2361;carpet;地毯;Carpet;\nhandmadegunsmodex;2362;handmadeguns;自定义枪械;HandmadeGuns;HMG\nequivalent-additions;2363;equivadditions;等价拓展;Equivalent Additions;\nmixinbootstrap;2364;mixinbootstrap;MixinBootstrap;;\npowah;2365;powah;Powah!;;\nnatural-pledge;2366;naturalpledge;自然誓约;Natural Pledge;\nmore-berries;2367;moreberries;更多浆果;More Berries;\nzeiyocraft;2368;zeiyocraft;ZeiyoCraft;;\ntime-stages;2369;timestages;阶段时间;Time Stages;\nadditional-events;2370;;Additional Events;;\nartemislib;2371;artemislib;ArtemisLib;;\npams-harvestcraft-2-food-core;2372;;潘马斯农场2;Pam's HarvestCraft 2;PHC2\neasy-retrogen;2373;retrogen;简易重生成;Easy Retrogen;\nextraplayerrenderer;2374;explayerenderer;额外玩家渲染;Extra Player Renderer;EPR\nrwbym;2375;rwbym;RWBY Mod;;RWBYM\nlibnonymous;2376;libnonymous;Libnonymous;;\nshattered-moon;2377;;Shattered Moon;;\ntinkersurvival;2378;tinkersurvival;匠魂生存;Tinkers' Survival;\nportablecraft;2379;;便携物品;PortableCraft;\n;2380;balance_equations;方程式配平;Balance Equations;BE\nbetter-than-bunnies;2381;betterthanbunnies;给兔子戴上礼帽;Better Than Bunnies;\narcane-essentials;2382;arcane_essentials;奥术精要;Arcane Essentials;\nthe-aurorian;2383;theaurorian;极光幽境;The Aurorian;TA\negg-auto-hatch;2384;egg-auto-hatch;鸡蛋自动孵化;Egg Auto Hatch;\nperformant;2385;performant;性能优化;Performant;\ntube-transport-system;2386;tts;管道传输系统;Tube Transport System;TTS\n;2387;dyntranslation;动态翻译;DynTranslation;DT\ntransmutation-alchemy;2388;;Transmutation Alchemy;;\nextra-spells-electroblobs-wizardry;2389;extra_spells;额外咒语;Extra Spells;\nancient-spellcraft;2390;ancientspellcraft;古代咒法;Ancient Spellcraft;\nmoving-elevators;2391;movingelevators;Moving Elevators;;\ninterstellar-exoplanets;2392;exoplanets;星际：系外行星;Interstellar: Exoplanets;Exo\ninfini-tic;2393;;Infini-TiC;;\nextra-disks;2394;extradisks;更多磁盘;Extra Disks;ExD\noptiforge;2395;optiforge;OptiForge;;\nmagic-seeds-for-electroblobs-wizardry;2396;t3s4ebw;巫术学魔法种子;Magic seeds for Electroblobs Wizardry;\nextra-alchemy;2397;extraalchemy;扩展炼药;Extra Alchemy;\nbeyond-the-veil;2398;beyondtheveil;帷幕彼端;Beyond The Veil;BTV\nmaple-syrup;2399;maplesyrup;枫糖;Maple Syrup;\nblood-particles;2400;blood_particles;血液粒子效果;Blood Particles;\nnyx;2401;nyx;Nyx;;\nlieutenant;2402;lieutenant;Lieutenant;;\nmatthews-difficulty-mod;2403;difficultymod;Matthew's Difficulty Mod;;\nold-combat-system;2404;oldcombatsystem;旧版战斗系统;Old Combat System;\nvampires-need-umbrellas;2405;;Vampires Need Umbrellas;;\nhypnotics;2406;hypnotics;安眠药;hypnotics;ZT\nvillager-market;2407;villagermarket;村民市场;Villager Market;\n;2408;;生存挑战;Survival Challenge;SC\nlinear;2409;linear;线性放置;Linear;\nimmersive-portals-for-forge;2410;immersive_portals;沉浸式传送门;Immersive Portals;ImmPtl\nuniversalbestiary;2411;universalbestiary;Universal Bestiary;;\nautumnity;2412;autumnity;秋原;Autumnity;\nworldgeneration-profiler;2413;worldgenerationprofiler,Worldgeneration Profiler Plugin;Worldgeneration Profiler;;\nchunk-in-a-globe;2414;globedimension;球内区块;Chunk In A Globe;\ndragon-mounts-legacy;2415;dragonmounts;龙骑士：传承;Dragon Mounts: Legacy;DML\nfruit-trees;2416;fruittrees,fruitfulfun;妙趣果园 🍊;Fruitful Fun 🍊;\njourney-into-the-light-mod;2417;journey,jitl,essence,eotg;光明之旅·神之本质;Journey Into the Light · Essence of the Gods;JITL\nkitchen-mod;2418;kitchen;厨房;The Kitchen Mod;\nmore-dungeons;2419;;更多地牢;More Dungeons;\nvoyage;2420;voyage;Voyage;;\nidealland;2421;idealland;理想境;Idealland;IDL\n;2422;;简便合成;EazyCrafting;EzC\n;2423;MattCore;MattCore;;\nsecond-screen;2424;;Second Screen;;\n;2425;scpex;SCP轻型扩展;SCP Expansion;\nfastasyncworldedit-forge;2426;worldedit;FastAsyncWorldEdit;;FAWE\narcanecraft;2427;;奥法工艺;ArcaneCraft;\nretrobees;2428;retrobees;RetroBees;;\nbeebetteratbees;2429;beebetteratbees;育蜂指南;BeeBetterAtBees;\ninverted-enchantments;2430;ivrench;负魔书;InvertedEnchantments;\ngeneral-laymans-aesthetic-spying-screen-glass;2431;generallaymansaestheticspyingscreen;General Laymans Aesthetic Spying Screen Glass;;GLASS\ndiy_title;2432;diytitle;自定义名称前后缀;diy_title;PPM\ntooltweaks;2433;tooltweaks;禁用原版工具;ToolTweaks;\natop;2434;ATOP,atop;超多生物群系护甲;Armor for Biomes O' Plenty;ATOP\ntinkers-forging;2435;tinkersforging;工匠锻造;Tinker's Forging;\ngravity-gun;2436;gravitygun;重力枪;Gravity Gun;\nmekanism-additions;2437;mekanismadditions;通用机械附加;Mekanism Additions;MekA\nsignpicture;2438;signpic;告示牌图片;Sign Picture;\nvampirism-integrations;2439;vampirism_integrations;吸血鬼联动;Vampirism Integrations;\ngottschcore;2440;gottschcore;GottschCore;;\nnot-enough-crashes;2441;notenoughcrashes;崩溃优化;Not Enough Crashes;NEC\ncrash-to-main-menu;2442;crash-to-main-menu;崩溃返回主菜单;Crash To Main Menu;\ntinkers-jei;2443;tinkersjei;Tinker's JEI;;\nbetter-questing-quest-book;2445;questbook;更好的任务-任务书;Better Questing - Quest Book;\nengineers-doors;2446;engineersdoors;工程师的门;Engineer's Doors;\ntps-generator;2447;tpsgenerator;TPS发电机;TPS Generator;\nbetter-drowning;2448;betterdrowning;更好的溺水;Better Drowning;\nbibliotheca;2449;bibliotheca;Bibliotheca;;\nkubejs;2450;kubejs;KubeJS;;KJS\ntickcentral;2451;tickcentral;TickCentral;;\nlowocalizatiwn;2452;lowocalization;lOwOcalizatiωn;;\nredlib;2453;redlib;RedLib;;\nmodern-ui;2454;modernui;现代化 UI;Modern UI;MUI\nauto-config-updated-api;2455;autoconfig1u;Auto Config Updated API;;\nmedieval-madness;2456;the_medieval_times;中世纪的疯狂;Medieval Madness;\ncaelus;2458;caelus;Caelus API;;\nxeocore;2459;xeocore;XeoCore;;\nwtbw_lib;2460;wtbw_lib;WTBW Lib;;\ndev-world-fabric;2461;devworld;Dev World (Fabric);;\nthreecore;2462;threecore;ThreeCore;;3C\nmoddirector;2463;moddirector;ModDirector;;\nfunction-api;2464;functionapi;Function API;;\ntargetingapi;2465;targeting_api;TargetingAPI;;\nre-targetingapi;2466;re-targeting_api;Re-TargetingAPI;;\ndominionlib;2468;;DominionLib;;\n;2469;salt_fish_craft;咸鱼工艺;Salt Fish Craft;SFC\nendergetic;2470;endergeticexpansion,endergetic;末地拓展;The Endergetic Expansion;\nswamp-expansion;2471;swampexpansion;Swamp Expansion;;\ngalactictweaks;2472;galactictweaks;GalacticTweaks;;GCT\ndungeon-tactics;2473;dungeontactics;地牢战术;Dungeon Tactics;\nvillagers-nose;2474;VillagersNose;村民的鼻子;Villager's Nose;\ndungeons2;2475;dungeons2;自定义地牢 2;Dungeons 2;\nlang_tool;2476;lengthen;加长工具;lang_tool;LT\n;2477;;Plans API;;\n;2478;;Labyrinths API;;\nemojiful;2479;emojiful;Emojiful;;\nitem-compatibility;2480;itemcompat;Item Compatibility;;\naether-lost-content;2481;lost_aether,lost_aether_content;天境：遗失之物;Aether: Lost Content Addon;\ngunpowderlib;2482;gunpowderlib;GunpowderLib;;\nnuclearcraft-overhauled;2483;nuclearcraft;核电工艺：重制版;NuclearCraft: Overhauled;NCO\nnei-lotr;2484;nei-lotr;魔戒NEI插件;NEI LotR;\nadventure-backpack;2486;adventurebackpack;探索者背包;Adventure Backpack;\nspeed-illusion;2487;speedillusion;Speed Illusion;;\nsupertic;2488;gk_super_tic;匠魂药水效果;SuperTiC;\nthe-bumblezone-forge;2489;the_bumblezone;蜜蜂领域/黄蜂领域;The Bumblezone;\nbeesourceful;2490;beesourceful;BeeSourceful;;\nmiddle-earth-industry;2491;mei;中洲工业;Middle-Earth Industry;MEI\nthut-wearables;2492;thut_wearables;Thut Wearables;;\nafter-the-drizzle;2493;afterthedrizzle;细雨田园;After the Drizzle;AtD\ncmdcam;2494;cmdcam;CMD 指令摄影机;CMDCam;\nclimate-control-geographicraft;2495;geographicraft;气候控制 / 地理世界;Climate Control / Geographicraft;CC/GC\ncosmetic-armor-reworked;2497;cosmeticarmorreworked;时装盔甲重置版;Cosmetic Armor Reworked;CAR\n;2498;moegadd;月蛋拓展;MoegAddon;\nefab;2499;efab;EFab;;\npotion-of-bees;2500;potionofbees;蜂群药水;Potion of Bees;\nwyrmroost;2501;wyrmroost;猛龙之居;Wyrmroost;WR\nproductivebees;2502;productivebees;资源蜜蜂;Productive Bees;\ngkolivers-super-tic;2503;gk_super_tic;Gkoliver's Super TiC;;\n;2504;;Loot Table Randomizer;;\nsongs-of-war-mod;2505;sow;战争之歌;Songs of War;SoW\nfluidict;2506;fluidict;FluiDict;;\nsquidcraft;2507;squidcraft;鱿鱼工艺;SquidCraft;\nclothesline;2508;clothesline;晾衣绳;ClothesLine;\nsiege;2509;siege;Siege;;\nfermion-core;2510;fermion;Fermion Lib;;\nomnitranslation;2511;ot;万能翻译;OmniTranslation;OT\nthe-egg-came-first;2512;tecf;先有蛋;The Egg Came First;\nnuclearcraft-helper;2513;nuclearcraft_helper;核电助手;NuclearCraft Helper;\nthe-hive-more-bee-content;2514;thehive;The Hive! More Bee Content;;\ntinkersmiddleearth;2515;lotrtc;中洲匠魂;Tinker's Middleearth;\ninventory-pause;2516;inventorypause;Inventory Pause;;\nsnowy-leaves;2517;snowy_leaves;霜花叶;Snowy Leaves;\nmaterialis;2518;materialis;Materialis;;\ntesseract;2519;tesseract;超立方体;Tesseract;\ncarrier-bees;2520;carrierbees;物流蜜蜂;Carrier Bees;\ntinkers-reforged;2521;tinkers_reforged;工匠再锻;Tinkers Reforged;\nmystical-world;2522;mysticalworld;魔幻世界;Mystical World;\nrecipes-for-all;2523;rfa;配方书全解锁;Recipes for All;RFA\ngregicality;2524;gtadditions;Gregicality Legacy;;GCY\nthe-lord-of-the-rings-mod-renewed;2525;lotr;魔戒：复兴;The Lord of the Rings Mod: Renewed;\nmodelloader;2526;modelloader;ModelLoader;;\nanimal-crops;2527;animalcrops;动物作物;Animal Crops;\nterrafirmacraftneiplugin;2528;TerraFirmaCraftNEIplugin;TerraFirmaCraftNEIplugin;;\nfurenikusroads;2529;furenikusroads;Fureniku的路;Fureniku's Roads;\npatchouli-quests;2530;patchoulitask;帕秋莉任务手册;Patchouli Quests;\nextraacademy;2531;exac;学园附属;ExtraAcademy;EXAC\niitemrenderer-reborn;2532;iitemrenderer;IItem Renderer Reborn;;\ntime-overhaul;2533;timeoverhaul;时间改革;Time Overhaul;TO\nsteamweapon;2534;stemweapon;梗多武器;StemWeapon;\ncounter-guns;2535;counter_guns;Counter Guns;;\nthaumcraft;2536;;神秘时代2;Thaumcraft 2;TC2\ngregtech-chill-edition;2537;;格雷科技轻松版;GregTech: Chill Edition;\ncustom-player-model-cpm;2538;custommodel;自定义玩家模型;Custom Player Model;CPM\nroughly-enough-resources;2539;roughlyenoughresources;Roughly Enough Resources;;RER\nso-many-enchantments;2540;somanyenchantments;更多附魔;So Many Enchantments;SME\nsisser;2542;sisser;Sisser;;\nhxcenchants;2543;HxCEnchants;多样化附魔;HxCEnchants;\nbetter-dropped-items;2544;betterdroppeditems;更好的掉落物;Better Dropped Items;\nhxc-core;2545;hxccore;HxC Core;;\nsurrounding-indicators;2546;surrounding_indicators;Surrounding Indicators;;\ngobber-fabric;2547;gobber2;戈伯2;Gobber2;\ngobber;2548;gb;戈伯;Gobber;\niblis;2549;iblis;恶魔;Iblis;\n;2550;;万能篝火;What's wrong with the campfire;\nthe-king-of-the-villagers;2551;kingvillager;村民之王;The King of the Villagers;\nbring-me-the-rings;2552;bmtr;Bring Me The Rings;;\nbloomful;2553;bloomful;Bloomful;;\npick-pocketer;2554;pickpocketer;扒手;Pick Pocketer;\nterraforged;2555;terraforged;TerraForged;;\nrad-metabolosis;2556;ncradmetabolosis;Rad Metabolosis;;\nmmd-orespawn;2557;orespawn;MMD OreSpawn;;\ntragicmc3;2558;tragicmc;悲惨世界3;TragicMC 3;TMC3\n;2559;;神秘时代1;Thaumcraft 1;TC1\nnot-enough-rtgs;2560;notenoughrtgs;更多RTG;Not Enough RTGs;\nqmd;2561;qmd;量子块动力学;Quantum Minecraft Dynamics;QMD\nenvironmental-rads;2562;ncenvironmentalrads;Environmental Rads;;EnvRads\nfission-based-neutron-collector;2563;fbnc;裂变能中子素收集器;Fission Based Neutron Collector;FBNC\ntrinity;2564;trinity;三位一体;Trinity;\nmacaws-windows;2565;mcwwindows;Macaw 的窗户;Macaw's Windows;\nmusic-player;2566;musicplayer;音乐播放器;Music Player;\ncsokicraftutil;2567;CsokiCraftUtil,csokicraftutil;CsokiCraftUtil;;\nlarge-enchants;2568;gud_largeenchants;Large Enchants;;\nangrysun;2569;angrysun;炙热太阳;AngrySun;\nits-not-long-enough;2570;itsnotlongenough;与现实相同的昼夜交替;It's Not Long Enough;INLE\nsunscreen-gel;2571;sunscreen;防晒霜;Sunscreen Gel;\nmuya1-7-10;2572;Muya;姆亚核心;MuyaCore;\nmacaws-furniture;2573;mcwfurnitures;Macaw的家具;Macaw's Furniture;\nmacaws-doors;2574;mcwdoors;Macaw 的门;Macaw's Doors;\nblueprint;2575;abnormals_core,blueprint;Blueprint / Abnormals Core;;\ngeyser-mod;2576;geysermod;Geyser Mod;;\nnot-enough-pets;2577;notenoughcats;更多宠物;Not Enough Pets;NEP\npotionextension;2578;PotionExtension;PotionExtension;;\ntorchslabs-mod;2579;torchslabmod;更好的火把放置;Torch Slabs Mod;\nrouterrebornlib;2580;routerrebornlib;RouterReborn Lib;;\nthe-magic-doorknob;2581;magic_doorknob;The Magic Doorknob;;\nultimate-skyblock-generator;2582;usrg;Ultimate Skyblock Resource Generator;;USRG\neveryone-poops-dung-fertilizer;2583;everyonepoops;肥料;Everyone Poops - Dung Fertilizer;\nascension-of-the-divine;2584;crustyburritosalotofstuffmod;Ascension of the Divine;;\nnebulaecraft;2585;;Nebulaecraft;;\ninventory-overlay;2586;inventoryoverlay;背包显示;Inventory Overlay;\nic2magma;2587;ic2magma;IC2Magma;;\niridium-source;2588;iridiumsource;铱矿产;Iridium Source;\nfrogcraft-rebirth;2589;frogcraftrebirth;青蛙化工:重生;FrogCraft: Rebirth;FC\ncoaxium-mod-1-12-2-ic2-addon;2590;fm;科亚烯;Coaxium Mod;\nenergy-control;2591;energycontrol;能源控制;Energy Control;EC\ngulliver-reborn;2592;gulliverreborn;Gulliver Reborn;;\n;2593;tamedmobs;可驯服的怪物;Tamed mobs;\nrationcraft;2594;rationcraft;Rationcraft;;\ndefault-options;2595;defaultoptions;Default Options;;\nender-mail;2596;endermail;末影邮递;Ender Mail;\nmore-overlays;2597;moreoverlays;更多叠加层;More Overlays;\nstefinus-3d-guns-mod;2598;stefinusguns;3D枪械;New Stefinus Guns;S3DGM\n;2599;movement;运动渲染;Movement;\nmoderncraft-core;2600;moderncraftcore,moderncraft-core;ModernCraft|Core;;\nlang_magic;2601;language_arts;言灵;lang_magic;YL\nnoexpensive;2602;noexpensive;不昂贵;NoExpensive;NE\nrough-mobs-revamped;2603;roughmobsrevamped;粗暴的怪物重置版;Rough Mobs Revamped;RMR\nfullbright;2604;fullbright;充满光明;Fullbright;\nbackpacks;2605;backpacks16840;背包！;Backpacks!;\n;2606;slashfortifier;拔刀强化器;Slash Fortifier Plus;\nliving-enchantment;2607;livingenchantment;生命附魔;Living Enchantment;\nkarat-garden;2608;karatgarden;克拉的花园;Karat Garden;\n;2609;cocricotmod;Cocricot;;\nbard-mania;2610;bard_mania;Bard Mania;;\nshulkerboxtooltip;2611;shulkerboxtooltip;潜影盒提示框;Shulker Box Tooltip;\n;2612;ism;瞬时建造;Instant Structures Mod;ISM\ncrafting-dead-official;2613;craftingdead;行尸走肉;Crafting Dead;CrD\nlord-craft;2614;lordcraft;Lord Craft;;\n;2615;MoreSA;大牛的SA;DaNiu's SA;\nstupid-things;2617;stupidthings;Stupid Things;;\nmore-gems-fabric;2618;more_gems;更多宝石;More Gems;\nminewars;2619;minewars;MineWars;;\ngregicadditionscompatibility;2620;;Gregic Additions Compatibility;;\ncombined-potions;2621;compot;组合药水;Combined Potions;\nplusticminusbad;2622;plustic;xXx_MoreToolMats_xXx;;\nbaileys-dailies;2623;dailies;Bailey's Dailies;;\njoshuas-christmas-mod;2624;joshxmas;Joshua's Christmas Mod;;\nenderio-fluxified;2625;enderiofluxified;EnderIO Fluxified;;\nskizzik;2626;skizzik;Skizzik ＆ Co;;\n;2627;miners;矿工天堂;Miner's Heaven;\n;2628;zongzi;粽子;ZongZi;ZZ\nsandbag;2629;sandbag;沙袋;Sandbag;\ntinkers-arsenal;2630;tinkersarsenal;Tinkers' Arsenal;;\ngarbage-energy;2631;GarbageEnergy;废品能源炉;Garbage Energy;\nquirky-generators;2632;quirkygenerators;Quirky Generators;;\nmegaloot;2633;megaloot;超级战利品;MegaLoot;\n;2634;Kradxns Xray;Kradxn's X-Ray;;X-Ray\nmad-villagers;2635;mad_villagers;疯狂的村民;Mad Villagers;\nlogical-drops;2636;logicaldrops;逻辑工艺;Logical Craft / Logical Drops;LC\ndrone-craft;2637;dronecraft;Dronecraft;;\nwater-source;2638;watersource;水源;Water Source;WS\nhungteens-plants-vs-zombies-mod;2640;pvz;HungTeen的植物大战僵尸;HungTeen's Plants vs Zombies Mod;HTPVZ\npams-desertcraft;2641;desertcraft;潘马斯沙漠工艺;Pam's DesertCraft;\npams-bonecraft;2642;bonecraft;潘马斯骨头工艺;Pam's BoneCraft;\nthermal-singularities;2643;thermsingul;热力奇点;Thermal Singularities;TS\nminejurassic;2644;minejurassic;MineJurassic;;MJ\nterraria-craft;2645;terraria;泰拉瑞亚世界;TerrariaCraft 🌳;TC\ncolytra;2646;colytra;鞘翅胸甲;Colytra;\ninsects;2647;insects;昆虫;Insects;\nsound-reloader;2648;soundreloader;声音重载;Sound Reloader;\ntool-belt;2649;toolbelt;工具皮带;Tool Belt;\nargentina-food-mod;2650;argentina_food_mod;阿根廷的食物;Argentina Food Mod;\npet-names;2651;petnames;宠物名字;Pet Names;\ncollective;2652;collective;Collective;;\ngiselbaers-durability-viewer;2653;durabilityviewer;耐久指示器;Durability Viewer;\nmore-cookies;2654;morecookies;更多饼干;More Cookies;\napple-carrot;2655;applecarrot;苹果胡萝卜;Apple Carrot;\neat-yo-veg;2656;eatveg;Eat Yo' Veg!;;\nbountiful;2657;bountiful;赏金;Bountiful;\nbetterenchantment;2658;betterenchantments;更好的附魔;BetterEnchantment;\npurified-flesh-mod;2659;purifiedfleshmod;纯净的腐肉;Purified Flesh Mod;\ngotminecraftmod;2660;got;权力的游戏;Game of Thrones Mod;GOT\nnosleep;2661;nosleep;禁止睡觉;No Sleep;\nminersadvantage;2662;minersadvantage;矿工的优势;MinersAdvantage;\n;2663;mobbottle;生物展示瓶;MobBottle;\nsimple-backpack-fabric;2664;simple_backpack;简单背包;Simple Backpack;\ngas-conduits;2666;gasconduits;气体导管;Gas Conduits;\nhandgun-from-terraria;2667;mod;Handgun From Terraria;;\nmore-discs-mod;2668;more_music_disc;更多唱片;More discs mod;\nedible-honeycombs;2669;;Edible Honeycombs;;\nemerald-and-ruby;2670;emeraldandruby;Emerald And Ruby;;\ncraftable-nametag;2671;craftable_nametag;可合成的命名牌;Craftable Nametag;\ndyeable-flower-pots;2672;dyeable_flower_pots,more_flower_pots;染色花盆;More Flower Pots / Dyeable Flower Pots;\ngraffiti;2673;graffiti;涂鸦;Graffiti;\nbunny-boots;2674;bunnyboots;Bunny Boots;;\nbackported-flora;2675;backportedflora;Backported Flora;;\neat-eggs;2676;eateggs;Eat Eggs;;\nwool-plates;2677;woolplates;羊毛压力板;Wool Plates;\nmore-berry-uses;2678;sweetberryuses;更多浆果用途;More Berry Uses;\nelemental-blades;2679;elemental_blades;元素之刃;Elemental Blades / Elementium;\nbeyond-earth;2680;boss_tools,beyond_earth;飞越地球;Beyond Earth/Space-BossTools;\nsimple-planes;2681;simpleplanes;简单飞机;Simple Planes;\nworld-tooltips;2682;worldtooltips;掉落物信息显示;World Tooltips;\nextra-arcane-knowledge-electroblobs-wizardry;2683;ghostspells;额外巫术知识;Extra Arcane Knowledge;\nbacteriums;2684;bacterium;细菌重置版;Bacterium;\npillagers;2685;pillagers;掠夺者;Pillagers;\nguard-villagers;2686;guardvillagers;警卫村民;Guard Villagers;\nsimple-machinery;2687;simplemachinery;简单机器;Simple Machinery;\n;2688;;更困难的生存;;\nbridge-maker;2689;bridge_maker;桥梁制造者;Bridge Maker;\ncraft-your-saddle;2690;sattlecrafter;HorseCrafting;;\nno-pvp-cooldown;2691;;No PVP Cooldown;;\nnever-break;2692;neverbreak;损坏防护;Never Break;\nsimple-villager-spawn-egg;2693;simplevillagerspawnegg;Simple Villager Spawn Egg;;SVSE\nbanana-decor-forge;2694;bananadecor;Banana Decor;;\nimmersive-metal;2695;immersivemetal;沉浸金属;Immersive Metal;\ncoffee-time;2696;coffeetime;Coffee Time!;;\ndrink-mod;2697;drinksmod;饮料模组;Drink Mod;\nmore-zombie-villagers;2698;morezombievillagers;更多僵尸村民;More Zombie Villagers;\nnaturally-charged-creepers;2699;naturallychargedcreepers;自然生成闪电苦力怕;Naturally Charged Creepers;\nskinport;2700;skinport;皮肤移植;SkinPort;\ndynamic-surroundings-soundcontrol;2701;sndctrl;动态环境：音效控制;Dynamic Surroundings: SoundControl;\n;2702;cheat_ban;无盐:创造毁灭者;No More Salt:Cheat Ban;CB\nchop-down-updated;2703;chopdownupdated;Chop Down Updated;;\naegis-weapon-system-mod;2704;AegisSystemMod;宙斯盾作战系统-战舰模组;Aegis Weapon System -Warship's Mod;ASM\nadvanced-hook-launchers;2705;adhooks;高级钩爪发射器;Advanced Hook Launchers;\nvampire-magic;2706;vampiremagic;Vampire Magic;;\nfantasyunderworld;2707;fantasyunderworld;江湖行;fantasyunderworld;\nstacc;2708;stacc;Stacc;;\na-riot-of-colour-craft;2709;shui;五彩斑斓工艺;a riot of colour craft;\ncrop-dusting;2710;cropdusting;排气施肥;Crop Dusting;\ndynamic-surroundings-mobeffects;2711;mobeffects;动态环境：生物效果;Dynamic Surroundings: MobEffects;\ndynamic-surroundings-environs;2712;environs;动态环境：周边环境;Dynamic Surroundings: Environs;\nrecipe-stages;2713;recipestages;配方阶段;Recipe Stages;\nattack-on-minecraft;2714;aom,wingsoffreedom;自由之翼;Wings of Freedom;\ncavern-miner;2715;cavern;洞穴：矿工;Cavern: Miner;\nvanilla-tools;2716;supertools,vanillaplustools;原版工具+;Vanilla Plus Tools;\nserver-tab-info;2717;servertabinfo;Server Tab Info;;\nletsencryptcraft;2718;letsencryptcraft;LetsEncryptCraft;;\ncreeperhost-minetogether;2719;minetogether;MineTogether;;\n;2720;ilib;又一个优化Mod;ImpLib;IL\nfullscreen-windowed-borderless-for-minecraft;2721;fw;窗口化全屏;Fullscreen Windowed (Borderless) for Minecraft;FW\nnocurse;2722;nocurse;不诅咒;NoCurse;\npretty-beaches;2723;prettybeaches;Pretty Beaches;;\nqing_gong;2724;qing_gong;轻功;qing_gong;qg\nremnants;2725;remnants;腐败;Remnants;\npane-in-the-glass;2727;pitg;Pane In The Glass;;PITG\ndynamic-dynamos;2728;dyndyn;能源炉动画;Dynamic Dynamos;\ntrophy-slots;2729;trophyslots;Trophy Slots;;\n;2730;magicwithdrinks;魔法与饮料;Magic With Drinks;MWD\nreplanting-crops;2731;replantingcrops;补种农作物;Replanting Crops;\ncomplex-crops;2732;complexcrops;复杂作物;Complex Crops;\nscreenshot-to-clipboard;2733;screenshotclipboard;截图到剪贴板;Screenshot to Clipboard;\nhide-armor;2734;hidearmor;隐藏盔甲;Hide Armor;\nhunterillager;2735;hunters_return,hunterillager;Hunter's Return;;\n;2736;;僵尸启示录：语音聊天;DecimationVoiceChat;\npocket-apocalypse-mod;2737;pocalypse;便携式天灾;Pocket Apocalypse Mod;\ncompass;2738;compass;指南针;compass;\ntinkers-evolution;2739;tconevo;匠魂进化;Tinkers' Evolution;TConEvo\ntinkers-exporter;2740;tic_exporter;匠魂数据导出器;Tinkers Exporter;\nfarlanders;2741;farlanders;更多末影人;The Farlanders;\nvials;2742;vials;瓶瓶罐罐;Vials;\nadvanced-lightsabers;2743;lightsabers;高级光剑;Advanced Lightsabers;AL\nfall_attack;2744;fallattack;下落击杀;fall_attack;FA\nswimsuit;2745;swimsuit;泳装;swimsuit;YZ\nmore-overlays-updated;2746;moreoverlays;More Overlays Updated;;\ndothack-hack-weapons;2747;dothack;Dothack Weapons;;\nimmersive-energy;2748;immersive_energy;沉浸能源;Immersive Energy;\ncrimson-moon;2749;;绯红之月;Crimson Moon;\nmobs-attempt-parkour;2750;mobs_attempt_parkour;Mobs Attempt Parkour;;\nidentity;2751;identity;化身;Identity;\nindustrial-revolution;2752;indrev;工业革命;Industrial Revolution;IR\nart-of-alchemy;2753;artofalchemy;炼金艺术;Art of Alchemy;\nlan-server-properties;2754;lanserverproperties;自定义局域网联机;Lan Server Properties;LSP\n;2755;custombuki;自定义武器;Custombuki;\nimmersive-intelligence;2756;immersive-intelligence,immersiveintelligence;沉浸智能;Immersive Intelligence;II\nanalyzeio;2757;analyzeio;AnalyzeIO;;\nwool-buttons;2758;sbmwoolbuttons;羊毛按钮;Wool Buttons;\ntotal-tinkers;2759;totaltinkers;统合匠艺;Total Tinkers;\nthaumic-wands;2761;thaumicwands;神秘法杖;Thaumic Wands;\njapanese-food-mod;2762;japanese_food_mod;日式料理;Japanese Food Mod;\npandoras-creatures;2763;pandoras_creatures;潘多拉生物;Pandoras Creatures;\nstatus-tags;2764;;名称标签状态栏;Status Tags;\njust-enough-dimensions;2765;justenoughdimensions;维度配置;Just Enough Dimensions;JED\nmultishot;2766;multishot;延时摄影;Multishot;\ntweakers-construct;2767;tweakersconstruct;匠魂调整;Tweakers Construct;\nvalhelsia-structures;2768;valhelsia_structures;Valhelsia Structures;;\n;2769;yvanchuxiuzhen;原初修真;yuanchuxiuzhen;\n;2770;initial;原初修真前置;Initial Immortal;\njust-enough-reactors;2771;justenoughreactors;Just Enough Reactors;;\naoa-infobundle;2772;aoainfo;虚无世界信息包;AoA InfoBundle;AoAIB\nmana-and-artifice;2773;mana-and-artifice;魔法艺术3：巧工魔艺;Mana and Artifice;MA3/MNA\n;2774;;远程强度工艺 前置模组;RICraft API;\nslab-helper;2776;slabhelper;半砖小帮手;Slab Helper;\nno-jumping-allowed;2777;nojumpingallowed;禁止跳跃;No Jumping Allowed;\nswan-boats;2778;swanboat;天鹅船;Swan Boats;\nprimal-winter;2779;primalwinter;原始寒冬;Primal Winter;\nbetter-burning;2780;betterburning;更好的燃烧机制;Better Burning;\ncompact-void-miners;2781;compactvoidminers;紧凑型虚空采矿机;Compact Void Miners;CVM\nprobe;2782;probe;Probe;;\nmekanism-fluxified;2783;mekanismfluxified;“通量”机械;Mekanism Fluxified;\nstepup;2784;stepup;平滑自动上坡;StepUp;SU\nsodium;2785;sodium;钠;Sodium;\n;2786;sv;月光树林;Moon Woods;\nnetherlicious;2787;netherlicious;Netherlicious;;\nyungs-better-mineshafts-forge;2788;bettermineshafts;YUNG 的矿井优化;YUNG's Better Mineshafts;\nterrible-chest;2789;terrible_chest;可怕的箱子/可怖箱子;Terrible Chest;\nspartan-shields;2790;spartanshields;斯巴达之盾;Spartan Shields;\nsilent-gear;2791;silentgear;寂静装备;Silent Gear;SGear\n;2792;watersprayer;喷雾瓶;Water Sprayer;\nsimple-smeltery-accelerator;2793;simplesmelteryaccelerator;匠魂冶炼炉加速器;Simple Smeltery Accelerator;\nportality;2794;portality;Portality;;\nnatural-absorption;2795;naturalabsorption;伤害吸收天赋;Natural Absorption;\nunique-enchantments;2796;uniquee;独特的附魔;Unique Enchantments;\ntombstone-revived;2797;;墓碑：复生;Tombstone: Revived;\ntravelers-backpack-integrations;2798;tbintegration;旅行者背包集成;Traveler's Backpack Integrations;\nBaby-Mobs;2799;babymobs;Baby生物;Baby Mobs;\ncompressedblock;2800;compressedblock;压缩方块;Compressed Block;\nwildnature;2801;wildnature;狂野自然;Wild Nature;WN\nnovam-terram;2802;nt;Novam Terram;;NT\nterrafirmathings;2803;tfcthings;群峦作品;Terra Firma Things;\nmechanization-datapack;2804;mechanization;机械工厂;Mechanization;MECH\nsimpledifficulty;2805;simpledifficulty;坚定意志;SimpleDifficulty;\nminema;2806;minema;Minema;;\ntfctech-unofficial;2807;tfctech;群峦工业非官方版;TFCTech Unofficial;\n;2808;ee;等价交换;Equivalent Exchange;EE\ninvmove;2809;invmove;边拿边走;InvMove;\nautofish-for-forge;2810;forgeautofish;自动钓鱼Forge版;AutoFish for (Neo)Forge;\nindustrial-upgrade;2811;super_solar_panels,industrialupgrade;工业升级;Industrial Upgrade;IU\nrpgstats;2812;rpgstats;RPGStats;;\ndatapackrecipemaker;2813;dprm;数据包配方助手;Datapack Recipe Maker;DPRM\nnever-needed-or-wanted;2814;nnow;Never Needed Or Wanted;;NNOW\nama-damage-indicator;2815;damage_indicator;Ama's Damage Indicator;;\n;2816;wlgr;伍德的美食红宝书;Woody's Le Guide Rouge;WLGR\nmagic-circle;2817;magiccircle;阵法;Magic Circle;\ncrash-utilities;2818;crashutilities;Crash Utilities;;CU\neldritch-mobs;2819;eldritch_mobs;Eldritch Mobs;;\nfarmers-delight;2820;farmersdelight;农夫乐事;Farmer's Delight;FD\nartifacts;2821;artifacts;奇异饰品;Artifacts;\ndynamictreestfc;2823;dynamictreestfc,dttfc;动态的树：群峦传说附属;Dynamic Trees TFC;\nlucky-block-avaritia;2824;;无尽幸运方块;Avaritia Lucky Block;\ndynamic-trees-the-twilight-forest;2825;dynamictreesttf;动态的树：暮色森林附属;Dynamic Trees-The Twilight Forest;\ndrp-global-datapack;2826;drpglobaldatapack,globaldataandresourcepacks,globalpacks;全局数据包;Global Packs/Global Data & Resourcepacks;\nmob-farm-nerfer;2827;mob_farm_nerfer;Mob Farm Nerfer;;\nblood-in-the-water;2828;blood_in_the_water;Blood in the Water;;\nbio-technik;2829;biotechnik;Bio Technik;;\n;2830;;Tinkers' Guns Construction;;\n;2831;germplasm;种质工艺;Germplasm;\nastromine-fabric;2832;astromine;天体矿工;Astromine - Machines n' Space!;\nrustic-thaumaturgy;2833;rusticthaumaturgy;神秘乡村;Rustic Thaumaturgy;RT\n;2834;gasterblaster;龙骨炮;gasterblaster;\nthallium;2835;thallium;铊;Thallium;\nhaema;2836;haema;Haema;;\nevil-ores;2837;evil-ores;Evil Ores;;\nunsuspicious-stew;2838;unsuspicious-stew;炖菜解谜/炖菜效果显示;Unsuspicious Stew;\ntfc-seasonal;2839;tfcseasonal;TFC Seasonal;;\nimblockerfabric;2840;imblocker;输入法冲突修复;IMBlockerFabric;\nsaomclib;2841;saomclib;saomclib;;\nsurvival-utilities;2842;svutils;生存工具;Survival Utilities;\nslashblade;2843;slashblade;拔刀剑2;SlashBlade 2;\nportal-blocks-2-0;2844;portalblocks;传送门方块;Portal Blocks;\nslimecraft-add-tools-etc;2845;slimecraft;SlimeCraft;;\nmechanized-steam-power;2846;mechanized;机械化蒸汽动力;Mechanized Steam Power;\nmoreswordonline;2847;swordcraftonline;动漫神域;SwordCraftOnline;\ncamera-mod;2848;camera;照相机;Camera Mod;\nheartdrops;2849;heartdrops;生命汲取;Heart Drops;\nobserverlib;2850;observerlib;ObserverLib;;\nzen-summoning;2851;zensummoning;Zen Summoning;;\ncampanion-forge;2852;campanion;野营物品;Campanion;\nwolves-with-armor;2853;wolveswitharmor;狼铠;Wolves With Armor;\nget-exp-for-everything;2854;getexpforeverything;万物皆可获得经验;Get exp for everything;\njei-enchantment-info;2855;jeienchantmentinfo;JEI 附魔信息;JEI Enchantment Info;\nproject-red-core;2856;projectred-core;红石计划：核心;Project Red - Core;\nproject-red-fabrication;2857;projectred-fabrication;红石计划：制作;Project Red - Fabrication;\nproject-red-integration;2858;projectred-integration,projectred-transmission;红石计划：集成;Project Red - Integration;\nproject-red-illumination;2859;projectred-illumination;红石计划：照明;Project Red - Illumination;\nproject-red-world;2860;projectred-exploration;红石计划：探索;Project Red - Exploration;\nproject-red-compat;2861;projectred-compat;红石计划：兼容性;Project Red - Compat;\ncanvas-renderer;2862;canvas;Canvas 渲染器;Canvas Renderer;\ncoolfood;2864;kur;冷饮;CoolFood;\ninfinitevillagertrading;2865;infinitevillagertrading;无限村民交易;InfiniteVillagerTrading;\ninfinite-trading;2866;infinitetrading,infinitetrading-fabric;无限交易;Infinite Trading;\nbetter-loading-screen;2867;customloadingscreen;自定义加载画面;Custom Loading Screen;\nextended-lights-mod;2868;extlights;灯具拓展;Extended Lights;\nmore-swords-legacy;2869;msmlegacy;更多剑：传承;More Swords Legacy;\nthe-undergarden;2870;undergarden;深暗之园;The Undergarden;TUG\nallure;2871;;诱惑;Allure;\n;2872;honkaiimpact;崩坏;Honkai Impact;\nglibys-voice-chat-reloaded;2873;gvc;Gliby的语音聊天重置版;Gliby's Voice Chat Reloaded;\ngreater-eye-of-ender-fabric;2874;greater_eye;高级末影之眼;Greater Eye of Ender;\nproject-red-mechanical;2875;projectred-expansion,projectred-relocation,projectred-transportation;红石计划：机械;Project Red - Expansion / Project Red - Mechanical;\nneon-craft-mod;2876;neoncraft;霓虹灯艺;Neon Craft Mod;NC\narrows-ignite-tnt;2877;;Arrows Ignite TNT;;\nenderite-mod-for-forge;2878;enderitemod;末影合金;Enderite Mod;\ndynamic-trees-industrial-craft-2;2879;dynamictreesic2;动态的树：工业2附属;Dynamic Trees - Industrial Craft 2;\ndynamic-trees-tinkers-construct;2880;dynamictreestconstruct;动态的树：匠魂附属;Dynamic Trees - Tinker's Construct;\nex-nihilo-sequentia;2881;exnihilosequentia;无中生有：传承;Ex Nihilo: Sequentia;\ntoms-storage;2882;toms_storage;汤姆的简易存储;Tom's Simple Storage Mod;\nfishing-real;2883;fishingreal;更真实的钓鱼;Fishing Real;\nmaessentials;2884;maessentials;Ma Essentials;;\nlingering-loot;2885;lingeringloot;Lingering Loot;;\nugraded-netherite;2886;upgradednetherite;下界合金增强;Upgraded Netherite;\n;2887;dreamer;空想家;Dreamer;DM\ninventory-profiles;2888;inventoryprofiles;一键背包整理;Inventory Profiles;\nslimevoid-library;2889;SlimevoidLib;Slimevoid Library;;\nkotlin-for-forge;2890;kotlinforforge;Kotlin for Forge;;KFF\nfabric-zero;2891;fabriczero;Fabric Zero;;\ntowers-of-the-wild;2892;;旷野之塔;Towers Of The Wild;\nsausagecore;2893;sausage_core;香肠核心;SausageCore;\nshadowlands;2894;shadowlands;暗影之地;Shadowlands;\npolymorph;2895;polymorph;多态合成;Polymorph;\nscalable-cats-force;2896;scala-library-object;Scalable Cat's Force;;\notyacraft-engine;2897;otyacraftengine;Otyacraft Engine;;\nomega-craft-mod;2898;omegacraft;Omega Craft Mod;;\ncustom-ports;2899;lan;自定义局域网端口;Custom LAN Ports;\ndecorative-blocks;2900;decorative_blocks;装饰方块;Decorative Blocks;\nmusic-layer;2901;musiclayer;Music Layer;;\ncolored-lux;2902;lux;彩色照明;Colored Lux;Lux\ncorsair-mccue;2903;mccue;Corsair McCUE;;\nright-click-clear;2904;right-click-clear;右键文本清除;Right Click Clear;\nwtfs-expedition-cavebiomes-ores-trees-and-tweaks;2905;CaveBiomes;WTF's Expedition: CaveBiomes, Ores, Trees, and Tweaks;;\nguide-api-village-and-pillage;2906;guideapi_vp;Guide-API Village and Pillage;;\ndrcyanos-lootable-bodies;2907;lootable;可拾取尸体;DrCyano's Lootable Bodies;\nsimply-cats;2908;simplycats;Simply Cats;;\npams-harvestcraft-2-crops;2909;pamhc2crops;潘马斯农场2 - 作物;Pam's HarvestCraft 2 - Crops;\nbase-metals;2910;basemetals;基础金属;Base Metals;\nmmdlib;2912;mmdlib;MMDLib;;\njust-poop-mod;2913;poop,pm;屎!;Poop Mod!;\ncostumes;2914;costumes;服装;Costumes;\neyemod-phone;2915;eyemod;EyeMod;;\nupgrade-aquatic;2916;upgrade_aquatic;碧海新生;Upgrade Aquatic;\ntfc-ambiental;2917;tfcambiental;TFC Ambiental;;\nmacaws-trapdoors;2918;mcwtrpdoors;Macaw 的活板门;Macaw's Trapdoors;\nzenutil;2919;zenutils;Zen Utils;;ZU\nbetter-beacon-effect;2921;betterbeaconeffects;更好的信标效果;Better Beacon Effect;\nnetherite-horse-armor;2922;netheritehorsearmor;下界合金马铠;Netherite Horse Armor;\ntacocraft-fabric;2923;tacocraft;TacoCraft;;\nlootr;2924;lootr;Lootr;;\nbiolib;2925;biolib;Bio Library;;BioLib\nscavenge;2926;scavenge;Scavenge;;\n;2927;WLM;不毛之地;Wasteland;\nheaven-earth-ring;2928;heavenearthring;乾坤戒指;Heaven Earth Ring;HER\n;2929;hnkinvention;奇妙发明;Wonderful Invention;\npams-harvestcraft-2-food-extended;2930;pamhc2foodextended;潘马斯农场2 - 食物拓展;Pam's HarvestCraft 2 - Food Extended;PHC2FE\npams-harvestcraft-2-trees;2931;pamhc2trees;潘马斯农场2 - 果树;Pam's HarvestCraft 2 - Trees;PHC2T\nbetter-minecart-rotation-forge;2933;lock_minecart_view;Better Minecart Rotation;;\nmodern-metals;2934;modernmetals;现代金属;Modern Metals;\nblockrenderer;2935;block_renderer,blockrenderer;Block Renderer;;\nblockus;2936;blockus;Blockus;;\nconjurers-cookbook;2937;conjurerscookbook;Conjurer's Cookbook;;\ncolor-unchained;2938;colorunchained;Color Unchained;;\naportingcore;2939;APortingCore;移植核心;APortingCore;\nywlib;2940;ywlib;YWlib;;\nsweet-potato;2941;sweet_potato;烤地瓜;Sweet Potato Mod;SPM\nredstone-lantern;2942;redstonelantern;红石灯笼;Redstone Lantern;\nculinaire;2943;ce_foodstuffs;烹饪;Culinaire;\nfurbishcraft;2944;lizardfurnituremod;FurbishCraft;;\n;2945;storageSilo,StorageSilo,storagesilo;存储仓;StorageSilo;\nplural-lucky-block;2946;;复数幸运方块;Plural Lucky Block;\nambientsounds;2947;ambientsounds;自然音效;AmbientSounds;AS\nordinary-firearms;2948;ordinary_firearms;普通枪械2;Ordinary Firearms 2;OFMS2\nterritory;2949;territory;领地;MineTerritory;\nbetter-ping-display;2950;betterpingdisplay;更好的延迟显示;Better Ping Display;BP\nturned-the-changed-mod;2951;turned_remaster;Turned: The Changed Mod;;\nsurvival-tweaks;2952;survivaltweaks;生存微调;Survival Tweaks;\nmelon-slabs;2953;melonslabs;西瓜台阶;Melon Slabs;\nlambdynamiclights;2954;lambdynlights;Lambd的动态光源;LambDynamicLights;LDL\nrealistic-torches;2955;realistictorches;真实火把;Realistic Torches;\n;2956;;跳舞的线;Dancing Line For Minecraft;DLMC\ndurability-viewer;2957;Durability Viewer,DurabilityViewer;耐久显示器;Durability Viewer;\nlucky-block-bow;2959;lootplusplus;Lucky Block Bow;;\n;2960;flammpfeil.slashblade;狐月刀改;FoxBlade Extra;\nground-item-highlighting;2961;grounditemhighlighting;地面物品高光显示;Ground Item Highlighting;\n;2962;opengldrawing;绘画;OpenGL Drawing;OD\nreal-survivor;2963;realsurvivor;幸存者;RealSurvivor;\narmor-toughness-bar;2964;toughnessbar;盔甲韧性显示;Armor Toughness Bar;\nblock-meter;2965;blockmeter;Block Meter;;\ntime-to-live;2966;timetolive;Time To Live;;TTL\ndual-hotbar-1-12-2;2967;dualhotbar;双重快捷栏1.12.2;Dual Hotbars 1.12.2;\ncha-s;2968;craftablehorsearmour;可合成的马铠和鞍;Craftable Horse Armour & Saddle;CHA&S\ndual-hotbar;2969;dualhotbar;双重快捷栏;Dual Hotbars;\ntravellers-boots;2970;travellersboots,travellersbootsreloaded;Traveller's Boots;;\n;2971;wfs;伍德的时装精选;Woody's Fashion Selection;WFS\ncofh-vanilla-satchels;2972;vanillasatchels;CoFH：Vanilla+ Satchels;;\nbetteranimalsplus;2973;betteranimalsplus;更多动物;Better Animals Plus;BA+\npackagedauto;2974;packagedauto;封包合成;PackagedAuto;PAuto\npackagedexcrafting;2975;packagedexcrafting;封包合成拓展;PackagedExCrafting;PExC\n226236-gilded-armor;2976;tnb_gildedarmor;镀金装甲;Gilded Armor;GA\ncompactstorage;2977;compactstorage;紧凑存储;compactstorage;\nuseful-backpacks;2978;usefulbackpacks;实用背包;Useful Backpacks;\nconquest-reforged;2979;conquest,blockpalette,connect;征服者;Conquest Reforged;\nsteelseries-gamesense;2980;gamesense;Steelseries Gamesense;;\npizzaatimes-timber-mod;2981;timber;pizzaatime's Timber Mod;;\ntellme;2982;tellme;TellMe;;\nminerva-library;2983;minerva;Minerva library;;\n;2984;chromeball;土球;ChromeBall;\n;2985;godblessing;神佑;God Blessing;\n;2986;hamburger;老八秘制小汉堡;The Hamburger Of LaoBa;\nedit-sign;2987;signedit;编辑告示牌;Edit Sign;\nlogistical-automation;2988;logisticalautomation;Logistical Automation;;LA\ndynamic-view;2989;dynview;动态视距;Dynamic View;\n;2990;portalcraft;传送门工艺;PortalCraft;PC\ninstrumental-mobs;2991;instrumentalmobs;乐器怪物;Instrumental Mobs;\nfinder-compass;2992;findercompass,stalkercreepers;Finder Compass;;\nspacering;2993;spacering;储物戒;SpaceRing;\ndoot;2994;trumpetskeleton,trumpet-skeleton;Trumpet Skeleton;;\n;2995;loadedmodsexporter,LoadedModsExporter;加载模组导出;Loaded Mods Exporter;LME\nemotes;2996;Emotes;Emotes;;\noc-sensors;2997;ocsensors;OC Sensors;;\nfabric-for-fabric;2998;fabricforfabric;Fabric for Fabric;;\nstorage-tech;2999;storagetech;Storage Tech;;\nthaumic-periphery;3000;thaumicperiphery;神秘外饰;Thaumic Periphery;\nembellishcraft;3001;embellishcraft;装饰工艺;EmbellishCraft;\nopen-loader;3002;openloader;开放式加载;Open Loader;\ndeep-mob-learning-refabricated;3003;dml-refabricated;深度怪物学习：重制版;Deep Mob Learning: Refabricated;\nftb-ultimine-forge;3004;ftbultimine;连锁破坏;FTB Ultimine;\nserver-translation-api;3005;server_translations,server_translations_api;Server Translations API;;\nimage2map;3006;image2map;Image2Map;;\ncorpse;3007;corpse;遗体;Corpse;\nring-of-the-miner-fabric;3008;ring_of_miner;矿工戒指;Ring of the Miner;\ninstant-bubble-studio;3009;instantbubblestudio;瞬息气泡工坊;Instant Bubble Studio;\n;3010;rpgmaths;RPG数学家;RPG Maths;\n;3011;unexpectedteleport;未曾设想的传送方式;Unexpected Teleport;\n;3012;vacation_diary;度假日记;Vacation Diary;\n;3013;bananacraft;香蕉工艺;BananaCraft;\nkaleido;3015;kaleido;Kaleido 🎨;;\n;3016;arcaneart;奥法艺术;Arcane Art;\ncrock-pot;3017;crockpot;烹饪锅;Crock Pot;\n;3020;abouttea;AboutTea;;\n;3021;vida;生命;Vida;\n;3022;throwable;投掷物;Throwable;\n;3023;icycream;冰激凌;IcyCream;\n;3024;suuuuuuuper_herbal_tea;超级凉茶;Suuuuuuuper herbal tea;\n;3025;sinocraft;华夏工艺;SinoCraft;\ntogether-forever;3026;togetherforever;Together Forever;;\niron-furnaces;3027;ironfurnaces;更多熔炉;Iron Furnaces;\n;3028;jutsubag;忍术手袋;JutsuBag;\nlenient-creepers;3029;lenientcreepers;仁慈的苦力怕;Lenient Creepers;\ntreasure2;3030;treasure2;寻宝记2;Treasure2!;\n;3031;autoreconnector;自动断线重连;AutoReconnector-Fabric;\n;3032;jigsaw;Jigsaw Lib;;\nstructure-gel-api;3033;structure_gel;Structure Gel API;;\nthe-conjurer;3034;conjurer_illager;魔术师;The Conjurer;\nganys-nether;3035;ganysnether;Gany的下界;Gany's Nether;\nganys-end;3036;ganysend;Gany的末地;Gany's End;\nepic-fight-mod;3037;epicfight;史诗战斗;Epic Fight;EF\nattack-missed;3038;attack_missed,attack_missed_port;攻击闪避;Attack Missed;AM\nlava-monsters;3039;lava_monster;熔岩怪;Lava Monsters;\ntumat;3040;tumat;TUMAT;;\nriddle-chests;3041;riddlechests;谜题箱;Riddle Chests;\n;3042;cinfinitelib;CinfiniteLib;;CiL\ntotem-plus;3043;totemplus;图腾增强;Totem Plus;\nblockmeterfabric;3044;blockmeter;方块计量表;Block Meter Fabric;\n;3045;kamenrider3dmod;假面骑士 (3D版);KamenRider3D;KR3D\nfluid-drawers;3046;fluiddrawers;储液抽屉;Fluid Drawers;\ncreepypastacraft-reborn;3047;creepypastacraft;蠕动意面重置版;CreepyPastaCraft Reborn;\nitemzoom;3048;itemzoom;物品缩放;ItemZoom;\nlight-overlay;3049;lightoverlay;亮度覆盖;Light Overlay;\nsmallerfpsincrements;3050;smallerfpsincrements;SmallerFPSIncrements;;\nregex-filters;3051;regexfilters;正则表达式过滤器;Regex Filters;\n;3052;realworld;真实的世界;RealWorld;\nfluidsystem;3053;fluidsystem;流体系统;FluidSystem;\nfalling-tree;3054;falling_tree,fallingtree;伐树;FallingTree;\nuntranslated-items;3055;untranslateditems;物品名称双语同屏;Untranslated Items;UTI\nparticle-culling;3056;particleculling;粒子渲染机制优化;Particle Culling;\naggressive-chickens;3057;aggresive-chickens;Aggressive Chickens;;\nentity-culling;3058;entity_culling;实体渲染机制优化;Entity Culling;\nhammerandvil;3059;hammerandvil;锻造锤与锤锻台;HammerAndVil;H&V\n;3060;arclight;Arclight;;\nsimply-backpacks;3061;simplybackpacks;简易背包;Simply Backpacks;\nbetter-placement;3063;betterplacement;更好的放置;Better Placement;\nfat-cat;3064;fat_cat;大资本家;Fat Cat;\nwaystones2waypoints;3065;w2w;传送石碑路径点;Waystones2Waypoints;W2W\nbeta-days;3066;beta_days;B测往昔;Beta Days;\ntrajectory-preview;3067;trajectory_preview;弹道预览;Trajectory Preview;\nxp-spawner-control;3068;xpspawnercontrol;刷怪笼经验控制;Xp Spawner Control;\nmob-spawner-control;3069;spawnercontrol;刷怪箱控制与修改;Mob Spawner Control;\nrefined-pipes;3070;refinedpipes;精致管道;Refined Pipes;\nsingularities;3071;singularities;奇点;Singularities;\nbetter-spawner-control;3072;betterspawnercontrol,betterspawnercontrol-fabric;更好的刷怪笼控制;Better Spawner Control;\ncrying-ghasts;3073;cryingghasts;哭泣的恶魂;Crying Ghasts;CG\ndynamic-fps;3074;dynamicfps,dynamic_fps;动态 FPS;Dynamic FPS;\ncompact-ores;3075;compactores;致密矿石;Compact Ores;\nthaumic-additions;3076;thaumadditions;神秘领域：重构;Thaumic Additions：Reconstructed;TAR\nfabric-disable-custom-worlds-advice;3077;disable_custom_worlds_advice;禁用自定义世界警告;Disable Custom Worlds Advice;DCWA\nleaf-decay;3078;leaf-decay;树叶速腐Fabric版;Leaf Decay;\nfast-furnace-for-fabric;3079;fastfurnace;熔炉性能优化 Fabric 版;Fast Furnace for Fabric;\ndiggus-maximus;3080;diggusmaximus;连锁挖掘;Diggus Maximus;\nsuper-sentai-craft;3081;supersentaicraft;超级战队工艺;Super Sentai Craft;SSC\nwildly-eat;3082;wildly_eat;狼吞虎咽;Wildly Eat;\ngotta-climb-fast;3083;gottaclimbfast;快速爬梯;Gotta Climb Fast!;\ncan-i-mine-this-block;3084;can-i-mine-this-block;我­­ 能 挖 它 吗？;cAn i MiNe thIS bLOCk?;cimtb\nrotten-creatures;3085;rotten_creatures,rottencreatures;腐烂生物;Rotten Creatures;\nfairy-lights;3086;fairylights;圣诞彩灯;Fairy Lights;\npackmode;3087;packmode;PackMode;;\ndank-null;3088;danknull;/dank/null;;\navaritia-complement;3089;avaritiatweaks;Avaritia's Complement;;\nbetter-questing-triggerer;3090;bqt;Better Questing Triggerer;;\nBroken-Wings;3091;brokenwings;Broken Wings;;\n;3092;ChickenASM;ChickenASM;;\ncompacter;3093;compacter;压缩者;Compacter;\nnot-enough-potions;3095;nep;Not Enough Potions;;NEP\nframed-compacting-drawers;3096;framedcompactdrawers;镶框压缩抽屉;Framed Compacting Drawers;FCD\ndiscordsuite;3097;discordsuite;DiscordSuite;;\nmikes-mods-library;3098;mikesmodslib;Mike's Mods Lib;;\nTeslafied;3099;teslafied;Teslafied;;\nshurlin;3100;shurlin;仙灵;Shurlin;XL\nmob-amputation;3101;mobamputation;怪物截肢;Mob Amputation;\npackguard;3102;packguard;PackGuard;;\nwtfs-texturegeneratorlib;3103;TextureGeneratorLib;WTF's TextureGeneratorLib;;\nmore-totems-of-undying;3104;moretotems;更多的不死图腾;More Totems Of Undying;\ndungeon-crawl;3105;dungeoncrawl;Dungeon Crawl;;\ntesslocator;3106;tesslocator;超立方转运器;Tesslocator;\niblis-headshots;3107;iblis_headshots;爆头击杀;Iblis Headshots;\nbee-barker;3108;beebarker;Bee Barker;;\nsky-bonsais;3109;skybonsais;天空盆景;Sky Bonsais;\n;3110;the_golden_autumn;黄金之秋;The Golden Autumn;\norigins;3111;origins;起源;Origins (Fabric);\nalexiillib;3112;alexiillib;AlexiiLLib;;\nfeed-a-friend;3113;feedafriend;Feed A Friend;;\n;3114;;斗地主;Minecraft Dou Dizhu;\n;3115;irar;又一个IRR重制版;Item Render Async Reloaded;IRAR\nthe-legend-of-herobrine;3116;herobrine;The Legend of Herobrine;;\nextra-origins;3117;extraorigins;起源扩展;Extra Origins;\ncaves-and-cliffs;3118;caves_and_cliffs,cavesandcliffs;Caves and Cliffs Mod, 1.17 backport;;CC\nminer-golems;3119;minergolems;矿工傀儡;Miner Golems;\nbakadanmaku;3120;bakadanmaku;直播弹幕模组;BakaDanmaku;\nonline-card;3121;onlinecard;网络卡片;Online Card;\nfelling;3122;felling;连锁砍树;Felling;\nextra-player-render;3123;extra_player_renderer;额外玩家渲染;Extra Player Renderer;\nfabric-api;3124;fabric,fabric-api;Fabric API;;\ncoloredlanterns;3125;coloredlanterns;多彩灯笼;ColoredLanterns;\nae2-fluid-crafting;3126;ae2fc;AE2流体合成套件;AE2 Fluid Crafting;ae2fc\ncardinal-energy;3127;cardinalenergy;能量基本;Cardinal Energy;\ntravel-anchors;3128;travel_anchors,travelanchors;旅行锚;Travel Anchors;\nbnbgaminglib;3129;bnbgaminglib;BnBGamingLib;;\ntriumph;3130;triumph;Triumph;;\noverloaded-armor-bar;3131;overloadedarmorbar;护甲上限突破;Overloaded Armor Bar;\ntoomanydanyores;3132;TooManyDanyOres;TooManyDanyOres;;\nglass-pipe;3133;glasspipe;玻璃管道;Glass Pipe;\norigins-classes;3134;origins-classes;起源：职业;Origins: Classes;\nplonk;3135;plonk;世界内物品放置;Plonk;\nbaritone-simply;3136;baritone;Baritone;;BT\npotionsmaster;3137;potionsmaster;魔药大师;Potions Master;\ntinkered-hegemony;3138;tinkeredhegemony;匠器主导;Tinkered Hegemony;\nbetter-impaling;3139;betterimpaling;更好的穿刺附魔;Better Impaling;\ncaliper;3140;caliper;Caliper;;\nbotanical-machinery;3141;botanicalmachinery;植物机械;Botanical Machinery;\ndemagnetize;3142;demagnetize;消磁;Demagnetize;\n;3143;gregtech;格雷科技1;GregTech 1;GT1\n;3144;gregtech;格雷科技2;GregTech 2;GT2\n;3145;;格雷科技3;GregTech 3;GT3\ncinderscapes;3147;cinderscapes;余烬奇景;Cinderscapes;\ntfc-homestead;3148;tfcstorage,tfchomestead;TFC Homestead / TFC Storage;;\ntfctinkers;3149;tfc_tinkers;群峦与匠魂;TFC Tinkers;\nthick-font-fix;3150;thick,thickfontfix;Thick Font Fix;;\n;3151;;LEGENDS CORE;;\ndynamic-trees-traverse;3152;dttraverse;动态的树：遍历附属;Dynamic Trees - Traverse;\nmcmt-multithreading;3153;jmt_mcmt,mcmtfabric;Minecraft Multi-Threading;;MCMT\nbwm-core;3154;betterwithlib;Better With Lib (BWM - Core);;\nice-ice-baby;3155;ice_ice_baby;冰术师;Ice Ice Baby;\nhardlib;3156;HarderLib,hardlib;HardLib;;\nbilingualname;3157;bilingualname;物品名称双语显示;BilingualName;\nchlorine;3158;chlorine;氯;Chlorine;\nbedrock-miner;3160;bedrockminer;基岩矿工;Bedrock Miner;\nreasonable-realism;3161;ReasonableRealism;合理的现实主义;Reasonable Realism;\npassable-foliage;3162;passablefoliage;叶间穿行 🌳;Passable Foliage 🌳;\nbetterend;3163;betterend;更好的末地;BetterEnd;\narmor-chroma;3164;armorchroma;盔甲颜色显示;Armor Chroma;\ninteractions;3165;interactions;自定义交互事件;Interactions;\nlods-of-emone;3166;lodsofemone;很多钱;Lods of Emone;\nfabric-language-scala;3167;fabric-language-scala;Fabric Language Scala;;\nfluid-physics-forge;3168;fluidphysics;流体物理;Fluid Physics;\nzettai-grimoires;3169;zettaigrimoires;Zettai Grimoires;;\nrunorama;3170;runorama;全景截图背景;Runorama;\nitem-stages;3171;itemstages;物品阶段;Item Stages;\nrocksalt;3172;rocksalt;Rocksalt;;\ncazfps-the-dead-sea;3173;cazfps_the_dead_sea;死亡之海;CAZfps The Dead Sea;\npouch-of-unknown;3174;pouchofunknown;未知之袋;Pouch of Unknown;\nwizardry-fates;3175;wizardryfates;Wizardry - Fates;;\nthe-mighty-architect;3176;mightyarchitect;The Mighty Architect;;TMA\nphosphophyllite;3177;phosphophyllite;Phosphophyllite;;\nbiggerreactors;3178;biggerreactors;Bigger Reactors;;\nftb-teams-forge;3179;ftbteams;FTB 团队;FTB Teams;\nwaveaway;3180;waveaway;挥挥手;WaveAway;\nno-advancements;3181;noadvancements;移除进度机制;No Advancements;\ntfc_rocksplus;3182;tfc_rocksplus;群峦岩石;TFC Rocksplus;\nmars-mod-reborn;3183;mars_mod_reborn;Mars Mod Reborn Mod;;\nftb-library-forge;3184;ftbguilibrary,ftblibrary;FTB Library / FTB GUI Library;;\ncrossroads-mc;3185;crossroads;交错道途;Crossroads;CR\nconfig-checker;3186;concheckrmd;整合包配置检测;Modpack Configuration Checker;\nthaumic-speedup;3187;thaumicspeedup;Thaumic Speedup;;\narmor-curve;3188;armorcurve;盔甲曲线修改;Armor Curve;\nambience-extras;3189;ambience;环境音乐：附加;Ambience - Extras;\nadditions-mod;3190;additions;Additions Mod;;\nextracpus;3191;extracpus;更多CPU;Extra CPUs;\nextra-crafting-storage;3192;ExtraCraftingStorage;Extra Crafting Storage;;\nunderground-city-engineering;3194;underground_city_engineering,uce;地下城市工程;Underground City Engineering;UCE\nessentials;3195;essentials;Essentials;;\nsoul-shards-respawn;3196;soulshardsrespawn;Soul Shards Respawn;;\nsoul-shards-the-old-ways;3197;soulshardstow;Soul Shards: The Old Ways;;\n;3198;mwaw;元素女巫;Elemental Witches;\nthe-flying-things;3199;flying_things;The Flying Things;;\nftb-ranks;3200;ftbranks;FTB Ranks;;FTBR\nftb-chunks-forge;3201;ftbchunks;FTB 区块;FTB Chunks;FTBC\nftb-essentials;3202;ftbessentials;FTB Essentials;;FTBE\nsilveroak-outpost;3203;silveroakoutpost;银橡前哨;Silveroak Outpost;\npehkui;3204;pehkui;Pehkui;;\njeitweaker;3205;jeitweaker;JEITweaker;;\nyungs-law;3206;yungslaw;YUNG's Law;;\nlibx;3207;libx;LibX;;\natmospheric;3208;atmospheric;悠然一派;Atmospheric;\nmonospace-font;3209;monospacefont;等宽字体;Monospace font;\n;3210;mbg;武德;wude;WuDe\nmana-liquidizer;3211;manaliquidizer;魔力液化器;Mana Liquidizer;\nneapolitan;3212;neapolitan;那不勒斯风味;Neapolitan;\nclear-despawn;3213;cleardespawn;Clear Despawn;;\ntinkers-modifier-modifier;3214;tconmodmod;匠魂强化修改;Tinkers' Modifier Modifier;TMM\nrandomtweaks;3215;randomtweaks;随意微调;RandomTweaks;\nzettai-magic;3216;zettaimagic;Zettai Magic;;\nstrawgolem-reborn;3217;strawgolem;稻草傀儡重制版;Straw Golem Reborn;\nrare-ice;3218;rare-ice;冰中有宝;Rare Ice;\ncreate-integration;3219;createintegration;Create Integration;;\nthe-magic-mirror;3220;magic_mirror;魔镜;The Magic Mirror;\n;3221;qemenu;QE菜单;Quick Easy Menu;\nboatload;3222;extraboats,boatload;额外船只;Extra Boats / Boatload;\noutvoted;3223;outvoted;落选生物;Outvoted;\n;3224;collision;粒子碰撞;Collision;\nattribute;3225;attributefix;属性修复 - Fabric版;Attribute Fix {FABRIC};\npackmodemenu;3226;packmodemenu;PackMode 菜单;PackModeMenu;\nsimple-gamemodes;3227;simplegamemodes,simple_gamemodes;游戏模式短指令;Simple Gamemodes;\nuniq;3228;uniq;流体统一/物品统一;UniQ;\n;3229;render-layer-trans;Render Layer Transformer;;\nharder-ores;3230;harderores;更难的矿物处理;Harder Ores;\nrune-craft;3231;runecraft;Rune Craft;;\ngeckolib;3232;geckolib,geckolib3;GeckoLib;;\nmemory-cleaner-mod;3233;memorycleaner;内存自动清理;Memory Cleaner Mod;\nore-flowers;3234;oreflowers;矿物花卉;Ore Flowers;\naqua-creepers;3235;aqua_creepers;水下苦力怕;Aqua Creepers!;\nextrastorage;3236;extrastorage;更多存储;ExtraStorage;\nthermal-locomotion;3237;thermal_locomotion;热力运输;Thermal Locomotion;\nexternal-tweaker;3238;externaltweaker;External Tweaker;;\nmorebiomesxl;3239;extrabiomesxl;MoreBiomesXL;;\nnotes;3240;notes;笔记;Notes;\nstare-till-they-grow;3241;;盯到它们熟为止！;Stare Till They Grow!;\nvm-computers;3242;mcvmcomputers;虚拟电脑;VM Computers;\ninventory-free;3243;inventory_free;无物品栏;Inventory-Free;\n;3244;oreplus;矿物加强;OrePlus;\nrandompatches-integration;3245;rpintegration;RandomPatches Integration;;\nbubble-column-elevator-backport;3246;113_water_mechanics;1.13水域机制;1.13 Bubble Columns for 1.12;\nbasic-nether-ores;3247;bno;基础下界矿石;Basic Nether Ores;BNO\nnuclearcraft-reactor-builder;3248;reactorbuilder;核电工艺反应堆建造机;NuclearCraft Reactor Builder;NCRB\npams-harvestcraft-2-food-core;3249;pamhc2foodcore;潘马斯农场2 - 食物核心;Pam's HarvestCraft 2 - Food Core;\neki-lib;3250;eki_lib;Eki Lib;;\n;3251;ImmibisCore;Immibis Core;;\nnodami;3252;nodami;伤害免疫机制移除;No Damage Immunity;NoDamI\nastrological-sorcery;3253;astrosorc;星占魔法;Astrological Sorcery;\nefab-ct-bindings;3254;efabctbindings;EFab CT Bindings;;\ngolden-hopper;3255;goldenhopper;金漏斗;Golden Hopper;\nwizardry-golems;3256;wizardrygolems;巫术傀儡;Wizardry Golems;\n;3257;autoclick;连点器;AutoClick;\n;3258;rpgtool;RPG时代;RPGEra;\ndevtech;3259;devtech;GTCEu-CrT开发套件;Devtech;\n;3260;customenchanttable;自定义附魔台;CustomEnchantTable;\n;3261;mengsword2;神剑养成(二)-魔兵养成;MengSword2;\n;3262;mengsword;神剑养成(一);MengSword1;\n;3263;preemptiveedition;1.17“抢先版”;PreemptiveEdition17;\n;3264;ff;快餐食物;FastFood;\njurassic-world-reborn-mod;3265;rebornmod;侏罗纪重生;Jurassic Reborn;JR\n;3266;keystrokesmod;按键显示;Keystrokes;\ncardboard;3267;bukkitfabric;Cardboard / Bukkit4Fabric;;\nenchanting-with-thaumcraft;3268;enchantingwiththaumcraft;神秘附魔学;Enchanting With Thaumcraft;\ncrimson-revelations;3269;crimsonrevelations;血腥启示;Crimson Revelation;\nenvironmental-core;3270;envirocore;Environmental Core;;\nbigger-crafting-tables;3271;biggercraftingtables;Bigger Crafting Tables;;\ncovers-everywhere;3272;covers_everywhere;万用覆盖板;Covers Everywhere;\nmo-villages;3273;movillages;更多村庄;Mo' Villages;\ninventoryneko;3274;inventoryneko;InventoryNeko;;\nloading-screens;3275;loadingscreens;自定义加载画面;Loading Screens;\nofms-crisis-ridden;3276;ofms_crisis_ridden,ofms_crisisridden;OFMS:危机重重;OFMS:crisis-ridden;OFCR\ndupefix-project;3277;dupefixproject;DupeFix Project;;\nore-control;3278;orecontrol;矿物控制;Ore Control;\ntwitch-chat-integration;3279;twitchintegration;Twitch信息显示;Twitch Chat Integration;TCI\n;3280;time;时间传说：重生;TIME: Reborn;TIME\navaritia-lite;3282;avaritia;无尽贪婪精简版;Avaritia Lite;\npackmenu;3283;packmenu;PackMenu;;\nconsole-experience;3285;consolehud;原主机版界面特性;Console Experience;\n;3286;such_metal,such_metal_;晶体;Crystal;\n;3287;legendarybeasts;幻想怪物;Legendary Beasts;\nmite-reborn;3288;fiveminsurvival,mite;MITE：重生;MITE:Reborn;\ndetailed-technology;3289;dt;细节科技;Detailed Technology;DT\noreberries;3290;oreberries;矿莓;Oreberries;\nstone-block-utilities;3291;stoneblockutilities;StoneBlock Utilities;;\nmystical-creations;3292;mysticalcreations;Mystical Creations;;\nmulticonnect;3293;multiconnect;MultiConnect;;\nart-of-alchemy-memoriam;3294;artofalchemy;炼金艺术：纪念版;Art of Alchemy: Memoriam;\nblockphysics;3295;BlockPhysics;方块物理;BlockPhysics;\nstoneblock-dimensions;3296;stoneblockdimensions;StoneBlock Dimensions;;\ncharging-gadgets;3297;charginggadgets;充电设备;Charging Gadgets;CG\nintegration-foregoing;3298;integrationforegoing;工业先锋集成附属;Integration Foregoing;\nenderfuge;3299;enderfuge;末影熔炉;Enderfuge;\ncalm-down-zombie-guy;3300;calmdownzombieguy;Calm Down Zombie Guy;;\nuniversal-mod-core;3301;universalmodcore;通用模组核心;Universal Mod Core;UMC\nsuspended-server;3302;suspendedserver;挂起服务器;Suspended Server;\nstarlight-forge;3303;starlight;星光;Starlight;\nresearch-table;3304;researchtable;研究台 🔬;Research Table 🔬;\nshoulder-surfing-reloaded;3305;shouldersurfing;越肩视角重制;Shoulder Surfing Reloaded;\naqua-acrobatics;3306;aquaacrobatics;水游技艺;Aqua Acrobatics;\nsilents-mechanisms;3307;silents_mechanisms;寂静机械;Silent Mechanisms;SM\nproject-potman;3308;proj_pot;背锅人计划;ProjectPotman;\nnbt-crafting;3309;nbtcrafting;数据包 NBT 合并;Nbt Crafting;\ndiregoo;3310;diregoo;DireGoo;;\nogdragon;3311;ogdragon;OG龙+;OGDragon+;\nsevtweaks;3312;sevtweaks;SevTweaks;;\njourneymapstages;3313;jmapstages;旅行地图阶段控制;JourneyMapStages;\ntextile-backup;3314;textile_backup;Textile 备份;Textile Backup;\ngoopers;3315;goopers;Goopers;;\nmob-drops;3316;md;自定义生物战利品;Mob Drops Your Way;\n;3317;gtcefe;GTCE/BC能源拓展;Gregic Power;\nalexs-mobs;3318;alexsmobs;Alex 的生物;Alex's Mobs;AM\nall-stackable;3319;allstackable;可堆叠物品自定义;All Stackable;\nthermal-mj;3320;thermal_mj;热力MJ支持;Thermal MJ;\nctd-core;3321;ctdcore;CTD Core;;\ntime-speed-mod;3322;B3M;Minecraft地球自转/真实昼夜循环;Вращение Земли Майнкрафтская (Accurate day/night-cycles Mod);ВЗМ\n;3323;devops;冒险岛;Maple Story;MS\nmineral-tracker;3324;mineraltracker;Mineral Tracker;;\ncarpet-extra;3325;carpet-extra;Carpet 拓展;Carpet Extra;\n;3326;ctrl;热加载合成表;CraftTweakerReload;CTRL\nviafabric;3327;viafabric;ViaFabric;;\ndouble-slabs;3328;doubleslabs;双重台阶;Double Slabs;\ntools-and-armor-bop;3329;toolsandarmorbop;超多生物群系：旧工具和装甲;Tools And Armor - BOP;\ncentralized-materials;3330;centralizedmaterials;集中材料;Centralized Materials;\nserver-sided-chunk-loader;3331;server_side_chunk_loader;服务器端区块加载器;Server Sided Chunk Loader;\nfluid-cows;3332;fluidcows;流体牛;Fluid cows;\nshulkerboxviewer;3333;shulkerboxviewer;ShulkerBoxViewer;;\ngameinfo;3334;gameinfo;游戏信息;GameInfo;\narcaea;3335;mukaimods;Arcaea韵律源点;Arcaea;\nadvanced-tfc-tech;3336;att;进阶群峦科技;Advanced TFC Tech;ATT\nenriched;3337;enriched;原版增强;Enriched/Vanilla Enhanced;\nsatisforestry;3338;Satisforestry;Satisforestry;;SF\nasmc;3339;asmc;ASMC;;\nsimple-generators;3340;simplegenerators;简易发电机;Simple Generators;\ntfc-bonsai;3341;tfc_bonsai;群峦盆栽;TFC Bonsai;\ntfc-drying-rack;3342;tfc_drying_rack;群峦晾干架;TFC Drying Rack;\n;3343;mcbbswiki;MCBBS Wiki 模组;MCBBS Wiki Mod;MBW\nlumen;3344;lumen;流明;Lumen 🔥;\nopenblocks-elevator;3345;elevatorid;开放式电梯;OpenBlocks Elevator / ElevatorMod;\ncrafttweaker-integration;3346;ctintegration;CrT集成;CraftTweaker Integration;CTIG\nspiders-2-0;3348;spiderstpo;蜘蛛行为增强;Spiders 2.0;\nhammer-metals;3350;hammermetals;锤子材料;Hammer Metals;\nbunch-of-biomes;3351;bunchofbiomes;生物群系扩充;Bunch Of Biomes!;\n;3352;sword_shura,swordshura;修罗之剑;Sword: Shura;\nbetter-slimes;3353;betterslimes;更好的史莱姆;Better Slimes;\nwindowlogging;3354;windowlogging;玻璃板贴合;Windowlogging;\nthe-time-stop-mod;3355;vm,timestopmod;时间静止;The Time Stop Mod;\n;3356;carbon;碳;Carbon;C\nvanilla-hammers;3357;vanillahammers;原版材质锤;Vanilla Hammers;\nimblocker;3358;imblocker;输入法冲突修复;IMBlocker;IMB\nhardcore-map-reset;3359;HardcoreMapReset,hardcoremapreset;HardCore Map Reset;;\ntfc-aged-drinks;3360;aged_drinks;群峦陈酿;TFC Aged Drinks;\nyoutubers-lucky-blocks;3362;ytluckyblocks;YouTuber的幸运方块;Youtuber's Lucky Blocks;\nwandering-trapper;3363;wandering_trapper;流浪猎人;Wandering Trapper;\nchocolate-fix;3364;chocolate;Chocolate Fix;;\nthaumcraft-4-scrolling;3365;tc4tweak,tcscrolling;Thaumcraft 4 Tweaks;;\norderly;3366;orderly;Orderly;;\nwater-control-extreme;3367;watercontrolextreme;Water Control Extreme;;WCE\nvariant16x;3368;variant16x;Variant16x;;\nendremastered;3369;endrem;末地：创世;End Remastered;ER\n;3370;spedosketeleportationcommands;传送指令;Teleportation Commands;\nunique-crops;3371;uniquecrops;独特作物;Unique Crops;UC\nyungs-api;3372;yungsapi;YUNG's API;;\ndawn;3373;dawn;Dawn API;;\nassembly-line-machines;3374;assemblylinemachines;装配线;Assembly Line Machines;ALM\nnetherending-ores;3375;netherendingores;Netherending Ores;;\nFlex-FOV;3376;flexfov;扭曲视野;Flex FOV;\nhooked;3377;hooked;抓钩;Hooked;\ncreatetweaker;3378;createtweaker;CreateTweaker;;\niTorch;3379;itorch;iTorch;;\ngullivern;3381;gulliver;Gullivern;;\nnbt-manipulator;3382;nbtmanipulator;NBT Manipulator;;\nnetherending-ores-configs;3383;;Netherending Ores - Configs;;\n;3384;undiedtale;不死传说;undiedtale;UDT\nsulfur-and-potassium-make-more-gunpowder;3385;sulfurpotassiummod;Sulfur And Potassium - More Gunpowder;;\nstatues;3386;statues;雕像;Statues;\nadvanced-mining-dimension;3387;mining_dimension;先进的采矿维度;Advanced Mining Dimension;\ntime-travel-mod;3388;timetravelmod;时间旅行;Time Travel Mod;\nbackslotaddon;3389;backslotaddon;背用武器拓展;BackSlotAddon;\noptifabric-1-8-9;3390;;OptiLegacy;;\n;3391;;Legacy Fabric;;\ngt6-ore-helper;3392;gt6orehelper;GT6 矿石助手;GT6 Ore Helper;\napplied-integrations;3393;appliedintegrations;应用集成;Applied Integrations;AI\n;3394;noitemrest;不剩魔法;NoItemRest;NIR\ninventory-hud-forge;3395;inventoryhud;物品栏HUD+;Inventory HUD+;\nprehistoric-flora;3396;lepidodendron;Prehistoric Flora;;\nlangsplit;3397;langsplit;LangSplit;;\nlegacy-fabric-api;3398;legacy-fabric-api;Legacy Fabric API;;\nkrypton;3399;krypton;氪;Krypton;\nmana-gear;3400;managear;Mana Gear;;\nnot-enough-enchantments;3401;nee,notenoughenchantments;Not Enough Enchantments;;NEE\nbon-appetit-forge;3402;bonappetit;祝您好胃口;Bon Appetit;\ngugu-utils;3403;gugu-utils;咕咕工具;GuGu Utils;\nscuba-gear;3404;scuba_gear;潜水装备;Scuba Gear;\navatar-mod-2-out-of-the-iceberg;3405;avatarmod;降世神通：突破冰山;Avatar Mod 2: Out of the Iceberg;\n;3406;hydrogen;氢;Hydrogen;\nlazydfu;3407;lazydfu;DFU载入优化;LazyDFU;\n;3408;tic-tacs;井字棋;tic-tacs;\nridable-ender-pearls;3409;;可骑乘末影珍珠;Ridable Ender Pearls;\ngildedarmor;3410;gildedarmor;镀金盔甲;GildedArmor;\ndeadly-book;3411;deadlybook;致命书本;Deadly Book;\nspeedy-hoppers;3412;speedyhoppers;极速漏斗;Speedy Hoppers;\nindium;3413;indium;铟;Indium;\n;3414;cadmium;镉;Cadmium;\ngo-fish;3416;go-fish;一起钓鱼吧！;Go Fish;\nmore-player-models;3417;moreplayermodels;更多玩家模型;More Player Models;MPM\nloot-overhaul;3418;lootoverhaul;Loot Overhaul;;\nminers-helmet;3419;mining_helmet;矿工头盔;Miner's Helmet;\nlightweight-blood-mechanics;3420;lbm;轻量级流血机制;Lightweight Blood Mechanics;LBM\nkbackup-fabric;3421;;KBackup-Fabric;;\nsmooth-boot;3422;smoothboot;流畅加载;Smooth Boot;\n;3423;;Minecraft Thread Pool Agent;;\nnetherite-plus-mod;3424;netherite_plus;下界合金拓展;Netherite Plus Mod;\nchunkpregenerator;3425;chunkpregen,chunkpregenerator;区块预生成器;Chunk-Pregenerator;\nantighost;3426;antighost;幽灵方块同步修复;AntiGhost;\n;3427;abyssaltweaker;AbyssalTweaker;;\nbackslot;3428;backslot;背用武器;BackSlot;\nminecraftcapes-mod;3429;minecraftcapes;MinecraftCapes Mod;;\nspartan-weaponry-arcana;3431;spartanweaponryarcana;斯巴达奥法;Spartan Weaponry Arcana;\n;3432;xzhlpe;原初修真属性重置;XiuZhenHelper;\ngreek-fantasy;3433;greekfantasy;希腊幻想;Greek Fantasy;\narchitectury-api;3434;architectury;Architectury API;;\n;3435;miac;MI反作弊;MIAntiCheat;MIAC\ncreateaddition;3437;createaddition;机械动力：创想附加;Create Crafts & Additions;CC&A\nsmithee;3438;smithee;Smithee;;\ndragon-egg-replicator;3439;dd;龙蛋复制器;Dragon Egg Replicator;\nassassins-creed-blocklegend;3440;acbl;刺客信条：方块传奇;Assassin's Creed Blocklegend;ACBL\nelemental-ingots-ei;3441;elemental_ingots;元素锭;Elemental Ingots;EI\nsolargeneration;3442;solargeneration;太阳能发电机;Solar Generation;\nenchant-with-mob;3443;enchantwithmob;Enchant With Mob;;\n;3444;gibby_pun;The Ultimate Pun Mod!;;\nworldstatecheckpoints;3445;WorldStateCheckpoints,worldstatecheckpoints;WorldStateCheckpoints;;\ndungeons-plus;3446;dungeons_plus;Dungeons Plus;;\nliquid-enchanting;3447;liquidenchanting;药水附魔;Liquid Enchanting;\nshutup-experimental-settings;3448;shutupexperimentalsettings;禁用实验性设置警告;Shutup Experimental Settings!;\ndatafixerslayer;3449;datafixerslayer;DataFixerSlayer;;\nhexcraft;3450;HexCraft,hexcraft;HEXCraft;;\npolice-mod;3451;policemod;警察;Police Mod;\nenhanced-celestials;3452;enhancedcelestials;月亮事件;Enhanced Celestials;\nresourceful-bees;3453;resourcefulbees;缤纷蜜蜂;Resourceful Bees;\nheld-item-info;3454;held-item-info;手持物品信息;Held Item Info;\ncleancut;3455;cleancut;干净利落;CleanCut;\nplus-tweaks;3456;plustweaks;Plus Tweaks;;\nglobal-gamerules;3457;globalgamerules;全局游戏规则;Global GameRules;\n;3458;mineshot;高清截图;Mineshot;\nsave-my-stronghold-fabric;3459;savemystronghold;Save My Stronghold!;;\nphysics-mod;3460;physicsmod;真实物理;Physics Mod;\nbluemap;3461;bluemap;BlueMap;;\ndregora;3462;dregora;德雷戈拉;Dregora;\nk4lib;3463;k4lib;K4Lib;;\ndeadly-end-phantoms;3464;deadlyendphantoms;Deadly End Phantoms;;\nspice-of-life-potato-edition;3465;solpotato;生活调味料：马铃薯版;Spice of Life: Potato Edition;\neasiervillagertrading;3466;easiervillagertrading;简单村民交易;EasierVillagerTrading;\nwwta;3467;wwta;聊天栏时间显示;When Was That Again;wwta\nars-nouveau;3468;ars_nouveau;新生魔艺;Ars Nouveau;\neidolon;3469;eidolon;幻梦;Eidolon;\nrats-ratlantis;3470;ratlantis;老鼠：鼠西洲;Rats: Ratlantis;\nwthit;3471;wthit;What The Hell Is That;;WTHIT\nmodern-industrialization;3472;modern_industrialization;现代工业化;Modern Industrialization;MI\nchunk-pregenerator-fabric;3473;chunkpregen;区块预生成器 (Fabric);Fabric Chunk Pregenerator;\njumploader;3474;jumploader;Jumploader;;\nunloader;3475;unloader;Unloader;;\nbetter-animal-models;3476;betteranimals;更真实的动物;Better animal models;\n;3477;minelp,mod_MineLittlePony;我的小马驹;Mine Little Pony;MineLP\ndruidcraft;3479;druidcraft;德鲁伊工艺;Druidcraft;\nberry-good;3480;berry_good;Berry Good;;\nsavage-and-ravage;3481;savageandravage,savage_and_ravage;残暴与掠夺;Savage & Ravage;\njade;3482;jade;玉 🔍;Jade 🔍;\nsomnia;3483;somnia;梦醒时分;Somnia Awoken;\nindustrial-reborn;3484;indreb,industrialreborn;Industrial Reborn;;\nmo-structures-forge;3485;mostructures;更多自然生成结构;Mo' Structures;\nwallpapercraft-a-fresh-roll;3486;wallpapercraft;墙纸创造;Wallpapercraft - A Fresh Roll;\n;3488;moddd;Overlord in Minecraft;;OiM\n;3489;bedrockworkshop;基岩工坊;Bedrock Workshop;BW\nunionlib;3490;unionlib;UnionLib;;\nbotany-trees;3491;botanytrees;植树盆栽;Botany Trees;\nbetter-third-person;3492;betterthirdperson;Better Third Person;;\nsurvive;3493;survive;生存要素;Survive;\nwallpapercraft-a-fresh-roll-stairs;3494;wallpapercraftstairs;墙纸创造：楼梯;Wallpapercraft - A Fresh Roll (Stairs!);\nwallpapercraft-a-fresh-roll-slabs;3495;wallpapercraftslabs;墙纸创造：台阶;Wallpapercraft - A Fresh Roll (Slabs!);\nwallpapercraft-a-fresh-roll-walls;3496;wallpapercraftwalls;墙纸创造：围墙;Wallpapercraft - A Fresh Roll (Walls!);\ncompact-crafting;3497;compactcrafting;Compact Crafting;;\nassembly-lines;3498;assemblyline;装配线;Assembly Lines;\nbotany-pots;3499;botanypots;植物盆栽;Botany Pots;\ncallable-horses;3500;callablehorses;召之马来;Callable Horses;\n;3501;;Kibble Patcher;;\nultimate-solar-panels;3502;ultimatesolarpanels;Ultimate Solar Panels;;\nslight-gui-modifications;3503;slight-gui-modifications;界面微调;'Slight' Gui Modifications;\nelemental-craft;3504;elementalcraft;元素工艺;Elemental Craft;\njust-another-rotten-flesh-to-leather-mod;3505;jrftl;腐肉换皮革;Just Another Rotten Flesh to Leather Mod;JRFTL\nspatial-harvesters-forge;3506;spatialharvesters;Spatial Harvesters;;\nemotecraft;3507;emotecraft;表情工艺;Emotecraft;\npattysmorestuff;3508;pattysmorestuff;PattysMoreStuff;;PMS\nhats;3509;hats;帽子;Hats;\nsmoother-bedrock;3510;smootherbedrock;平滑基岩层;Smoother Bedrock;SB\nc2me-fabric;3511;c2me;C^2M 引擎;C^2M-Engine/Concurrent Chunk Management Engine;C2ME\ndatapack-anvil;3512;dpanvil;DataPack Anvil;;\nnuclear-science;3513;nuclearscience;核科学;Nuclear Science;\nglaretorch;3514;glaretorch;强光火把;Glare Torch;\nmrcrayfishs-jumping-castle-mod;3515;cjcm;MrCrayfish的充气城堡;MrCrayfish's Jumping Castle;\nexposer;3516;exposer;Exposer;;\nto-the-bat-poles;3517;adpoles;\"To the Bat Poles!\";;\ncc-restitched;3518;computercraft;CC: Restitched;;\nwearables;3519;wearables;可穿戴设备;Wearables;\n;3520;advancedfurnace;先进熔炉;Advanced Furnace;\n;3521;arkitems;明日方舟物品;ArkItems;\nbetter-weather;3522;betterweather;更好的天气;Better Weather;BW\nultimate-car-mod;3523;car;终极汽车;Ultimate Car Mod;\ncraft-time;3524;timecraft;耗时合成;Craft Takes Time;CTT\nbetterf3;3525;betterf3,betterf3forge;更好的F3;BetterF3;\n;3526;ArknightsBlade,arknightsblade;方舟拔刀;ArknightsBlade;AB\nthe-abyss-chapter-ii;3527;theabyss;深渊：第二章;The Abyss: Chapter II / The Abyss II - The Other Side;TATOS\nmapper-base;3528;mapperbase;Mapper Base;;\nbedrockify;3529;bedrockify;BedrockIfy;;\n;3530;minecraft_tag;Minecraft-凝胶测试轨道;Minecraft Tag-Gel test track;MT\nrftools-power;3531;rftoolspower;RF工具：能量;RFTools Power;\npipez;3532;pipez;Pipez;;\ncoins-je;3533;coins;Coins JE;;\n;3534;tlm;最后的时刻;The Last Mod;TL\nbeenfo;3535;beenfo;蜂巢信息;Beenfo;\nchest-building-blocks;3536;chestblocks;Chest Building Blocks;;\nzycraft;3537;zycraft;ZYCraft;;\nquitconfirm;3538;quitconfirm;退出前确认;QuitConfirm;\nruined-equipment;3539;ruined_equipment;Ruined Equipment;;\nclickthrough;3540;clickthrough;穿牌开箱;ClickThrough;\ndynamic-sound-filters;3541;dynamicsoundfilters;动态声音滤波器;Dynamic Sound Filters;\nbinniepatcher;3542;BinniePatcher;BinniePatcher;;\nsubterranean-wilderness;3543;subwild;Subterranean Wilderness;;\n;3544;dynamic;科能工程1;Technical Engineering 1;TEN1\nconstruction-wand;3545;constructionwand;建筑手杖;Construction Wand;\nsqueedometer;3546;Squeedometer,squeedometer;Squeedometer;;\nforgery;3547;fabrication;Forgery;;\nfabrication;3548;fabrication;Fabrication;;\nmcdj;3549;mcdj;MCDJ;;\nboost-boots;3550;boostboots;Boost Boots;;\nbetter-sodium-video-settings-button;3551;bettersodiumvideosettingsbutton;更好的钠视频设置按钮;Better Sodium Video Settings Button;BSVSB\n;3552;difficult_minecraft;困难的Minecraft;Difficult Minecraft;\nartisan-worktables-1-16;3553;artisanworktables;工匠之作2 / 工匠工作台2;Artisan Worktables 2 / Artisan Worktables 1.16;AW2\nthermal-solars;3554;thermalsolars;Thermal Solars;;\nsupplementaries;3555;supplementaries;锦致装饰;Supplementaries;\nmore-fuels-mod;3556;morefuelsmod;More Fuels Mod;;MFM\n;3557;flammpfeil.slashblade.exsa;更多超强SA;SlashBlade-ExSA;ExSA\npointless-tech-collective;3558;pointless_tech_collective;Pointless Tech Collective;;PTC\nrevampedwolf;3559;revampedwolf;RevampedWolf;;\nbedrock-b-gone;3560;betterbedrock;Bedrock B Gone;;\n;3561;last_minute;最后时刻;Lastminute;LMNT\nelemental-creepers-og;3562;elementalcreepers;元素爬行者;Elemental Creepers;\ngymcraft;3563;gymcraft;GymCraft;;\ninfernal-expansion;3564;infernalexp;地狱扩展;Infernal Expansion;IE\n;3565;ng;更多装备与工具;;MEaT\n;3566;Manymonsters;独立：更多妖怪;Manymonsters;myms\nconfigurable-mob-potion-effects;3567;configurablemobpotioneffects;怪物药水效果自定义;Configurable Mob Potion Effects;CMPE\nsapience;3568;sapience;Sapience;;\nnetherite-nuggets-fabric;3569;netherite_nugget;下界合金粒;Netherite Nuggets;\neroding-stone-entities;3570;erodingstoneentities;Eroding Stone Entities;;\ndirebats-forge;3571;direbats;Direbats;;\nadvanced-electric-tools;3572;advancedelectrictools;高级电动工具;Advanced Electric Tools;\n;3573;slashblade.housamo;犬仕双刃;Slashblade-Housamo;\nbackpacker;3574;backpacker;Backpacker;;\n;3575;improvedwolves;改进的狼;Improved Wolves;\nynet;3576;ynet;YNet;;\nsimple-drawers;3577;simpledrawers;简单的抽屉;Simple Drawers;\ncharmonium;3578;charmonium;Charmonium;;\nxnicex;3579;nice;xNICEx;;\nkrate;3580;krate;Krate;;\nchickensshed;3581;chickensshed;鸡棚重置版;ChickensShed;\nchickenshed;3582;ChickenShed;ChickenShed;;\nexpanded-storage;3583;expandedstorage;扩展存储;Expanded Storage;ES\nproject-mmo;3584;pmmo;Project MMO;;PMMO\nabnormals-delight;3585;abnormals_delight;Abnormals Delight;;\njojos-bizarre-survival;3586;jojomod;JoJo's Bizarre Survival;;\n;3587;PlayerContainerFix;背包合成修复;Gambiarra;\nsomnus;3588;somnus;Somnus API;;\nlost-trinkets;3589;losttrinkets;遗失饰物;Lost Trinkets;\nlookingglass;3590;LookingGlass;LookingGlass;;\nenvironmental;3591;environmental;自然环境;Environmental;\n;3592;slashblade.qian;魔刀千刃;Qian The mighty Blade;\ngolden-airport-pack-immersive-vehicles-content;3593;ivairlinerpack;Golden Airport Pack;;GAP\nhole-filler-mod;3595;hole_filler_mod;洞口填充器;Hole Filler;\n;3596;gdzb;更多装备;More Equipment;MQT\nsimply-tea;3597;simplytea;简单的茶！;Simply Tea!;ST\nbuttermilk;3598;buttermilk;Buttermilk;;\ndiet;3599;diet;均衡饮食;Diet;\nbrazilian-fields;3600;brazilian_fields;巴西风情;Brazilian Fields;\nae2-extras;3601;ae2extras;AE2 附加;AE2 Extras;\nanimalium;3602;animalium;动物;Animalium;\nmob-origins;3603;moborigins;生物起源;Mob Origins;\nbetter-default-biomes;3604;betterdefaultbiomes;更好的默认生物群系;Better Default Biomes;\nkaimyentity;3605;kaimyentity;KAI我的实体;KAIMyEntity;\ntablechair;3606;tablechair;桌椅;TableChair;\nwhen-dungeons-arise;3607;dungeons_arise;地牢浮现之时;When Dungeons Arise;WDA\nanimal-feeding-trough;3608;animal_feeding_trough;动物喂养槽;Animal Feeding Trough;\nwiki-zoomer;3609;wikizoomer;Wiki Zoomer;;\npersonality;3610;personality;Personality;;\nallurement;3611;allurement;Allurement;;\ndwarf-miner-totem;3612;dwarf_miner;矮人矿工;Dwarf Miner Totem;\nyungs-better-portals;3613;betterportals;YUNG 的传送门机制重构;YUNG's Better Portals;\ngerman-road-signs-mod;3614;schildermod;德国交通标志;German Road Signs Mod;\niguana-in-a-blanket;3615;iguana-blanket;真实调整;Iguana in a Blanket;\nanthill-inside;3616;anthillinside;蚂蚁物流;Anthill Inside;\nnei-integration;3617;neiintegration;NEI Integration;;\nuncrafting-grinder;3618;uncraftingtable;分解磨床;Uncrafting Grinder;\nemendatus-enigmatica;3619;emendatusenigmatica;Emendatus Enigmatica;;EE\n;3620;slashblade.kitsunekamuya;拔刀剑附属：御神刀「神狐」;SlashBlade-KitsuneKamuya;\n;3621;slashblade.shinkuofuda;拔刀剑附属：炼狱刀「真红」;Slashblade-ShinkuOfuda;\n;3622;ryuichimonji;龙一文字;RyuIchimonji;\njousting;3623;jousting;马术;Jousting;\nbamboo-blocks;3624;bamboo_blocks;Bamboo Blocks;;\nscape-and-run-parasites;3625;srparasites;逃逸：寄生体;Scape and Run: Parasites;SRP\njust-enough-drags;3626;justenoughdrags;更多JEI拖拽;Just Enough Drags;JEDrag\nexnihilo-automation;3627;exnihiloauto;无中生有：自动化;Exnihilo: Automation;\nworld-handler-command-gui;3628;worldhandler;世界管理器：图形交互;World Handler - Command GUI;\nentityculling;3629;entityculling;实体渲染机制优化;Entity Culling;\n;3630;halocraft;光环;HaloCraft mod;\ntea-kettle;3631;tea_kettle;茶壶;Tea Kettle;\nenhanced-block-entities;3632;enhancedblockentities;方块实体优化;Enhanced Block Entities;EBE\ngun-customization-infinity;3633;guncus;Gun Customization: Infinity;;\ndungeons-gear;3634;dungeons_gear;地下城装备;Dungeons Gear;\nintegrated-nbt;3635;integratednbt;NBT 联合;Integrated NBT;\npillagers-plus;3636;pillagertrust;Pillagers Plus;;\n;3637;PotionMosaic,potionmosaic_;药水镶嵌宝石;PotionMosaic;\n;3638;PotionEnchanting,potionenchanting;趣味药水附魔;PotionEnchanting;\n;3639;StubbornMonster;怪物的倔强;StubbornMonster;\nabundance;3640;abundance;Abundance;;\n;3641;workbenchfc;开局三子;WorkbenchFC;WFC\n;3642;ultracraft;奥特工艺;UltraCraft;\njust-enough-calculation;3643;jecalculation;Just Enough Calculation;;JECa\nmythicbotany;3644;mythicbotany;神话植物学;MythicBotany;\nimproved-mobs;3645;improvedmobs;怪物增强;Improved Mobs;\nlimitless;3646;limitless;无上限附魔;Limitless;\nvpn-blocker;3647;vpnblocker;禁用VPN;VPN Blocker;\npremium-wood;3648;premium_wood;Premium Wood;;\ntenshilib;3649;tenshilib;TenshiLib;;\nrandomportals;3650;randomportals;随意传送门;RandomPortals;\nconfigured;3651;configured;配置界面;Configured;\n;3652;siwangyinyve;专业团队music;siwangyinyve;\njellyfishing;3653;jellyfishing;抓水母;Jellyfishing;\naccurate-block-placement;3654;accurateblockplacement;Accurate Block Placement;;\npro-placer;3655;proplacer;放置专家;Pro Placer;PPlacer\nfarmers-delight-compats;3656;farmersdelightintegrations;Farmer's Delight Compats;;\nfarmers-tea;3657;farmerstea;农夫的茶;Farmer's Tea;\nfarmers-delight-integration;3658;farmersdelightintegration;Farmer's Delight Integration;;\nmagical-psi-redux;3659;magipsi;Magical Psi: Redux;;\nanvil-fix;3660;anvil_fix;铁砧修复;Anvil Fix;\nno-increasing-repair-cost;3661;noincreasingrepaircost;No Increasing Repair Cost;;\nwool-temples;3662;wooltemples;羊毛神殿;Wool Temples;\nexp-bottling;3663;exp_bottling;经验装瓶;EXP Bottling;\nbuildersaddition;3664;buildersaddition;Builders Crafts & Additions;;\ngt-remastered;3665;gtr;格雷科技重制版;GT: Remastered;GTR\ntips;3666;tips,tipsmod;提示;Tips;\nInteractio;3667;interactio;Interactio;;\nadvancement-book;3668;advancementbook;进度之书;Advancement Book;\ndrawers-tooltip;3669;drawerstooltip;抽屉提示;Drawers Tooltip;\nparadise-lost;3670;paradise_lost;天境：重生;Paradise Lost;\n;3671;extended;拓展;Extended;EX\n;3672;gregfood;格雷的食物;GregFood;GF\nmore-jellyfish;3673;more_jellyfish;更多水母;More Jellyfish;\neasy-ladders;3674;ladderhelper;Easy Ladders;;\nbeta-dannys-expansion;3675;dannys_expansion;Danny's Expansion;;\ninventory-tweaks-renewed;3676;invtweaks;Inventory Tweaks Renewed;;\narrp;3677;advanced_runtime_resource_pack;高级运行时资源包;Advanced Runtime Resource Packs;ARRP\ntougher-glass-forge;3678;tougherglass;Tougher Glass;;\ndifferentiate;3679;differentiate;Differentiate;;\ncookie-core-fabric;3680;cookiecore;Cookie Core (Fabric);;\nimage-world-generator;3681;imggen;图片地图转换器;Image World Generator;\ninductive-logistics;3682;indlog;集成物流;Inductive Logistics;\n;3683;crystal_two;晶体2;;\nautomatic-path;3684;autopath;Automatic Path;;\n;3685;useful_coals_2;实用煤炭2;Useful Coals 2;UfC2\nmorevanillalib;3686;morevanillalib;MoreVanillaLib;;\nvanilla-excavators;3687;vanillaexcavators;Vanilla Excavators;;\ndynagear;3688;dynagear;DynaGear;;\nmorevanillatools;3689;morevanillatools;更多原版工具;MoreVanillaTools;MVT\ncolorful-health-bar;3690;colorfulhealthbar;Colorful Health bar;;\ntipline;3691;tipline;TipLine;;\nultimate-plane-mod;3692;plane;终极飞机;Ultimate Plane Mod;\nsimple-voice-chat;3693;voicechat;简单的语音聊天;Simple Voice Chat;SVC\nnot-enough-milk;3694;notenoughmilk;Not Enough Milk;;\newewukeks-musket-mod;3695;musketmod;ewewukek 的火枪模组;ewewukek's Musket mod;\nirisshaders;3697;iris;Iris Shaders;;\n;3698;dimthread;世界多线程化;DimensionalThreading;\nchambers;3699;chambers;处理仓;Chambers;\n;3700;nokiamod;诺基亚模组;Nokia Mod;\nsodium-extra;3701;sodium-extra,sodium_extra;钠 · 扩展;Sodium Extra;\ngrind-enchantments;3702;grindenchantments;砂轮增强;Grind Enchantments;\nvoice-chat;3703;voicechat;语音聊天;Voice Chat;\nxnet-gases;3705;xnetgases;XNet Gases;;\nfiling-cabinets-refurbished;3706;filcabref;Filing Cabinets Refurbished;;\n;3707;magic_maid;魔法女仆教堂遗迹;Magic Maid The Church Ruins;\ndropz;3708;dropz;Dropz;;\nfigura;3709;figura;Figura;;\n;3710;c_storage;容积储存;Capacity Storage;\nautopilot;3711;autopilot;Autopilot;;\napplied-energistics-2-wireless-terminals;3712;ae2wtlib;AE2无线终端;Applied Energistics 2 Wireless Terminals;AE2WT\nappliedenergisticstweaker;3713;appliedenergisticstweaker;AppliedEnergisticsTweaker;;\nsleeping-bags;3714;sleeping_bags;睡袋;Sleeping Bags;\nmass-inscriber;3715;mass-inscriber;Mass Inscriber;;\n;3716;secureexplosion;爆炸保护;SecureExplosion;\nrandom-title;3717;randomtitle;随机标题;Random Title;\nastikorcarts;3718;astikorcarts;AstikorCarts;;\n;3719;invsync;背包同步;InvSync;\njea;3720;jea;JEI进度;Just Enough Advancements;JEA\net-futurum-requiem;3721;etfuturum;把未来带回现实;Et Futurum Requiem;EFR\nbetter-op;3722;better-op;更好的 OP;Better Op;\nfastworkbench-minus-replacement;3723;fastbenchminusreplacement;FastWorkbench Minus Replacement;;\ncrimson-compass;3724;crimsoncompass;绯红指南针;Crimson Compass;\ntinkers-construct;3725;tconstruct;匠魂3;Tinkers' Construct 3;TiC3\nryoramas-terraria-mod;3726;trewrite;TerrariaMod;;\nsteves-workshop;3727;StevesWorkshop;Steve's Workshop;;\nupgraded-ender-chests;3728;upgradedechests;高级末影箱;Upgraded Ender Chests;UEC\nydm-ygo-dueling-mod-ii;3729;ydm;YGO Dueling Mod II;;YDM\nversioner;3730;versioner;整合包版本工具;Versioner;\n;3731;meshi;饭;Meshi;\nengineers-workshop-reborn;3732;engineersworkshop;Engineers Workshop Reborn;;\n;3733;more_coals;煤炭工艺-更多煤炭;More Coals;CCMC\n;3734;immersive-cursedness;沉浸传送门服务端版;Immersive Cursedness;\ncrimson-xp-charm;3736;crimsonxp;经验护符;Crimson XP Charm;\nlocatebiome;3737;locatebiome;LocateBiome;;\nenderman-evolution;3738;endermanevo;Enderman Evolution;;\nsophisticated-backpacks;3739;sophisticatedbackpacks;精妙背包;Sophisticated Backpacks;\nage-of-exile;3740;mmorpg;Age of Exile;;\nwhoosh;3741;whoosh;Whoosh;;\ngregtech-intergalactical;3742;gti,gregtech,gtu;泛银河系格雷科技;GregTech Intergalactical;GTI\ncatalogue;3743;catalogue;模组目录;Catalogue;\nlibblockattributes;3744;libblockattributes;Lib Block Attributes;;\ndungeons-of-exile;3746;world_of_exile;Dungeons of Exile;;\nlots-more-food-forge;3747;morefood;Lot's More Food;;\npe-superheroes-mod;3748;;SuperHeroes Mod;;\nnecromancy;3749;necromancy;Necromancy;;\nlibrary-of-exile;3750;library_of_exile;Library of Exile;;\n;3751;;Mite-打破一切;MITE-Break Everything;\nvariegated;3752;variegated;Variegated;;\nchest-cavity;3753;chestcavity;胸腔;Chest Cavity;\nextended-note-block;3754;noteblockne;音符盒扩展;Extended Noteblocks;NNE\nhbms-nuclear-tech-mod-reloaded;3755;hbm;HBM的核科技重制版;Hbm's Nuclear Tech Mod Reloaded;HBMR\nloading-timer;3756;lt;加载时长显示;Loading Timer;\nocrocketry;3757;ocrocketry;OCRocketry;;\nchime-fabric;3758;chime;Chime;;\nwitherite;3759;witherite;凋灵合金;Witherite;\nmermod-fabric;3760;mermod;Mermod / Mermaid Tail Mod;;\nastromine-core;3761;astromine;天体矿工：核心;Astromine: Core;\ndungeondq;3762;dungeondq;DungeonDQ;;\ndynamic-trees-integrated-dynamics;3763;dynamictreesintegrateddynamics;动态的树：动态联合/集成动力附属;Dynamic Trees - Integrated Dynamics;\nskyblock-builder;3764;skyblockbuilder;空岛建立者;Skyblock Builder;\nantiportals;3765;antiportals;反传送门;AntiPortals;\ntrash-cans;3766;trashcans;垃圾桶;Trash Cans;\nmattdahepic-core;3767;mdecore;MattDahEpic Core;;MDECore\nmob-farm-helpers-forge;3768;mobfarmutilities;刷怪塔小帮手;Mob Farm Helpers;\nmetal-bushes;3769;metalbushesmod;金属灌木;Metal Bushes;\n;3770;no cheating;禁止作弊;No cheating;\n;3771;Vanilla Behavior Pack;附加包模板;Bedrock Add-On Sample Files;\nthe-vanilla-experience;3772;thevanillaexperience;The Vanilla Experience;;TVE\nflower-seeds;3773;flowerseeds;Flower Seeds 2;;\n;3774;impact;Impact;;\n;3775;;直线生存;Straight Line Minecraft;SLM\nastemirs-forest-craft;3777;forestcraft;Astemir's Forest Craft;;\nastromine-foundations;3778;astromine;天体矿工：基础;Astromine: Foundations;\ncreate-stuff-additions;3779;create_stuff_additions,create_sa;机械动力：物品附加;Create Stuff 'N Additions;\nslab-machines;3780;slabmachines;台阶型设备;Slab Machines;\nmore-create-stuffs;3781;morecreatestuffs;更多机械动力物品;More Create Stuffs;\nrsinfinitybooster;3782;rsinfinitybooster;RS 无限范围增幅卡;RSInfinityBooster;\ncuneiform;3783;cuneiform;Cuneiform;;\nterraincognita;3784;terraincognita;Terra Incognita: The Unknown Land;;\ncloth-api;3785;cloth-api;Cloth API;;\ningameime;3786;ingameime;游戏内输入法;IngameIME;\nyungs-better-strongholds;3787;betterstrongholds;YUNG 的要塞优化;YUNG's Better Strongholds;\nmojangdark;3788;mojangdark;暗色加载;MojangDark;\nastromine-technologies;3789;astromine;天体矿工：科技;Astromine: Technologies;\nastromine-transportations;3790;astromine;天体矿工：运输;Astromine: Transportaions;\nazuredooms-chunk-loader;3791;chunk;AzureDoom's Chunk Loader;;\ncreeper-spores;3792;creeperspores;苦力怕孢子;Creeper Spores;\nmore-dragon-eggs;3793;moredragoneggs;更多的龙蛋;More Dragon Eggs;\nmore-red;3794;More_Red,morered;更多红石;More Red;\n;3795;herobrine;Herobrine;;\ncraftable-items;3797;;Un-Craftable;;\nex-nihilo-sequentia-mekanism-addon;3798;exnihilomekanism;无中生有：传承 - 通用机械扩展;Ex Nihilo: Sequentia - Mekanism Addon;\npanorama;3799;panorama;全景图;Panorama;\nbgcore;3800;bgcore;BGCore;;\ninduction-charger;3801;InductionCharger;Induction Charger;;\ntiered-forge;3802;tiered;Reforged / Tiered;;\nc-o-m-b-a-t;3803;combat;Concept Of Magic Battle And Technology;;COMBAT\nburden-of-time;3804;burdenoftime;Burden Of Time;;\ngamemenumodoption;3805;gamemenumodoption;游戏菜单模组设定;Game Menu Mod Option;\ngame-menu-remove-gfarb;3806;gamemenuremovegfarb;游戏菜单移除GFARB;Game Menu Remove GFARB;\nrandomdeathmessage;3807;randomdeathmessage;随机死亡消息;RandomDeathMessage;\nex-nihilo-sequentia-ae2-addon;3808;exnihiloae;无中生有：传承 - 应用能源2扩展;Ex Nihilo: Sequentia - AE2 Addon;\nsweetmagic;3809;sweetmagic;甜蜜与魔法;SweetMagic;\nastromine-discoveries;3810;astromine;天体矿工：探索;Astromine: Discoveries;\nazuredooms-angel-ring;3811;doomangelring;AzureDoom的天使指环;AzureDooms Angel Ring;\ntogglelib;3812;togglelib;列车手柄库;ToggleLib;TBLib\nregrowth;3813;regrowth;再生;Regrowth;\nmobs-main-menu;3814;mobsmainmenu;Mobs Main Menu;;\nchaos-awakens;3816;chaosawakens;混沌觉醒;Chaos Awakens;CA\nhot-or-not;3817;hotornot;Hot or Not;;\nplayerpig;3818;ppig;PlayerPig;;\ntumble-dryer;3819;tumble_dryer;Tumble Dryer;;\ncat-jammies;3820;catjammies;Cat Jammies;;\nrhino;3821;rhino;犀牛;Rhino;\nfastsuite;3822;fastsuite;配方性能优化;FastSuite;FS\ntwerkitmeal;3823;twerkitmeal;跳舞生长一切;Twerk To Grow All The Things;TwerkItMeal\nthermal-foundation;3824;thermal;热力系列;Thermal Series;\npiggybanks;3825;piggybanks;存钱罐;Piggybanks;\nin-control;3826;incontrol;In Control!;;\nmystical-agriculture-refabricated;3827;mysticalagriculture;神秘农业：Fabric版;Mystical Agriculture: Refabricated;\npedestals;3828;pedestals;Pedestals;;\naerial-affinity;3829;aerialaffinity;Aerial Affinity;;\ncommunism-life-and-art;3830;communism_lifeandart,communism_life_and_art_backtrack,cla;共产主义：生活与艺术;Communism:Life and Art;CLA\nproject-war-dance;3831;wardance;战舞计划;Project: War Dance;PWD\nfaster-ladder-climbing;3832;fasterladderclimbing;更快的爬梯;Faster Ladder Climbing;\ninfinitymending;3833;infinitymending;无限+经验修补;InfinityMending;\nbetter-than-mending;3834;betterthanmending;更好的经验修补;Better Than Mending;\nelenai-dodge-2;3835;elenaidodge2,elenaidodge;Elenai Dodge 2;;\ntaterzens-forge;3837;taterzens;Taterzens;;\ndragons-survival;3838;dragonsurvival;龙之生存;Dragon Survival;DS\n;3839;;Legacy Mod Menu;;\nno-lan-cheats;3840;nolancheats;禁用局域网作弊;No LAN Cheats;\ndashloader;3841;dashloader;疾速加载;DashLoader;\ncaveworld-2;3842;caveworld;洞穴世界;Caveworld 2;\nbreak-progress;3844;breakprogress;破坏进度;Break Progress;BP\nitem-model-fix;3845;itemmodelfix;物品模型修复;Item Model Fix;\n;3846;useful_coals_2_integrations;实用煤炭2联动;Useful coals 2 Integrations;UfC2I\nadditional-pipes-for-buildcraft;3847;additionalpipes;附加管道;Additional Pipes for Buildcraft;\nlolasm;3848;loliasm;CensoredASM;;\nrftools-utility;3849;rftoolsutility;RF工具：实用设备;RFTools Utility;\nctm-fabric;3850;ctm;连接纹理Fabric版;ConnectedTexturesMod for Fabric;CTMF\nwhats-that-slot;3851;wts;What's That Slot?;;WTS\npmmo-and-nbt-compat;3852;dicemcpmmonbt;PMMO and NBT Compat;;\ntechstacks-heavy-machinery-mod;3853;machinemod;TechStack's Heavy Machinery Mod;;\nenhanced-mushrooms;3854;enhanced_mushrooms;蘑菇增强;Enhanced Mushrooms;\nversatile-portals;3855;versatileportals;通用传送门;Versatile Portals;\nspawn-egg-recipes;3856;spawn;刷怪蛋配方注册;Spawn Egg Recipes;\nconfigurable-extra-mob-drops;3857;configurableextramobdrops;可配置的额外怪物掉落;Configurable Extra Mob Drops;CEMD\nenhanced-mob-spawners;3858;spawnermod;刷怪笼增强;Enhanced Mob Spawners;\n;3859;chair;原版椅子;Vanilla Chair;\nautorun;3860;simpleautorun;AutoRun;;\nanimania-farm;3861;animania;动物谷:农场;Animania Farm;\nanimania-extra;3862;animania;动物谷:额外动物;Animania Extra Animals;\nanimania-cats-dogs;3863;animania;动物谷:猫猫狗狗;Animania Cats & Dogs;\nbetter-foliage-renewed;3864;betterfoliage;更好的树叶重制版;Better Foliage Renewed;\nblock-swap;3865;blockswap;Block Swap;;\nbasalt-walker;3866;basalt_walker;玄武岩行者;Basalt Walker;\n;3867;doubancraft;豆瓣工艺;DouBan Craft;\nendless-ocean-adventures;3868;endlessoceans;无尽海洋：冒险;Endless Ocean: Adventures;EO\nbpm;3869;bpm;BPM;;\nstone-circles;3870;magic_stone_circles;Stone Circles;;\nphase-potion;3871;phase;相位药水;Phase Potion;\nmarblegates-exotic-enchantment-flowing-agony;3872;flowingagony;白门的奇异附魔：苦痛长河;MarbleGate's Exotic Enchantment: Flowing Agony;\nmeasurements;3873;measurements;Measurements;;\nvisible-armor-slots;3874;visiblearmorslots,visiblearmorslotslegacy;可见装备槽;Visible Armor Slots;\nmoon-and-space-dimensions-fabric;3875;agape_space;星际次元;Space Dimensions / Moon and Space Dimensions;\n;3876;totem;复活图腾;Totem of Resurrection;\nwinged;3877;winged;Winged;;\n;3878;slide_show;幻灯片;Slide Show;\nmob-stages;3879;mobstages;生物阶段;Mob Stages;\nrefooled;3880;refooled;Refooled;;\n;3881;bnb;更好的下界：Beta版;BetterNether Beta;BNB\nfabric-seasons;3882;seasons;Fabric Seasons;;\ndehydration;3883;dehydration;脱水;Dehydration;\neasy-villagers;3884;easy_villagers;村民物品化/简单村民;Easy Villagers;\nlargebar;3885;gud_largebar;LargeBar;;\ntis-3d;3886;tis3d;TIS-3D;;\nentity-nan-health-fix;3887;nanfix;假死修复;Entity NaN Health Fix;\nferritecore-fabric;3888;ferritecore;铁氧体磁芯;FerriteCore;FC\nthaumcraft-research-patcher;3889;tcresearchpatcher;Thaumcraft Research Patcher;;\nmasterful-machinery;3890;masterfulmachinery;Masterful Machinery;;MM\nminecraft-earth-mod;3891;minecraft_earth_mod;Derec 的地球生物;Derec's Earth Mobs;\nscotts-essential;3892;scessential;Scott's Essential;;\ndrawerfps;3893;drawerfps;储物抽屉渲染配置;DrawerFPS;\n;3894;touhou_craft;東方幻想鄉;TouhouCraft;\ncursed-earth;3895;cursedearth;Cursed Earth;;\nsll;3896;villagers;逆向交易;sɹǝƃɐllᴉΛ;\nlagmeter;3897;lagmeter;延迟表;LagMeter;\nhexxit-gear-2;3898;hexxitgear;Hexxit 装备 2;Hexxit Gear R;\nscootys-plants-vs-zombies;3899;scootys_pvz;Scooty的植物大战僵尸重生;Scootys Plants Vs. Zombies Regrown;SPVZR\n;3900;;BulletCart;;\n;3901;quilt_loader;Quilt Loader;;\n;3902;;Cursed Legacy Fabric;;\ncustom-machinery;3903;custommachinery;Custom Machinery;;CM\nbonsai-tree-crops;3904;arcadialbonsaitrees;Bonsai Tree Crops;;\nrpgz;3905;rpgz;RpgZ;;\n;3907;xiuxian,xiuxianchengzhen,xiuxianlianqi,xiuxianyaoshou,xiuxianliandan;原初修真1.15.2;xiuxian;\n;3908;stationapi;Station API;;\nscavenge-timing-addon;3909;scavenge_timing;Scavenge Timing Addon;;\ncull-particles;3910;cullparticles;粒子渲染机制优化;Cull Particles;\nmajos-broom;3911;majobroom;魔女的扫帚;Majo's broom;\nseamless-loading-screen;3912;seamless_loading_screen;无缝加载界面;Seamless Loading Screen;\ncurios-quark-oddities-backpack;3913;curiosquarkobp;夸克背包饰品;Curios Quark Oddities Backpack;\nnature-expansion;3914;nature_expansion;Nature Expansion;;\n;3915;library;Library;;\nelectromagnetic-coherence;3917;electromagneticcoherence;Electromagnetic Coherence;;\namaleafs-expantion-world-mod;3918;aewmod;Amaleaf's Expantion World Mod;;\nthe-elemelon-mod;3919;elemelon;元素西瓜;The Elemelon Mod;\ndcsp;3920;diamondscompressorsp;钻石压缩;Diamonds Compressor SP;\ncorpse-complex;3921;corpsecomplex;Corpse Complex;;\nmodularvoicechat;3922;modularvc;模块化语音聊天;ModularVoiceChat;\nelytra-slot;3923;curiouselytra,elytratrinket,elytraslot;鞘翅插槽/鞘翅槽位;Curious Elytra / Elytra Trinket / Elytra Slot;\n;3924;our;时间纪元;Time Era;\ntconstructcompatible;3925;ticc,tconstruct_compatible;匠魂兼容;TConstructCompatible;TiCC\n;3926;Your Records Mod;自定义唱片;Your Records Mod;\nincubation;3927;incubation;Incubation;;\nabyssmana;3929;abyssmana;奈落魔法学;AbyssMana;AM\n;3930;nanzhi_grave;南织鸽子的坟墓模组;;NZGrave\ncreate-goggles;3931;createplus,creategoggles;Create Goggles;;\n;3932;strange_ingot;奇奇怪怪的锭;Strange ingot;\ncoloured-tooltips;3933;colouredtooltips;彩色提示框;Coloured Tooltips;\nmagicraft-magic;3934;magicfoods;魔法材料;Magical Materials;\nglowstone-ores;3935;gom;荧石矿石;Glowstone Ores;\nmore-wires;3936;morewire,morewires;More Wires;;\nalexandrite-and-more;3937;alexandriteandmore;Alexandrite And More;;\nendgame-materials;3938;endgamematerials;终局材料;Endgame Materials;\nmore-cobblestone;3939;cm;更多圆石;More Cobblestone;\nwild-netherwart;3940;wildnetherwart;野生地狱疣;Wild Netherwart;\nthe-periodic-table-mod;3941;periodictablemod;元素周期表;The Periodic Table;\nadamantium-in-minecraft;3942;adamantiumsword;adamantium ores;;\nforgerocks;3943;forgerocks;ForgeRocks;;\nballistix;3944;ballistix;弹道学;Ballistix;\nwireless-industry;3945;wirelesstools;Wireless Industry;;\nspice-of-life-tonio-edition;3946;soltonio;生活调味料：托尼欧版;Spice of Life: Tonio Editon;\nhusk-spawn;3947;huskspawn;尸壳生成微调;Husk Spawn;\nartisan-tools-1-16;3948;artisantools;工匠工具;Artisan Tools;AT\nyttr;3949;yttr;Yttr;;\nwisla;3950;wisla;What is Sauron looking at;;Wisla\nmixin-0-7-0-8-compatibility;3951;mixincompat;兼容Mixin0.7-0.8;Mixin 0.7-0.8 Compatibility;\nfarplanetwo;3952;fp2;远平面2;Far Plane Two;FP2\nxenoclus-one;3953;xenoclus_v;Xenoclus One;;\nsatako-library;3954;satako;Satako Library;;\nunique-enchantments-utils;3955;;独特的附魔：效用扩展;Unique Enchantments - Utils;\nforge-ae2-additional-opportunity;3956;ae2ao;AE2 Additional Opportunity;;AE2AO\niconexporter;3957;iconexporter;IconExporter;;\ncurious-shulker-boxes;3958;curiousshulkerboxes;潜影盒插槽/潜影盒槽位;Shulker Box Slot;\nxenoclus-2;3959;xenoclustwo;Xenoclus 2;;\ndarker-depths;3960;darkerdepths;深岩之下;Darker Depths;\nterracraft-journey;3961;terracraft;TerraCraft Journey;;\nsolar-cooker;3962;solarcooker;太阳灶;Solar Cooker;\ncreate-addon;3964;create_addon;Create Addon;;\nboat-contraptions;3965;boatcontraptions;Boat Contraptions;;\nender-rift;3966;enderrift;末影裂缝;Ender Rift;\n;3967;Mob_time_era;生物时间;Mob Time Era;MTE\nsplashanimation;3968;splashanimation;SplashAnimation;;\ncontinue-button;3969;continuebutton;继续按钮;Continue Button;\nchem-craft;3970;minecraft_expansions;化学工艺;Chem Craft;\ntime-in-a-bottle-standalone;3971;tiab;时间之瓶;Time in a bottle standalone;\n;3972;mvp;更多原版药水;More Vanilla Potions;MVP\ncompressed-grass;3973;gm;压缩草;Compressed Grass;\napplepie;3974;ApplePie;苹果派;ApplePie;\ndoles-mod;3975;firstmod;Dole's Mod;;\nex-nihilo-sequentia-thermal-addon;3976;exnihilothermal;无中生有：传承 - 热力扩展;Ex Nihilo: Sequentia - Thermal Addon;\nex-naturae;3977;exnaturae;无中生有：传承 - 植物魔法扩展;Ex Naturae;\nex-nihilo-sequentia-tinkers-addon;3978;;无中生有：传承 - 匠魂扩展;Ex Nihilo: Sequentia - Tinkers Addon;\niron-jetpacks;3979;ironjetpacks,iron-jetpacks;Iron Jetpacks;;\ncustom-discs;3980;discs;自定义唱片;Custom Discs;\nplaytime-counter-fabric;3981;playtime_count;游戏时长计数器;Playtime Counter;\nentity-banners;3982;entitybanners;实体旗帜;Entity Banners;\ncurious-jetpacks;3983;curiousjetpacks;Curious Jetpacks;;\ncurious-armor-stands;3984;curious_armor_stands;Curious Armor Stands;;CAS\ntrinkets-fabric;3985;trinkets;Trinkets;;\noccultism;3986;occultism;神秘学;Occultism;\nstray-spawn;3987;strayspawn,strayspawn-fabric;流浪者生成微调;Stray Spawn;\nmooshroom-spawn;3988;mooshroomspawn,mooshroomspawn-fabric;哞菇生成微调;Mooshroom Spawn;\ncardinal-components;3989;cardinal-components;Cardinal Components API;;CCA\n;3990;ember_top_and_waila;余烬高亮支持;Ember Top and Waila;ETW\n;3991;nsn;不那么新;Not So New;NSN\nintegrated-proxy;3992;integrated_proxy;集成代理器;Integrated Proxy;\na-cobblestone-crafting-table;3993;cw;圆石工作台;A Cobblestone Crafting Table;\ncolored-iron;3994;ColoredIron;Colored Iron;;\nfinal-kobold;3995;fk;Final Kobold;;\nears;3996;ears;耳朵;Ears;\nwildfires-female-gender-mod-forge;3997;wildfire_gender;Wildfire's Female Gender Mod;;\nconsistencyplus;3998;consistency_plus;Consistency+;;\nmodularwarfare;3999;modularwarfare;模块化战争/模块化武装;ModularWarfare;MWF\nmputils-basic-tools;4000;mpbasic;MPUtils Basic Tools;;\nthe-agartha-mod;4001;agartha,conranel;The Agartha Mod / Squiggles's Agartha Mod;;\nfalling-with-style;4002;fallingwithstyle;花式降落;Falling With Style;\nvalhelsia-core;4003;valhelsia_core;Valhelsia Core;;\n;4004;minecrafttagreborn;Minecraft凝胶测试轨道：重生;MinecraftTag:Reborn;MT:R\nalwayseat;4005;salwayseat;吃不停;AlwaysEat;\n;4006;inputmethodblocker;游戏中中文输入法冲突修复;InputMethodBlocker;\n;4007;theballofhelp;帮助之球;Ball Help;\n;4008;scp_chamber;SCP-Chamber;;\naquarium;4009;aquarium;深海;Aquarium;\nmixin-booter;4010;mixinbooter;MixinBooter;;\n;4011;;一个物品格的生存;One Item Only;OIO\njrds-moneymod;4012;moneymod;jrd's MoneyMod;;\nskeletdimension;4013;SkeletonModID;骷髅维度;SkeletDimension;\nsushigocrafting;4014;sushigocrafting;巧制寿司;Sushi Go Crafting;\nshrines-structures;4015;shrines;Shrines Structures;;\nvalhelsia-tweaks;4016;valhelsia_tweaks;Valhelsia Tweaks;;\nlazierae2;4017;lazierae2;懒人AE2 1.16+;Lazier AE2;\nmagical-crops-core;4018;magicalcrops;Magical Crops: Core/Regrowth;;\nmagical-crops-decorative;4019;magicalcropsdeco;Magical Crops: Decorative;;\nmagical-crops-armoury;4020;magicalcropsarmour;Magical Crops: Armoury;;\n;4021;rflcmd,reflectcommand;反射命令;Reflection Command;\nsimpleeco;4022;simpleeco;简单经济;SimpleEconomy;\nvistas;4023;vistas;Vistas;;\n;4024;authforge;锻造：认证;AuthForge;AFg\nfish-in-planks;4025;fish_in_planks;Fish In Planks;;\nfish-on-the-line;4026;fishontheline,fishontheline-fabric;Fish On The Line;;\nfish-traps;4027;fishtraps;鱼栅;Fish Traps;\ncombustive-fishing;4028;combustivefishing;Combustive Fishing;;\nfabric-survival-island;4029;survivalisland;Survival island;;\nsonicraft;4030;sonicraft;索尼克工艺;SoniCraft;\nislands;4031;islands;Islands;;\norigins-forge;4032;origins;起源非官方Forge版;Origins (Forge);\nmagic-feather;4033;magicfeather;Magic Feather;;\nalet;4034;alet;A Little Extra Tiles;;ALET\nnetheriteplus;4035;netheriteplus;Netherite Plus +;;\nthe-survivalists-mod;4036;survivalist;The Survivalist's Mod;;\ncampfire-overhaul;4037;campfire_overhaul;Campfire Overhaul;;\nshatter;4038;shatter;Shatter;;\nelectric-self-balance-scooters;4039;electric_selfbalancing_scooters;电动平衡车;Electric Self-balance Scooters;ESBS\nrats-mischief;4040;ratsmischief;Rat's Mischief🐀;;\ndream-rng;4041;dreamrng;Dream RNG;;\nwandering-trader-improvements;4042;wt_improvements;Wandering Trader Improvements;;\nwasteland-reborn;4044;wastelands;不毛之地：重生;Wasteland Reborn;\nskds-core;4045;skds-core;SKDS Core;;\nendertech;4046;EnderTech;末影科技;Ender Tech;ET\nenderpig-mod;4047;enderpig;末影猪;Enderpig Mod;\n;4048;treeores;矿石树;TreeOres;\nno-no-ai-spawn;4049;spider2fix;镇压前端召唤;No No-AI Spawn;\natomic-bomb-1-16;4050;nuclear_craft;枪，炮，原子弹！;Guns, Rockets and Atomic Explosions;\nse-sign-shop;4051;sesignshop;简单经济：牌子商店;SE: Sign Shop;\n;4052;technical_engineering;科能工程2;Technical Engineering 2;TEN2\nfancymenu;4053;fancymenu;FancyMenu;;FM\nhalogen;4054;;卤素;Halogen;\nzetamod;4055;zetamod;泽塔;ZetaForged / Zeta's Mod;\n;4056;pixelmon;宝可梦 世代;Pixelmon Generations;PG\n;4057;xianjiange;仙剑阁;;\nwholetreeaxe;4058;WholeTreeAxe,wholetreeaxe;Whole Tree Axe;;\nfabrishot;4059;fabrishot;高清截图Fabric;Fabrishot;\nmaterialisation;4060;materialisation;物质化;Materialisation;\nrecipebuffers;4061;recipebuffers;配方缓冲;RecipeBuffers;\n;4062;mineshotrevived;高清截图：复生;Mineshot Revived;\nportal-gun-fabric;4063;portalgun;传送枪 Fabric;Portal Gun Fabric;\nincendium;4064;incendium;Incendium;;\n;4065;nfc;New Frontier Craft;;NFC\ncooperative-advancements;4066;cooperativeadvancements;合作进度;Cooperative Advancements;\nlightstones;4067;lightstones;Lightstones;;\nopenbackup;4068;openbackup;OpenBackup;;\nfat-experience-orbs;4069;fatxporbs;肥胖经验球;Fat Experience Orbs;\nbetter-pipes;4070;betterpipes;更好的管道;Better Pipes;\ntruetype-font-replacement;4071;BetterFonts;TrueType Font Replacement;;TTFR\nfluidlogged-api;4072;fluidlogged_api;Fluidlogged API;;\nspark;4073;spark;火花;spark;\nmco;4074;saoui;Mine Craft Online;;\nguardians-galore;4075;guardiansgalore;Guardians Galore;;\ncogwheel-tweaker;4076;CogwheelTweaker;Cogwheel Tweaker;;\nultra-amplified-mod;4077;ultra_amplified_dimension;超大群系维度;Ultra Amplified Dimension;\nclassical-art;4078;classicalart;经典艺术;Classical Art;\nemc-baubles;4079;emcbaubles;等价饰品;EMC Baubles;\nlag-removal;4081;lag_removal;Lag Removal;;\ngender;4082;gender;Gender & Age Mod;;\n;4083;mtm;Magical Talismans Mod;;\nticktock;4084;servertick;Server Tick;;\n;4085;synthesis_formula_added;行尸走肉弹夹合成配方添加;;\nfps-reducre-for-fabric;4086;;FPS减速器 (Fabric);FPS Reducer (For Fabric);\nart-artillery;4087;art_artillery,aa2;艺术？火炮！;Art? Artillery!;AA\nminemenufabric;4088;modmenu;我的菜单Fabric版;MineMenuFabric;\n;4089;tabtps-fabric;TabTPS;;\nafkpeace;4090;afkpeace;AFKPeace;;\n;4091;stop_hostname_leak;Stop HostName Leak;;\nsecret-rooms-fabric;4092;secretrooms;Secret Rooms Fabric;;\nsmooth-chunks;4093;smooth-chunks;平滑区块加载;Smooth Chunks;\nsaturationoverflow;4094;zenny3d.saturationoverflow;饱和度溢出;SaturationOverflow;\n;4095;;艾玛的起源;Emma's origins;EO\nphantom-blood;4096;phantomblood;幻影之血;Phantom Blood;PB\n;4097;;洞穴与山崖扩展包;Caves & Cliffs Expansion Pack;\nsearchable-containers;4098;searchablecontainers;可搜索容器;Searchable Containers;\n;4099;original_development;原版扩展;Original Development;\n;4100;conranel;卡莱内尔;Conranel;CRN\nintegrated-additions;4101;integratedadditions;集成扩展;Integrated Additions;\nmatchlock-guns;4102;matchlockguns,matchlockweapons;火绳枪;Matchlock Guns;\nfabric-autoswitch;4103;autoswitch;自动切换工具;AutoSwitch;\ninventory-profiles-next;4104;inventoryprofilesnext;一键背包整理Next;Inventory Profiles Next;IPN\ndrill;4105;drill;钻头;Drill;\nore-tree;4106;ore_tree;矿物树;Ore Tree;\nferdinands-flowers;4107;ferdinandsflowers;Ferdinand's Flowers;;\nclear-water;4108;clearwater;清澈的水;Clear Water;\napplied-energistics-2-wireless-terminals-forge;4109;ae2wtlib;AE2无线终端Forge版;Applied Energistics 2 Wireless Terminals Forge;AE2WT-Forge\nferroustry;4110;ferroustry;Ferroustry;;\nvillage-employment;4111;village_employment;村庄就业;Village Employment;\n;4112;;20W14✨;;\ndeathlog;4113;deathlog;死亡日志;Deathlog;\nautomatic-elytra;4114;;自动鞘翅;Automatic Elytra;\naxolotl-bucket-fix;4115;axolotlitemfix;美西螈桶修复;Axolotl Bucket Fix;\nxks-decoration;4116;xkdeco;XK的装饰;XK's Decoration;\nendgame-materials-addons;4117;endgamematerialsgalaxyaddon;更多终局材料;Endgame Materials Addons;\nravage-and-cabbage;4118;ravageandcabbage;Ravage & Cabbage;;\nstackup;4119;stackup;StackUp!;;\nsimple-anti-x-ray;4120;;Simple Anti X-Ray;;\nbreakme;4121;breakme;BreakMe;;\nmod-info-command;4122;mod-info-cmd;Mod Info Command;;\nhexxit-gear-fabric;4123;hexxitgear;Hexxit Gear (Fabric);;\nbuffed-tools;4124;buffedtools;属性工具;Buffed Tools;\nthaumcraft-nei-plugin;4125;thaumcraftneiplugin;神秘时代NEI插件;Thaumcraft NEI Plugin;\nmoarrecipes;4126;MoarRecipes;更多的配方;MoarRecipes;\nluminium;4127;luminium;Luminium;;\nbetter-nether-map-port;4128;nethermap;Better Nether Map;;\n;4129;mcs;多重压缩物品;MultipleCompressedStuffs;mcs\niguanatweaks-reborn;4130;iguanatweaks,iguanatweaksreborn;生存重构/蜥蜴微调重生;Survival Reimagined / IguanaTweaks Reborn;ISO\nenter-the-void;4131;thevoidsshadow;进入虚空;Enter The Void;\ntensura-mod-that-time-i-got-reincarnated-as-a-slime;4132;ttigraas;关于我转生变成史莱姆这档事;Tensura Mod -That time I got reincarnated as a Slime;\n;4133;deep_dark_layer;深暗层;Deep dark layer;DDL\nspeedometer;4134;speedometer;速度表;Speedometer;\nfalling-leaves-forge;4135;fallingleaves;落叶;Falling Leaves (NeoForge/Forge);\n;4136;mcef;MCEF 非官方版本;MCEF-Unofficial;MCEF\n;4137;god_metal;神之金属;God Metal;GM\ncustom-entity-models-cem;4138;cem;自定义实体模型;Custom Entity Models;CEM\ntweakmyclient;4139;tweakmyclient;TweakMyClient;;TMC\nlovely-snails;4140;lovely_snails;可爱蜗牛;Lovely Snails;\n;4141;oursemod;Ourse MOD;;\nclipboard;4142;clipboard;备忘录;Clipboard;\ncrimson-nbt-tags;4143;;Crimson NBT Tags;;\n;4144;cabo;随意弓数据包;Casual Bow Datapack;\nwater-physics-overhaul;4145;wpo;Water Physics Overhaul;;WPO\nnetherless;4146;Netherless;Netherless;;\nauto-ore-dictionary-converter;4147;autooredictconv;Auto Ore Dictionary Converter;;AODC\nelytraboosters;4148;elytraboosters;Elytra Boosters;;\ncobblegenrandomizer;4149;cobblegenrandomizer;CobbleGenRandomizer;;\nunified-resources;4150;unified_resources;Unified Resources;;\nwooden-buckets;4151;woodenbuckets;木桶;Wooden Buckets;\nno-nether-portals;4152;nonetherportals;没有下界传送门;No Nether Portals;\nbeehivetooltips;4153;beehivetooltips;蜂巢提示框;BeehiveTooltips;\nbuddycards;4154;buddycards;巴迪卡牌;Buddycards;\nextended-caves;4155;extcaves;Extended Caves / Expanded Caves [Forge];;\ninventorio;4157;inventorio;Inventorio;;\nmore-curios-totems-of-undying;4158;morecuriostotemsofundying:;More Curios Totems of Undying;;\nselene;4159;moonlight,selene;Moonlight Lib / Selene;;\ncompatibilitylayerforcustomskinloader;4160;CompatibilityLayerForCustomSkinLoader;万用皮肤补丁兼容层;CompatibilityLayerForCustomSkinLoader;CLFCSL\nno-potion-shift;4161;nopotionshift;No Potion Shift;;\nno-potion-offset;4162;no-potion-offset-forge,no-potion-offset;No Potion Offset;;\ndemonslayer;4163;kimetsunoyaiba;鬼灭之刃;Kimetsu no Yaiba;\nnautilium;4164;Nautilium;鹦鹉螺;Nautilium;\nauto-attack;4165;auto attack;自动攻击;Auto Attack;\ntamagotti;4166;tamagottimod;Tamagotti;;\n;4167;;传送;Teleport;TP\nsignal;4168;signal;讯号;Signal;\nrealistic-horse-genetics;4169;horse_colors;真实马匹遗传;Realistic Horse Genetics;\ntimeless-and-classic-guns;4170;tac;永恒枪械工坊;Timeless and Classics Guns;TaC\ntool-progression;4171;toolprogression;工具进阶;Tool Progression;\n;4173;birdblade;棘羽「毕方」;BirdBlade;B.B\na-paimon;4174;apaimon;派蒙;A Paimon;\n;4175;creative;BH Creative;;\nmusic-maker-mod;4176;xercamusic;Music Maker Mod;;\npenguin-lib;4177;penguinlib;Penguin-Lib;;\nflywheel;4178;flywheel;飞轮;Flywheel;\n;4179;stem_further;梗进一步;Stem further;SF\ncoralreef;4180;coralreef;珊瑚礁;CoralReef;\n;4181;jewelry_bar_core;简易饰品;Simple Accessories;SAC\nsimply-seasons;4182;simplyseasons;简单季节;Simply Seasons;\nbclib;4183;bclib;BCLib;;\nactually-baubles;4184;actuallybaubles;实用拓展饰品;Actually Baubles;\nalexs-mobs-battle-music;4185;alexs_mobs_extra_music;♪ Alex's Mobs Extra Music ♪;;\nlava-waders-bauble;4186;lavawaderbauble;熔岩魔靴饰品;Lava Waders Bauble;\nhardcore-revival;4187;hardcorerevival;硬核复活;Hardcore Revival;\nadditional-guns;4188;additionalguns;更多的枪;Additional Guns;\noh-my-minecraft-client;4189;ommc;Oh My Minecraft Client;;OMMC\nwooden-armors-stone-armor;4190;woodarmor;木制盔甲;Wooden Armors Mod;\n;4191;cdi;自定义伤害免疫;Custom Damage Immunity;\ninsanelib;4192;insanelib;InsaneLib;;\ndaydreamer;4193;daydreamer;Daydreamer;;\nphantomfaces;4194;phantomfaces;幻灵接口;Phantomfaces;\nimproved-villagers;4195;iv;改进的村民;Improved Villagers;IV\nequipment-compare;4196;equipmentcompare;装备比较;Equipment Compare;\n;4197;pca;Plusls Carpet Addition;;PCA\nPickupWidely;4198;pickupwidelysmp;PickupWidely;;\n;4199;dongdongxiangyaodefasheqi;冬冬想要的发射器;dong dong want the launcher;DDWTL\nlilliputian;4200;lilliputian;Lilliputian;;\nsampler;4201;Sampler;Sampler;;\ncustom-splash-screen;4202;customsplashscreen;自定义加载界面;Custom Splash Screen;\nmasa-gadget;4203;masa_gadget_mod;Masa Gadget;;\nmeet-your-fight;4204;meetyourfight;迎战;Meet Your Fight;\nhot-or-not-plus;4205;hotornot;Hot or Not +;;\nmacaws-furniture-tfc;4206;mcwfurnituretfc;Macaw's Furniture for TFC;;\ntfc-florae;4207;tfcflorae;TFC Florae;;\ntfc-elementia;4208;tfcelementia;TFC Elementia;;\ninfinity-water-bucket;4209;infinitywaterbucket;无限水桶;Infinity Water Bucket;\ntfc-caffeine-addon;4210;ca;TFC Caffeine Addon;;\ntfc-cellars-add-on;4211;cellars;TFC: TNG Cellars Add-on;;\nboats-and-beeps;4212;boats-and-beeps;Boats and Beeps;;\nkonkrete;4213;konkrete;Konkrete;;\nxp-from-harvest;4214;xpfromharvest;收获经验;XP From Harvest;\nmodern-beta;4215;modern_beta;Modern Beta;;\ntfcflux;4216;tfcflux_core;TFCFlux;;\ntfc-medicinal;4217;tfcmedicinal;TFC Medicinal;;\nwater-flasks;4218;waterflasks;TFC Water Flasks;;\nstructure-world;4220;structureworld;Structure World;;\nmacaws-lights-and-lamps;4221;mcwlights;Macaw的灯;Macaw's Lights and Lamps;\nterrarian-slimes;4222;terrarianslimes;Terrarian Slimes;;\ncreate-gears;4223;creategears;机械动力：齿轮附加;Create Gears;\nsimple-big-backpack;4224;big_backpack;Simple Big Backpack;;\ncroptopia;4225;croptopia;作物盛景;Croptopia;\nfirmalife;4226;firmalife;FirmaLife;;\nsupermartijn642s-core-lib;4227;supermartijn642corelib;SuperMartijn642's Core Lib;;\ncherished-worlds;4228;cherishedworlds;存档置顶;Cherished Worlds;\nironfist;4229;IronFist;铁拳;Ironfist;\n;4230;mining_dimension_cave_version;挖矿维度 (矿洞版);Mining dimension(cave version);\nsimply-acceleration;4231;simplyacceleration;简单加速;Simply Acceleration;\ncreeper-habitats;4232;creeperhabitats;Creeper Habitats;;\nhusbandry;4233;husbandry;Husbandry;;\nits-the-little-things;4234;itlt;It's The Little Things;;itlt\nfruitful;4235;fruitful;硕果累累;Fruitful;\nenvironmental-energy;4236;enviroenergy;环境科技-发电机;Environmental Energy;EnE\nnakranoths-herdcraft;4237;herdCraft;群兽行为;Nakranoth's Herdcraft;\nlambdabettergrass;4238;lambdabettergrass;Lambd 的更好的草方块;LambdaBetterGrass;LBG\n;4239;clientcommands;clientcommands;;\nbright-ore;4240;bright-ore;矿石高亮;Bright Ore;\nchinese-style-sword;4241;chinese_sword;中式剑;Arsenal Core;\n;4242;fishfish;钓🐟🐠;Fish 🐟🐠;\nheroic-armory;4243;heroicarmory;英雄武器;Heroic Armory;\ncolossal-battery;4244;colossal_battery;巨型电池;ColossalBattery;\nindustrial-meat;4245;industrialmeat;Industrial Meat;;\nbiomancy;4246;biomancy;血肉重铸;Biomancy;\nextra-generators;4247;extragenerators;额外发电机;Extra Generators;\nhorticulture;4248;horticulture;园艺;Horticulture;\nmr-pineapples-food-mod;4249;pinesfoodmod;Mr. Pineapple's Food Mod;;\n;4250;herosw;Herobrine JD;;HJ\n;4251;pixelmoninfoplus;更多宝可梦信息;Pixelmon Info Plus;\nlegacyvault;4252;legacyvault;Legacy Vault;;\nshopaholic;4253;shopaholic;Shopaholic;;\n;4254;mooncake;月饼;Moon Cake;MMC\nok-zoomer;4255;okzoomer,ok_zoomer;Ok Zoomer;;\nuntitled-duck-mod-forge;4256;untitledduckmod;无题鸭;Untitled Duck;\nv-tweaks;4257;vtweaks;V-Tweaks;;\nfairenchanting;4258;fairenchanting;公平附魔;FairEnchanting;\nbedspreads;4259;cosmeticbeds,bedspreads;Bedspreads / Cosmetic Beds;;\npiglib;4260;piglib;Piglib;;\nnether-portal-spread;4261;netherportalspread,netherportalspread-fabric;Nether Portal Spread;;\nx-hp;4262;x-hp;x-hp;;\n;4263;kaimyentity;KAI我的实体 - 修改版;KAIMyEntity - Modify;\nmr-pineapples-toy-guns;4264;toyguns;Mr Pineapple's Toy Guns;;\nmagickcore;4265;magickcore;魔法核心;MagickCore;\nfeywild;4266;feywild;仙灵荒野;Feywild;\nThe-final-sword;4267;finalsword;最终之剑重置版;The final sword;\n;4268;benchworks;做功台;Benchworks;\ngog-skybox;4269;gogskybox;GoG Skybox;;\n;4270;morebarrel;更多桶;More Barrel;\nlemon-lib;4271;lemonlib;Lemon Lib;;\ncustom-entity-models-kaimyentity-reborn;4272;;自定义玩家和实体模型 / KAI我的实体：重生;Custom Player & Entity Models/KAIMyEntity-Reborn;\nminiutilities;4273;miniutilities;迷你实用设备;Mini Utilities;\nstumble-upon-campsites;4274;stumbleuponcamps;邂逅：营地;Stumble Upon: Campsites;\nnomad-books;4275;nomadbooks;游牧之书;Nomad Books;\nyungs-extras;4276;yungsextras;YUNG's Extras;;\nstoneholm-forge;4277;stoneholm;地下村庄;Stoneholm, Underground Villages;\n;4278;tlwc;两面包夹芝士;Two loaves with cheese;TLWC\ninspecio;4279;inspecio;Inspecio;;\nspecial-ai;4280;specialid;特殊AI;Special AI;\ndream-waifu-mod;4281;waifucraft;梦中情人;Dream Waifu mod;\nhelp-wanted;4282;helpwanted;求助;Help Wanted;\noff-hand-combat;4283;offhandcombat;副手战斗;Off Hand Combat;\nreaping-mod-fabric;4284;reapingmod,reaping;收割;Reaping;\ngilding;4285;gilding;镀金;Gilding;\ngrims-transportables;4286;transportables;格里姆的便捷交通工具;Grim's Transportables;\nreroll;4287;reroll;刷新附魔;Reroll;\ntax-free-levels;4288;taxfreelevels;免税附魔;Tax Free Levels;\nportable-dimensions;4289;portabledimensions;便携式传送门;Portable Dimensions;\nstone-age-by-yanny;4290;stone_age;Yanny's Stone Age;;\nterra-world-generator;4291;terra;Terra;;\n;4292;morecows;更多牛;MoreCows;\nomnis;4293;omnis;灾厄村民拓展;Omnis;\nomnis-backpacks;4294;omnisbackpacks,omnis_backpacks;Omnis Backpacks;;\nsuperhotmc;4295;superhotmod;SuperhotMC;;\nbag-of-yurting;4296;bagofyurting;帐篷袋;Bag of Yurting;\nbetter-combat-rebirth;4297;bettercombatmod;更好的战斗：重生;Better Combat Rebirth;\niron-and-gold-from-1-17;4298;iron_and_gold;来自 1.17 的铁和金;iron and gold from 1.17;\nimmersive-cooking;4299;immersivecooking;沉浸烹饪;Immersive Cooking;\nstructured-crafting;4300;structuredcrafting;Structured Crafting;;\nrandom-shulker-colours;4301;randomshulkercolours,randomshulkercolours-fabric;随机潜影贝颜色;Random Shulker Colours;\ncreate-automated;4302;createautomated;Create Automated;;\nrandom-sheep-colours;4303;randomsheepcolours,randomsheepcolours-fabric;随机绵羊颜色;Random Sheep Colours;\nsleep-sooner;4304;sleepsooner,sleepsooner-fabric;早点睡;Sleep Sooner;\nrespawning-shulkers;4305;respawningshulkers,respawningshulkers-fabric;潜影贝重生;Respawning Shulkers;\nrespawn-delay;4306;respawndelay,respawndelay-fabric;延迟重生;Respawn Delay;\nrealistic-bees;4307;realisticbees;真实的蜜蜂;Realistic Bees;\nrecast;4308;recast,recast-fabric;持续钓鱼;Recast;\nsofter-hay-bales;4309;softerhaybales,softerhaybales-fabric;柔软的干草块;Softer Hay Bales;\nportable-jukebox-forge;4310;portablejukebox;便携式唱片机;Portable Jukebox;\nspiders-produce-webs;4311;spidersproducewebs,spidersproducewebs-fabric;蜘蛛吐网;Spiders Produce Webs;\nassassins-creed-blocklegend;4312;acbl;刺客信条：方块传奇2;Assassin's Creed Blocklegend II;ACBL2\ngravestone-mod-graves;4313;gravestone,GraveStone;墓碑坟墓;Gravestone mod Graves;\nthe-earth-mod;4314;earth;地球;The Earth Mod;\nlifts;4315;lifts;电梯;Lifts;\ncorail-recycler;4316;corail_recycler;Corail的回收站;Corail Recycler;\nmove-minecarts;4317;moveminecarts;可移动的矿车;Move Minecarts;\ninventory-totem;4318;inventorytotem,inventorytotem-fabric;更好的图腾;Inventory Totem;\nlava-picker-upper;4319;lavacontainer;熔岩收集器;Lava Picker Upper;\nnethers-exoticism;4320;nethers_exoticism;Nether's Exoticism;;\neverpotion;4321;everpotion;永恒药水 ⚗️;EverPotion ⚗️;\nquick-shulker;4322;quickshulker;快捷潜影盒;Quick Shulker;\nrespawnable-pets;4323;respawnablepets;宠物可重生;Respawnable Pets;\nschmucks;4324;schmucks;小阿呆;Schmucks!;\nautomatic-doors;4325;automaticdoors,automaticdoors-fabric;自动门;Automatic Doors;\nconfigurable-despawn-timer;4326;configurabledespawntimer;Configurable Despawn Timer;;\nchunkmap;4327;chunkmap;区块加载小地图;ChunkMap;\ninventory-tabs;4328;inventorytabs;Inventory Tabs;;\nbottle-your-xp;4329;bottleyourxp,bottleyourxp-fabric;装瓶你的经验;Bottle Your Xp;\ngravestone-mod-extended;4330;gravestone-extended;墓碑坟墓 - 拓展;Gravestone mod - Extended;\nbetter-enchanted-books;4331;bebooks;更好的附魔书;Better Enchanted Books;BEB\neasy-steel-forge;4332;easy_steel;简单的钢;Easy Steel & More;\nfabric-waystones;4333;fwaystones;Fabric 版传送石碑;Wraith Waystones / Fabric Waystones;\n;4334;ire;铁块电梯;Iron Elevators;\n;4335;seedcracker;SeedCracker;;\ndark-enchanting;4336;dark-enchanting;暗黑附魔;Dark Enchanting;\n;4337;techdawn;科技黎明;TechDawn;TD\nbetter-wandering-traders;4338;;更好的流浪商人;Better Wandering Traders;\nsuperflat-world-no-slimes;4339;superflatworldnoslimes,superflatworldnoslimes-fabric;没有史莱姆的超平坦世界;Superflat World No Slimes;\nscorge;4340;scorge;Scorge;;\n;4341;recipedumper;合成表导出;RecipeDumper;\nbeaconoverhaul;4342;beaconoverhaul;信标改革;Beacon Overhaul;\ncontact;4343;contact;往来;Contact;\nblockshifter;4344;blockshifter;Blockshifter;;\n;4345;fjt;食物工程;foodproject;FJT\nvanilla-builders-extension;4346;vbe;Vanilla Builders Extension;;VBE\nbig-beacons;4348;bigbeacons;大信标;Big Beacons;\nwooden-hopper;4349;woodenhopper;木漏斗;Wooden Hopper;\n;4350;pvz;植物大战僵尸2;Plants vs Zombies 2;\nages-api;4351;ages_api;Ages API;;\nfancyhud;4352;fancyhud;Spiffy HUD / FancyHUD;;\nadvanced-compass;4353;advancedcompass;高级指南针;Advanced Compass;\nsalutem;4354;salutem;Salutem;;\nthe-sol;4355;sol;太阳系;The Sol;TS\nbetter-beds;4356;betterbeds;更好的床;Better Beds;\nworld-lives-mod;4357;jordan7102_lm;World Lives Mod;;\nlib;4358;flytre_lib;Flytre Lib;;\n;4359;tic_json_generator;匠魂JSON生成器;TConstruct JSON Generator;\njust-some-chocolate;4360;chocomod;只是一些巧克力;Just some Chocolate;\n;4361;hormone;激素农业;Hormone;\nvacuum-horizon;4362;VacuumHorizon;Vacuum Horizon;;\nunique-enchantments-battle;4363;uniquebattle;独特的附魔：战斗扩展;Unique Enchantments - Battle;\nbalanced-enchanting;4364;balancedenchanting;平衡附魔;Balanced Enchanting;\npipe;4365;pipe;管道;Pipe;\nsimple-ambients;4366;ambients;Simple Ambients;;\n;4367;thewanderingearth;流浪地球;The Wandering Earth;TWE\ncountouryside-and-magic;4368;cam;乡野与魔法;Countryside And Magic;CAM\nmodern-loading-screen;4370;modernloadingscreen;现代加载界面;Modern Loading Screen;MLS\n;4371;zyfdroid-myessentialx;MyEssentialX;;\ndong-dongs-random-creation;4372;dongdongmod;冬冬的随意创作;Dong Dong's random creation;DDRC\n;4373;threebody;三体;ThreeBody;\ntransliterationlib;4374;transliterationlib;TRansliterationLib;;\n;4375;loan;借贷;Loan;\nrpmtw-update-mod;4376;rpmtw_update_mod,rpmtw_platform_mod;RPMTW平台模組;RPMTW Platform Mod;RPMTW\nfarsight-spyglasses;4377;farsight_spyglasses;Farsight: Spyglasses;;\nnot-enough-animations;4378;notenoughanimations;更多动画;Not Enough Animations;\nbetter-beacon;4379;betterbeacon;更好的信标与潮涌核心;Better Beacon / Conduit;\ndungeon-now-loading;4381;dungeonnowloading;地牢进行时;Dungeon Now Loading;DNL\nsimple-emerald-tools-fabric;4382;easy_emerald;Easy Emerald Tools & More;;\ncold-sweat;4383;cold_sweat;冷汗;Cold Sweat;\nwitchery-rewitched;4384;witchery_rewitched;巫术：重生;Witchery: Rewitched;\nmajou-densetsu-3d;4385;backtones;魔城传说3D;Majou Denetsu 3D;MD3D\nicarus-wings;4386;locusazzurro_icaruswings;伊卡洛斯之翼;Icarus Wings;\nkeyboard-wizard;4387;keywizard;按键精灵;Keyboard Wizard;\npiscary;4388;piscary;Piscary;;\nstorage-cabinet;4390;storagecabinet;储藏柜;Storage Cabinet;\nfirst-person-model;4391;firstperson;更真实的第一人称模型;First-person Model;\nrandore;4392;randore;Rand'Ore;;\nrising-uppercut;4393;rising_uppercut;上勾拳;Rising Uppercut;\njackie-chan-s-weapon;4394;jackie_chans_weapon;成龙的武器;Jackie Chan's weapon;\nexplorers-compass;4395;explorerscompass;探险者指南针;Explorer's Compass;\ngravestones;4396;gravestones;墓碑;Gravestones;\ncrafting-dead-survival;4397;craftingdeadsurvival;行尸走肉：生存;Crafting Dead: Survival;\nredstone-control-2;4398;rs_ctr2;红石控制2;Redstone Control 2;\ncsenchants;4399;coldenchants2;Colds: Enchants 2.0;;\nmystical-customization;4400;mysticalcustomization;Mystical Customization;;\n;4401;;泥土信标;Dirt beacon;DB\nlucky-blocks-project-e;4402;lucky;等价交换幸运方块;Equivalent Lucky Block;\nfmprojecte-fmpe;4403;;等价交换重制-EMC上限扩展版;FMProjectE;FMPE\n;4404;;物品栏备份;Flexibag;FB\nibe-editor;4405;ibeeditor;IBE Editor;;IBE\nexotic-critters;4406;experimental_m;Exotic critters;;\narchers-paradox;4407;archers_paradox;弓箭手悖论;Archer's Paradox;\ndynamic-trees-neapolitan;4408;dtneapolitan;动态的树：那不勒斯风味附属;Dynamic Trees - Neapolitan;\n;4409;soulforging;灵魂锻造;Soul Forging;SF\nrandomite-classic;4410;randomite;Randomite Classic;;\n;4411;super_tool_reloaded;超级工具重置版;Super Tools Reloaded;\nwilds;4412;wilds;Wilds;;\nroughly-searchable;4413;roughlysearchable;Roughly Searchable;;\ncull-leaves;4414;cullleaves;树叶渲染优化;Cull Leaves;\ndynamic-trees-atum-2;4415;dtatum;动态的树：阿图姆2附属;Dynamic Trees - Atum 2;\nrandomtweaker;4416;randomtweaker;RandomTweaker;;RT\n;4417;bizarre_fans;奇异风扇;Bizarre Fans;BFAN\niron-chests-fabric;4418;;[Fabric] Iron Chests;;\nsteamworld;4419;steamworld;SteamWorld;;\n;4420;;Cyan;;\nfalling-leaves-fabric;4421;fallingleaves;落叶;Falling Leaves (Fabric);\ncraftable-creative-ability;4422;craftable_creative_ability;可合成的创造能力强化;Craftable Creative Ability;cca\nnot-enough-energistics;4423;neenergistics;Not Enough Energistics;;NEE\nterracore;4424;terracore;TerraCore;;\n;4425;night;夜;night;N\nseeker-compass;4426;seeker_compass;Seeker Compass;;\nghosts-explosives;4427;ghostsexplosives;Ghost's Explosives;;\npower-drop;4428;powerdrop;蓄力投掷;Power Drop;\nyungs-better-dungeons;4429;betterdungeons;YUNG 的地牢优化;YUNG's Better Dungeons;\n;4430;shrgt;社会人梗图;peduncle;SHR\nore-controller;4431;ore_controller;Ore Controller;;\nmorechickens;4432;chickens;更多鸡;MoreChickens;\nghosts-explosives-2;4433;ghostsexplosives2;Ghost's Explosives 2;;\nweapon-throw;4434;weaponthrow;武器投掷;Weapon Throw;\nmore-villagers;4435;morevillagers;更多村民;More Villagers;\ndungeons-mobs;4436;dungeons_mobs;地下城生物;Dungeons Mobs;\n;4437;doraemon_props;哆啦A梦的小道具;Doraemon's Props;\n;4438;magic_circle;Magic circle;;\n;4439;meteorrandomidea;流年年的随想作;MeteorRandomIdea;\n;4440;fortyyears;四十年;Miyazaki Hayao Forty Years;\n;4441;flycycle;Flycycle;;\n;4442;civilizationswars;三体工业 (TeaCon预览版);Civilizations Wars;\n;4443;king_and_knight;明日方舟;King And Knight;\n;4444;ashihara;苇原 (芝之章~百里乡野行纪);Ashihara;ASHR\nuusi-aurinko;4445;uusi-aurinko;新日;Uusi Aurinko;\n;4446;serializableconsciousness;Serializable Consciousness;;SeriCons\n;4448;elytra_booster;推进鞘翅;Elytra Booster;\n;4449;minecardstouhou;Mine[Touhou]Card;;\n;4450;favourite_tags;食物标签;Favourite Tags;\n;4451;talisman;符·道;Talisman;\n;4452;decorations;Rotarism Decorations;;\n;4453;teamod;荒野乱斗;Tea Mod;\n;4454;the_elixir;不死灵药;The Elixir;\n;4455;miyaworld;Miya World;;\nchronicler-the-key-of-stellaris;4456;chronicler_lh;历史记录者·星之匙;Chronicler: The Key of Stellaris;\n;4457;goldtoucher;摸金;Gold Toucher;\nneubulaeko-slime;4458;neubulaeko_slime;星云子史莱姆;Neubulaeko Slime;\n;4459;darksword22;Darksword22;;\n;4460;tea_sorcerer;茶叶魔法使;Tea Sorcerer;\n;4461;m1917gun;梗系列Mod小合集;MemesP;\n;4462;threebodysophon;三体·智子;Threebody Sophon;\nfantasysoup;4463;fantasy_soup;FantasySoup;;\njust-enough-professions-jep;4464;justenoughprofessions;JEI工作方块;Just Enough Professions;JEP\n;4465;norecipebook;没有配方书非官方版;No Recipe Book;\npaimonfood;4466;paimonfood;应急食品;PaimonFood;\nplumed-belt;4467;plumedbelt;Plumed Belt;;\nbetter-with-minecolonies;4468;betterwithminecolonies;Better With Minecolonies;;\ncalemis-utilities;4469;calemiutils;Calemi的实用设备;Calemi's Utilities;\nbotanical-cowardice;4470;botanicalcowardice;Botanical Cowardice;;\ntravelers-titles;4471;travelerstitles;旅人标题;Traveler's Titles;\nexpand-chinese-sword;4472;expand_chinese_sword;中式剑拓展;Arsenal Integration;ECS\n;4473;kongcheng;空城魔法;;\nlittleframes;4474;littleframes;小画;LittlePictureFrames;\nlavalogged-blocks;4475;lavalogging;岩浆充填;Lavalogged blocks;\nawoken-world;4476;awokenworld;Awoken World;;\ngt4-reimagined;4477;gt4r;格雷科技4：重构想;GregTech 4 Reimagined;GT4R\ncamouflaged-creepers;4478;camoucreepers;Camouflaged Creepers;;\nore-tweaker;4479;oretweaker;Ore Tweaker;;\nopenpython;4480;OpenPython;OpenPython;;\njei-lotr;4481;jeilotr;JEI LOTR;;\ncapes;4482;capes;Capes;;\nattack-speed-enchantment;4483;attackspeedenchantment;攻击速度附魔;Attack Speed Enchantment;\nnatures-minerals;4484;natureminerals;Nature's Minerals;;\nbalm;4485;balm-fabric,balm;Balm;;\nresource-hogs;4486;;资源猪;Resourse Hogs;\nadvanced-xray;4487;advanced-xray-fabric,xray;高级透视;Advanced XRay;\n;4488;Bontany Ore Trees;植物盆栽矿物树拓展;Botany Ore Trees;BOT\nring-of-attraction-forge;4489;ring_of_attraction;吸引戒指;Ring of Attraction;\ncustomhud;4490;custom_hud;自定义HUD;CustomHud;\ndeadly-world;4491;deadlyworld;致命世界;Deadly World;\nfastdecay;4492;fastdecay;FastDecay;;\nessential-commands;4493;essential_commands;Essential Commands;;\ncaves-and-cliffs-backport;4494;cavesandcliffs;Caves & Cliffs Backport;;\nthat-future-mod-glow-squid-axolotl-moobloom-etc;4495;minecraft__concept_vicale;That future mod;;\ndiamond-buildcraft;4496;diamond;钻石装饰;Diamond Buildcraft;\napocalypse-mod;4497;apocalypse;启示录重制;Apocalypse Rebooted;\nmcwifipnp;4498;mcwifipnp;更高级联机设置;LAN World Plug-n-Play;mcwifipnp\nrequious-frakto;4499;requious;Requious Frakto;;ReF\n;4500;workmanship01;工匠技艺;Workmanship Mod;\n;4502;;切石机增强;Cutter Plus;\ncapsule;4503;capsule;Capsule;;\nisometric-renders;4504;isometric-renders;等轴渲染;Isometric Renders;IR\n;4505;;Abstract's Recraft;;\nenchanting-secretaire;4506;enchantnote;附魔记录;Enchanting Secretaire;\nsync-fabric;4507;sync;克隆 (Fabric);Sync (Fabric);\ntale-of-kingdoms-a-new-conquest;4508;taleofkingdoms;王国：新征程;Tale of Kingdoms: A new Conquest;\n;4509;networktitle;网络窗口标题;NetworkTitle;\nschwarz;4510;schwarz;黑;Schwarz;\nmegane-forge;4511;megane;Megane;;\ncrusade;4512;crusade;Crusade;;\nvending-machine;4513;vm;售货机;Vending Machine;\nmodernxl;4514;minecraftmodernxl;Modernxl;;\ntaiga2;4515;taiga;匠魂合金附加2;Tinkers Alloying Addon Port;TAIGA2\nchunkgenlimited;4516;chunkgenlimit;区块生成限制;ChunkGenLimiter;\nmore-crafting-tables-for-forge;4517;mctb;更多工作台;More Crafting Tables for Forge!;\nrepurposed-structures;4518;repurposed_structures;结构变体;Repurposed Structures;RS\nezpas;4519;ezpas;EZ Pipes and Stuff;;EZPaS\nelectrona;4520;electrona;Electrona;;\n;4521;mcg;MCG;;\nreasonable-sorting;4522;reasonable-sorting;合理排序;Reasonable Sorting;\nchat-heads;4523;chat_heads;聊天头像;Chat Heads;\nhidden-armor;4524;hiddenarmor;隐藏盔甲;Hidden Armor;\nmajruszs-progressive-difficulty;4525;majruszs_difficulty;Majrusz的进阶难度;Majrusz's Progressive Difficulty;\npharmacist;4526;pharmacist;药师;Pharmacist;\nkappa;4527;kappa;Kappa;;\nequivalent-integrations;4528;equivalentintegrations;等价集成;Equivalent Integrations;\nkeep-equipment;4529;keepequipment;保留装备;Keep Equipment;\nherobrines-origin;4530;herobrine;Herobrine 的起源;Herobrine's Origin;\nthe-abyss;4531;theabyss;深渊：远古;The Abyss: Legacy;\nsekiro-sense-symbols;4532;sekiro_kanji;Sekiro Sense Symbols;;\nlurker;4533;lurkermod;Cold的潜伏者;Colds: Lurker;\ncavebiomeapi;4534;cavebiomeapi;CaveBiomeAPI;;\nextended-block-shapes;4535;extshape;扩展方块形状;Extended Block Shapes;\npassive-mobs;4536;passivemobs;Passive Mobs;;PM\nsponsor;4537;sponsor;赞助者;Sponsor;\n;4538;minecraftplusplus;我的世界++;MinecraftPlusPlus;MPP\n;4539;silver;银世界模组;Silver World Mod;SWM\nbetween-ores;4540;betweenores;Between Ores;;\nblack-hole;4541;blackhole;黑洞;Black Hole;\nmore-and-more-minerals;4542;more minerals,more_mineral;越来越多的矿物;more and more minerals;\npermafrost;4543;permafrost;Permafrost;;\n;4544;ice_and_fire_and_wizardry;霜炙魔法;Ice,fire and wizardry;IFW\nscootys-plants-vs-zombies-2;4545;scootys_pvz2;Scooty的植物大战僵尸2;Scooty's Plant Vs. Zombies 2;SPVZ2\nbetter-furnaces-reforged;4546;betterfurnacesreforged;更好的熔炉重铸版;BetterFurnaces Reforged;\n;4547;mahou_tattoo;画皮;MahouTattoo;\n;4548;beyond_the_mineral;超越矿物;beyond the mineral;BTM\nschools-of-magic-second-semester;4549;schoolsofmagic;魔法学校：第二学期;Schools of Magic: Second Semester;\ndark-loading-screen;4550;dark-loading-screen;深色加载界面;Dark Loading Screen;\nhals-exploration-mod;4551;hem;Hals Exploration Mod;;HEM\nfarmers-delight-cookbook-addon;4552;fd_cookbook;农夫乐事：烹饪书拓展;Farmer's Delight Cookbook Addon;\nheads-down-display;4553;headsdowndisplay,head-down-display;heads down display;;\nmrcrayfish-more-furniture-mod;4554;morecfm;MrCrayfish 的更多家具;MrCrayfish's More Furniture Mod;\nmulti-world;4555;multi_world;Multi world;;MW\n;4556;more bowls;更多碗;more bowls;\nterralith;4557;terralith;Terralith;;\noverworld-two-forge;4558;overworld_two;Overworld Two;;\nxl-packets;4559;XLPackets;XL数据包;XL Packets;\nrftools-storage;4560;rftoolsstorage;RF工具：存储;RFTools Storage;\nrftools-builder;4561;rftoolsbuilder;RF工具：建造机;RFTools Builder;\n;4562;armor_hero;铠甲勇士;Armor Hero;\nnethers-delight;4563;nethers_delight,nethersdelight;下界乐事;Nether's Delight;\nfarmers-extra-foods;4564;farmers_extra_foods;农夫的拓展食物;Farmer's Extra Foods;\ntinkers-delight;4565;thinkers_delight,tdelight;工匠乐事;Tinker's Delight;\n;4566;sic;锻铁工艺;Strike Iron Craft;SIC\n;4567;FlashFreeze;FlashFreeze;;\nmajruszs-accessories;4568;majruszs_accessories,majruszsaccessories;Majrusz的配件;Majrusz's Accessories;\nwater-2-mana;4569;water2mana;Water 2 Mana;;\n;4570;;工程师板鱼;Engineer Slabfish;\n;4571;item_export;Item Export;;IEx\ncraft-and-hunt;4572;craftandhunt;狩猎工艺;Craft and Hunt;\ntmats;4573;tmats;TMats;;\nthe-afterlight;4574;the_afterlight;隐光之域;The Afterlight;\ntinkers-planner;4575;tconplanner;匠魂蓝图;Tinker's Planner;\nplaying-cards;4576;playingcards;扑克牌;Playing Cards;\nrainbow-oak-trees-2;4577;rainbowoaks;彩虹橡树2;Rainbow Oak Trees 2;\nmapfrontiers;4578;mapfrontiers;地图边界;MapFrontiers;\ncustomizable-elytra;4579;customizableelytra;可自定义鞘翅;Customizable Elytra;\npuzzles-lib;4580;puzzleslib;Puzzles Lib;;PL\nunique-enchantments-base;4581;uniquebase;独特的附魔：基础;Unique Enchantments Base;\nore-stages;4582;orestages;矿石阶段;Ore Stages;\nkrays-magic-candles;4583;krayscandles;Kray's Magic Candles;;\nthaumcraft-localization-optimizer;4584;ThaumcraftLocalizationOptimizer;神秘时代本地化优化;Thaumcraft Localization Optimizer;TLO\ndrink-beer-fabric;4585;drinkbeer;喝啤酒啦;Drink Beer;\nemerald-tools-armour;4586;emerald_armor_and_tools;Emerald Tools/Armour;;\nhidden-recipe-book;4587;hiddenrecipebook;隐藏配方书;Hidden Recipe Book;\nkibe;4588;kibe;羊肉面饼实用设备;Kibe Utilities;\npoisoncraft;4589;poisoncraft;毒药工艺;PoisonCraft;\ndetail-armor-bar-forge;4590;detailab;细节盔甲;Detail Armor Bar;\nwings-horns-hooves-the-ultimate-unicorn-mod;4591;ultimate_unicorn_mod;Wings Horns & Hooves, the Ultimate Unicorn Mod;;\nproject-rankine;4592;rankine;兰金计划;Project Rankine;\ntinkers-mechworks;4593;tmechworks,TMechworks;Tinkers' Mechworks;;\nqdc-core-4-0;4594;qdc_mod;量子拆解工艺;Quantum Dis-Assembly Craft;QDC\nwilliam-wythers-overhauled-overworld;4595;william-wythers-overhauled-overworld,wwoo;William Wythers' Overhauled Overworld/William Wythers' Expanded Ecosphere;;WWOO/WWEE\ninfinite-music;4596;infinitemusic;Infinite Music;;\ncobbler;4597;cobbler;可靠的潜影贝工厂;Shulker's Faithful Factories/Cobbler;SFF\nnether-fast-transport-layer;4598;nether_ice;Nether Fast Transport Layer;;\nthe-outer-end;4599;outer_end;The Outer End;;\nhamncheese;4600;hamncheese,cheesemod;Ham N' Cheese;;\nbuzzy-drones;4601;buzzydrones;Buzzy Drones;;\nmorevanillaarmor;4602;morevanillaarmor;更多原版盔甲;MoreVanillaArmor;\n;4603;drinksaddons;饮品追加;Drinks Addons;\neternal-tales;4604;eternal_tales;永恒传说;Eternal Tales;\noh-the-biomes-youll-go-fixes;4605;;你将去的生物群系修复版;Oh The Biomes You'll Go Fixes;\nwild-world;4606;wildworld;Wild World;;\nmiskatonic-mysteries;4608;miskatonicmysteries;Miskatonic Mysteries;;MM\nharvesters-night;4609;harvestersnight;收割者之夜;Harvester's Night;\nslotlock;4610;slotlock;锁定槽;SlotLock;\n;4611;neoultracraft;新·奥特工艺;Neo UltraCraft;NUC\npanorama-tweaker;4612;panorama_tweaker;Panorama Tweaker;;\nsimple-tomb;4613;simpletomb;简单坟墓;Simple Tomb;\nmodern-life;4614;modernlife;摩登生活;Modern Life;\npaxi-fabric;4615;paxi;Paxi;;\n;4616;er;原神-起源;Genshin_Elemental_Reaction;GER\nwaveycapes;4617;waveycapes;飘扬披风;Wavey Capes;\nskin-layers-3d;4618;skinlayers3d,skinlayers;3D 皮肤层;Skin Layers 3D;\nall-the-discs;4619;allthediscs;唱片大全;All the Discs;\ncryptic-cosmos;4620;crypticcosmos;Cryptic Cosmos;;\nbayou-blues;4621;bayou_blues;长沼蓝调;Bayou Blues;\nhex-lands;4622;hexlands;六边形地貌;Hex Lands;\nfixrtm;4623;fix-rtm;fixRTM;;\nserene-tweaks;4624;serenetweaks;Serene Tweaks;;\nScalingGuis;4625;ScalingGUIs;界面缩放;ScalingGUIs;\nide-better-command-block-forge-editor;4626;bettercommandblock;更好的命令方块;Better Command Block IDE;\nbreakfree;4627;breakfree;自由破坏;Break Free;\ntinkers-mechworks-fork;4628;;Tinkers Mechworks Fork;;\nextended-nether-backport;4629;extendednether;Extended Nether Backport;;\nborderless;4630;borderlesswindow;无边框全屏;Borderless Window;\nlifted-restrictions-structure-block;4631;better_structure;更好的结构方块;Better Structure Block;\nentity-model-json;4632;entitymodeljson;实体模型 JSON;Entity Model JSON;\ndeath-coords;4633;deathcoords;死亡坐标显示;Death Coords;\n;4634;ultimatec;终极合金2;Ultimatec2;ult\nnever-enough-currency;4635;currency;货币多多;Never Enough Currency;NEC\nnever-enough-currency-lite;4636;currency;货币多多：精简版;Never Enough Currency Lite;NECL\ncreate-chunkloading;4637;createchunkloading;Create Chunkloading;;\nname-pain;4638;namepain;Name Pain;;\nwireless-chargers;4639;wirelesschargers;无线充电器;Wireless Chargers;\nbouncier-beds;4640;bouncierbeds;弹力床;Bouncier Beds;\nhexlands-ii;4641;hexlands;六边形地貌 2;HexLands II;\ngold-in-river-mod;4642;gold_in_river_mod;河中淘金;Gold In River Mod;\n;4643;imagesign;图片告示牌;Image Sign;\nlorespawn;4644;lorespawn;怪物巢穴;Lorespawn;\ncreatuures;4645;leescreatures;Lee's Creatures;;\nfabrics-biome-api-reforged;4646;fabric_biome_api;Fabric's Biome API Reforged;;\nstalwart-dungeons;4647;stalwart_dungeons;坚固地牢;Stalwart Dungeons;\nmaidens-marvelous-materials;4648;maidensmaterials;Maiden's Marvelous Materials;;\nnekoration;4649;nekoration;猫咪装饰;Nekoration;\ntomb-many-graves-2;4650;tombmanygraves;Tomb Many Graves 2;;\nfins-and-tails;4651;finsandtails;鳍和尾巴;Fins and Tails;F&T\nsmarter-farmers-farmers-replant;4652;farmersreplant,smarterfarmers;更聪明的农夫;Smarter Farmers / Farmers Replant;\npeculiars;4653;peculiars;独特风味;Peculiars;\ndraggable-resource-packs;4654;draggable-resource-packs;可拖动列表/可拖动资源包;Draggable Lists/Draggable Resource Packs;DRP\nfastchest;4655;fastchest;快速箱子渲染;FastChest;\nyuushya-townscape;4656;yuushya;方块小镇;Yuushya Townscape;\narchitects-palette;4657;architects_palette;建筑师的调色板;Architect's Palette;\nwhisperwoods;4658;whisperwoods;低语之森;Whisperwoods;\ndimensional-dungeons;4659;dimdungeons;维度地牢;Dimensional Dungeons;\nbosses-of-mass-destruction;4660;bosses_of_mass_destruction;祸乱鬼魅;Bosses of Mass Destruction;BOMD\nneoelfeos-medieval-pub-decoration;4661;nefdecomod;Nef's Medieval decoration;;\ndustrial-decor;4662;dustrial_decor;'Dustrial Decor;;\nfeders-scarecrows;4663;feders_scarecrows;Feder的稻草人;Feder's Scarecrows;\nzetter;4664;zetter;Zetter;;\nmystical-pumpkins;4665;mystical_pumpkins;神秘南瓜;Mystical Pumpkins;\nskinned-lanterns;4666;skinnedlanterns;灯笼皮肤;Skinned Lanterns;\nscarecrows;4667;;稻草人;Scarecrows;\nseasonals;4668;seasonals;季节风味;Seasonals;\nmimi-mod;4669;mimi;Musical Instrument Minecraft Interface;;MIMI\nblocktuner;4670;blocktuner;音符盒调音助手;BlockTuner;\ncaged-mobs;4671;cagedmobs;笼中生物;Caged Mobs;\nadvanced-rifles;4672;advancedrifles;Advanced Rifles;;\ntome-of-blood;4673;tomeofblood;血之宝典;Tome of Blood;\nblockfront-world-war-ii;4674;bf;方块前线;BlockFront;BF\narchaic-guns;4675;archaicguns;Archaic Guns;;\nblockcarpentry;4676;blockcarpentry;BlockCarpentry;;\nsurvival-overhaul;4677;survivaloverhaul;Survival Overhaul;;\nraining-cats-and-dogs;4678;;猫狗雨;Raining Cats and Dogs;\nmelt;4679;zephmelt;Melt;;\n;4680;gamebrowser;游戏内浏览器;In-game Browser;IGB\nstuff-a-sock-in-it;4681;sasit;日志过滤器;Stuff A Sock In It;SASIT\nsupermartijn642s-config-lib;4682;supermartijn642configlib;SuperMartijn642's Config Lib;;\nsmall-ships;4683;smallships;Small Ships;;\nsound-engine-reloader;4684;ssoundreloader;重新加载声音;Sound Engine Reloader;\ncrystal-caves;4685;crystalcaves;Crystal Caves;;\nincreasemobs;4686;increasemobs;IncreaseMobs;;\nscarecrows-territory;4687;scarecrowsterritory;稻草人领域;Scarecrows' Territory;\n;4688;borderremover;Border Remover;;\ndungeons-mod;4689;dungeonsmod;Dungeons Mod;;\nloot-beams;4690;lootbeams;战利品光束;Loot Beams: Relooted!;\ntheurgy;4691;theurgy;Theurgy;;\nbetter-badlands;4692;better_badlands;恶地改善;Better Badlands;\n;4693;;按键显示1.16+;Keystrokes;\nthe-mysthical-reworked;4694;mysthical_reworked;The Mysthical;;\nvillagers-and-monsters-mod-legency;4695;villagers_and_mosnetrs_legacy;Villagers And Monsters Mod Legacy;;\nrepair-chests;4696;repairchests;修损宝盒;Repair Chests;\n;4697;photontech;光子科技;Photon-Tech;Pt\nrelics-of-gaming;4698;rog;游戏遗物;Relics Of Gaming;\nmodular-controller;4699;modularcontroller;模块化控制器;Modular Controller;\n;4700;artificialcreature;Artificial Creature;;\nItem-Collectors;4701;itemcollectors;Item Collectors;;\ngamemodeoverhaul;4702;gamemodeoverhaul;旧版指令;GamemodeOverhaul;\ndash;4704;dash;Dash;;\nminecraft-dungeons-content;4705;dungeons;地下城内容;Dungeons Content;\nold-beacon;4706;oldbeacon;旧信标;Old Beacon;\noc2;4707;oc2;开放式电脑 II;OpenComputers II;OC2\nbotany-pots-tiers;4708;botanypotstiers;更好的植物盆栽;Botany Pots Tiers;\ncompatchedstorage;4709;compatched;紧凑存储修复;ComPatchedStorage;\niseedragons;4710;iseedragons;ISeeDragons;;\npsionic-peripherals;4711;psipherals;Psionic Peripherals;;\nmalum;4712;malum;灵灾;Malum;\nallomancy;4713;allomancy;镕金术;Allomancy;\nsteamcraft2;4714;steamcraft2;SteamCraft 2;;\nboilerplate;4715;boilerplate;Boilerplate;;\nmeldexuns-crystalic-void;4716;crystalic_void;Meldexun's Crystalic Void;;\nrecipes-and-more;4717;recipes_and_more;Recipes and More;;\nllama-steeds-forge;4718;llamasteeds;骑羊驼;Llama Steeds;\nlevellib;4719;levellib1562,levellib12484;LevelLib;;\nenigmatic-graves;4720;enigmaticgraves;Enigmatic Graves;;\nboat-item-view-forge;4721;boatiview;边划边看;Boat Item View;\nforge-creeper-heal-unofficial;4722;creeperheal;Forge 苦力怕坑修复[非官方版];Forge Creeper Heal [Unofficial];\ndesolation-forge;4723;desolation;荒芜;Desolation;\nma-astral;4724;astral;Miner Arcana - Astral;;\nzaraklib;4725;zaraklib;ZarakLib;;\nchipped;4726;chipped;Chipped;;\nmending-for-1-7-10;4728;;给老版本的经验修补;Mending for Older Version;\nunreachable-fantasy-mod;4729;unreachablefantasymod;Unreachable Fantasy;;\nai-reducer;4730;aireducer;AI减速器;AI Reducer;\nsimple-teleporters;4731;lteleporters,simpleteleporters;简易传送器;Simple Teleporters;\nkillmods;4732;killmods;Eternal / KillMods;;\nbig-brain;4733;bigbrain;Big Brain;;\nadaptive-performance-tweaks;4734;adaptive_performance_tweaks;适应性性能调整;Adaptive Performance Tweaks;APTweaks\nrltweaker;4735;rltweaker;RLTweaker;;\nmultibags;4736;multibags;多重背包;MultiBags;\nlavalib;4737;lavalib2806;LavaLib;;\nftb-banners-forge;4738;ftbbanners;FTB Banners;;\noverpowered-armor-bar;4739;overpoweredarmorbar;Overpowered Armor Bar;;\ntinkers-rapier;4740;tinker_rapier;匠魂西洋剑;Tinkers' Rapier;\nearth-reworked;4741;earthworks;土方工程重制版;Earth Reworked;\ncinderscapes-reforged;4742;cinderscapes;余烬奇景Forge版;Cinderscapes Reforged;\nbetter-diving;4743;better_diving;水世界/更好的潜水;Better Diving;\nauto-sprint;4744;autorun;自动冲刺：一键切换;Auto Sprint : key toggle;\nnebula-rpg;4745;nebularpg;Nebula RPG;;\nbetternether-reforged;4746;betternether;更好的下界Forge版;BetterNether Reforged;\nitem-stitching-fix;4747;itemstitchingfix;物品模型修复移植版;Item Stitching Fix;\nblood-and-madness;4748;bloodandmadness;Blood And Madness;;\nafraid-of-the-dark;4749;afraidofthedark;Afraid of the Dark;;\nores-above-diamonds;4750;oresabovediamods;超越钻石;Ores Above Diamonds;\ncrafting-automat;4751;craftingautomat;Crafting Automat;;\ningotter;4752;ingotter;ingotter;;\npresence-footsteps;4753;presencefootsteps;脚步声;Presence Footsteps;\nwalk-the-air;4754;walk_the_air;凌空微步;Walk the air;\ncastle-dungeons;4755;castle_dungeons;Castle Dungeons;;\nmobs-properties-randomness;4756;mpr;Mobs Properties Randomness;;\niguana-tweaks;4757;IguanaTweaks;蜥蜴微调;Iguana Tweaks;\nenhanced-ai;4758;enhancedai;Enhanced AI;;\nvulcanite;4759;vulcanite;软碲铜;vulcanite;\nziheasymodding;4760;ziheasymodding;ZIHEasyModding;;\npath-to-dirt;4761;pathtodirt;Path To Dirt;;\ndirt2path;4762;dirt2path;Dirt2Path;;\ncarbonado;4763;carbonado;Carbonado;;\nstrange;4764;strange;Strange;;\ntfctweaker;4765;tfctweaker;TFCTweaker;;\nscaling-feast;4766;scalingfeast;Scaling Feast;;\ndelete-item;4767;deleteitem;Delete Item;;\nstexcraft-1-12-2-mod;4768;stexcraft;StexCraft☼;;\nlongerdays;4769;longerdays;LongerDays;;\nmob-heads;4770;mobheads;Mob Heads;;\ncompass-coords;4771;compasscoords;Compass Coords;;\nneverdark;4772;neverdark;Neverdark;;\nstats-keeper;4773;stats_keeper;Stats Keeper;;\nvanillatweaks;4774;vt;Vanilla Tweaks;;VT\nbacodifficulty;4775;BacoDifficulty,bacodifficulty;BacoDifficulty;;BD\nimmersiveposts;4776;immersiveposts;沉浸长杆;Immersive Posts;\nfyres-youwilldiemod;4777;fyresmodjam;必死无疑;The \"You Will Die\" Mod;\nold-walking-animation;4778;oldwalkinganimation;旧版行走动画;Old Walking Animation;\nenders-magic;4779;endmagic;末影魔法;Ender's magic;\nanvil-patch-lawful;4780;anvilpatch;Anvil Patch - lawful;;\nelegant-networking;4781;elegant_networking;ElegantNetworking;;\ngemsplusplus;4782;gemsplusplus;宝石＋+;GemsPlusPlus;G++\ndefaultsettings;4783;defaultsettings;默认设置;DefaultSettings;\ndraylars-battle-towers;4784;battletowers;Draylar的战斗高塔;Draylar's Battle Towers;\n;4785;MCMMO - Forge;Forge版McMMO;MCMMO-Forge;\nleos-craftable-chainmail;4786;craftablechainmail;可制作的锁链甲;Leo's Craftable Chainmail;\ntime-to-die;4787;timetodie;Time To Die;;\nbetter-survival-mod;4788;mujmajnkraftsbettersurvival;更好的生存;Better Survival mod;\n;4789;baguette,fb;法棍;Baguette;\ntschipplib;4790;tschipplib;TschippLib;;\nthe-deep-dark-ocean-dimension;4791;deepdarkoceanmod;The Deep Dark Ocean Dimension;;\nonline-picture-frame;4792;opf;在线相框显示2;Modern Online Picture Frame;OPF\npyromancer;4793;pyromancer;炽焰术师;Pyromancer;\nmekanism-fission-recipe;4794;fissionrecipe;Mekanism Fission Recipe;;\nmacaws-fences-and-walls;4795;mcwfences;Macaw的栅栏与墙;Macaw's Fences and Walls;\n;4796;altoclef;Alto Clef;;\nlunae-silva;4797;lunae_silva;Lunae Silva;;\n;4798;Hasok,hasok;格特斯幻想;Getesi Fantasy;GtsF\nrainbow6;4799;rainbow6;我的六号;Rainbow6:Minesiege;R6MS\nfriendlyendermite;4800;friendermite,friendlyendermite;友好的末影螨;FriendlyEndermite;\nfood-expansion;4801;foodexpansion,fe;食物扩展;Food Expansion;FE\nforgotten-items;4802;forgottenitems;被遗忘的物品;Forgotten Items;\nfood-expansion-reimagined;4803;foodexpansion;食物扩展：重构想;Food Expansion: Reimagined;FER\nredstone-igniter;4804;redstoneigniter;Redstone Igniter;;\nenchanting-tweaks;4805;enchanting_tweaks;Enchanting Tweaks;;\nthrown-slime;4806;thrownslime;Thrown Slime;;\n;4807;littlemaidrebirth;女仆重制版;LittleMaid ReBirth;LMRB\ninventory-auto-fill;4808;invautofill;Inventory Auto Fill;;\nqueen-bee;4809;queen_bee;蜂后;Queen Bee;\nbetter-barrels;4810;pinesbarrels;Better Barrels;;\nenderbeetles;4811;ender_beetles;Enderbeetles;;\ncustomport;4812;customport;CustomPort;;\nmore-sound-config;4813;sounddeviceoptions;Sound Device Options;;\nundead-expansion;4814;undead_expansion;Undead Expansion;;\nhourglass;4815;hourglass;沙漏;Hourglass;\nrelics-mod;4816;relics;遗物;Relics;\ncomfortable-nether;4817;comfortable_nether;舒适下界;Comfortable Nether;\narcanecraft-ii;4818;arcanecraft;奥法工艺2;ArcaneCraft II;\npresence-footsteps-forge;4819;presencefootsteps;脚步声Forge版;Presence Footsteps (Forge);\naardvarks-weird-wonderful-wild;4820;weird_wonderful_wild;Aardvark's Weird Wonderful Wild;;\n;4821;lmmx;LittleMaidMob Enhanced;;\n;4822;lmreengaged;LittleMaidReengaged Firis's Patch;;\nlittle-delicacy-cafes;4823;littledelicacies;Little Delicacy Cafes;;LDC\nee3-helper;4824;EE3HELP;EE3 Helper;EE3H;\nsave-your-pets;4825;syp;拯救你的宠物;Save Your Pets;\nmain-menu-scale;4826;mainmenuscale;Main Menu Scale;;\npotion-fingers;4827;potionfingers;Potion Fingers;;\nreload-audio-driver-rad;4828;rad;音频驱动重载;Reload Audio Driver;RAD\nshieldbreak;4829;shieldbreak;ShieldBreak;;\nholiday-helper;4830;holidayhelper;Holiday Helper;;\nfabric-tickratechanger-mod;4831;;Fabric版运算变速;Fabric TickRateChanger;\nobservable;4832;observable;可视性能侦测;Observable;\ndisenchantment-edit-table;4833;editenchanting;祛魔编辑台;Disenchantment Edit Table;\n;4834;lmlibrary;LMLibrary;;\ncit-resewn;4835;citresewn;CIT Resewn;;\nlazy-language-loader;4836;lazy-language-loader;Lazy language loader;;\nclef;4837;clef;Clef;;\nzettai-magic-2;4838;zettaimagic;Zettai Magic 2;;\n;4839;nucleartech;Nuclear Tech Mod Remake;;NTMR\nwotw-mod;4840;wotwmod;世界之战;The War of the Worlds Mod;WOTW\n;4842;starcraft;星辰工艺;StarCraft;SC\n;4843;kgqgj;坎特伯雷公主与骑士唤醒冠军之剑的奇妙冒险的武器;katerbal_legend;KG\nsimple-camp-fire;4844;campfire;简单营火;Simple Camp Fire;\nbiome-locator;4845;biome_locator;生物群系定位器;Biome Locator;\nextra-coals;4846;extracoals;Extra Coals;;\ndark-caverns;4847;dark_caverns;暗黑深穴;Dark Caverns;\n;4848;lot_of_discs;更多唱片;Lot Of Discs;LODs\n;4849;;卡牌大师;;\nender-ring;4850;ender_ring;末影环;Ender Ring;\nmilk-all-the-mobs;4851;milkallthemobs;Milk All The Mobs;;\nroadrunner;4852;roadrunner;RoadRunner;;\npokecube-aoi;4853;pokecube;Pokecube AIO;;\ngrim-statues;4854;statues;Grim的雕像;Grim Statues;\noauth;4855;oauth,oauth-fabric;OAuth;;\nomaypaty-japan-mod;4856;omaypatys_japan_mod_new;OmayPaty's Japan mod;;\ndemon-slayer-mod-kimetu-no-yaiba;4857;kimetsu_no_yaiba;鬼灭之刃;DEMON SLAYER MOD;\ntipthescales;4858;tipthescales;TipTheScales;;\nxplosives-mod;4859;xplosives;Xplosives;;\narkys-environment;4860;arky_environment;Arky's Environment;;\ncandylands;4861;candylands;Candylands;;\nelements-of-power;4862;elementsofpower;Elements of Power;;\naquafina;4863;aquafina;Aquafina;;\nway-through-dimension;4864;way_through_dimensions;WTDimensions;;WTD\njourneymap-integration;4865;jmi;JourneyMap Integration;;JMI\n;4866;dimension_weapons_thereset;次元装备重置;Dimension Weapons theReset;DWR\ndamage-tint;4867;damagetint;伤害色调;Damage Tint;\nmo-spells;4868;mospells;Mo' Spells;;\nprehistoric-nature;4869;lepidodendron;史前自然;Prehistoric Nature;PN\nblade-works;4870;blade_works;剑制;Blade Works/nikgub_'s Blades+;\ncreate-confectionery;4871;create_confectionery;机械动力：甜食;Create Confectionery;\n;4872;mahjong;麻将;Mahjong;\n;4873;random-botany;随意植物学;RandomBotany;RaBo\n;4874;elementalenhancement;元素强化;Elemental Enhancement;EET\nrotten-piglins-mod;4875;rotten_piglins;腐朽猪灵;Rotten Piglins;\nex-nihilo-random;4876;ex_nihilo_random;无中生有：随想;Ex Nihilo: Random;ExNR\n;4877;lantern;术使提灯;Conjurer's Lantern;\n;4878;gespenst;幽灵;Gespenst;\nfabrictailor;4879;fabrictailor;离线皮肤切换;Fabric Tailor;\nambientworld;4880;ambient_world;AmbientWorld;;\n;4881;craid;自定义袭击;Custom Raid;craid\napple-trees-revived;4882;appletreesrev;苹果树：复生;Apple Trees Revived;\npotatos-lucky-block;4883;potatos_lucky_block;马铃薯幸运方块;Potato's Lucky block;\neven-more-tnt;4884;motnt;Even More TNT;;\nfun-tnt;4885;funtnt;有趣的TNT;Fun TNT;\n;4886;hbm_nwc;核废料工艺;Nuclear Waste Craft;NWC\nchileancraft;4887;chileancraft;智利工艺;Chilean Craft;\ncanals;4888;canals;水渠;Canals;\n;4889;sky_flied_lib;天空领域前置;Sky Flied Dependence;SKDep\nunderground-loot-crates;4890;dungeoncrates;地下战利品箱;Underground Loot Crates;\n;4891;virus;病毒;virus;vs\nsmooth-scrolling-everywhere;4892;smoothscrollingeverywhere;平滑滚动;Smooth Scrolling Everywhere;\nsword-blocking;4893;swordblocking;持剑格挡;Sword Blocking;\nbetter-mount-hud;4894;bettermounthud;更好的骑乘 HUD;Better Mount HUD;\nfire-overlay-controller;4895;fireoverlaycontroller;火焰叠加层调整;Fire Overlay Controller;\n;4896;thedesolat;The Desolat;;\nparticle-rain;4897;particlerain;粒子雨;Particle Rain;\nsodium-reforged;4898;;镁;Magnesium / Sodium Reforged;\nphosphor-reforged;4899;phosphor;硫;Sulfuric/Phosphor Reforged;\nhydrogen-reforged;4900;hydrogen;氦;Helium;\nxl-food-mod-plus;4901;xlfoodmod;超多食物+;XL Food Mod+;\n;4902;cthulhu_mythos;克苏鲁世界;Cthulhu Mythos;\nmcbot;4903;mcbot;群服互联🔗;McBot🔗;\nice-and-fire-dragonseeker;4904;dragonseeker;Ice and Fire: Dragonseeker;;\nreeses-sodium-options;4905;reeses-sodium-options;Reese的钠视频界面;Reese's Sodium Options;RSO\ncontinuity;4906;continuity;Continuity;;\nairportmod;4907;airportlight;Airport Mod;;\nbetter-gui-scaling;4908;betteruiscale;更好的界面缩放;Better GUI Scaling;\ntitles;4909;titles;头衔;Titles;\nskiing;4910;skiing;Skiing;;\ncave-biomes;4911;cavebiomes;Cave Biomes;;\ntatami;4912;tatami;Tatami;;\nfinal-fantasy-12-mod;4913;ffxiimod;Final Fantasy XII Mod;;\nmojangster;4914;mojangster;加载界面动画;Mojangster (Animated Loading Screen);\nanvil-tweaks;4915;anviltweaks;Anvil Tweaks;;\nasphaltmod;4916;asphaltmod;沥青;Asphalt Mod;\n;4917;;KAI我的实体:跨平台版;KAIMyEntity-CrossPlatform;\nreroll-forge;4918;reroll;刷新附魔Forge版;Reroll-Forge;\nswordenlarger;4919;swordenlarger;剑模型扩大;SwordEnlarger;\ncastle-in-the-sky-the-fairytale-of-laputa;4920;castle_in_the_sky;天空之城;Castle in the Sky;\ndimensionalores;4921;dimores;维度矿石;DimensionalOres;\nillagers;4922;illagers_plus;更多的灾厄村民;Illagers+;\n;4923;jeb_;🌈 真实的 jeb_;🌈 Real jeb_;\n;4924;mcga;Make Copper Great Again;;MCGA\n;4925;nikkorimod;Nikkori MOD;;\nvillagers-follow-emeralds-fabric;4926;villagers-follow-emeralds;村民跟随绿宝石;Villagers follow Emeralds;\n;4927;;极限追逐战;;\ndiabloloot;4928;diabloloot;暗黑破坏神战利品;DiabloLoot - Bosses Update;DL\nyggdrasil;4929;yggdrasil;世界树;Yggdrasil;\nftb-dripper-forge;4930;ftbdripper;FTB Dripper;;\nforge-snad;4931;snad;子沙1.14+;Snad;\n;4932;JiuCore;JiuCore;;JC\nopentoall;4933;opentoall;OpenToALL;;\n;4934;better_cauldrons;更好的炼药锅;BetterCauldrons;\nnet-music;4935;netmusic;网络音乐机;Net Music;\ntorchbowmod;4936;torchbowmod;火把弓;TorchBowMod;\nesencia;4937;esencia;Esencia;;\nweirdmobs;4938;weirdmobs;YDM's WeirdMobs;;\nenchantment-level-language-patch;4939;enchlevel-langpatch,enchlevellangpatch;附魔等级语言补丁;EnchLevel-LangPatch;\nopen-to-lan;4940;open2lan;Open2Lan;;\nsimple-translation;4941;simpletranslation;简单翻译;Simple Translation;\nfx-control;4942;fxcontrol;效果控制;Fx control;\nwhat-are-you-voting-for;4943;whatareyouvotingfor;What Did You Vote For?;;\nsteam-powered-create;4944;steampowered;蒸汽动力;Create: Steam Powered;SP\ncreepers-burn;4945;creepersburn;Creepers Burn;;\n;4946;elementalsorcery;元素魔法;ElementalSorcery;ES\npretty-game-enhhancing-gobblet-pgeg-remake;4947;pgeg;魔法森林重置版;Pretty, Game-Enhhancing Gobblet Remake;PGEGRemake\n;4948;hr10;同床异梦;Bedual;\nkvc-equipment;4949;kvc-equipment;蛋挞君的原版补全装备篇;KVCEquipment;\nmooshroomcraft;4950;mooshroomcraft;哞菇工艺;Mooshroomcraft;\nunearthed;4951;unearthed;出土;Unearthed;\nterrestria;4952;terrestria;Terrestria;;\nsimplex-terrain-generation;4953;simplexterrain;Simplex地形生成器;Simplex Terrain Generation;\nenderscape;4954;enderscape;末影之景;Enderscape;\ngoblin-traders-fabric;4955;goblintraders;哥布林商人Fabric版;Goblin Traders (Fabric);\nlakeside;4956;lakeside;湖畔;Lakeside;\nchenziqius-originality;4957;czq;CHENZIQIU的创意;CHENZIQIU's Originality;\nalloy-forgery;4958;alloy_forgery;合金冶炼炉;Alloy Forgery;\nmechanical-tech;4959;mechanicaltech;机械科技;Mechanical Tech;\nhadesgame;4960;hadesgame;阴间游戏;HadesGame;\none-block-forge;4961;oneblock;One Block Plus;;\n;4962;eightcuisine;八大菜系;Eight Cuisine;\nhuman-reborn;4963;human_reborn;Human Reborn;;\nadditional-additions-forge;4964;additionaladditions;额外扩展;Additional Additions;AA\nearlygame;4965;earlygame;EarlyGame;;\nhappiness-is-a-warm-gun;4966;hwg;Happiness is a Warm Gun;;HWG\n;4967;animewifeinmc;纸片人;Anime-In-Mc;\nkvc-blocks;4968;kvc-blocks;蛋挞君的原版补全方块篇;KVCBlocks;\nenigtech2-util;4969;etutil;EnigTech2 Util;;\nenvironmental-creepers;4970;environmentalcreepers;环保型苦力怕;Environmental Creepers;\n;4971;coding-lib;CodingLib;;\nlook-at-that;4972;lookatthat;Look At That;;LAT\nadvanced-peripherals;4973;advancedperipherals;高级外设;Advanced Peripherals;AP\nnether-chest-fabric;4974;netherchest;Nether Chest Fabric;;\n;4975;essentialaddons;EssentialAddons;;\nlevelz;4976;levelz;LevelZ;;\nbetterend-re-forked;4977;betterendforge;更好的末地Forge重铸版;BetterEnd Reforked;\nmining-gadgets;4978;mininggadgets;采矿小工具;Mining Gadgets;\nponderjs;4979;ponderjs;PonderJS;;\nmajrusz-library;4980;majrusz_library,majruszlibrary;Majrusz Library;;mlib\nydms-coppergolem;4981;coppergolem;YDM的铜傀儡;YDM's CopperGolem;\nchococraft-3;4982;chococraft;ChocoCraft 3;;\nchococraft-plus;4983;chococraftplus;ChocoCraft Plus;;\ntravel-huts;4984;travelhut;Travel Huts;;TH\nexpandability;4985;expandability;ExpandAbility;;\nsquirrels;4986;squirrels;松鼠;Squirrels;\nftb-power-pots;4987;ftb-power-pots;FTB 电力植物盆;FTB Power Pots;\nthe-graveyard-forge;4988;graveyard;墓园;The Graveyard;\nnether-agriculture;4989;netheragriculture;下界农业;Nether Agriculture;\nconjuring;4990;conjuring;Conjuring;;\nawesome-dungeon-forge;4991;awesomedungeon;Awesome Dungeon;;\nout-of-sight;4992;out_of_sight;Out Of Sight;;\ndecorative-blocks-modded-compat;4993;decorative_blocks_abnormals;装饰方块模组兼容;Decorative Blocks modded compat;\ncamp-chair;4994;campchair;Camp Chair;;\nsize-matters;4995;sizematters;Size Matters;;\nchalk;4996;chalk;粉笔;Chalk;\nchalk-fabric;4997;chalk;Chalk (Fabric);;\n;4998;modmanager;模组管理器;Mod Manager;\nyarcf;4999;recipehandler;合成冲突消除;Yet Another Recipe Conflict Fixer;YARCF\ndrones;5000;drones;Drones;;\nticbc;5001;bettercompat;Tinkers Better Compat;;TiCBC\n;5002;web,craftbrowser;WEB-UI;;\nmeetle;5003;marquot;Meetle!;;\ntarot;5004;tarot;塔罗;Tarot;\nforcecraft;5005;forcecraft;力量工艺;ForceCraft;\nright-click-harvest;5006;right-click-harvest;右击收割;Right-Click-Harvest;\ncustom-content-packs;5007;ccpacks;Custom Content Packs;;CCPacks\ncake-chomps;5008;cakechomps;Cake Chomps;;\ndistant-horizons;5009;lod,distanthorizons;Distant Horizons;;DH\nbetterp2p;5010;betterp2p;更好的P2P支持;Better P2P;\nob-aquamirae;5011;ob_aquamirae,aquamirae;海灵物语;Aquamirae;\nnuclear-tfc;5012;nucleartfc;Nuclear TFC;;\njust-more-cakes;5013;jmc;只是更多的蛋糕！;Just More Cakes!;JMC\nimage-mod;5014;imagemod;Image Mod;;\nreinforced-barrels;5015;reinfbarrel;增强木桶;Reinforced Barrels;\nspyglass-zoom;5016;spyzoom;望远镜变焦;Spyglass Zoom;\nnpc-variety-port;5017;npcvariety;NPC变体;NPC Variety;\njust-enough-effects;5018;effectinfo;Just Enough Effects;;\nincorporeal-2-rhododendrite;5019;incorporeal,rhododendrite;幻想多媒体2;Incorporeal 2 + Rhododendrite;\n;5020;ex.invoker;祈雨三叉戟;Invoker Trident;\neinsteins-library;5021;einsteins_library;Einstein's Library;;\nworldshape;5022;worldshape;Worldshape;;\nrslargepatterns;5023;rslargepatterns;精致存储大样板;Refined Storage Large Patterns;\ngendustry-jei-addon;5024;gendustryjei;Gendustry JEI Addon;;\nstatement;5025;statement;Statement Library;;\nmutant-more;5026;mutantmore;更多突变生物;Mutant More;\n;5027;DisasterCraft,disastercraft;灾难后的世界;Disaster Craft;\njust-enough-keg;5028;just_enough_keg;Just Enough Keg;;\npaleolithic-revolution;5029;revolution;Paleolithic Revolution;;\nbetter-shields;5030;bettershields;更好的盾牌;Better Shields;\nyungs-bridges;5031;yungsbridges;YUNG 的桥;YUNG's Bridges;\nproject-red-transmission;5032;projectred-transmission;红石计划：传导;Project Red - Transmission;\nday-dream;5033;day_dream;Day Dream;;\nsavage-ender-dragon;5034;dragonfight;更凶猛的末影龙;Savage Ender Dragon;\nage-of-weapons;5036;ageofweapons;武器时代;Age of Weapons;AOW\ndrawerstorage-keychain;5037;drawer_keychain;抽屉钥匙扣;DrawerStorage Keychain;\nfracdustry;5038;fracdustry;分形工业;Fractal Industry;FD\nmagical-forest;5039;magicalforest;魔法森林;Magical Forest;\nskin-swapper;5040;skinswapper;皮肤更换器;Skin Swapper;\n;5041;technology_development;科技发展:测试版;Technology Development:BETA;TDBeta\n;5042;cubeshin;方之提瓦特;Cubeshin Impact;CI\nowo-lib;5043;owo;owo-lib;;oωo\npicturesign;5044;picturesign;Picture Sign;;\ndivine-missions;5045;divine_missions;神圣任务;Divine Missions;\ngregsieves;5046;gregtechsieves;格雷筛子;GregSieves;\ngregtech-food-option;5047;gregtechfoodoption;格雷科技食物优选;GregTech Food Option;GTFO\nconverting-industrial-wires;5048;industrialwires;工业线缆：转化版;Converting Industrial Wires;\ntinkers-oredict-cache;5049;tinkersoredictcache;匠魂矿辞缓存;Tinkers OreDict Cache;\nmentalillness;5050;mentalillness;精神病;Mental illness;Mi\nbetter-magnesium-video-settings-button;5051;bmvsb;更好的镁视频设置按钮;Better Magnesium Video Settings Button;BMVSB\nadventurez;5052;adventurez;AdventureZ;;\nmobz;5053;mobz;MobZ;;\nvillagerquests;5054;villagerquests;村民任务;VillagerQuests;\nenvironmentz;5055;environmentz;EnvironmentZ;;\n;5056;road_to_the_truth;真理之路;Road To the Truth;RTT\nhoming-ender-eye;5057;homing_ender_eye;回归之眼;Homing Ender Eye;\nmagical-crops-regrowth-1-16-5;5058;magicalcrops;魔法作物：重生;Magical Crops: Regrowth;\nvoidz;5059;voidz;VoidZ;;\nmining-master;5060;miningmaster;Mining Master;;\n;5061;myminecraft;绿宝石和钻石;Emerald And Diamond;EAD\nvictus;5062;victus;Victus;;\nlots-of-food-revived;5063;lotsoffoodrev;更多食物：复生;Lots of Food Revived;\ndontstarve;5064;dontstarve;不要饿死2;DontStarve2;DT2\nsteves-universe-space-mod;5065;stevesuniverse;Steve's Universe;;\ncandy-world-recaramelized;5066;candyworld;糖果世界：重制版;Candy World - ReCaramelized;\ncarrier;5067;carrier;搬运;Carrier;\n;5068;;Dawn's PlayGround;;dawn\nundo;5069;undo;撤销！;Undo!;\nunnamed-animal-mod;5070;unnamedanimalmod;无名动物模组;Unnamed Animal Mod;UAM\ncamels;5071;camels;骆驼;Camels;\nfood-effects;5072;foodeffects;Food Effects;;\nitem-borders;5073;itemborders;物品边框;Item Borders;\niceberg;5074;iceberg;冰山;Iceberg;\n;5075;voyager;Voyager;;\nenhanced-farming;5077;enhancedfarming;增强农业;Enhanced Farming;EF\n;5078;chunkmod;Chunk Mod;;\n;5079;tiny_coals;小型煤炭;Tiny Coals;TC\nlava-clear-view;5080;clearview;熔岩下清晰视野;Lava Clear View;\nglobal-xp;5081;globalxp;Global XP;;\nuniversal-graves;5082;universal-graves;Universal Graves;;\n;5083;computronics;Computronics;;\nmore-bows-restrung;5084;morebows;更多弓：重振旗鼓！;More Bows: Restrung!;\nnimble-fabric;5085;nimble;灵活;Nimble;\nmetal-barrels;5086;metalbarrels;金属储物桶;Metal Barrels;\n;5087;Lazystronghold;LazyStrongHold;;\n;5088;autoreset;Auto Reset Mod;;\n;5089;fast_reset;FastReset;;\nmotiono;5090;motiono;mutioNO;;\n;5091;;法杖与巫师;Staffs and Wizards Mod;\nvanillarecipe;5092;vanillarecipe;香草食谱;VanillaRecipe;\n;5093;magic_maid2;魔法女仆迷雾森林;Magic Maid The Fog Forest;\n;5094;maid_skill_1;魔法女仆技能扩展包1;Magic Maid Skill Extension 1;\nfantastic-fish-alpha-v1-4;5095;;神奇的鱼;Fantastic Fish;\n;5096;reiminimap;Rei的小地图;Rei's Minimap;\n;5097;extvil;扩展村庄;Extended Villages;extvil\nmore-mounted-storages;5098;moremountedstorages;更多存储挂载;More Mounted Storages;\nminecraft-comes-alive-reborn;5099;mca;凡家物语：重生;MCA Reborn;MCAR\nnoodles-apex;5100;apex;面条的Apex系列;Noodles'Apex Series;\nftb-beast-coin-miner;5101;ftb_beast_coin_miner;货币矿机;Beast Coin Miner;BCM\nscape-and-run-parasites-addon-by-nocube;5102;nocubessrpaddon,nocubessrparmory;Scape and Run: Parasites Combat Addon;;\nvanilla-degus;5103;vanilla_degus;八齿鼠;Vanilla Degus;\nksyxis;5104;ksyxis;Ksyxis;;\nmetallurgy-4-reforged;5105;metallurgy;冶金4：重铸;Metallurgy 4: Reforged;\nharder-natural-healing;5106;hardernaturalhealing;Harder Natural Healing;;\nextra-bit-manipulation;5107;extrabitmanipulation;Extra Bit Manipulation;;EBM\nspartan-weaponry-twilight-forest;5108;spartantwilight;斯巴达的武器：暮色森林;Spartan Weaponry: Twilight Forest;\niris-reforged;5109;pupil;Lilac / Pupil;;\nadditional-lanterns;5110;additionallanterns;更多灯笼;Additional Lanterns;\nbottled-air;5111;bottledair-fabric,bottledair;瓶装空气;Bottled Air;\nfabricproxy-lite;5112;fabricproxy-lite;FabricProxy-Lite;;\nabyssal-depths;5113;abyssaldepths;Abyssal Depths;;\ntomes;5114;tomes;秘典;Tomes;\nbudschies-morph-mod;5115;bmorph;Budschie's Morph Mod;;\npswg;5116;pswg;帕尔奇的星球大战;Parzi's Star Wars Mod;PSWG\naquatic-abyss;5117;aquaticabyss;Aquatic Abyss;;\nslimier-slimes;5118;slimier-slimes;Slimier Slimes;;\nstupid-weapon;5119;stupid_weapons;Stupid Weapons;;\nslime-carnage;5120;SlimeCarnage;史莱姆杀戮;Slime Carnage;\nportal-tags;5121;portaltags;Portal Tags;;\neden-ring;5122;edenring;伊甸星环;Eden Ring;\nplacement-tweaks;5123;placement_tweaks;放置调整;Placement Tweaks;\nkimetsu-animation-demon-slayer-animation;5124;kimetsuanimationplayer;鬼灭动作;Kimetsu Animation;\nhals-enhanced-biomes;5125;heb;Hals Enhanced Biomes;;\nimprovedbackpacks;5126;improvedbackpacks;改良背包;Improved Backpacks;\nender-skills;5127;enderskills;末影技能;Ender Skills;ES\nuseful-railroads;5128;usefulrailroads;实用铁轨;Useful Railroads;\nvoluminous-energy;5129;voluminousenergy;Voluminous Energy;;\nflaminco;5130;flaminco;Flaminco;;\nsimple-graves;5131;gravestones;Simple Graves;;\n;5132;pixel_tarot;像素塔罗牌;Pixel Tarot;\nwi-zoom;5134;wi_zoom,wi-zoom;WI变焦;WI Zoom;\nforge-carpet;5135;carpet;Forge Carpet;;\n;5136;anc;铁砧工艺：数据包版;AnvilCraft;anc\nfabric-tree-chopper;5137;fabric-tree-chopper;砍树;Fabric Tree Chopper;\ni-see-lava;5138;iseelava;I See Lava;;\ndual-riders;5139;dual_riders;Dual Riders;;\nrefined-relocation-2;5140;refinedrelocation;简单分类2;Refined Relocation 2;\nftb-sluice;5141;ftbsluice;FTB Sluice;;\nsky-villages-forge;5142;skyvillages;天空村庄;Sky Villages;\ngui-clock;5143;guiclock,guiclock-fabric;GUI Clock;;\nhand-over-your-items;5144;handoveryouritems;Hand Over Your Items;;\nkoopas-critters;5145;koopascritters;Koopa's Critters;;\nkrypton-reforged;5146;kryptonreforged;氪Forge版;Krypton Reforged;\nitem-counter;5147;item_counter;物品计数器;Item Counter;\n;5148;candiedmedicine;药糖;Candied Medicine;\nreality-check-real-life-timer;5149;reality_check;Reality Check;;\nretail;5150;retail;Eric's Retail;;\n;5151;;魔力结界(UHC);;UHC\ntime-core-lib;5152;timelib;Time Lib;;\nmclogin;5154;mclogin;MCLogin;;\nrereskillable;5155;rereskillable;Rereskillable;;\nkubejs-blood-magic;5156;kubejs_blood_magic;KubeJS Blood Magic;;\nkubejs-create;5157;kubejs_create;KubeJS Create;;\nkubejs-ui-forge;5158;kubejs_ui;KubeJS UI;;\nkubejs-thermal;5159;kubejs_thermal;KubeJS Thermal;;\nkubejs-immersive-engineering;5160;kubejs_immersive_engineering;KubeJS Immersive Engineering;;\nkubejs-mekanism;5161;kubejs_mekanism;KubeJS Mekanism;;\nstarter-kit;5162;starterkit;初始套件;Starter Kit;\n;5163;CPs;CustomPLus;;CPs\nstacker;5164;stacker;Stacker;;\ngcm;5165;gcm;GCM;;\nocean-floor-clay-sand-and-dirt;5166;oceanfloor;Ocean Floor - Clay Sand and Dirt;;\nlinfox-stacked-dimensions-warden-edition;5167;stacked_dimensions_warden;Deeper in the Caves - RESTART (+ 1.12.2 Version);;\nexperience-bottler;5168;experiencebottler;Experience Bottler;;\ngregicality-ore-addon;5169;gregicalityoreaddon;Gregicality矿物拓展;Gregicality Ore Addon;GAOE\ngregtech-battery-buffer-driver-for-opencomputers;5170;BatteryBufferDriver;格雷科技电池箱的开放式电脑驱动;GregTech Battery Buffer Driver for OpenComputers;\norimod;5171;orimod;OriMod;;\nhtm;5172;htm;Hey That's Mine;;HTM\ngtce-inventory;5173;gtceinventory;格雷物品管道;GregTech CE Inventory Pipes;GI\nmechtech;5174;mechtech;机械工程;MechTech;\ngregtech-energistics;5175;gregtechenergistics;格雷能源;GregTech Energistics;\nempatic;5176;empatic;EmpaTiC;;\nnukepowered-utils;5177;nputils;Nukepowered Utils;;\ngtce2oc;5178;energy_container;格雷科技开放式电脑兼容;gtce2oc;\nazure-rpg-items;5179;azurerpgitems;Azure的RPG风格物品;Azure RPG Items;\nmine-and-slash-expansion;5180;mineandslashexpansion;挖矿与砍杀拓展;Mine and Slash - Expansion Mods;\ndevil-may-cry-weapons;5181;dmcweapons;鬼泣武器;Devil May Cry Weapons;\ncraft-to-exile-custom-uniques;5182;ctecu;放逐之路定制物品;Craft to Exile Custom Uniques;\nterraria-arsenal;5183;terrariaarsenal;泰拉瑞亚装备;Terraria Arsenal;T.A.\nworld-of-warcraft-weapons;5184;wowweapons;魔兽世界装备;World of Warcraft Weapons;\nmcdoom;5185;doom;MC 毁灭战士;MCDoom;\nbleach-weapons;5186;;死神装备;Bleach Weapons;\nbehgameon;5187;behgameon;Behgameon RPG Additions;;\ninto-the-maelstrom;5188;mm;冒险漩涡;Into The Maelstrom;ITM\ncreate-deco;5189;createdeco;Create Deco;;\nwarp-pipes;5190;warppipes;Warp Pipes;;\nfertile-farmland;5191;fertile_farmland;沃壤千里;Fertile Farmland;FF\nluckperms;5192;luckperms;LuckPerms;;\ninv-view;5193;invview;Inv View;;\nftb-industrial-contraptions;5194;ftbic;FTB工业奇械;FTB Industrial Contraptions;FTBIC\nmore-respawn-anchors;5195;morerespawnanchors;更多重生锚;More Respawn Anchors;\nwerewolves-become-a-beast;5196;werewolves;狼人;Werewolves;\nomnipotent-card;5197;omni_card;万用卡牌;Omnipotent Card;\n;5198;;NBT合成台;Creater:NBT_Crafting;CNC\ndoes-it-tick;5199;kotrt_core;Does It Tick? / KoTRT Core;;DIT\npublic-gui-announcement;5200;publicguiannouncement;公开GUI显示;Public Gui Announcement;PGA\nwet-lava-sponge;5201;wetlavasponge;Wet Lava Sponge;;\nripples-of-the-past;5202;jojo;Ripples of the Past;;RotP\n;5203;casual_bows;随意弓;Casual Bows;\ncursor-mod;5204;customcursormod;自定义光标;Cursor Mod;\namplified-nether;5205;amplified_nether,amplifiednether;放大化下界;Amplified Nether;\noreberries-replanted;5206;oreberriesreplanted;矿莓重制版;Oreberries Replanted;\n;5207;withcake;蛋糕相伴;WithCake！;\neye-contact-prohibited;5208;ecp;禁止眼神交流！;Eye contact prohibited;ECP\n;5209;bow_api;弓API;Bow API;\nmodular-assembly;5210;modularassembly;模块化组装;Modular Assembly;MoAs\nhealer;5211;healer;Healer;;\ncalcium;5212;calcium;钙;Calcium;\nfrom-the-shadows;5213;fromtheshadows;From the shadows;;\nl_ender-s-cataclysm;5214;cataclysm;灾变;L_Ender 's Cataclysm;\nbrighter-block-light;5215;brighter;亮光;Brighter;\npick-up-notifier;5216;pickupnotifier;拾取提示;Pick Up Notifier;PN\n;5217;fix4log4j;Log4J修复补丁;Fix4Log4J;\ntalkbubbles;5218;talkbubbles;TalkBubbles;;\nservux;5219;servux;Servux;;\nconfig-menus-forge;5220;configmenusforge,forgeconfigscreens;Forge Config Screens/Config Menus for Forge;;\nsaplanting;5221;saplanting;落苗生根;Saplanting;SPLT\nlogin;5222;login;Login;;\nedible-bugs;5223;ediblebugs;Edible Bugs;;\nfarsight;5224;farsight,farsight_view;Farsight;;\n;5225;;额外的主世界;An Extra Overworld;\natlantis;5226;atlantis;亚特兰蒂斯;Atlantis;\ngaia-dimension;5227;gaiadimension;Gaia Dimension;;\npwem;5228;pwem;Panda's Weapon Editor;;PWEM\nakisephilas-bosses;5229;akisephilas_bosses;Akisephila's Bosses;;\n;5230;fixlog4j214;FixLog4j214;;\n;5231;ExploitFix;ExploitFix;;\nfluffys-farming;5232;flavourfull;Fluffy's Farming | Flavourfull;;\naquatic-odyssey-mod;5233;aquaticabyssmod;Aquatic Odyssey;;\nvanilla-cookbook;5234;vanillacookbook;原版烹饪书;Vanilla Cookbook;\nedible-nether-wart;5235;edible_netherwart;可食用下界疣;Edible Nether Wart;\nplasmo-voice;5236;plasmo_voice;Plasmo 语音;Plasmo Voice;\narmor-sound-tweak;5237;armorsoundtweak;盔甲音效调整;Armor Sound Tweak;\nhexerei;5238;hexerei;魔法巫师;Hexerei;\nmorerefinedstorage;5239;;More Refined Storage;;\ngive-me-water-or-give-me-death;5240;waterordeath;Give Me Water Or Give Me Death;;\nlost-and-found;5241;lostandfound;Lost and Found;;\nbetter-fps-render-distance;5242;betterfpsdist;Better Fps - Render Distance;;\narmor-chroma-for-fabric;5243;armorchroma;盔甲颜色显示Fabric版;Armor Chroma for Fabric;\nore-reeds;5244;ore_reeds;矿石甘蔗;Ore Reeds;\nconnected-glass;5245;connectedglass;Connected Glass;;\nblockui;5246;blockui;BlockUI;;\nstorage-racks;5247;storageracks;Storage Racks;;\nDung-Pipe;5248;dungpipe;Dung Pipe;;\nextra-armor;5249;extraarmor;更多盔甲;Extra Armor;\npig-poop;5250;pigpoop;Pig Poop (Fabric);;\nstupid-horse-stand-still;5251;horsestandstill;Stupid Horse Stand Still;;\nceramic-bucket;5252;ceramicbucket;陶瓷桶;Ceramic Bucket;\ndurability101;5253;durability101;Durability101;;\nceramic-shears;5254;ceramicshears;陶瓷剪刀;Ceramic Shears;\ntorch-bandolier;5255;torchbandolier;火把弹药袋;Torch Bandolier;\nrapid-leaf-decay;5256;rapid_leaf_decay;Rapid Leaf Decay;;\nnetherite-horse-armor-mod;5257;netheritehorsearmor;下界合金马铠;Netherite Horse Armor Mod;\nambient-environment;5258;ambientenvironment;Ambient Environment;;\nglass-cutter;5259;glasscutter;Glass Harvesters;;\ndicemc-tiered-armors;5260;dicemcta;DiceMC Tiered Armors;;\ntetranomicon;5261;tetranomicon;Tetranomicon;;\nmore-observers;5262;moreobservers;更多侦测器;More Observers;\nvillagers-respawn;5263;villagersrespawn;村民重生;Villagers Respawn;\nhot-swappable-armor;5264;;热切换护甲;Hot-Swappable Armor;\nadvancement-plaques;5265;advancementplaques;进度牌匾;Advancement Plaques;\n;5266;magcore;魔源核心;MagCore;\nminceraft-mod;5267;mince;Minceraft Mod;;\nalexs-delights;5268;alexsdelight;Alex乐事;Alex's Delights;\nbetter-bedrock-generator;5269;betterbedrockgenerator;Better Bedrock Generator;;BBG\nprehistoric-fauna;5270;prehistoricfauna;史前生物;Prehistoric Fauna;PF\nbetter-conduit-placement;5271;betterconduitplacement,betterconduitplacement-fabric;更好的潮涌核心放置;Better Conduit Placement;\ndeath-counter;5272;deathcounter;死亡计数器;Death Counter;\ndimension-stages;5273;dimstages;维度阶段;Dimension Stages;\npassive-dragon;5274;dragon;Passive Dragon (APRIL FOOLS);;\ni-want-to-die-at-home;5275;iwtdah;I want to die at home!;;\nmorevanillaweapons;5276;mvw;更多原版武器;MoreVanillaWeapons;mvw\nore-tree-reborn;5277;ore_tree;矿物树：重制;Ore Tree:Reborn;OT:R\nplayer-tracking-compass;5278;playertrackingcompass;Player Tracking Compass;;\n;5279;da;诡梦;DreamAchievement;\ntooltipfix;5280;tooltipfix;信息提示修复;ToolTipFix;\nextended-noteblock-forge;5281;jumpingnoteblock;音符盒扩展;Extended Noteblock;\nore-prospecting-stick-forge;5282;oreprospectingstick;探矿之戒;Rings Of Prospecting;\nworldedit-express-forge;5283;worldeditexpress;WorldEdit Express Keybinding;;\nawesome-dungeon-edition-ocean-forge;5284;awesomedungeonocean;Awesome Dungeon Ocean Edition;;\npistorder;5285;pistorder;Pistorder;;\nescape-rope-pokemon-item-forge;5286;escaperope;Escape Rope - Item Pokemon;;\nawesome-flooring-forge;5287;awesomeflooring;Awesome Flooring;;\nmedievial-entity-forge;5288;medievalentity;中世纪生物;Medieval Entity;\nupgraded-shulkers;5289;upgradedshulkers;更多潜影盒;Upgraded Shulkers;\n;5290;seedcrackerx;SeedCrackerX;;\nbobby;5291;bobby;服务器区块缓存;Bobby;\nboosted-brightness;5292;boostedbrightness;亮度增强;Boosted Brightness;\neffective;5293;effective,effective_fg;Effective 💦;;\n;5294;merchants;回收商;Merchants;\nvoidtotem;5295;voidtotem;虚空图腾;Void Totem;\ntool-leveling-plus;5296;toolleveling;工具升级;Tool Leveling+;\nmythicmetals;5297;mythicmetals;神话金属;Mythic Metals;\ncreeper-firework;5298;creeper_firework;烟花苦力怕;Creeper Firework;\nsecond-chance;5299;secondchance,secondchanceforge;Second Chance;;\npromenade;5300;promenade;Promenade;;\nlondon-underground;5301;londonunderground;伦敦地铁;MTR London Underground Addon;LU\ndynamiclights-reforged;5302;dynamiclightsreforged,sodiumdynamiclights;钠/Embeddium：动态光源;Sodium/Embeddium Dynamic Lights;\nthe-wild-mod;5303;twm,the_wild_update;荒野更新;The Wild Mod;TWM\ntuff-depth;5304;tuffdepth;凝灰岩深度;Tuff Depth;\nminefantasy-reforged;5305;minefantasyreforged;我的幻想重铸版;MineFantasy Reforged;MFR\n;5306;uechants;实用附魔;Useful Enchantments;\nframework;5307;framework;Framework;;\n;5308;nzpigeon_chairs;南织鸽子的椅子;Nzpigeon chairs;\n;5309;applied_create;应用机械;Applied Create;\nanointed-items;5310;anointeditems;Anointed Items;;\ngolden-age-combat;5311;goldenagecombat;黄金时代的战斗;Golden Age Combat;GC\nmagnesium-extras;5312;magnesium_extras,rubidium_extras,sodiumextras,embeddiumextras;钠/Embeddium：附属;Sodium/Embeddium Extras;\nchest-tracker;5313;chesttracker;箱子追踪;Chest Tracker;\nadvancementinfo;5314;advancementinfo;进度信息展示;AdvancementInfo;\nanimatica;5315;animatica;Animatica;;\nall-dimension-height-increase;5316;dimheight;全维度建筑高度提升;All Dimension Height Increase;\nmerchant-markers;5317;merchantmarkers;商人标记;Merchant Markers;\nloot-bag;5318;loot_bag,lootbag;战利品袋子;Loot Bag;LB\nminer-watcher;5319;miner_watcher;矿工监视者;Miner Watcher;MW\nmemento-mori-remember-death;5320;mementomori;人终有一死/记住死亡;Memento Mori / Remember Death;\nnot-enough-gamerules;5321;not_enough_gamerules;Not Enough Gamerules;;\n;5322;jsonem;Json格式化实体模型;Json Entity Models;JsonEM\nspeedrunigt;5323;speedrunigt;SpeedRunIGT;;IGT\narchmagus;5324;archmagus;Archmagus;;\nslimyboyos;5325;slimyboyos;SlimyBoyos;;\nfireplace-lib;5326;fireplacelib;Fireplace Lib;;\nimmersive-geology;5327;immersive_geology;沉浸地质学;Immersive Geology;IG\nwool-tweaks;5328;wooltweaks;羊毛调整;Wool Tweaks;\ntools-complement;5329;tools_complement;Tool's Complement;;\nadventtic;5330;adventtic;AdventTiC;;\nquestionably-immersive;5331;questionablyimmersive;Questionably Immersive;;\nlong-fall-boots;5332;longfallboots;Long Fall Boots;;\ntotal-darkness;5333;totaldarkness;Total Darkness;;\ntrue-darkness;5334;darkness;True Darkness;;\nstarlight-x-create;5335;;Starlight x Create;;\ngrand-economy;5336;grandeconomy;Grand Economy;;\nchinacraft2;5337;chinacraft;华夏文明2;ChinaCraft2;CC2\ninteractic;5338;interactic;Interactic;;\nfnars-roguelike-dungeons;5339;roguelike;冒险地牢-Fnar版;Roguelike Dungeons -- Fnar's Edition;\nplayerex;5340;playerex;PlayerEx;;\ndata-attributes;5341;dataattributes;Data Attributes;;\nhardcore-buoyance;5342;hcbuoy;Hardcore Buoyance;;\ngregtech-ce-unofficial;5343;gregtech;格雷科技社区版：非官方版;GregTech CE: Unofficial;GTCEu\neggtab-fabric;5344;eggtab;独立刷怪蛋列表;Egg Tab;\nalways-a-wither-skull;5345;alwaysawitherskull;必得凋灵骷髅头颅;Always a Wither Skull;\nanvil-restoration;5346;anvilrestoration;铁砧修复;Anvil Restoration;\nhide-hands;5347;hidehands;隐藏主副手;Hide Hands;\nbetter-beacon-placement;5348;betterbeaconplacement;更好的信标放置;Better Beacon Placement;\ndead-totems;5349;deadtotems;Dead Totems;;\nfunctional-storage;5350;functionalstorage;功能性存储;Functional Storage;\nthe-wild-update;5351;the_wild_update;荒野更新 1.19;The Wild Update 1.19;\narcheology;5352;archeology;考古学;CapsLock Archeology;\n;5353;child-folder-mods-scaner;子文件夹Mod扫描器;child folder mods scaner;CHMS\ntrinkets-and-baubles;5354;xat;饰品与小玩意;Trinkets and Baubles;\n;5355;;雪王;Snow King;\nrope-ladders;5356;ropeladders;绳梯;Rope Ladders;\nskewers-updated;5357;skewers;烤串更新版;Skewers Updated;\nkeep-my-soil-tilled;5358;keepmysoiltilled;保持肥沃;Keep My Soil Tilled;\ndouble-doors;5359;slopes,doubledoors;双开门;Double Doors;\nexpanded-armor-enchanting;5360;expanded_armor_enchanting;盔甲附魔扩展;Expanded Armor Enchanting;\nexpanded-weapon-enchanting;5361;expanded_weapon_enchanting;武器附魔扩展;Expanded Weapon Enchanting;\nexpanded-axe-enchanting;5362;expanded_axe_enchanting;斧附魔扩展;Expanded Axe Enchanting;\nexpanded-trident-enchanting;5363;expanded_trident_enchanting;三叉戟附魔扩展;Expanded Trident Enchanting;\nurns;5364;urns;Urns;;\npylons;5365;pylons;Pylons;;\non-soul-fire;5366;onsoulfire;On Soul Fire;;\nwizard-staff;5367;wizard-staff;Wizard Staff;;\nkambrik;5368;kambrik;Kambrik;;\nadditional-bars;5369;additionalbars;更多的栏杆;Additional Bars;\nleap;5370;leap;Leap;;\ncameraoverhaul;5371;cameraoverhaul;Camera Overhaul;;\nenchantment-id-extender;5372;eide;Enchantment ID Extender;;\ncreeper-overhaul;5373;creeperoverhaul;苦力怕改革 / 苦力怕革新;Creeper Overhaul;\ncool-elytra-roll;5374;cool_elytra;Cool Elytra Roll;;\ncamera-overhaul-forge;5375;cameraoverhaul;CameraOverhaul (Forge);;\nblame;5376;blame;Blame;;\npatboxs-banhammer;5377;banhammer;BanHammer!;;\nterraformed-api-reforged;5378;;Terraformed Api Reforged;;\nmultiblocktweaker;5379;multiblocktweaker;MultiblockTweaker;;MBT\ngood-ol-currency;5380;modcurrency;Good Ol' Currency;;\nmacaws-bridges-atmospheric;5381;macawsbridgesatmospheric;Macaw 的桥梁：悠然一派附属;Macaw's Bridges - Atmospheric;\nleadvillagers;5382;;LeadVillagers;;\nfabric-shield-lib;5383;fabricshieldlib;Fabric Shield Lib;;\nfrys-things-guns-and-more;5384;frys_things;Fry's Things: Guns and More;;\nquickbind;5385;command-gui-buttons;功能集成;Quick Bind/Command GUI buttons;\nompd;5386;ompd;开放式模块化被动防御;Open Modular Passive Defense;OMPD\nbasic-shields-fabric;5387;basicshields;Basic Shields;;\nborderless-mining;5388;borderlessmining,fw;Borderless Mining;;\nledger;5389;ledger;Ledger;;\npet-owner;5390;petowner;Pet Owner;;\nspartan-hud-baubles;5391;spartanhudbaubles;斯巴达饰品栏HUD;Spartan HUD: Baubles;\nmekanism-matter;5392;mekanismmatter;通用机械：物质拓展;Mekanism Matter;\ninzhefop-core;5393;inzhefopcore;inzhefop's Core;;\n;5394;milky;Milky;;\n;5395;slopes;Slopes;;\nlegendary-tooltips;5396;legendarytooltips;传说提示框;Legendary Tooltips;\nmyrtrees;5397;myrtrees;Myrtrees;;\n;5398;;Nameless v1.1;;\ntiny-redstone;5399;tinyredstone;Tiny Redstone;;\n;5400;blooddisplayer;血量显示器;BloodDisplayer;\nicarus;5401;icarus;Icarus;;\nthe-splash-milk;5402;splash_milk;喷溅牛奶;Splash Milk;\nfastbench-for-fabric;5403;fastbench;工作台性能优化Fabric版;FastBench for Fabric;\nmob-buckets-fabric;5404;mobbuckets;生物桶;Mob Buckets;\n;5405;er_bot;额外原神学;Ex_genshin_er_botonia;ERBOT\n;5406;vng;Type-32的枪械数据包;;\nenchantable;5407;enchantable;Enchantable;;\nglass-doors;5408;glassdoors;玻璃门;Glass Doors;\nglasscutter;5409;glasscutter;玻璃切割器;Glass Cutter;\nupgradable-crystal-tools;5410;crystal_tools;Upgradable Crystal Tools;;\nno-dragon-cheese;5411;;No Dragon Cheese;;\nglassware;5412;glassware;玻璃制品;Glassware;\nmilk-plus;5413;milk_plus;Milk Plus;;\nmodifiers;5414;modifiers;重铸;Modifiers;\ncustomnpc-plus;5415;customnpcs;自定义NPC+;CustomNPC+;\ningredient-extension-api;5416;ingredient-extension-api;Ingredient Extension API;;\n;5417;svb;简单原版增强;Simple Vannila Boost;SVB\nwireless-networks;5418;wirelessnetworks;Wireless Networks;;\ncompass-manhunt;5419;manhunt;Compass Manhunt;;\nexlines-furniture;5420;exlinefurniture;Exline's Furniture Mod;;\ntinkers-archery;5421;tinkersarchery;工匠箭术;Tinkers' Archery;\ndesigner-modeling;5422;designermodeling;设计师建模;Designer Modeling;\nnetherite-horse-armor-fabric;5424;nha;下界合金马铠;Netherite Horse Armor;\nnetherite-elytra;5425;netherelytra;下界合金鞘翅;Netherite Elytra;\n;5426;zhohome;猪红世界;Zhohome;\nmore-tcon;5427;tconmaterial,moretcon;更多匠魂材料;Moar TCon;\nexlines-emerald-equipment;5428;emeraldequipment;Exline's Emerald Equipment;;\nexlines-candy-mod;5429;candymod;Exline's Candy Mod;;\nrenamer;5430;renamer;代号名称;Renamer;\n;5431;fractal_coppery;分形铜艺;Fractal Coppery;FC\nthe-herobrine-mod-1-7-10;5432;herobrinemod;The Herobrine Mod (Classic);;\naquatic-armors;5433;aquaticarmors;Aquatic Armors;;\nimmersive-industry;5434;immersiveindustry;沉浸工业;Immersive Industry;II\nlightmans-currency;5435;lightmanscurrency;Lightmans Currency;;\nhellfire-fireball-launcher;5436;hellfire;地狱火：火焰弹发射器;Hellfire: Fireball Launcher;H:FL\nguild;5437;guild;The Guild;;\nendless-deep-space;5438;endless_deep_space;无尽深空;Endless Deep Space;EDS\n;5439;foodex;原版食物拓展;;\ndynamic-trees-for-sakura;5440;dtsakuracompact;动态的树：樱附属;Dynamic Trees for Sakura;\nadvanced-fishing;5441;advanced-fishing;Advanced Fishing;;\n;5442;partyrecipes;派对食谱;PartyRecipes！;PR\nbettertabs;5443;bettertabs;BetterTabs;;\nclassy-hats;5444;classyhats;时髦帽子;Classy Hats;\nbedbreakbegone;5445;bedbreakbegone;BedBreakBegone;;\nbq-multiblock-structure-integration;5446;bq_msi;更好的任务 - 多方块与结构扩展;Better Questing - Multiblock and Structure Integration;\nbqtweaker;5447;bqtweaker;Better Questing Tweaker;;BQTweaker\nhypnos;5448;hypnos;Hypnos;;\ncollision-damage;5449;collisiondamage;Collision Damage;;\nproject-74246;5450;dldungeonsjbg;毁灭地牢;Doomlike Dungeons;\ndynamic-trees-defiled-lands;5451;dynamictreesdefiledlands;动态的树：污秽之地附属;Dynamic Trees - Defiled Lands;\nxaero-map-addition;5452;xaero_map_addition;Xaero Map Addition;;XMA\nlambdacontrols;5453;lambdacontrols;Lambd 的控制优化;LambdaControls;\nducky-mod;5454;duckymod;Ducky Mod;;\ncraftgr;5455;craftgr;幻想乡电台;CraftGR;\nquartz;5456;quartz;Quartz;;\nall-the-blocks;5457;;All The Blocks;;\nallthemodium;5458;allthemodium;Allthemodium;;\nato;5459;alltheores;All the Ores;;ATO\njust-enough-effect-descriptions-jeed;5460;jeed;JEI药水效果;Just Enough Effect Descriptions;JEED\nhostile-neural-networks;5461;hostilenetworks;Hostile Neural Networks;;\nmo-cakes;5462;mocake;更多蛋糕;Mo' Cake;\nfishing-made-better;5463;fishingmadebetter;更好的钓鱼;Fishing Made Better;FB\nneon-craft-ultimate;5464;neoncraft2;霓虹灯艺2;Neon Craft Ultimate/Neon Craft 2 Mod;NC2\narchitects-dream;5465;architectsdream;建筑师之梦;Architect's Dream;\nignition-enderbags;5466;enderbags;Ignition: EnderBags;;\ncorrelated;5467;correlated;Correlated;;\nenderbags;5468;ender_bags;EnderBags;;\nno-mob-spawning-on-trees;5469;nmsot;树枝树干不刷怪;No Mob Spawning on Trees;NMSOT\nrf-lux;5470;rflux;RF Lux;;\n;5472;roughly-enough-characters;Roughly Enough Characters;;RECh\n;5473;registrate,registrate-fabric;Registrate;;\neasy-normal-and-magic-foods;5474;foodplus;Easy Normal and Magic Foods;;\ntiefix;5475;tiefix;TieFix;;\nmore-chat-history;5476;morechathistory;更多聊天记录;More Chat History;\nphosphor-reloaded;5477;phosphor_reloaded;磷重制版;Phosphor Reloaded;\n;5478;;Minecraft, But Leaves Drop OP Loot!;;\npigstep-backport;5479;pigstep;Pigstep;;\nagadars-brewing-api;5480;brewingapi;Agadar's Brewing-API;;\n;5481;;炫酷僵尸出生动画;;\n;5482;;饮水值OOC;;\nae-shield;5483;aeshield;奕 秉甲;Ae Shield;AES\n;5484;koroworld;科洛的服务器支持;KoroWorld-Minecraft Support;KW\ngiant-player-boss;5485;playerbosses;巨人玩家 Boss;Giant Player Boss;\nset-bonus;5486;setbonus;Set Bonus;;\ntntutils;5487;tnt_utilities;TNTUtils;;\ngiant-swords;5488;giantswords;巨剑;Giant Swords;\nterrablender;5489;terrablender;TerraBlender;;\ncommandhelper;5490;comhelper;命令助手;CommandHelper;CHper\nbilibili;5491;bilibilitianlovol;Bilibili;;\n;5492;;连锁挖矿轻量版;;\ncreatures;5493;creatures;Frikinzi's Fauna;;\nmcdw;5494;mcdw;我的世界：地下城武器;MC Dungeons Weapons;MCDW\n;5495;tooltipinjector;提示框注入器;Tooltip Injector;\nbeta-creepers;5496;beta_creepers;Beta Creepers;;\n;5497;more_plentiful_vanilla;更丰富的原版;More Plentiful Vanilla;MPV\nmacaws-paintings;5498;mcwpaintings;Macaw的画;Macaw's Paintings;\ndyenamics;5499;dyenamics;Dyenamics;;\nmulti-piston;5500;multipiston;Multi-Piston;;\nblood-magic;5501;bloodmagic;血魔法3;Blood Magic 3;BM3\nresolutioncontrol;5502;resolutioncontrol;分辨率控制;Resolution Control/ResolutionControl+;\nwdl;5503;wdl;世界下载器;World Downloader;WDL\n;5504;;自定义合成OOC2;;\n;5505;info-display;信息显示器;;\ndomum-ornamentum;5506;domumornamentum,domum_ornamentum;Domum Ornamentum;;\nmethodicalmilking;5507;methodicalmilking;MethodicalMilking;;\n;5508;modmdo;ModMdo;;\nsave-and-load-inventories;5509;saveandloadinventories;Save and Load Inventories;;\nforge-config-api-port-fabric;5510;forgeconfigapiport;Forge Config API Port;;FC\npams-cookables;5511;pamscookables;Pam's Cookables;;\nend-metals;5512;;End Metals;;\nvitality;5513;;Vitality;;\nfeatured-servers;5514;featuredservers;Featured Servers;;\noldjavawarning;5515;oldjavawarning;旧Java警告;OldJavaWarning;\nmercurius;5516;mercurius;Mercurius;;\nall-the-swords;5517;alltheswords;All The Swords;;\npams-simple-recipes;5518;simplerecipes;Pam's Simple Recipes;;\nbow-infinity-fix;5519;bowinfinityfix;无限附魔修复;Bow Infinity Fix;BIF\nenchanting-infuser-forge;5521;enchantinginfuser;附魔灌注台;Enchanting Infuser;EI\nmenumobs;5522;menumobs;主菜单生物渲染;MenuMobs;\n;5523;heq,hexerei;坚硬装备;HarderEquipments;\ntiny-skeletons-forge;5524;tinyskeletons;小骷髅变种;Tiny Skeletons;TS\n;5525;topfix;TOP修复;TOP Fix;\nmatter-overdrive-community-edition;5526;matteroverdrive;超能物质社区版;Matter Overdrive: Community Edition;MOCE\ncavetweaks;5527;cavetweaks;洞穴调整;CaveTweaks;\ncosmetic-armor-fabric;5528;cosmetic-armor;时装盔甲;Cosmetic Armor;\n;5529;attack-distance;Attack Distance;;\n;5530;ems;更多寂静材料;Extra Material Spieces;EMS\ncrimson-chickens;5531;crimsonchickens;Crimson Chickens;;\ndrippy-loading-screen;5532;fancymenu,drippyloadingscreen;自定义游戏加载界面;Drippy Loading Screen;\nextreme-sound-muffler;5533;extremesoundmuffler;Extreme Sound Muffler;;ESM\ndragonloot;5534;dragonloot;龙战利品;DragonLoot;\nwtfcore;5535;WTFCore;WTFCore;;\nore-remover;5536;ore_remover;Ore Remover;;\nbetweenlands-redstone;5537;betweenlandsredstone;Betweenlands Redstone;;\nmore-peripherals;5538;peripherals;More Peripherals;;\nageing-spawners;5539;ageingspawners;Ageing Spawners;;\nloot-extra;5540;loot_extra;战利品表拓展;LootExtra;LX\ncloud-boots-mod;5541;cloudboots;云靴;Cloud Boots;\nbetter-compatibility-checker;5542;bcc;Better Compatibility Checker;;\nfastfurnace-minus-replacement;5543;fastfurnaceminusreplacement;Fast Furnace minus Replacement;;\nharvest;5544;harvest;Harvest;;\ncraftpresence;5545;craftpresence;CraftPresence;;CP\nstonecutter-recipe-tags;5546;stonecutter_recipe_tags;Stonecutter Recipe Tags;;\ncustomnpcsreborn;5547;customnpcs;自定义NPC：重生;Custom NPCs Reborn;\nbrassamberbattletowers;5549;ba_bt;BrassAmber 战斗高塔;BrassAmber BattleTowers;\njags;5550;jags;Just Another Grass Seed;;JAGS\nsedna;5551;sedna;Sedna;;\nfog-tweaker;5552;fogworld,fogtweaker;Fog World / Fog Tweaker;;\nopen-elevator-fabric;5553;elevator233;Fabric版开放式电梯;OpenElevator (Fabric);\n;5554;enchant-syringe;魔咒注入器;enchant syringe;ES\nnullscape-end-reborn;5555;nullscape_forge,nullscape;空无之景;Nullscape;\nbrutal-bosses-dungeon;5556;brutalbosses;Brutal Bosses - Custom Bosses;;\nguerrilla-vs-command-mod-high-difficulty-addition;5557;GVC;游击队员vs突击队员;Guerrilla vs Command;GVC\nsuper-tools-reloaded-reloaded;5558;super_tools_reloaded_reloaded;超级工具重置版重制版;Super Tools Reloaded-Reloaded;STRR\nlychee;5559;lychee;Lychee;;\nmorepaths;5560;morepaths;MorePaths;;\nextended-mushrooms;5561;extendedmushrooms;Extended Mushrooms;;\nshear-madness;5562;shearmadness;Shear Madness;;\nmissing-pieces;5563;missing_pieces;Missing Pieces;;\nhatchery;5564;hatchery;Hatchery;;\nalexs-delight;5565;alexsdelight;Alex's Delight;;\ncreatures-and-beasts;5566;cnb;动物和野兽;Creatures and Beasts;\ndead-guys-deep-dark;5567;dead_guys_untitled_deep_dark_;Dead Guys Untitled Deep Dark;;\ntowers-of-the-wild-reworked;5568;towers_of_the_wild_reworked;旷野之息高塔：重制;Towers of the Wild: Reworked;\ntowers-of-the-wild-reloaded;5569;towers_of_the_wild;旷野之塔：重置;Towers of the Wild: Reloaded;\nbansoko;5570;bansoukou;创可贴;Bansoukou;\n;5571;;MITE：破晓;MITE:Dawn;\ninventory-backpack;5572;inventory_backpack;Inventory Backpack;;\n;5573;mod_BetterThenBuildCraft;比狼好BC兼容;Better Then Buildcraft;BTB\nreinforced-shulker-boxes;5575;reinfshulker;增强潜影盒;Reinforced Shulker Boxes;\nfastopenlinksandfolders;5576;fastopenlinksandfolders;快速打开链接和文件夹;FastOpenLinksAndFolders;\nreinforced-chests;5577;reinfchest;增强箱子;Reinforced Chests;\nbiome-staff;5578;biomestaff;Biome Staff;;BS\ndynamic-trees-quark;5579;dynamictreesquark;动态的树：夸克附属;Dynamic Trees - Quark;\nradium-reforged;5580;radium;镭;Radium Reforged;\nbackpackmod;5581;backpackmod;BackpackMod;;\n;5582;;3格物品栏挑战;;\nuntamedwilds;5583;untamedwilds;不羁野性;Untamed Wilds;\nadvancements-tracker;5584;advancementstracker,advancements_tracker;进度追踪器概览;Advancements Tracker and Overview;\nno-null-processors;5585;no_null_processors;No Null Processors;;\n;5586;jm;果汁贩卖机;Juice Vending Machine;JVM\nma-enchants;5587;maenchants;Ma 附魔;Ma Enchants;\nbits-and-chisels;5588;bitsandchisels;Bits and Chisels;;\ntrading-post;5589;tradingpost;交易站;Trading Post;TP\nphantasmic;5590;nourished_nether;Nourished Nether/Phantasmic/Netherific;;\nfurnus;5591;furnus;Furnus;;\npermanent-light-generator;5592;plg;恒久光发电机;Permanent Light Generator;PLG\nimmortuos-calyx;5594;immortuoscalyx;Immortuos Calyx;;\nclaim-chunk;5595;cc;Claim Chunk;;CC\ndicemc-money-mod;5596;dicemcmm;Money and Sign Shops;;\nclient-command-aliases;5597;dicemccca;Client Command Aliases;;CCA\ninteractive-corporea;5598;interactive_corporea;交互多媒体;Interactive Corporea;\njust-another-auto-clicker;5599;justanotherautoclicker;Just Another Auto Clicker;;JAAC\n;5600;zelda;旷野之息;Breath Of The Wild;\nbetter-sneak;5601;bettersneak;更好的潜行;Better Sneak;\ncrapping-copper;5602;crappingcopper;压缩升级铜;Crapping Copper;\nbotany-pots-ore-planting;5603;botany_pots_ore_planting;植物盆栽矿石种植;Botany Pots Ore Planting;BPOP\n;5604;transformers;变形金刚;Transformers Mod;TFmod\nsatin-api;5605;satin;Satin API;;\neasy-magic;5606;easymagic;简易附魔台;Easy Magic;EM\nenlightend;5607;nourished_end,enlightened_end;末地启明;Enlightend/Enlightened End/Nourished End;\nrubidium;5608;rubidium;铷;Rubidium;\ncultural-delights;5609;culturaldelights,cultural_delights;多元乐事;Cultural Delights;\n;5610;;我的世界，但是没有含水层;Minecraft, but there are NO AQUIFERS;\njer-integration;5611;jerintegration;JER集成;JER Integration;JERI\njust-enough-beacons;5612;justenoughbeacons;JEI信标;Just Enough Beacons;\nwindowed-fullscreen;5613;windowedfullscreen;Windowed Fullscreen;;\n;5614;sandbox;Sandbox;;SBX\nhelpful-hitboxes-forge;5615;helpfulhitboxes;Helpful Hitboxes;;\nvein-mining;5616;veinmining;连锁采集;Vein Mining;\nars-creo;5617;ars_creo;Ars Creo;;\nit-fell-from-the-sky;5618;ItFellFromTheSky;It Fell From The Sky;;\n;5619;;友好世界;FriendlyCraft;FC\n;5620;integerarray_scimitar;弯刀;Scimitar Mod;\n;5621;integerarray_magiccookie;魔法曲奇;Magic Cookie Mod;\n;5622;integerarray_commonemerald;常见的绿宝石;Common Emeralds Mod;\n;5623;integerarray_chickencage;鸡笼;Chicken Cage Mod;\n;5624;integerarray_leatherarmor;Leather Armor Plus Mod;;\n;5625;integerarray_leatherarmor;撑竿与羽翼;Pole Vault Mod;\nlyonheart;5626;lyonheart;Lyonheart;;\ngenshin-12;5627;genshin12;原神12;Genshin 12;\nae-additions-extra-cells-2-fork;5628;aeadditions,ae2additions;AE Additions - ExtraCells2 Fork;;EC2F\nsilent-addons;5629;silentaddons;Silent Addons;;SA\nars-arsenal;5630;arsarsenal;Ars Arsenal;;\ngps-mod-stalker-mod;5631;gps;GPS Mod / Stalker mod;;\nextended-armor-bars;5632;extended_armor_bars;Extended Armor Bars;;\natom-economy;5633;atomeco;基石-经济;Atom-Economy;\njust-enough-piglin-bartering;5634;jepb;JEI猪灵以物易物;Just Enough Piglin Bartering;JEPB\n;5635;groophite;Groophite;;GrT\n;5636;aft_fabroads;暗访谈的Fabric道路;aftersans53228's fabric roads;AFRoads\nprimal-magick;5637;primalmagick;原初魔法;Primal Magick;\nore-creeper;5638;ore_creeper;矿石苦力怕;Ore Creeper;\npanthalassa;5639;panthalassa;泛古洋;Panthalassa;\nstardew-armory;5640;stardewarmory;星露谷武器;Stardew Armory;SA\nno-more-nether-desert-edition;5641;netherindesserts;No More Nether: Desert edition;;NMN\nblaze-gear;5642;blazegear;烈焰装备;Blaze Gear;\n;5643;aier;反内能革命;Anti-internal Energy Revolution;AIER\nrhodonite-tools-armour;5644;rhodonite;Rhodonite;;\nmore-minecarts;5645;moreminecarts;More Minecarts and Rails;;\ncorn-delight;5646;corn_delight,corndelight;玉米乐事;Corn Delight;\n;5647;auto_login;自动登录指令;Auto Login Command;\n;5648;ro_ss;懒狗生存;Simple Survival;RO_SS\nblast-craft;5649;blastcraft;防爆工艺;Blastcraft;\nzdgx;5650;zdgx;mod自动更新;zdgx-AutoUpdateMod;\n;5651;polymc;PolyMc;;\ntinkers-construct-bedrock-edition;5652;;匠魂2：基岩版;Tinkers' Construct: Bedrock Edition;\nore-highlight;5653;light_ore;矿石发亮;Ore Highlight;\nsodium-shadowy-path-blocks;5654;sspb;钠 · 土径阴影;Sodium Shadowy Path Blocks;SSPB\n;5655;slashblade.twilighttsuki;暮色月光;Slashblade-TwilightTsuki;\npolymer;5656;polymer_bundled,polymer,polymer-bundled;Polymer;;\nelytra-bombing;5657;ebb;鞘翅轰炸;Elytra Bombing;EBB\ncuboiddroids-support-mod;5658;;CuboidDroid's Support Mod;;\n;5659;newyeartaste,firecracker;年味;firecrackers/New Year TASTE;\n;5660;liver_exhaustion_reincarnation;肝尽轮回;Liver Exhaustion Reincarnation;LER\nlitematica-tool;5662;litematicatool;投影工具;Litematica Tool;\nfrozen-up;5663;frozenup;千里冰封;Frozen Up;\ncarpet-tis-addition;5664;carpet-tis-addition;Carpet TIS Addition;;\n;5665;solomon;所罗门之钥;The Solomon Key;TSK\nmore-banner-features;5666;morebannerfeatures;更多旗帜功能;More Banner Features;MBF\nhorsestatsvanilla;5667;horsestatsvanilla;马匹状态;Horse Stats Vanilla;\nbiome-id-extender;5668;biomeidextender;Biome ID Extender;;BIDE\nrereskillable-reborn;5669;reskillable;Reskillable Reborn;;\nrereskillable-rereforked;5670;rereskillablerereforked;Rereskillable Rereforked;;\ntetra-pak;5671;tetrapak;Tetra Pak;;\nars-magica-legacy;5672;arsmagicalegacy;Ars Magica: Legacy;;\ncompressium;5673;compressium;Compressium;;\ntalking-villagers;5674;talking-villagers;会说话的村民;Talking Villagers;\nartifality;5675;artifality;Artifality;;\nboat-container;5676;boatcontainer;箱船;Boat Container;\ndelogger;5677;delogger;DeLogger;;\njust-another-vanilla-addon-java;5678;java;迫真原版增强;Just Another Vanilla Addon;JAVA\nspectrite-mod;5679;spectrite;彩虹;Spectrite;\ngenshin-nature;5680;genshin_nature;原神：自然;Genshin Nature;\npower-utilities-remastered;5681;powerutils;能源转换重制;Power Utilities Remastered;\ncreate-gear-addon;5682;creategearaddon;Create gear addon;;\ngun-mod-tl;5683;gm;Gun mod-Weapon update;;GMWU\n;5684;classification;分类仓库;;\nenergymeter;5685;energymeter;Energy Meter;;\nheadindex;5686;headindex;头颅菜单;Head Index;HI\nquantum-generators;5687;quantum_generators;量子发电机;Quantum Generators;\nsimply-quarries;5688;simplyquarries;简易采石场;Simply Quarries;\nlethal-peaceful;5689;lethal_peaceful;🐷Lethal Peaceful🔫;;\n;5690;pvz_mod;大方的山羊和道具的植物大战僵尸模组;Generous goats and props 's Plants Vs Zombies mod;\n;5691;kamen_rider_kuuga,kuuga_mod;假面骑士kuuga;;\nstorage-drawers-crafting-recipe;5692;sdcr;Storage Drawers Crafting Recipe;;\nenhanced-attack-indicator;5693;enhanced_attack_indicator;增强攻击提示器;Enhanced Attack Indicator;\nparasites-addon-overlast;5694;overlast;寄生兽：末世扩展;OverLast;\ncancel-block-update;5695;cancelblockupdate;取消方块更新;Cancel Block Update;CBU\nfranciscofunari;5696;medieval_craft;中世纪工艺;Medieval Craft;\nd3core;5697;d3core;D3Core;;\ngreen-thumb-v1-2-03;5698;GreenThumb;Green Thumb - Nature Overhaul;;\ngreenthumb;5699;GreenThumb;GreenThumb;;\ntfc-adventure;5700;tfc_adventure;群峦冒险;TFC+ Adventure;TFCA\njust-zoom-forge;5701;justzoom;Just Zoom;;JZ\nserver-holograms;5702;holograms;全息图;Holograms;\neidolons-hexblades;5704;hexblades;幻梦咒剑;Eidolon's Hexblades;\nmonke-madness;5705;primate;Monke Madness;;\nput-ingot-down;5706;putingotdown;把锭放在地上吧！;Put Ingot Down!;PID\ntorchtools;5707;torchtools;TorchTools;;\nlottweaks;5708;lottweaks;LotTweaks;;LT\nquests-additions-fabric;5709;questsadditions;任务拓展;Quests Additions;\ncustom-mob-spawner;5710;CustomSpawner;自定义生物生成器;Custom Mob Spawner;\nrpg-hud;5711;rpghud;RPG-Hud;;\nthe-wild-update-concept-mod;5712;wildupdate;The Wild Update Concept 1.19 Mod;;\n;5713;skylanterns;孔明灯重制版;Sky Lanterns Reload;\nsinocate;5714;cate;肴馔;SinoCate;SC\noreregen;5715;oreregen;再生矿石;OreRegen;\neidolon_tweaker;5716;eidolon_tweaker;Eidolon Tweaker;;\n;5717;darky_time;暗魔光阴 / 黑暗时光;Darkytime;\nherodotusutils;5718;hdsutils;HerodotusUtils;;HDSU\nplatypuses;5719;platypus;Platypuses;;\nhostile-villages;5720;hostilevillages;敌对村庄;Hostile Villages;\nthrowability;5721;throwability;投掷;Throwability;\nbiomeinfo;5722;biomeinfo;生物群系信息;BiomeInfo;\nclickable-advancements;5723;clickadv;可点击的进度;Clickable Advancements;\nfactory0-resources;5724;;Factory0 Resources;;\nepic-knights-armor-and-weapons;5725;magistuarmory;史诗骑士：盾牌，盔甲和武器;Epic Knights: Shields, Armor and Weapons;EK\nyoure-an-expert-harry;5726;youre_an_expert_harry;你是个专家，哈利！;You're an Expert, Harry!;YEAH\ndragon-mounts-3;5727;dragonmounts;龙骑士3;Dragon Mounts 3;DM3\nviaforge;5728;viaforge;ViaForge;;\nhappy-trails;5729;happytrails;快乐小径;Happy Trails;\nnaturesaura_tweaker;5730;naturesaura_tweaker;Natures Aura Tweaker;;\ndeath-finder;5731;deathfinder;死亡溯源;Death Finder;\n;5732;stattrak;Stattrak™ 计数器;Stattrak;\nportable-craft;5733;portablecraft;便携工艺;Portable Craft;\n;5734;polypack_host;Polypack Host;;\netched;5735;etched;Etched;;\ncaves-cliffs-backport-additions;5736;cavesandcliffsbackportadditions;Caves & Cliffs Backport (Additions);;\ncobblefordays;5737;cobblefordays;CobbleForDays;;\nadmin-shop;5738;;官方商店;Admin Shop;\nvanillamagic;5739;vanillamagic;纯净魔法;Vanilla Magic;\ndeep-mob-learning-simulacrum;5740;dmlsimulacrum;Deep Mob Learning: Simulacrum;;\noculus;5741;oculus;Oculus;;\nsimple-planes-tfc;5742;;Simple Planes for TFC;;\nmishang-urban-construction;5743;mishanguc;迷上城建;Mishang Urban Construction;\n;5744;api;Cursed Legacy API;;\nphi;5745;476728;Phi;;\nae2-utilities;5746;ae2utilities;AE2实用设备;AE2 Utilities;AE2U\nepic-psi;5748;epicpsi;Epic Psi;;\nmissingmagic;5749;missingmagic;Missing Magic;;\nenchanting-convergence;5750;enchanting_convergence;Enchanting Convergence;;\nmagical-psideas-model-replacer;5751;magipsi;Magical Psideas Model Replacer;;\npsio;5752;psicosts;Psio;;\n;5753;;Fabric Loader (Alpha 1.2.2a);;\n;5754;;Mod Menu (Alpha 1.2.2a);;\n;5755;extradebug;Extra Debug;;\n;5756;seedselector;Seed Selector;;\n;5757;movementhacks;Movement Hacks;;\ncopper-pot;5758;copperpot;铜锅！;Copper Pot!;\n;5759;bib;博物志;Bibliotheque;bib\n;5760;viaversion;ViaVersion;;VV\nviarewind;5761;viarewind;ViaRewind;;\nviabackwards;5762;viabackwards;ViaBackwards;;\nthaumic-isorropia;5763;isorropia;Thaumic Isorropia;;\nloading-manager;5764;loadingmgr;更多加载信息;Loading Manager;\npsitools;5765;psitools;PsiTools;;\npsionic-upgrades;5766;psionup;PSIonic Upgrades;;\npsionicolor;5767;psionicolor;Psionicolor;;\nreskillable-psi-compat;5768;rspcompat;Reskillable PSI Compat;;\nzettai-psi;5769;zettai_psi;Zettai Psi;;\npsicaster;5770;psicaster;PSICaster;;\ntweakermore;5771;tweakermore;TweakerMore;;\nlegacy-display;5772;legacy_display;Legacy Display;;\nthe-backrooms;5773;backrooms;The Backrooms;;\ncreatures-of-the-snow;5774;creatures_of_the_snow;雪地里的生物！;Creatures From The Snow!;CFTS\ncollisions-lib;5775;collisionslib;Collisions Lib;;\nwooled-boots;5776;wooledboots;羊毛靴子;Wooled Boots;\nzawa-evolved;5777;zawa;野生动物园：进化;Zoo and Wild Animals: Evolved;ZAWA\nbongo;5778;bongo;宾果游戏;Bongo;\nimpersonate;5779;impersonate;Impersonate;;\ncalemi-core;5780;calemicore;Calemi Core;;CCore\n;5781;zaomengxiyou;造梦西游模组;;\n;5782;skyland;AnvilCraft SkyLandMod;;AncSL\nvisual-workbench;5783;visualworkbench;可视化工作台;Visual Workbench;VW\nstylish-effects;5784;stylisheffects;Stylish Effects;;\ndeath-compass-forge;5785;deathcompass;死亡指南针;Death Compass;\nportable-stonecutter;5786;portable_stonecutter;便携式切石机;Portable Stonecutter;\n;5787;RecipeBook;Recipe Book;;\nenchanted-book-redesign;5788;enchantedbookredesign;Enchanted Book Redesign;;\nquick-hotbar;5789;quickhotbar;Quick Hotbar;;\nmodular-augment;5790;modularaugment;Modular Augment;;\nlumberjack;5791;lumberjack;Lumberjack;;\nbetter-mods-button;5792;bettermodsbutton;更好的模组按钮;Better Mods Button;\nadvancement-frames;5794;advancementframes;Advancement Frames;;\n;5795;qwxlgun;秋晚夕落的枪;Qwxl's guns;QG\ndynmapforge;5796;dynmap;Dynmap;;\nspellbind;5797;spellbind;物品魔法;Spellbind;\n;5798;mlt;多行提示框;Multi-line Tooltip;\nbumpkinbatch;5799;bumpkinbatch;BumpkinBatch;;\nbeekeeper;5800;bk;养蜂人;Beekeeper;BK\nsome-assembly-required;5801;some_assembly_required,someassemblyrequired;Some Assembly Required;;SAR\nladylucs-critters-mod;5802;ladys_critter_mod;LadyLucs' Critters Mod;;\n;5803;;ModMenu Beta;;\n;5804;quitbutton;Quit Button;;\nbetter-signs;5805;bettersigns;Better Signs;;\n;5806;glsl,macula;Macula / GLSL Shaders (Fabric Beta 1.7.3);;\n;5807;hmi;HowManyItems;;HMI\n;5808;hmifabric;HowManyItems Fabric;;\n;5809;manymoreblocks-test,manymoreblocks;ManyMoreBlocks;;\n;5810;gcapi;Glass Config API;;GCAPI\n;5811;translatedbeta;Translated Legacy;;\n;5812;shiftrightclick;Shift Right Click;;\n;5813;mpentityphysicsfix;MPEntityPhysicsFix;;\n;5814;ironchest;Iron chest SL;;\ncreate-irrigation;5815;createirrigation;Create Irrigation;;\ndelete-worlds-to-trash-forge;5816;deleteworldstotrash;Delete Worlds To Trash;;\n;5817;fabricloader;Cursed Fabric BTW;;\n;5818;betterterrain;Better Terrain Addon;;BTA\nsimple-magnets;5819;simplemagnets;简易磁铁;Simple Magnets;\nguns-without-roses;5820;gunswithoutroses;枪炮“没”瑰;Guns Without Roses;\nbuddycards-expansions;5821;buddycardsexp;巴迪卡牌扩展;Buddycards Expansions;\nzettai-ars;5822;zettaiars;Zettai Ars;;\ncant-sleep-clowns-will-eat-me;5823;cant-sleep-clowns-will-eat-me;夜不能寐;Can't Sleep Clowns Will Eat Me;\ncolony-cards;5824;colonycards;殖民地卡牌;Colony Cards;\nautoharvest;5825;autoharvest;自动收获;AutoHarvest;\nrealistic-block-physics;5826;rbp;Realistic Block Physics;;\ndomestication-innovation;5827;domesticationinnovation;驯养革新;Domestication Innovation;\nsnuffles;5828;snuffles;魄罗;Snuffles;\nflan-forge;5829;flan;Flan;;\nawesome-dungeon-the-end-forge;5830;awesomedungeonend;Awesome Dungeon The End edition;;\ntehnuts-torcherino;5831;torcherino;TehNut's Torcherino;;\nlibrary-ferret-forge;5832;libraryferret;Library Ferret;;\nawesome-dungeon-nether-forge;5833;awesomedungeonnether;Awesome Dungeon Nether edition;;\nae2-extended-life;5834;appliedenergistics2;应用能源非官方延续版;AE2 Unofficial Extended Life;AE2UEL\nrefined-cooking;5836;refinedcooking;精致厨房;Refined Cooking;\njade-addons;5837;jadeaddons;Jade Addons;;\nfeature-nbt-deadlock-be-gone;5838;feature_nbt_deadlock_be_gone;Feature NBT Deadlock Be Gone;;\n;5839;decoaddon;Deco Addon;;\nreal-infinity-bow-allow-inf-mending;5840;rib;真正的无限弓;Real Infinity Bow;\ndrink-it-forge;5841;drinkit;喝！;Drink It!;\nfoxlib;5842;foxlib;FoxLib;;\ntails;5843;Tails;Tails;;\nhowling-moon-rebooted-forge;5844;howlingmoon;Howling Moon - Rebooted;;\n;5845;24-hour-time-format;24-Hour Time Format;;\n;5846;knoothing;Knoothing;;\n;5847;dustrial_decor;'Dustrial Decor Fabric;;\nfurnish;5848;furnish;Furnish;;\niammusicplayer;5849;iammusicplayer;Iam Music Player;;IMP\nnotenoughheroes;5850;;NotEnoughHeroes;;\npirates;5851;pirates;海盗;Pirates;\ncomics-bubbles-chat;5852;comicsbubbleschat;Comics Bubbles Chat;;\nfabric-extended-armor-bars-forked;5853;extendedarmorbars;Extended Armor Bars Forked;;\neating-animation-forge;5854;eatinganimation,eatinganimationid;进食动画;Eating Animation;\nwaterdripsound;5855;waterdripsound;Drip Sounds;;\nbetter-horse-hud;5856;bhh;更好的骑马 HUD;Better Horse Hud;\nthermopolium;5857;thermopolium;汤饮铺;Thermopolium;THP\n;5858;taiga;匠魂合金附加重栽版;TAIGA Replant;TAIGAR\n;5859;auto-fishing;轻松钓鱼;Auto-fishing;\norderly-fabric-2-0;5860;orderly;Orderly 2.0;;\n;5861;orderly;Orderly (Fabric 1.17+);;\nentity-texture-features-fabric;5862;entity_texture_features;实体纹理特性;Entity Texture Features;ETF\nsculk-plus;5863;skulk-plus;Sculk Plus;;\ncreeper-confetti-fabric;5864;creeperconfetti;烟花苦力怕Fabric版;Creeper Confetti Fabric;\ndouble-jump-attribute;5865;doublejumpattribute;Double Jump Attribute;;\noverweight-farming;5866;overweight_farming;Overweight Farming;;\nhealthcare;5867;healthcare;HealthCare;;\nbetterloadingscreen;5868;better_loading_screen;加载画面改良;Better Loading Screen;\nfabric-title-changer;5869;ftc;Fabric Title Changer;;\nwindow-title-changer;5870;window_title_changer;Window Title Changer;;\n;5871;corelib;CoreLib;;\n;5872;betterlight;BetterLight;;\n;5873;colorfulfabric,bismuthlib;BismuthLib / Colorful Fabric;;\n;5874;betterleaves;Better Leaves;;\n;5875;bushyleaves;Bushy Leaves;;\nrobins-ruby-mod;5876;ruby_mod;Robins Ruby Mod;;\n;5877;betaloader;BetaLoader;;\n;5878;pouchofunknown;未知之袋：重置;Pouch of Unknown Reloaded;POU\njust-needed;5879;justneeded;Just Needed;;\nnodami-remake;5880;nodami_remake;伤害免疫机制移除重制版;NoDamIRemake;\nhad-enough-items;5881;;HEI物品管理器;Had Enough Items;HEI\nworldinfo;5882;world_info;WorldInfo;;\n;5883;bang;Benchworking Aesthetic Noise Generator;;BANG\nblock-helper;5884;mod_BlockHelper;Block Helper;;\n;5885;;Class Loader Fixer;;\n;5886;fontfixer;Font Fixer;;\ngregicprobe;5887;gregicprobe;Gregic Probe;;GP\n;5888;dsurround;Dynamic Surroundings: Fabric Edition Forked by ThexXTURBOXx;;\ntravelers-index;5889;travelers_index;Traveler's Index;;\n;5890;fd;未来装饰;FutureDecoration;FD\ndimension-data-fix;5891;dimension_data_fix;维度数据修复;Dimension Data Fix;\ngtce-bees;5892;gtcebees;GTCE 蜜蜂;GTCE Bees;\n;5893;IntermediaryMod;Intermediary;;\n;5894;;Java 8 compatibility patch;;\narmory-expansion-bits-and-pieces;5895;armoryexpansion-bitsandpieces;Armory Expansion - Bits And Pieces;;\nelectrostatic-tinkers-toys;5896;electrostatic;Electrostatic Tinker's Toys;;\nbanhammer;5897;banhammer;BanHammer;;\nslabgen;5898;slabgen;Slab Gen;;\npotioncraft;5899;potioncraft;PotionCraft;;\natlas-lib;5900;atlaslib;Atlas Lib;;\nblood-smeltery;5901;bloodsmeltery;Blood Smeltery;;\ntinkers-memes;5902;tmemes;Tinkers' MEMES;;\ntinkers-golems-addon;5903;golems_tcon;Tinkers' Golems Addon;;\ntinkers-addons-fork;5904;TinkersAddons;Tinkers' Addons(Fork) with Mod Support;;\nreagenchant;5905;;Reagenchant;;\n;5906;extraenchantments;额外附魔;Extra Enchantments;\nsim-u;5907;sim;模拟城市/模拟大都市;;Sim\n;5908;copper_and_crystal;铜与晶;Copper And Crystal;CAC\nthorium;5909;thorium;钍;Thorium;Th\ncreative-one-punch;5910;creativeonepunch;Creative One-Punch;;\nradon;5911;radon;氡;Radon;\nars-omega;5912;arsomega;Ars Omega;;\ngame-stages-conditions;5913;gamestageconditions;Game Stages Conditions;;\ntinkers-construct-tool-leveling-with-random;5914;liketechnikstinkertweaks;Tool Leveling with Random Modifiers;;\nodd-water-mobs;5915;oddwatermobs;Odd Water Mobs;;\nglowsquids-begone;5916;glowsquids-begone;Glowsquids Begone!;;\ntheundead;5917;undead,undead_revamp2;THE UNDEAD REVAMPED;;\nframedblocks;5918;framedblocks;框架方块;FramedBlocks;\nnekos-enchanted-books;5919;nebs;Neko's Enchanted Books;;\nistc;5920;istc;我说了算;I Said The Calculation;ISTC\n;5921;;Fycraft;;\ndracovita-farm-life;5922;dracovita-farm-life;Dracovita Farm Life;;\nno-enchant-cap;5923;noenchantcap;No Enchant Cap;;\n;5924;visions_block_mod;幻象;Visions Blocks;VB\nartisan-automation;5925;artisanautomation;工匠自动化;Artisan Automation;\nenchanted-wolves;5926;enchantedwolves;Enchanted Wolves;;\n;5927;atom_jcef;Atom-JCEF;;\nrain-growth;5928;rain-grow;Rain Growth;;\nscape-and-run-monstress;5929;srmonstress;逃逸：魔物娘;Scape and Run: Monstress;SRM\nraised;5930;raised;Raised;;\nresetchunks;5931;resetchunks;resetchunks;;\n;5932;swordcraftstory;铸剑物语;Sword Craft Story;\n;5933;;染料工坊;Dye Workshop;DW\nmoreblocksforyou;5934;moreblocksforyou;MoreBlocksForYou;;MBFY\ntrident-return;5935;tridentreturn;Trident Return;;\nbettertridentreturn;5936;bettertridentreturn;Better Trident Return;;\nmultibeds;5937;multibeds;MultiBeds;;\nenhanced-armaments-reload;5938;enhancedarmaments;增强装备重制版;Enhanced Armaments Reload;\nentangled;5939;entangled;Entangled;;\n;5940;water_is_highly_toxic;水是剧毒的！;;\ncreateplus;5941;createplus;CreatePlus;;CP\ncustom-fluid-mixin;5942;customfluidmixin;Custom Fluid Mixin;;\nslender-reimagined;5943;Slender;Slender – Reimagined;;\ndoors-by-ichun;5944;Doors;Doors;;\nslender-man-mod;5945;SlenderMan;Slender Man;;\ncookielicious;5946;cookielicious;曲奇佳味;Cookielicious;\nthe-forbidden;5947;theforbbiden;禁忌者;The Forbidden;\nmemory-usage-screen;5948;memoryusagescreen;Memory Usage Screen;;\nevergreenhud;5949;evergreenhud;EvergreenHUD;;\nnuit;5950;fabricskyboxes,nuit;Nuit / FabricSkyboxes;;\n;5951;level-border;Level = Border;;\n;5952;fvt;Flour's Various Tweaks;;FVT\nblue_skies_tcon;5953;blue_skies_tcon;Blue Skies Tinkers Compat;;\ntdv-tweaks;5954;tdv_tweaks;TDV Tweaks;;\npicture-in-picture-pip;5956;pip;Picture-in-Picture;;PiP\nguilt-trip;5957;guilttrip;Guilt Trip;;\nparcool;5958;parcool;跑酷！;ParCool!;\nlegacy-mc-kotlin;5959;legacymckotlin;Legacy MC Kotlin;;\nminimepets;5960;minimepets;MiniMePets;;\nugly-scoreboard-fix;5961;uglyscoreboardfix;Ugly Scoreboard Fix;;\n;5962;;Better Than Adventure!;;BTA\n;5963;smartmoving;Smart Moving Reloaded;;\n;5964;RenderPlayerAPIEnhancer;Render Player API Enhancer;;\nrailcrafttweaker;5965;railcraft_tweaker;Railcraft Tweaker;;\npig-grinder;5966;mod_PigGrinder;Pig Grinder;;\nphotoreal;5967;Photoreal;Photoreal;;\nkeygrip-mocap-mod;5968;keygrip;Keygrip – Mocap;;\n;5969;buddycam;BuddyCam;;\nattachable-grinder;5970;attachablegrinder;Attachable Grinder;;\nhat-stand;5971;HatStand;Hat Stand;;\nchunk-copy-fabric;5972;chunkcopy;Chunk Copy;;\ninventory-home;5973;inventoryhome;回归箱;Inventory Home;\nnether-hexed-kingdom;5974;netherhexedkingdom;Nether Hexed Kingdom;;\n;5975;;机械动力实用配方;Create Practical Recipes;\n;5976;pmfreborn;应急食品：重制;PaimonFood:Reborn;PFR\ntalking-ben-mod;5977;talking_ben;My Talking Ben;;\nin-backpack-eat;5978;in-backpack-eat;背包内食用;In-backpack Eat;IBE\nwith-thursday;5979;with_thursday;相约星期四;WithThursday!;\nsmooth-swapping;5980;smoothswapping;平滑转移;Smooth Swapping;\ntinkering-with-blood-magic;5981;bloodtinker;Tinkering with Blood Magic;;\ntinker-toolcasts;5982;tinkertoolcasts;Tinker Toolcasts;;\nthermal-tinkering;5983;thermaltinkering;Thermal Tinkering;;\ndeuf-duplicate-entity-uuid-fix;5984;deuf;Duplicate Entity UUID Fix;;DEUF\ncorundum-meadows;5985;corundum_meadows;Corundum Meadows;;\nmillenaire-extended-inuits;5986;millenaireextendedinuits;Millénaire Extended: Inuits;;\nmct-smeltery-io;5987;mctsmelteryio;Smeltery IO;;\nopencomputers-drivers-for-tinkers-construct;5988;tinkersoc;OpenComputers Drivers for Tinkers Construct;;\noedldoedl-construct;5989;oedldoedlconstruct;Oedldoedl Construct;;\nmillenaire-extended-byzantine;5990;millenaireextendedbyzantine;Millénaire Extended: Byzantine;;\nmillenaire-extended-normans;5991;millenaireextendednormans;Millénaire Extended: Normans;;\nmillenaire-extended-indians;5992;millenaireextendedindians;Millénaire Extended: Indians;;\nmillenaire-extended-maya;5993;millenaireextendedmaya;Millénaire Extended: Maya;;\noedldoedl-core;5994;oedldoedlcore;Oedldoedl Core;;\noedldoedl-resources;5995;oedldoedlresources;Oedldoedl Resources;;\noedldoedl-gear;5996;oedldoedlgear;Oedldoedl Gear;;\ndisguiseheads;5997;disguiseheads;DisguiseHeads;;\nsimple-tcon;5998;simpletcon;Simple Tcon;;\nenchanted-tooltips;5999;enchantedtooltips;Enchanted ToolTips;;\nstackablepotions-forge;6000;stackablepotions;StackablePotions;;\nmobifier;6001;mobifier;Mobifier;;\nart-of-forging-a-tetra-addon;6002;art_of_forging;锻造之艺;Art of Forging;AoF\nharder-staged-difficulty;6003;harder;Harder;;\nmaterial-changer;6004;materialchanger;Material Changer;;\ntree-harvester;6005;treeharvester,treeharvester-fabric;Tree Harvester;;\nitemfx;6006;itemfx;ItemFX;;IFX\nmekanism-ce;6007;Mekanism;通用机械：社区版;Mekanism Community Edition;MEKCE\n;6008;appliedenergistics2;应用能源2非官方版;Applied Energistics 2 Unofficial;\nproperty-modifier;6009;propertymodifier;Property Modifier;;\ngrimoire-of-tetra;6010;grimoire_of_tetra;Grimoire of Tetra;;\nprehistoric-delight-by-nocube;6011;prehistoric_delight,pre_delight;史前乐事;Prehistoric Delight;\n;6012;weaila;我正在看的是什么实体?;What entity am I looking at?;WEAILA\nsimple-stations;6013;simplestations;Simple Stations;;\nars-elemental-elemental-spell-foci;6014;ars_elemental;元素魔艺;Ars Elemental;\ntoo-many-glyphs;6015;toomanyglyphs;Too Many Glyphs;;\ncreate-cafe;6016;createcafe;机械动力：咖啡馆;Create Cafe;\n;6017;worldbordercolor;Recolorable World Border;;\nic2-rpg;6018;ic2rpg;工业RPG;IC2 RPG;\ndrop-the-meat;6019;dropthemeat;肉多多;Drop the Meat;\nbetter-invisibility;6020;betterinvisibility;更好的隐身;Better Invisibility;\ntweakerplus;6021;tweakerplus;TweakerPlus;;\nautorun-fabric;6022;autorun;AutoRun(Fabric);;\nfpv-drone;6023;fpvdrone;Minecraft FPV;;\ncampfire-backport;6024;campfirebackport;Campfire Backport;;\nskip-transitions;6025;skiptransitions;Skip Transitions;;\njust-load;6026;justload;Just Load;;JL\ncheese-dragon;6027;cheese_dragon;CheeseDragon;;\nboatjump;6028;boatjump;BoatJump;;\n;6029;;Invincible Difficulty;;\ntorched;6030;torched;Torched;;\nnyfs-quivers;6031;nyfsquiver;Nyf的箭袋;Nyf's Quivers;\nparty-parrots;6032;partyparrots;Party Parrots;;\npatina-pipeworks;6033;patinapipeworks;古铜管道;Patina Pipeworks;\nbrewevolution;6034;brewevolution;酿造进化🍺;Brewevolution 🍺;\ndevil-fruits;6035;devilfruits;恶魔果实;Devil Fruits;\n;6036;BetterFonts;BetterFonts Forked by Suzutsuki;;\ntrail-mix;6037;trailmix;Trail Mix;;\nmultiplayer-server-pause-forge;6038;serverpause;多人服务器暂停;Multiplayer Server Pause;\nnoshelter-reborn;6039;noshelter;无庇护所：重生;NoShelter Reborn;\ninitial-spawn-dimension;6040;isd;Initial Spawn Dimension;;ISD\nthe-one-probe-fabric;6041;theoneprobe;The One Probe Fabric;;TOPF\ngehenna-super-spooky-nether-overhaul-for-forge;6042;gehenna;Gehenna;;\ndraconicminus;6043;draconicplus;DraconicMinus-;;DM\nhygiene;6044;hygiene;Hygiene;;\nworldeditcui-forge-edition-3;6045;worldeditcuife3;WorldEditCUI Forge Edition 3;;\nexternal-view-reload;6046;externalview;External View Reload;;\nfluid-drawers-legacy;6047;fluiddrawerslegacy;储液抽屉：遗产;Fluid Drawers Legacy;\nmy-server-is-compatible;6048;myserveriscompatible;My Server Is Compatible;;\nmedieval-siege-machines;6049;siegemachines;中世纪攻城机器;Medieval Siege Machines;\nleapoffaith-forge;6050;lofaith;信仰之跃;Leap of Faith;LoF\ntop-plus;6051;top_plus;TOP Plus;;TOPP\nrefined-storage-fluxified;6052;refstoragefluxified;Refined Storage Fluxified;;\nquit-confirm;6053;quitconfirm;Quit Confirm;;\ncompressedcreativity;6054;compressedcreativity;压缩动力;Compressed Creativity;\napplied-mekanistics;6055;appmek;应用能源：通用机械附属;Applied Mekanistics;\ncraftdumper;6056;craftdumper;CraftDumper;;\nimmersive-weathering-fabric;6057;immersive_weathering;沉浸式风化;Immersive Weathering;IW\nreplay-mod-for-forge;6058;replaymod;Replay Mod for Forge;;\nbigtrees;6059;bigtrees;BigTrees;;\nabsolutely-not-a-zoom-mod;6060;anazm;绝对不是一个缩放模组;Absolutely Not A Zoom Mod;ANAZM\nglymworld-lush-caves;6061;gwlushcaves;Lush Caves;;\ncolored-crafting-stations;6062;coloredcraftingstations;Colored Crafting Stations;;\nharvest-scythes;6063;harvest_scythes;Harvest Scythes;;\ncroptosis;6064;croptosis;Croptosis;;\nweeping-angels-mod;6065;weeping_angels;Doctor Who - Weeping Angels;;\nhardcore-item-stages;6066;;Hardcore Item Stages;;\ngame-stages-viewer;6067;gamestagesviewer;Game Stages Viewer;;\nmpm-lotr;6068;mpmLotr;更多玩家模型：魔戒兼容;MPM LOTR;\nguard-villagers-fabric;6069;guardvillagers;警卫村民Fabric版;Guard Villagers (Fabric);\n;6070;server_cmd;服务器指令;Server Cmd;\nitem-highlighter;6071;highlighter;物品高亮;Item Highlighter;\n;6072;exstrangefood;更多奇怪的食物;ExStrangeFood;EXSF\nenvironmental-armor;6073;enva;Origins - Environmental Armor;;\n;6074;iconr;图标渲染;IconRenderer;IconR\nhealth-levels;6075;health_levels;Health Levels;;\nthe-comfort-zone;6076;thecomfortzone;The Comfort Zone;;\n;6077;zwtings;阿伍的东西;zwtings;ZWT\nmekatweaker;6078;mekatweaker;Mekatweaker;;\nsmithing-in-the-90s;6079;smithing;Smithing In The \"90's\";;\nembellished-enchanting;6080;embellishedenchanting;Embellished Enchanting;;\n;6081;hbmplus;HBM增强;hbmplus;HPs\nbetter-snowball-fight;6082;bsf,better-snowball-fight;更好的打雪仗;Better Snowball Fight;BSF\nonastick-forge;6083;onastick;⚒️ On A Stick;;\ntradernpcs;6084;tradernpcs;TraderNPCs;;\nmore-pads;6085;jump_pad;更多Pad;More Pads;\ntexture-dump;6086;texturedump;Texture Dump;;\nspartan-weaponry-ice-and-fire;6087;spartanfire;斯巴达的武器：冰火传说;Spartan Weaponry: Ice and Fire;SWIAF\nhefty-crops;6088;heftycrops;Hefty Crops;;\n;6089;strangefuelsmod;奇奇怪怪的燃料;Strange Fuels Mod;SFMod\ncustom-player-models;6090;cpm;自定义玩家模型;Customizable Player Models;CPM\nextrachampions;6091;extrachampions;强敌扩充;ExtraChampions;\n;6092;ReCreate;ReCreate;;\n;6093;CustomCursor;Custom Cursor;;\n;6094;godsenderpearl;上帝的末影珍珠;God's Ender Pearl;\nanother-origins-mod;6095;more_dragonkin,extra-extra-origins;Extra-Extra Origins;;\njust-another-void-dimension;6096;javd;Just Another Void Dimension;;JAVD\nplayer-weight-mod;6097;playerweight;玩家重量;Player Weight Mod;\nchunk-loaders;6098;chunkloaders;区块加载器;Chunk Loaders;\ntinkerstages;6099;tinkerstages;TinkerStages;;\n;6100;oneboxatatime;One Box At A Time;;\neccentric-tome-updated;6101;eccentrictome;怪奇宝典;Eccentric Tome;\narnicalib;6102;arnicalib;ArnicaLib;;\n;6103;meteor-client;彗星端;Meteor Client;\ngregicality-multiblocks;6104;gcym;Gregicality Multiblocks;;GCYM\n;6105;forcecloseloadingscreen;强制关闭加载屏幕;kennytv's epic force close loading screen mod for fabric;\nthe-box-trot;6106;boxtrot;The Box Trot;;\nbetter-biome-blend;6107;betterbiomeblend;更好的生物群系过渡;Better Biome Blend;BBB\nforever-enough-items;6108;FEI;FEI物品管理器;Forever Enough Items;FEI\nchess;6109;chess;象棋;Chess;\nimmersive-fx;6110;dsurround;Immersive Fx;;IFX\nydms-weapon-master;6111;weaponmaster,weaponmaster_ydm;YDM的武器大师;YDM's Weapon Master;\ngalacticrafttweaker;6112;galacticrafttweaker;Galacticraft Tweaker;;\nmoreleads;6113;moreleads;拴绳增强;More Leads;\n;6114;providencraft;普罗维登;Providencraft;PdC\nsimple-discord-rpc;6115;simple-rpc,simplerpc;Simple Discord RPC / Simple RPC;;\n;6116;;科洛的服务器支持-群服互联机器人;;\nexcore;6117;excore;Excore;;\nmessmod;6118;messmod;MessMod;;\nlog4j2-jndi-exploit-fix;6119;l4j_jndi_fix;Log4J2 JNDI Exploit Fix;;\nwoodcutter;6120;woodcutter;Woodcutter;;\norigins-classes-forge;6121;origins_classes;起源：职业非官方Forge版;Origins: Classes (Forge);\ntool-stats;6122;toolstats;Tool Stats;;\nthatched;6123;thatched;Thatched;;\nfumo-mod;6124;fumomod;Fumo Mod;;\nbasement-origins;6125;basementorigins;Basement Origins;;\nno-weak-attack;6126;noweakattack;No Weak Attack;;\nbettas;6127;bettas;搏鱼;Bettas;\nbaby-fat;6128;babyfat;婴儿肥;Baby Fat;\nquickplant;6129;quickplant;顷刻吐芽;QuickPlant;QP\ninfinite-dungeons;6130;infinite_dungeons;Infinite Dungeons;;\nwpo-addon-mod;6131;wpo_addon;WPO Addon Mod;;\njust-the-binding-of-isaac-pills;6132;just_the_binding_of_isaac_pills;Just the binding of isaac pills;;\npendant-of-life;6133;jmilpol;Pendant of Life;;\nthe-binding-of-isaac-tears-mod;6134;thebindingofisaactears;The binding of Isaac Tears Mod;;\nhumbling-bundle;6135;humblingbundle;慈善捆绑包;Humbling Bundle;\ngrindstone-sharper-tools;6136;grindstonesharpertools;Grindstone Sharper Tools;;\nnutritious-milk;6137;nutritiousmilk;Nutritious Milk;;\nextract-poison;6138;extractpoison;Extract Poison;;\nzombie-villagers-from-spawner;6139;zombievillagersfromspawner;Zombie Villagers from Spawner;;\nvine-climber;6140;vineclimber;Vine Climber;;\nhealing-soup;6141;healingsoup;Healing Soup;;\nidas;6142;idas;地牢建筑统合;Integrated Dungeons and Structures;IDAS\nmob-sunscreen;6143;mobsunscreen;Mob Sunscreen;;\n;6144;dontstarvereborn;不要饿死：重生;Don't Starve: Reborn;DTR\ndecorative-winter;6145;decorative_winter;Decorative Winter;;\nlooot;6146;looot;Looot;;\nblock-event-separator;6148;block-event-separator;Block Event Separator;;\nno-damage-immunity-reload;6149;;伤害免疫机制移除重制版;No Damage Immunity Reload;NoDamI\nsheve;6150;shelve;Shelve;;\nchromatic-currents;6151;chromatic_currents;Chromatic Currents;;\ndropfullcarts;6152;dropfullcarts;DropFullCarts;;\nare-you-blind;6153;areyoublind;Are You Blind!?;;\nalwayseat-reload;6154;;吃不停重制版;AlwaysEat Reload;AER\n;6155;herobrinemod;The Herobrine Mod;;\ntps-hud-fabric;6156;tpshud;TPS HUD;;\n;6157;thaumic_tinkerer_lite;神秘工匠精简版;Thaumic Tinkerer Lite;TTL\norcz;6158;orcz;Vanilla Expanded - Orcz;;\nsandwichable;6159;sandwichable;Sandwichable;;\n;6160;shelve;Shelve Refabricated;;\nmod-erate-loading-screen;6161;moderate-loading-screen;Mod-erate Loading Screen;;\nshrink_;6162;shrink;Shrink;;\ncable-tiers;6163;cabletiers;Cable Tiers;;\njumbo-furnace;6164;jumbofurnace;Jumbo Furnace;;\nno-villager-death-messages;6165;novillagerdm;No Villager Death Messages;;\ntank-null;6166;tanknull;/tank/null;;\ntrajans-tanks-forge;6167;trajanstanks;Trajan's Tanks;;\ncolds-easy-paxel-lite;6168;easypaxellite;Colds: Easy Paxel;;\n;6169;sei;SEI 物品管理器;Surely Enough Items;SEI\nsmooth-chunk-save;6170;smoothchunk;平滑区块保存;Smooth Chunk Save;\nrigoranthus-emortis;6171;rigoranthusemortisreborn;Rigoranthus Emortis Reborn;;\njei-utilities;6172;jeiutilities;JEI实用设备;JEI Utilities;JEIU\nplummet;6173;plummet;Plummet;;\nstackone-mod;6174;stackone;禁止堆叠;StackOne;\nspectrum;6175;spectrum;光谱世界;Spectrum;\nkitteh6660s-morecraft;6176;morecraft;Kitteh6660's MoreCraft;;\nangel-ring;6177;angelring;天使指环;Angel Ring;\ndebugify;6178;debugify;Debugify;;\nnomowanderer;6179;nomowanderer;NoMoWanderer;;\nallthecompressed;6180;allthecompressed;AllTheCompressed;;\ncave-enhancements;6181;ce;Cave Enhancements;;CE\ncreative-apiary;6182;creativeapiary;创造蜂场;Creative Apiary;\ncreative-crafter;6183;creativecrafter;Creative Crafter;;\nmcmod-search-reborn;6184;mcmodwiki,msr;MC百科资料搜索-重生;MCMOD Search Reborn;\nproxy-server;6185;proxyserver;Proxy Server;;\nmultithreaded-noise;6186;multithreadednoise;Multithreaded Noise;;\nspelling-table-mod;6187;spelling_table;拼写台;Spelling Table Mod;\n;6188;makecustomitem;打造自定义物品;MakeCustomItem;\nadvancedchatcore;6189;advancedchatcore;AdvancedChatCore;;\ndirt-deco;6190;dirt_deco;Dirt Deco;;\nmultiblocked;6191;multiblocked;Multiblocked;;mbd\narcanus-legacy;6192;arcanus;Arcanus: Legacy;;\narmor-weight;6193;armor_weight;Armor Weight;;\nall-the-tweaks;6194;allthetweaks;All The Tweaks;;\nrunelic;6195;runelic;Runelic;;\nalexs-cloud-storage;6196;cloudstorage;云存储;Cloud Storage;\nvisuality;6197;visuality;可视性;Visuality;\necologics;6198;ecologics;丰富的生态;Ecologics;\nmob-scarecrows;6199;mobscarecrow;Mob Scarecrows;;\ntrolls-community;6200;trolls;Trolls Community!;;\ng4mespeed;6201;g4mespeed;G4mespeed;;\nogres-community;6202;ogres;Ogres Community!;;\ngoblin-community;6203;goblins;Goblins Community!;;\nkobolds-community;6204;kobolds;Kobolds Community!;;\nmutant-villager;6205;mutantvillager;Mutant Villager!;;\npick-your-poison;6206;pickyourpoison;Pick Your Poison 🐸;;\nillager-expansion-fabric;6207;illagerexp;Illager Expansion;;\nimmersive-armors;6208;immersive_armors;沉浸式盔甲;Immersive Armors;\nseeds;6209;seeds;Seeds;;\nunusual-drill;6210;unusual_drill_mod;Unusual Drill;;\nfrosted-friends;6211;frosted_friends;Frosted Friends;;\nhover-pets;6212;hoverpets;飘浮宠物;Hover Pets;\nic2-patcher;6213;ic2patcher;IC2 Patcher;;\nuseless-sword;6214;useless_sword;朴华之剑;Useless Sword;\njei-professions;6215;jeiprofessions;JEI Professions;;\natm-additions;6216;atmadditions;ATM: Additions;;\nextra-tags;6217;extratags;额外标签;Extra Tags;\nadvancedchatlog;6218;advancedchatlog;AdvancedChatLog;;\ncrafting-station;6219;craftingstation;合成站;Crafting Station;\nsons-of-sins;6220;sons_of_sins;七罪之子;Sons Of Sins;\nadvancedchathud;6221;advancedchathud;AdvancedChatHUD;;\nfuzes-relics;6222;fuze_relics;Fuze's Relics;;\nchirpys-wildlife;6223;wildlife;Chirpy's Wildlife;;\nmegaparrot;6224;megaparrot;MegaParrot;;\nnourish;6225;nourish;Nourish;;\nvillage-box;6226;villagebox;村庄盒子;Village Box;VB\nwaila-stages;6227;wailastages;Waila Stages;;\nsteves-vanilla;6228;steves_vanilla;Steve's Vanilla;;\nduckling;6230;duckling;Duckling;;\nwandering-bag;6231;wandering_bag;Wandering Bags;;\nfriends-and-foes-forge;6232;friendsandfoes;Friends & Foes;;\ndynamic-trees-the-betweenlands;6233;dynamictreestbl;动态的树：交错次元附属;Dynamic Trees - The Betweenlands;\n;6234;gilded;装备镶金;Gilded;\npyrologer-and-friends;6235;pyrologernfriends;Pyrologer And Friends;;\ntree-hollows;6236;treehollows,tree-hollows;树洞;Tree Hollows;\nsolar-apocalypse-refabricated;6237;solar_apocalypse;Solar Apocalypse;;\nicommonlib;6238;icommon;iCommon;;\nchunky-pregenerator;6239;chunky;预生成区块;Chunky;\nbeast-slayer;6240;ancientbeasts;上古凶灵/远古野兽;Ancient Beasts/Beast Slayer;\neasyauth;6241;easyauth;EasyAuth;;\ndepth;6242;depth;Depth : Hidden World;;\nslimy-stuff;6243;slimy_stuff;Slimy Stuff;;\nnetheriteroad;6244;netherite_road;下界之路;Netherite Road;NeRo\norganics;6245;organics;Organics;;\nqsl;6246;quilted_fabric_api,quilt_base;Quilt Standard Libraries;;QSL\nkaliastyle;6247;carianstyle;卡利亚式附魔;CarianStyle;CS\n;6248;dacron;Dacron;;\nwmitaf;6249;wmitaf;What Mod Is This Actually From?;;WMITAF\ndepth-meter;6250;depthmeter;深度计;Depth Meter;\nomni-hopper;6251;omnihopper;Omni-Hopper;;\njumpy-boats;6252;jumpboat;Jumpy Boats;;\n;6253;pasterdream_beta;帕斯特之梦 旧版;PasterDream beta;\ncarpetclient;6254;carpetclient;CarpetClient;;\n;6255;kamenridertime;假面骑士时刻;Kamen Rider Time;KRT\ndynamic-trees-vampirism;6256;dtvampirism;动态的树：吸血鬼附属;Dynamic Trees - Vampirism;\ntranscend;6257;transcend;超越;Transcend;Tr\n;6258;cleanwater;Clean up The Water;;CTW\nconnectible-chains;6259;connectiblechains;Connectible Chains;;\n;6260;shiny_netherite;原版下界合金修改;ShinyNetherite;\nbirds-nests-fabric;6261;birdsnests;鸟巢 Fabric 版;Birds Nests (Fabric);\nhot-or-not-tfc;6262;hotornot;Hot or Not TFC;;\neye-of-dragons;6263;eyeofdragons;Eye of Dragons;;\npaperdoll;6264;paperdoll;纸娃娃;PaperDoll;\nlittle-logistics;6265;littlelogistics;Little Logistics;;\nshow-me-what-you-got;6266;smwyg;Show Me What You Got;;SMWYG\nherdspanic;6267;herdspanic;HerdsPanic;;\nextrasounds;6268;extrasounds;ExtraSounds;;\nbrooms;6269;broomsmod;Brooms;;\nthe-stormlight-archive-mod;6270;stormlight_mod;飓光志;The Stormlight Archive Mod;\nextrae;6271;extrae;ExtraE;;\nprojecte-integration;6272;projecteintegration;等价兼容;ProjectE Integration;PEI\nmekajs;6273;mekajs;MekaJS;;\nantique-dragons;6274;antiquedragons;Antique Dragons;;\nbadpackets;6275;badpackets;Bad Packets;;BP\n;6276;pineapple;梦见凤梨;Dream With Pineapple;DWP\nundead-army;6277;zombiearmy;Undead Army!;;\nironagefurniture;6278;ironagefurniture;Iron Age Furniture;;\nminekeamod;6279;minekea;Minekea;;\naeble;6280;aeble;AEble;;\ntrident-mod;6281;trident;三叉戟;Trident;\ncustom-professions;6282;spacecatcustomprofessions;自定义村民职业;Custom Villager Professions;\n;6283;minecraftbut_the_colorful_puzzle;MC，但是彩砖迷题;;MCBCP\nnextspring;6284;nextspring;化作春泥更护花;NextSpring;NS\npsionic-utilities;6285;psionicutilities;Psionic Utilities;;\nlucent;6286;lucent;Lucent;;\ntogglesneak;6287;togglesneak;ToggleSneak;;\niron-apples;6288;iron_apples;Iron Apples;;\nactually-additions-fluxified;6289;aafluxified;Actually Additions Fluxized;;\ndank-storage;6290;dankstorage;Dank Storage;;\npixelgamewizards-banded-torches;6291;pgwbandedtorches;Banded Torches;;\ndynamic-trees-byg;6292;dtbyg;动态的树：你将去的生物群系附属;Dynamic Trees - Oh The Biomes You'll Go;\n;6293;memoryfix;MemoryFix;;\ncomfort-magic;6294;homebodies_fantasy;Comfort: Magic;;\ncolored-lights-fabric;6295;colored_lights;Colored Lights;;\nbiomes-far-away;6296;simplybiomes;Biomes far away!;;\nbetter-runtime-resource-pack;6297;better_runtime_resource_pack,brrp_v1;更好的运行时资源包;Better Runtime Resource Pack;BRRP\nends-delight;6298;end_delight,ends_delight;末地乐事;End's Delight;\nnatures-starlight;6299;naturesstarlight;Nature's Starlight;;\nalloygery;6300;alloygery;Alloygery;;\ncustom-game-rules;6301;customgamerules;More Powerful Game Rules;;\nlava-walker;6302;lava_walker;Lava Walker;;\npretty-pipes;6303;prettypipes;更好的管道;Pretty Pipes;\n;6304;deathflashback;死亡回溯;Death Flashback;DF\nvictorymod;6305;victorymod;VictoryMod;;\ncryptic-assets-pack;6306;cryptic_assets_pack;Cryptic Assets;;\ndynamicregistries;6307;dynamicregistries;DynamicRegistries;;\namong-us-mobs;6308;amongusmobs;Among Us Mobs;;\namong-us;6309;amongus;在我们之中;Among Us;\nuniversal-ores-fabric;6310;universal_ores;Universal Ores;;\n;6311;cannon_fire;火炮;Cannon Fire;\nproject-copper;6312;addonscopper;Project Copper;;\npirates-and-looters-mod;6313;piratesandlootersreborn;Pirates And Looters Mod;;\n;6314;mudrock;泥岩;Mudrock;MR\nitemchat;6315;itemchat;ItemChat;;\n;6316;tooltips;屏幕工具条;ScreenTooltips;\nresident-evil-8-mod;6317;re8joymod,rejoymod;生化危机8;Resident Evil 8;RE8\nsolar-forge;6318;solarcraft,solarforge;Solar Craft;;\ncolored-allays;6319;coloredallays;染色悦灵;Colored Allays;\n;6320;;白金之星 TheWorld 招式还原;;\nphantasm;6321;phantasm;End's Phantasm;;\nemerald-craft-mod;6322;emeraldcraft;绿宝石工艺;Emerald Craft Mod;EC\nroughly-enough-professions-rep;6323;roughlyenoughprofessions;REI工作方块;Roughly Enough Professions;REP\nsophisticated-core;6324;sophisticatedcore;精妙核心;Sophisticated Core;\nedibles;6325;edibles;Edibles;;\nreach-fix;6326;reachfix;ReachFix;;\nlootjs-forge;6327;lootjs;LootJS: KubeJS Addon;;\nchat-sounds;6328;chatsounds;Chat Sounds;;\nadvancement-screenshot;6329;advancementscreenshot;Advancement Screenshot;;\nall-loot-drops;6330;alllootdrops;All Loot Drops;;\ndisc-jockey;6331;disc_jockey;Disc Jockey;;\nbetter-taskbar;6332;bettertaskbar;更好的任务栏;Better Taskbar;\nstorage-network;6333;storagenetwork;Storage Network;;\n;6334;cm_r2d;图形接口;Graphics Interface;GI\n;6335;cubist_texture;立体化纹理;Cubist Texture;\nsnr-more-metal;6336;snrmoremetal;SNR More Metal;;\ngate-of-babylon;6337;gateofbabylon;王之财宝;Gate of Babylon;\n;6338;;天境数据包;The Aether Datapack;\nevocation;6339;evocation;Evocation;;\nthe-illusioners-cabin;6340;;The Illusioner's Cabin;;\ngw-pillager-outpost;6341;pillageroutpost;GW Mods | Pillager Outpost;;\nmercs-and-menaces;6342;mercsandmenaces;Mercs And Menaces;;\nillage-and-spillage;6343;illageandspillage;Illage and Spillage;;\nmore-illagers;6344;;more illagers!!!;;\nprobably-chests;6345;probablychests;Probably Chests;;\nillager-plushies;6346;illager_plushies;Illager Plushies;;\npillageplunder;6347;pillageplunder;PillagePlunder;;\nextended-illagers-mod;6348;eim;Extended Illagers Mod;;\nmidnightcontrols;6349;midnightcontrols;MidnightControls;;\nwonderful-enchantments;6350;majruszsenchantments;Majrusz's Enchantments;;\na-lot-of-useless-stuff;6351;a_lot_of_useless_stuff;A Lot Of Useless Stuff;;\nlankaster-origins;6352;lank;Lankaster's Origins;;\n;6353;minc.hfn;隐藏展示框名称;HideFrameName;HFN\n;6354;eldenmessage;谏言;Elden Message;\npriority-target;6355;prioritytarget;优先目标;Priority Target;PT\nadvanced-botany;6356;AdvancedBotany;高级植物学;Advanced Botany;AB\ncharred;6357;charred;Charred;;\nincorporeal-3;6358;incorporeal;幻想多媒体3;Incorporeal 3;\n;6359;minc;禁用原版进度与合成配方;Disable Vanilla;DV\n;6360;oceanite;海洋合金;Oceanite;\noxygen;6361;Oxygen,oxygen;氧;Oxygen;\nimmersive-combat;6362;bettercombatmod;沉浸式战斗;Immersive Combat;\n;6363;essential-client;EssentialClient;;\nmystical-index;6364;mystical_index;Mystical Index;;\ncombat-commons;6365;combat_commons;Combat-Commons;;\ncorail-woodcutter;6366;corail_woodcutter;Corail Woodcutter;;\n;6367;;Babric;;\nnolocalizationconflict;6368;nolocalizationconflict;本地化冲突消除;NoLocalizationConflict;\nenhanced-celestials-mobifier-integration;6369;enhanced_celestials_mobifier_integration;Enhanced Celestials Mobifier Integration;;\ntwilightdecor;6370;twilightdecor;Twilight Decor;;\nmidas-hunger-fabric;6371;midashunger;Midas Hunger;;\nfiresafety;6372;firesafety;安全用火;FireSafety;FS\npretty-pistons;6373;prettypistons;Pretty Pistons;;\nmore-beautiful-trapdoors;6374;moretrapdoor;More Beautiful Trapdoors;;\nproject-expansion;6375;projectexpansion;Project Expansion;;\nthe-corners;6376;corners;The Corners;;\ndeep-mob-learning-reforged;6377;dmlreforged;深度怪物学习：重铸;Deep Mob Learning: Reforged;DMLR\nzenith;6378;zenith;Zenith;;\nextended-drawers;6379;extended_drawers;Extended Drawers;;\nequippable-utilities;6380;supplementaryaccessories;Equippable utilities;;\nnever-enough-currency-2;6381;currency;货币多多2;Never Enough Currency 2;NEC2\nquartz-chests;6382;quartzchests;石英箱子;Quartz Chests;\nfrozen-delight;6383;frozen_delight;冰封乐事;Frozen Delight;\n;6384;mossfrp,mossfrpforgesupport,mossfrpfabricsupport;Moss内网穿透;MossFrp;\nclean-creative-mod;6385;clean_creative;Clean Creative;;\nthis-rocks;6386;rocks;这就是石粒！;This Rocks!;\nmotschens-puddles;6387;puddles;Motschen's Puddles;;\ndense-ores-reborn;6388;dense;致密矿石：重生;Dense Ores Reborn;\n;6389;inventoryring;纳戒;Inventory Ring;\ntfctreatedwood;6390;tfctreatedwood;群峦·防腐木;TFC Treated Wood;\nretro64;6391;retro64;Retro64;;\ndouble-hotbar;6392;double_hotbar;双重快捷栏;Double Hotbar;\nlavatech;6393;lavaplus;熔岩科技;Lava Tech;\npineapple-psychic;6394;paster_fix,pineapple_psychic;凤梨通灵术;Pineapple Psychic;PPc\nfragile-torches;6395;fragiletorches;Fragile Torches;;\n;6396;timechanger;Time Changer;;\ncome-closer;6397;;Come Closer;;\ncreeper-chain-reaction;6398;creeperchainreaction;Creeper Chain Reaction;;\ntiffitlib;6399;tiffitlib;TiffitLib;;\nsanity;6400;sanity;Sanity;;\n;6401;firesafer;更安全的消防;Fire Safer;\ndynamic-trees-the-aether-legacy;6402;dynamictreestheaether;动态的树：天境附属;Dynamic Trees - The Aether Legacy;\ndynamic-trees-cuisine;6403;dynamictreescuisine;动态的树：料理工艺附属;Dynamic Trees - Cuisine;\nancient-classes;6404;ancientclasses;Ancient Classes;;\nbigger-stacks;6405;biggerstacks;Bigger Stacks;;\nsilence;6406;silence;快速静音;Silence!;\ndynamic-trees-extra-utilities-2;6407;dynamictreesextrautils2;动态的树：更多实用设备2附属;Dynamic Trees - Extra Utilities 2;\nplatforms;6408;platforms;Platforms;;\neasierchests;6409;easierchests;简便箱子;EasierChests;\ncrackers-wither-storm-mod;6410;witherstormmod;Cracker的凋灵风暴;Cracker's Wither Storm Mod;CWSM\nore-canes-mod;6411;ore_canes;Ore Canes;;\nspartan-defiled;6412;spartandefiled;Spartan Defiled;;\nmcmtfabricce;6413;mcmtce;Minecraft多线程增强Mod兼容性增强分支;Minecraft Multi-Threading Mod Fabric Port Compatibility Enhancement;MCMTCE\ndynamic-trees-roots;6414;dynamictreesroots;动态的树：根源魔法附属;Dynamic Trees Roots;\ndynamic-trees-voyage;6415;dtvoyage;动态的树：Voyage附属;Dynamic Trees - Voyage;\nmystical-biomes;6416;mysticalbiomes;Mystical Biomes;;\n;6417;heartblade;心之所向;HeartBlade;\nforgero;6418;forgero;Forgero;;\ndisenchantingforge;6419;disenchanting;Disenchanting;;\nredstone-pen;6420;redstonepen;红石笔;Redstone Pen;\nae2-things-forge;6421;ae2things;AE2 Things;;\navaritia-endless;6422;endless;无尽贪婪·伪;Avaritia Endless;\ndynamic-trees-jurassicraft;6423;dynamictreesjurassicraft;动态的树：侏罗纪时代附属;Dynamic Trees - JurassiCraft;\ndynamic-trees-natures-aura;6424;dtnaturesaura;动态的树：自然灵气附属;Dynamic Trees - Nature's Aura;\ndynamictreesplus;6425;dynamictreesplus;动态的树：拓展;Dynamic Trees +;\ndynamic-trees-forbidden-arcanus;6426;dtforbiddenarcanus;动态的树：禁忌与奥秘附属;Dynamic Trees - Forbidden And Arcanus;\ndynamic-trees-terraforged;6427;dtterraforged;动态的树：TerraForged附属;Dynamic Trees - TerraForged;\ndynamic-trees-corvus;6428;dynamictreescorvus;动态的树：Corvus附属;Dynamic Trees - Corvus;\ndynamic-trees-mystical-biomes;6429;dtmysticalbiomes;Dynamic Trees - Mystical Biomes;;\ncorvus;6430;corvus;Corvus;;\ngoat-horns-copper-horns;6431;goat_horns_mod;山羊角与铜制号角;Goat Horns & Copper Horns;\nmystical-powers;6432;mysticalpowers;Mystical Powers;;\n;6433;;Fabric Is Too Easy;;FITE\ndamp;6434;damp;受潮;Damp;\nwooden-bucket;6435;woodenbucket;木桶;Wooden Bucket;\ncompressed-barrels;6436;compressed_barrels;压缩木桶;Compressed Barrels;CBs\n;6437;possessblocks;附身方块;Possess Blocks;\nadvanced-netherite;6438;advancednetherite;高级下界合金;Advanced Netherite;\nre-avaritia;6439;avaritia;无尽贪婪：重生;Re:Avaritia;\nsimply-conveyors-more;6440;simplyconveyors;简易传送带;Simply Conveyors & More;\nporting-lib;6441;porting_lib;Porting Lib;;\nexpanded-delight;6442;expandeddelight;扩充乐事;Expanded Delight;\nspell-bundle;6443;spellbundle;Spell Bundle;;\nnightmareesm;6444;nightmareinminecraft;Nightmare Epic Siege;;\nsimply-no-shading;6445;simply-no-shading;Simply No Shading;;\nhexcasting;6446;hexcasting;咒法学;Hex Casting;HC\npaucal;6447;PAUCAL;PAUCAL;;\nuneffectual;6448;uneffectual;Uneffectual;;\nloot;6449;lootplusplus;Loot++;;\ncrafting-dead-immerse;6450;craftingdeadimmerse;行尸走肉：沉浸;Crafting Dead: Immerse;\n;6451;witchery;巫术：新生;Witchery: Resurrected;\nextended-clouds;6452;extendedclouds;Extended Clouds;;\ndistinct-damage-descriptions;6453;distinctdamagedescriptions;Distinct Damage Descriptions;;DDD\ntechnicalities-lib;6454;tklib;Technicalities: Lib;;TKLib\nfantasys-furniture;6455;fantasyfurniture;Fantasy的家具;Fantasy's Furniture;\ntouhou-spell-card-additional;6456;SpellCardPlusMod;东方符卡附加包;Touhou Spell Card Additional Pack;\nregistrator;6457;registrator;Registrator;;\napexcore;6458;apexcore;ApexCore;;\nmagiclib;6459;magiclib;魔法类库;MagicLib;\ncull-less-leaves;6460;cull-less-leaves;更好的树叶渲染优化;Cull Less Leaves;\n;6461;exstellae;繁星之凝;Ex Stellae;ExS\nwith-craft;6462;with_fire;手工艺;With Crafts;\nfluidconverters;6463;fluidconverters;流体转化器;FluidConverters;\nmultiblock-stages;6464;multiblockstages;MultiBlock Stages;;\nmetallurgylegend;6466;metallurgy_legend;冶金传奇;Metallurgy Legend;\nwaterskin;6467;waterskin;WaterSkin;;\nrealism-thirst;6468;thirst;Realism Thirst;;\ndungeons-libraries;6469;dungeons_libraries;Dungeons Libraries;;\n;6470;easy_gun;简易枪械;Easy Gun;EG\n;6471;fabricated-forge;Fabricated Legacy Forge;;\nraknetify;6472;raknet-fabric;Raknetify;;\nvmp-fabric;6473;vmp-fabric,vmp;Very Many Players;;VMP\nflotage;6474;flotage;漂浮物;Flotage;Flo\ndynamic-trees-for-hungteens-plants-vs-zombies-mod;6475;dtpvz;动态的树：HungTeen的植物大战僵尸附属;Dynamic Trees for PVZ;\nantidrop;6476;antidrop;防丢;AntiDrop;AD\nelemental-items;6477;elementalitems;Elemental Items;;\n;6478;zenless_zone_zero;绝区零;Zenless Zone Zero;ZZZ\nonekeyminer;6479;onekeyminer;一键矿工;OneKeyMiner;OKM\nboathud;6480;boathud;Boat Hud;;\nninjaxxs-needs;6481;ninjaxxs_needs;Ninjaxx's Needs;;\npixelmon-gui-blocks;6482;jpan_Pixelmon_gui_blocks,pixelmon_gui_blocks;Pixelmon GUI Blocks;;\n;6483;;Nether Castles;;\nmore-mekanism-processing;6484;moremekanismprocessing;More Mekanism Processing;;\nitem-banning;6485;itemblacklist;Item Banning;;\nprobejs;6486;probejs;ProbeJS;;\nimpaled;6487;impaled;更多三叉戟;Impaled🔱;\nlaserio;6488;laserio;LaserIO;;\nvillager-tools;6489;villagertools;Village Artifacts;;\n;6490;trifle;琐物;Trifle;\nmcsa;6491;mcsaforge;MC Story Mode Armors;;MCSA\nno-resource-pack-warnings;6492;no-resource-pack-warnings;No Resource Pack Warnings;;\ntake-it-slow;6493;takeitslow;Take it Slow;;\nslime-mimic;6494;slime_mimic;Slime Mimic;;\n;6495;decay;变质;Decay;DCY\n;6496;Easter Egg Mod;复活节彩蛋;EasterEgg;\n;6497;tactical_cards;战术核显卡;Tactical Card;\nldlib;6498;ldlib;LowDragLib;;LDLib\nshimmer;6499;shimmer;微光;Shimmer;\nducts;6500;ducts;Ducts;;\n;6501;baguette;Baguette;;\n;6502;cc2001_floo_powder;飞路粉;Floo Powder;\nnosiphus-furniture-mod;6503;nfm;Nosiphus Furniture Mod;;NFM\ntf2-engineer-buildings-mod;6504;tf2engineer;TF2 Engineer Buildings Mod;;\nkubejs-botania;6505;kubejs_botania;KubeJS Botania;;\nadvancements-enlarger;6506;advancements-enlarger;Advancements Enlarger;;\nbetter-muffling;6507;bettermuffling;更好的消音器;Better Muffling;\nlightest-lamps;6508;lightestlamp;Lightest Lamps;;\nbigbuckets;6509;bigbuckets;大铁桶;Big Buckets;\ndimstorage;6510;dimstorage;DimStorage;;\nspawner-imbuer;6511;;Spawner Imbuer;;\n;6512;nilloader;NilLoader;;0L\nunnamed-delight;6513;unnamed_delight;无名乐事;Unnamed Delight;\n;6514;aleph;Aleph;;\nmonsters-girls-mushroom-update;6515;monsters_and_girls;Monsters&Girls;;\nftb-tutorial-mod-forge;6516;ftbtutorialmod;FTB Tutorial Mod;;\nmc-terrain-editor;6517;MCTE;MC Terrain Editor;;MCTE\nextra-gems;6518;extragems;额外的宝石;ExtraGems;\nvarious-world;6519;various_world;Various World;;\nbetter-impaling-forge;6520;better_impaling_forge;更好的穿刺Forge版;Better Impaling Forge;BIF\nbucketlib;6521;bucketlib;BucketLib;;\naquatic-torches;6522;aquatictorches;Aquatic Torches;;\nbetter-sponges;6523;bettersponges;更好的海绵;Better Sponges;\neasyexcavate;6524;easyexcavate;EasyExcavate;;\nwooden-shears;6525;woodenshears;木剪刀;Wooden Shears;\nbrick-furnace;6526;brickfurnace;砖熔炉;Brick Furnace;\nbrick-hopper;6527;brickhopper;砖漏斗;Brick Hopper;\nhuman-companions;6528;humancompanions;Human Companions;;\nodysseyhud;6529;odysseyhud;OdysseyHUD;;\ncolorblindness;6530;colorblindness;ColorBlindness;;\naether-addon;6531;aether_legacy_addon;Aether Continuation;;\nmoonstonemod;6532;moonstone,moonstonemod;月之石;MoonStone;\nserver-redirect;6534;serverredirect;ServerRedirect;;\ncity-blocks;6535;cityblocks;City Blocks;;\nuseful-hats;6536;usefulhats;Useful Hats;;\n;6537;panorama_changer;全景图切换器;Panorama Changer;\nmodern-splash;6538;modernsplash;高版本加载界面;Modern Splash;\nbabel;6539;babel;Babel;;\ncultural-treasures-paintings;6540;cultural_treasures_paintings;文化瑰宝 - 画作与乐曲;Cultural Treasures - Paintings and Music;\nkitchen-karrot;6541;kitchenkarrot;胡萝卜厨房;Kitchen Karrot;\nservercore;6542;servercore;ServerCore;;\nivan-carpet-addition;6543;ivan-carpet-addition;Ivan Carpet Addition;;\nfood-funk;6544;;Food Funk;;\nwumpleutil;6545;wumpleutil;Wumple Util Library;;\ntwilight-delight;6546;twilightdelight;暮色乐事;Twilight Delight;\nrecruits;6547;recruits;Villager Recruits;;\nthe-true-backrooms;6548;the_backrooms;The True Backrooms;;\nwebstreamer;6549;webstreamer;WebStreamer;;\nop-items-tab;6550;opitems;Op Items Tab;;\nthaumisc;6551;keletupack;神秘杂项学;Thaumisc;\n;6552;NewZombieMod;New Zombie;;\n;6553;playerattributes;玩家属性点;PlayerAttributes+;\ntreechop;6554;treechop;HT's TreeChop;;\nidwtialsimmoedm;6555;idwtialsimmoedm;idwtialsimmoedm;;\nentity-collision-fps-fix;6556;entitycollisionfpsfix;Entity Collision FPS Fix;;\nhollowwoods;6557;hollowwoods;HollowWoods;;\n;6558;a_lot_of_respawn;更多不死图腾;;\ndynamic-crosshair;6559;dynamiccrosshair;动态准星;Dynamic Crosshair;\nraiders;6560;raiders;Raiders;;\nbetter-animals-plus-plus;6561;betteranimalsplus;Better Animals Plus Plus;;\nblock-overlay-fix;6562;blockoverlayfix;Block Overlay Fix;;\nmini-effects;6563;minieffects;Mini Effects;;\nnature-overhaul;6564;NatureOverhaul;Nature Overhaul;;\nae2-fluid-crafting-rework;6565;ae2fc;AE2流体合成套件重置;AE2 Fluid Crafting Rework;AE2FC\n;6566;swordcraft;剑工艺;SwordCraft;SC\n;6567;;神秘时代4：重制版;Fabric Thaumcraft Rewrited;\nmoreculling;6568;moreculling;More Culling;;\nbetter-village-forge;6569;bettervillage;更好的村庄;Better Villages;\ntrinkets-curios-theme;6570;trinkets-curios-theme;Trinkets Curios Theme;;\nbanilla-claws-forge;6571;vanilla_claws;Banilla Claws/Vanilla Claws;;\n;6572;chinese_charactoers_survival;汉字生存;Chinese Characters Survival;CCS\ntransparent-cosmetics;6573;transparent_cosmetics;Transparent Cosmetics;;\nmore-geodes-reforged;6574;geodes;More Geodes;;\nright-click-get-crops;6575;right_click_get_crops;Right Click, Get Crops;;\noh-my-gourd;6576;omgourd;Oh My Gourd;;\nbetter-block-sounds;6577;bbs;Better Block Sound;;BBS\n;6578;DimensionAntiCheat,ciyuananticheat,CiYuanAntiCheat;次元反作弊;DimensionAntiCheat;DAC\nv0ids-smart-backpacks;6579;v0idssmartbackpacks;V0id的智能背包;V0id's Smart Backpacks;\n;6580;more_lens;更多魔力透镜;More Lens;\nwinterly;6581;winterly;银装素裹;Winterly;\nforgetmechunk;6582;forgetmechunk;ForgetMeChunk;;\n;6583;moremusic;More Music;;\n;6584;mobgrows;怪物成长;MobGrows;\nbetter-f1;6585;betterf1;更好的F1;Better F1;\nremovehudbutnothand;6586;removehudbutnothand;RemoveHUDbutNotHand;;\ntool-part-stages;6587;toolpartstage;Tool Part Stages;;\n;6588;;简单的RPG属性加点;;ERPG\niron-backpacks-redone;6589;ironbackpacksredone;特殊背包重做版;Iron Backpacks Redone;\nb0bgarys-biome-remover-v-1-0-0-1;6590;BiomeRemove;B0bGary的生物群系禁用器;B0bGary's Biome Remover;\ndualwielding;6591;dualwielding;双持;DualWielding;\nmalum-quilt;6592;malum;Malum (Quilt);;\nmemoryleakfix;6593;memoryleakfix;内存泄漏修复;Memory Leak Fix;\n;6594;axolotlviewer;AxolotlViewer;;\n;6595;compactchat,compact-chat;紧凑聊天;Compact Chat;\nlanguage-reload;6596;languagereload;Language Reload;;\n;6597;fixusername;用户名修复;FixUsername;\n;6598;firesource;火源;FireSource;FS\n;6599;block-entity-extended-rendering;Block Entity Extended Rendering;;BEER\nsound-categories;6600;soundcategories;Sound Categories;;\n;6601;unsuspicious-stew;Unsuspicious Stew (Reloaded);;\npreventer;6602;preventer;Preventer;;\n;6603;wpit;Whose Pet Is That?;;WPIT\nstarflight-innovation;6604;space;Starflight Innovation;;\npowah-rearchitected;6605;powah;Powah! Rearchitected;;\n;6606;mouserate-framerate;Mouserate Framerate;;\npickup-notifications;6607;pickupnotifications;Pickup Notifications;;\n;6608;practicalbarrels;Practical Barrels;;\nbetter-rocks;6609;betterrocks;Better Rocks;;\nhorse-combat-controls;6610;horsecombatcontrols;Horse Combat Controls;;\nlibgui;6611;libgui;LibGui;;\nsimply-houses;6612;simply_houses;Simply Houses;;\nyungs-better-desert-temples;6613;betterdeserttemples;YUNG的沙漠神殿优化;YUNG's Better Desert Temples;\nmodern-dynamics;6614;moderndynamics;现代动力学;Modern Dynamics;MD\ndoggo-mod;6615;doggomod;Doggo Mod;;\ntan-huge-trees;6616;tans_huge_trees_1165;Tan's Huge Trees;;THT\n;6617;wiangdtbisadtn;草我本来不想这么做的但是我这么做了啊啊啊啊啊啊啊啊啊啊啊啊啊啊;Wait I Am Not Gonna Do This But I Somehow Accidentally Did This Nooooooooooooooo;wiangdtbisadtn\nyungs-better-witch-huts;6618;betterwitchhuts;YUNG 的沼泽小屋优化;YUNG's Better Witch Huts;\npanorama-creator;6619;spanorama;全景图创建器;Panorama Creator;\nui-input-undo-fabric;6620;uiinputundo;UI Input Undo;;\npaimeng;6621;paimeng;应急食物;PaiMeng;\nnyfs-quivers-expanded;6622;nyfsquiversexpanded;Nyf's Quivers Expanded;;\n;6623;water;水桶获取非流体源;;\nzoomify;6624;zoomify;Zoomify;;\ngolemania;6625;golemania;Golemania;;\nvulkanmod;6626;vulkanmod;VulkanMod;;\nsledgehammer;6627;sledgehammer;Sledgehammer;;\nquilt-loading-screen;6628;quilt_loading_screen,quilt-loading-screen;Quilt 加载画面;Quilt Loading Screen;\namethyst-imbuement;6629;amethyst_imbuement;紫晶魔艺;Amethyst Imbuement;AI\nemi;6630;emi;EMI;;\nnuclear-contraptions;6631;nuclear_contraptions;Nuclear Contraptions;;\nfire-crafting;6632;firecrafting;烈焰配方;FireCrafting;\ntooltiptweaks;6633;tooltiptweaks;提示框微调;Tooltip Tweaks;\n;6634;shulker;Cursed Shulkers;;\nmore-babies;6635;more_babies;More Babies;;\n;6636;save-my-bed;Save My Bed!;;\ndata-reload;6637;datareload;Data Reload;;\nwinver;6638;winver;Winver;;\narmor-visibility;6639;armor_visibility,armour_visibility;Armor Visibility;;\n;6640;;电摇嘲讽;GrooveBattle;GB\nthe-chicken-came-first;6641;tccf;先有鸡;The Chicken Came First;\nhardcore-dungeons;6643;hardcoredungeons;Hardcore Dungeons;;HCD\nblock-relocation-forge;6644;block_relocation;Block Relocation;;\nnorecipebook-fabric;6645;norecipebook;没有配方书Fabric版;NoRecipeBook (Fabric);\n;6646;sort;Sort;;\nfpsdisplay;6647;fpsdisplay;FPS - Display;;\n;6648;mystuff;Where Is My Stuff;;\nmicrodurability;6649;microdurability;microDurability;;\nindicatia;6650;indicatia;Indicatia;;\nfishy-delight-a-farmers-delight-add-on;6651;fishy_delight;Bettas Delight;;\nsimple-delights;6652;simpledelights;Simple Delights;;\ncolor-me-outlines;6653;colormeoutlines;为轮廓上色;Color Me Outlines;\nnbttooltips;6654;nbttooltips;NBTtooltips;;\n;6655;mi;无相接口;MirageInterface;MI\nupstream;6656;upstream;Upstream;;\nastemirs-fireflies;6657;firefliesmod;Astemir's Fireflies;;\n;6658;cabrically;Cabrically;;\nender-crop;6659;endercrop;Ender Crop;;\nmodupdater;6660;modupdater;ModUpdater;;\nfivehead;6661;fivehead;FiveHead;;\nattained-drops;6663;attainedDrops;可种植掉落物;Attained Drops;\nattained-drops-2;6664;attained_drops;可种植掉落物 2;Attained Drops 2;\n;6665;;多世界优先加载优化;;\nauth-me;6666;authme;Auth Me;;\nrefinedcreativeinventory;6667;refinedcreativeinventory;创造模式物品栏重制;RefinedCreativeInventory;RCI\n;6668;XRayTweaker,XRayForgeTweaker;XRay;;\nrunesword;6669;runesword;Runesword;;\njcm;6670;jsblock;常磐装饰;Joban Client Mod;JCM\nautopackager;6671;autopackager;AutoPackager;;\ndynamx;6672;dynamxmod;DynamX;;\nmob-cards;6673;mob_cards;Mob Cards;;\nmavm;6674;mavm;More Axolotl Variants Mod;;\n;6675;waxednotwaxed;Waxed Not Waxed;;\nhidden-names;6676;hiddennames;隐藏名称;Hidden Names;\ndoph;6677;doph;远程命中提示;Ding On Projectile Hit;Doph\nkeybinds;6678;keybindsgalore;Keybinds Galore;;\nhat-list;6679;hatlist;Hat List;;\nnmuk;6680;nmuk;No More Useless Keys;;NMUK\nraven-brews-core;6681;ravenbrewscore;Raven Brews Core;;\nraven-coffee;6682;ravencoffee;Raven咖啡;Raven Coffee;\ncqc;6683;;CQC;;\ngeode;6684;geode;Geode;;\nrealistic-spiders;6685;realisticspiders;真实的蜘蛛;Realistic Spiders;\n;6686;timedisplay;时间显示;Time Display;\nbiome-particle-weather;6687;impactfulweather;Biome Particle Weather;;\nftb-backups-2;6688;ftbbackups2;FTB备份2;FTB Backups 2;\ndarkkore;6689;darkkore;DarkKore;;\n;6690;MinersDeluxe;豪华矿工;Miners Deluxe Mod;\n;6691;;更多工具;UTools;UT\nillagers-wear-armor;6692;illagersweararmor;Illagers Wear Armor;;\nhudium;6693;hudium;Hudium;;\nocean-world;6694;oceanworld;海洋的世界;Ocean World;OW\ncobblegen;6695;cobblegen;CobbleGen;;\nzombie-extreme;6696;zombie_extreme;Zombie Extreme;;\nmodularmovements;6697;modularmovements;模块化动作;Modular Movements;MWFT\nuncle-js;6698;;Uncle J's;;\nwither-bones;6699;wither_bones;凋灵之骨;Wither Bones;\nsleep-warp;6700;sleepwarp;SleepWarp;;\ndiagonal-fences;6701;diagonalfences;Diagonal Fences;;DF\njumpy-boats-fabric;6702;jumpyboat;跳跃的船 (Fabric/Quilt);Jumpy Boats(Fabric/Quilt);\ndyeable-redstone-signal;6703;dyeable_redstone_signal;染色红石信号;Dyeable Redstone Signal;DRS\nvelocity-based-damage;6704;veldmg;Velocity Based Damage;;\nno-telemetry;6705;no-telemetry;No Telemetry;;\n;6706;fullbright;Fullbright;;\n;6707;expbook;冒险经验书;;\ncreate-chromatic-return;6708;createchromaticreturn;机械动力：异彩化合物;Create: Chromatic Return;\ncoffee-spawner;6709;coffeespawner;Coffee Spawner;;\ntimber-mod;6710;timber;Timber Mod;;\nsophisticated-storage;6711;sophisticatedstorage;精妙存储;Sophisticated Storage;\nadvanced-lootable-weapons;6712;advancedlootableweapons;Advanced Lootable Weapons;;ALW\nwhat-the-bucket;6713;whatthebucket;What The Bucket;;\nquickhomes;6714;quickhomes;QuickHomes;;\nreal-time-clock;6715;realtimeclock;Real Time Clock;;\ntardim;6716;tardim;TARDIM;;\naether-extra-structure;6717;;天境结构拓展;Aether Extra Structures;\n;6718;nullifytelemetry;Nullify Telemetry;;\nluckytnt;6719;luckytntmod;Lucky TNT;;\ngamma-utils;6720;gammautils;伽玛工具;Gamma Utils;\npickle-tweaks;6721;pickletweaks;Pickle Tweaks;;\ndps-chaoscraft;6722;;DP's Chaoscraft;;\n;6723;cc-tweaked-polymer-patch;Polymer Patch for CC: Tweaked;;\nmoyai;6724;moyai;Moyai;;\nmega-meteors-mod;6725;megameteors;Mega Meteors;;\ngravity-generator;6726;gravity_generator;重力发电机;GravityGenerator;GG\n;6727;mcmouser;McMouser;;\nmixinbootstraplegacy;6728;;MixinBootstrapLegacy;;\nfreecam;6729;freecam;FreeCam by kapiteon;;\n;6730;atomess;基石-基础;Atom-Essentials;\nanimationrecorder;6731;animt;动图记录;AnimationRecorder;AnR\ningame-biome-map-exporter;6732;ingame_biome_map;群系地图一键导出;Ingame Biome Map;IBM\nfishing-overhaul;6733;fishingoverhaul;Fishing Overhaul;;\nkronhud;6734;kronhud;KronHUD;;\nshared-ender-chest;6735;sharedechest;共享末影箱;Shared Ender Chest;\nno-beacon-beams;6736;nobeam;无信标光柱;No Beacon Beams;\nfarmers-respite;6737;farmersrespite;农夫暇事;Farmer's Respite;\n;6738;fabricaeexnihilo;Fabricae Ex Nihilo;;FEN\ncrafting-mania;6739;crafting_mania;Crafting Mania;;\nloot-goblins;6740;lootgoblins;Loot Goblins;;\nskylib;6741;skylorlib;SkyLib;;\ndt3;6742;dontstarve3;不要饿死3;DontStarve3;DT3\ntbscclick;6743;tbscclick;TbscClick;;\nmodern-elevators-and-escalators;6744;;现代电梯与扶梯;Modern Elevators and Escalators;MEAE\n;6745;traveller_music;腊小粥的音乐;Traveller's music;LTM\nextra-inventory;6746;extrainv;Extra Inventory;;\nchroma-concrete;6747;chromaconcrete;Chromatic Blocks;;\ndracovita-delight-a-farmers-delight-add-on;6748;dracovitadelight;Dracovita Delight;;\ngobber-delight-a-farmers-delight-add-on;6749;gobberdelight;戈伯乐事;Gobber Delight;\n;6750;qiq2i,qiq2i_clga1;时间追逐生存挑战;;\n;6751;bathappymod;🦇蝙蝠快乐 Fabric;🦇 Bat Happy Mod Fabric;\nextinguish-by-uss_shenzhou;6752;extinguish;灭;Extinguish;\nbiome-makeover-forge;6753;biomemakeover;生物群系改造;Biome Makeover;\nthrowable-blocks;6754;throwableblocks;Throwable Blocks;;\nancient-warfare-3;6755;AncientWarfare;古代战争3;Ancient Warfare 3;\nno-chat-reports;6756;nochatreports;禁用聊天举报;No Chat Reports;NCR\nvalleycraft;6757;valleycraft;ValleyCraft;;\nno-xp-bar;6758;noxpbar;No Xp Bar!;;\ntowers-of-the-wild-additions;6759;totw_additions;Towers of the Wild: Additions;;\nworld-of-pure-imagination;6760;world-of-pure-imagination;想象界;World of Pure Imagination;\nconnectivity;6761;connectivity;Connectivity;;\nmore-useful-villagers;6762;more_useful_villagers;More Useful Villagers;;\n;6763;borderless_window;无边框窗口;Borderless Window;\n;6764;symbol-chat;符号聊天;Symbol Chat;\n;6765;forcegl20;ForceGL20;;\nhyle;6766;hyle;Hyle;;\n;6767;ItemDash;ItemDash;;\nadvanced-solar-panels-patcher;6768;advsolarpatch;Advanced Solar Panels Patcher;;\nno-recipe-book-reborn;6769;norecipebookreborn;没有配方书重制版;No Recipe Book Reborn;\n;6770;paxels_mr;Paxels;;\n;6771;dreamland;梦幻幽境;Dreamland;DLAND\nshulker-tooltip;6772;shulkertooltip;Shulker Tooltip;;\n;6773;dynamicmappings;DynamicMappings;;\n;6774;meddle,Meddle;Meddle;;\n;6775;meddleapi;Meddle API;;\nmidnightlib;6776;midnightlib;MidnightLib;;\n;6777;AllTheItems,alltheitems;All The Items;;\njust-backpacks;6778;fyberbackpack;Just Backpack;;\ndoubledoors;6779;DoubleDoorsMod;DoubleDoors;;\n;6780;mod-remapping-api;Mod Remapping API;;\ntic-tooltips;6781;TiCTooltips;TiC Tooltips;;\nydms-wizarding-world-of-harry-potter;6782;harrypotter;Wizarding World Of Harry Potter;;\nlucraft-id-extender;6783;lucraftcoreidextender;Lucraft: Core ID Extender;;\nmodern-flat-colored-blocks;6784;fcb;Modern Flat Colored Blocks;;\n;6785;xiaosego;邵的自我;Xiao's E.G.O;\n;6786;harvest_delight;丰年乐景;Harvest Delight;HDe\nflat-bedrock;6787;flatbedrock;Flat Bedrock;;\nqsl;6788;quilted_fabric_api;Quilted Fabric API;;QFAPI\ndans-crafting-tweaks;6789;;合成辅助非官方版;Crafting Tweaks Unofficial;\nhandle-cane;6790;handle_cane;手杖;Handle Cane;\n;6791;;BicycleCraft;;\nmega-cells;6792;megacells;MEGA存储单元;MEGA Cells;\nstructory;6793;structory;Structory;;\nenchlevel-langpatch-config2;6794;enchlevel-langpatch-config2,enchlevel-langpatch-conf3,langpatchconf,langpatch-conf4;附魔等级语言补丁 - 配置扩展;EnchLevel-LangPatch Config IV;\ninstant-unify;6795;instantunify;瞬间统一+;InstantUnify+;\nsneaky-farts;6796;sneakyfarts;Sneaky Farts;;\njust-enough-recipe-integrations;6797;jeri;Just Enough Recipe Integrations;;JERI\nhanami;6798;hanami;Hanami;;\nis-a-mod-by-vazkii;6799;isamodbyvazkii;...is a mod by Vazkii;;\ninputfix;6800;InputFix;输入修正;InputFix;\ninventory-tabs-updated;6801;inventorytabs;Inventory Tabs (updated);;\nez-mod-lib;6802;ezmodlib;Ez Mod Lib;;\narmor-toughness-bar-updated;6803;;Armor Toughness Bar (Ported);;\ncolorful-health-bar-updated;6804;;Colorful Health Bar (Updated);;\n;6805;;Bone Sword BTA;;\nwizardry-necromancers-delight;6806;necromancersdelight;Wizardry - Necromancer's Delight;;\nwizardryutils;6807;wizardryutils;WizardryUtils;;\nxp-orb-clump;6808;xporbclump;Fixeroo;;\ninventory-weight;6809;inventory_weight;Inventory Weight;;\ntime-reverse;6810;time_reverse_mod;Time Reverse;;\nnether-tears;6811;ntrs;下界遗泪;NetherTears;Ntrs\n;6812;utm,ucr;UNDERCRAFT;;UCR\n;6813;gvcr2;游击队员vs突击队员2;GVCReversion2;GVCR2\nsafaricraft;6814;safaricraft,waterbuck_and_hyena_mod;SafariCraft / AfriCraft;;\napplied-botanics-addon;6815;appbot;应用能源：植物魔法附属;Applied Botanics Addon;\noverloaded-armor-bar-updated;6816;;Overloaded Armor Bar (Updated);;\ndelightful;6817;delightful;Delightful;;\nwizardry-tales;6818;w_tweaks;Wizardry Tales;;\n;6819;;Better with Furnaces;;BWF\ntamedrespawn;6820;tamedrespawn;驯兽重生;TamedRespawn;\n;6821;kelpcraft;海带工艺;Kelp Craft;KC\ndumplings-delight;6822;dumplings_delight;饺子乐事;Dumplings Delight;\nthermal_extra;6823;thermal_extra;Thermal: Extra;;\nnether-sky-block;6824;skyland;下界空岛;Nether Sky Block;\narte-magicum;6825;arte_magicum;Arte Magicum;;\nocean-blender;6827;oceanblender;海洋搅拌机;OceanBlender;OB\nbuildcraft-fluxified;6828;buildcraftfluxified;Buildcraft Fluxified;;\nbrewin-and-chewin;6829;brewinandchewin;饮酒作乐;Brewin' And Chewin';\ninfinite-source-of-water;6830;watersources;Infinite source of water;;\ndramatic-doors;6831;dramaticdoors;Dramatic Doors;;DD\nrecipe-gui;6832;recipe_gui;Recipe GUI / CraftTweaker GUI;;\nautotechno;6833;autotechno;AutoTechno;;\nvillager-hats-mod;6834;villager-hats;Villager Hats;;\ncustom-damage-particles;6835;torohealthmod;自定义伤害粒子;Custom Damage Particles;\n;6836;;AEI物品管理器;Always Enough Items;AEI\ncyan-warrior-swords-mod;6837;cwsr;Cyan Warrior Swords;;\nplant-in-a-jar;6838;plantinajar;Plant In A Jar;;\nsolid-mobs;6839;solid_mobs;Solid Mobs;;\nnaturalist;6840;naturalist;自然主义;Naturalist;\nc3h6n6o6;6841;c3h6n6o6;环三亚甲基三硝胺;Cyclotrimethylenetrinitramine;C3H6N6O6\nsyncmatica;6842;syncmatica;共享原理图;Syncmatica;\nzephs-hammers;6843;zephshammers;Zeph's Hammers;;\nzephs-excavators;6844;zephsexcavators;Zeph's Excavators;;\n;6845;delayedreports;DelayedReports;;\n;6846;technomodel;Technopig;;\ncat-herder;6847;catherder;Cat Herder;;\ncommand-creative-tab;6848;commandcreativetab;Command Creative Tab;;CCT\numu-backpack;6849;umu_backpack;UMU Backpack;;\n;6850;clover;四叶草;Clover;\nshort-stacks;6851;shortstacks;Short Stacks;;\nsmooth-boot-reloaded;6852;smoothboot;流畅加载重置版;Smooth Boot Reloaded;\nstone-crafting-table;6853;sct;Stone Crafting Table;;\nlodestonelib;6854;ortus,lodestone;Lodestone;;\nwitherturnsskeletons;6855;witherturnsskeletons;WitherTurnsSkeletons;;\ntwilight-forest-the-lost-blocks;6856;tflostblocks;Twilight Forest: The Lost Blocks;;\nthe-nexus-deep-dark-dimension;6857;thenexus;The Nexus;;\njepp;6858;jepp;JEI绘画预览;Just Enough Painting Previews;JEPP\ncreate-curios;6860;createcurios;Create Curios;;\nctm-refabricated;6861;ctm;CTM Refabricated;;\nplaceable-pumpkin-pie;6862;rejected;可放置南瓜派;Placeable Pumpkin Pie;\nbirch-forest-upgrade;6863;bfu;白桦林更新;Birch Forest Upgrade;\nok-zoomer-forge;6864;;Ok Zoomer Forge;;\n;6865;koroworld-lib,koro-utils-lib;科洛的工具库;Koro Utils Lib;\ncosmicnpcs;6866;cosmicnpcs;CosmicNPCs;;\nskinrestorer;6867;skinrestorer;Skin Restorer;;\nhitchhike;6868;hitchhike;Hitchhike;;\nsonic-deflect;6869;sonic_deflect;Sonic Deflect;;\nmpkmod;6870;mpkmod;MPKMod;;\nsupernatural-creatures;6871;supernatural_creatures;Supernatural Creatures;;\nfood-rebalanced;6872;food_overhaul;Food - Rebalanced;;\npsi-unofficial;6873;;Psi 非官方版;Psi Unofficial;\ntime-core;6874;timecore;Time Core;;\n;6875;searchablecontainers;可搜索容器：重遍历;Searchable Containers: Retraversal;\nunusual-squad-into-the-void;6876;intothevoid;Into The Void;;\ncreate-chromaticreturn;6877;createsupercharged;Create Chromatic Return;;\npowertool;6878;powertool;电动工具;PowerTool;\nkeymap;6879;keymap;键盘映射;Keymap;\nactually-useful-stonecutter;6880;mandatory;Actually Useful Stonecutter;;\nitemphysic-lite-fabric;6881;itemphysiclite;物品物理掉落：轻量版 - Fabric;ItemPhysic Lite - Fabric;\nforgotten-treasures;6882;forgottentreasures,forgotten_treasures;Forgotten Treasures;;\nplayer-health-indicators;6883;healthindicators;Player Health Indicators;;\ndisplaycase;6884;display_case;陈列柜;Display Case;\nauto-look;6885;autolook;Auto Look;;\ngrimoire-of-gaia;6886;grimoireofgaia;盖亚魔典4;Grimoire of Gaia 4;GOG4\nftb-jar-mod-forge;6887;ftbjarmod;FTB 玻璃罐;FTB Jar Mod;\ndimensional-paintings;6888;dimpaintings;维度绘画;Dimensional Paintings;\nitem-recycler;6889;itemcollectors;物品回收机;Item Recycler;\nlinsapi;6890;linsapi;LinsAPI;;\ntitlechanger;6891;;标题切换器;TitleChanger;\nadobe-blocks-2;6892;adobeblocks;Adob​​e Blocks 2;;\nmenacing-monsters;6893;menacing_monsters;Menacing Monsters;;\npacking-tape;6894;packingtape;Packing Tape;;\nnyctophobia-forge;6895;nyctophobia,graveyard_biomes;Nyctophobia / The Graveyard Biomes;;\n;6896;playerhuds;玩家HUD显示优化;PlayerHudShow;\ncammies-minecart-tweaks;6897;minecarttweaks;Cammie's Minecart Tweaks;;\nrenderlib;6898;renderlib;RenderLib;;\nnothirium;6899;nothirium;Nothirium;;\nadvancedchatbox;6900;advancedchatbox;AdvancedChatBox;;\nquick-pick;6901;quick_pick;Quick Pick;;\nadvancements-debug;6902;advdebug;Advancements Debug;;\nrandomium-ore;6903;randomium;Randomium Ore;;\ndegradeexplodedblocks;6904;degradeexplodedblocks;DegradeExplodedBlocks;;\n;6905;;我射中了谁;Who did I shoot;WDIS\nzeras-scps;6906;zerascp;SCP Lockdown: Zera's SCPs;;\nenderite-lieonlion;6907;lolenderite;LieOnLion's Enderite;;\nadvancedchatfilters;6908;advancedchatfilters;AdvancedChatFilters;;\nadvancedchatmacros;6909;advancedchatmacros;AdvancedChatMacros;;\naof-emotes;6910;aofemotes;AOF Emotes;;\ngrasslessdirtbackport;6911;grasslessdirtbackport;GrasslessDirtBackport;;\ndazzle-2;6912;dazzle;Dazzle 2;;\nsmaller-nether-portals;6913;smallernetherportals,smallernp,smallernetherportals-fabric;Smaller Nether Portals;;\nbasic-aiots;6914;basicaiots;Basic AIOTs;;\npokeball;6915;pokeball;精灵球;PokeBall;\n;6916;GoodGenerator;GoodGenerator;;\ngravitychanger;6917;gravitychanger;Gravity Changer;;\nbuilding-wands;6918;wands;Building Wands;;\nadvancedchat;6919;advancedchat;AdvancedChat;;\naiots-expanded-fabric;6920;aiotexpanded;AIOTs Expanded;;\nmoremcmeta;6921;moremcmeta;MoreMcmeta;;\nstarry-skies;6922;starry_sky;Starry Skies;;\nflesh2leather-forge;6923;flesh2leather;Flesh 2 Leather;;\nfall-damage-indicator;6924;fdi;Fall Damage Indicator;;\naww-man-achievement-mod;6925;awwman;Aww Man Achievement Mod;;\nfire-extinguisher;6926;fire_extinguisher;灭火器;Fire Extinguisher;\nfarmers-knives;6927;farmersknives;Farmer's Knives;;\nsullys-mod;6928;sullysmod;Sully's Mod;;\nmagicaldecorations;6929;mdeco;MagicalDecorations;;\nmoss-and-monsters;6930;ecosystemmod;Moss And Monsters;;\ndetonation;6931;detonation;Detonation;;\n;6932;rpgdurability;RPG装备耐久;RPG Durability;\ncoal-processing-machine;6933;coalprocessing;Coal Processing Machine;;\njoy-of-painting;6934;xercapaint;绘画之乐;Joy of Painting;\nwarden-loot-forge;6935;wardenloot;Warden Loot;;\n;6936;vmwcr;维克的现代战争补充合成表;Crafting Recipes of Vic's Modern Warfare Mod;VMWCR\n;6937;advagri;高级农业;Advanced Agriculture;AdvAgri\n;6938;experience_book;经验存储书;Experience Book;EB\noptifine-checker;6939;optcheck;高清修复检测;OptiFine Checker;\npaintings;6940;paintings;Paintings ++;;\nbuiltin-servers;6941;builtinservers;Builtin Servers;;\nextractinator;6942;the_extractinator;提炼机;Extractinator;\nchoicetheorems-overhauled-village;6943;ctov;ChoiceTheorem's Overhauled Village;;CTOV\nblast;6944;blast;BLAST;;\nno-indium;6945;noindium;No Indium?;;\nwindsweptmod;6946;windswept;Windswept!;;\nimmersive-paintings;6947;immersive_paintings;沉浸画框;Immersive Paintings;IP\nmelon-wireless-redstone;6948;wireless-redstone;Melon Wireless Redstone;;\nautomated-crafting;6949;automated_crafting;Automated Crafting;;\nop-tab;6950;optab;Op Tab;;\neasy-piglins;6951;easy_piglins;简易猪灵;Easy Piglins;\nwaterlogged-redstone;6952;wlrs;Waterlogged Redstone;;\nprehistoric-flora-integration;6953;prehistoricfloraintegration;Prehistoric Nature Integration;;\nbetter-bad-omen;6954;better_bad_omen;Better Bad Omen;;\ncan-you-trash-it;6955;cyti;Can You Trash It?;;cyti\ncroptopias-chocolaterie-forge;6956;cacao;作物盛景：巧克力工坊;Croptopia's Chocolaterie;\ncrawl;6957;crawl;爬行;Crawl;\nchisel-reborn;6958;chisel;Chisel Reborn;;\nroost-ultimate;6959;chicken_roost;Roost Ultimate;;\ncammies-wearable-backpacks;6960;camsbackpacks;Cammie's Wearable Backpacks;;\n;6961;;Midas Hunger;;\nparrying;6962;parrying;Parry This!;;\nback-in-slime-slime-blocks-for-1-7;6963;bis;Back in Slime;;\nmodern-furniture-survival-addon;6964;;现代家具;Modern Furniture;\ncreate-alloyed;6965;alloyed;机械动力：合金;Create: Alloyed;\nverdure;6966;verdure;郁郁葱葱;Verdure;\nback-to-basics-addon;6967;btb;Back to Basics;;\n;6968;;Dynamic Lighting;;\nthe-ori-mod;6969;etimod;The Ori Mod;;\ncoxinha-utilities;6970;coxinhautilities;Coxinha Utilities;;\n;6971;;Interdimensional Ores;;\ndragon-fire-mod;6972;dragonfire;Dragons Fire;;\n;6973;extrabiomes;Extra Biomes;;\n;6974;mohammedkhc;Morph Plus;;\n;6975;;Personal Vanish;;\nsandy-ores;6976;sandyores;沙子矿石;Sandy Ores;\nrainbowify;6977;rainbowify;Rainbowify;;\nbe-quiet-please;6978;bequietpls;请安静;Be Quiet Please;BQP\nprehistoric-revival-a-prehistoric-fauna-add-on;6979;prehistoric_revival;史前复生;Prehistoric Revival - A Prehistoric Fauna Add-On;\nhad-enough-characters;6980;hecharacters;Had Enough Characters;;HECh\nmedieval-origins;6981;medievalorigins;Medieval Origins;;\nbetter-trades-addon;6982;;Better Trades;;\n;6983;;Block-Geo Fixer / Sonic Bug Fix;;\nelytra-chestplate-swapper;6984;ecs;鞘翅/胸甲切换;Elytra/Chestplate Swapper;ECS\nthe-ends-flora;6985;ends_flora;The End's Flora🌻;;\napollos-additional-structures;6986;apollo;Apollo's Additional Structures;;AAS\n;6987;test-utils;test-utils;;\nchimes;6988;chimes;Chimes;;\n;6989;vc;Visual Commands;;\nendless-ocean-aquatic-adventures;6990;endlessocean;Endless Ocean: Aquatic Adventures;;EO\nic2-no;6991;ic2-no;IC2-No;;\n;6992;;Player Head Drops;;\norigins-plus-plus;6993;origins_plus_plus;起源++;Origins++;\n;6994;greedy_piglin;贪婪的猪灵;Greedy Piglin;\nmcda;6995;mcda;我的世界：地下城盔甲;MC Dungeons Armors;MCDA\n;6996;mcreator_link;MCreator Link;;\nsun-burns;6997;playersunburn;Sun Burns;;\nbattle-tools;6998;enchantment_level_x;Battle Adventure;;\njobsplus;6999;jobsplus;Jobs+;;\ntowns-and-towers-structure-add-on;7000;t_and_t;Towns and Towers;;T&T\ndrg-flares-forge;7001;drg_flares;DRG Flares;;\nsimple-uncrafting-table-fabric;7002;uncraftingtable76;Simple Uncrafting Table;;\ncaracal-mod;7003;aqupdcaracal;Caracal mob;;\nrough-tweaks-revamped;7004;roughtweaks;Rough Tweaks Revamped;;\nthe-wild-backport;7005;wildbackport;The Wild Backport;;\nunvoted-shelved;7006;unvotedandshelved;Unvoted & Shelved;;\nyeos-mobs;7007;creaturesfromthelushcave;Yeo's Mobs / Creatures From The Lush Cave! / All The Creatures;;\ninfohud;7008;infohud;InfoHUD;;\nharuhiism;7009;haruhiism;春日主义;Haruhiism;\nwithcoffee;7010;with_coffee,with_fire;咖啡相伴/天堂之路;WithCoffee;\nemeraldstuff;7011;emerald_stuff;Emerald Stuff : Armor and Tools;;\ngrass-slabs-carpets-stairs;7012;grassslabs;Grass Slabs, Carpets & Stairs;;\nnot-mutually-exclusive;7013;nme;不冲突;Not Mutually Exclusive;NME\nbuilders-delight-forge;7014;buildersdelight;Builder's Delight;;\nminers-minerals;7015;miners-minerals;Miner's Minerals;;\ndays-to-mine;7016;sevendaystomine;七日杀;7 Days to Mine;\npieces-of-ore;7017;piecesofore;Pieces of Ore;;\n;7018;manhunt-datapack;追杀指南针数据包;Manhunt Datapack;mh\n;7019;fix-keyboard-on-linux;Fix Keyboard issues on Linux;;\nlockdown-additions;7020;lockdown_additions;SCP: Lockdown Additions;;\nscp-lockdown-extras;7021;scple;SCP Lockdown Extras;;\nplayer-tabs;7022;playertabs;Player Tabs;;\ncie;7023;cie;Configurable Item Entities;;CIE\neasy-mob-farm;7024;easy_mob_farm;Easy Mob Farm;;\nmaterial-elements;7025;material_elements;Material Elements;;\nraised-clouds;7026;raisedclouds;Raised Clouds;;\nmusic-triggers;7027;musictriggers;音乐触发器;Music Triggers;\nsneaky-screens;7028;sneakyscreens;Sneaky Screens;;\ninmis;7029;inmis;Inmis;;\n;7030;wh1;怪兽工艺;TheKaijuCraft;TKC\nrechiseled;7032;rechiseled;Rechiseled;;\ndurability-tooltip;7033;durabilitytooltip;Durability Tooltip;;\noreganized-mod;7034;oreganized;井然有矿;Oreganized Mod;\nriding-utilities;7035;ridingutils;Riding Utilities;;\ncompact-help-command;7036;compacthelpcommand;Compact Help Command;;\nminers-lung;7038;minerslung;矿工之肺;Miner's Lung;\ngreenery;7039;greenery;绿色植物🌿;Greenery🌿;Gr\nbutchers-delight;7040;butchersdelight;屠夫乐事;Butcher's Delight;\nlarge-meals-an-add-on-for-farmers-delight;7041;largemeals;Large Meals;;\nexplorers-delight;7042;exdel;Explorer's Delight;;\nthe-irregular-chef;7043;irregularchef;The Irregular Chef!;;\ncompat-o-plenty;7044;compatoplenty;Compat O' Plenty;;\nsnow-pig;7045;snowpig;雪猪;Snow Pig;\nwandering-collector;7046;wandering_collector;Wandering Collector;;\n;7047;portalpatch;下界传送门按键补丁;Nether Portal Patch;\n;7048;rc;Create Addon;;\n;7049;firewood;薪火;Firewood;\nskull-craft;7050;skullcraft;头颅工艺;Skull Craft;SC\nre-chiseled-compat;7051;rechiseled_compat;Re-chiseled Compatibilities;;\nauto-leveling;7052;autoleveling;Auto Leveling;;\npacked-inventory;7053;packed-inventory;Packed Inventory;;\n;7054;rsigntip19;移除签名警告;Remove Sign Tips;\ncrafting-on-a-stick;7055;crafting_on_a_stick;Crafting on a stick;;\nendlessids;7056;endlessids;EndlessIDs;;\n;7057;ae2_additions;应用能源扩展;AE2 Additions;AE2A\ncompact-machines-fabric;7058;;压缩空间 Fabric;Compact Machines - Fabric;\ncycle-paintings;7059;cyclepaintings;Cycle Paintings;;\n;7060;nbtreader;NBT 检视者;NBT Reader;NR\nfire-spread-tweaks;7061;firespreadtweaks;Fire Spread Tweaks;;\nanger-management;7062;angermanagement;Anger Management;;\neasy-elytra-takeoff;7063;easyelytratakeoff;Easy Elytra Takeoff;;\nstack-refill;7064;stackrefill;Stack Refill;;\nlight-meals;7065;lightmeals;轻食物语;Light Meals;\nlost-and-fished;7066;lost_found;失而钓得;Lost and Fished;\ncustom-backgrounds;7067;custombackgrounds;自定义背景;Custom Backgrounds;CB\nydms-allay;7068;allay;YDM的悦灵;YDM's Allay;\nstarmaker;7069;starmaker;StarMaker;;\n;7070;swdheftpywaed;对不起，我们没有充足的资金为您提供一只末影龙;Sorry We Don't Have Enough Funds To Provide You With An Ender Dragon;SWDHEFTPYWAED\nsimply-snow;7071;simplysnow;Simply Snow;;\nareas;7072;areas;Areas;;\nemberroot-zoo;7073;emberroot;EmberRoot Zoo;;ERZ\nmctimeextendsfabric;7074;mctimeex;MCTimeExtendsFabric;;\nl2library;7075;l2library;L2 Library;;\nunstructured;7076;unstructured;Unstructured;;\ncolorful-cut;7077;colorfulcut;Colorful Cut;;\n;7078;fake-floader;Fake Fabric Loader;;\nenvironmental-materials;7079;enviromats;Environmental Materials;;\nhorse-tweaks;7080;horsetweaks;Horse Tweaks;;\nauto-workstations-fabric;7081;auto-workstations;自动化工作站;Auto Workstations;\nl2-backpack;7082;l2backpack;莱特兰-背包;L2 Backpack;\n;7083;watch_the_stonecutter;小心切石机！;Watch The Stonecutter!;WTSC\n;7084;mbp;More Block Predicates;;MBP\ncapybara-uam;7085;capybara;Capybara;;\njust-enough-petroleum;7086;justenoughpetroleum;Just Enough Petroleum;;JEP\ndiamethysts;7087;diamethysts;紫晶钻！;Diamethysts!;\ntwigs;7088;twigs;Twigs;;\n;7090;exp1182;魔城传说·火了;Majou Burns Red;\n;7091;bobs_better_nausea;Bob氩的更好反胃;Bob's Better Nausea;BBN\n;7092;Baubles;指环王;Baubles Rebuild;\nfplib;7093;falsepatternlib;FalsePatternLib;;\nspongemixins;7094;spongemixins;SpongeMixins;;\nnock-enough-arrows;7095;nock_enough_arrows;Nock Enough Arrows;;\nevery-compat;7096;everycomp;泛用兼容：木材;Every Compat: Wood Good;\noffline-mode;7097;offlinemode;Disable online mode / Offline Mode;;\nghastly-wail;7098;ghastly_wail;Ghastly Wail;;\nimmersive-weapons;7099;immersiveweapons;沉浸式武器;Immersive Weapons;IW\n;7100;petphraseclient;口癖;Pet Phrase;PP\nclear-skies;7101;Clear Skies;Clear Skies;;\n;7102;Moreaspects;更多要素;Polybius;\nShretNether;7103;shretnether;Shret Nether;;\npling;7104;pling;Pling;;\njustthetips;7105;justthetips;JustTheTips;;\n;7106;beyondrealitycore;Beyond Reality Core;;\nnotenoughitems-gtnh;7107;NotEnoughItems;NEI物品管理器非官方版;NotEnoughItems Unofficial;NEIU\n;7108;item_kill;清除掉落物;Item Kill;\nsketchbook-attributes;7109;sketchbookattributes;SketchBook Attributes;;\nbetter-combat-by-daedelus;7110;bettercombat;Better Combat;;\ncustom-stars;7111;customstars;自定义星星;Custom Stars;\n;7112;;电梯;Lift;\nice-and-fire-tweaker;7113;iceandfiretweaker;Ice And Fire Tweaker;;\nsoaring-clouds;7114;soaring-clouds;Soaring Clouds;;\nlapis-stays-in-the-enchanting-table;7115;csb_ench_table;Lapis Stays in the Enchanting Table;;\ngoodall;7116;goodall;Goodall;;\nfreecam-by-zergatul;7117;freecam;FreeCam;;\n;7118;pyrotechnicraft;烟火工艺;Pyrotechnicraft;\nmacaws-byg-bwg;7119;macawsbridgesbyg;Macaw 的桥梁：你将去的生物群系附属;Macaw's Bridges - Oh The Biomes You'll Go;\nmacaws-fences-oh-the-biomes-youll-go;7120;mcwfencesbyg;Macaw 的栅栏与墙：你将去的生物群系附属;Macaw's Fences - Oh The Biomes You'll Go;\nalternate-current;7121;alternate_current,alternate-current;Alternate Current;;\nno-unused-chunks;7122;nounusedchunks;No Unused Chunks;;\nno-fade;7123;nofade;No Fade;;\nradioactive-universe;7124;radioactive;Radioactive universe ☢️;;\n;7125;jeim;额外MODJEI信息;More JEI Info For Some Mod;MJI\ncompleteconfig;7126;completeconfig;CompleteConfig API;;\nfixmyspawnr;7127;fixmyspawnr;FixMySpawnR;;\nl2-artifacts;7128;l2artifacts;莱特兰-古遗物;L2 Artifacts;\ncritters-and-companions;7129;crittersandcompanions;Critters and Companions;;\nmemorytester;7130;memorytester;MemoryTester;;\ncolormatic;7131;colormatic;Colormatic;;\n;7132;reign;王权;Reign;\nfogbox;7133;fogbox;Fogbox;;\n;7134;hopper;Hopper;;\nlots-of-shields;7135;lots_of_shields;Lots of Shields;;\ntimestopclock;7136;megatimestop;Mega的时间静止;MegaTimeStopMod;MTS\numu-little-maid;7137;umu_little_maid;UMU Little Maid;;\nloot-games;7138;lootgames;战利品游戏;Loot Games;\n;7139;extracraft;附加工艺;ExtraCraft;ExC\nydms-scorpions;7140;scorpions;YDM的蝎子;YDM's Scorpions;\nauto-reconnect;7141;autoreconnect;Auto Reconnect;;\ntext-damage-indicators;7142;textdamageindicators;Text Damage Indicators;;\nrecall;7143;recall;Recall;;\nmonster-coin;7144;dreblecoins;Monster Coin;;\ndotcoin-mod;7145;dotcoinmod;Dot Coin Mod;;\nsuper-potato;7146;superpotato;超级土豆;Super Potato;SP\ncustom-fog;7147;custom-fog;Custom Fog;;\n;7148;falling_blocks;自定义重力方块;Custom Gravity Block;\noc-xnet-driver;7149;ocxnetdriver;OC XNet Driver;;\n;7150;;计算练习;CalcPrac;CP\n;7151;;奇异世界;Strange World;BZ\n;7152;immersiveacft,vehiclecraft_ia;沉浸航空;Immersive Aircrafts;IA\nydms-glare;7153;glare;YDM的怒目怪;YDM's Glare;\ncaupona;7154;caupona;分茶;Caupona;\nydms-iceologer;7155;iceologer;YDM的冰术师;YDM's Iceologer;\nfabric-sculk-worm;7156;sculk_worm;Sculk Infection / Sculk Worm;;\napex-guns;7157;apexguns;Apex 枪械工艺;Apex Guns;\n;7158;undergroundfire;地火;UndergroundFire;UGF\n;7159;rtorch;减速火把;RetardTorch;RT\nhwyla-addon-horse-info;7160;horseinfo;Hwyla Addon Horse Info;;\npostal-pigeons;7161;postalpigeons;Postal Pigeons;;\nswagoween;7162;swagoween;Swagoween;;\nno-lag-tickratechanger;7163;trc-no-lag;No Lag Tick Rate Changer;;\nalcocraft-beer-and-stuff;7164;alcocraft;Alcocraft: Beer & Stuff;;\nsimplevillagers;7165;simplevillagers;SimpleVillagers;;\n;7166;anothermagicrecipe;另一款可视化合成表修改;Another Magic Recipe;\nautoreconnect;7167;autoreconnect;AutoReconnect;;\ntwelvefold-better-combat;7168;tfbettercombat;更好的战斗;Twelvefold Better Combat;TFBC\ndusk;7169;dusk;Dusk;;\ncopper-craft;7170;coppercraft;铜制工艺;Copper Craft;\nextrabotany2;7171;extrabotany;额外植物学重置版;ExtraBotany2;EXBOT\nterramine;7172;terramine;TerraMine;;\naeinfinitybooster;7173;aeinfinitybooster;AEInfinityBooster;;\nendertech-infinity;7174;endertechinf;Endertech Infinity;;\nmore-frogs-fabric;7175;morefrogs;更多青蛙;More Frogs;\n;7176;catinbucket;桶中猫猫;Cat in Bucket;\n;7177;the_blue_skies_delight,blue_skies_delight;蔚蓝乐事;Blue Skies Delight;\ncreate-big-cannons;7178;createbigcannons;机械动力：火炮;Create Big Cannons;CBC\nfastanim;7179;faster_entity_animations;FastAnim;;\nrubidium-extra;7180;sodiumextra,rubidium_extra,embeddium_extra;铷 · 扩展;Embeddium (Rubidium) Extra;\nless-feature;7181;lessfeatures;更少的特性;Less Features;\nmores-forge;7182;mores;More Ore Stones;;\ntactical-fishing;7183;tactical_fishing;Tactical Fishing;;\ntoken-enchanter;7184;tokenenchanter;Token Enchanter;;\nigiexteneded;7185;igiextended;IGI 信息扩展;IGIExteneded;IGIE\n;7186;xibao;喜报;xibao;\nlandofsignals;7187;landofsignals;LandOfSignals;;\nlootbeams;7188;lootbeams;lootbeams;;\n;7189;protector;存档保护器;Protector;\nstepupnext;7190;stepup;StepUpNext / StepUp;;\nsimple-bbq;7191;simplebbq;简易烧烤;Simple BBQ;\nmythic-mounts-forge;7192;mythicmounts;Mythic Mounts;;\nspice-of-life-sweet-potato-edition;7193;solsweetpotato;生活调味料：红薯版;Spice of Life: Sweet Potato Edition;\n;7194;tcwm;真实城市建设模组;Reality City Construction;RCC\nsneak-through-berries;7195;stb;Sneak Through Berries;;STB\nwinter-overhaul;7196;winteroverhaul;冬季改革;Winter Overhaul;\nofflineskins;7197;OfflineSkins,offlineskins;离线皮肤;OfflineSkins;\n;7199;firefly8,xdi8,xdi8_fonts;灯纟火;Xdi8Aho Mod;\n;7200;hourglass;沙漏;Hourglass;\nanother-furniture;7201;another_furniture;别样家具;Another Furniture;\n;7203;packetprofiler;PacketProfiler;;\nrayon;7204;rayon;Rayon;;\nponder;7205;ponderjs;Ponder for KubeJS;;\nender-logistics;7206;ender_logistics;末影物流;Ender Logistics;EL\n;7207;drift_sand;流沙;Drift Sand;\n;7208;ntr;地狱迁跃堡;Nether Transition Remains;NTR\n;7209;quick_fix_iron_golem;快速修复铁傀儡;Quick Fix Iron Golem;\nmegaplanets;7210;MegaPlanets;MegaPlanets;;MP\ncosmic-horizons-gc-addon;7211;cosmichorizons;Cosmic Horizons;;\nultris-boss-expansion;7212;splatus_ultris,ultris_mr;Ultris: Boss Expansion;;\nbedfix;7213;bedfix;BedFix;;\nmo-glass;7214;mo_glass;Mo Glass;;\nflans-mod-global-firestorm;7215;GFS;Global Firestorm;;GFS\nturtlemancy;7216;turtlemancy;Turtlemancy;;\n;7217;miku_music;自定义音乐物品;CustomMusic;\n;7218;barter;交易站;Barter;\n;7219;sm;Salts Mills;;\ngoblins-dungeons;7220;goblinsanddungeons;Goblins & Dungeons;;\noptigui;7221;optigui;OptiGUI;;\n;7222;eeemod;咏e;eeemod;\nbackground-music-selector;7223;bgmselector;背景音乐选择器;Background Music Selector;BGMSelector\ndynamic-surrounding-resurrected;7224;;Dynamic Surroundings Resurrected;;\n;7225;infogetter;信息获取者;InfoGetter;IFG\nspells-shields;7226;spells_and_shields;Spells & Shields;;\nplayer-companions;7228;player_companions;Player Companions;;\ntechnical-engineering-3;7229;ten3;科能工程3;Technical Engineering 3;TEN3\n;7230;composter_tweaks;堆肥桶微调;Composter Tweaks;\nextra-utilities-reborn;7231;extrautilitiesrebirth;更多实用设备：重生;Extra Utilities Reborn;ExUR\n;7232;vanilla_equipment_enhanced;原版装备增强;Vanilla Equipment Enhanced;\n;7233;icy;冰！！！;Icy!!!;\nchatflow;7234;chatflow;ChatFlow;;\nauto-third-person;7235;auto_third_person;自动第三人称;Auto Third Person;\nflowery-structures;7236;flowery;Flowery Structures;;\nchat-up;7237;chat-up;Chat Up!;;\nflowery-core;7238;flowerycore;Flowery Core;;\nlevel-hp;7239;level_hp;Level HP;;\nsanguine-arsenal;7240;sanguinearsenal;幻梦：血染武装;Eidolon's Sanguine Arsenal;\nautomobility;7241;automobility;飞车奇匠;Automobility;\n;7242;torchikoma;拓奇科马;Torchikoma;\n;7243;addrlimiter;AddrLimiter;;\nftb-quests-localization-keys;7244;ftbconv;FTB Quests: Localization keys;;\n;7245;hollow_magic;空洞魔法;Hollow Magic;\nunlit-realistic-torch-remake;7246;unlit;真实火把：重置;Realistic Torch Remake;Unlit\nchunk-saving-fix;7247;chunksavingfix;区块保存修复;Chunk Saving Fix;\nfullscreeno;7248;fullscreeno;fullscreeNO;;\n;7249;xibaopp;喜报++;Xibao Plus Plus;\npiglin-proliferation;7250;piglinproliferation;Piglin Proliferation;;\nthe-binding-of-isaac-style-consumables;7251;isaac_style_consumables;The Binding Of Isaac Style Consumables;;\nguns-rpg;7252;gunsrpg;GUNS RPG;;\nconfiguration;7253;configuration;Configuration;;\n;7254;deathswap;橘子的死亡交换;OrangeDeathSwap;ODS\n;7255;homekit;居家套件;HomeKit furniture;\nnatural-decoration;7256;natural_decoration;Natural Decoration;;\nspice-of-fabric;7257;spiceoffabric;Spice of Fabric;;\nbetter-climbing;7258;better_climbing;更好的攀爬;Better Climbing;\ndog-yeeter;7259;dog_yeeter;Dog Yeeter;;\nmc122477fix;7260;mc122477fix;Mc122477Fix;;\narmors-hud-revived-liteloader-only;7261;ArmorsHUDRevived;Armors HUD Revived;;\nliving-things;7262;livingthings;自然生物;Living Things;\nbeautify-decorate;7263;beautify;美化！;Beautify!;\nVoodoo;7264;voodoo;Voodoo Poppets;;\nnotenoughluck;7265;notenoughluck;更多幸运;NotEnoughLuck;NEL\n;7266;difficulty;困难;Difficulty;\n;7267;hideplayers;隐藏玩家;HidePlayers;\nmixintrace;7268;mixintrace;MixinTrace;;\nbancis-enchantments;7269;bancis_enchantments;Banci's Enchantments;;\n;7270;maze;迷宫;Maze;\n;7271;potionofbadomen;不祥之兆药水;;POBO\n;7272;vanilla_craft_enhanced;原版合成增强;Vanilla Craft Enhanced;VCE\nskylands-fabric;7273;skylands;Skylands;;\njjthunder-overhauled-world-generation;7274;jjthunder;JJThunder King of the Hills / Terrathunder;;\nbetterblockoutline;7275;betterblockoutline;BetterBlockOutline;;\n;7276;sodiumfix;Sodium 临时崩溃修复;Sodium Crash Fix;\nscift;7277;scift;Scift;;\n;7278;pingme;Ping Me;;\n;7279;tool_rods;Tool Rods;;\nthe-squid-game-mod;7280;squid_game_mod;The Squid Game Mod;;\ndatapack-portals;7281;datapackportals;数据包传送门;Datapack Portals;\nmore-beautiful-walls;7282;morewalls;More Beautiful Walls;;\niris-flywheel-compat;7283;irisflw;Iris & Oculus Flywheel Compat;;\nprism-lib;7284;prism;Prism;;\n;7285;flamereaction;焰色反应;Flame Reaction;FRC\nmap-atlases-forge;7286;map_atlases;地图册;Map Atlases;\n;7287;epr;额外玩家渲染Rift版;ExtraPlayerRenderer-Rift;EPRR\n;7288;arkworld;方舟世界;Arkworld;AW\n;7289;weedylib;温蒂库;WeedyLib;\nsmartbrainlib;7291;smartbrainlib;SmartBrainLib;;SBL\ntwilight-tweaks;7292;twilighttweaks;Twilight Tweaks;;\naureus-simple-slabs;7293;aureusslabs;Aureus' Simple Slabs;;\njust-mob-heads;7294;justmobheads;Just Mob Heads;;\nsquid-game-mod;7295;derec_squid_game;Squid Game: The Mod;;\n;7296;prismatic_sorcery;七彩魔法;Prismatic Sorcery;\nquantum-querry-plus;7297;quantumquarryplus;量子采石场增强;Quantum Quarry Plus;\nfood-enhancements;7298;food_enhancements;食物扩增;Food Enhancements;\n;7299;bindgg;BindGG;;\ncreate-ore-excavation;7300;createoreexcavation;机械动力：矿石开掘;Create Ore Excavation;COE\njust-player-heads;7301;justplayerheads;Just Player Heads;;\nsurvivalplus-lightsabers;7302;lightsaber;生存加光剑;SurvivalPlus Lightsabers;\nfclib;7303;fclib;FC: Library;;\nprimitive-utilities;7304;primutils;Primitive Utilities;;\nguglecarpetaddition;7305;gca;Gugle的Carpet附加包;GugleCarpetAddition;GCA\nsurvivalplus;7306;survivalplus;生存加;SurvivalPlus;\nmrp-library;7307;mrplibrary;mrp Library;;\nbypass-anvil-restriction;7308;bypass_anvil_restriction;Bypass Anvil Restriction;;\nno-early-loading-progress;7309;noearlyprogesswindow;No Early loading progress;;\n;7310;spaceplane;三体工业：空天科技;Space&SkyTechnology;\nblock-runner-forge;7311;blockrunner;Block Runner;;BR\nquick-paths;7312;quickpaths;Quick Paths;;\nsolar-energy;7313;solarenergy;Solar Energy;;\nrestored-earth;7314;restored_earth;Restored Earth;;\n;7315;;TweakerMC;;\n;7316;spruceui;SpruceUI;;\npig-pen-cipher;7317;piqpen;Pig Pen Cipher;;\nluckyblock-fabric;7318;luckyblocks;幸运方块;Lucky Blocks;\n;7319;autosmeltenchant;Smelting Touch;;\nthings-fabric;7320;things;杂七杂八的东西;Things;\nexlines-snorkel-mod;7321;snorkelmod;Exline's Snorkel Mod;;\ntwo-players-one-horse;7322;twoplayersonehorse;Two Players One Horse;;\nheadshot-forge;7323;headshot;Headshot;;\nleft-hanging;7324;hangman;Left Hanging;;\nstars_carrier;7325;sc;星光的载具;stars_carrier;\n;7326;mekanism_make_item;通用机械附属：合成用物品;MekanismMakeItem;\naetherworks;7327;aetherworks;天华之作;AetherWorks;\nslice-and-dice;7328;sliceanddice;Create Slice & Dice;;\ntextrues-rubidium-options;7329;;TexTrue的铷视频界面;TexTrue's Rubidium Options;TRO\n;7330;danmaku;弹幕姬;Danmaku;\n;7331;bbqsdelight;烧烤乐事;BBQ's Delight;\nrubber-duck;7332;rubber_duck;橡皮鸭;Rubber Duck;\nunlimited-dragon-eggs-fabric;7333;unlimiteddragoneggs;Unlimited Dragon Eggs;;\nmoreplayermodels-plus;7334;moreplayermodels;更多玩家模型+;MorePlayerModels+;MPM+\n;7335;actioninventory;Action Inventory Mod;;\n;7336;beta_brewing_system;远古酿造系统;Beta Brewing System;bbs\ncustom-void;7337;customvoid;自定义虚空;Custom Void;\ncreate-jetpack;7338;create_jetpack;机械动力：喷气背包;Create Jetpack;\nentity-outliner;7339;entityoutliner;Entity Outliner;;\n;7340;sharpness6;Sharpness VI;;\nno-name-tags;7341;no-name-tags;隐藏名称标签;No Name Tags;\nranged-wireless-redstone;7342;rangedwirelessredstone;Ranged Wireless Redstone;;\npotato-canon-plus-forge;7343;potato_canon_plus;Potato Canon Plus;;\nunusual-end;7344;unusualend;不同寻常的末地;Unusual End;\nsolar-flux;7345;SolarFlux;日光通量;Solar Flux;SF\n;7346;vdmod2;夜视眼镜;Night Glasses;\ncustom-background-color;7347;cbc;自定义背景颜色;Custom Background Color;CBC\nsimple-shulker-preview;7348;simpleshulkerpreview;Simple Shulker Preview;;\nbygone-nether;7349;bygonenether;逝去的下界;Bygone Nether;\n;7350;clickopener;Click Opener Mod;;\nkokoalinux-backport;7351;kokoalinux;KokoaLinux-Retro;;\nmechanical-clothesline;7352;clothesline;机械晾衣绳;Mechanical Clothesline;\nloading-progress-bar;7353;lpb;加载进度条;Loading Progress Bar;\nintercontinental-ballistic-redux;7354;icbm;Intercontinental Ballistic Redux;;\nplanefix;7355;planefix;PlaneFix;;\nquick-leaf-decay;7356;quickleafdecay;Quick Leaf Decay;;QLD\nsimple-trophies;7357;simple_trophies;Simple Trophies;;\nthaumic-computers;7358;thaumcomp;Thaumic Computers;;\n;7359;clicksoundfix;Slider ClickSoundFix;;\ninstant-massive-structures-ims;7360;imsm;Instant Massive Structures;;IMS\nhunger-remover;7361;hunger_remover;Hunger Remover;;\nrad-enchants;7362;radenchants;Rad Enchants;;\nplus-the-end;7363;plus_the_end;Plus The End;;\nrunic;7364;runic;Runic;;\nbroken-discs;7365;brokendiscs;Broken Discs;;\nforgedfabric;7366;forgedfabric;ForgedFabric API;;\ncreate-extended-flywheels;7367;extendedflywheels;机械动力：更多飞轮;Create: Extended Flywheels;\npiggy-bank;7368;piggybank;Colds: Piggy Bank;;\ndeeperdarker;7369;deeperdarker;幽邃黑暗;Deeper And Darker;\n;7370;mr_fishing_war;钓鱼战争：重生;Fishing War;FsW\nremote-gui-opener-forge;7371;remoteguiopener;Remote GUI Opener;;\n;7372;ridespide;Ride Spiders;;\nillagers-pillagers;7373;illagers__pillagers;Illagers & Pillagers;;\nguard-illagers;7374;guardillagers;Guard Illagers;;\nmoon-light-illager;7375;moonillager;Moon light Illager;;\nthe-woodland-update;7376;the_wood_land_update;The Woodland Update;;\na-foe-on-fire;7377;a_foe_on_fire;A Foe On Fire;;\nhunting-delight;7378;hunting_delight;狩猎乐事;Hunting delight;\nmore-barrels;7379;barrels;更多桶;More Barrels;\norigins-accessibilities;7380;oriacs;起源：通行;Origins: Accessibilities;\n;7381;minerslunchbox;矿工的午餐盒;Miner's Lunchbox;\nfuelcanister;7382;fuelcanister;FuelCanister;;\n;7383;animations;OldAnimationsMod;;OAM\n;7384;wingsmod;DragonWingsMod;;\nf5-begone;7385;f5begone;F5 Begone;;\nsmartvision;7386;smartvision;SmartVision;;\n;7387;argusmod;Argus' Weapons Mod;;AWM\n;7388;inventoryvehicles;Inventory Vehicles;;\nno-more-purple;7389;no_more_purple;No More Purple;;\n;7390;immersivedecoration;IMMERSIVE DECORATION;;\ncontent-creator-integration;7391;contentcreatorintegration;Content Creator Integration;;CCI\ndefaultworldtype;7392;defaultworldtype;默认世界类型;Default World Type;\nno-nether;7393;nonether;No Nether;;\nminiatures;7394;miniatures;Miniatures;;\nad-astra;7395;ad_astra;Ad Astra!;;\nbig-items-duh;7396;big_items_duh;Big items, duh!;;\nscape-and-run-parasite-vaccine;7397;scapeandrunvaccine;Scape And Run: Parasite Vaccine;;\ndoge-mod;7398;doge;Doge Mod;;\nice-fire-katanas;7399;ice__fire_katanas;Ice & Fire Katanas;;\n;7400;pressalttomeow;Press Alt To Meow;;\n;7401;cma;地毯-MCT拓展;Carpet MCT Addition;CMA\nbrvsb;7402;bsvsb;更好的铷视频设置按钮;Better Rubidium Video Settings Button;BRVSB\nroughly-enough-trades;7403;roughly_enough_trades;Roughly Enough Trades;;RET\nsimple-quarry;7404;squarry;Simple Quarry;;\nmagitek-mechs;7405;mtmechs;Magitek Mechs;;\ninfectum;7406;infectum;Infectum;;\nnocubes-sea-dwellers;7407;seadwellers;Realm RPG: Sea Dwellers;;\nshow-me-your-skin;7408;showmeyourskin;Show Me Your Skin!;;\ninvulnerable-pigs;7409;invulnerablepigs;Invulnerable Pigs;;\nt88;7410;t88;T88;;\nk-turrets;7411;k_turrets;K-Turrets;;\nnevertooexpensive;7412;nevertooexpensive;NeverTooExpensive;;\nmax-enchant-x;7413;maxenchantx;Max Enchant X;;\nvisible-toggle-sprint;7414;visible_toggle_sprint;Visible Toggle Sprint;;\nlet-me-despawn;7415;letmedespawn;Let Me Despawn;;LMD\nglsl-panorama;7416;glsl_panorama;GLSL Panorama Shaders;;\nfeyfriends;7417;feyfriends;FeyFriends;;\nmccalc;7418;mccalc;McCalc;;\nnekeys;7419;nekeys;Not Enough Keys;;\ncrafted-forge;7420;crafted;Crafted!;;\ncursery;7421;cursery;Cursery;;\nmirage-orb;7422;mirageorb;Mirage Orb;;\ngalacticborn-origin;7423;galacticborn_origin;Galacticborn Origin;;\nextra-recipe;7424;extra_recipe;更多配方;Extra Recipe;ER\n;7425;mor;更多煲汤;More Soups;\n;7426;HA;HBMAddon;;HA\ncustom-window-title;7427;customwindowtitle;自定义窗口标题;Custom Window Title;\n;7428;perspectivemod;Perspective Mod;;\ncrated-foods-farmers-delight;7429;eggcrate;Crated Vanilla Foods;;\ndamage-tilt;7430;damagetilt;Damage Tilt;;\nupgraded-core;7431;upgradedcore;Upgraded Core;;\nupgraded-netherite-ultimerite;7432;upgradednetherite_ultimate;下界合金增强：终极;Upgraded Netherite : Ultimerite;\nupgraded-netherite-items;7433;upgradednetherite_items;下界合金增强：物品;Upgraded Netherite : Items;\nupgraded-netherite-creativerite;7434;upgradednetherite_creative;下界合金增强：创造;Upgraded Netherite : Creativerite;\nelementary-ores;7435;elementaryores;Elementary Nether and End Ores;;\n;7436;accountswitcher;游戏内账号切换;Account Switcher;\nminecraft-dark-souls;7437;darksouls;黑暗之魂;MC: Dark Souls;\ndontmineit;7438;dontmineit;Don't Mine It;;DMI\ntmp;7439;toomanyplayers;Too Many Players;;TMP\nfeytweaks;7440;feytweaks;FeyTweaks;;\nshow-yourself;7441;showyourself;Show Yourself;;\nconvenient-name-tags;7442;convenientnametags;Convenient Name Tags;;\nanakus-status-bars;7443;anakus_status_bars;Anaku's Status Bars;;\nminemenu-refabricated;7444;minemenufabric;MineMenu Refabricated;;\ncfl;7445;coloredfishingline;Colored Fishing Line;;CFL\ncreative-fly;7446;evilcreativefly;Creative Fly;;\nwhy-does-my-glass-sound-like-that;7447;whydoesmyglasssoundlikethat;Why Does My Glass Sound Like That;;\nitemwallet;7448;itemwallet;Item Wallet;;\nway-much-faster-oxidize;7449;way_much_faster_oxidize;更快铜锈蚀;Way Much Faster Oxidize;\n;7450;;FarLandsMod;;\nemi-trades-villager-trading-emi-plugin;7451;emitrades;EMI村民交易;EMI Trades;\n;7452;vdmod4;更多的合成配方;More Craft Recipes;\nsim-u-kraft-reloaded;7453;suk2;模拟城市重置版;Sim-U-Kraft Reloaded;\ncreate-factory;7454;create_factory;Create: Factory;;\nhorseinboat;7455;horseinboat;HorseInBoat;;\nupgraded-tools;7456;upgradedtools;下界合金增强：工具;Upgraded Tools;\ntools-for-caves-and-cliffs;7457;tools_for_caves_and_cliffs;Tools for Caves and Cliffs;;C&C\nbetterendsky;7458;better_end_sky;Better End Sky;;\n;7459;momostories;MoMo故事会;MoMo's Stories;\ninfinity-with-mending;7460;infwithmend;无限与经验修补;Infinity With Mending;\ninfinity-craft;7461;infinity;Infinity Craft;;\nhookshot;7462;hookshot;Hookshot;;\ncustom-spawns;7463;customspawns;Custom Spawns;;\ncreate-misc-and-things;7464;create_things_and_misc;机械动力：实用物件;Create : Misc & Things;\nlow-fire;7465;lowfire;Low Fire;;LF\ninfinity-buttons;7466;infinitybuttons;无限按钮;Infinity Buttons;\ni-know-what-im-doing-fabric;7467;ikwid_fabric,ikwid_forge;I know what I'm doing!;;IKWID\nskin-hotkey;7468;skin-hotkey;Skin Hotkey;;\ndyeable-fishing-lines-fabric;7469;dyeablefishinglines;Dyeable Fishing Lines;;\nreciperemover;7471;reciperemover;RecipeRemover;;\nroughly-enough-loot-tables;7472;roughly_enough_loot_tables;Roughly Enough Loot Tables;;\nfld;7473;fldf;Speedy Leaf Decay / Fast Leaf Decay;;FLD\nlostcities-tfc;7474;lostcities_tfc;LostCities-TFC;;\nsnowed-fabric;7475;snowed;Snowed;;\nplayer-blink;7476;blink;Player Blink;;\nbetter-command-block-ui;7477;bettercommandblockui;Better Command Block UI;;\ncreate-experienced;7478;create_experienced;Create Experienced;;\ngotta-go-fast;7479;gottagofast;Gotta Go Fast;;\n;7480;the_golden_autumn;黄金之秋 12;The Golden Autumn 12;\n;7481;moremomostories;更多MoMo学;More MoMostories;3M\nmad-particle;7482;madparticle;疯狂粒子;Mad Particle;mp\nfabrihud;7483;fabrihud;FabriHUD;;\nshiny-horses;7484;shinyhorses;Shiny Horses;;\nnyx-rotn-edition;7485;nyx;Nyx: RotN Edition;;\n;7486;ra2sa;红警2救世;;RA2SA\nplayeranimator;7487;player-animator,playeranimator;PlayerAnimator;;\napathy-mod;7488;apathy;Apathy;;\nauudio-forge;7489;auudio;Auudio;;\n;7490;beta_brewing_system;远古酿造系统重置版;Beta Brewing System Reloaded;BBSR\neasy-shulker-boxes;7491;easyshulkerboxes;Easy Shulker Boxes;;ES\n;7492;redefined_baking;重定义烘焙;Redefined Baking;\nqu-enchantments;7493;qu-enchantments;QU Enchantments;;\nironchests;7494;ironchests;Iron Chests: Restocked;;\npiercing-paxels;7495;piercingpaxels;Piercing Paxels;;\nicy-incitement;7496;icy-incitement,icyincitement;Icy Incitement;;\nvisible-librarian-trades;7497;vlt;Visible Librarian Trades;;VLT\nunlimited-chisel-works-tfc1;7498;unlimitedchiselworks_tfc1;Unlimited Chisel Works - TerraFirmaCraft;;\nenchantedshulkers;7499;enchantedshulkers;附魔潜影盒;EnchantedShulkers;\nnoits-simple-sorting;7500;simplesorting;Noit's Simple Sorting;;\ncolds-gauntlets;7501;coldsgauntlets;Colds Gauntlets;;\nlevel-one;7502;level_one;Level One;;\nwilder-wild;7503;wilderwild;Wilder Wild;;\npaintings-with-dragons;7504;blackaures_paintings;Paintings with Dragons / Black Aures Paintings;;\nmore-redstone-lamps;7505;moreredlamps;更多红石灯;More Redstone Lamps;\nplaceable;7506;placeable;可放置植物;Placeable Plants;\ntfc-hot-or-not;7507;tfchotornot;TFC Hot or Not;;\nquick-elytra;7508;quick_elytra;Quick Elytra;;\nother-berries;7509;other_berries_mod;Other Berries;;\nitank;7510;itank;ITank;;\nsnowy-spirit;7511;snowyspirit;Snowy Spirit;;\nno-tutorial-toasts;7512;no_tutorial_toast;No Tutorial Toasts;;\nexperience-ores;7513;expore;Experience Ores;;\nanvil-player-heads-fabric;7514;headrename;Anvil Player Heads;;\nartisan-integrations;7515;artisanintegrations;Artisan Integrations;;\nseemyname;7516;seemyname;SeeMyName;;\nbiomes-o-plenty-tools;7517;boptools;Biomes o' Plenty Tools;;\nbedrockcraft;7518;bedrockcraft;BedrockCraft;;\nbetter-mobgriefing-gamerule;7519;bettermobgriefinggamerule;Better mobGriefing GameRule;;\nfrozen-fiend;7520;iceologer;Frozen Fiend;;\nminefall;7521;titanfall;MineFall;;\ngateways-to-eternity;7522;gateways;永恒之门;Gateways to Eternity;\ngate-of-babylon-additions;7523;gate_of_babylon_additions;王之财宝附加;Gate of Babylon Additions;\nhephaestus-fabric;7524;tconstruct;匠神;Hephaestus;\ncrafting-bench;7525;craftingbench;Crafting Bench;;\nmorebeeinfo;7526;bee_info;更多蜜蜂信息;MoreBeeInfo;\nbq-forestry;7527;bqforestry;更好的任务 - 林业扩展;Better Questing - Forestry;\n;7528;cfmadd;MrCrayfish 的家具模组附加;MrCrayfish's Furniture Mod Additional;CFMA\ncreate-infinidrill;7529;infinidrill;Create InfiniDrill;;\n;7530;disable_report_button;禁用举报按钮;Disable Report Button;\n;7531;all_banning;全局封禁器;AllBanning;\n;7532;scrollable_tooltips;Scrollable Tooltips;;\nattribute-tooltip-fix;7533;attributetooltipfix;Attribute Tooltip Fix;;\n;7534;tntdrop;空袭;TNT Drop;TD\nno-slow-down;7535;no_slow_down;不减速;No Slow Down;\ncrumbs;7536;crumbs;Crumbs;;\nitalians-delight;7537;italian_delight;Italian's Delight;;\nimmersivemc;7538;immersivemc;沉浸式MC;ImmersiveMC;\nmc-vr-api;7539;vrapi;MC VR API;;\nexordium;7540;fastergui,exordium;Exordium;;\nfabric-launchpads;7541;zihlaunchpads;Launchpads;;\n;7542;whitelist;白名单修复;Whitelist Repair;\nthermal-innovation-cyclic-compat;7543;thermalinnovationcycliccompat;Thermal Innovation Cyclic Compat;;\n;7544;beefix;蜜蜂修复;Bee Fix;\nnoel-christmas-mod;7545;noel;NOEL;;\nmoar-fps;7546;moarfps;Moar FPS;;\nflighthud;7547;flighthud;FlightHUD;;\nmeal-api;7548;mealapi;Meal API;;\nseed-drop;7549;seeddrop;Seed Drop;;\nfishery-expansion;7550;fishery;🎣 Fishery‎‎‎‏‏‎ ‎;;\n;7551;ftbconv;FTB Quests: Localization Keys Refabricated;;\narrowcamfabric;7552;arrowcam;ArrowCam;;\noverpowered-mending;7553;overpoweredmending;OverpoweredMending;;\nwoof;7554;woof;Wolves Of Other Furs;;W.O.O.F\ninstant-blocks;7555;instantblocks;Instant Blocks;;\nbetter-sprinting;7556;bettersprinting;更好的疾跑;Better Sprinting;\nrpglevels;7557;rpglevels;RPGLevels;;\nsnow-under-trees;7558;snowundertrees;Snow Under Trees;;\nelytra-physics;7559;elytra_physics;鞘翅物理;Elytra Physics;\nscout;7560;scout;Scout;;\nzenstages;7561;zenstages;ZenStages;;\n;7562;;Industrial Craft/Autocrafters;;\nwondrous-wilds;7563;wondrouswilds;Wondrous Wilds;;\ntruly-treasures;7564;trulytreasures;Truly Treasures;;\nwhere-is-it;7565;whereisit;Where Is It;;\nrelicex;7566;relicex;RelicEx;;\nled;7567;led,led-common;发光二极管;Light Emitting Diode;LED\ni-dont-need-stone;7568;idontneedstone;再见 圆石;I DON'T Need Stone;\n;7569;libgui;LibGui Forge;;\ncolorize;7570;colorize;Colorize;;\nno-magic-milk;7571;no_magic_milk;No More Magic Milk;;\npowered-food;7572;powered_food;Powered Food;;\nspirit;7573;spirit;Spirit;;\n;7574;moonlight_with_wolf;狼塘月色;Moonlight With Wolf;MW\nelytra-bounce;7575;elytrabounce;Elytra Bounce;;\nno-report-button;7576;noreportbutton;无举报按钮;No Report Button;NRB\nmana-wizardry;7577;mana_wizardry;Mana Wizardry;;\naileron;7578;aileron;Aileron;;\ndevworld;7579;devworld,devworld2;DevWorld;;\n;7580;transmission_anchor;传送锚;Transmission anchor;\nimmersive-weapons-compatibility-bridge;7581;iwcompatbridge;Immersive Weapons Compatibility Bridge;;IWCB\ngameidiots-craftable-wizardry-items;7582;craftablewizardry;GameIdiot's Craftable Wizardry Items;;\ncraft-hacks;7583;crafthacks;Craft Hacks;;\nserene-seasons-harvestcraft2-compat;7584;sereneseasonsphc2;静谧四季 - 潘马斯农场 2：作物兼容;Serene Seasons - Pam's HarvestCraft 2: Crops Compat;\ngoat;7585;ohmygoat;Oh My Goat;;\n;7586;eattoomuch;吃太饱了！;Eat Too Much!;ETM\nsimple-autofish;7587;autofish;Simple Autofish;;\nthink-before-drop;7588;thinkbeforedrop;丢弃物品保护;Think Before Drop;\nomo;7589;omo;OMO;Teleport Asker;\nmushroom-additions;7590;mushroom_additions;Mushroom Additions;;\n;7591;long_summer;那年夏天;Long Summer;\nproject-the-void;7592;project_void;项目 -虚空-;Project the Void;\nice-and-fire-rotn-edition;7593;iceandfire;冰火传说：RotN 版;Ice and Fire: RotN Edition;\nminescript;7594;minescript;Minescript;;\nno-capes;7595;nocapes;禁用披风;No Capes;\napocalypsecraft;7596;apocalypse_craft;ApocalypseCraft;;\nmcree_highnoons-enchantments;7597;mrhnsenchantments;McRee_HighNoon的附魔;McRee_HighNoon's Enchantments;MHE\ncanary;7598;canary;Canary;;\nbetter-give;7599;bettergive;Better Give;;\nzonkos-monsters;7600;zmonsters_,zmonsters;Zonko's Monsters;;ZM\ntetratic-combat;7601;tetraticcombat;Tetratic Combat;;\nfastload;7602;fastload;Fastload;;\ndramatictrees;7603;dynamictrees;Dramatic Trees;;\nrobinhood;7604;robinhood;Robin Hood;;\ntrajans-core;7605;trajanscore;Trajan's Core;;\nstop-hiding;7606;stophiding;Stop Hiding;;\n;7607;bracken;The Bracken Pack;;BP\nsimple-world-timer;7608;SimpleWorldTimer,simpleworldtimer;Simple World Timer;;\nrecursive-resources;7609;recursiveresources;Recursive Resources;;\ninventory-actions;7610;inventoryactions;Inventory Actions;;\nlavafishing;7611;lavafishing;熔岩钓鱼;Lava Fishing;\nhaunted-harvest;7612;hauntedharvest;Haunted Harvest;;\ndungeonsenhanced;7613;dungeons_enhanced;Dungeons Enhanced;;\ncreate-crystal-clear;7614;create_crystal_clear,crystal_clear;机械动力：晶莹剔透;Create: Crystal Clear;\n;7615;create_crystal_clear;Create: Crystal Clear Refabricated;;\nfix-experience-bug;7616;experiencebugfix;Fix Experience Bug;;\n;7617;m24;24点;Minecraft024;m24\n;7618;gcys;Gregicality Science;;GCYS\n;7619;gregification;Gregification;;\n;7621;;Weapons And Tools Survival Expansion;;\nit-takes-a-pillage;7622;takesapillage;It Takes a Pillage;;\nwool-recipes-datapack;7623;wool_recipes;羊毛调整 - 数据包版;Wool Recipes - Datapack;WR-D\nget-it-together-drops;7624;getittogetherdrops;Get It Together, Drops!;;\nfire-n-blood;7625;firenblood;Fire N' Blood;;\nchunknogobyebye;7626;chunknogobyebye;ChunkNoGoByeBye;;\nbeaming-drops;7627;beamingdrops,lootbeamsbackport;掉落物光束;Beaming drops / Loot Beams Backport;\nprotocharset;7628;protocharset;ProtoCharset;;\nthe-hordes;7629;hordes;The Hordes;;\n;7630;fd;永冻之地;The Forever Frozen;FF\ninventory-management;7631;inventorymanagement;Inventory Management;;\nsoul-fired;7632;soulfired;Soul fire'd;;\nfaithfulbackrooms;7633;faithfulbackrooms;Faithful Backrooms;;\nex-machinis;7634;exmachinis;Ex Machinis / Ex Machinis: Divitiae Deorum;;\ngoety;7635;goety;诡厄巫法;Goety;\ntorch-hit;7636;torch_hit,torch-hit;Torch hit!;;\n;7637;nan_health_fix;假死修复 : 重生;Entity NaN Health Fix : Reborn;\nwe-got-runners;7638;wegotrunnners;We Got Runners!;;\n;7639;cabricality;Cabricality Utils;;CABFU\ndynamic-trees-croptopia;7640;dtcroptopia;Dynamic Trees Croptopia;;\nmedieval-music;7641;medievalmusic;Medieval Music;;\ngempire;7642;gempire;Gempire;;\nraw-ores-processing-datapack;7643;raw_ores_processing;粗矿处理数据包;Raw Ores Processing - Data Pack;ROP-D\nsbm-bone-torch;7644;bonetorch;Bone Torch;;\nlinkmyitem;7645;linkmyitem;LinkMyItem;;LMI\nsprout;7646;sprout;Sprout;;\nresourceful-lib;7647;resourcefullib;Resourceful Lib;;\n;7648;Caption;字幕;Caption;\n;7649;email;邮箱;Inbox;\n;7650;scalability;Modding Scalability;;\nmarg;7651;marg;Materials and Automatic Resource Generation;;MARG\ncozy;7652;cozy;Cozy;;\nbetter-piglin-trade;7653;betterpiglintrade;更好的猪灵交易;Better Piglin Trade;\nmodel-gap-fix;7654;modelfix;Model Gap Fix;;\n;7655;cookie;超级曲奇;SuperCookie;\nposture;7656;posture;Posture;;\nlittle-ants;7657;ants;小蚂蚁;Little Ants;\nhardcore-torches;7658;hardcore_torches;硬核火把;Hardcore Torches;\nauto-torch;7659;autotorch;自动火把;Auto Torch;\nnightlights;7660;nightlights;小夜灯;Night Lights;\nangry-mobs;7661;angrymobs;Angry Mobs;;\nonlylooking;7662;onlylooking;OnlyLooking;;\nthe-birdwatching-mod;7663;birdwmod;The Birdwatching Mod;;\ngiant-ai;7664;giant_ai;Giant AI;;\nunlit-campfire;7665;unlitcampfire;Unlit Campfire;;\nxl-packets-renewed;7666;xl_packets_renewed;XL Packets Renewed;;\nnameplate;7667;nameplate;Nameplate;;\nrpgdifficulty;7668;rpgdifficulty;RpgDifficulty;;\ntooltiprareness;7669;tooltiprareness;稀有度提示框;TooltipRareness;\n;7670;minimotd_reforged;MiniMOTD Reforged;;\ncombat-roll;7671;combatroll;战斗翻滚;Combat Roll;\nproject-brazier;7672;projectbrazier;Project Brazier / Dark Roleplay Medieval;;\nover-enchanted;7673;overenchanted;Over Enchanted;;\ncreeper-overhaul-firework;7674;creeper_overhual_fireowork;烟花苦力怕改革;Creeper Overhaul Firework;\nspartan-weaponry-bewitchment;7675;spartanbewitchment;斯巴达的武器：巫师之路;Spartan Weaponry - Bewitchment;\nno-water-spread;7676;nowaterspread;No Water Spread;;\nshoppy;7677;shoppy;Shoppy;;\ncathedral;7678;cathedral;Cathedral;;\n;7679;osmod;矿物生成扫描;Osmod;OS\nmeowing;7680;meowing;Meowing;;\ndogwhistle;7681;dogwhistle;DogWhistle;;\n;7682;;MineVocal;;\n;7683;onlylooking;OnlyLooking Refabricated;;\n;7684;yyyy;羊羊羊羊;YYYY;YYYY\n;7685;;Ore Creepers;;\nwilder-wilds;7686;wilder_wilds;Wilder Wilds;;\n;7687;ae2_unpowered;AE2: Unpowered;;\nbetter-respawn;7688;better_respawn;更好的重生;Better Respawn;BR\nessentialgui;7689;essentialgui;EssentialGUI;;\nelementary-staffs;7690;element;Kroko's Elementary Staffs;;\ndragon-fight-config;7691;dragonfightconfig;Dragon Fight Config;;\ninfused-foods;7692;infusedfoods;Infused Foods;;\nwhat-meme;7693;what;What-meme;;\nshiny-gear;7694;shinygear;Shiny Gear;;\nwould-you-shut-up-man;7695;stfu;Would You Shut Up, Man?;;STFU\nweaker-spiderwebs;7696;weakerspiderwebs,weakerspiderwebs-fabric;Weaker Spiderwebs;;\nnetherbarrels;7697;nether_barrels;NetherBarrels;;\nwooden-elytra;7698;wooden_elytra;木制鞘翅;Wooden Elytra;\nconstruction-site-deco;7699;construction_deco;Construction Deco;;\nwthit-harvestability;7700;wthitharvestability;WTHIT Harvestability;;\nhabitat;7701;habitat;Habitat / Bloom and Gloom;;\n;7702;emicompat;EMICompat;;\nharmonicenchantments;7703;harmonicench;调和附魔;HarmonicEnchantments;\n;7704;never_famine;不要饿死：Fabric;Never Famine;\n;7705;wnl;伍德的霓虹灯;Woody's Neon Lights;wnl\ncustom-cards;7706;customcards;自定义卡牌;Custom Cards;\nbiome-cards;7707;biomecards;群系卡牌;Biome Cards;\nbombinghud;7708;bombinghud;BombingHUD;;\nastral-dimension;7709;astral_dimension;Astral Dimension;;\nfollowers-teleport-too;7710;followersteleporttoo-fabric,followersteleporttoo;Followers Teleport Too;;\n;7711;citycraft;城市工艺;City Craft;CC\nodysseymod;7712;odysseymod;Odyssey: Overhauled Trades & World Expansion;;\nlibipn;7713;libipn;libIPN;;\nhulsealib;7714;hulsealib;HulseaLib;;\nblues-dynamic-lights;7715;bdlmod;Blue's Dynamic Lights;;\ngtc-expansion;7716;gtc_expansion;VGT Expansion;;\nnether-depths-upgrade;7717;netherdepthsupgrade;炽海生机;Nether Depths Upgrade;\ncloud-factory;7718;cloud_factory;Cloud Factory;;\ndisc-holders-dj;7719;discholder;Disc holders & DJ;;\n;7720;econ;eCon;;\nkeepmum;7721;keepmum;三缄其口;KeepMum;\nsaturn;7722;saturn;Saturn;;\nstackable;7723;stackable;Stackable;;\ndo-a-barrel-roll;7724;do-a-barrel-roll,do_a_barrel_roll;Do a Barrel Roll;;\nfantasys-dice;7725;fantasydice;Fantasy的骰子;Fantasy's Dice;\n;7726;ukulib;ukulib;;\n;7727;betterhurtcam;BetterHurtCam;;\nitemresistance;7728;itemresistance;ItemResistance;;\ndistinguished-potions;7729;distinguishedpotions;Distinguished Potions;;\n;7730;enhancedweather;Enhanced Weather;;\njust-enough-immersive-multiblocks;7731;jeimultiblocks;Just Enough Immersive Multiblocks;;\nadapaxels;7732;adapaxels;AdaPaxels;;\nfabrizoom;7733;fabrizoom;FabriZoom;;\njson-things;7734;jsonthings;Json Things;;\n;7735;origins-randomiser;Origins Randomiser;;\nsimply-swords;7736;simplyswords;简易刀剑;Simply Swords;\nneokuayue;7737;kuayue;跨越;KuaYue;KY\n;7738;Technical_Engineering_3_compatible;科能工程3兼容;Technical Engineering 3 compatible;TEN3C\nsummoningrituals;7739;summoningrituals;召唤仪式;Summoning Rituals;\nno-rest;7740;nosleep;No Rest;;\nyou-died;7741;you_died;你死了;You Died;\nhotbarcycle;7742;hotbarswap;HotbarCycle;;\nhungry-in-peaceful-mode;7743;hungry_in_peaceful_mode;无中生饥;Hungry In Peaceful Mode;HIP\nauto-respawn-mod;7744;AutoRespawn;AutoRespawn;;\ncave-generator;7745;cavegenerator;洞穴生成器;Cave Generator;\nconsole-filter;7746;consolefilter;Console Filter;;\ncustomized-dungeon-loot;7747;customizeddungeonloot,cdl;Customized Dungeon Loot;;CDL\ncustomquests;7748;customquests;Custom Quests;;\nportal-cubed;7749;thinkingwithportatos;Portal Cubed / Thinking With Portatos;;\npehkui-resizer;7750;pehkui_resizer;Pehkui Resizer;;\nkaffees-dual-ride;7751;kaffees_dual_ride;Kaffee's Dual Ride;;\n;7752;sfreeze;Sfreeze;;\nportable-hole;7753;portablehole;Portable Hole;;\nbetterteamchat;7754;betterteamchat;BetterTeamChat;;\nlogic-chips;7755;logicchips;逻辑芯片;Logic Chips;\nquit;7756;quit;Quit;;\n;7757;mob_firework;生物烟花;Mob Firework;\nnears;7758;nears;Nears;;\nobsidianui;7759;obsidianui;ObsidianUI;;\nwalk-jog-run;7760;walk-jog-run;Walk, Jog, Run!;;\nconveyor-belts-fabric;7761;conveyor-belts;传送带;Conveyor Belts;\ncreate-sifting;7762;createsifter;机械动力：筛子;Create Sifting;\nplayer-name-tweaks;7763;playernametweaks;角色名调整;PlayerNameTweaks;\ndat-modding-api;7764;datmoddingapi;Dat Modding API;;\nrangefinder-hud;7765;rangefinderhud;Rangefinder HUD;;\nprelaws-blocky-siege;7766;blocky_siege;Lucky's Blocky Siege;;\nmodget;7767;modget,modget-core;Modget;;\n;7768;babiesforever;Babies Forever;;\nno-leaf-decay;7769;noleafdecay;No Leaf Decay;;\nmysteriumlib;7770;mysteriumlib;MysteriumLib;;\n;7771;inrealtime;In RealTime;;\nfurnace-recycle;7772;furnacerecycle,furnacerecycle-fabric;熔炉回收;Furnace Recycle;\n;7773;bathappyplus;🦇蝙蝠快乐Mod Plus;🦇Bat Happy Mod Plus;\nwild-lands;7774;wildlands;Wild Lands;;\n;7775;more_plentiful_vanilla;更丰富的原版重制版;More Plentiful Vanilla reborn;MPV\nnoenchanting;7776;miscellaneous;NoEnchanting;;\ngregtech-experimental;7777;gregtechmod;GregTech Experimental;;\n;7778;totem_cooldown;Cooldown for Totem of Undying;;\nalmost-bedtime;7779;almost_bedtime;Almost Bedtime;;\ncatlib;7780;catlib;CatLib;;\n;7781;mythic_botany_tweaker;MythicBotany Tweaker;;\npolaroid-camera;7782;polaroidcamera;宝丽来相机;Polaroid Camera;\ndig;7783;dig;Dig;;\nstargate-network;7784;sgcraft;Stargate Network;;SGCraft\ndimensional_ores;7785;dimensional_ores;Mrthomas20121's Dimensional Ores;;\ndevices-mod;7786;devices;Omnixerio Devices Mod;;\ndynamistics;7787;dynamistics;Dynamistics;;\neguilib;7788;eguilib;Erdbeerbaer's GUI Library;;\nglacidus;7789;glacidus;Glacidus;;\noriginspe-be-add-on-1-2;7790;;起源：基岩版;OriginsPE;\nworldedit-be-addon;7791;wedit;创世神：基岩版;WorldEdit: Bedrock Edition;\ncompat-makeover;7792;compat_makeover;Compat Makeover;;\n;7793;deferred_registries;Deferred Registries;;\n;7794;nametagping;NameTagPing;;\nmap-in-slot;7795;map-in-slot;Map In Slot;;\nhungertweaker;7796;hungertweaker;HungerTweaker;;\nknob-control;7797;kc;Knob Control;;KC\ndaedalus-labyrinth;7798;labyrinth;Daedalus' Labyrinth;;\ntip-the-gui;7799;screenscale;界面尺寸设置优化;GUI Scale;\ncopper-overhaul;7800;copperoverhaul;Copper Overhaul;;\nepic-fight-nbt-integration;7801;fightnbtintegration;Epic Fight NBT Integration;;\nlogin-protection;7802;logprot;登录保护;Login Protection;\nswem;7803;swem;星虫马术;Star Worm Equestrian Mod;SWEM\nmetal-barrels-legacy;7804;metalbarrels;金属储物桶：传承;Metal Barrels: Legacy;\ninventoryfullalert;7805;inventoryfullalert;InventoryFullAlert;;\nlead-beyond;7806;lead_beyond;引向超越;Lead Beyond;BYD\ndyeallthethings;7807;dyeallthethings;Dye All The Things;;\nswampier-swamps;7808;swampier_swamps;Swampier Swamps;;\nweather-settings;7809;weathersettings;更好的天气控制;Weather settings;\nshutdowner;7810;shutdowner;Shutdowner;;\nshield-mechanics;7811;shieldmechanics;盾牌机制优化;Tactical Shield Overhaul / Shield Mechanics;\nsun-and-moon-celestial-configuration;7812;celesteconfig;Sun and Moon Celestial Configuration;;\nchatsound;7813;chatsound;Chat Sound;;\nadvancedshader;7814;advancedshader;光影前向兼容;AdvancedShader;\nthe-wools;7815;the_wools;羊毛！;The Wools!;TWS!\nfabric-enchantments;7816;fabricenchantments;Fabric Enchantments;;\nglued;7817;glued;Glued;;\ntinkers-craft;7818;tinkercraft;工匠创意;Tinkers' Craft;\ntinker-leveling;7819;tinkerleveling;Tinker Leveling;;\ngtce-tj-edition;7820;gregtech;格雷科技社区版：TJ版;GregTech Community Edition: TJ Edition;GTCE-TJ-Edition\nupc;7821;upc;UPC;;\ngregicality-tjfork;7822;gtadditions;Gregicality TJ Fork;;\n;7823;flagslib;FlagsLib;;\ncreate-mechanical-extruder;7824;createmechanicalextruder,create_mechanical_extruder;Create Mechanical Extruder;;\nno-debug-5-you;7825;nd5u;No Debug 5 You;;\npackagedavaritia;7826;packagedavaritia;封包无尽贪婪;PackagedAvaritia;\npillar;7827;pillar;Pillar;;\nresound;7828;resound;RE-Sound;;\nmob-plaques;7829;mobplaques;Mob Plaques;;\ncorail-scanner;7830;scanner;Corail Scanner;;\nspace-ambient-addon-for-galacticraft;7831;spaceambient;Space Ambient;;\nspiked-foods;7832;spikedfoods;Spiked Foods;;\nauraddons;7833;auraaddons;Auraddons;;\nauracontrol;7834;auracontrol;AuraControl;;\ncontenttweaker-registry-orderer;7835;cotro;ContentTweaker Registry Orderer;;CoTRO\nthe-otherside;7836;otherside;The Otherside;;\npogo-sticks;7837;pogostick;Pogo Sticks;;\ntickrate-changer;7838;tickrate_changer;运算变速;Tickrate Changer;\ncamcord;7839;camcord;Camcord;;\nmodern-keybinding;7840;mkb;现代化按键绑定;Modern KeyBinding;MKB\nmake-bubbles-pop;7841;make_bubbles_pop;Make Bubbles Pop;;\ncouplings;7842;couplings;Couplings;;\nmambience;7843;mambience;MAmbience;;\nelytra-aeronautics;7844;elytra_aeronautics;Elytra Aeronautics;;\n;7845;xkdeco;XeKr's Decorations Quilted;;\ninfinitory;7846;infinitory;Infinitory;;\nloud-leads;7847;loud_leads;拴绳音效;Loud Leads;\nmusic-duration-reducer;7848;musicdr;Music Duration Reducer;;\ntelepistons;7849;telepistons;TelePistons;;\nmariums-soulslike-weaponry;7850;soulsweapons;Marium的魂类武器;Marium's Soulslike Weaponry;M'SW\nlets-go-herping;7851;herping;Lets Go Herping!;;LGH\nmodularui;7852;modularui;ModularUI;;\ninventory-bogo-sorter;7853;bogosorter;Inventory Bogo Sorter;;\ngregtech-sieves;7854;gregtechsieves;格雷矿石筛子;GregTech Sieves;\ndynamic-trees-gregtech-ceu-rubber-tree;7855;gtcedyntree;动态的树：格雷科技社区版：非官方版附属;Dynamic Trees - GregTech CEu;\nfertility;7856;fertility;Fertility;;\n;7857;universecatastrophe;造梦西游3;;\ntcintegrations;7858;tcintegrations;Tinkers' Integrations and Tweaks;;\nunderground-villages;7859;underground_villages;Underground Villages;;\nspook;7860;spook;Spook!;;\nbroglis-owls;7861;broglisowls;Broglis Owls;;\npluto;7862;pluto;Pluto;;\n;7863;elephantfix;大象修复;Elephant Fix;\nyoure-in-grave-danger;7864;yigd;You're in Grave Danger;;\nreds-better-biomes-fabric-forge;7865;vanillabetterbiomes;Red's Better Biomes;;\nreds-more-structures;7866;redmorestructure,reds_morestructures_mr;Red's More Structures;;\nrotn-tweaker;7867;rotntweaker;RotN Tweaker;;\ncannot-build-over-lava-source-blocks;7868;nolavabuild;Cannot Build over Lava Source blocks;;\nquark-rotn-edition;7869;;夸克：RotN 版;Quark: RotN Edition;\ninmisaddon;7870;inmisaddon;InmisAddon;;\npineapple-delight;7871;pineapple_delight;凤梨乐事;Pineapple Delight;\nwoodworks;7872;woodworks;Woodworks;;\ninformational-accessories;7873;infoaccessories;信息饰品;Informational Accessories;\nnyfs-spiders;7874;nyfsspiders,spiderstpo;Nyf's Spiders;;\nillager-additions;7875;illager_additions;Illager Additions;;\nidolizing;7876;the_idle;Idolizing;;\nevocation-and-annihilation;7877;evocationandannihilation;Evocation🧙‍♂️ and Annihilation💣!!;;\nmore-golems;7878;moregolems;More Golems;;\nkebabs-crowns;7879;crowns;Kebab's Crowns;;\nrerereskillable;7880;rereskillable;rerereSkillable;;\nhedgehogs;7881;hedgehog;刺猬;Hedgehogs;\n;7882;pendulum;Pendulum;;\nyacl;7883;yet_another_config_lib_v3,yet-another-config-lib;YetAnotherConfigLib;;YACL\nkeyboard-wizard-legacy;7884;keywizard;按键精灵旧版;Keyboard Wizard: Legacy;\nqueen-cats-dogs;7885;queencats;Queen Cats & Dogs;;\nadditional-lights;7886;additional_lights;更多光源;Additional Lights;\nin-time-presence;7887;intimepresence;In Time Presence;;\nmob-vote-2022-forge;7888;mobvote2022;Mob Vote 2022;;\ncurious-lands;7889;curious_lands;Curious Lands;;\ncpmsvcc;7890;cpmsvcc;自定义玩家模型的简单的语音聊天兼容;Customizable Player Models Simple Voice Chat compat;CPM-SVC\npettable;7891;pettable;Pettable;;\ncreate-enchantment-industry;7892;create_enchantment_industry;机械动力：附魔工业;Create: Enchantment Industry;CEI\nenchanted-witchcraft;7893;enchanted;Enchanted: Witchcraft;;\nwyml;7894;wyml;Why You Make Lag;;WYML\n;7895;catalyst;Overgrown Overworld;;\nbunch-o-bugs;7896;bunchobugs;Bunch O' Bugs;;\nuseless-reptile;7897;uselessreptile;Useless Reptile;;\ntechnomancy-2;7898;technom;量子魔法2;Technomancy2;\nquickstore;7899;quickstore;快速贮藏重制版;QuickStoreRemake;\nyour-items-are-safe;7900;youritemsaresafe;Your Items Are Safe;;\ntechemistry;7901;techemistry;Techemistry;;\nthe-missing-villages;7902;themissingvillages;The Missing Villages;;\ncontinents;7903;continents;Continents;;\nyungs-better-ocean-monuments;7904;betteroceanmonuments;YUNG 的海底神殿优化;YUNG's Better Ocean Monuments;\nechoing-depths;7905;echoingdepths;Echoing Depths;;\n;7906;EUC;ER的传说之下挑战MOD;ER's Undertale Challenges Mod;EUC\nadvanced-finders;7907;adfinders;Advanced Finders;;\nmaple-forest;7908;maple_forest;枫林;Maple Forest;\n;7909;legacyinputpatch;Legacy Fabric 输入补丁;LegacyInputPatch;\ndroplight;7910;droplight;Droplight;;\nxanders-sodium-options;7911;xanders-sodium-options;Xander's Sodium Options;;XSO\nentity-view-distance;7912;entity-view-distance;Entity View Distance;;\ncreeper-ai-updated;7913;creeperaiupdated;Creeper AI Updated;;\nsihywtcamd;7914;sihywtcamd;So I heard you were talking crap about Minecraft's difficulty?;;SIHYWTCAMD\nsihywtcamc;7915;sihywtcamc;So I heard you were talking crap about Minecraft's combat?;;SIHYWTCAMC\nwhy-stacks-of-16;7916;wso16;Why stacks of 16?;;WSO16\njumpquilt;7917;jumpquilt;JumpQuilt;;\noh-how-the-crafting-has-tabled;7918;morecraftingtables;Oh, How the Crafting Has Tabled!;;\n;7919;;TFC:模组联动;TFC:ModsLinkage;\nroughly-enough-items-hacks;7920;rei_plugin_compatibilities;REI Plugin Compatibilities;;REIPC\npiglin-expansion;7921;piglin_expansion;猪灵扩展;Piglin Expansion;\n;7922;not-enough-servers;Not Enough Servers;;NES\ndreadsteel;7923;dreadsteel;悚怖钢;Dreadsteel;\nauto-hud;7924;autohud;Auto HUD;;\ncamera-utils;7925;camerautils;Camera Utils;;\n;7926;hmyukari;YukariLib无害化;HarmlessYukari;\nroughly-enough-items-server-component;7927;roughlyenoughitems_servercomponent;Roughly Enough Items Server Component;;REI-SC\ndaves-building-extended;7928;daves-building-extended,davebuildingmod;Dave's Building Extended;;\n;7929;rechiseled_bookshelves;Rechiseled Bookshelves;;\ndimensional-pockets-ii;7930;dimensionalpocketsii;四次元口袋2;Dimensional Pockets II;DP2\n;7931;scandy;糖果创意;Cnady Create;CdC\n;7932;calculator-rinf;Calculator RinF;;\nblack-hole-storage;7933;blackholestorage;黑洞存储;Black Hole Storage;\nhudtweaks;7934;hudtweaks;HUDTweaks;;\nmod-button;7935;modsetting;Forge Mod Button in Game Menu;;\nopt-carpet-addition;7936;opt-carpet-addition;OptCarpetAddition;;\ntrees-do-not-float;7937;tdnf;Trees Do Not Float;;TDNF\ncosmos-library;7938;cosmoslibrary;Cosmos Library;;\ngroovyscript;7939;groovyscript;GroovyScript;;GrS\n;7940;wolftail;Wolftail;;\npotion-ring-reforged;7941;potionring;药剂环重铸;Potion Rings - REFORGED;\nscp-overtime;7942;overtime;SCP: Overtime;;SCPO\nchubby-stuff-fabric;7943;chubbystuff;Chubby Stuff!;;\nbetter-brightness-slider;7944;brightnessslider;Better Brightness Slider;;\nopen-parties-and-claims;7945;openpartiesandclaims;Open Parties and Claims;;OPAC\nloading-screen-tips;7946;loadingscreentips;Loading Screen Tips;;\nbetter-tips;7947;bettertags;Better Tag NBT Tips;;\nimmediatelyfast;7948;immediatelyfast;ImmediatelyFast;;\nopac-fabric-create-support;7949;opaccreatesupport;Create Support for Open Parties and Claims;;\nsteel-for-fabric;7950;steel;钢;Steel;\nmekanism-tweaks;7951;mekanismtweaks;Mekanism Tweaks;;\nchanged-minecraft-mod;7952;changed;Changed: Minecraft Mod;;\nhello-crosshair;7953;hellocrosshair;Hello Crosshair;;\n;7954;;农夫乐事基岩版;Farmer's Delight Bedrock Edition;\nbendy-lib;7955;bendylib,bendy-lib;bendy-lib;;\nenchantment-transfer-forge;7956;enchantmenttransfer;Enchantment Transfer;;\ngolden-crops;7957;goldencrops;Golden Crops;;\nlil-wings;7958;lilwings;Lil' Wings;;\n;7959;oto;OTO;WithCommand;\n;7960;;Delights Better Life;;\n;7961;scpsharp;SCP: Sharp;;\nscary-mobs-and-bosses;7962;scary_mobs_and_bosses;Scary Mobs And Bosses;;\nelytra-replace;7963;elytra_replace;鞘翅替换;Elytra Replace;ER\nmt-garden-arsenal-forge;7964;gardenarsenal;MT：花园军火库;MT: GardenArsenal;\nvisual-overhaul;7965;visualoverhaul;Visual Overhaul;;\ndrawerfps-legacy;7966;drawerfps;储物抽屉渲染配置：遗产;DrawerFPS Legacy;\nstatus-effect-timer;7967;statuseffecttimer;Status Effect Timer;;\nlogical-zoom;7968;logical_zoom;Logical Zoom;;\n;7969;no_trampling_on_farmland;阻止踩踏农田;No trampling on farmland;\nthe-space-age-mod;7970;space;太空时代;The Space Age Mod;\ntouhou-origins;7971;touhouorigins;起源：东方 Project 附加包;Touhou Origins;\nearth-mobs;7972;earthmobsmod;地球生物;Earth Mobs;EM\n;7973;expansion_and_enhancement;扩展与增强;Expansion And Enhancement;EAE\nsimplemetals-tin;7974;simpleores_tin,simplemetals_tin;Simple Metals: Tin / Simple Ores: Tin;;\njust-enough-keys;7975;justenoughkeys;Just Enough Keys;;JEK\nwhy-am-i-on-fire;7976;whyamionfire;Why Am I on Fire?;;\nbouncing-balls-api;7977;bouncingballs_api;弹跳球 API;Bouncing Balls API;\nbouncing-balls;7978;bouncingballs;弹跳球;Bouncing Balls;\nteletubbies-mod;7979;teletubbies;天线宝宝;Teletubbies Mod;\nalmost-unified;7980;almostunified;Almost Unified;;\ndraw-bridge;7981;drawbridge;Draw Bridge;;\ncreative-fireworks;7982;creativefirework;Creative Fireworks;;\ninvertedbed;7983;invertedbed;Inverted Bed;;\ntime-arrow;7984;;Time Arrow;;\ncatch-me-if-you-can-cmiyc;7985;cmiyc;Catch Me If You Can!;;CMIYC\nallis-one-way-glass;7986;onewayglass;Alli的单向玻璃;Alli's One Way Glass;\nsanguis;7987;sanguis;Sanguis;;\nender-trigon;7988;endertrigon;Ender Trigon;;\nlitlaunch;7989;litlaunch;LitLaunch;;\npuzzle;7990;puzzle,Puzzle;Puzzle;;\nwhy-am-i-on-fire-refoxed;7991;yaif;WhyAmIonFire[Forge];;YAIF\n;7992;more-create-recipes_datapack;更多机械动力配方-数据包;MoreCreateRecipes - DataPack;MCR-D\n;7993;tdfp;暮色乐事：冰雪豪饮;Twilight Delight: Frozen Pride;TDFP\nftb-pack-companion;7994;ftbpc;FTB Pack Companion;;\nteams-stages;7995;teamsstages;Teams Stages;;\nkiwi-boi;7996;kiwiboi;Kiwi Boi;;\nrubidium-toolkit;7997;rubidium_toolkit;铷 · 工具箱;Rubidium Toolkit;RT\nadvanced-combat;7998;advancedcombat;高级战斗装备;Advanced Combat;\nadvanced-combat-revitalized;7999;advancedcombat;高级战斗装备重置;Advanced Combat (Revitalized);\ntotemic-overhaul;8000;totemicoverhaul;Totemic Overhaul;;\n;8001;nig;便宜的修复;Cheap Repair;\nbattlecorrection;8002;battlecorrection;战斗修正;BattleCorrection;BC\nmidnightcontrols-extra;8003;midnightcontrols-extra;MidnightControlsExtra;;\ni-wanna-skate;8004;iwannaskate;I Wanna Skate;;\ntectonic;8005;tectonic;Tectonic;;\nborn-in-chaos;8006;born_in_chaos_v1;生于混沌;Born in Chaos;\neureka-ships;8007;vs_eureka;Eureka! Ships! for Valkyrien Skies;;\ngoofy-goober;8008;goofygoober;Goofy Goober;;\ntime-stop-clock-mod;8009;timeclock;时之钟;Time Stop Clock;TSC\ndynamic-asset-generator;8010;dynamic_asset_generator;Dynamic Asset Generator;;\nexcavated-variants;8011;excavated_variants;Excavated Variants;;\n;8012;reachdisplaymod;Reach Display Mod;;\n;8013;combodisplay;Combo Display Mod;;\nsurvival-light-blocks;8014;survivallightblocks;Survival Light Blocks;;\n;8015;lottery;兑换码 / 礼品码;RedemptionCode;RC\ngarden-stuff;8016;GardenStuff,GardenCore,GardenContainers,GardenTrees;园林组合;Garden Stuff;\nforgottenrecipes;8017;forgottenrecipes;Forgotten Recipes;;\ncorgilib;8018;corgilib;CorgiLib;;\nmoms-love;8019;momlove;母爱;Mom's Love;\nfood-effect-tooltips;8020;foodeffecttooltips;食物效果显示;Food Effect Tooltips;\n;8021;resounding;Resounding;;\nhbm-fixes;8022;zhbmfixes;HBM Fixes;;\nstatus-effect-bars;8023;status-effect-bars;Status Effect Bars;;\nmonsters-in-the-closet;8024;monster-in-the-closet,monsters_in_the_closet,monster_in_the_closet;Monsters in the Closet;;\nrecipe-unlocker;8025;recipe-unlocker;Recipe Unlocker;;\nmetamorph-max;8026;metamorphmax;变形拓展;Metamorph Max;\nkubejs-tinkers-construct;8027;kubejs_tinkers_construct;KubeJS Tinkers Construct;;\ncrazy-features;8028;;Crazy Features;;\nghost;8029;ghosts;Ghost;;\nbetter-stats;8030;betterstats;更好的统计信息界面;Better Statistics Screen;BSS\n;8031;shropshire;什罗普郡的花园;;\n;8032;asian_difficulty;亚洲难度;AsianDifficulty;ADy\n;8033;yvanchuxiuzhen;凡人修仙人界篇;;\nabyssalblocks;8035;abyssalblocks;深渊国度幸运方块;AbyssalBlocks;\narchon;8036;archon;Archon;;\nstackie;8037;stackie;Stackie;;\n;8038;cullclouds;Cull Clouds;;\n;8039;superfancyclouds;SuperFancyClouds;;\nstage-tabels;8040;stagetables;Stage Tables;;\ntemporal-dynamics;8041;temporaldynamics;Temporal Dynamics;;\nvillagertradingban;8042;vtb;VillagerTradingBan;;\nenchantment-machine;8043;enchantmentmachine;Enchantment Machine;;\nyosbr;8044;yosbr;Your Options Shall Be Respected;;YOSBR\nminefortress-rts;8045;minefortress;MineFortress RTS;;\nonebar;8046;onebar;单条;OneBar;\nberdinskiybears-armor-hud;8047;armor_hud;BerdinskiyBear's Armor HUD;;\n;8048;blockoverlay;Block Overlay;;\n;8049;PingTag;Ping Tag;;\ntiered-tooltips;8050;tieredtooltips;Tiered Tooltips;;\nrgb-blocks-forge;8051;rgbblocks;调色方块;RGB Blocks;\n;8052;deathinterface;独特的死亡界面;UniqueDeathInterface;UDI\nquicksand-fabric;8053;quicksand;流沙;Quicksand;\nbetter-durability;8054;betterdurability;更好的耐久度;Better Durability;\npassthrough-signs;8055;passthroughsigns;Passthrough Signs;;\nexcorrelation;8056;exco;Excorrelation;;Exco\npacket-size-doubler;8057;packet_size_doubler;Packet Size Doubler;;\n;8058;packetsizedoublerforge;Packet Size Doubler Forge;;\narachnophobia-mode-for-minecraft;8059;arachnophobiamode;Arachnophobia Mode For Minecraft;;\nvillagerconfig;8060;villagerconfig;村民配置;Villager Config;\njasm;8061;speedometer;Speedometer / Just Another Speedometer Mod;;JASM\npaginated-advancements;8062;paginatedadvancements;分页进度;Paginated Advancements & Custom Frames;\n;8063;backportgamerules;Backport Gamerules;;\nstructures-compass;8064;structures_compass;Structures' Compass;;\nswiss-cheese;8065;swisscheese;Swiss Cheese Caves;;\nvitalize;8066;vitalize;Vitalize;;\ni18n-nlts;8067;i18nupdatemod;自动汉化更新非长期支持版;I18nUpdateMod Non-LTS;i18n-NLTS\ningame-info-xml-serene-seasons;8068;igisereneseasons;InGame Info XML - Serene Seasons;;\ningame-info-xml-addon-serene-seasons;8069;igi|sereneseasons;Seasons XML Integration;;\n;8070;tiab;时间之瓶：遗产;Time in a bottle standalone: Legacy;TIAB\n;8071;backpack-backup;背包备份;BackpackBackup;\nstaff-of-building;8072;staffofbuilding;建筑之杖;Staff of Building;\ndark-mode-everywhere;8073;darkmodeeverywhere;夜间模式无处不在;Dark Mode Everywhere;\ncustom-shop;8074;custom_shop;自定义商店;CustomShop;CTS\nanother-liquid-milk;8075;almm;Another Liquid Milk;;\ndark-mode-everywhere-fabric;8076;darkmodeeverywhere;夜间模式无处不在Fabric版;Dark Mode Everywhere - Fabric;\n;8077;wasteland;不毛之地;Waste Land;\nbartering-station;8078;barteringstation;易物站;Bartering Station;BS\ndark-paintings;8079;darkpaintings;Dark Paintings;;\n;8080;kusl_recipe_fix;KuSL Recipe Fix;;\nfiltpick;8081;filtpick;拾取筛;FiltPick;FP\n;8082;pog;Better on Bedrock;;\ndeepslatecutting;8083;deepslatecutting;DeepslateCutting;;\neasy-anvils;8084;easyanvils;简易铁砧;Easy Anvils;EA\nleaves-be-gone;8085;leavesbegone;Leaves Be Gone;;LG\nrecipe-cache;8086;recipecache;配方缓存;Recipe Cache;\nmob-catcher;8087;mobcatcher;Mob Catcher;;\n;8088;quiltgoslightlyfasterlol;🗿QuiltGoSlightlyFasterLol🗿;;\n;8089;dont_starve_to_deathreload;不要饿死重置版;Don't Strave To Death:Reload;DSDR\n;8090;tamedmonster;驯化怪物;Tamed Monster;\nguns-rpg-waystone-addon;8091;grpgwaystones;Guns-RPG Waystones;;\nbiospheres-fabric;8092;biospheres;现代生物圈;Modern Biospheres;\nbetter-snowball-fight-2;8093;bsf;更好的打雪仗3;Better Snowball Fight 3;BSF3\nfeathers;8094;feathers;Feathers;;\nbrb;8095;betterrecipebook,brb;更好的配方书;Better Recipe Book;BRB\nboatoverhaul-forge;8096;boatoverhaul;行船机制修改;Boat Overhaul;BO\nsculk-warden;8097;warden_and_sculk;Warden & Sculk;;\nbetter-questing-gamestages-expansion;8098;bq_gs;更好的任务-游戏阶段扩展;Better Questing - Gamestages Expansion;\nomega-chips;8099;omegachips;Omega Chips;;\nyoudroppedthis;8100;ydt;You Dropped This;;\n;8101;;搬箱器;Chest Transporter;\n;8102;road_stuffs_refabricated;道路重置版;Road Stuffs Refabricated;RSR\ntoomanyorigins;8103;toomanyorigins;TooManyOrigins;;\nevil-regeneration;8104;evilregeneration;Evil Regeneration;;\nfood-treasure-box;8105;alien_food;食物宝盒;Food Treasure Box;\nrevolution;8106;revolution;Revolution;;\napugli;8107;apugli;Apugli;;\nharvest-season;8108;harvestseason;Harvest Season;;\n;8109;ic2fix;IC2Fix;;\n;8110;extendedbookshelves;Extended bookshelves;;\ncocoainput;8111;cocoainput;CocoaInput;;\nchargers;8112;chargers;Chargers;;\n;8113;axolotlbuckets;Axolotl Buckets;;\nrecipe-book-is-pain;8115;recipe_book_is_pain,recipe-book-is-pain;Recipe Book is Pain;;RBIP\n;8116;one-bad-apple;One Bad Apple;;\n;8117;high-unrestricted,wacfabric,wacforge;世界高度控制;World Altitude Contorl / High Unrestricted;WAC\nwith-json;8118;ovo,with_json;OVO;WithJson;\nmystical-nature;8119;mysticalcrops;神秘自然;Mystical Nature;\n;8120;formula_addition;配方追加;Formula addition;FA\nrandomlib;8121;randomlib;RandomLib;;\n;8122;kinventory;KInventory;;\npullup;8123;pullup;飞行警报;Pullup;PLUP\nid-primeval;8124;primeval;Primeval;;\ncolorfulazaleas;8125;colorfulazaleas;杜鹃缤纷;Colorful Azaleas;\npigeon-post;8126;pigeonpost;Pigeon Post;;\nhunted-api;8127;hunted;Hunted API;;\n;8128;kablade;斩无不断;Ka Blades;\nbigger-structures;8129;bigger_structures;Bigger Structures;;\nfind-my-friends;8131;findmyfriends;Find My Friends;;\n;8132;;Legacy LazyDFU;;\nfallout-wastelands;8133;fallout_wastelands;Fallout Wastelands;;\n;8134;fangyu;防御工艺;DefenseCraft;DC\n;8135;safety;更安全的MC;Safety;SFM\n;8136;letitsnow;Let It Snow;;\ndons-lightning-rod-mod;8137;dlr;Don's Lightning Rod;;\nrainglow;8138;rainglow;Rainglow;;\nnethers-delight-fabric;8139;nethersdelight;下界乐事 (Fabric 版);Nether's Delight (Fabric);\nquicksand-neoforge;8140;quicksand;流沙 (NeoForge);Quicksand (NeoForge);\naxolotl-bucket-fix-forge;8141;axolotlbucketfix;美西螈桶修复 Forge 版;Axolotl Bucket Fix (Forge);\nscape-and-run-parasites-survival-addon-by-nocube;8142;nocubessrpsurvival;Scape and Run: Parasites Survival Addon;;\nscape-and-run-parasites-nests-addon-by-nocube;8143;nocubessrpnests;Scape and Run: Parasites Nests Addon;;\n;8144;emiffect;EMIffect;;\nvillagers-sell-animals;8145;villagersellanimals;Villagers Sell Animals;;\nteal-bricks;8146;tealbricks;Teal Bricks;;\nbodacious-berries;8147;bodacious_berries;多汁浆果;bodacious berries;\n;8148;bag_of_holding;手袋;Bag Of Holding;BOH\nrgb-chat;8149;jianghun;RGB Chat;;\n;8150;sodium-sight-distance-unlocked;钠视距解锁;Sodium Sight Distance Unlocked;\ndiamond-caliber;8151;caliber;Diamond Caliber;;\n;8152;emi_farmersdelight;EMI Farmer's Delight;;\nworld-stripper;8153;worldstripper;World Stripper;;\ndayz-remastered;8154;future_of_the_dead;DayZ 重制版;DayZ Remastered;\nvisuality-reforged;8155;visuality;可视性：重铸;Visuality: Reforged;\nease-out-night-vision;8156;nnvf,easeoutnightvision,ease-out-night-vision;Ease Out Night Vision / No Night Vision Flickering;;NNVF\nathelas;8157;athelas;阿塞拉斯;Athelas;\nmore-beautiful-buttons;8158;more_beautiful_buttons,morebeautifulbuttons;More Beautiful Buttons;;\naoa-end-realmstone;8159;aoaendr;AoA: Extra Realmstones;;\n;8160;;时间到了;Time's up;TIU\nadaptive-tooltips;8161;adaptive-tooltips;Adaptive Tooltips;;\nvariantbarrels;8162;variantbarrels;Variant Barrels;;\nvariant-crafting-tables-fabric;8163;variantcraftingtables;Variant Crafting Tables [Fabric];;\nimmersive-structures;8164;imst;Immersive Structures;;IMST\navaritia-port-fabric;8165;avaritia;Greedy Grinding Mod - Avaritia (Fabric);;\nexplorify;8166;explorify-fabric,explorify;Explorify – Dungeons & Structures;;\nvariant-bookshelves-fabric;8167;variant_bookshelves;Variant Bookshelves;;\nvariant-tiered-shields-fabric;8168;variant_shields,variantshields;Variant Shields;;\nlibertys-villagers;8169;libertyvillagers;自由村民;Liberty's Villagers;\nrough-mobs-2;8171;roughmobs;粗暴的怪物2;Rough Mobs 2;\nbarren-isles;8172;barrenisles;Barren Isles;;\nkorgelin;8173;korgelin;Korgelin;;\nvariant-composters-fabric;8174;variantcomposters;Variant Composters;;\nvariant-grindstones-fabric;8175;variant_grindstones;Variant Grindstones;;\ncustom-villager-trades-new;8176;customvillagertrades;自定义村民交易;Custom Villager Trades;CVT\nspice-of-life-apple-pie-edition;8177;solapplepie;生活调味料：苹果派版;Spice of Life: Apple Pie Edition;\nxp-obelisk;8178;xps;经验方尖碑;XP Obelisk;\nenhanced-searchability;8179;enhanced-searchability;搜索增强;Enhanced Searchability;\ncompact-ui;8180;compact-ui;Compact UI;;\nlets-do-wine;8181;vinery;葡园酒香;Let's do Wine / Vinery;\nwealthyandgrowth;8182;wealthy_and_growth;WealthyAndGrowth;;\nsubtitle-highlight;8183;subtitle_highlight;字幕高亮;Subtitle Highlight;\nclockhud;8184;clock-hud,clockhud;Clock HUD;;\n;8185;sorln;限制格数的生存;Survival of restricted lattice number;SORLN\nvariant-lanterns-fabric;8186;variant_lanterns;Variant Lanterns;;\nlanterns-bow;8187;walllanterns;Lanterns Belong on Walls;;\nauto-crouch;8188;auto-crouch;Auto Crouch;;\nmy-village-pack;8189;myvillage;My Village Pack;;\nvariant-sticks-stuff;8190;vsas;Variant Sticks & Stuff;;VSAS\nmultiple-server-lists;8191;multiple-server-lists;Multiple Server Lists;;MSL\nsuperflat-biomes;8192;superflatbiomes;Superflat Biomes;;\ncombat-music;8193;combat_music;Combat Music;;\nyuoenchants;8194;yuoenchants;Yuo的更多附魔;YuoEnchants;\nhibernal-herbs;8195;hibernalherbs;Hibernal Herbs;;\nbetter-resource-pack-sorting;8196;better-resource-pack-sorting;Better Resource Pack Sorting;;\nsound-physics-remastered;8197;sound_physics_remastered;物理声效重制版;Sound Physics Remastered;\n;8198;fix_quark_3951;Quark3951号issue修复;FixQuark3951;\nstarcoffee;8199;starcoffee;Star Coffee;;\nseed-finder-mod;8200;seedfindermod;Seed Finder;;\npotato-food;8201;potatofood;Potato Food;;\n;8202;morerecipes;更多配方;More Recipes;MR\ncompassplus;8203;compassplus;Compass Plus;;\ncreate-industry;8204;createindustry,tfmg;机械动力：工业长路;Create: Industry/Create: The Factory Must Grow;TFMG\nsimple-splash-screen;8205;simplesplashscreen;Simple Splash Screen;;\nunusual-prehistory-forge;8206;unusualprehistory;Unusual Prehistory;;\nbiospheres;8207;biospheres;生物圈;Biosphere;\nqkl;8208;qkl,qkl_core,qkl_qsl,qkl_minecraft;Quilt Kotlin Libraries;;QKL\n;8209;arboria;Arboria: Biome Enhancement;;\ndynamic-trees-tech-reborn;8210;dynamictreestechreborn;动态的树：科技复兴附属;Dynamic Trees - Tech Reborn;\n;8211;bdd;半颠倒生存挑战;;BDD\ndaylight-mobs-reborn;8212;daylight_mobs_reborn;Daylight Mobs Reborn;;\n;8213;delightful_tinkers;工匠乐事;Delightful Tinkers;DT\ntoast-manager;8214;toastmanager;Toast Manager;;\nexplosives-i-approve;8215;throwmod;Explosives;;\nminers-delight-plus;8216;miners_delight,minersdelight;矿工乐事;Miner's Delight / Miner's Delight +;\nigniting-arrows;8217;ignitingarrows;Igniting Arrows;;\nrise-of-the-animagus;8218;morphspellpack;Rise of the Animagus;;\nlost-books;8219;lostbooks;Lost Books;;\nshingeki-no-kyojin-attack-on-titan;8220;attack_on_titan;Shingeki no Kyojin;;\nhelicopters-madness;8221;helicopters_madness;Helicopters Madness;;\nfanon-mutant-entities;8222;fme;Fanon Mutant Entities;;\ntechnical-enchant;8223;teplus;更多附魔重置版;TechnicalEnchant+;TE+\nnaturally-spawning-vex;8224;naturallyspawningvex;Naturally Spawning Vex;;\nbetter-game-menu;8225;bettergamemenu;Better Game Menu;;\n;8226;;MQ的连锁采集;MQ's Chain;MQC\nseasonhud;8227;seasonhud;SeasonHUD;;\nstack-to-nearby-chests;8228;stack-to-nearby-chests;堆叠到附近的箱子;Stack to Nearby Chests;\ngmod-title-screen;8229;gmod-title-screen;GMod Title Screen;;\ncreate-steam-n-rails;8230;railways;机械动力：汽鸣铁道;Create: Steam 'n' Rails;SnR\nneutron;8231;neutron;Neutron;;\napplied-cooking;8232;appliedcooking;应用厨房;Applied Cooking;\n;8233;litematica_printer;投影打印机;Litematica Printer;\nrecipe-printer;8234;recipeprinter;Recipe Printer;;\nbotarium;8235;botarium;Botarium;;\nqfapi-and-qsl-biomefix-1-18-2;8236;quilted_fabric_api;QFAPI and QSL - Biomefix;;\n;8238;cottonmod;Cotton;;\n;8239;someone_things;狼尾杂物;Things;\nhoney-expansion-add-on-for-farmers-delight;8240;honeyexpansion;蜂蜜拓展;Honey Expansion;\nmoguns;8241;moguns;Mo' Guns;;\ndayz-minecraft;8242;dayz;DayZ for Minecraft;;\ninfusion-table;8243;infusion_table;灌注祛魔台;Infusion Table;\nbackroomtastic;8244;the_backrooms;Backrooms With 19 Levels;;\nxps-things;8245;exps_things;经验之谈;XP's Things;\ngravityapi;8246;gravity_api;Gravity API;;\nfabric-super-secret-settings;8247;supersecretrevival;Super Secret Revival;;\nitem-alchemy-fabric;8248;itemalchemy;Item Alchemy;;\nbat-elytra;8249;bat_elytra;蝙蝠鞘翅;Bat Elytra;\nsuper-secret-settings;8250;sss;Super Secret Settings;;SSS\n;8251;petphraseplus;口癖plus;PetPhrasePlus;PPP\n;8252;plumeconfig;Plume Config;;Plug\ngeophilic;8253;geophilic;Geophilic – Biome Overhauls;;\ncritter-fights;8254;critterfights;Critter Fights;;\nhypothermic;8255;coldwaters;Hypothermic / Coldwaters❄️;;\nnosecrets;8256;nosecrets;不秘密;NoSecrets;\nextra-creeper-types;8257;excp;Extra Creeper Types;;\nloot-casket;8258;lootcasket;Loot Casket;;\nfarmers-bundle-of-joy;8259;farmers_bundle;Farmer's Bundle of Joy;;\nmusical-allays;8260;musicalallays;Musical Allays;;\npure-chaos-chaos-awakens;8261;purechaos;Pure Chaos;;\nflatter-entities;8262;flatterentities;Flatter Entities;;\ndaves-potioneering;8263;davespotioneering;Dave's Potioneering;;\nlast-days-of-humanity-tweaks;8264;hundreddayz;LDoH Tweaks;;\nspace-bosstools-giselle-addon;8265;beyond_earth_giselle_addon;Beyond Earth: Giselle Addon;;\n;8266;spawn_eggs_recipes,ser,ser_for_alex;刷怪蛋配方;Spawn Eggs Recipes;SER\nmined-rider-kuuga;8267;mined_rider_kuuga;Mined Rider KUUGA;;\ninventory-essentials;8268;inventoryessentials;Inventory Essentials;;\nastral-additions;8269;aam;星辉附加;Astral Additions;\nsimple-copper-pipes;8270;copper_pipe,lunade,simple_copper_pipes;Simple Copper Pipes;;\nadvanced-nbt-tooltips;8271;advanced-nbt-tooltips;高级NBT提示框;Advanced NBT Tooltips;\nfollow-me;8272;followme;Follow Me;;\nstabx-modern-guns;8273;stabxmodernguns;Stabx Modern Guns;;\njusthammers;8274;justhammers;锤子合集;JustHammers;\nreinforced-deepslate-crafting;8275;reinforced_deepslate_crafting;强化深板岩的合成;Reinforced Deepslate Crafting;RDC\njaopcacustom;8276;jaopcacustom;JAOPCA自定义;JAOPCA Custom;\nfriendly-fire;8277;friendlyfire;Friendly Fire;;\ncompanion;8278;companion;Companion 🐕;;\nsebastrnlib;8279;sebastrnlib;SebastrnLib;;\nwhat-music-is-this;8280;wmit;What Music Is This?;;WMIT\n;8281;bayou_delight;沼泽乐事;Bayou Delight;BD\nelemental-combat;8282;elementalcombat;元素战斗;Elemental Combat;\nroasted;8283;roasted;烘烤;Roasted;\nlotr-attack-indicator-addon;8284;attackindicator;魔戒攻击指示器;LOTR Attack Indicator Addon;\nsteels-delight;8285;steelsdelight;Steel's Delight;;\nlotr-pouch-viewer-addon;8286;pouchviewer;魔戒口袋内容展示;LOTR Pouch Viewer Addon;\nfastfood-delight;8287;fastfooddelight;快餐乐事;FastFood Delight;\nfestive-delight;8288;festive_delight;节日乐事;Festive Delight;\nproject-arsenal;8289;projectarsenal;Project Arsenal;;\nbathroom-blocks;8290;bathroom;浴室方块;Bathroom Blocks;\ngalosphere;8291;galosphere;Galosphere;;\nlazydfu-agent;8292;;DFU 禁用器;Lazydfu-agent;\n;8294;mana_box;魔力盒;Mana Box;\nnether-mushroom-stew;8295;nether_mushroom_stew;下界蘑菇煲;Nether Mushroom Stew;\nshibapi;8296;shibapi;Shibapi;;\ngalosphere-delight-a-farmers-delight-add-on;8297;galosphere_delight;Galosphere Delight;;\nstrong-crops;8298;strongcrops;Strong Crops;;\nvegetarian-delight;8299;vegetarian_delights;素食乐事;Vegetarian Delight;\nresourceful-config;8300;resourcefulconfig;Resourceful Config;;\ncoppers-delight;8301;coppersdelight;Copper's Delight;;\nenderites-delight;8302;enderitesdelight;Enderite's Delight;;\nsimply-steel-forge;8303;simplysteel;Simply Steel;;\nwilder-world;8305;wilderworld;Wilder World;;\nadvanced-solar-materials;8306;advsolars;Advanced Solar Materials;;\n;8307;survival-tooltips;Survival Tooltips;;\n;8308;notifyme;NotifyMe;;\n;8309;damagedarrows;DamagedArrows;;\nmodulargolems;8310;modulargolems;傀儡装配;Modular Golems;MG\ntooltip-scroll-fabric;8311;toolscroll;Tooltip Scroll;;\nlegoatooms-golden-berries;8312;goldenberries;legoatoom's Golden Berries;;\nnyfcore;8313;nyfcore;NyfCore;;\ncall-of-the-wild;8314;cotw;荒野之声;Call of the Wild;cotw\nmore-lapis-lazuli;8315;morelapis;More Lapis Lazuli;;\nslimefun-essentials;8316;sftoemi,slimefun_essentials;Slimefun Essentials / SlimefunToEmi;;SFE\ndecoration-delight;8317;decoration_delight;装饰乐事;Decoration Delight: Refurbished;\n;8318;hcookbook;Havio的烹饪书;Havio's Cookbook;\nformidablefarmland;8319;formidablefarmland;FormidableFarmland;;\n;8320;katana;刀艺;KatanaCraft;\nsurvivality;8321;survivality;Survivality;;\ndynamic-crosshair-compat;8322;dynamiccrosshaircompat;动态准星兼容;Dynamic Crosshair Compat;\nlitewolfcore;8323;litewolfcore;LiteWolfCore;;\nvineyard-mod;8324;vineyardmod;Vineyard;;\n;8325;affectionate;Affectionate;;\nmo-colors;8326;mocolors;Mo' Colors;;\npeek;8327;peek;Peek;;\nfancy-dyes;8328;fancydyes;Fancy Dyes;;\n;8329;;进度追加;Advancements addition;AA\nextra-mod-integrations;8330;extra-mod-integrations;EMI Addon: Extra Mod Integrations;;ExMI\nmod-settings;8331;modsettings;Mod Settings for Fabric;;\ndont-disappear-on-me;8332;dontdisappear;Don't dissapear on me;;\nfastquit;8333;fastquit;快速退出;FastQuit;\nvioleta-cannons;8334;violetacannons;Violeta Cannons;;\nwar-of-valor;8335;shocked;War of Valor;;\n;8336;qdf;日予签到;Qiandao For;QDF\nno-tool-break;8337;notoolbreak;No Tool Break;;\nmotionblur-fabric;8338;motionblur;动态模糊;Motion Blur;\nepic-paladins;8339;arclight;Epic Paladins;;\nbrazier;8340;brazier;生灵火盆;Brazier;\nstackcalc-revived;8341;stackcalc;StackCalc Revived;;\nwith-thursday-delight;8342;thursday_delight;乐在星期四;With Thursday Delight;\nosmiummod;8343;osmium;锇;Osmium;\n;8344;mgbuttons;Command GUI Buttons;;\nwagyourminimap;8345;wagyourminimap;WagYourMinimap;;WYM\nchunks-fade-in;8346;chunksfadein;区块淡入动画;Chunks fade in;\nsword-displays;8347;sworddisplay;刀剑展示架;Sword Displays;\nslabfishs-delights;8348;slabfishdelight;Slabfish's delights;;\nsaltymod-expanded;8349;saltymod;SaltyMod Expanded;;\nmasonry-blocks;8350;masonry_blocks;Masonry Blocks;;\n;8351;sqlib;SQLib;;\nmssql-jdbc;8352;mssql_jdbc;Microsoft SQL JDBC;;\npackages;8353;packages;自定义包裹;Packages;\ngiant-spawn;8354;giantspawn,giantspawn-fabric;Giant Spawn;;\n;8355;mtrsteamloco;纸板箱的交通扩展;Nemo's Transit Expansion;NTE\nserene-shrubbery;8356;serene_shrubbery;Serene Shrubbery;;\ngardens-of-the-dead;8357;gardens_of_the_dead;死亡花园;Gardens of the Dead;\npirate-hats;8358;pirate_hat;海盗帽子;Pirate Hats;\ndecor4fabric;8359;decor4fabric;Decor4Fabric;;\nmob-skills;8360;mobskills;Mob Skills;;\nclay-no-more-balanced;8361;clay_no_more_balanced;黏土不再制衡;Clay No More Balanced;\nblossom;8362;blossom;Blossom;;\nwhipdashing;8363;whipdashing;Whipdashing;;\npotiontimestacker-forge;8364;potiontimestacker;Time Stacker;;\nbreezy;8365;breezy;Breezy;;\nheart-balance;8366;heartbalance;HeartBalance;;\nthrowabletorch-fabric;8367;throwabletorch,throwabletorchmod;ThrowableTorch;;\npotion-descriptions;8368;potiondescriptions;Potion Descriptions;;\nhalf-doors;8369;halfdoors;Halfdoors;;\ndecor4forge;8370;decor4forge;Decor4Forge;;\n;8371;kubejs-koro-extra-tools;KubeJS Koro Extra Tools;;\nmineterrariabosses;8372;mineterrariabosses;MineTerrariaBosses;;\nbiomes-in-jars-forge;8373;biomesinjars;群系罐子;Biomes in Jars;\n;8374;bocchitheblock;Bocchi the Block!;;\ncraftable-potion-combinations;8375;craftablepotioncombinations;Craftable Potion Combinations;;\npolylib;8376;polylib;PolyLib;;\n;8377;creeper-fire-charge-fix;苦力怕火焰弹修复;Creeper Fire Charge Fix;\ntags-binder;8378;tags_binder;Tags Binder;;\nfastcaps;8379;fastcaps;FastCaps;;\nthrowable-fluids;8380;throwablefluids;Throwable Fluids;;\ntime-in-a-bottle;8381;timeinabottle;时间之瓶 Fabric 版;Time in a Bottle (Fabric);\nupgraded-netherite-spartan-weaponry;8382;upgradednetherite_spartan;下界合金增强：斯巴达的武器;Upgraded Netherite : Spartan Weaponry;\nsimple-jams;8383;jamed;Simple Jams;;\nnethers-cruelty;8384;sweetys_nethers_cruelty;Nether's Cruelty;;\n;8385;;Create Enchantment Industry: Refabricated;;\natom-sweep;8386;atomsweep;基石-清理;Atom-Sweep;\nthe-gold-rush;8387;the_gold_rush;The Gold Rush;;\noptifabric-origins;8388;optifabric;OptiFabric Origins;;\nlng-deco;8389;lngdeco;LnG Deco;;\ndarkrpg-reborn;8390;darkrpg;黑暗 RPG：重生;DarkRPG: REBORN;\nmodpack-manager;8391;packmger;模组管理器;Modpack Manager;\nrocket-connector;8392;rocketconnector;Rocket Connector;;\nhot-and-cold;8393;heat;HOT and COLD;;\ndonthityourfriend;8394;donthityourfriend;DontHitYourFriend;;\ndrink-beer-unofficial-clockwerk-edition;8395;drinkbeer;Drink Beer Unofficial: Clockwerk Edition;;\nnovacraft;8396;nova_craft;NovaCraft;;\n;8397;myitian-hirespaintings;高分辨率画;High-Resolution Paintings;HRP\n;8398;;Rich Ores;;\nbetter-hurt-timer;8399;betterhurttimer;Better Hurt Timer;;BHT\ndrink-beer-refill;8400;drinkbeer;喝啤酒啦：再来一杯;Drink Beer Refill;\nminecraft-style-paintings;8401;minecraft_style_paintings;MC style paintings;;\npotato-cooking;8402;potato_cooking_mod_fabric,brian;Potato Cooking;;\n;8403;cftags;Conveniented Forge Tags;;CFT\nclienttime;8404;clienttime;ClientTime;;\nmcpitanlibarch;8405;mcpitanlibarch;MCPitanLib;;\n;8406;bontany_ore_trees_reborn;植物盆栽矿物树拓展：重制;Botany Ore Trees:Reborn;BOT:R\nmanure;8407;manure,manure-fabric;粪便;Manure;\nwell;8408;well;Well well well...;;\n;8409;;Sky Structures;;\nenderling-invaders;8410;enderlinginvaders;Enderling Invaders;;\nproton;8411;proton;Proton;;\nzetter-gallery;8412;zettergallery;Zetter Gallery;;\nhedge-magic;8413;hedgemagic;Hedge Magic;;\narcane-additions;8414;arcaneadditions;Arcane Additions;;\n;8415;LibLoader;LibLoader;;\ninstrument;8416;ipp;更多乐器;Instrument++;IPP\nrpg-gods;8417;rpggods;RPG Gods;;\n;8418;HeadCrafter;头颅制造者;HeadCrafter;\nmob-crossing;8419;mobcrossing;Mob Crossing;;\njust-enough-vehicles;8420;jev;Just Enough Vehicles;;JEV\n;8421;gift_from_the_black_dragon;黑龙赠礼;Gift From The Black Dragon;\nmax-health-fix;8422;maxhealthfix;Max Health Fix;;\nsynlib;8423;synlib;SynapseLib;;\ntell-me-my-items;8424;tellmemyitems;Tell Me My Items;;TMMI\npollen;8425;pollen;Pollen;;\nmavapi;8426;mavapi;More Axolotl Variants API;;MAVAPI\nplayer-hopper;8427;playerhopper;Player Hopper;;\nguaranteed-villager-conversion;8428;guaranteedvillagerconversion;Guaranteed Villager Conversion;;\nwindchimes;8429;windchimes;风铃;Windchimes;\n;8430;projecta;代号 A;ProjectA;\ndiamond-ingots;8431;diamondingots;钻石锭;Diamond Ingots;\n;8432;mouse-danmu,mousedanmu;鼠鼠弹幕;;\ninventory-sorting;8433;inventorysorter;Inventory Sorting;;\nmine-piece;8434;minepiece;Mine Piece;;\nmine-piece-animation;8435;minepiece_animation;Mine Piece Animation;;\nhbms-nuclear-tech-mod-extended-edition;8436;hbm;Hbm's Nuclear Tech - Extended Edition;;\ncustom-skyboxes;8437;customskyboxes;Custom Skyboxes;;\nbreedable-killer-rabbit;8438;breedablekillerrabbit,breedablekillerrabbit-fabric;可繁殖的杀手兔;Breedable Killer Rabbit;\nbqenergyexpansion;8439;bqenergyexpansion;BQEnergyExpansion;;\nmagic-and-wand;8440;tlot_magic_wand;法术与法杖;Magic&Wand;MW\nre-ftbqkeys;8441;ftbqlocalkeys,ftbqkeys;FTB任务本地化键：重置;Re-FTBQLocalizationKeys;Re-FTBQKeys\nenchantment-sort;8442;enchsort;附魔排序;Enchantment Sort;\nwrapfix;8443;wrapfix;换行修复;WrapFix;\ngem-crabs;8444;gem_crab;宝石蟹;Gem Crabs;\ncreate-rainbow-compound;8445;rainbowcompound;机械动力：彩虹化合物;Create: Rainbow Compound;CRC\ninmis-back-pack-mod-retextured;8446;inmis;Inmis Backpack Retextured;;\nthonkutil;8447;thonkutil;ThonkUtil;;\nxp-storage;8448;xp_storage;XP Storage;;\nxp-storage-trinkets;8449;xp_storage_trinkets;XP Storage - Trinkets;;\ncarpet-layers;8450;carpet-layers;Carpet Layers;;\nitemswapper;8451;itemswapper;ItemSwapper;;\ncaxton;8452;caxton;Caxton;;\ntreasure2-wizardry-loot-pack;8453;treasure2_wizardry_lp;Treasure2: Wizardry Loot Pack;;\nlaser-creeper-robot-dino-riders-from-space;8454;lcrdrfs;来自外太空的激光爬行者机器恐龙骑士;Laser Creeper Robot Dino Riders From Space;\npaladins-furniture;8455;pfm;Paladin的家具;Paladin's Furniture;PFM\ncompact-status-effects;8456;compactstatuseffects;迷你状态效果配置;Compact Status Effects;\n;8457;chunkbase_slimemod_ssp,chunkbase_slimemod;Slime Mod;;\nend-goblin-traders-fabric;8458;endgoblintraders;末地哥布林商人;End Goblin Traders;\n;8459;mikulib;MikuLib;;\n;8460;signin;签到系统;SignIn;\nplayersync;8461;playersync;玩家数据同步;PlayerSync;\ncreate-more-potatoes;8462;createmorepotatoes;机械动力：更多土豆;Create: More potatoes;CMP\nprimitive-beasts;8463;primitivemobsr;Primitive Beasts;;\nraided;8464;raided;Raided;;\nmoisturization-fabric;8465;moisturization;Moisturization;;\ntreasure2-twilight-forest-loot-pack;8466;Treasure2TwilightForestLP;Treasure2: Twilight Forest Loot Pack;;\nsanity-and-insanity;8467;sanity_and_insanity;Sanity and Insanity;;\ncrops-love-rain;8468;crops_love_rain;Crops Love Rain;;\ntill-zombies-tear-us-apart;8469;till_zombies_tear_us_apart;Till Zombies Tear Us Apart;;\ndifficult-raids;8470;difficultraids;Difficult Raids;;\n;8471;;粗矿块处理;BlockOfRawOreHandle;BOROH\ngag;8472;gag;Gadgets Against Grind;;GAG\nbleach-awaken-by-deephantom15;8473;bleachawaken;Bleach Awaken;;\nkleiders-custom-renderer-api;8474;kleiders_custom_renderer;Kleiders Custom Renderer API;;\nchefs-delight-fabric;8475;delightchef,chefsdelight;Chef's Delight;;\n;8476;industrial_creations;Industrial Creations;;ICDP\nmcgltf;8477;mcgltf;MCglTF;;\nadd-potion-into-your-food;8478;add_potion;给您下药了;Add Potion into Your Food;AP\n;8479;scarborough_fair;Scarborough Fair;;\n;8480;chunkbordium;Chunk Bordium;;\nthaumic-energistics-extended-life;8481;;神秘能源延续版;Thaumic Energistics Extended Life;\ncreate-ender-transmission;8482;createendertransmission;机械动力：末影传输;Create: Ender Transmission;CET\nhorror-elements-mod;8483;horror_elements_mod;Horror Elements mod;;\ncreate-extended-cogs;8484;extendedgears;机械动力：更多齿轮;Create: Extended Cogwheels;\ncreate-connected-block-textures;8485;create_connected_blocks;Create: Connected Block Textures;;\n;8486;elainalike;魔女之绘;Elainalike;\ncccbridge;8487;cccbridge;CC:C Bridge;;\nliquid-burner;8488;liquidburner;Liquid Burner;;\n;8489;;生电增强;SurvivalRedstoneEnhance;SRE\ncreate-electric-stonks;8490;create_electric_stonks;Create: Electric Stonks;;\ncreate-mounted-storages;8491;moremountedstorages;机械动力：更多存储挂载;Create: Mounted Storages;\nminecells;8492;minecells;我的细胞;Mine Cells - Dead Cells Mod;\n;8493;heatwaves;热浪;Heatwaves;\nsteves-mini-pouch;8494;stevesminipouch;Steve's Mini Pouch;;\ninfinite-abyss;8495;infinite_abyss;Infinite Abyss;;\n;8496;ktice;KubeJS Tinkers Construct Extra;;Ktice\ncreator-overlays;8497;mcro3overlay,creatoroverlays;Creator Overlays / MC Rule Of Thirds Overlay;;CO\nmaplecraft;8498;maplecraft;MapleCraft;;\ninvutils;8499;invutil;InvUtils;;\n;8500;with_fire;手工艺：升级版;With Crafts: Upgraded;\n;8501;usageticker;Usage Ticker;;\ndistant-worlds;8502;distant_worlds;Distant Worlds;;\n;8503;bocreate;超多合成配方;Biomes o' Create;BOCr\n;8504;avl;Advanced Vanilla Logistics;;AVL\nsimple-flashlight-port;8505;flashlight;Simple Flashlight Port;;\nalwaysopenwater;8506;aow;始终开放水域;AlwaysOpenWater;AOW\nquest-plaques;8507;questplaques;任务牌匾;Quest Plaques;\n;8508;cheese_dragon;CheeseDragon: Reborn;;\nvideo-player;8509;watchvideo;视频播放器;Video Player;\nstendhal;8510;stendhal;Stendhal;;\ndynamic-surroundings-remastered-fabric-edition;8511;;Dynamic Surroundings: Remastered Fabric Edition;;\nterraplusplus;8512;terraplusplus;TerraPlusPlus;;\nblahaj;8513;blahaj;布罗艾;Blåhaj;\ntinkers-levelling-addon;8514;tinkerslevellingaddon;Tinkers' Levelling Addon;;\nterramap;8515;terramap;Terramap;;TM\n;8516;;Ride Players;;\nvillagemarkermod-for-liteloader;8517;KaboVillageMarker;Village Marker;;\n;8518;showdurability;Show Durability;;\ndisable-enderman-picking-up-blocks;8519;disable_enderman_picking_up_blocks;禁用末影人搬运;Disable Enderman Picking Up Blocks;DEPB\nbeautified-chat-server;8520;beautifiedchatserver,beautifiedchatserver-fabric;Beautified Chat [Server];;\nbeautified-chat-client;8521;beautifiedchatclient,beautifiedchatclient-fabric;Beautified Chat [Client];;\n;8522;exp_shop;经验商店;Exp Shop;\naxes-are-weapons;8523;axesareweapons;Axes Are Weapons;;\n;8524;curseplus;诅咒+;Curse+;\noptifine-capes;8525;optifinecapes;Optifine Capes;;\n;8526;candycraftce;糖果世界社区版;CandyCraft Community Edition;CCCE\nimmersive-aircraft;8527;immersive_aircraft;沉浸式飞机;Immersive Aircraft;\ndefault-world-generator;8528;defaultworldgenerator;Default World Generator;;\n;8529;defaultworldgenerator;DefaultWorldGenerator;;\npasterdream;8530;pasterdream;帕斯特之梦;PasterDream;PD\nanimalistic;8531;animalisticmod;Animalistic;;\n;8532;qdresloader;QDResLoader;;\n;8533;prismconfig;Prism Config;;\nfabricskyboxes-interop;8534;fsb-interop,nuit_interop;Nuit Interop / FabricSkyBoxes Interop;;\nold-inventorism;8535;inventory;Old Inventorism;;\ntitle-fix;8536;title-fix;标题修复;Title Fix;\ncreate-alloyed-guns;8537;alloyedguns;Create: Alloyed Guns;;\n;8538;damp;受潮：升级版;Damp: Upgraded;\nluna-slimes;8539;lunaslimes;Luna Slimes;;\nspecial-drops;8540;special_drops;Special Drops;;\nforgebrainlib;8541;forgebrainlib;ForgeBrainLib;;\nrender-360;8542;render360;Render 360;;\n;8543;codeovencore;火炉核心;CodeOvenCore;COC\nasync-locator;8544;asynclocator;Async Locator;;\nminemention;8545;minemention;提及;MineMention;\numapyoi;8546;umapyoi;马儿蹦跳;Umapyoi;\norigins-full-moon;8547;originsfullmoon;起源：满月;Origins: Full Moon;\ncodechickenlib-unofficial;8548;;CodeChickenLib Unofficial;;CCLU\ncodechickencore-unofficial;8549;CodeChickenCore;鸡块核心非官方版;CodeChickenCore Unofficial;CCCU\npermafrost-biome;8550;permafrost_biome;permafrost biome;;\n;8551;;苹果肉;AppleFlesh;AF\nambient-additions;8552;ambientadditions;Ambient Additions;;\nsweep;8553;sweep;扫雷;Minesweeper;\n;8554;stonemod;我的石头不见了;StoneMod-missing;\nsekclib;8555;sekclib;SekCore Lib;;SekCLib\nbetweenlands-storage;8556;bsb;Betweenlands Storage;;\nore-stone-variants;8557;catlib;Ore Stone Variants;;OSV\nfood-effect-tooltips-forge;8558;foodeffecttooltips;食物效果显示Forge版;Food Effect Tooltips (Forge);\nalexs-armoury;8559;alexs_armoury;Alex's Armoury;;\n;8560;tiny_coals;小型煤炭Fabric版;Tiny Coals;\ndynamic-crops;8561;dynamiccrops;Dynamic Crops;;\nsuper-chocolate-maker;8562;choco;超级巧克力制造;Super Chocolate Maker;\nnaturalized;8563;naturalized;Naturalized;;\n;8564;disenchanter;祛魔;Disenchanter;\nrsls;8565;rsls;Raise Sound Limit Simplified;;\nemi-loot;8566;emi_loot;EMI Loot;;\n;8567;mfs;MC百科闪烁标语;MCmod Slogan;MS\nbackhand;8568;backhand;Backhand;;\n;8569;stones_cookbook;Stone的烹饪书;Stone's Cookbook;SC\nfarmers-delight-jei-plugin;8571;fdjei;Just Enough Farmer's Recipes;;\nkeybind-fix;8572;keybind_fix;Keybind Fix;;KF\ntales-of-villagers-the-infiltrators;8573;infiltrators;村民之书：渗透者;Tales of Villagers: The Infiltrators;Inf\n;8574;morecullingextra;More Culling Extra;;\ncarpet-fixes;8575;carpet-fixes;Carpet-Fixes;;\ner-core;8576;ercore;ER 核心;ER Core;ERC\nrings-of-ascension;8577;ringsofascension;提升戒指;Rings of Ascension;\n;8578;fanrenxiuxian;凡人修仙前置;;\n;8579;yuanchuzhushou;修士小助手;;\n;8580;minecraft-command-permissions;Minecraft Command Permissions Fabric;;\nblanket;8581;blanket-client-tweaks;Blanket;;\nconvenient-curios-container;8582;convenientcurioscontainer;Convenient Curios Container;;\nmoogs-voyager-structures;8583;mvs;Moog's Voyager Structures;;MVS\n;8584;coin;金币;Coin;\nextra-aoa-info;8585;aoainfo;额外虚无世界信息;Extra AoA Info;EAOAI\nopulence;8586;opulence;Opulence;;\nlonely-biome;8587;lonelybiome;孤独的生物群系;Lonely Biome;\nlamps;8588;lamps_plus_plus;Lamps++;;\n;8589;chinawarecraft;瓷器工艺;Chinaware Craft;CwC\ngeneric-ecosphere;8590;genericeco;Generic Ecosphere;;\nwired-redstone;8591;wiredredstone;Wired Redstone;;\npixelgalaxy;8592;GCPixelGalaxy;PixelGalaxy;;\nhopper;8593;hplus;漏斗+;Hopper+;\nexcalibur-the-mighty-sword;8594;excalibur;Excalibur: The Mighty Sword;;\nundergarden-paths;8595;ugpaths;深园小径;Undergarden Paths;\nundergarden-tetra-patch;8596;undergardenpatch;深暗之园：Tetra 兼容;Undergarden/Tetra Patch;\nuncrafter;8597;uncrafter;Uncrafter;;\n;8598;pebblegetter;PebbleGetter;;PG\n;8599;;进度淘汰赛;Knockout Advancements;\npangea-ultima;8600;christophers_creatures;Pangea Ultima;;\n;8601;fpsplus;FPS优化Legacy Fabric版;FPS Plus Legacy Fabric;\ntable-top-craft;8602;table_top_craft;Table Top Craft;;\nddyc;8603;dont_drop_your_compass;Dont Drop Your Compass;;DDYC\n;8604;stonecutter_damage_mr;Stonecutter Damage;;\nshipping;8605;shipping;Shipping;;\nstructureutils;8606;sutils;StructureUtils;;\n;8607;cloudmusic;云音乐;CloudMusic;\ni-like-wood;8608;ilikewood;I Like Wood;;\nmacaws-furnitures-biomes-o-plenty;8609;mcwfurnituresbop;Macaw 的家具：超多生物群系附属;Macaw's Furnitures - Biomes O' Plenty;\nmacaws-furnitures-oh-the-biomes-youll-go;8610;mcwfurnituresbyg;Macaw 的家具：你将去的生物群系附属;Macaw's Furnitures - Oh The Biomes You'll Go;\narmiger;8611;armiger;Armiger;;\nnews-pillagers;8612;illager_plus;News Pillagers;;\nrangers-haven;8613;rangers_haven;Ranger's Haven;;\nheyberryshutup;8614;heyberryshutup;Hey Berry! SHUT UP;;\ndestroy-on-death;8615;destroyondeath;Destroy on Death;;\n;8616;yes_steve_model;是，史蒂夫模型;Yes Steve Model;YSM\nmacaws-roofs-oh-the-biomes-youll-go;8617;macawsroofsbyg;Macaw 的屋顶：你将去的生物群系附属;Macaw's Roofs - Oh The Biomes You'll Go;\nmacaws-roofs-biomes-o-plenty;8618;macawsroofsbop;Macaw 的屋顶：超多生物群系附属;Macaw's Roofs - Biomes O' Plenty;\nconfigured-feature-saplings;8619;configured_feature_saplings;Configured Feature Saplings;;\ncoyote-time;8620;coyote_time;Coyote Time;;\n;8621;LegacyJavaFixer;Legacy Java Fixer;;LJF\nmacaws-bridges-modding-legacy;8622;mcwbridgesmoddinglegacy;Macaw 的桥梁：Modding Legacy 附属;Macaw's Bridges - Modding Legacy;\nmacaws-bridges-aurora;8623;mcwbridgesaurora;Macaw 的桥梁：Aurora 附属;Macaw's Bridges - Aurora;\nbetterlands;8624;betterlands;Betterlands;;\nkoremods;8625;koremods;Koremods;;\nheart-of-ender;8626;heartofender;Heart of Ender;;\nheart-of-spartan;8627;heartofspartan;Heart of Spartan;;\nmacaws-fences-biomes-o-plenty;8628;mcwfencesbop;Macaw 的栅栏与墙：超多生物群系附属;Macaw's Fences - Biomes O' Plenty;\nendemic;8629;endemic;Endemic;;\nlucys-sloths;8630;lucys_sloths;Lucy's Sloths;;\n;8631;gdtweaker;GDTweaker;;\ndrop-the-rock-no-tree-punching-compat;8632;droptherock;Drop the Rock - No tree punching Compat;;\nmacaws-bridges-abnormals;8633;mcwbridgesabnormals;Macaw 的桥梁：Abnormals 附属;Macaw's Bridges - Abnormals;\nmacaws-bridges-shroomed;8634;mcwbridgesshroomed;Macaw 的桥梁：Shroomed 附属;Macaw's Bridges - Shroomed;\nshroomed;8636;shroomed;Shroomed;;\nrlcraft-structures-not-official;8637;rlstructures;RLCraft Structures (not official);;\nmoving-sunflowers;8638;moving_sunflowers;Moving Sunflowers;;\nlunar-mana-generator;8639;lunar_mana;月光魔能制造器;Lunar Mana Generator;\nost-overhaul;8640;ostoverhaul;Ost Overhaul;;\nchunk-by-chunk;8641;chunkbychunk;Chunk By Chunk;;\nbeyond-planets-core;8642;beyond_planets_core;飞越群星：核心;Planeteer - Core;\nwebdisplays;8643;webdisplays;内置网页浏览器;WebDisplays;WD\nmcef;8644;mcef;Minecraft Chromium 嵌入式框架;Minecraft Chromium Embedded Framework;MCEF\n;8645;;Custom Throwable Weapons+;;\n;8646;;神秘农业：基岩版;Mystical Agriculture: Bedrock Edition;\n;8647;;Strat's Food Expansion;;\n;8648;;Strat's Paint;;\nthe-monk-mod;8649;monkmod;The Monk Mod;;\nexpanded-enderchest;8650;expandedenderchest;Expanded Enderchest;;\ntext-utilities;8651;textutilities;Text Utilities;;\n;8652;erukabaubles;奇妙饰品;ErukaBaubles;EB\nmemorysweep;8653;memorysweep;内存清扫;Memory Sweep;\n;8654;explosiveenhancement;爆炸动画增强;Explosive Enhancement;\nautostereogram;8655;eyething;Autostereogram;;\nbounced;8656;bounced;Bounced!;;\nidentifier-translation;8657;identifier-translation;标识符翻译;Identifier Translation;\nvanity;8658;vanity;Vanity: Core;;\nreid;8659;jeid;Roughly Enough IDs;;REID\nits-been-twelve-years;8660;ibty;It's Been Twelve Years;;IBTY\nriftlin;8661;riftlin;Riftlin;;\nsorcerium;8662;sorcerium;Sorcerium;;\nzeropoint;8663;zeropoint;ZeroPoint Energy API;;\ncampful;8664;campful;Campful;;\n;8665;soul;魂力;Soul;\nwell-rested;8666;well_rested;Well Rested;;\nraw-input-1-12-2;8667;rawinput;Raw Mouse Input;;\nspider-pig;8668;spig;Spider Pig;;\njingames-hair-c;8669;jrhc;JinGames Hair C;;\nmap-tooltip;8670;maptooltip;Map Tooltip;;\nplanetoid;8671;TeNNoX_Planetoid;Planetoids;;\ngolem-tweaks;8672;golemtweaks;Golem Tweaks;;\nyummy-foods;8673;yummyfoods;Yummy Foods;;\nhome-system-forge;8674;homesystem;Home System;;\ngeta-footwear;8675;geta;Geta;;\ncatwalks-inc;8676;catwalksinc;Catwalks Inc.;;\nfirework-rocket-duration;8677;cr-firework-rocket-duration;Firework Rocket Duration;;\n;8678;chatwalk;Chat Walk;;\nemcstage;8679;emc_stage;EMCStage;;\n;8680;caffeineconfig;ColaConfig;;\n;8681;;Toast's Engineering;;\n;8682;;Toasts Engineering 2;;\n;8683;brokenleadwarner;拴绳断裂警告;Broken Lead Warner;\n;8684;alwayzsteve;永远史蒂夫;Alwayzsteve;AzS\nvanilla-vistas;8685;vv;Vanilla Vistas;;VV\nmenulogue;8686;menulogue;Menulogue;;\narchaicfix;8687;archaicfix;Archaic Fix;;\nekac;8688;ekac;糕蛋;ekaC;\nbyg-plus;8689;bygvanillabiomes;你将去的生物群系+;Oh The Biomes You'll Go Plus;\nfarmers-compats;8690;nocubes_farmer_compats;Farmer's Compats;;\n;8691;mapcompass;Map Compass;;\n;8692;ridehorse;我要策马;RideHorse;RH\nspace-research-addon-for-galacticraft3;8693;sr;Space Research;;\npatchoulibutton;8694;patchoulibutton;帕秋莉手册按钮;PatchouliButton;\nsonorama;8695;sonorarama;Sonorama;;\nvanity-dungeons-pack;8696;vanity_dungeons_pack;Vanity: Dungeons Pack;;\ndivinerpg-processing-compat;8697;drpgpc;DRPG Processing Compat;;DRPGPC\nweapon-master;8698;weaponmaster;武器大师;Weapon Master;\nyour-ideas;8699;yourideas;你的想法;Your Ideas;\nvanity-ancient-tool-variants;8700;ancient_tools;Vanity: Ancient Tool Variants;;\nmotion-capture-mod-mocap;8701;mocap;动作捕捉;Motion Capture;\nwormhole-portals;8702;wormhole;虫洞;Wormhole;\ndragon-magic-and-relics;8703;dragonmagicandrelics;神龙魔法与遗物;Dragon Magic And Relics;DM&R\nyummy-foods-forked;8704;yummyfoods;Yummy Foods (Forked);;\ncontent-creator;8705;content_creator;Content Creator;;\nfullblock-energistics;8706;fullblock_energistics;Fullblock Energistics;;\nyoumatter;8707;youmatter;YouMatter;;\nvanity_battle_axes;8708;vanity_battle_axes;Vanity: Farcr's Battle Axes;;\nmarkdownmanual;8709;markdown_manual;Markdown Manual;;\npowershot;8710;powershot;Powershot;;\n;8711;crockpot_tweaker;CrockPot Tweaker;;\nautomatic-tool-swap;8712;toolswap;Automatic Tool Swap;;\nknight-quest-reforged;8713;knight_quest;Knight Quest;;\nmodernfix;8714;modernfix;现代化修复;ModernFix;\nblades-plus;8715;blades_plus;Blades Plus;;\nhomeostatic;8716;homeostatic;稳态;Homeostatic;\nsuper-saturation;8717;supersaturation;超级饱和度;Super Saturation;\nrpg-rpgae;8718;rpg_tag;RPG天启进化;RPGApocalypticEvolution;RPGAE\ndungeon-difficulty;8720;dungeon_difficulty;Dungeon Difficulty;;\ncazfps-chronicles;8721;cazfps_chronicle;CazFps Chronicles;;\nunstable-tools;8722;unstabletools;不稳定金属工具;Unstable Tools;\ntech-reborn-patchouli;8723;tr-patchouli;Tech Reborn Patchouli;;\nbiome-bundle;8724;biomebundle;Biome Bundle;;\nmoon-phase-info;8725;moonphaseinfo;Moon Phase Info;;\n;8726;fabwork;Fabwork;;\n;8727;projecte.max;等价交换max;ProjectE Max;PEMAX\nadvanced-tweakery;8728;AdvancedTweakery;Advanced Tweakery;;\ncreeper-hector;8729;creeper_hector;Creeper Hector;;\ntightened-village-security;8730;villagesecurity;Tightened Village Security;;\n;8731;blahaj;布罗艾扩展版;Blahaj Expanded;\n;8732;blahaj;布罗艾：重铸;Blåhaj - Reforged;\nbuddingcrystals;8733;buddingcrystals;Budding Crystals;;\ndynamic-trees-tectonic;8734;dttectonic;动态的树：Tectonic附属;Dynamic Trees - Tectonic;\nprp;8735;prp;Patchouli Resource Patch;;PRP\n;8736;moonphaseinfoplus;Moon Phase Info+;;\nharvest-day;8737;harvest_day;丰收之日;Harvest Day;HD\nhungteens-lib;8738;htlib;HungTeen's Lib / PangTeen's Lib;;HTLib\nmore-tool;8739;moretool;更多工具;More Tool;\n;8740;exhud;Ex HUD;;\n;8741;hoarding;囤积;Hoarding;\n;8742;;Paladin的家具：合成冲突消除;Paladin's Furniture Recipes Addition;PFMRA\noptifine-crash-fix;8743;optifinefixer;Optifine Crash Fix;;\n;8744;powerscale;PowerScale;;\nwyrmroost-patch;8745;wyrmroostpatch;猛龙之居补丁;Wyrmroost Patch;WRP\ntravelers-dream;8746;travelersdream;Traveler's Dream;;TD\nthaumic-travelers-dream;8747;thaumictravelersdream;Thaumic Traveler's Dream;;TTD\n;8748;gasstation;GasStation;;\n;8749;flashlight;Flashlight mod;;\naetherium-ashen-armor;8750;aetheriumashenarmor;Aetherium Ashen Armor;;\nsmoothmenu;8751;smoothmenu;平滑菜单;Smooth Menu;\ncabricality;8752;cabricality;Cabricality;;\nunusual-fish;8753;unusualfish,unusualfishmod;独特鱼类;Unusual Fish Mod;\nobscure-tooltips;8754;obscure_tooltips;Obscure Tooltips;;\n;8755;xibao;喜报-低版本移植版;XiBao;\n;8756;transformersg1;变形金刚G1;Transformers Mod: G1 Edition;\notg-tutorial-world;8757;otgtutorialworld;OTG: Tutorial World;;\notg-skylands;8758;skylands;OTG: Skylands;;\nfxs-rail-optimization;8759;rail_optimization;FX's Rail Optimization;;\nmorejs;8760;morejs;MoreJS;;\n;8761;neodymium;钕;Neodymium;Nd\n;8762;more_snowballls;更多雪球;MoreSnowballs;MSS\nvalkyrien-skies-control;8763;vs_control;Valkyrien Skies Control;;\nthe-end-expanded-mod-ice-and-fire-addon;8764;end_expanded;End Expanded;;\nvault-patcher;8765;vaultpatcher,vaultpatcher_asm;保险库补丁;Vault Patcher;VP\nprojectile-damage-attribute;8766;projectile_damage;Projectile Damage Attribute;;\nnoteable;8767;noteable;Noteable;;\ndeftulib;8768;deftulib;DeftuLib;;\ntrofers;8769;trofers;Trofers;;\nobscure-api;8770;obscure_api;Obscure API;;\n;8771;bettercrashes;BetterCrashes;;\ndeath-punishment;8772;death_punishment;死亡惩罚;Death Punishment;DP\ninventory-tabs-forge;8773;inventorytabs;Inventory Tabs (Forge);;\nreaper;8774;reaper;Reaper;;\ncontainersearcher;8775;csearcher;ContainerSearcher;;\n;8776;objmodel;GVCObjModel;;\n;8777;kettenkrad;KettenKrad;;\nscreencapper;8778;screencapper;Screencapper;;\nvalkyrien-skies-world;8779;vs_world;Valkyrien Skies World;;\nlucky-tnt-lib;8780;luckytntlib;Lucky TNT Lib;;\nwaffles-placeable-foods;8781;wafflesplaceablefoods;Waffle's Placeable Foods;;\notg-the-void;8782;otgvoid;OTG: The Void;;\notg-flatlands;8783;otgflatlands;OTG: Flatlands;;\notg-dungeons;8784;otgdungeons;OTG: Dungeons;;\n;8785;lightmeals;Light Meals Quilted;;\ngtnhlib;8786;gtnhlib;GTNH Lib;;\nsculpt;8787;sculpt;Sculpt;;\nlevelhearts;8788;levelhearts;LevelHearts;;\nbring-back-luck;8789;bring_back_luck_forge,bring_back_luck;Bring Back Luck;;\nteleport-master;8790;tpmaster;传送命令;Teleport Master;TM\n;8791;mufog;锻造;Mufog;\nbetterparry;8792;betterparry;更好的格挡;BetterParry;\nrltweaker2;8793;rltweaker;RLTweaker2;;\nthaumcraft-aspect-creator;8794;thaumcraftaspectcreator;神秘时代要素创建器;Thaumcraft Aspect Creator;\nvanity_katanas;8795;vanity_katanas;Vanity: Farcr's Katanas;;\npixels-character-models;8796;pcm;Pixel 的角色模型;Pixel's Character Models;PCM\nl2-complements;8797;l2complements;莱特兰-扩充;L2 Complements;\nepicfight-auto-shouldersurfing;8798;autosurfing;Epicfight Auto ShoulderSurfing;;\nzsrarity;8799;zsrarity;ZSRarity;;\nzenscroll;8800;zenscroll;ZenScroll;;\nzentraits;8801;zentraits;ZenTraits;;\nl2-archery;8802;l2archery;莱特兰-弓艺;L2 Archery;\nvanity-metallurgical;8803;vanity_metallurgical;Vanity: Metallurgical;;\nminespaceex;8804;minespaceex;MineSpaceEX;;MSEX\ngregious-maximus;8805;gregiousmaximus;Gregious Maximus;;\nshut-up-model-loader;8806;shutupmodelloader;Shut Up, Model Loader!;;SUML\ncultural-delights-fabric;8807;culturaldelights;多元乐事 (Fabric 版);Cultural Delights [Fabric];\ncollectors-reap;8808;collectorsreap;Collector's Reap;;\nbones-and-swords;8809;bonesandswords;Bones And Swords;;\nvanity-shedds-mystical-toolsets-pack;8810;sheddmersopal;Vanity: Shedd's Mystical Toolsets Pack;;\nemc-builders-wand;8811;emcbuilderswand;EMC Builders Wand;;\nvanity-charcutier;8813;vanity_charcutier;Vanity: Charcutier;;\nhunger-in-peace;8814;HungerInPeace,hungerinpeace;Hunger In Peace;;\n;8815;;复杂枪械;complex guns;CG\nlexicon;8816;lexicon;Lexicon;;\nscaffolding-drops-nearby;8817;scaffoldingdropsnearby,scaffoldingdropsnearby-fabric;Scaffolding Drops Nearby;;\nfuel-info;8818;fuelinfo;FuelInfo;;\nglacier-ice;8819;GlacierIce;Glacier Ice;;\ngtnhmixins;8820;gtnhmixins;GTNH Mixins;;\ndawn-to-dusk;8821;projectdown;Project Dawn;;\nopen-tower-defence;8822;opentd;开放式塔防;Open Tower Defence;OpenTD\nmtr-station-decoration;8823;msd;MTR车站装饰;Station Decoration;MSD\nperviaminvenire;8824;per_viam_invenire;PerViamInvenire;;PVI\nsilverbirch;8826;silverbirch;Silver Birch;;\n;8827;mublueprint;蓝图;Mublueprint;\nflame-sweeping;8828;flamesweeping,flame_sweeping;烈焰横扫;Flame Sweeping;\ncreepypastas-mod;8829;creepypastas_mod;CreepyFights;;\nmore-slab;8830;more_slab;More slab;;\nlandk-brick;8831;landk_block_brick;LandK brick;;\nkubejs-offline;8832;kubejsoffline;KubeJS 离线文档;KubeJS Offline Documentation;\n;8833;bannerpp;Banner++;;\nex-nihilonatureminerals;8834;exnihilonatureminerals;无中生有：传承 - Nature's Minerals 扩展;Ex Nihilo: Sequentia - Nature's Minerals Addon;\n;8835;teleportation;瞬身术;Teleportation;\nmemory-settings;8837;memorysettings;内存设置;Memory Settings;\ntiny-item-animations;8838;tia;Tiny Item Animations;;TIA\nmore-plates-revamped;8839;moreplates;更多金属板重置版;More Plates Revamped;\nextended-slabs-plus;8840;extendedslabs;Extended Slabs +;;\nbrewin-and-chewin-fabric;8841;;饮酒作乐 Fabric 版;Brewin' And Chewin' [Fabric];\nparticular;8842;particular;Particular;;\nlonger-login-times;8843;longerlogintimes;Longer Login Times;;\nbroglis-plants;8844;broglisplants;Broglis Plants;;\nrlmixins;8845;rlmixins;RLMixins;;\nsimplehats;8846;simplehats;简单帽子;Simple Hats;\ntimeoutout-fabric;8847;timeoutout;TimeOutOut;;\nfabric-furnaces;8848;fabric-furnaces;Fabric Furnaces;;\n;8849;;UIN;;\nmixinbooterlegacy;8850;mixinbooter;MixinBooterLegacy;;\nbasalt-walker-reborn;8851;bswk;玄武岩行者：重生;Basalt Walker : Reborn;\ncreate-industrial-chemistry;8852;createindustrialchemistry;机械动力：工业化学;Create: Industrial Chemistry;CIC\n;8853;oct;万能工作台;Omnipotent Crafting Table;OCT\n;8854;fl_flaming_fabric,fl_flaming_forge;火焰熊熊;Flaming;FL\ncruelars-triforcemod;8855;cruelars_triforcemod;Cruelars Triforcemod;;\n;8856;kamen_rider_black_rx;假面骑士Black RX;;\n;8857;more_spawn_eggs;更多刷怪蛋;MoreSpawnEggs;MSE\nbeautify-refabricated;8858;beautify;美化！重织;Beautify: Refabricated;\n;8859;inventoryprotection;更好的死亡掉落;InventoryProtection;IP\nmanatweaks;8860;manatweaks;ManaTweaks;;\nchicken-nugget;8861;chickennugget;Chicken Nugget;;\ngoldfish-myth;8862;goldfish;Goldfish Myth;;\nrezombies;8863;rezombiesv15ver1122jar;ReZombies;;\nwound;8864;wound;外伤;Wound;\nsurvival-campfire;8865;survival_campfire;Survival Campfire;;\neyes-mod;8866;eyes;Eye Blocks;;\n;8867;lnrepair_2317;提示栏换行;\\n Repair;\ncolorful-rods;8868;colorful_rods;缤纷彩烛;Colorful Rods;\nxp-plus;8869;xpplus;XP-Plus;;\n;8870;stillhungry;Still Hungry;;\njaffas;8871;Jaffas-Technic;Jaffas;;\nno-holds-barred-halloween-mod;8872;halloween;The No-Holds-Barred Halloween;;\n;8873;damage;自定义伤害;Custom Damage;\n;8874;crystal_craft_2;水晶工艺2;Crystal Craft 2;CC2\n;8875;joinmsg;发送加入信息与离开信息到网址;Post Server Join & Leave Messages to URL;\nadvanced-extended-creative-mode;8876;act;Advanced Creative Tab;;ACT\neven-more-creatures-mod;8877;extracreatures;Even more Creatures Mod;;\neerie-entities;8878;eerieentities;Eerie Entities;;\nbetter-weather-revived;8879;;Better Weather Revived;;\n;8880;exstrangefood2;更多奇怪的食物2;ExStrangeFood2;EXSF2\nrlcombat;8881;bettercombatmod;更好的战斗-RLCraft版;RLCombat;\nbotanical-addons;8882;shadowfox_botany;Botanical Addons;;\n;8883;scoreboardbackup;计分板备份;Scoreboards Backup;SBK\nhodgepodge;8884;hodgepodge;Hodgepodge;;\nfast-projectile-fix;8885;fast_projectile_fix;Fast Projectile Fix;;\nedivadlib;8886;edivadlib;EdivadLib;;\nsysteams;8887;systeams;热力系列：蒸汽锅炉;Thermal Systeams;\nwaymaker;8888;waymaker;Waystone Waypoint Maker;;\n;8889;mixingasm;Mixingasm;;\ndmod;8890;dmod;D-Mod;;\nextra-redstone-craft;8891;extracraft;更多红石合成;Extra Redstone Craft;EXRS\nfarmers-respite-fabric;8892;farmersrespite;农夫暇事 (Fabric 版);Farmer's Respite (Fabric);\nwizards;8893;wizards;魔法师;Wizards (RPG Series);\nbasketcase;8894;basketcase;Basketcase;;\nprimal-lib;8895;primallib;Primal Lib;;\nbiomes-youll-go-1-12-2-fixed;8896;;你将去的生物群系修复版;Biomes You'll Go Fixed;\nrune-crafting;8897;runes;符文;Runes;\ncontrolled-random;8898;controlled_random;Controlled Random;;\nleos-bosses;8899;leosbosses;Leo的Boss;Leo's Bosses;LB\nleos-illagers;8900;leosillagers;Leo's Illagers;;LI\n;8901;moving_piston_mod;移动的活塞;Block 36 Mods;\nmodularmachinery-community-edition;8902;modularmachinery;模块化机械：社区版;Modular Machinery: Community Edition;MMCE\nwearable-backpacks-rlcraft-edition;8903;wearablebackpacks;可穿戴背包-RLCraft版;Wearable Backpacks: RLCraft Edition;\nspartan-fire-rlcraft-edition;8904;spartanfire;斯巴达之冰与火-RLCraft版;Spartan and Fire: RLCraft Edition;\nframe-void-patch-mc-59363;8905;framevoidpatch;Frame Void Patch (MC-59363);;\narchprp;8906;archprp;帕秋莉手册加载补丁跨平台版;Patchouli Resource Patch - Arch/Architectured Patchouli Resource Patch;PRP-Arch/ArchPRP\nclear-skies-forge-port;8907;clearskies;Clear Skies Forge Port;;\nspell-power;8908;spell_power;Spell Power Attributes;;\n;8909;;九宫法令2;;\nmorecrashinfo;8910;morecrashinfo;更多崩溃报告信息;MoreCrashInfo;\nepicfight-saopack;8911;epicaddon;史诗战斗：ACG附属包;EpicFight ACG AddonPack;\nscreenshot-viewer;8912;screenshot_viewer;截图查看器;Screenshot Viewer;\ngml;8913;gml;GroovyModLoader;;GML\n;8914;futurepack_additions;飞向未来附加;Futurepack Additions;FA\nbee-fix;8915;beefix;蜜蜂修复;Bee Fix;\nspawner-fix;8916;sf;刷怪笼漏洞修复;Spawner Bug Fix;\nblock-limit-fix;8917;block_limit_fix;Block Limit Fix;;\nnochatlag;8918;nochatlag,nochatlagforge;聊天无卡顿;NoChatLag;\nspell-engine;8919;spell_engine;法术引擎;Spell Engine;\nspellblade-next;8920;spellbladenext;Spellblades and Such;;\nnetherite-road-2;8921;netheriteroad2,netheriteroadii;下界之路2;Netherite Road 2;NeRo2\n;8922;teresetfix;TileEntity Reset Fix;;\nnotenoughrecipebook;8923;nerb;Not Enough Recipe Book;;NERB\nurushi-mod;8924;urushi;漆;Urushi;\nnicephore;8925;nicephore;Nicephore;;\nterraria-paintings;8926;terraria_paintings;Terraria Paintings;;\nminegooseduck;8927;joyggd;Mine Goose Duck;;GGD\nfloating-islands;8928;floating_islands;Terrarian Floating Islands;;\n;8929;mineshell;MineShell;;MSH\n;8930;qwq;QωQ Library;;\nscorched-guns;8931;scorchedguns,scguns;~SCORCHED GUNS 2~;;\n;8932;createplus;CreatePlus1.19分支;;CP\nfred-qol;8933;fred;Fred;;\nmekanism-ce-unofficial;8934;mekanism;通用机械社区版：非官方;Mekanism CE Unofficial;MekCEu\nguntopia-legend-of-the-guns;8935;tylerdirden_guntopia;Guntopia Legend of the Guns;;\n;8936;slots-checker;Slots Checker;;\ncarpet-ams-addition;8937;carpet-ams-addition;Carpet AMS Addition;;\npixelmon-gameshark;8938;gameshark;Gameshark;;\nhidden-creepers-creepers-pretend-to-be-other-mobs;8939;fakemob;Hidden Creepers;;\nob-core;8940;ob_core;Obscuria's Essentials;;\nob-pandorica;8941;pandorica;Pandoricа;;\nno-weather-effects;8942;noweathereffects;No Weather Effects;;\nob-tooltips;8943;ob_tooltips;Obscuria's Tooltips (Legacy);;\nhekate-lib;8944;hekate_lib;Hekate Lib;;\nloot-journal;8945;loot_journal;Loot Journal;;\n;8946;flansmod;Flan's Mod Ultimate 1.1 Modified;;FMUM\nrepurposed-structures-better-ocean-monuments;8947;repurposed_structures_yungs_better_ocean_monuments_compat;结构变体：YUNG 的海底神殿优化兼容数据包;Repurposed Structures - Better Ocean Monuments Compat Datapack;\nrepurposed-structures-better-desert-temples-compat;8948;repurposed_structures_yungs_better_desert_temples_compat;结构变体：YUNG 的沙漠神殿优化兼容数据包;Repurposed Structures - Better Desert Temples Compat Datapack;\nrepurposed-structures-savage-ravage-datapack;8949;repurposed_structures_savage_and_ravage_compat;结构变体：残暴与掠夺兼容数据包;Repurposed Structures - Savage & Ravage Compat Datapack;\nrepurposed-structures-oh-the-biomes-youll-go;8950;;结构变体：你将去的生物群系兼容数据包;Repurposed Structures - Oh The Biomes You'll Go Compat Datapack;\nrepurposed-structures-more-villagers-datapack;8951;repurposed_structures_more_villagers_compat;结构变体：更多村民兼容数据包;Repurposed Structures - More Villagers Compat Datapack;\nrepurposed-structures-immersive-engineering;8952;;结构变体：沉浸工程兼容数据包;Repurposed Structures - Immersive Engineering Compat Datapack;\nrepurposed-structures-friends-and-foes-datapack;8953;repurposed_structures_friends_and_foes_compat;结构变体：Friends & Foes 兼容数据包;Repurposed Structures - Friends and Foes Compat Datapack;\nrepurposed-structures-farmers-delight-datapack;8954;repurposed_structures_farmers_delight_compat;结构变体：农夫乐事兼容数据包;Repurposed Structures - Farmer's Delight Compat Datapack;\nrepurposed-structures-better-witch-huts-compat;8955;repurposed_structures_yungs_better_witch_huts_compat;结构变体：YUNG 的沼泽小屋优化兼容数据包;Repurposed Structures - Better Witch Huts Compat Datapack;\nrepurposed-structures-better-strongholds-datapack;8956;repurposed_structures_yungs_better_strongholds_compat;结构变体：YUNG 的要塞重制兼容数据包;Repurposed Structures - Better Strongholds Compat Datapack;\nrepurposed-structures-better-dungeons-datapack;8957;;结构变体：YUNG 的地牢优化兼容数据包;Repurposed Structures - Better Dungeons Compat Datapack;\nrepurposed-structures-hexerei-datapack-compat;8958;repurposed_structures_hexerei_compat;结构变体：魔法巫师兼容数据包;Repurposed Structures - Hexerei Compat Datapack;\nrepurposed-structures-ice-and-fire-datapack-compat;8959;repurposed_structures_ice_and_fire_compat;结构变体：冰火传说兼容数据包;Repurposed Structures - Ice and Fire Compat Datapack;\nrepurposed-structures-advanced-peripherals-compat;8960;repurposed_structures_advanced_peripherals_compat;结构变体：Advanced Peripherals 兼容数据包;Repurposed Structures - Advanced Peripherals Compat Datapack;\nrepurposed-structures-pneumaticcraft-repressurized;8961;repurposed_structures_pneumaticcraft_repressurized_compat;结构变体：气动工艺兼容数据包;Repurposed Structures - PneumaticCraft: Repressurized Compat Datapack;\nrepurposed-structures-bountiful-datapack-compat;8962;;结构变体：赏金兼容数据包;Repurposed Structures - Bountiful Compat Datapack;\nstructure-floater-fabric;8963;structure_floaters;Structure Floater;;\napplecore-unofficial;8964;AppleCore;苹果核非官方版;Applecore Unofficial;\ndrilling-veins;8965;dveins;Drilling Veins;;\nserver-chat-history;8966;chathistory;服务端聊天历史;Server Chat History;\nscriptor-magicae;8967;scriptor;字符魔法师;Scriptor Magicae;\njson-entity-models-jems;8968;jsonentitymodels;JSON实体模型;JSON Entity Models;JEMs\n;8970;evergreen;Evergreen;;\n;8971;genetics;Better Genetics;;\nrain-of-hell;8972;rainofhell;Rain of Hell;;\n;8973;dreamland;虚幻梦境;Dreamland;IDM\nsuggestion-provider-fix;8974;suggestionproviderfix;Suggestion Provider Fix;;\nworldblender;8975;world_blender;World Blender;;\nno-oceans;8976;noocean;无海洋的世界;No Oceans;\nmonoblocks;8977;Monoblocks;Monoblocks;;\ntinkers-things-json;8978;tinkers_things;匠魂杂物;Tinkers' Things;\nfree-cam;8979;freecam;Freecam;;\nworkings;8980;workings;Workings;;\n;8981;mod-loading-screen;模组加载屏幕;Mod Loading Screen;\nlucky-cases;8982;lucky_cases;Lucky Cases;;\natmosfera;8983;atmosfera;Atmosfera;;\nferuchemy-allomancy;8984;feruchemy;藏金术;Feruchemy;\nalive-combat;8985;alivecombat;战斗生机;Alive Combat;\ndialog;8986;dialog;对话框;Dialog;\nrepurposed-structures-etched-datapack-compat;8987;;结构变体：Etched 兼容数据包;Repurposed Structures - Etched Compat Datapack;\nrepurposed-structures-domestication-innovation;8988;;结构变体：驯养革新兼容数据包;Repurposed Structures - Domestication Innovation Compat Datapack;\nrepurposed-structures-incubation-compat-datapack;8989;;结构变体：Incubation 兼容数据包;Repurposed Structures - Incubation Compat Datapack;\nrepurposed-structures-drunken-mug-compat-datapack;8990;;结构变体：Drunken Mug 兼容数据包;Repurposed Structures - Drunken Mug Compat Datapack;\nrepurposed-structures-environmental-datapack;8991;;结构变体：自然环境兼容数据包;Repurposed Structures - Environmental Compat Datapack;\nrepurposed-structures-buzzier-bees-datapack-compat;8992;;结构变体：Buzzier Bees 兼容数据包;Repurposed Structures - Buzzier Bees Compat Datapack;\nrepurposed-structures-jellyfishing-datapack-compat;8993;;结构变体：抓水母兼容数据包;Repurposed Structures - Jellyfishing Compat Datapack;\nrepurposed-structures-simply-cats-datapack-compat;8994;;结构变体：Simply Cats 兼容数据包;Repurposed Structures - Simply Cats Compat Datapack;\nrepurposed-structures-caves-cliffs-backport;8995;;结构变体：Caves & Cliffs Backport 兼容数据包;Repurposed Structures - Caves & Cliffs Backport Compat Datapack;\nrepurposed-structures-tidbits-datapack-compat;8996;;结构变体：Tidbits 兼容数据包;Repurposed Structures - Tidbits Compat Datapack;\nrepurposed-structures-new-tardis-mod-datapack;8997;;结构变体：New TARDIS Mod 兼容数据包;Repurposed Structures - New TARDIS Mod Compat Datapack;\nfix-selected-item-text;8998;fixselecteditemtext;Fix Selected Item Text;;\ndregora-o-plenty;8999;;Dregora O Plenty;;DBOP\nintegrated-api;9000;integrated_api;Integrated API;;\nthe-betweenlands-eternal-melodies;9001;thebetweenlandsmusic;交错次元：永恒旋律;The Betweenlands: Eternal Melodies;\n;9002;gbr;枪战大逃杀;Gun Battle Royale;GBR\nfrozen-apocalypse;9003;frozen_apocalypse;冰封启示录;Frozen Apocalypse;\nwhymap;9004;whymap;WhyMap;;\ncraftify;9005;craftify;Craftify;;\novergrown-cities-fabric;9006;overgrowncities;Overgrown Cities;;\nnight-config-fixes;9007;nightconfigfixes;Night Config Fixes;;\nstandardconfigs;9008;standardconfigs;Standard Configs;;\narmor-adjustment;9009;armoradjustment;盔甲调整;Armor Adjustment;\nenchanting-max_level;9010;enchantingmaxvalue;Enchanting.MAX_LEVEL;;\nfixbookgui;9011;fixbookgui;FixBookGUI;;\ncustom-explosions;9012;custom-explosion;自定义爆炸;Custom Explosions;\ndatapacks-screen;9013;datapacksscreen;数据包屏幕;Datapack Screen;\npandoras-box;9014;pandorasbox;潘多拉的魔盒;Pandora's Box;\nboss-rifts;9015;bossrifts;Boss Rifts;;\nrandom-teleport-altar;9016;teleport_altar;Random Teleport Altar;;\nsyncpotioneffects;9017;sync_potion_effects;Sync Potion Effects;;\naobd-ore-chunks;9018;jaopcaoc,aobdorechunks;AOBD/JAOPCA Ore Chunks;;AOBDOC/JAOPCAOC\nevasive-items;9019;evasiveitems;Evasive Items;;\nspagheters-enchants;9020;spagheters_enchants;Spagheters' Enchants;;\nfactory-api;9021;factory_api;Factory API;;\ntempad;9022;tempad;任意门;Tempad;\nstructure-void-toggle;9023;structure_void_toggle;结构空位切换;Structure Void Toggle;\n;9024;cactusfix;Cactusfix;;\nmorezombies;9025;morezombies;更多僵尸;More Zombies;\nmushroom-quest;9026;gachashroom;Mushroom Quest;;\nchowder-express;9027;chowderexpress;Chowder Express;;\nmod-browser;9028;modbrowser;模组浏览器;Mod Browser;\nfastback;9029;fastback;Fast Backups;;\ncopper-hopper;9030;copperhopper;铜漏斗;Copper Hopper;\noxidized;9031;oxidized;Oxidized;;\nping-wheel;9032;ping-wheel;Ping Wheel;;\ndark-waters;9033;darkwaters;Dark Waters;;\nsneak-tweak;9034;sneaktweak;Sneak Tweak;;\n;9035;galacticraftfoods1122101357;Galacticraft Foods;;\nserene-seasons-fix;9036;sereneseasonsfix;静谧四季修复;Serene Seasons Fix;\ncross-dim-commands;9037;crossdimcommands;跨维度指令;Cross-Dim Commands;\ntemporary-spawners;9038;temporary_spawners;Temporary Spawners;;\nslyde;9039;slyde;Slyde;;\nphonos;9040;phonos;Phonos;;\nspelunkery;9041;spelunkery;Spelunkery;;\n;9042;endless_cake;无尽的蛋糕;Endless Cake;\nrusticated;9043;rusticated;Rusticated;;\ninvmovecompats;9044;invmove_compat;边拿边走兼容;InvMoveCompats;\n;9045;tinkerers-smithing;Tinkerer's Smithing;;\nhesperus;9046;phosphor-lighting;燐;Hesperus;\ninventory-hotswap;9047;inventoryhotswap;Inventory Hotswap;;\ncorail-backpack;9048;corail_backpack;Corail Backpack;;\nchomper;9049;chomper;食人花;Chomper;\nflamelib;9050;cursedlib;FlameLib;;\n;9051;wateristoxic;水是剧毒的;Water Is Toxic;wit\nbetter-planting;9052;betterplanting;Better Planting;;\nsuperfancyclouds-refabricated;9053;sfcr;超级华丽云：重织版;SuperFancyClouds: Refabricated;SFCR\n;9054;;浮空岛数据包;FloatingIslands;\npet-tracker;9055;pettracker;Pet Tracker;;\n;9056;splashfox;狐狸加载界面;SplashFox;\ntap-tab;9057;taptab;Tap Tab;;\nmekanism-ce-tools;9058;MekanismTools;通用机械工具：社区版;Mekanism Community Edition: TOOLS;MEKCET\nmekanism-ce-generators;9059;MekanismGenerators;通用机械发电机：社区版;Mekanism Community Edition: GENERATORS;MEKCEG\n;9060;ten3;Technical Engineering 3 Quilted;;TEN3Q\n;9061;dataexp;NBT数据导出;NBT Data Exporter;NDE\nmore-mobgriefing-options;9062;moremobgriefingoptions;More MobGriefing Options;;\ncobalt-mod-think-blue;9063;mod_cobalt,cobalt-mod;Cobalt Mod;;\ngraphutil;9064;graphutil;GraphUtil;;\n;9065;random_teleport;随机传送;Random Teleport Mod;RT\ncomfy-sky;9066;comfysky;舒适空岛;Comfy Sky;\nentropy;9067;entropy;Entropy: Chaos Mod;;\nngv-forge;9068;ngv;Nether Gold Veins;;NGV\nworld-of-wonder;9069;worldofwonder;World of Wonder;;\n;9070;;磁铁;Magnet;\nfull-brightness-toggle;9071;fullbrightnesstoggle;Full Brightness Toggle;;\n;9072;your_ideas;你的想法;Your Ideas;YI\nnohurtcam;9073;nohurtcam;NoHurtCam;;\narrows-break-torches;9074;arrowsbreaktorches;箭矢破坏火把;Arrows Break Torches;\ngolden-delicacies;9075;golden_delicacies;Golden Delicacies;;\nhightiercrops;9076;ic2HighTierCrops;HighTierCrops;;\ncommandspy;9077;cmdspy;CommandSpy;;\nslimyfloor;9078;slimyfloor;SlimyFloor;;\ndaily-dad;9079;dailydad,dailydad_server;每日冷笑话;Daily Dad;\ngood-ending;9080;goodending;Good Ending;;\ncreeper-nuggets;9082;creepernuggets;Creeper Nuggets;;\ncoherent-villages;9083;coherentvillages;Coherent Village;;\n;9084;;Unicode扩展字符支持;UnicodeFontExtend;\n;9085;;发包崩服修复;PacketCrashFix;\nterrains;9086;terrains;Terrains;;\nbugtorched-curseforged;9087;bugtorch;BugTorch;;\nbetter-tridents;9088;bettertridents;更好的三叉戟;Better Tridents;BT\nundead-unleashed;9089;undead_unleashed;Undead Unleashed;;\nmissing-wilds;9090;missingwilds;遗落荒野;Missing Wilds;\n;9091;objparticle;obj particle;;op\nstellar-fluid-conduits;9092;stellarfluidconduits;恒星流体导管;Stellar Fluid Conduits;\nadvancement-locator;9093;cta;Advancement Locator;;CTA\nuniversal-tweaks;9094;universaltweaks;通用修改;Universal Tweaks;\n;9095;boat-maneuverability-enhancing;船只操控性增强;Boat Maneuverability Enhancing;BME\n;9096;succ;Succ™️;;\nwelcome-to-the-jungle;9097;thejungle;丛林传说;Welcome to the Jungle;\n;9098;lifeissohard;生存重负：食物改革;life is so hard - food part;lish:fp\n;9099;dash-loading-screen;Dash Loading Screen;;\ntransparent;9100;transparent;Transparent;;\nhopperplus-instant-hoppers;9101;hopr;HopperPlus;;\nsbm-merpig;9102;merpig;Merpig;;\nlwjgl3ify;9103;lwjgl3ify;LWJGL3ify;;\n;9104;mcopper;原版主义;Mcopper;\nmine-treasure;9105;mine_treasure_mr,mr_mine_treasure;Mine Treasure;;\ncreates-delight;9106;createsdelight;机械动力乐事;Create's Delight;\nmo-villager;9107;redstonevillager;更多村民;Mo' Villager;\n;9108;chinacraft;华夏文明2Fabric版;ChinaCraft for Fabric;CC2F\nlogunspam;9109;log_un_spam;LogUnSpam;;\n;9110;;布罗艾 Fabric Back Port 版;Blåhaj (Fabric Back Port);\nchatimage;9111;chatimage;聊天栏图片显示;ChatImage;CI\n;9112;treasure_hunt;寻宝;TreasureHunt;TH\nno-tema-stahp;9113;notemastahp;No Tema Stahp;;\nlabels;9114;labels;存储标签;Storage Labels;\n;9115;qdaa;QDAA;;\n;9116;niteloader;NiteLoader;;\nbetter-creeper-mod;9117;bettercreeper;更好的苦力怕;Better Creeper;\nregions-unexplored;9118;regions_unexplored;未至之地;Regions Unexplored;RU\n;9119;cheapersmithtemplate;更便宜的锻造模板;Cheaper Smith Template;CST\nrancraft-penguins;9120;rancraftpenguins;Rancraft Penguins;;\n;9121;tweezers;镊子;Tweezers;\nmore-mob-variants;9122;moremobvariants;更多生物变种;More Mob Variants;\nexperience-rings;9123;xprings;经验指环;Experience Rings;\ndeeper-oceans;9124;deeper_oceans;更深的海洋;Deeper Oceans;\nim-crossover-ic2;9125;emt;Industrial Magic - Crossover IC2;;\nlibjf;9126;libjf;LibJF;;\n;9127;cng;自定义NBT枪械;Custom NBT Gun;CNG\nhopper-xtreme;9128;hopperxtreme;Hopper X-Treme;;\nmaleks-infinity-gauntlet;9129;maleksinfinitygauntlet;Malek's Infinity Gauntlet;;\nmalum-skyland;9130;malum_skyland;灵灾空岛;Maland;\nbetweencards;9131;betweencards;Betweencards;;\n;9132;;宫灯与孔明灯;;\n;9133;boomood;boomood;;\n;9134;;物品展示台;Item Displayer;\n;9135;fire_track;烈焰轨迹;FireTrack;\n;9136;create_dragon_lib;机械龙库;Create Dragon Lib;\ngoogle-chat;9137;google-chat;GoogleChat;;\nresclone;9138;resclone;Resclone;;\ntame-them-all;9139;tame_them_all;Tame Them All!;;\n;9140;djyy;建筑楼层批量复制;Bulk copy of building floor;BCBF\nlittle-contraptions;9141;littlecontraptions;Little Contraptions;;\nchoicetheorems-overhauled-village-farmer-delight;9142;mr_ctov_farmersdelightcompat;CTOV：农夫乐事兼容数据包;CTOV - Farmer Delight Compat;\nconvenient-decor;9143;convenientdecor;精简锦饰;Convenient Decor;\nmixin-conflict-helper;9144;mixin-conflict-helper;Mixin Conflict Helper;;\ncreate-decoration-casing;9145;create_deco_casing;Create Decoration Casing;;\ncreate-brass-coated;9146;create_brass_coated;机械动力：黄铜覆壳;Create: Brass Coated;\nsculked;9147;sculked;Sculked;;\nbetter-mob-spawner;9148;better_spawner;更好的刷怪笼;Better Mob Spawner;BMS\nplaneteer-dimensions;9149;beyond_planets_dimensions;飞越群星：维度;Planeteer - Dimensions;\nplaneteer-machines;9150;beyond_planets_machines;飞越群星：机器;Planeteer - Machines;\ncreate-central-kitchen;9151;create_central_kitchen;机械动力：中央厨房;Create: Central Kitchen;CCK\njamd;9152;jamd;JAMD;;\nninezeros-gun-mod-addon;9153;nzgmaddon;NineZero's Gun Expansion;;\ndonthurtthatboat;9154;donthurtthatboat;DontHurtThatBoat;;\n;9155;horsebuff;Horse Buff;;\nenchanting-table-descriptions;9156;enchanting-table-descriptions;附魔台描述;Enchanting Table Descriptions;ETD\nharvest-with-ease;9157;harvestwithease,harvest_with_ease;Harvest with ease;;\ntrade-refresh;9158;traderefresh;交易刷新;Trade Refresh;TR\ncreate-molten-geodes;9159;molten_geodes;Create: Molten Geodes;;\nthats-a-lot-of-items;9160;taloi;That's A Lot Of Items;;TALOI\nnether-skeletons;9161;netherskeletons;Nether Skeletons;;\n;9162;;旧版粗金属;Raw Metal;RM\npenguins;9163;selimpenguin;Penguins;;\nrocket-squids;9164;rocketsquidsft;Rocket Squids;;\n;9165;;Better Biomes;;\nwood-stuff;9166;woodstuff;Wood Stuff;;\nmagmafurnace-weapons-bosses-and-more;9167;magmafurnace;MagmaFurnace;;\nmonstersregulararmy;9168;monsterregulararmy;MonstersRegularArmy;;\nwhy-does-my-food-taste-like-that;9169;whydoesmyfoodtastelikethat;Why Does My Food Taste Like That;;\niron-generators;9170;irongenerators;Iron Generators;;\nelytra-swapper-forge;9171;elytraswapperforge;鞘翅切换;Elytra Swapper Forge;\nftb-team-islands-forge;9172;ftbteamislands;FTB Team Islands;;\nmarket-crates;9174;marketcrates;集市板条箱;Market Crates;\nindustrial-renewal-port;9175;industrialrenewal;工业复兴移植版;Industrial Renewal Port;\ntfcm;9176;tfc_metallum;群峦合金非官方版;TFC Metallum U;\ntnt-time-mod;9177;samhoque.forge.tnttime;TNT Time Mod;;\ndrunken-mug;9178;drunken_mug;Drunken Mug;;\ncartoonish-horde-library;9179;cartoonishhorde,villainoushordemanager;Villainous Horde Manager;;\nlimited-lives;9180;limitedlives;有限生命;Limited Lives;\nuniverse-box;9181;universebox;Universe Box;;\ninvtweaks-emu-for-ipn;9182;invtweaksemuforipn;InvTweaks Emu for IPN;;\n;9183;euid;EasyUID;;\ncreate-compact-exp;9184;nocubescreateexp;机械动力：压缩经验;Create Compact Exp;\n;9185;;施法追加;;\n;9186;;Villages Overhauled;;\nheat-and-processing;9188;hap;Heat And Processing;;\nskytheorylib;9189;stlib;SkyTheoryLib;;\nad-astra-giselle-addon;9190;ad_astra_giselle_addon;Ad Astra!: Giselle Addon;;\nshift-scroll-fix;9191;shiftscrollfix;Shift-Scroll Fix;;\ngim;9192;genshin_impact_mods;原神农业;Genshin Impact Cropper;GIM\noxygen-daily-rewards;9193;oxygen_dailyrewards;Oxygen: Daily Rewards;;\nstructure-compass;9194;structurecompass;结构指南针;Structure Compass;\ncold-snap-horde;9195;coldsnaphorde;Cold Snap Horde;;\ntis-the-season;9196;tistheseason;Tis The Season;;\n;9197;;更多镶嵌材质;More Trims;\n;9198;baubles,baublesexpanded;Baubles Expanded;;\ncobblehaters;9199;CblH8Rs;CobbleHaters;;\nmob-captains;9200;mob_captains_mr,mobcaptains;怪物队长;Mob Captains;\n;9201;thewandofrunning;跑搭之杖;The Wand Of Running;TWOR\n;9202;sound_track;Sound-Track;;\nsnowballing;9203;snowballing;Snowballing;;\nwinter-essentials;9204;winteressentials;Winter Essentials;;\nembeddium-extras;9205;magnesium_extras;镁/铷附属：土豆版;Magnesium/Rubidium Extra: Potato Editon (Embeddium Extras);\n;9206;realistic_cruelty;真实残忍;Realistic Cruelty;\ntrapper-pelts;9207;trapperpelts;Trapper Pelts;;\nworkshops-of-doom;9208;workshopsofdoom;Workshops of Doom;;\ncreate-mechanical-spawner;9209;create_mechanical_spawner;机械动力：动力刷怪笼;Create Mechanical Spawner;\n;9210;;炼丹法;;\namethyst-core;9211;amethyst_core;Amethyst Core;;\nfzzy-core;9212;fzzy_core;Fzzy Core;;\ntnt-breaks-bedrock;9213;tntbreaksbedrock;TNT Breaks Bedrock;;\nfrom-the-north;9214;fromthenorth;From The North;;\nspruce-willis-the-xmas-tree;9215;sprucewillisthexmastree;Spruce Willis the Xmas Tree;;\nsweet-potato-core-spc;9216;;地瓜核心;Sweet Potato Core;SPC\nmerge-enchantments;9217;merenc;Merge Enchantments;;\nturkey;9218;turkey;火鸡;Turkey;\nsnowy-weaponry;9220;snowyweaponry;Snowy Weaponry;;\ndungeon-mobs-reborn;9221;dungeonmobs;Dungeon Mobs Reborn;;\nquickteleports;9222;quickteleports;Quick Teleports;;\npowerfuljs;9223;powerfuljs;PowerfulJS;;\nmemoryclear-refabricated;9224;memory_clear;内存清理：重织;Memory Clear : Refabricated;MCR\nvillager-comfort;9225;villagercomfort;村民舒适度;Villager Comfort;\nender-quarry;9226;ender_quarry;末影采石场;Ender Quarry;\n;9227;;战斗追加;;\n;9228;bh3rd;崩坏3rd;Honkai Impact 3;\nmythic-upgrades-fabric;9229;mythicupgrades;Mythic Upgrades;;\nfat-chicken;9230;fatchicken;Fat Chickens;;\n;9231;;强化生物;;\nsnowmancy;9232;snowmancy;Snowmancy;;\n;9233;roadchina;道路中国;Road China;RCN\n;9235;immersivesnow;Immersive Snow;;\n;9236;note;备注;note;\nfunctional_tfc;9237;functional_tfc;Functional TFC;;\nbetter-end-potato-edition;9238;betterendforge;更好的末地：土豆版;Better End Potato Edition;\nbossbar-stack;9239;bossstack;Bossbar Stack;;\norigins-umbrellas;9240;originsumbrellas;起源：雨伞;Origins: Umbrellas;\ntfc_decoration;9241;tfc_decoration;TFC Decoration;;\nshroom-dealers;9242;shroom_dealers;Shroom Dealers!;;\nnbtedit;9243;nbtedit;NBTEdit;;NBTE\nburlap-sack;9244;burlapsack;粗麻布袋;Burlap Sack;\n;9245;;附魔锻造;;\n;9246;lighteco;轻量经济;LightEconomy;LE\nanimalnet;9247;animalnet;Animalnet;;\nbiome-backlog;9248;biome_backlog;Biome Backlog;;\ndoespotatotick;9249;does_potato_tick,doespotatotick;远物停载：土豆版;Does Potato Tick?;DPT\n;9250;aquaman_gift;海王馈赠;Aquaman's Gift;AMG\nwelcome-to-meadow;9251;meadow;青青草甸;[Let's do] Meadow / Cheese!;\nuniversal-enchants-forge;9252;universalenchants;通用附魔;Universal Enchants;\naa-farmer-compat;9253;farmeradditions;AA Farmer Compat;;\n;9254;sctz_ywsj;只要听话就不会死！;;\n;9255;configurable-firework-explosion;可配置的烟花爆炸;Configurable Firework Explosion;CFE\n;9256;;内修炼体 (属性加点);;\nbetter-crates;9257;bettercrates;Better Crates;;\nvanillaicecreamfix;9259;vanillaicecreamfix;VanillaIcecreamFix;;\nactually-unbreaking-forge;9260;actuallyunbreaking;Actually Unbreaking;;\nhandcrafted;9261;handcrafted;精巧手艺;Handcrafted;\nno-nether-teleport;9262;nonether;No Nether Teleport;;\ncrasher;9263;crasher;崩溃机;Crasher;\nrepurposed-structures-wizards-compat-datapack;9264;repurposed_structures_wizards_compat;结构变体：魔法师兼容数据包;Repurposed Structures - Wizards Compat Datapack;\neasywhitelist;9265;easywhitelist;简易白名单;EasyWhitelist;\nclayworks;9266;clayworks;Clayworks;;\nminiscaled;9267;miniscaled;MiniScaled;;\nlost-cities-potato-edition;9268;lostcities;失落的城市：土豆版;Lost Cities: Potato Edition;\nendless-noend;9269;endless;Endless / NoEnd;;\nmatmos-2;9270;matmos;MAtmos 2 sound effect;;\ntfc-item-barrels;9271;tfcbarrels;TFC Item Barrels;;\nvillagersplus-forge;9273;villagersplus;VillagersPlus;;\ndata-criteria;9274;datacriteria;Data Criteria;;\njapanese-emoji-commands;9275;japaneseemojicommands;Japanese Emoji Commands;;\nattack-on-christmas;9276;aoc;Attack On Christmas;;\nvoxel-latest;9278;voxellatest-remapper,voxellatest;Voxel Latest;;\nthe-printer;9279;theprinter;The Printer;;\n;9280;ecohelper;经济小助手;EcoHelper;EH\nflying-cullers;9281;flyingcullers;Flying Culler;;\ntimeless-power;9282;timelesspower;Timeless Power;;\ncreate-deco-fabric-fixed;9283;;Create Deco Fabric Fixed;;\ncalibrated;9284;calibrated;Calibrated Access;;\nfrozen-memories;9285;frozenmemories;Frozen Memories;;\natlantis-api;9286;atlantis;Atlantis API;;\nnutritional-balance;9287;nutritionalbalance;Nutritional Balance;;\nprofession-lock;9288;proflock;Profession Lock;;\nspectrum-jetpacks;9290;spectrumjetpacks;Spectrum Jetpacks;;\ndifficult-caves;9291;difficult_caves;Difficult Caves;;\n;9292;experience_shards_mr;Experience Shards;;\nspartanlands-rereforged;9293;;Spartanlands Rereforged;;\nred-pandas;9294;redpandas;Red Pandas;;\n;9295;bundle;收纳袋;;\nsimple-cobblestone-generator-forge;9296;simple_cobblestone_generator;Simple Cobblestone Generator;;\nfloral-flair-fabric;9297;floral_flair;🌷Floral Flair🌷;;\nvertical-slabs-compat;9298;v_slab_compat;Vertical Slabs Compat;;VSC\nruins;9299;philipsruins;Philip's ruins;;\ncobblemon;9300;cobblemon;方块宝可梦;Cobblemon;\nmysterious-biomes;9301;spookybiomes;Mysterious Biomes;;\nspawn-animations;9302;spawn_animations_mr;Spawn Animations;;\nhead-in-the-clouds;9303;head_in_the_clouds;Head in the Clouds;;\nfdmc;9304;fdmc;HyperWorld;;\nrpg-style-more-weapons;9305;rpgsmw;⚔️ RPG style More Weapons!;;\nsiriuss-origins-origins-datapack;9307;;Sirius's Origins - Origins Datapack;;\neasy-experience;9308;easyexperience;Easy Experience;;\n;9309;originiumtech;源石科技;OriginiumTech;OT\nfull-turtle-armor;9310;fullturtlearmor;Full Turtle Armor;;\nchoicetheorems-overhauled-village-waystone;9311;ctov_waystonecompat_mr;CTOV：传送石碑/指路石兼容数据包;CTOV - Waystone Compat;\nchoicetheorems-overhauled-village-more-villagers;9312;mr_ctov_morevillagerscompat;CTOV：更多村民兼容数据包;CTOV - More villagers compat;\nthip;9313;tokusatsu_hero_completion_plan;特摄英雄补完计划;Tokusatsu Hero Instrumentality Project;THIP\n;9314;djyy;对江的现代生存模组;Djyy's morden survival mod;DMSM\n;9315;hardcodepatcher;硬编码文本补丁;HardcodePatcher / HardcodeTextPatcher;HP/HTP\nspyglass-astronomy;9316;spyglass_astronomy;Spyglass Astronomy;;\nmchalo;9317;mchalo;MCHalo;;\ndarkmenu-darkrpg-legacy-menu;9318;darkmenu;DarkMenu - DarkRPG Menu (RPG Theme);;\nrl-artifacts;9320;;奇异饰品-RLCraft版;RLArtifacts;\nazurelib;9321;azurelib;AzureLib;;\nharder-monster-boats-finer-boat-control;9322;hardermonsterboats;Harder Monster Boats;;\nspyglass-improvements;9323;spyglass-improvements,spyglass_improvements;望远镜改进;Spyglass Improvements;\nvanilla-zoom;9324;vanillazoom;原版缩放;Vanilla Zoom;\ntime-in-a-bottled;9325;timeinabottled;Time in a Bottled;;\nimproved-end;9326;improved_end;Improved End;;\nraw-block-smelting;9327;raw_block_smelting;Raw Block Smelting;;\nnocubes-better-blast-furnace;9328;ncbetterblastfurnace;NoCube's Better Blast Furnace;;\nbugjump;9329;bugjump;BugJump;;\ncluttered;9330;luphieclutteredmod,cluttered;Cluttered;;\ndigitality;9331;digitality;DigitalityAPI;;\n;9332;multitool_mr;Multitool;;\nnautral-decor-mod;9333;naturaldecormod;Natural Decor Mod;;NDM\n;9334;naraka;Naraka;;\ncobbled-deepslate-generator;9335;deepslategen;Cobbled Deepslate Generator;;\nfemale-villagers;9336;femalevillagers;女性村民;Female Villagers;\n;9337;;New in Town | The Settler's Experience Data Pack;;\nsubaquatic;9338;subaquatic;Subaquatic;;\nleos-sans;9339;sansmod;Leo's Sans;;LS\n;9340;damagevignette;DamageVignette;;\nno-irom-farm;9341;no_iron_farm;No Iron Farm;;\namethyst-tools-mod;9342;amethysttoolsmod;Amethyst Tools Mod;;\n;9343;grateblocks;Grate Blocks;;\nlush-forests;9344;lushforest;Lush Forests;;\n;9345;;柑橘的切石机增强;;\nretraining;9346;retraining;Retraining;;\nender-ore;9347;enderore;Ender Ore;;\nars-instrumentum;9348;ars_instrumentum;Ars Instrumentum;;\neasier-sleeping;9349;easier_sleeping;Easier Sleeping;;\nharder-farther;9350;harderfarther;Harder Farther;;\ncustom-signposts;9351;customsignposts;Custom Signposts;;\ngalactic-research;9352;galacticresearch;Galactic Research;;GR\nmining-dimensions-fabric;9353;mining_dims;Mining Dimensions;;\nbejs;9354;bejs;beJS;;\nscreenjs;9355;screenjs;ScreenJS;;\npioneer;9356;pioneer;Pioneer;;\n;9357;sha1runtime;Runtime Hash Updater;;\nnight-vision-enchantment-forge;9358;nightvision;Night Vision Enchantment;;\ngalactic-computers;9359;galacticcomputers;Galactic Computers;;\nmini-cites;9360;minicity;Mini Cites;;\nad-tetra;9361;adtetra;Ad Tetra!;;\nchest-cavity-forge-port;9362;chestcavity;胸腔Forge版;Chest Cavity - Forge Port;\nentity-lagfix-lag-fix;9363;LagFix;Entity LagFix;;\naudioplayer;9364;audioplayer;AudioPlayer;;\nvibes;9365;vibes;Vibes;;\ndisco;9366;disco;Disco;;\nthe-mcxr-core;9367;mcxr-core;MCXR Core;;\nmcxr-play;9368;mcxr-play;MCXR Play;;\nbeneath-the-wetlands;9369;wetlands;Beneath the Wetlands;;\n;9370;go_shopping;购物同乐;With Shopping！;\nadd-on-the-abyss-beginning;9371;thebeginning;The Abyss: Beginning;;\nreusable-rockets;9372;reusable_rockets;Reusable Rockets;;\nitem-info;9373;minecraftenchant;Item Info;;\nflorist;9374;florist;Florist;;\nomni;9375;omni;Omni;;\nfrostrealm;9376;frostrealm;FrostRealm;;\nnameless-trinkets;9377;nameless_trinkets;Nameless Trinkets;;\nunsafe-spawns;9378;unsafe_spawns;Unsafe Spawns;;\n;9379;;无攻击冷却;No Attack Cooldown;NAC\nbubbles;9380;pop;Pop!;;\nnan-health-fixer;9381;nanhealthfixer;假死修复：土豆版;Potato NaN Health Fix;\n;9382;voxelmap;VoxelMap Updated;;\nblood-and-stuff;9383;blood_and_stuff;Blood and Stuff;;\nyungs-better-nether-fortresses;9384;betterfortresses;YUNG 的下界要塞优化;YUNG's Better Nether Fortresses;\nsquidless;9385;Squidless;Squidless;;\n;9386;;Mod Menu Legacy;;\nenders-delight;9387;endersdelight;末影乐事;Ender's Delight;\ndecostorage;9388;deco_storage;Decorative Storage;;\nmahogany;9389;mahogany;Mahogany;;\naspen;9390;aspen;Aspen;;\nunmending-potato-edition;9391;unmending;经验不修补：土豆版;Unmending Potato Edition;\nallens-rocket-launcher-arl;9392;arl;简易火箭筒;Allen's Rocket Launcher;ARL\n;9393;item_tributes;Item Tributes;;\nassorted-discoveries;9394;assorteddiscoveries;Assorted Discoveries;;\nnot-so-shrimple-forge;9395;notsoshrimple;Not So Shrimple;;\nvalkyrien-computers;9396;vc;Valkyrien Computers;;VC\nblazeandcaves-advancements-pack;9397;blazeandcave;BlazeandCave's Advancements Pack;;BACAP\ngems-jewels;9398;gemsnjewels;Gems & Jewels;;\n;9399;angling;Angling;;\ndimensional-sync-fixes;9400;dimensionalsyncfixes;维度同步修复;Dimensional Sync Fixes;\nexhaustion-fatigue;9401;exhaustion_fatigue;Exhaustion Fatigue;;\nchainmail-bucket;9402;chainmail_bucket;Chainmail Bucket;;\npreciseblockplacing;9403;preciseblockplacing;Precise Block Placing;;\naccurate-block-placement-reforged;9404;accurate_block_placement;Accurate Block Placement Reforged;;\ncarpet-tctc-addition;9405;carpet-tctc-addition;Carpet TCTC Addition;;\n;9406;artedprvt;ArtedPrvt Frame;;\nplushie-mod;9407;plushies;Plushie Mod;;\nshield-expansion;9408;shieldexp;Shield Expansion;;SE\nmany-systems;9409;manysystems;Many Systems;;\nbarbarians;9410;barbarians;野蛮人;Barbarians;\nminecraft-startup-sound-aka-mojaaaaaang;9411;;Minecraft Startup Sound / MOJAAAAAANG;;\nrealistic-terrain-generation-unofficial;9412;rtg;真实地形生成非官方版;Realistic Terrain Generation Unofficial;RTGU\ncombat-enchantments;9413;cenchants;战斗附魔;Combat Enchantments;\nfastentitytransfer;9414;fastentitytransfer;Fast Entity Transfer;;\nchalice;9415;chalice;Chalice;;\n;9416;dtbe;不要饿死基岩版;Don't Starve Bedrock Edition;DTBE\none-twenty-update;9417;one_twenty;Minecraft 1.20 One Twenty Update;;\nminecraft-middle-ages;9418;mma;Days in the Middle Ages;;\ngibmemojangstudio;9419;;GibMeMojangStudio;;\nredwolf-music-expansion;9420;redwolf_music_mod;RedWolf's Music Expansion;;\n;9421;nameprotect;NameProtect;;\ngliders;9422;vc_gliders;Gliders;;\n;9423;no_teleport_cooldown;No Teleport Cooldown;;\nriding-partners;9424;riding_partners;双人骑行;Riding Partners;\nrpgskillable;9425;rpgskillable;RpgSkillable;;\nextra-discs;9426;extra-discs;Extra Disc;;\npropertyerizer;9427;propertyerizer;Propertyerizer;;\nwaxable-coral;9428;waxablecoral;涂蜡珊瑚;Waxable Coral;\n;9429;fireframe;火焰框架;FireFrame;FF\ngoated;9430;goated;You've Goat to be Kidding Me!;;\nmorebread;9431;more-bread;MoreBread;;\n;9432;DayZ;DayZ for Minecraft：重铸;DayZ for Minecraft: Reforge;\nrepurposed-structures-villagerplus-compat-datapack;9433;repurposed_structures_villagersplus_compat;结构变体：VillagerPlus 兼容数据包;Repurposed Structures - VillagerPlus Compat Datapack;\nrepurposed-structures-chefs-delight-compat;9434;repurposed_structures_chefs_delight_compat;结构变体：Chef's Delight 兼容数据包;Repurposed Structures - Chef's Delight Compat Datapack;\nice-prevents-crop-growth;9435;icepreventscropgrowth-fabric,icepreventscropgrowth;Ice Prevents Crop Growth;;\n;9436;coins;Coins Mod;;\nglowing-ores;9437;glowingores;矿石发光;Glowing Ores;\n;9438;NpcBoundBoxFix;NPC碰撞箱修复;NpcBoundBoxFix;\nfalsetweaks;9439;falsetweaks;FalseTweaks;;FT\nmetaworlds-unofficial;9440;mwtestmod;元世界：非官方版;Metaworlds Unofficial;\ntslateffectslib;9441;tslateffectslib;TslatEffectsLib;;TEL\ntslatentitystatus-tes;9442;tslatentitystatus;TslatEntityStatus;;TES\nrounds-invisible-frames;9443;invisibleframes;Invisible Frames;;\nelytra-capes;9444;elytracapes;Elytra Capes;;\nconfig-swapper;9445;configswapper;Config Swapper;;\nviafabricplus;9446;viafabricplus;ViaFabricPlus;;\nmenu-companions;9447;menucompanions;Menu Companions;;\nentity-render-distance-extender;9448;erde;Entity Render Distance Extender;;ERDE\nrpg-stamina;9449;rpgstamina;RPG 耐力;RPG Stamina;\nmjs-animations;9450;mjs_animations;MJ`s Animations;;\ndeepslate-generator;9451;deepslategenerator;Deepslate Generator;;\nhairy-zombies;9452;;Hairy Zombies;;\n;9453;recipes_export;合成表导出;Recipes Export;\nfootprintparticle;9454;footprintparticle;脚印粒子;FootprintParticle;\ntabbychat;9455;tabbychat;TabbyChat;;\n;9456;leafculling;Leaf Culling;;\nunimixins;9457;unimixins,unimixins-mixin,unimixins-compat,unimixins-devcompat;UniMixins;;\nnei-lotr-fa;9458;;魔戒：第一纪元 NEI 插件;NEI LOTR FA;\nnogravity-mod;9459;nogravity;零重力;NoGravity;\nmilk-of-magnesium;9460;milk_of_magnesium;Milk Of Magnesium;;\n;9461;slumber;Slumber;;\nnei-recipe-handlers;9462;neirecipehandlers;NEI Recipe Handlers;;NEIRH\nnox-a-mob-and-difficulty-overhaul;9463;nox;Nox;;\nchristmas-festivity;9464;christmasfestivity;Christmas Festivity;;\ntameable-beasts;9465;tameablebeasts;Tameable Beasts;;\ncute-hermit-crabs;9466;cutehermitcrabs;Cute Hermit Crabs;;\n;9467;ulib;uLib;;\nreceiver-mod;9468;rm;Receiver Gun Mod;;\nchargepads;9469;ChargePads;充电座;ChargePads;\nsnow-real-magic-potato-edition;9470;;雪！真实的魔法！(土豆版);Snow Real Magic Potato Edition;\n;9471;depthstrider;Depth Strider;;\nravenous-void;9472;rv;Ravenous Void;;\nimmersive-engineering-blueprint-tweaker;9473;iebpt;Immersive Engineering Blueprint Tweaker;;IEBPT\nmob-compack;9474;mobcompack;Mob Compack;;\nkore-sample;9475;koresample;Kore Sample;;\nmultiworld;9476;MultiWorld;MultiWorld;;\n;9477;createenchant;机械动力：附魔附加;Create Enchant Additions;\npeat-zombie;9478;peat_mummy;Peat Zombie;;\nragna-goblin-updated;9479;ragnagoblin;Ragna Goblin;;\ndemogorgon;9480;demogorgon;The Demogorgon Mod;;\nhaunted-fields;9481;hauntedfields;Haunted Fields;;\nhealth-command;9482;healthcommand;Health Command;;\nsplash-logo-color-fix;9483;;Splash Logo Color Fix;;\nfrv-tools;9484;frv;FRV Tools;;\nbombs-bandits;9485;bombs_abroad;Bombs & Bandits;;\nimmersive-cavegen;9486;immersivecavegen;Immersive Cavegen;;\nvillager-brute;9487;villagerbrute;Villager Brute;;\nexspiravit-magicae;9488;;Exspiravit Magicae;;\nequiped;9489;equiped;Equiped;;\nlimited-chunkloading;9490;limitedchunk;Limited Chunkloading;;\ncustom-villager-trades-forge;9491;customvillagertrades;自定义村民交易;Custom Villager Trades;CVT\ndungeons-more-mod;9492;dungeons_mod;Dungeons & More Mod;;\nancient-trees;9493;dendrology;Ancient Trees;;\nextraskills;9494;extraskills;ExtraSkills;;\nscotts-tweaks;9495;scottstweaks;Scott's Tweaks;;\nkore-sample-l;9496;koresample;Kore Sample L;;\nbook-wyrms;9497;bookwyrms;Book Wyrms;;\nnakaroths-herdcraft-l;9498;herdcraft;群兽行为L;Nakaroths Herdcraft L;\nmystical-oak-tree;9499;mysticaloaktree;Mystical Oak Tree;;\nancient-trees-l;9500;dendrology;Ancient Trees L;;\nbutchercraft;9501;butchercraft;屠宰工艺;Butchercraft;\nzombie-invasion;9502;zombieinvasion;Zombie Invasion;;\nclay-soldiers-reborn;9503;clay;Clay Soldiers Reborn;;\ngeore;9504;geore;GeOre;;\nmt-claysoldiers2-forge;9505;claysoldiers2;MT: ClaySoldiers2;;\nsafekeystring;9506;safekeystring;安全键位名;SafeKeyString;\nloadingtips;9507;loadingtips;LoadingTips;;\ngear-o-plenty;9508;gearoplenty;Gear O' Plenty;;\nregional-water;9509;regionalwater;Regional Water;;\nbattle-text;9510;BattleText;Battle Text;;\nmusic-choices;9511;musicchoices;Music Choices;;\n;9512;damagemark;命中反馈;Damage Mark;\nchemcraft-snowfall;9513;redgear_snowfall;落雪;Snowfall;\nclosed-captions;9514;ClosedCaption;隐藏式字幕;Closed Captions;\ndont-pick-up;9515;DPU;Don't Pick Up;;\neasy-breeding;9516;easybreeding;简单繁殖;Easy Breeding;\n;9517;betterrain;更好的降雨;Better Rain;\nchemcraft-core;9518;redgear_core;Red Gear Core;;\n;9519;mr_time_trap;时间陷阱;TimeTrap;\nbonsai-man-mod;9520;bonsaimanmod;盆栽人;Bonsai Man Mod;\n;9521;chatcopier;聊天复制;Chat Copier;\npet-cemetery;9522;pet_cemetery;Pet Cemetery;;\nchemcraft-geocraft;9523;redgear_geocraft;Geocraft;;\n;9524;steamcraft;SteamCraft;;\nsteamagerevolution;9525;steamagerevolution;蒸汽时代革命;SteamAgeRevolution;\n;9526;zipline;滑索;Zipline;\nbrewcraft;9527;redgear_brewcraft;Brewcraft;;\nzombie-awareness-freeze-fix;9528;zombieawarenessfix;僵尸意识冻结修复;Zombie Awareness Freeze Fix;\nintegrated-stronghold;9529;integrated_stronghold;Integrated Stronghold;;\ngrass-overhaul;9530;sod,grassoverhaul;Grass Overhaul;;\n;9531;purpurclient;PurpurClient;;\nalkimicraft-part-i-project-oasis;9532;alkimicraft;AlkimiCraft;;\nwild-caves-3;9533;wildcaves3;洞穴装饰3;WildCaves 3;\nmodest-mining;9534;modestmining;Modest Mining;;\nvanilla-compats;9535;vanillapluscompat;Delightful Alex's Abnormalities;;\nvending-block;9536;vending;自动售货机;Vending block;\n;9537;blazeandcave;BlazeandCave's Advancements Pack Hardcore version;;\nvending-block-revived;9538;Vending;自动售货机：重生;Vending Block Revived;\nassortment;9539;assortment;Assortment;;\n;9540;ifmodloadboom,howdareyouload;如果你在加载我就梆梆梆 / 你怎么敢加载的啊;IFMODLOADBOOM / HowDareYouLoad;IMLB\nopotato;9541;opotato;哦，土豆！;Opotato;\ncontingameime;9542;ingameime;游戏内输入法续更版 / 游戏内输入法 Forge 重置版;ContingameIME / IngameIME-Forge / IngameIME-Forged;CIGIME/IGIME\nccastroadds;9543;ccastroadds;Astro Adds to Chest Cavity;;\naerial-hell;9544;aerialhell;Aerial Hell;;\nswashbucklers;9545;swashbucklers;Swashbucklers;;\nstyle;9546;doggystyle;Doggy Style;;\neatable-livestock;9547;eatable_sheep;Eatable Livestock;;\nhunger-strike;9548;hungerstrike;绝食;Hunger Strike;\nextrabuttons;9549;extrabuttons;更多按钮;ExtraButtons;\nbig-doors;9550;BigDoors;Big Doors;;\nblockshot;9551;blockshot;BlockShot;;\njump-over-fences-forge;9552;jumpoverfences;Jump Over Fences;;\nluppiis-ladders;9553;LLadders;Luppii's Ladders;;\n;9554;simplyhorses;Simply Horses!;;\nanthropophagy;9555;anthropophagy;Anthropophagy;;\nambient-structures;9556;ambient_structures;Ambient Structures;;\nminegicka-iii;9557;minegicka3;魔能3;Minegicka III;\nhopo-better-underwater-ruins;9558;hopobetterunderwaterruins,hopour;Hopo Better Underwater Ruins;;\n;9559;nighttears;夜影;NightShadow;\njust-another-crafting-bench;9560;JACB;Just Another Crafting Bench;;JACB\ntiny-ents;9561;tiny_ents;Tiny Ents;;\n;9562;noodlesmusket;面条的火枪;Noodles' Musket;\n;9563;;谁伤害了我而我又伤害了谁;Who hurt me and who I hurt;WHM\n;9564;emmaitar;Emmaitar;;\nnei-emmaitar;9565;neiEmmaitar;NEI Emmaitar;;\nmagicrafted-rings-of-power;9566;MagiCraftedRingsOfPower;MagiCrafted Rings Of Power;;\n;9567;joetater;Joetater;;\nskybox-loader-forge;9568;skybox;Skybox Loader;;\neasy-feed;9569;mcf-easyfeed;简单饲养;Easy feeding;\ncreate-buffers-beams;9570;createbufferandbeams;Create: Buffers & Beams;;\nsmall-boats-mod;9571;smallboats;Small Boats;;\nredev;9572;redev;ReDev / Reimplemented;;\nwands-and-wizardry;9573;wands_and_wizardry;Wands and wizardry;;\ngeore-nouveau;9574;georenouveau;GeOre Nouveau;;\ncrimsonsteves-more-mobs;9575;crimson_steves_mobs;CrimsonSteve's more mobs;;\nthe-ulterlands;9576;ulterlands;Ulterlands: Worlds Apart;;\nmt-lotr;9577;mt-lotr;MT LOTR;;\nasjcore;9578;asjlib,hooklib,worldengine;ASJCore;;\nbane-tooltips;9579;bane-tooltips;克星信息显示;Bane Tooltips;\nlava-crystal;9580;lavacrystal;Lava Crystal;;\nliteral-sky-block;9581;literalskyblock;Literal Sky Block;;\n;9582;literalskyblock;Literal Sky Block Fabric;;\naniskin;9583;aniskin;AniSkin;;\nmob-stacker;9584;mobstacker;Mob Stacker;;\nmob-stacker-integration;9585;mobstacker;Mob Stacker: Integration;;\nmob-stacker-farming;9586;mobstacker;Mob Stacker: Farming;;\nlightspeedmod;9587;lightspeed;Lightspeed;;\ncoffee-delight;9588;coffee_delight;咖啡乐事;Coffee Delight;\nsimple-sculk-dimension-deeper-dark;9589;simplesculkdim;Simple Sculk Dimension;;\nno-more-inifinite-water;9590;finitewater;No more Infinite Water;;\nchoo-choo-craft;9591;choochoocharlescraft;Choo-Choo Craft;;\neco-mobs;9592;eco_mobs;Eco Mobs;;\nclassic-hostiles;9593;classichostiles;Classic Hostiles;;\nchalk-fabric-colorful-addon;9594;chalk-colorful-addon;Chalk (Fabric): Colorful Addon;;\n;9595;asielib;AsieLib;;\nfood-bosses-reheated;9596;foodbosses;food bosses: reheated!;;\ndreamland-biomes;9597;dreamland;Dreamland Biomes;;\n;9598;bedrockjukebox;基岩版唱片机;BedrockJukebox;\nenvironmental-expansion;9599;environmentalexpansion;Environmental Expansion;;EE\nneutrality;9600;neutrality;Neutrality;;\nbygone-land;9601;bygone_land;Bygone Land;;\nthe-amazing-world-of-mobs;9602;the_amazing_world_of_mobs;The Amazing World Of Mobs!;;\nfalloutcraft;9603;falloutcraft;Falloutcraft;;\nbotania-additions;9604;botaniaadditions;植物魔法附加;Botania Additions;\ndragonrealm;9605;dragonrealm;DragonRealm;;\nydms-ducks-mod;9606;theducksmod;The Ducks Mod;;\narthropod-reborn;9607;arthropod_reborn;Arthropod Reborn;;AR\nthe-undefeatables;9608;undefeatables;Undefeatables;;\ndurts-nether-dungeons;9609;netherdungeons;Durt's Nether Dungeons;;\n;9610;tweakeroo;Tweakfork;;\nhealth-and-hunger-tweaks;9611;healthhungertweaks;Health and Hunger Tweaks;;\n;9612;create-track-map;Create Track Map;;CTM\nimproved-hoes;9613;improvedhoes;Improved Hoes;;\norehider;9614;orehider;OreHider;;\nfull-grown-crop-marker;9615;crop_marker;FullGrownCropMarker;;\n;9616;;Minecraft Seasons Datapack;;\n;9617;mr_realistic_itemdrops;真实物品掉落;Realistic Item Drops;\nluggage;9618;luggage;行李箱;Luggage;\n;9619;scorched_mr;Scorched;;\nslime-breeder-advanced;9620;slimebreeder;Slime Breeder Advanced;;\nahznbs-naruto-mod;9621;narutomod;AHZNB's Naruto ShinobiCraft;;\ndelightful-mining;9622;delightfulmining;Delightful Mining;;\n;9623;itemscroller;Item Scroller crafting-fix;;\n;9624;beancoin;Bean Coin Mod;;\n;9625;controlling;ModernControlling;;\ntrimmable-tools;9626;trimmable_tools_mr;Trimmable Tools;;\n;9627;tablesaw;锯木台;Tablesaw;\nglowglass;9628;glowglass;Glowglass;;\nall-the-fan-made-discs;9629;all_the_fan_made_discs;All The Fan Made Discs;;ATFMD\nfrom-the-fog;9630;watching;From The Fog;;\nmore-ladders;9631;more_ladders;More+ Ladders;;\nreblured;9632;reblured;Reblured;;\nspellbound-weapons;9633;spellbound_weapons_mr;Spellbound Weapons;;\n;9634;;End Spawn;;\nsword-parry;9635;frycparry;Fryc's Parry / Sword Parry;;\n;9636;create_ruins;Create: Ruins;;\n;9637;minegpt;MineGPT;;MGPT\nrespiteful;9638;respiteful;闲暇风味;Respiteful;\nuseful-slime;9639;usefulslime;Useful Slime;;\nkelvins-better-player-animations;9640;betteranimations;Kelvin's Better Player Animations;;\n;9641;external_command;外部指令输入;External Command;\n;9642;this_you_model;这是你的模型;This You Model;TYM\ntfc-casting-with-channels;9643;tfcchannelcasting;TFC Casting With Channels;;\ntfc-create;9644;tfcreate;TFC Create;;\nmax-health-fixer;9645;maxhealthfixer;Max Health Fixer;;MHF\ngjeb;9646;gjeb;GammaJustExtremeBright;;GJEB\nbundled-delight;9647;bundledelight;Bundled Delight;;\n;9648;chunk_export;区块资源导出;Chunk Export;CEx\ntravellerstoasts;9649;travellerstoasts;TravellersToasts;;\n;9650;pilib;圆周率;PILib;\n;9651;ijo_pona_poki;ijo pona poki (AE2Things rewrite);;\nsk1llful;9652;skillful;身怀绝技;Skillful;\nsalt;9653;salt;The Salt;;\nmonobank;9654;monobank;Monobank;;\nsooty-chimneys;9655;sootychimneys;Sooty Chimneys;;\nareas-undefined;9656;areas_undefined;Areas Undefined;;AU\ntrivialthoughts;9657;trivialthoughts;魔金琐想;TrivialThoughts;TRT\ntfc-delight;9658;tfcdelight;TFC Delight;;\nmyssyheads-sculk-adventures;9659;myssyheads_sculk_adventures;Myssyhead's Sculk Adventures;;\nwaystone-towers;9660;waystone_towers;Waystone Towers;;\nsmitheesfoundry;9661;smithees-foundry;Smithee's Foundry;;\n;9662;wall_breaking_machine;黑曜石破壁机;Wall Breaking Machine;\nbeach-slimes;9663;beachslimes;Beach Slimes;;\nrideable-ravagers;9664;rideableravagers;可骑乘的劫掠兽;Rideable Ravagers;\nsplatcraft;9665;splatcraft;斯普拉遁;Splatcraft;\nbeneath-the-snow;9666;beneath_thesnow_mr;Beneath The Snow;;\nsea-creeps;9667;aquacreepermod;Sea Creeps;;\nskills-v;9668;skillsvanilla;技能：香草;Skills: Vanilla;\n;9669;teleportation;瞬身术 (Fabric);Teleportation (Fabric);\nflower-foxes;9670;flower_foxes;Flower Foxes!;;\npack-it-up;9671;pack_it_up;Pack It Up!;;\nmine-trading-cards-open-source-edition;9672;is_mtc;Mine Trading Cards Open Source Edition;;\nchainsaw-man-csm;9673;chainsawman;电锯人;Chainsaw Man;CSM\nsuperior-shields;9674;superiorshields;Superior Shields;;\nalternative-world-save-location;9675;alternativeworldsavelocation;Alternative World Save Location;;\nskills-firearms;9676;skillscgm,skillsfirearms;技能：枪械;Skills: Firearms;\ntx-loader;9677;txloader;TX Loader;;\nReacharound;9678;reacharound;Reacharound;;\nvariety-aquatic;9679;varietyaquaticforge,varietyaquatic;Variety Aquatic;;\nblocks-booster;9680;blockbooster;Blocks Booster;;\n;9681;ifmodloadwarn;如果你在加载我就梆梆两下;IFMODLOADWARN;IMLW\ntfc-cast-iron-grill;9682;castirongrill;群峦铸铁烤架;TFC Cast Iron Grill;\ntfc_metalwork;9683;tfc_metalwork;群峦金属加工;TFC Metalwork;\n;9684;GanDelightReborn;赣味乐事与家常菜;Gan Delight Reborn;GD\n;9685;;渔师与渔火重制版;Re:fisma;\n;9686;natura_repair;自然修补;Natural Repair;NR\nfurniture-compatibility-for-macaw-tfc;9687;mcw_tfc_furn;Macaw的家具：群峦传说附属;Furniture Compatibility for Macaw/TFC;\nroof-compatibility-for-tfc-macaw;9688;mcw_tfc_roof;Macaw的屋顶：群峦传说附属;Roof Compatibility for TFC/Macaw;\n;9689;cleanroom;Cleanroom;;\ntfc-supplementaries;9690;tfcsupplementaries;群峦锦致装饰;TFC Supplementaries;\ntfc-ie-crossover;9691;tfc_ie_addon;群峦传说：沉浸工程兼容附属;TFC + IE Crossover;\nbridging-mod;9692;placementpog,bridgingmod;Bridging Mod;;\ngeode-plus;9693;geode;晶洞+;Geode+;\ncursed-difficulty;9694;curse;Cursed Difficulty;;\nrepurposed-structures-yungs-better-nether-fortress;9695;repurposed_structures_yungs_better_nether_fortresses_compat;结构变体：YUNG 的下界要塞优化兼容数据包;Repurposed Structures - Yung's Better Nether Fortress Compat Datapack;\nrepurposed-structures-apocalyptic-fortress-compat;9696;;结构变体：Apocalyptic Fortress 兼容数据包;Repurposed Structures - Apocalyptic Fortress Compat Datapack;\nalgane;9697;algane;Algane;;\ndawn-of-time;9698;dawnoftimebuilder;时之黎明;Dawn of Time;\nkubejs-projecte;9699;kubejsprojecte;KubeJS ProjectE;;\nbiome-backport;9700;biomebackport;Biome Backport;;\ninfernal-geode;9701;infernal_geode;Infernal Geode;;\ngeode-o-plenty;9702;geodeoplenty;Geode O' Plenty;;\nthe-banished;9703;the_banished;The Banished;;\npufflos;9704;pufflos;Pufflos!;;\nwizardry-the-next-generation;9705;wizardrynextgeneration;Wizardry Next Generation;;\n;9706;loading_hint;玩家加入显示;Loading Hint;\nlagbgon;9707;LagBGon;Lag'B'Gon;;\ndoodads-fabric;9708;doodads;Doodads;;\nsubject-three-sweet-dreams;9709;subject3;科目三：甜蜜的梦;Subject Three: Sweet Dreams;ST\nocd-shaders-mod;9710;;OCD Shaders;;OCD\ncreate-shrunk;9711;shrunk;Create Shrunk;;\ncherish;9712;cherish;Cherish;;\nthe-lost-castle;9713;tlc;失落城堡;The Lost Castle;TLC\nllamarama;9714;llamarama;羊驼！;Llamarama;\nsimple-conveyors;9715;simple_conveyors;Simple Conveyors;;\nitemupgrader-core;9716;itemupgrader;Item Upgrader Core;;\nitemupgrader;9717;itemupgrader_content;Item Upgrader;;\nscreenfx;9718;screenfx;ScreenFX;;\ndense-ores-abundant;9719;denseores;Dense Ores - Abundant!;;\ntinkers-thinking;9720;tinkers_thinking;工匠巧思;Tinkers' Thinking;TiT\nsimple-musket;9721;simplemusket;Simple Musket;;\ncash-ores;9722;cashore;Cash Ores;;\nstacked-dimensions;9723;stackeddimensions;Kelvin's Stacked Dimensions;;\n;9724;unlimitedmaxhealth;Unlimited MaxHealth;;\nfloral-enchantment;9725;floralench;Floral Enchantment;;\nnewworld;9726;newworld;NewWorld;;\n;9727;morestats;More Stats;;\nnotifmod;9728;notifmod;NotifMod;;\nwallfence;9729;wallfence;Wallfence;;\nezwastelands;9730;ezwastelands;ezWastelands;;\nzombie-survival-kit;9731;zombiekit;末日生存工具包;Zombie Survival Kit;\nkobolds;9732;kobolds;狗头人！;Kobolds!;\nsupernatural;9733;supernatural;超自然;Supernatural;\nbetter-ladder-placements;9734;salju_ladders;Salju的梯子;Salju's Ladders;\nmagic-mirrors-forge;9735;magicmirror;Magic Mirrors;;\n;9736;doglib;狗库;Dog Lib;\nabundant-atmosphere;9737;abundant_atmosphere;Abundant Atmosphere;;\nswampex;9738;jawfish;Swampex;;\nbamboo-combat;9739;bamboocombat;Bamboo Combat;;\nnicer-skies;9740;nicer-skies;Nicer Skies;;\nbed-benefits;9741;bedbenefits;Bed Benefits;;\naccurate-maps;9742;accuratemaps;Accurate Maps;;\nincantationem;9743;incantationem;Incantationem;;\nrebind-narrator;9744;rebind_narrator;Rebind Narrator;;\nearth2java;9745;earthtojavamobs;Earth2Java;;\nfairy-dragons;9746;dragonmod;Fairy Dragons!;;\nturned-changed-extension;9747;turned;Turned: Changed Extension;;\nimmediatelyfast-reforged;9748;;ImmediatelyFast Reforged;;\nmagic-mirror;9749;magicmirror;Magic Mirror;;\nmagic-mirror-fabric;9750;magic_mirror;Magic Mirror;;\nchassis;9751;chassis;Chassis;;\n;9752;banit;禁用它！;Ban It！;\nfast-doll;9753;fast-doll;FastDoll;;\nbetter-lily-pads;9754;betterlily;更好的睡莲;Better Lily Pads;\nmo-zombies-wave;9755;mozombies_wave;Mo' Zombies Wave;;\nalmost-all-the-ores;9756;almostalltheores;Almost all the Ores;;\n;9757;geyser-fabric,geyser_neoforge;Geyser;;\nsupplementaries-squared;9758;suppsquared;Supplementaries Squared;;\nchanged-addon-plus;9759;changed;Changed Addon Plus;;\nfunctional-armor-trim;9760;functionalarmortrim;Functional Armor Trim;;\n;9761;teleportblocker;Teleport Blocker;;\nbelly-button;9762;bellybutton;Belly Button;;\nraptor-chickens;9763;raptor_chickens;Raptor Chickens;;\n;9764;stygian;幽冥末地：非官方版;Stygian End:Unoffical;\ncraftheraldry;9765;CraftHeraldry;CraftHeraldry;;\npfaeffs-mod;9766;pfaeffsmod;Pfaeff's Mod;;\na-bad-experience;9767;a_bad_experience;A Bad Experience;;\nthe-nectar-mod;9768;the_nectar_mod;The Nectar;;\nheavy-boulders;9769;heavy_boulders;Heavy Boulders;;\nwitherbold;9770;witherbold;Witherbold;;\njewelcraft;9771;jewelcraft;珠宝工艺;Jewelcraft;\ncorrupted-souls;9772;corrupted_souls;Corrupted Souls;;\ncinnamon;9773;cinnamon;cinnamon!;;\ntfc-fight;9774;tfcfight;TFC Fight;;\nsbm-magic-mirror;9775;sbmmagicmirror;Magic Mirror;;\nfarmers-delight-heat-compats;9776;farmersdelightintegration;Farmer's Delight Heat Compats;;\ntofu-delight;9777;tofudelight;豆腐乐事;Tofu Delight;\ngencreator;9778;gencreator;GenCreator;;\ndangeonplus;9779;DungeonPlus;DungeonPlus;;\nend-upgrade;9780;idi_naxui_suca;End upgrade;;\nbufferfish;9781;mobvotebuffer;Bufferfish;;\nflorescence;9782;flowering;Florescence;;\nmobcages;9783;mobcages;MobCages;;\nyou-win-button;9784;youwinbutton;You Win Button;;\ntea-anyone;9785;teamod;Tea, Anyone?;;\nenvirorot;9786;envirorot;Envirorot;;\nthe-seasons-mod;9787;seasonsmod;四季模组;The Seasons Mod;\n;9788;castledefenders;Castle Defenders;;\nthe-realistic-leaves-mod;9789;RealisticLeaves;The Realistic Leaves Mod;;\nuntitled-swans-forge;9790;untitledswanmod;Untitled Swans;;\nplaydate;9791;playdate;Playdate;;\nmolotov;9792;molotov;Molotov;;\nanother-weapons;9793;another_weapons;Another Weapons;;AW\n;9794;elytratoggle;Elytra Toggle;;\ncompression;9795;compression;Compression;;\ncompressor;9796;compressor;Compressor;;\n;9797;patcher;Patcher;;\nhexal;9798;Hexal;咒法拓展;Hexal;\n;9799;;实用盔甲纹饰;Useful Armor Trim;UAT\ncreate-encased;9800;createcasing,createsasing-extendedgears;机械动力：封装工艺;Create Encased;\ntreeplacer;9801;treeplacer;Treeplacer;;\ndeath-knell;9802;deathknell;Death Knell;;\nmoreiotas;9803;MoreIotas;MoreIotas;;\nnumismatic-overhaul;9804;numismatic-overhaul;货币学拓展;Numismatic Overhaul/Numismatic Overhaul: Reforged（forge）;\nhexbound-fork;9805;Hexbound;咒法组构学;Hexbound;\nvanity-druid-pack;9806;vanity_druid;Vanity: Druid Pack;;\nvanity-farcrs-colossal-weapons;9807;vanity_colossal_weapons;Vanity: Farcr's Colossal Weapons;;\ndebris-chunk-raw-netherite-scrap;9808;raw_netherite;残骸碎块;Debris Chunk;\nmalum-vestis;9809;vestis;Malum: Vestis;;\nculllessleaves-reforged;9810;culllessleaves;更好的树叶渲染优化重铸版;CullLessLeaves Reforged;\nbiome-music;9811;biomemusic;生物群系音乐;Biome Music;\nagers-earthquake-generator;9812;agers_earthquake_mod;Ager's Earthquake Generator;;\nvanity-black-gold;9813;vanity_black_gold;Vanity: Black Gold Pack;;\nvanity-bone-pack;9814;vanity_bone;Vanity: Bone Pack;;\nwhat-that-wolfhouse;9815;wolfhouse;那是什么？仓库管理员！;What That？ Wolfhouse！;WW\nclick-for-hot-chicks;9816;hotchicks;Click for Hot Chicks;;\nvanity-molten-pack;9817;vanity_molten;Vanity: Molten Pack;;\nvanity-spartan-pack;9818;vanity_spartan;Vanity: Spartan Pack;;\nwoods-and-mires;9819;woods_and_mires;Woods and Mires;;\nsuggestion-tweaker;9820;suggestion_tweaker;Suggestion Tweaker;;\nAdditional-Redstone;9821;additionalredstone;Additional Redstone;;\nheartstone;9822;heartstone;Heartstone;;\ndyed;9823;dyed;Dyed;;\nwireless-redstone;9824;wirelessredstone;Wireless Redstone;;\n;9825;scm;史莱姆工艺&火枪;Slime's Craft&Musket;SCM\nvanity-steel-pack;9826;vanity_steel;Vanity: Steel Pack;;\nvanity-viking;9827;vanity_viking;Vanity: Viking Pack;;\nhorse-expert;9828;horseexpert;Horse Expert;;\nsnow-under-trees-remastered;9829;snowundertrees;Snow Under Trees (Fabric);;\noceans-delight;9830;oceansdelight;海洋乐事;Ocean's Delight;\ntranscending-trident;9831;transcendingtrident;Transcending Trident;;\nsnow-under-trees-fabric;9832;snowundertrees;Snow Under Trees for Fabric;;\nprecision-enchanter;9833;precisionenchanter;Precision Enchanter;;\n;9834;leahs-immersive-thunder,leahs_immersive_thunder;ImmersiveThunder;;\ntop-extras;9835;topextras;TOP 附加;TOP Extras;TOPE\n;9836;;Minecraft Diverge;;\npotatweaker;9837;potatweaker;Potatweaker;;\neffect-tooltips;9838;effecttooltips;Effect Tooltips;;\n;9839;LMSYSG;Show Me Something Good;;SMSG\nvariant-crafting-tables;9840;vct;Variant Crafting Tables;;\nendless-biomes;9841;endlessbiomes;Endless Biomes;;EB\n;9842;skillfultweaker;SkillfulTweaker;;\nchatimg;9843;chatImg;图片聊天;chatImg;CI\nthrowable-explosives;9844;throwableexplosives;Throwable Explosives;;\n;9845;skillmmo;SkillMMO;;\narcheon;9846;archeon;Archeon;;\nchattoggle;9847;chattoggle;Chat Toggle;;\nenchancement;9848;enchancement;Enchancement;;\n;9849;mtp;M传送;;mtp\nuseful-spyglass;9850;usefulspyglass;Useful Spyglass;;\nnff-girls;9853;nffgirls;NFF: Girls;;\nlaunchgui;9854;launchgui;LaunchGUI;;\nvillager-armor-layer;9855;villagerarmor;村民盔甲显示;Villager Armor Layer;VAL\nwasabi;9856;wasabi;Wasabi;;\nmagnet-craft;9857;magnetcraft;磁铁工艺;Magnet Craft;\nitem-obliterator;9858;item_obliterator;Item Obliterator;;\nyou-shall-not-spawn;9859;ysns;You Shall Not Spawn!;;YSNS\nhell-on-earth;9860;hellonearth;Hell on Earth;;\nzombie-friends;9861;zombiefriends;Zombie Friends! 🧟‍♀️ 🧟 🧟‍♂️;;\n;9862;oldb,stonecraft;石头工艺;Stone Craft;SC\ntrade-cycling;9863;trade_cycling;Trade Cycling;;\nillager-revolution;9864;illagerrevolutionmod;Illager Revolution;;\nextra-trades;9865;extratrades;Extra Trades;;\nblood-particles-mod;9866;hit_particles;blood particles mod;;\n;9867;twoweeks;Two Weeks HUD;;\nfazcraft-mod;9868;fazcraft;Fazcraft;;\nmod-menu-no-more;9869;test-1;Mod Menu No More;;\n;9870;;Slime Craft;;\nway2wayfabric;9871;way2wayfabric;way2wayfabric;;\ncloche-profit-peripheral;9872;clochepp;Cloche Profit Peripheral;;\nl2weaponry;9873;l2weaponry;莱特兰-武器;L2 Weaponry;\n;9875;;铑;Rhodium;\nzoom-o-matic;9876;zoom-o-matic;Zoom-o-Matic;;\n;9877;brokentoolmod;Broken Tool;;\nfishing-ruler;9878;fishing_ruler;Fishing Ruler;;\ncommand-relay;9879;commandrelay;Command Relay;;\nbeasts-bosses;9880;beastsnbosses;Beasts & Bosses;;\n;9881;sc;压缩石锤;Stone Hammer;SH\nthe-one-probe-community-edition;9882;theoneprobe;检测器-社区版;The One Probe Community Edition;TOPML\npackageddraconic;9883;packageddraconic;封包龙之进化;PackagedDraconic;\nimitationcore;9884;com.paleimitations.imitationcore,imitationcore;ImitationCore;;\nbig-sip;9885;bigsip;Big Sip;;\nfloating;9886;floating;Floating;;\n;9887;blasttravel;Blast Travel;;\nskygrid;9888;skygrid;Skygrid;;\ndisable-new-world-creation-screen-forge;9889;disable_new_world_screen;Disable New Create World Menu;;\npackagedmekemicals;9890;packagedmekemicals;封包通用化学品;PackagedMekemicals;\npackagedastral;9891;packagedastral;封包星辉魔法;PackagedAstral;\nmekanism-advancedgenerators;9892;solarpanels;通用机械-更多太阳能板;Mekanism Advanced Generators / Mekanism-MoreSolarPanels;\npaperandquill;9893;paperandquill;Paper And Quill;;\n;9894;baron_bunny;兔兔伯爵;Baron Bunny;\nsneaky-curses-forge;9895;sneakycurses;Sneaky Curses;;\nmagnum-torch-forge;9896;magnumtorch;卓越火炬;Magnum Torch;\nuniversal-bone-meal;9897;universalbonemeal;通用骨粉;Universal Bone Meal;UB\n;9898;bottleofgender;Bottle of Gender;;\n;9899;coretweaks;CoreTweaks;;\nleave-my-bars-alone;9900;leavemybarsalone;改进的骑乘HUD;Leave My Bars Alone;\nall-enchantments-for-trade;9901;allenchantmentsfortrade;All Enchantments for Trade;;\ni-need-block;9902;ineedblock;I NEED BLOCK;;\nspiky-spikes;9903;sneakycurses;Spiky Spikes;;\nstraw-statues;9904;strawstatues;稻草雕像;Straw Statues;SS\nlinear-xp;9905;linearxp;Linear XP;;\nsave-gear-on-death;9906;sgod;Save Gear on Death;;\ncurtain;9907;curtain;窗帘;Curtain;\nrrls;9908;rrls;移除重载界面;Remove Reloading Screen;RRLS\nentity-model-features;9909;entity_model_features;实体模型特性;Entity Model Features;EMF\nforgestory;9910;forgestory;ForgeStory;;FS\narmor-statues;9911;armorstatues;Armor Statues;;AS\n;9912;clocks_andchimes_mr;Axel's Clocks and Chimes;;\nworld-of-bosses;9913;somebosses;World of bosses;;\n;9914;mwdmgsrcfix;模块化武装伤害源修复;ModularWarfareDamageSourceFix;\nmirabilis-vanilla-biome-overhaul;9916;mirabilis;Mirabilis: Biome Overhaul;;\nenergized-power;9917;energizedpower;Energized Power;;\ncarpenter;9918;carpenter;Carpenter;;\ntolerable-creepers;9919;tolerablecreepers;Tolerable Creepers;;\nanchor;9920;anchor;Anchor;;\nlocksmith;9921;locksmith;Locksmith;;\nmystical-machinery;9922;mysticalmachinery;神秘机械;Mystical Machinery;\nlife-steal-enchantment;9923;life_steal_enchantment;Life Steal Enchantment;;\nmore-wandering-trades;9924;more_wandering_trades;更多流浪商人交易;More Wandering Trades;\nmineralas;9925;mineralas;Mineralas;;\necospherical-expansion;9926;eco;Ecospherical Expansion;;\necospherical-depths;9927;ecod;Ecospherical Depths;;\narrhys-simple-currency;9928;arrhys_simple_currency;Arrhy's Simple Currency;;\nbetter-wandering-trader;9929;wandering_trader;Prelaw's Better Wandering Trader;;\ninterdimensional-wireless-transmitter;9930;creativewirelesstransmitter;创造无线发送器;Interdimensional Wireless Transmitter;\ncozy-home;9931;cozy_home;Lucky's Cozy Home: Refurnished;;\ninventory-crafter;9932;inventorycrafter;Inventory Crafter;;\n;9933;paimonfoodrecooked;应急食品：重烹;Paimon Food: Recooked;PFRe\nmaxs-armory;9934;max_armory;Max 的武器库;Max's Armory;\nnefs-farm-decoration;9935;nefdecomod;Nef's Farm Decoration & Explorer gear;;\ncreate-armory;9936;createarmory;Create: Armory;;\ndangerous-wasps;9937;klsts_wasps,wasps;Dangerous Wasps / Klsts' Wasps Mod;;\ncreatefabricreibugfix;9938;createreibugfix;机械动力 Fabric 版 REI 配方显示修复;Create&REIBugFix / CreateFabric&REIBugFix;CRBF/CREIBF/CFREIBF\nseasontweaks;9939;seasontweaks;SeasonTweaks;;\n;9940;Potion_Tool;药水工具;Potion_Tool;PT\n;9941;mr_villedit;VillEdit;;\n;9942;automessenger;AutoMessenger;;\n;9943;equipsuit;装备套装;EquipSuit;EQ/EQC\n;9944;bettercheesemodel;更好的奶酪模型;Better Cheese Model;BCM\npadoru;9945;padoru;Padoru;;\n;9946;craftablereinforceddeepslate;可合成的强化深板岩;Craftable Reinforced Deepslate;CRD\nsnow-coated;9947;snowcoated;Snow Coated;;\nmiracle-berry;9948;miracle_berry_mod;Miracle Berry;;\nlocked-chests;9949;locked_chests;Locked Chests;;\nmaxs-armory-expansion;9950;max_armory_exp;Max 的扩展武器库;Max's Armory Expansion;\nrepurposed-structures-bygone-nether-compat;9951;repurposed_structures_bygone_nether_compat;结构变体：逝去的下界兼容数据包;Repurposed Structures - Bygone Nether Compat Datapack;\nremove-mouseover-highlight;9952;rmh;Remove Mouseover Highlight;;\napocalyptic-fortress;9953;hexfortress;Apocalyptic Fortress;;\n;9954;horsefallfix;HorseFallFix;;\n;9955;filetweaker;FileTweaker;;\nunbreakabler;9956;unbreakabler;Unbreakabler;;\ntrails-and-tales-fabric;9957;various_update;Trails and Tales 1.20;;\ntinkers-bounce-pad;9958;tbouncepad;匠魂弹跳板;Tinkers Bounce Pad;\nenderio-unofficial;9959;EnderIO;末影接口非官方版;EnderIO Unofficial;EIOu\n;9960;pastertetra;PasterTetra;;\ndimensional-gravity;9961;end_gravity;Dimensional Gravity;;\n;9962;;时间整蛊;Time pranks;TEP\nbring-color-to-my-skies;9964;colormysky;Bring Color to my Skies;;\nportal-dungeons;9965;portal_dungeons_mod;Portal Dimension;;\n;9966;flammpfeil.slashblade.laemmle;幻魔炼金拵;Laemmle;\nradiant-gear;9967;radiantgear;Radiant Gear;;\ncreate-teleporters;9968;createteleporters;机械动力：传送器;Create : Teleporters;\nenchantato;9969;enchantato;Enchantato;;\nfloating-block;9970;floating_block;浮空立方体;Floating Cube;\ncurious-lanterns;9971;curiouslanterns;Curious Lanterns;;\nriver-redux;9972;riverredux;River Redux;;\nrestrictions;9973;restrictions;Restrictions;;\ncreate-modern-train-parts;9974;moderntrainparts;Create: Modern Train Parts;;\n;9975;dstc;Don't Starve Together: Cuisine;;DSTC\ntargetato;9976;targetato;Targetato;;\nmessagetweaker;9977;messagetweaker;MessageTweaker;;\n;9978;rubeAurora;Aurora Rubealis;;\n;9979;endercore;末影核心非官方版;EnderCore Unofficial;\ntskimi-seirans-fruit-wine;9980;tksrwine;月见清兰的果酒;Tskimi Seiran's Fruit Wine;TSFW\ngraduated-cylinders;9981;graduatedcylinders;刻度量筒;Graduated Cylinders;\n;9982;EnderIO;末影接口GTNH非官方版;EnderIO-Unofficial;\nfps-monitor;9983;fps;FPS Monitor;;\nathena;9984;stitch,athena;Athena / Stitch;;\ncalm-mornings;9985;calmmornings;Calm Mornings;;\nfog-looks-good-now;9986;foglooksgoodnow;Fog Looks Good Now;;\nguarding;9987;guarding;Guarding!;;\ncats-expanded;9988;catsexpanded;Cats Expanded;;\nmodular-bosses;9989;mb;Modular Bosses;;MB\nexperience-obelisk;9990;experienceobelisk;Cognition / Experience Obelisk;;\nhere-be-no-dragons;9991;here-be-no-dragons;Here be no Dragons!;;\ngnumus-settlement;9992;gnumus;Gnumus Settlement;;\nfading-night-vision;9993;fadingnightvision;Fading Night Vision;;\nmru;9994;mru;Mineblock's Repeated Utilities;;M.R.U\nmute;9995;mute;Mute;;\nsimple-knives;9996;simpleknives;Simple Knives;;\niron-fishing-rods;9997;iron_fishing_rods;Iron Fishing Rods;;\ncave-rats;9998;cave_rats;Cave Rats;;\n;9999;squeedometer;Squeedometer (Fabric);;\n;10000;effect_export;药水状态效果导出;Effect Export;EE\nsky-structures;10001;sky_structures;Sky Structures;;\nmannequins;10002;mannequins;Mannequins;;\nsimple-mango;10003;simplemango;Simple Mango;;\nbetterdrawhoveringtext;10004;bdht;更好的工具信息显示;betterDrawHoveringText;bdht\n;10005;turn_into_eggs;Turn Into Eggs;;TIE\nbadges-by-harmex;10006;badges;CobbleBadges;;\nterrarian-arsenal;10007;terrarian_arsenal;Terrarian Arsenal;;\nzombie-veterinarian;10008;zombieveterinarian;僵尸科兽医;Zombie Veterinarian;\njust-enough-sculk-sensor;10009;justenoughsculksensor;Just Enough Sculk Sensor;;JESS\nhearth-and-home;10010;hnh,hearth_and_home;Hearth & Home;;\nbetter-leveling;10011;betterleveling;更好的升级;Better Leveling;\napplied-mana;10012;mana_ae;应用魔法;Applied Mana;AM\nboundless-flux;10013;boundlessflux;Boundless Flux;;\njkbw;10014;mr_jkbw;起床战争工具包;JK's BedWars ToolPack;jkbw\n;10015;axolotlclient,axolotlclient-common;Axolotl Client;;\nenergy-nodes;10016;energynodes;Energy Nodes;;\nfog;10017;fog;Fog;;\nthirst-was-taken;10018;thirst;口渴;Thirst Was Taken;\n;10019;blazeandcave;BlazeandCave's Advancements Pack Terralith version;;\nunlimited-fluidity;10020;unlimitedfluidity;Unlimited Fluidity;;\ne4mc;10021;e4mc_minecraft,e4mc;e4mc;;\nlate-game-golems;10022;lategamegolems;Late Game Golems;;\niter-rpg;10023;iter_rpg;Iter RPG;;\n;10024;stationarysourceblocks;Stationary Source Blocks Potato Edition;;\noceanic-expanse;10025;oe;海洋拓展;Oceanic Expanse;\n;10026;datapackloaderrorfix;数据包加载错误修复;Datapack Load Error Fix;DLEF\n;10027;sophon;智子;Sophon-Architectury;\norigins-vampire;10028;origins_vampire;起源：吸血鬼;Origins: Vampire;\n;10029;norandomfluid;抱歉，我们拒绝为您提供随机液体;No Random Fluid;\nbad-to-the-bone;10030;badbone;Bad to the Bone;;\nworkers;10031;workers;Villager Workers;;\n;10032;cregtech;克瑞科技;CregTech;\nsculk-horde;10033;sculkhorde;幽匿部落;Sculk Horde;\nstratospherical-expansion;10034;strato;Stratospherical Expansion;;\nlets-do-candlelight-farm-charm-compat;10035;candlelight;烛火晚宴;[Let's Do] Candlelight;\nlet-me-play-iris;10036;lmpi;Let me play Iris!;;\ndimensional-depths;10037;ecod;Dimensional Depths;;\ncreate-molten-vents;10038;molten_vents;Create: Molten Vents;;\neverything-as-a-hat;10039;everythingasahat;Everything as a Hat;;\n;10040;crashguard;CrashGuard;;\nworld-border-expander;10041;world_expander;World Border Expander;;\nhtmltech;10042;htmltech;htmlTech;;\nextra-tan;10043;extratan;Extra TAN;;\ndiamond-economy;10044;diamondeconomy;钻石经济;Diamond Economy;\ntexels-paintings;10045;texelspaintings;Texels Paintings;;\ntinkers-expansion;10046;tinkersexpansion;Tinkers' Expansion;;\nrevelationary;10047;revelationary;Revelationary;;\nfullstack-watchdog;10048;fullstackwatchdog;FullStack Watchdog;;\n;10049;kbtwkr;KeyBindingTweaker;;\nadventure-apparatus;10050;adventureapparatus;Adventure Apparatus;;\nneruina;10051;neruhina,neruina;Neruina;;\nkawaii-dishes;10052;kawaiidishes;Kawaii Dishes;;\n;10053;gregica;Gregica++;;GC++\nkeep-my-hand;10054;keepmyhand;Keep My Hand;;\nno-lan-cheating;10055;nolancheating;No LAN Cheating;;\ngoat-horn-block-mod;10056;goat_horn_block_mod;可放置山羊角;Placeable Goat Horns;\n;10057;ouo;OUO;With Task;\nmetallics-arts;10058;metallics_arts;金属技艺;Metallics Arts;\nactions-of-stamina;10059;actionsofstamina;Actions of Stamina;;AoS\n;10060;pkttwkr;PktTweaker;;\ndisenchanting-book;10061;disenchantingbook;Disenchanting Book;;\nstackdeobf;10062;stackdeobfuscator;StackDeobfuscator;;\n;10063;MultiPageChest;多页箱子：魔戒版;Multi Page Chest: LOTR Edition;\nportfolio;10064;portfolio;Portfolio;;\n;10065;nvidium;Nvidium;;\nmerequester;10066;merequester;ME Requester;;\ninvade;10067;invade;入侵;Invade;\nframe-api;10068;frame;🔨 Frame API;;\n;10069;creepersneeze;苦力怕喷嚏;CreeperSneeze;CS\nrealisticclouds;10070;realisticclouds;RealisticClouds;;RC\narcane-world;10071;arcaneworld;Arcane World;;\ntiled-cauldron;10072;tiled_cauldron;Tiled Cauldron;;\nmanyideas-christmas;10073;manyideas_christmas;ManyIdeas Christmas;;\noverflowing-bars;10074;overflowingbars;Overflowing Bars;;OB\nkokoalinux;10075;kokoalinux;KokoaLinux;;\nmedievalweapons;10076;medievalweapons;中世纪武器;MedievalWeapons;\ncompletionists-index;10077;completionistsindex;Completionist's Index;;CI\nstoneworks;10078;stoneworks;Stoneworks;;SW\n;10079;hutoolcore;HutoolCore;;\nmob-lassos;10080;moblassos;Mob Lassos;;ML\nmutant-monsters;10081;mutantmonsters;Mutant Monsters;;MM\nxk-perfect-glass-pane;10082;;完美玻璃板;Perfect glass pane;\n;10083;physics_toys;Physics Toys;;\ntarot-cards;10084;tarotcards;塔罗牌;Tarot Cards;\n;10085;TcpNoDelayMod;TcpNoDelayMod;;\nchatmove;10086;chatmove;ChatMove;;\n;10087;forgedfabricloader;ForgedFabricLoaderAPI / ForgedLoaderAPI;;FFL\npublic-transport-mod;10088;transportmod;Public Transport Mod;;PTM\nmineral-chance;10089;mineralchance;Mineral Chance;;\nrandom-bone-meal-flowers;10090;randombonemealflowers;Random Bone Meal Flowers;;\nnow-playing-forge;10091;nowplaying;Now Playing;;\nend-portal-recipe;10092;endportalrecipe;末地传送门配方;End Portal Recipe;\ncrying-portals;10093;cryingportals;Crying Portals;;\ndragon-drops-elytra;10094;dragondropselytra;末影龙掉落鞘翅;Dragon Drops Elytra;\nname-tag-tweaks;10095;nametagtweaks;命名牌调整;Name Tag Tweaks;\nfunky-yoyo;10096;funkyyoyo;Funky Yoyo;;\npierce-arrow;10097;piercearrow;Pierce Arrow;;\ntool-sharpening;10098;tool_sharpening;Tool Sharpening;;\n;10099;ktfruaddon;群峦救援-非官方版扩展;kTFRUAddon;\npigsteel;10100;pigsteel;Pigsteel;;\n;10101;manic_mr;癫狂;Manic;\n;10102;PowerItemRenderer;PNX渲染图片导出;PowerItemRenderer;PIR\n;10103;ofstream_t;导出乐;OfstreamT;\n;10104;ofstream_advancement;导出乐<进度>;Ofstream Advancement;\nflowermap;10105;flowermap;花卉地图;Flower Map;\nhopo-better-mineshaft;10106;hopobettermineshaft,hopo;Hopo Better Mineshaft;;\n;10107;dip;伤害显示兼容;Damage Indicators Patch;\nreforgium;10108;reforgium;Reforgium;;\nspoorn-weapon-attributes;10109;spoornweaponattributes;Spoorn Weapon Attributes;;\n;10110;notfine;NotFine;;\nfabrc-legacy-fixes;10111;legacy-fixes;Legacy (Fabric) Fixes;;\n;10112;ofstream_plastic;导出乐<聚合>;Ofstream Plastic;\n;10113;ofstream_fasrirr;导出乐<快速IRR>;OfstreamFastIRR;\n;10114;night_rogue;夜霸;Night Rogue;\nmoa-decor-bath;10115;moa_decor_bath,moa_bath;MOA DECOR: BATH;;\n;10116;edf_remastered_mr,mr_edf_remastered;Ender Dragon Fight Remastered;;\nstealing-the-villagers-forge;10117;stealing_villagers;Stealing The Villagers;;\nspoorn-armorattributes;10118;spoornarmorattributes;Spoorn Armor Attributes;;\nconnectedness;10119;connectedness;Connectedness;;\nepic-samurais;10120;epicsamurai,samurai_dynasty;史诗武士;Epic Samurai's / Samurai Dynasty;\nsandwiches-n-more;10121;sandwichesnmore;Sandwiches N' More;;\nenchantments-plus-fabric;10122;enchantmentsplus;Enchantments-Plus;;\npurpeille;10123;purpeille;Purpeille;;\ncarpet-sky-additions;10124;carpetskyadditions;Carpet Sky Additions;;\nspice-of-life-carrot-edition-unofficial;10125;;生活调味料：胡萝卜非官方版;SOL - Carrot Unofficial;\nender-dragon-egg-respawn-fabric;10126;ender_dragon_egg_respawn;Ender Dragon Egg Respawn;;\npersonal-space;10127;personalspace;私人维度;Personal Space;\nheld-item-tooltips;10128;helditemtooltips;手持物品信息显示;Held Item Tooltips;IT\nminecraft-shark-mod;10129;shark_mod;Sharks Mod;;\nforgotten-delight;10130;forgotten_delight;Forgotten Delight;;\nenhancedlootbags;10132;enhancedlootbags;EnhancedLootBags;;\nrecipe-reshaper;10133;recipe_reshaper;Recipe Reshaper;;\nhellish-blahaj;10134;blahaj;布罗艾 Hellish 版;Hellish Blåhaj;\namber-jade;10135;amber;琥珀;Amber;\nmafglib;10136;malilib,mafglib;MaFgLib / MaLiLib-Forge;;\n;10137;fastminecart;Fast Minecart;;\nae2_tools;10138;ae2_tools;AE2 Tool's Complement Compat;;\ncreate-compats;10139;acc_nocube;Create Compats;;\ndont-drop-it;10140;dontdropit;Don't Drop It!;;\nnicemod-new-blocks;10141;nicemod;NiceMod - New blocks;;\nforgematica;10142;litematica,forgematica;全息蓝图;Forgematica / Litematica-Forge;\nframland;10143;framland;Framland;;\nblahaj-legacy;10144;blahaj;布罗艾：旧版;Blåhaj：Legacy;\ndeep-aether;10145;deep_aether;深入天境;Deep Aether;\nthe-forgotten-dimensions;10146;the_forgotten_dimensions;The Forgotten Dimensions;;TFD\n;10147;Sink;水槽;The Sink;\n;10148;killstreakhud;连杀HUD显示;KillStreakHUD;KST\n;10149;stairjumper;stairjumper;;\nwatchdog-anti-cheat;10150;watchdog;WatchDog Anti Cheat;;\nvillagers-drop-emeralds-on-death;10151;villagerdrops;Villagers Drop Emeralds on Death;;\nneydis-dragons;10152;neydis_dragons;Neydis dragons;;\ncolourful-goats;10153;colourfulgoats;Colourful Goats;;\nperfect-spawn;10154;perfectspawn;Perfect Spawn;;PS\n;10155;vanilla_refresh_mr;Vanilla Refresh;;\ninfinity-pickaxe;10156;infinity_pickaxe;Infinity Pickaxe;;\nbiome-spawn-point;10157;biomespawnpoint;Biome Spawn Point;;\nsmallbiomes;10158;smallbiomes;Biome Sizes;;\nenhanced-block-entities-reforged-unofficial;10159;enhancedblockentities;Enhanced Block Entities Reforged;;\nhopo-better-ruined-portals;10160;hopobetterruinedportal,hoporp;Hopo Better Ruined Portals;;\nwearthat;10161;wearthat;WearThat;;\nportable-crafting-table;10162;portablecraftingtable;便携工作台;Portable Crafting Table;\nstructory-towers;10163;structory_towers,structorytowers;Structory: Towers;;\neverything-is-a-hat;10164;everythingisahat;Everything is a Hat;;\nskeletal-remains;10165;skeletalremains;Skeletal Remains;;\ntidbits;10166;tidbits;Tidbits;;\nterrible-chest-potato-edition;10167;terrible_chest;可怖箱子：土豆版;Terrible Chest Potato Edition;\n;10168;dryinglava;Drying Lava;;\nadvancedreborn;10169;advanced_reborn;Advanced Reborn;;\nblue-archive-halos;10170;minecraftbluearchivehalo;Blue Archive Halos;;\n;10171;totemcounter;TotemCounter;;\n;10172;flighthud;FlightHUD重置版;FlightHUD Reloaded;\n;10173;manavisualizer;Mana Visualizer;;\nmhcraft;10174;mhcraft;Monster Hunter: Heaven and Hell / MHCraft;;\nirons-spells-n-spellbooks;10175;irons_spellbooks;Iron的法术与魔法书;Iron's Spells 'n Spellbooks;ISS\ninfernal-enchantments;10176;infernalenchantments;Infernal Enchantments;;\nreimagined-world-menu;10177;reimaginedmenus;Reimagined World Menu;;\norigins-assassin;10178;assassinorigin;Origins - Assassin;;\npeculia;10179;peculia;Peculia;;\nkoratio;10180;koratio;Koratio;;\npromans-origins;10181;promans_origins;Proman's Origins;;\nmermaid-mythical-origins;10182;mermaid;Mermaid - Mythical Origins;;\nwerewolf-mythical-origins;10183;werewolf;Werewolf - Mythical Origins;;\nfairy-mythical-origins;10184;fairy;Fairy - Mythical Origins;;\nelf-mythical-origins;10185;elf;Elf - Mythical Origins;;\ndragon-mythical-origins;10186;dragon;Dragon - Mythical Origins;;\nbudget-fire-aspect;10187;budget_fire_aspect;Budget Fire Aspect;;\nfacehugger;10188;facehugger;Facehugger;;\nvine-boom-moments;10189;vine_boom_moments;Vine Boom Moments;;\nlotta-blocks;10190;lottablocks;Lotta Blocks;;\nsnip-those-tags;10191;snipping;Snip those tags!;;\nzombie-variants-forge;10192;zombie_variants;Zombie Variants;;\nnights-of-sorrow;10193;nights_of_sorrow;Nights of Sorrow;;\nsimple-spells;10194;budget_fire_aspect;Simple Spells;;\nauditory;10195;auditory;Auditory;;\ntrulios-compilation;10197;trulios_compilation_rework;Trulio's Compilation;;\n;10198;cyclones;气旋;Cyclones;\nrealms-button-remover;10199;realmsfix,realmsbuttonremover;Realms Button Remover;;\nmanyideas-doors;10200;manyideas_doors;ManyIdeas Doors;;\nend-origins;10201;end_origins;End Origins;;\nextra-classes-origins;10202;extra_classes_origins;Extra Classes - Origins;;\nwire-decoration;10203;tower;电线装饰;Wire Decoration;\noptimizationsandtweaks;10204;optimizationsandtweaks;OptimizationsAndTweaks;;\nthe-flesh-that-hates;10205;the_flesh_that_hates;The Flesh That Hates;;TFTH\ntabfaces;10206;tabfaces;TabFaces;;\nmob-armor-mod-v2;10207;mobarmormod;Mob Armor Mod V3;;\numbral-skies;10208;umbral_skies;Umbral Skies;;\nrecipes-library;10209;recipes_lib;Recipes Library;;\ndimension-access-manager;10210;dimension_access_manager;Dimension Access Manager;;\nwitherborn-origin;10211;witherborn_origins;Witherborn Origins;;\norigins-gravity-enforcer;10212;gravity_enforcer;Origins - Gravity Enforcer;;\nsnuffles-fabric;10213;snuffles;魄罗Fabric版;Snuffles (Fabric);\nmanyideas-halloween;10214;manyideas_halloween;ManyIdeas Halloween;;\nselectable-painting;10215;selectable_painting;Selectable Painting;;\nboat-break-fix;10216;boatbreakfix;Boat Break Fix;;\nadvanced-movement;10217;advanced_movement;Advanced Movement;;\ntotem-overhaul;10218;totemoverhaul;Totem Overhaul / Alm's Extra Totems;;\nmobs-no-render;10219;mobnorender;Mobs No Render;;\ncovenants-choose-your-purpose;10221;alms_covenants;Covenants: Choose your purpose;;\ninsatiable;10222;insatiable;Insatiable;;\nodd-organisms;10223;oddorganisms;Odd Organisms;;\nrotten-eggs;10224;rotteneggs;Rotten Eggs;;\ncc-rt;10225;ccrt;CC: RT;;\nmanyideas-core;10226;manyideas_core;ManyIdeas Core;;\nmulti-builder-tool;10227;mbtool;多方块构建工具;Multi Builder Tool;\nender-storage-1-12-continuation;10228;;末影存储：延续版;Ender Storage continuation;\nsnail-mail;10229;snailmail;Snail Mail;;\nancient-rituals;10230;;Ancient Rituals;;\ncolorful-anvils;10231;colorfulanvils;Colorful Anvils;;\necofriendly;10232;ecofriendly;Ecofriendly;;\ntorchkey;10233;torchkey;Torchkey;;\ntorch-key-forge;10234;torchkey;Torch Key [Forge];;\njust-rose-gold;10235;justrosegold;Just Rose Gold;;\nrevolt-of-the-mobs;10236;rotm;Revolt of the Mobs;;\n;10237;sqrt;SQRT;;\nimproved-vanilla;10238;improvedvanilla;Improved Vanilla;;\nupgraded-netherite-aetherite;10239;upgradednetherite_aetherite;Upgraded Netherite : Aetherite;;\nbetterwithpatches;10240;betterwithpatches;Better With Patches;;\n;10241;biome_advancements;更多生物群系进度;Just Enough Biome Advancements;JEBA\npickable-orbs;10242;pickableorbs;Pickable Health Orbs;;\nmobneutralizer;10243;mobneutralizer;MobNeutralizer;;\n;10244;portal_block_item;传送门方块物品;;PBI\n;10245;sittable;Sittable;;\n;10246;echoes_of_the_past;Echoes of the Past;;\ncloud-rider;10247;cloudrider;Cloud Rider;;\n;10248;assembly;AssemblyMod;;AM\ntreasure2-bones;10249;treasure2bones;Treasure2: Bones;;\nender-pearl-swap;10250;enderpearlswap;Ender Pearl Swap;;\njer-ore-integration;10251;jeroreintegration;JER Ore Integration;;\nadorkable-dogs-pocky;10252;adorkabledogs;DK狗的Pocky;Adorkable dog's Pocky;\n;10253;rip;配方藏在代码里！;Recipe In Programming;RIP\nrealistic-explosion-physics;10254;rep;Realistic Explosion Physics;;\nnitwitification;10255;nitwitification;Nitwitification;;\nmutated-items;10256;items_to_mobs,mutated_items;Mutated Items / Items to Mobs;;\n;10257;;烤矿;Bake that ORE!;BtO!\nhomespun;10258;homespun;Homespun;;\nprimitive-start;10259;primitivestart;Primitive Start;;\nthrowing-dynamites;10260;dynamite;Throwing Dynamites;;\npaladins-and-priests;10261;paladins;Paladins & Priests (RPG Series);;\ncataclysm-heaven-burner;10262;incinerator;灾变：焚天神兵;Cataclysm: Heaven Burner;\necho-chest;10263;echochest;回响箱子;Echo Chest;EC\n;10264;healthnanfix;假死修复：重织版;Health NaN Fix:Refabricated;HNFR\nnether-chested;10265;netherchest,netherchested;下界箱子;Nether Chested;NC\nplenty-plates;10266;plentyplates;Plenty Plates;;PP\nsimple-sponge;10267;simplesponge;Simple Sponge;;\nnew-paper-doll;10268;paperdoll;Paper Doll;;PD\nender-zoology;10269;enderzoology;末影动物学;Ender Zoology;EZ\nvectorientation;10270;vectorientation;Vectorientation;;\nseven-days-to-fix-parasitus;10271;thesdtmfix;[7 Days To Fix];;\ndtm-integrations;10272;dtmintegrations;DTM Integrations;;\n;10273;hdskins;HDSkins;;\nbushier-flowers;10274;bushierflowers;Bushier Flowers;;\ncreate-gravity;10275;ender_space;Create : Gravity / Ender Space;;\ncataclysm-boss-bar;10276;cataclysmbossbar;灾变Boss血条显示;Cataclysm Boss Bar;\nunusual-gifts;10277;unusual_gifts;Unusual Gifts;;\nbuilding-blocks-overhaul-forge;10278;building_blocks_overhaul;Building Blocks Overhaul;;\n;10279;craftguide;CraftGuide - BTW CE Fix;;\ntick-info;10280;tickinfo;tick info;;\nprimal-stage;10281;primalstage;Primal Stage;;\nspontaneous-replace;10282;spontaneous-replace,spontaneous_replace;自然更替;Spontaneous-Replace;SR\npixel-loader;10283;pixelloader;像素加载器2;pixel loader2;\nkubejs-ars-nouveau;10284;kubejsarsnouveau;KubeJS Ars Nouveau;;\ncataclysmweaponery;10285;cataclysm_weaponery;Cataclysm Weaponery;;\ntwilight-paintings;10286;twilight_paintings;Twilight Paintings;;\ntwilight-origins;10287;tfo;Twilight Origins;;\ncpmoscc;10288;cpmoscc;自定义玩家模型OSC兼容;Customizable Player Models OSC Compat;CPM-OSC\ntwilight-forest-portal-catalyst;10289;twilightforestportalcatalyst;暮色森林传送门催化石;Twilight Forest Portal Catalyst;\nbetter-fog;10290;betterfog;Better Fog;;BF\ntool-upgrades;10291;toolupgrades;工具升级;Tool Upgrades;\nvisiblebarriers;10292;visiblebarriers;Visible Barriers;;\naether-gravitation;10293;gravitation;天境之引;Aether Gravitation;\n;10294;sublanguage;首选语言;Sublanguage;\nnff-services;10295;nffservices;NFF Services;;\nbetter-potion-visuals;10296;betterpotionvisuals;Better Potion Visuals;;\nmodern-warfare-cubed;10297;mwc;现代战争方块化;Modern Warfare Cubed;MWC\nextraordinary-extra-totems;10298;extraordinary_extra_totems;Extraordinary Extra Totems;;\n;10299;squidfriends;SquidFriends;;\nbeachparty;10300;beachparty;沙滩派对;Let's Do Beachparty;\n;10301;sittablescriptable_crt;SittableScriptable;;\ns33r-more-food;10302;s33r_more_food;S33R More Food;;\nfroglight-backport;10303;froglights;Froglight Backport;;\n;10304;;Poggy's What's That?;;\n;10305;arrowjumpcrit;弓箭跳射暴击;ArrowJumpCritical;\nexquisito;10306;exquisito;末域奇馔;Exquisito;\nspecies;10307;species;Species;;\ncles-battleitems;10308;battleitems;战斗物品;Cle's BattleItems;BI\nunu-parts-pack;10309;unuparts;UNU Parts Pack;;\nspleaves;10310;spleaves;Spleaves;;\nmodernworldcreation;10311;modernworldcreation;Modern World Creation;;\nenchant-the-rainbow;10312;enchat_the_rainbow;Enchant the Rainbow;;\n;10313;muffinssongkran;Muffin's Songkran;;\nfixed-anvil-repair-cost;10314;fixedanvilrepaircost;Fixed Anvil Repair Cost;;\nsleep-tight;10315;sleep_tight;Sleep Tight;;\nrandom-crafting;10316;randomcrafting;随机合成表;Random Crafting;RC\nfallout-inspired-power-amor;10317;fallout_inspired_power_armor;Fallout Inspired Power Armor;;\ninfinitefluids;10318;infinitefluids;无限流体;InfiniteFluids;\nupd8r;10319;upd8r;整合包更新检查工具;Upd8r;\nold-guns-mod;10320;oldguns;Old Guns Mod;;\n;10321;vlzoomer;VeryLegacyZoomer;;VLZ\n;10322;fmum;FMUM 2.0;;FMUM\nvanilla-teleporters;10323;teleporter;Vanilla-Inspired Teleporters;;\naquatic-frontiers;10324;aquatic_frontiers;Aquatic Frontiers;;\nlands-of-icaria;10325;lands_of_icaria;Lands of Icaria;;\nwta;10326;wildtoolaccess;Wild Tool Access;;\n;10327;GTNN;GT: New Horizons No Nerf;;GTNN\npickable-villagers;10328;pickable_villagers;Pickable Villagers;;\npickable-piglins;10329;pickable_piglings;Pickable Piglins;;\nbook-of-blocks-fabric;10330;bob;MC Book of Blocks;;BoB\ncreate-blue-skies-compat;10331;create_blue_skies_compat;Create Blue Skies Compat;;\ndawncraft-mobs;10332;dawncraft_mobs;DawnCraft Mobs;;\ndawncraft-tweaks;10333;dawncraft,afptweaks;DawnCraft Tweaks;;\n;10334;exshapestairs;异形楼梯;ExShape Stairs;\nnetherracked;10335;netherrrack_recipe;Netherracked;;\nthe-toolkits;10336;the_toolkits;The Toolkits;;\nimmersive-structures-ii-nether-edition;10337;imst;Immersive Structures II: Nether edition;;\n;10338;hllib;Hanling Lib;;hllib\nmossier-deepslate;10339;mossierdeepslate;Mossier Deepslate;;\ncelestial;10340;celestial;Celestial;;\n;10341;diet;简易营养均衡;Simple-Diet;\nquest-giver;10342;quest_giver;Quest Giver;;\n;10343;hextweaks;咒法调整;HexTweaks;\ninsects-recrafted;10344;insects_recrafted;Insects: Recrafted;;\nbetter-boilers;10345;betterboilers;Better Boilers;;\naether-plus;10346;aetherplus;Aether Plus;;\ncrafttweaker-sixik-utils-aoa3-nevermine;10347;crafttweakerutilsnevermine;CraftTweaker Sixik Utils AOA3 (Nevermine);;\naether-expansion;10348;aetherexpansion;Aether Expansion;;\nbarrels-2012;10349;barrels_2012;Barrels 2012;;\n;10350;libzoomer;Libzoomer;;\ntfc-sample-drill;10351;tfcsampledrill;TFC Sample Drill;;\ntfc-tng-rosia;10352;rosia;TFC - Rosia;;\naether-additions;10353;aeadditions;Aether Additions;;\ncustom-portal-api-forge;10354;customportalapi;自定义传送门 API Forge 版;Custom Portal API [Forge];\njaopca-extras;10355;jaopcaextras;JAOPCA Extras;;\n;10356;scriptblock;脚本方块;With Script Blocks;\nenclosure;10357;enclosure;Enclosure;;\n;10358;pillarbinshu;牛顿满意柱;;\n;10359;sit_down;坐下伙计！;Sit Down Dude！;\nisleofberk;10360;isleofberk;博克岛;Isle of Berk;\npatpatpat;10361;patpatpat;PatPatPat;;\n;10362;;搬箱器;Box Mover;\nwildberries;10363;wildberries;Wild Berries;;\nir-chinese-train-pack;10364;;三道岭余晖;JianShe in SanDaoLing;SDL\nkubejs-powah;10365;kubejspowah;KubeJS Powah;;\n;10366;hexkinetics;咒法动力学;HexKinetics;\n;10367;digi_tsuuruzu;Digi-tsuuruzu;;DTr\nsand_box;10368;sandbox;Sand Box;;\nsublime;10369;sublime;Sublime;;\nspells-shields-x-tinkers-construct;10370;spells_and_shields_x_tconstruct;Spells & Shields: Magicians' Tinkering;;\n;10371;armorstandtweaks;盔甲架微调;Armor Stand Tweaks;AST\nmowzies-mobs-the-broken-blade;10372;broken_blade;断刃;Mowzie's Mobs: The Broken Blade;\nwater-source-ex;10373;watersourceex;水源EX;Water Source EX;\ntinkers-calibration-tic3;10374;tinkerscalibration;匠魂校准;Tinkers' Calibration;TC\nyamatomoveset-epicfight-addon;10375;yamatomoveset;Yamatomoveset Epicfight Addon;;\nengineers-delight;10376;tmted;Engineers Delight;;\ntinkers-disassembler;10377;tinkersdisassemble;Tinkers Disassembler;;\npiseks-tinkers-enchantments;10378;piseks_tinkers_enchantments;Pisek's Tinker's Enchantments;;\ntinkering-with-embers;10379;emberstic;Tinkering with Embers;;\nmob-despawn-timers;10380;despawntimers;Mob Despawn Timers;;\ntfc-towerheat;10381;tfctowerheat;TFC TowerHeat;;\ntfc-toohotaroundhere;10382;thah;TFC TooHotAroundHere;;\nflower-patch;10383;flowerpatch;Flower Patch;;\nquickcure;10384;quick_cure;Quick Cure;;\nspoiled;10385;spoiled;Spoiled;;\nentity-specific-damage-invulnerability;10386;entityspecificinvulnerability;Entity-specific Damage Invulnerability;;\nhungryanimals;10387;hungryanimals;Hungry Animals;;\nopolis-utilities;10388;opolisutilities;Opolis Utilities;;\nbetter-breeding;10389;better_breeding;Better Breeding;;\nfrostiful;10390;frostiful;霜冻;Frostiful;\ncamo-creepers;10391;camocreepers;Camo Creepers;;\ncoupons;10392;coupons;Coupons;;\nsimple-bracers;10393;simple_bracers;Simple Bracers;;\nbaby-delight;10394;babydelight;Baby Delight;;\nstrategic-enchanting;10395;strategic_enchanting;Strategic Enchanting;;\nforged-in-fire;10396;forgedinfire;Forged In Fire;;\nlumy-skin-patch;10397;LumySkinPatch;Lumy Skin Patch;;\n;10398;le0s_spoons;Le0s Spoons;;\nredstone-clock;10399;redstone_clock;Redstone Clock;;\nanti-mob-griefing;10400;antimobgriefing;Anti Mob Griefing;;\nnbtedit-reborn;10401;nbtedit;游戏内NBT编辑器重制版;In-game NBTEdit Reborn;IGNR\n;10402;nucleoplasm_json_edit;核质JSON修改;Nucleoplasm Json Edit;NJS\nthe-filler-update;10403;the_filler_update;The Filler Update;;\ncustom-machinery-create;10404;custommachinerycreate;Custom Machinery Create;;\n;10405;rightproperguiscale;Right Proper GUI Scale;;\narmor-trims;10406;armor_trims;Armor Trims Backport;;\nrevised-phantoms;10407;revised_phantoms;Revised Phantoms;;\nchoruslib;10408;chorus_lib;ChorusLib;;\nfrogs-can-eat-any-hostile-mob;10409;frogs_can_eat_any_hostile_mob;Frogs Can Eat Any Hostile Mob;;\nartificial-thunder;10410;artificial_thunder;Artificial Thunder;;\nrp-renames;10411;rprenames;RP Renames;;\nopenblocks-trophies;10412;openblocks_trophies,obtrophies;OpenBlocks Trophies;;\n;10413;shrugitoff;Shrug It Off;;\n;10414;f3teverywhere;Debug Keys (F3+T) Everywhere;;\n;10415;;九宫法令三;;\nlootbag-patches;10416;lootbags;LootBag Patches;;\nstargate-journey;10418;sgjourney;星门之旅;Stargate Journey;\nxaero-arrow-fix;10419;xaeroarrowfix;Xaero Arrow Fix;;\nornamental;10420;ornamental;Ornamental;;\nxaero-zoom-out;10421;xaerozoomout;Xaero Zoomout;;\nminecard;10422;minecard;Minecard;;\n;10423;;允许作弊-TOW版;EnableCheats-TOW Edition;\nydms-custom-camera-view;10424;customcameraview;YDM's Custom Camera View;;\ngravitational-modulating-additional-unit;10425;gravitationalmodulatingunittweaks,gmut;Gravitational Modulating Additional Unit;;\nedgerunner;10426;edgerunner;边缘行者;Edgerunner;\nnbt-autocomplete;10427;nbt_ac;NBT Autocomplete;;NBTac\nsmells-fishy;10428;smellsfishy;Smells Fishy / Entity Rain Events;;\nprimogem-craft;10429;primogemcraft;原石工艺;Primogems Craft;PGC\nbenched;10430;benched;Benched;;\nkubejs-tfc;10431;kubejs_tfc;KubeJS TFC;;\nprecision-prospecting;10432;precisionprospecting;Precision Prospecting;;\npotatoos-custom-structure-for-hbms-nuclear-tech;10433;potatooscustomstructureforhbm;Potatoo's Custom Structure For HBM's Nuclear Tech Mod;;\nfabric-seasons-terraformers-compat;10434;seasonsterraformerscompat;Fabric Seasons: Terraformers Compat;;\nfabric-seasons-extras;10435;seasonsextras;Fabric Seasons: 拓展;Fabric Seasons: Extra;\nbetter-f3-plus;10436;betterf3plus;Better F3 Plus;;\nfabric-seasons-delight-compat;10437;seasonsdelightcompat;Fabric Seasons: Delight Compat;;\nfabric-seasons-terralith-compat;10438;seasonsterralithcompat;Fabric Seasons: Terralith Compat;;\nmarigolds;10439;marigolds;Marigolds;;\nfabric-seasons-croptopia-compat;10440;seasonscroptopiacompat;Fabric Seasons: Croptopia Compat;;\nfabric-seasons-byg-compat;10441;seasonsbygcompat;Fabric Seasons: BYG Compat;;\nsassot;10442;sassot;Spears, Axes, Swords, Shields, And Other Tools;;SASSOT\neasyhopper;10443;easyhopper;轻松漏斗;Easy Hopper;\nprogressive-archery;10444;progressive_archery;Progressive Archery;;\nnonconflictkeys;10445;nckey;全键无冲;NonConflictKeys;\narmor-restitched;10446;armor_restitched;Armor Restitched;;\n;10447;pillager_queen;Pillager Queen;;\nbetter-sassot-combat-data-pack;10448;;Better SASSOT Combat Data Pack;;\nnmpr;10449;nopoisonregen;No More Poison with Regeneration;;NMPR\nsprinklerz;10450;sprinklerz;Sprinklerz;;\nour-villager-discounts;10451;our_villager_discounts,ourvillagerdiscounts;Our Villager Discounts;;\neasydropper;10452;easydropper;轻松投掷;Easy Dropper;\nvaried-adventure;10453;va;Varied Adventure;;VA\nkubejs-delight;10454;kubejsdelight;KubeJS Delight;;\nblock-physics-overhaul;10455;bpo;Block Physics Overhaul;;BPO\nsearchlight-forge;10456;searchlight;探照灯;Searchlight (& Wall Lights);\nancient-manuscripts;10457;ancient_manuscripts;远古手稿;Ancient Manuscripts;\ngazebos;10458;gazebos,gazebo;Gazebos (RPG Series);;\nscoreboard-get-time;10459;nowtime;记分板获取时间;scoreboard get time;\nmodern-keywizard;10460;mkw;现代化按键精灵;Modern KeyWizard;MKW\nchunk-sending-forge-fabric;10461;chunksending;Chunk Sending;;\n;10462;potatorunner;PotatoRunner;;\nlimitless-structure-blocks;10463;limitlessstructureblocks;Limitless Structure Blocks;;\n;10464;customcrosshairmod;自定义准心：彼梦版;Custom Crosshair Mod ArchiDreamZ Edition;\nadvanced-spawn-control;10465;advancedspawncontrol;Advanced Spawn Control;;\noverworld-mirror;10466;overworldmirror;Overworld Mirror;;\nno-sonic-boom-togglable;10467;goodbyeboom,goodbyeboomforge;No Sonic Boom (Togglable) / Toggleable Sonic Boom;;\nquicksort;10468;quicksort;Quicksort;;\nfake-player;10469;fakeplayers;Fake Players;;\n;10470;lootbeams;战利品光束Fabric版;LootBeamsFabric;\ncontinuumation;10471;continuumation;Continuumation;;\nai-art-deco-1;10472;aiblock1;AI装饰一号;AI Art Deco 1;AI1\n;10473;ocrenderfix_sodium;Sodium/Rubidium Occlusion Culling Fix;;\neasy-dungeons;10474;easy_dungeons;简易地牢;Easy Dungeons;\nthe-morbid-harvester-reborn-reborn;10475;morbid;变态的凋灵收割者;The Morbid Reborn Reborn;\nars-armiger;10476;ars_armiger;Ars Armiger;;\nbettergolem;10477;bettergolem;Better Golems;;\n;10478;end-poem-extension,end_poem_extension,endpoemext;终末之诗扩展补丁;End Poem Extension;EPX\ntrampledisabler-fabric;10479;trampledisablerfabric;TrampleDisabler;;\nspotting;10480;spotting;索敌;Spotting;\nadvanced-tfc-tech-unofficial;10481;advancedtfctech;进阶群峦科技非官方版;Advanced TFC Tech Unofficial;ATTU\npineapple-tags;10482;pineapple_tags;凤梨标签;Pineapple Tags;\nimpostore;10483;impostore;ImpostOre;;\neffishiency;10484;effishiency;Effishiency;;\nglumbis;10485;glumbis;Glumbis;;\nprehistoric-nature-fossils;10486;prehistoricnaturefossils;Prehistoric Nature Fossils;;\npeeled;10487;peeled;Peeled;;\nbutterflys;10488;butterflies;Butterflys;;\n;10489;forgedapi;ForgedAPI;;\n;10490;keep_someinventory_mr;Keep Some Inventory;;\nbe-style-wither;10491;bestylewither;BE Style Wither;;\ncampfire;10492;campfire;Campfire;;\nars-enderstorage;10493;ars_enderstorage;Ars EnderStorage;;\n;10494;ftbchecker;FTB Checker;;\nmythicmetals-decorations;10495;mythicmetals_decorations;Mythic Metals Decorations;;\nmetallurgychisel;10496;metallurgychisel;MetallurgyChisel;;\nsooty-chimneys-fabric;10497;sootychimneys;Sooty Chimneys for Fabric;;\narboria;10498;arboria;Arboria;;\nmekanism-evolution;10499;mekaevolution;Mekanism-Evolution;;\n;10500;arboria_saplings;Arboria Saplings;;\nchiseledadditions;10501;chisableadditions;ChiseledAdditions;;\n;10502;tinkers_ingenuity;工匠匠心;Tinkers Ingenuity;\ngregtech-4;10503;gregtech_addon;格雷科技4移植版;GregTech 4 Rewritten;GT4\nlacrimis;10504;lacrimis;Lacrimis;;\nproductive-villagers;10505;productivevillagers;Productive Villagers;;\ncanned-goods;10506;cannedgoods;Canned Goods :|;;\nitemchat-fabric;10507;item_chat;ItemChat;;\n;10508;villageinvader;Villages Conquer;;\ncrafttweaker-sixik-utils-ftb-quest;10509;crafttweakerutils;CraftTweaker Sixik Utils FTB Quest;;\nwares;10510;wares;Wares;;\ndifficult-spawners;10511;difficult_spawners;Difficult Spawners;;\nrecoil-shotgun;10512;shotgunmod;Recoil Shotgun;;\nstratum;10513;stratum;Stratum;;\nobserved;10514;observed;Observed;;\nex-sartagine-requiem;10515;exsartagine;Ex Sartagine Requiem;;\ngiant-pacman-1-16-5;10516;giantpacman;Giant Pacman;;\nitem-banning-fabric;10517;itemblacklist;Item Banning (Fabric);;\nerroring-entity-remover;10518;eer;Erroring Entity Remover;;\nmclo-gs;10519;mclogs;Mclogs;;\npink-expansion;10520;pinkexpansion;Pink Expansion;;\npolluted-earth-reborn;10521;polluted_earth;Polluted Earth Reborn;;\nkitsus-forgecraft;10522;forgecraft;Kitsu's ForgeCraft;;\nstat-check;10523;stat_check;Stat Check;;\nhit-indication;10524;hitindication;Hit Indication;;\nmultipart-machines-farming;10525;multipart_machines_farming;Multipart Machines: Farming;;\n;10526;;机械动力矿石粉碎修复;Create Ore Crushing Fix;\nlotweakr;10527;lotrfixer;LOTRFixer;;\ntru-first-person-mod;10528;TrueFirstPersonMod;First Person Realism;;\nreduced-music-delay;10529;rmd;Reduced Music Delay;;\nagers-giant-junkyard-dimension;10530;junkyard;Ager's Giant Junkyard Dimension;;\njsmacros;10531;jsmacros;JS Macros;;\ngreat-big-world;10532;great_big_world;Great Big World;;GBW\narmored-mobs;10533;armored_mobs;Armored Mobs;;\nastrological;10534;inversia,astrological;Astrological / Inversia;;\nend-expansion-the-lamented-islands;10535;the_lamented_islands;End Expansion - The Lamented Islands;;\n;10536;;万物皆可造;;\n;10537;;万物皆可钓;;\n;10538;washable;洗刷刷;Washable;\n;10539;compromise;折衷;Compromise;\nbrute-force-rendering-culling;10540;brute_force_rendering_culling;野蛮渲染剔除;Brute force Rendering Culling;BFRC\nno-additional-repair-cost;10541;noaddrepcost;No Additional Repair Cost - Anvil / NoAddRepCost;;\ncave-dust;10542;cavedust;洞穴尘埃;Cave Dust;\nnocubes-better-grindstone;10543;nocubesbettergrindstone;NoCube's Better Grindstone;;\ncreate-weaponry;10544;create_weaponry;Create Weaponry;;\nnocubes-chinchillas;10545;Chinchillas;NoCube's Chinchillas;;\ncreate-applied-kinetics;10546;createappliedkinetics;机械动力：应用机械;Create: Applied Kinetics;CAK\nnocubes-zombie-mobs;10547;zombiemobs;NoCube's Zombie Mobs;;\ncreate-gourmet;10548;create_gourmet,gourmet;Create Gourmet;;\nmedical;10549;medical;Medical;;\nyellowbrosss-extras;10550;yellowbrosss_extras;Yellowbross's Extras;;\nsimply-caterpillar;10551;simplycaterpillar;Simply Caterpillar;;\ncataclysm-delight;10552;cataclysm_delight;Cataclysm Delight;;\nold-combat-mod;10553;old_combat_mod;Old Combat Mod;;\npipe-connector;10554;pipe_connector;Pipe Connector;;\ndiamethyst-arrows;10555;diamethyst_arrows;Diamethyst Arrows;;\ncherry-blossom-grotto;10556;cherryblossomgrotto;Cherry Blossom Grotto;;\n;10557;clay_smithing_template;黏土锻造模板;Clay Smithing Template;cst\n;10558;splash;闪烁;Splash;\nparties;10559;sedparties;Parties;;\npots-and-mimics-rpg;10560;pots_and_mimics_rpg;Realm RPG: Pots & Mimics;;\ngemspark;10561;gamspark;Gemspark;;\notakomod;10562;otakomod_anime_characters;OtakoMod;;\nwall-jumping;10563;wall_jumping;Wall Jumping;;\nnew-life;10564;newlife;New Life;;\nreeves-furniture;10565;reevesfurniture,furnitury;Furnitury;;\n;10566;undying;不朽;Undying;\ncreatania;10567;creatania;Creatania;;\nboat-rocked;10568;boatrocket;Boat Rocket;;\nproficiency-honest-work;10569;proficiency;Proficiency：Honest Work;;\nwarrior-rage;10570;warriorrage;Warrior Rage;;\npokefood;10572;pokefood;PokeFood;;\narmor-souls-reforged;10573;armor_souls_reforged;Armor Souls: Reforged;;\naether-redux;10575;aether_redux;天境：新生代;The Aether: Redux;\n;10576;;Taiyitist;;\nstaaaaaaaaaaaack;10577;staaaaaaaaaaaack;staaaaaaaaaaaack;;Stxck\nthanks-i-have-rei;10578;tihr;Thanks I Have REI;;TIHR\n;10579;bucketnerf;BucketNerf;;\nsatchels;10580;satchels;挎包;Satchels;\nmatex;10581;matex;Materials Expanded;;\nweapons-of-the-apocalypse;10582;;Weapons of the Apocalypse;;\ntis-advanced;10583;tisadvanced;TIS Advanced;;\nlove-me-love-my-wolf;10584;love_wolf;爱屋及呜;Love me love my wolf;\ntoroquest-revamped;10585;toroquest;ToroQuest Revamped;;\n;10586;bosomlang;BosomLang;;\ntis-create;10587;tiscreate;TIS-Create;;\ntektopia-trader;10588;tektopiatrader;Tektopia Trader;;\nworldtime;10589;worldtime;WorldTime;;\nbeasts;10590;beasts;Beasts;;\nalphabet-speedrun;10591;alphabet_speedrun,speedabc;自定义速通挑战;αβspeedrun;ABS\n;10592;symlink-check,symlinkcheck;符号链接拦截;Symlink Check;\ntis-stringify;10593;tisstring;TIS Stringify;;\n;10594;silkfalling;精准着陆;Silk Falling;SF\ndismount-entity;10595;dismountentity;Dismount Entity;;\ndyeable-compasses;10596;dyeblecompasses,dyeablecompasses;染色指南针;Dyeable Compasses;\nsupercompression;10597;super_compression;超级压缩;Super Compression;SC\nbetter-hungteen-s-plants-vs-zombies;10598;bhtpvz;更好的 HTPVZ;Better HungTeen's Plants vs. Zombies;BHTPVZ\npiglinsafety;10599;piglinsafety;猪灵：多喝岩浆;PiglinSafety;\ngatewayer;10600;gatewayer;Gatewayer;;\nbocchud;10601;bocchud;孤独HUD;BoccHUD / MiniHUD-Forge;BHUD\narrowcollector;10602;arrowcollector;你返箭吗;Arrow Collector;\nsaplingslayer;10603;saplingslayer;鸽子衔枝;SaplingSlayer;\nquality-quails;10604;taigachicken;Quality Quails;;\nhydration;10605;hydration;Hydration;;\n;10606;MinF;矿食;Mineral Food;MinF\nhalohud;10607;halohud;Halo HUD;;\nresourcify;10608;resourcify;Resourcify;;\nioclias;10609;ioclias;Ioclias;;IOC\ntwilights-flavors-delight;10610;twilightdelight;暮色风味乐事;Twilight's Flavors & Delight;\nclean-f3;10611;clean-debug;Clean F3;;\nenchanted-lib;10612;enchantedlib;Enchanted Lib;;\narchitect-tools-converter-1-7-10;10613;arctools;建筑师工具 - 转换器;Architect Tools - Converter;\n;10614;stntr;StabilizeAndTransform;;\n;10615;librarian-trade-finder;Librarian Trade Finder;;\nserver-resourcepack-checker;10616;resourcepackchecker;Server Resourcepack Checker;;\nmain-menu-credits;10617;isxander-main-menu-credits;Main Menu Credits;;\nphoton;10618;photon;光子;Photon;\nvoice-chat-interaction;10619;vcinteraction;Voice Chat Interaction;;\nreplay-voice-chat;10620;replayvoicechat;Replay Voice Chat;;\nships-mod-unofficial;10621;ship;船：非官方版;Cuchaz Ships Unofficial;\nstorage-overhaul;10623;storage_overhaul;Storage Overhaul;;\nchorus-warps;10624;choruswarps;Chorus Warps;;\naureate-reliquary;10625;aureatereliquary;Aureate Reliquary;;\nsubmerged-explosions;10626;submergedexplosions;Submerged Explosions;;\nnetherite-items;10627;netherite_items;Netherite Items;;\naim-plus;10628;aim_plus;Aim Plus;;\nreckless-drilling;10629;reckless;Reckless Drilling;;\ntick-stasis;10630;tick-stasis;Tick Stasis;;\nazure-paxels;10631;azure-paxels;Azure Paxels;;\n;10632;smithing_template;懒人锻造模板;Lazy people forge templates;LPFT\nno-angled-brackets;10633;no_angled_brackets;No Angled Brackets;;\nalien-apocalypse;10634;aliensinvasion;Alien Apocalypse;;\nserver-utilities;10635;server_utils;Server Utilities;;\nsu-addon-silk-spawners;10636;suspawners;SU Addon - Silk Spawners;;\nsu-addon-quests;10637;suquests;SU Addon - Quests;;\nharder-hardcore;10638;harderhardcore;Harder Hardcore;;\nargentinasdelight;10639;argentinas_delight;阿根廷乐事;Argentina's Delight;\ncolonies;10640;colonies;Colonies;;\neverythingcopper;10641;everythingcopper;万物皆铜;Everything is Copper;\npassive-endermen;10642;passiveendermen;被动型末影人;Passive Endermen;\n;10643;multiofflinefix;离线多人修正;MultiOfflineFix;MOF\nelemental-aspects;10644;elemental_aspects;Elemental Aspects!;;EA\ndune;10645;dune;Dune;;\nlithereal;10646;lithereal;Lithereal;;\nmobs-genus;10647;mobsgenus;Mobs Genus!;;\nore-extension;10648;oresextension;Ore Extension!;;\nextradelight;10649;extradelight;额外乐事;ExtraDelight;\nshady-alchemist;10650;shady_alchemist;Shady Alchemist;;\nender-knight;10651;enderknight;Ender Knight!;;\nbubbleland;10652;bubbleland;Bubbleland;;\ncake-cow;10653;cake_cow;Cake Cow;;\n;10654;enable-multiplayer;解锁多人游戏按钮;EnableMultiplayer;EM\nshrimps;10655;shrimps;Shrimps!;;\nprojecte-aether-addon;10656;peaether;ProjectE Aether Addon;;\ncoal-additions;10657;coal_additions;Coal Additions;;\nfungi-stew;10658;fungi_stew;Fungi Stew;;\ngrenade-launcher;10659;grenade_launcher;Grenade Launcher;;\nillness;10660;illness;illness!;;\nstructural-statues;10661;structural_statues;Structural Statues;;\n;10662;hltgo;单格水域垂钓 / 渔域单泽;Hook Let The Goods Out;\nwild-bushes;10663;wild_bushes;Wild Bushes;;\nmutant-wolf;10664;mutant_wolf;Mutant Wolf;;\ncurses-naturals;10665;curses_naturals;Curses' Naturals;;\n;10666;offline-multiplayer;开启离线模式多人游戏;Offline Multiplayer;OMP\nstraighten-up;10667;straightenup;Straighten Up;;\ntwilight-catalist;10668;twilightcatalist;暮色催化石;Twilight Catalist;\nextra-moas;10669;moa_dlc;Aether: Extra Moas;;\ncorruptional;10670;corruptional;Corruptional;;\nenablemultiplayermode;10671;enablemp;EnableMultiPlayerMode;;\nwizardy-delight;10672;bushesandberries,wizardry_delight;Wizardy delight;;\n;10673;ecm;滨蜀的匠魂工具附加;;\n;10674;memoryusagebar;内存使用率条;MemoryUsageBar;MUB\ntinyallies;10675;tinyallie;Tiny Allies;;\nall-da-nuggets;10676;all_da_nuggets;All Da Nuggets;;\nrandom-recipes;10677;random_recipes;Random Recipes;;\nstarks;10678;starks;史塔克一家;Starks;\neasy-paper;10679;easy_paper;Easy Paper;;\nmodern-ruins-pack;10680;AS_Ruins;Modern Ruins Pack;;\ntetras-delight;10681;tetrasdelight;Tetra's Delight;;\n;10682;kekkai;结界;Kekkai;\navaritia-reset-version;10683;;无尽贪婪重置版;Avaritia Reset Version;\narcheology-plus;10684;archeologyplus;Archeology Plus;;\nmobification;10685;mobificationtwo;Mobification;;\ntrimseffects;10686;trimseffects,trimeffects;TrimsEffects;;\ntough-glass;10687;toughglass;Tough Glass;;\ncelestisynth;10688;celestisynth;天神剑技;Celestisynth;\ngolems-plus-plus;10689;golemsplusplus;Golems++;;\nmore-axolotls;10690;more-axolotl;More Axolotl;;\nperfect-accuracy;10691;perfectaccuracy;Perfect Accuracy;;\n;10692;perfectaccuracy;Perfect Accuracy Forge;;\n;10693;swingthrough;SwingThrough;;\nlog-begone;10694;logbegone;Log Begone;;\n;10695;transferable_pets;Transferable Pets;;\n;10696;boneblocks;Bone Blocks;;\nwoodster;10697;woodster;Woodster;;\ndeaths-mark;10698;deaths_mark;Death's Mark;;\nsweet-boreal;10699;sweet_boreal;Sweet Boreal;;\nfuzzymobs;10700;fuzzymobs;FUZZYMOBS;;\n;10701;beslandia;Beslandia;;BES\n;10702;arsmagica2;魔法艺术2.5;Ars Magica 2.5;\ndelightful-froge;10703;delightful-froge;Delightful Froge;;\nenhancedsnowman;10704;enhanced_snowman;Enhanced Snowmen;;\nnew-world-height-and-depth;10705;swl_fabric,swl;Shattered World Limits;;\n;10706;inertiaanticheat;Inertia Anti Cheat;;\n;10707;tprequest;传送请求;TeleportationRequest;\nscroll-of-harvests;10708;harvests;Scroll of Harvests;;\n;10709;scripts-chunk-loaders;Script's Chunk Loaders;;\nclean-tooltips;10710;clean_tooltips;Clean Tooltips;;\nastral-lucky-blocks;10711;luckyBlockAstral;星际幸运方块;Astral Lucky Blocks;\nquick-save;10712;quticksave;快速存档&快速读档;QSave&QLoad;Qs&Ql\niron-bows-forge;10713;ironbows;Iron Bows;;\n;10714;eternalpotioneffects;永恒物品药水效果;Eternal Potion Effects;EPE\nfiltered-chests;10715;filteredchests;Filtered Chests;;\n;10716;pineapple_item_export;图片渲染导出凤梨版;Pineapple Item Export;PIE\n;10717;lobotomycorp;脑叶公司;Lobotomy Corp;LC\nantixray;10718;antixray;AntiXray;;\nspecial-strength;10719;specialstrength;Special Strength;;\narmorpoints;10720;armorpointspp;Armor Points ++;;\neasy-enchanting;10721;easyenchanting;轻松附魔;Easy Enchanting;\nelitia;10722;;Create: Elitia Addon;;\ncosmetic-wings;10723;cosmeticWings;Cosmetic Wings;;\ndeepspace;10724;deepspace;深空;DeepSpace;DS\ndeath-backup;10725;deathbackup;死亡备份;Death Backup;\nbear-with-me;10726;bearwithme;Bear With Me;;\ncreate-diesel-generators;10727;createdieselgenerators;机械动力：柴油动力;Create: Diesel Generators;\nvintagefix;10728;vintagefix;VintageFix;;\nnostalgic-tweaks;10730;nostalgic_tweaks;Nostalgic Tweaks;;N.T\nsuspicious-sand-maker;10731;suspiciouser;Suspicious Sand Maker;;\ncreate-cobblemon-industrialized;10732;cobblemon_industrialized;Create: Cobblemon Industrialized;;CCI\ncreate-pixelfactory;10733;createpixelmon;Create: Pixelfactory;;\nies-tweaks;10734;ietweaks;IE's Tweaks;;\nhaunt-furnace;10735;haunt_furnace;Haunt Furnace;;\nplayer-ladder;10736;playerladder;Player Ladder;;\norb-of-origin-plus;10737;orboforiginplus;Orb of Origin Plus;;\n;10738;hashs_falcons_mr;Hashs's Falcons;;\nfirework-minecart-mod;10739;fireworkminecart;Firework Boosted Minecarts;;\nsoots-sandwichcraft;10740;breadcraft;Soot's Sandwichcraft;;\nrpg-origins;10741;rpg_origins;RPG Origins;;\n;10742;zombie_lag_fix;Zombie Lag Fix;;\nclassic-minecraft-icon;10743;classic_minecraft_icon;Classic Minecraft Icon;;\n;10744;tboi_suzu;The Binding Of Isaac | Useful Items;;TBOI_UI\n;10745;trivia;Trivia;;\nsome-useful-ores;10746;some_useful_ores;Some Useful Ores;;\n;10747;stardew_valley;Stardew Valley;;\nrainy-harvest;10748;rainy-harvest;Rainy Harvest;;\nmythic-mobs;10749;mythic_mobs;Mobs of Mythology;;\nlost-features;10750;lostfeatures;Lost Features;;\nbagus-lib;10751;bagus_lib;Bagus Lib;;\n;10752;noprivateorprotected;我讨厌权限修饰符;NoPrivateOrProtected;NPOP\n;10753;better_doors;Better Doors;;\nserver-pack-unlocker-forge;10754;spu;Server Resource Pack Unlocker;;\nx-enchant;10755;x-enchant;X-Enchantment;;\n;10756;alarideable;能骑就是好马;AsLongAsRideable;\n;10757;blahajremake;Blåhaj - Remake;;\npamps-simple-edition;10758;pampssimpleeditions;Pamps Simple Editions;;\n;10759;babyzombie_drops;Babyzombie Drops;;\n;10760;visualprospecting;可视化勘探;VisualProspecting;\nbetter-chiseled-bookshelves;10761;gud_betterbookshelves;更好的雕纹书架;Better Chiseled Bookshelves;\n;10762;shorkmod;Shorks;;\n;10763;missing_vanilla_recipes;Missing Vanilla Recipes;;\nalcocraft;10764;alcocraftplus;AlcoCraft+;;\nsilences-defense-tower;10765;silence_s_defense_tower;Silence's Turrets;;\n;10766;hexscroll;咒法学导出卷轴;Hex Scroll;HS\nmowlib;10767;mowlib;MowLib;;\n;10768;random_sleeping_ratio;随机睡眠比例;Random sleeping ratio;RSR\nadvanced-backups;10769;advancedbackups;高级备份;Advanced Backups;\nnot-just-biomes;10770;njb;Not Just Biomes;;NJB\nagricultural-enhancements;10771;agriculturalenhancements;农业增强;Agricultural Enhancements;\n;10772;evolution_upgrading;进化：升级;evolution:upgradings;EU\n;10773;copperandcystalforfabric;铜与晶：Fabric;;CACFF\nlorrys-sword-breakers;10774;sword_breakers;罗毅的锏;Lorry's sword breakers;LSB\nramel;10775;ramel;Ramel;;\n;10776;assembly;Ansamblu;;\nchat-tools;10777;chattools;聊天工具箱;Chat Tools;\namys-rftl;10778;amys_rftl;Amy's RFTL;;\ncutepenguins;10779;;可爱的企鹅;Cute Penguins;\nhellions-sniffer;10780;snifferplus;Hellion's Sniffer+;;\n;10781;babyanimalsdropexperience;幼年生物掉落经验;Baby Animals Drop Experience;BADE\nhybrid-flowers;10782;hybrid_flowers;杂交花;Hybrid Flowers;\n;10783;;Redstone Crusaders - The World;;\n;10784;;Redstone Crusaders - Star Platinum;;\nmethane;10785;methane;Methane;;\nmushroom-villagers;10786;mushroom_travelers;Mushroom Villagers;;\n;10787;litematica-server-paster;Litematica Server Paster;;\ns33r-butterfly-paintings;10788;s33r_butterfly_paintings;S33R Butterfly Paintings;;\n;10789;more-statistics;More Statistics;;\nvillager-see-villager-do;10790;villager_see_villager_do;Villager See, Villager Do;;\nindustrybase;10791;industrybase;工业基石;IndustryBase;InB\nsilent-river-in-the-nether;10792;silentriverinthenether;炎液断蒸;Silent River In The Nether;\nramel-reforged;10793;ramel;Ramel Reforged;;\nsulfar-mod;10794;sulfar_mod;Sulfar Mod;;\napple-cows;10795;apple_cows;Apple Cows;;\ngardening-tools;10796;gardentools;Gardening Tools;;\nnether-ores-plus;10797;netheroresplus;Nether Ores Plus+;;\nriver-treasures;10798;river_treasures;River Treasures;;\nlumbers-axe;10799;lumber_axe;Lumber's Axe;;\n;10800;team_reborn_energy;Energy;;\nimmunity-enchantments;10801;immunity_enchantments;Immunity Enchantments;;\nbetter-fishing-rods;10802;better_fishing_rods;Better Fishing Rods;;\nbook-fishing;10803;book_fishing;Book Fishing;;\nmore-villager-trades;10804;more_villager_trades;More Villager Trades;;\ncustom-world-icons;10805;customworldicons;Custom World Icons;;\ntrims-begone;10806;trimsbegone;Trims Begone!;;\nhaste-enchantment;10807;hasteenchantment;Haste Enchantment;;\nworkshop-for-handsome-adventurer;10808;workshop_for_handsome_adventurer;Workshop for handsome adventurer;;\ndesert-upgrade;10809;desert_upgrade;Desert Upgrade;;\nocean-recovery;10810;ocean_recovery;Ocean Recovery;;\nmore-armor-trims;10811;more_armor_trims;更多盔甲纹饰;More Armor Trims;\nall-the-trims;10812;allthetrims;All The Trims;;\n;10813;signeditgui;告示牌编辑器;Bamboo Sign Editor;\nrevival-orb;10814;revivalorb;Revival Orb;;\nstackable-trims;10815;stackabletrims;Stackable Trims;;\nkeepcuriosinventory;10816;KeepCuriosInventory,keepcuriosinventory;饰品栏优化;KeepCuriosInventory;\nmegachicken;10817;megachicken;MegaChicken;;\ns33r-iris-azalea-mod;10818;seer_iris_azaleas;S33R Iris Azalea;;\ndifficultyplus;10819;difficultyplus;Difficulty+;;\nblack-lung;10820;the_black_lung;Black Lung;;\nfriends-and-foes-beekeeper-hut-forge;10821;beekeeperhut;Friends&Foes - Beekeeper Hut;;\nfriends-and-foes-flowery-mooblooms-forge;10822;flowerymooblooms;Friends&Foes - Flowery Mooblooms;;\nrandom-looting;10823;randomlooting;随机战利品表;Random Looting;RL\nkubejs-botany-pots;10824;kubejsbotanypots;KubeJS Botany Pots;;\nirons-rpg-tweaks;10825;irons_rpg_tweaks;Iron's Rpg Tweaks;;\n;10826;enchantips;Enchantips;;\ntokenable-decoration;10827;tokenabledecoration;Tokenable Decoration;;\nemogg;10828;emogg;emogg;;\nfestival-delicacies;10829;festival_delicacies;节日佳肴;Festival Delicacies;\nelytra-trims;10830;elytra_trims,elytratrims;Elytra Trims;;\nbenefits-of-reading;10831;benefitsofreading;雕纹书架注魔/阅页读著;Benefits of Reading;\ngothic-rpg;10832;gothic;Gothic RPG;;\narcadian-dream;10833;arcadiandream;幻想之梦;Arcadian Dream;AD\n;10834;removefancyoak;移除珍异橡树/削碧栽直;Remove Fancy Oak;\ndungeon-and-taverns;10835;mr_dungeons_andtaverns;地下城与酒馆;Dungeons and Taverns;DNT\nelytra-trims-extensions;10836;elytra-trims-extensions;Elytra Trims Extensions;;\nfallingblocks;10837;fallingblocks;FallingBlocks;;\nfabric-musica;10838;musica;Fabric Musica;;\ngrowmeal;10839;growmeal;Growmeal;;\nmoa-decor-art;10840;moa_decor_art,moa_art;MOA DECOR: ART;;\n;10841;;Flower Adornments for Armor;;\n;10842;unoriginal_mr;Unoriginal;;\neasier-armor-trim-duplication;10843;easier_armor_trim_duplication;Easier Armor Trim Duplication;;\nsteppy;10844;steppy;Steppy;;\nnew-thin-air;10845;thinair;稀薄的空气;Thin Air;TA\nthe-armory;10846;armory;BBM's Armory;;\nxannoszs-better-minecarts;10847;betterminecarts;Xannosz的更好的矿车;Xannosz's Better Minecarts;\nmeliusvanish;10848;melius-vanish;Melius Vanish / Vanish;;\ntooltip-tool-tips;10849;tooltiptooltips;Tooltip Tool Tips;;\nsanity-descent-into-madness;10850;sanitydim;Sanity: Descent Into Madness;;\nno-darkness-effect-mod;10851;removewardeneffect;无黑暗效果;No darkness effect;\n;10852;seamless_loading_screen_renewed;Seamless Loading Screen: Renewed Mod;;\narmorskin;10853;armorskin;ArmorSkin;;\n;10854;translucent-glass;Translucent Glass;;\nsimple-health-bar;10855;simplehealthbar,simple-health-bar;Simple Health Bar;;\nplenty-of-golems;10856;plenty_of_golems;Plenty of Golems;;\n;10857;hit_particles;HitParticles;;\n;10858;durabilityviewer;耐久指示器更新版;Durability Viewer Updated;\npuffish-skills;10859;puffish_skills;Pufferfish's Skills;;\nbetter-archeology;10860;better_archeology,betterarcheology;更好的考古学;Better Archeology;\nfake-people;10861;fakepeople;假人;Fake People;\napoca-mobs;10862;apocamobs;Apoca-Mobs;;\n;10863;mr_piseks_cheaptemplates;Cheap Templates;;\n;10864;snowysniffer;Snowy Sniffer;;\nresponsive-shields;10865;responsive-shields,responsiveshields;Responsive Shields;;\nbeehave;10866;beehave;蜜蜂信息显示;Beehave;\n;10867;;生命值显示;Hit Point;\nsign-clipboard;10868;sign_clipboard;Sign Clipboard;;\nnocubes-better-frogs;10869;betterfrogs,nocubes_better_frogs;NoCube's Better Frogs;;\nessential-permissions;10870;vanilla-permissions;Vanilla Permissions / Essential Permissions;;\ncreate-dreams-desires;10871;flavored,create_dd;机械动力：梦想与欲望;Create: Dreams & Desires;\ntrails-and-tales;10872;trailsandtalesplus;Trails and Tales +;;\n;10873;relatively-brittle-deepslate;易碎深板岩;Brittle Deepslate;\nstackable127;10874;stackableforge,stackable;Stackable127;;\n;10875;;警笛头;Siren Head Addon;\n;10876;customsus;Custom Archeology;;\nwanderer-profession;10877;wanderer_profession;Wanderer Profession;;\n;10878;hm_ravens;HM Ravens;;\ncataclysmic-combat;10879;cataclysmiccombat;Cataclysmic Combat;;\npersona;10880;persona;Persona​;;\n;10881;;WASD Libraries;;\ncreate-air-forge;10882;createair;Create Air;;\n;10883;gravity_pads;Gravity Pads;;\n;10884;mr_endermite_expansion;Endermite Expansion;;\n;10885;mr_cherry_villages;Cherry Grove Villages;;\ninhibited;10886;inhibited;Inhibited;;\nchina-railway-facilities;10887;crf;中国铁路设施;;CRF\nsekc-physics;10888;sekcphysics;SekC Physics;;\n;10889;artificial_lightning;Artificial Lightning Mod;;\ndo-api;10890;doapi,terraform;Let's Do API;;\n;10891;pythonmc;PythonMC;;\n;10892;tp-chat-cmd;TP 聊天命令;TP Chat Command;/tpchat\ngrabby-mobs;10893;grabbymobs;Grabby Mobs;;\npandas-falling-trees;10894;fallingtrees;Panda's Falling Trees;;\niron-ender-chests;10895;iron_ender_chests;Iron Ender Chests;;\ndelightful-creators-forge;10896;delightfulcreators;动力乐事;Delightful Creators;\ndeathbutthree;10897;deathbutthree;逝不过三;Death But Three;\n;10898;fnrt;王抹布の火影忍者模组;FnrtMod;Fnrt\ncomparties;10899;comparties;组队兼容;Comparties;\ndusty-decorations;10900;dustydecorations;Dusty Decorations;;\nsimple-double-jump;10901;derecs_double_jump;Simple Double Jump;;\nsimple-ore-generation;10902;simpleoregen;简单的矿石生成;Simple Ore Generation;\nsave-the-monument;10903;savethemonument;Save the Monument;;\nsnifferiety;10904;snifferiety;Snifferiety;;\nlight-the-night;10905;ltn;Light The Night;;\ntweakerge;10906;tweakeroo,tweakerge;Tweakerge;;\nterrafirmaships;10907;terrafirmaships;TerraFirmaShips;;\nbetter-ocelots;10908;betterocelots;Better Ocelots;;\nactually-useful-smithing-table;10909;actuallyusefulsmithingtable;Actually Useful Smithing Table;;\ndenyblocks;10910;denyblocks;DenyBlocks;;\nsculk-horn;10911;sculkhornid;Sculk Horn;;\nydms-red-panda;10912;ydms_redpanda;YDM's Red Panda;;\noverworld-piglins;10913;overworld_piglins;Overworld Piglins;;\nwaveapi;10914;waveapi;WaveAPI;;\nmodlist;10915;modlist;Modlist;;\npanorama-screens;10916;panorama_screens;Panorama Screens;;PS\ncampfire-spawn-and-tweaks;10917;campfire_spawn_and_tweaks;Campfire Spawn and Tweaks;;\nghostz;10918;ghostz;GhostZ;;\nbetter-totem-of-undying;10919;better_totem_of_undying;CERBON's Better Totem Of Undying;;\nmodonomicon;10920;modonomicon;模组秘典;Modonomicon;\nmoreyoyos;10921;moreyoyos;More Yoyos;;\nonetwenty-1-20-backport;10923;onetwenty;Trails&Tales Backport;;\nxtra-arrows;10924;xtraarrows;Xtra Arrows;;\nsecrets-of-forging-revelations-a-tetra-addon;10925;secrets_of_forging_revelations;Secrets of Forging: Revelations;;\ntrolldom;10926;trolldom;Trolldom;;\n;10927;made_in_china;中国制造;Made In China;\nsoundlimitextends;10928;soundlimitextends;SoundLimitExtends;;\nartemis-flower-pots;10929;flowerpots;Artemis' Flower Pots;;\nsculky-bits;10930;sculky_bits;Sculky Bits;;\nminecolonies-for-computercraft;10931;colony4cc;MineColonies for ComputerCraft;;\nflame-sweeping-potato-edition;10932;flamesweeping;烈焰横扫：土豆版;Flame Sweeping Potato Edition;\nskinned-carts;10933;skinnedcarts;Skinned Carts;;\nquick-effect;10934;quickeffect;Quick Effect;;\nblock-log;10935;blocklog;Block Log;;\n;10936;memorycleanermissnotoredict;青春内存清理不会梦到矿物词典学姐;Memory Clearer Miss-not Ore Dict;MCMOD\nequipmentbenediction;10937;equipmentbenediction;装备祝福;Equipment Benediction;\n;10938;gamemode;现代游戏模式选择器;Modern Gamemode Switcher;GMS\ndimension-localized-inventories;10939;dimensionlocalizedinventories;Dimension Localized Inventories;;\nskillcraft-modpack-tool;10940;skillcraft;Skillcraft;;\nmodern-inhibited;10941;moderninhibited;Modern Inhibited;;\nghostjump;10942;ghostjump;死者为大;GhostJump;\n;10943;notsoessential;Not So Essential;;\nworldsalads-opulence;10944;wsopulence;WorldSalad's: Opulence;;\n;10945;pvz;HungTeen的植物大战僵尸95版;HungTeen's Plants vs Zombies 95;HTPVZ95\nwyrms-of-nyrus;10946;wyrmsofnyrus;Wyrms of Nyrus;;WoN\npearfection;10947;pearfection;Pearfection;;\nenderchests;10948;enderchests;Ender Chests;;\ndragon-rider;10949;dragonrider;Dragon Riders;;\ncreateutilities;10950;create_utilities,createutilities;机械动力：实用物品;Create Utilities;\n;10951;cregtech;克瑞科技重制版 / 科雷格科技;CregTech Remake;CgT\ncreeperhost-presents-chickens;10952;chickens;CreeperHost Presents Chickens;;\ninet;10953;inet;INet;;\nsafefall;10954;safefall;你安星好啦;Safefall;\nmy-beloved-icon;10955;my-beloved-icon;My Beloved Icon;;\nmob-catcher-fabric;10956;mob_catcher;Mob Catcher;;\ntetra-enlarged;10957;tetraenlarged;Tetra: Enlarged;;\nfruit-delight;10958;fruitdelight;果品乐事;Fruit Delight;\n;10959;idletweaks;Idle Tweaks;;\ntagmod;10960;tagmod;TAGMOD;;\npassive-skill-tree;10961;skilltree;被动技能树;Passive Skill Tree;\nvillagertradefix;10962;villagerfix;VillagerTradeFix;;\n;10963;;盔甲纹饰效果;;\n;10964;ironfish_mr;Ironfish;;\nbullet-chat;10965;bchat;弹幕聊天;Bullet Chat;\nfishermans-trap;10966;fishermens_trap;Fisherman's Trap;;\nremovebats;10967;rain_g;禁止蝙蝠生成;RemoveBats;\nnabba;10968;nabba;Not (Just) Another Better Barrel Attempt;;NABBA\nenchantdemon;10969;enchantdemon;附恶魔;EnchantDemon;\n;10971;key_command;键盘命令;KeyCommand;KC\ncreate-alchemists-industry;10972;alchemists_industry;Create Alchemist's Industry;;\nctov-paladins-priests-compat;10973;mr_ctov_paladinsnpriestscompat;CTOV - Paladins & Priests compat;;\nctov-gazebo-compat;10974;mr_ctov_gazebocompat;CTOV - Gazebo compat;;\nstellarity;10975;mr_stellarity;Stellarity;;\nctov-paragliders-compat;10976;mr_ctov_paragliderscompat;CTOV - 滑翔伞兼容;CTOV - Paragliders compat;\nctov-all-bark-all-bites-compat;10977;mr_ctov_ababcompat;CTOV - All Bark, All Bites compat;;\n;10978;modernsound;Modern Sound;;\n;10979;polishedgui;Polished GUI;;\nall-bark-all-bite;10980;all_bark_all_bite;All Bark, All Bite;;\nlively-forests;10981;lively_forests;Lively Forests;;\n;10982;nz_mod;星光的逆战模组;Stars_NZ;NZ\ndynamic-fullbright;10983;dynamic-fullbright;Dynamic Fullbright;;\n;10984;modularwarfare;模块化武装：烨熠;ModularWarfare-Shining;MWFS\ndont-clear-chat-history;10985;dcch;Don't Clear Chat History;;\ndifficulty-locker;10986;difficultylocker;Difficulty Locker;;\nvirus-disease-mod;10987;virusdiseasespread;Virus Disease Mod;;VDM\nthis-boat-is-mine;10988;tbim;这是我的船！;This boat is MINE!;TBIM\npandas-extra-details;10989;extra_details;Panda's Extra Details;;\nebs-wildfire;10990;ebs_wildfire;EB's Wildfire;;\nctov-domestication-innovation-compat;10991;mr_ctov_domesticatedinnovationcompat;CTOV - Domesticated Innovation Compat;;\nctov-savage-and-ravage-compat;10992;mr_ctov_savageandravagecompat;CTOV - Savage and Ravage Compat;;\nbobby-forge;10993;;Bobby [Forge];;\nmatter-transporter;10994;mattertransporter;Matter Transporter;;\ntall-flower-pots;10995;tall-flower-pots;Tall Flower Pots;;\n;10996;with_rest;相约白日梦;WithRest;\ntoo-fast;10997;toofast;Too Fast;;\n;10998;frostrideable;宜驭冻足;FrostRideable;\nfast-paintings;10999;fastpaintings;Fast Paintings;;\npersonality-rp;11000;personality;Personality;;\nctov-vending-machine-compat;11001;mr_ctov_vendingmachinecompat;CTOV - Vending Machine compat;;\nctov-advanced-peripheral-compat;11002;mr_ctov_advancedperipheralcompat;CTOV - 高级外设兼容;CTOV - Advanced Peripheral Compat;\n;11003;fabric-yaml-configuration;Fabric Yaml Configuration;;\ncomfy-beds;11004;comfy-beds;Comfy Beds;;\ntwerk-crop-dusting-mod-fabric;11005;danceofgrowth;Twerk / Crop Dusting Mod [FABRIC];;\nextra-damage-enchantments;11006;extra-damage-enchantments;Extra Damage Enchantments;;ExtraDE\nlemon-core;11007;lemon_core;Lemon Core;;\nrightclickharvest;11008;rightclickharvest;RightClickHarvest;;\nplanters-forge;11009;planters;Planters;;\npassive-charms;11010;passivecharms;Passive Charms;;\nctov-immersive-engineering-compat;11011;mr_ctov_immersiveengineeringcompat;CTOV - 沉浸工程兼容;CTOV - Immersive Engineering Compat;\nctov-beautify-compat;11012;mr_ctov_beautifycompat;CTOV - 美化！兼容;CTOV - Beautify Compat;\nlinkart-updated;11013;linkart;Linkart (Fabric);;\njamlib;11014;jamlib;JamLib;;JL\nglobalization;11015;globalization;Globalization;;\nrenewable-spore-blossoms;11016;renewablesporeblossoms;Renewable Spore Blossoms;;\n;11017;paragraphs;Paragraphs;;\n;11018;roughly_enough_fang;稍微来点獠牙！;Roughly Enough Fang;REF\n;11019;scoreboard-helper;记分板助手;Scoreboard Helper;SbH\n;11020;siscu;Survivor's Elegy;;\nctov-villagers-plus-compat;11021;mr_ctov_villagerspluscompat;CTOV - Villagers Plus Compat;;\nctov-incubation-compat;11022;mr_ctov_incubationcompat;CTOV - Incubation Compat;;\nctov-wizards-compat;11023;mr_ctov_wizardscompat;CTOV - Wizards Compat;;\nctov-byg-compat;11024;mr_ctov_bygcompat;CTOV - BYG Compat;;\nctov-chefs-delight-compat;11025;mr_ctov_chefsdelightcompat;CTOV - Chef's delight Compat;;\nctov-croptopia-compat;11026;mr_ctov_croptopiacompat;CTOV - Croptopia compat;;\nctov-friends-and-foes-compat;11027;mr_ctov_friendsandfoescompat;CTOV - Friends & Foes Compat;;\nctov-bountiful-compat;11028;ctov_bountifulcompat_mr;CTOV - Bountiful compat;;\n;11029;randombirth;随机出生点;RandomBirth;RB\nmobs-info;11030;mobsinfo;生物信息;Mobs Info;\nngrok-api;11031;ngrok-api;Ngrok Api;;\n;11032;ctapi;寰缔科技API;CraftTech-API;CTAPI\n;11033;;水力魔法;HydroMagicalPower;WPM\n;11034;unsheathe_r;利刃出鞘：重铸;Sword Unsheathe Reforged;SUR\nrpgloot;11035;rpgloot;RPG Loot;;\n;11036;sus_saver;Sus Saver;;\n;11037;advancementsaddition;进度追加-糖块;Advancements addition candy;AAc\ndynamic-fps-reforged;11038;dynamicfps;Dynamic FPS Reforged;;\nutility-belt;11039;utilitybelt;Utility Belt;;\ncultivatecraft;11040;cropcraft;CultivateCraft;;\nrpgloot-patched;11041;rpgloot;RPGLoot Patched;;\nfluidlogged;11042;fluidlogged;Fluidlogged;;\ncreate-foundry;11043;createfoundry;Create: Foundry;;\ncobblemon-fight-or-flight;11044;fightorflight;Cobblemon - Fight or Flight;;\nender-magnet;11046;endermagnet;末影磁铁;Ender Magnet;\ndead-guys-hallucination-nightmare;11047;deadguyshallucinationnightmare;Dead Guy's Hallucination Nightmare;;\n;11048;;末影龙增强;;\nsimple-hot-air-balloons;11050;simple_hot_air_balloons;Simple Hot Air Balloons;;SHAB\ntoomuchrain;11051;TooMuchRain;TooMuchRain;;\nthirst-was-remade;11052;thirst;口渴-重制;Thirst Was Remade;\nies-tweaks-fabric;11053;ietweaksfabirc;IE's Tweaks (Fabric);;\nbrown-mooshrooms;11054;brownmooshrooms;Brown Mooshrooms;;\nwhetstones;11055;whetstones;Whetstones;;\n;11056;better_log4j_config;Better Log4j Config;;\n;11057;harvest;Harvest;;\nwooden-hoppers;11058;woodenhoppers;Wooden Hoppers;;\nskeleton-horse-spawn;11059;skeletonhorsespawn;Skeleton Horse Spawn;;\nconfiganytime;11060;configanytime;ConfigAnytime;;\nfaster-crouching;11061;fastercrouching;Faster Crouching;;\nyeetusexperimentus;11062;yeetusexperimentus;Yeetus Experimentus;;\nxbob;11063;xbob_modloader,xbob;准星摇晃;Crosshair Bobbing;\nzombie-horse-spawn;11064;zombiehorsespawn;Zombie Horse Spawn;;\ntinkers-katanas;11065;tinkers_katanas;匠魂武士刀;Tinkers' Katanas;\nfbp;11066;fbp;Fancier Block Particles;;FBP\nyeetem-potions;11067;yeetem_potions;YEETem Potions;;\nincapacitated;11068;incapacitated;Incapacitated;;\n;11069;fys;锻汝砧上;Forge Yourself;FYS\n;11070;mcopper;梧桐物语;MCopper;\nno-ranged-knockback;11071;resist;No Ranged KnockBack;;\nindustrial-agriculture;11072;industrialagriculture;Industrial Agriculture;;\nmacaws-bridges-sajevius;11073;mcwbridgessajevius;Macaw 的桥梁：Sajevius 附属;Macaw's Bridges - Sajevius;\n;11074;getwebbed;GetWebbed;;\n;11075;hwr;Water Resistance Potion;;\nthe-predators;11076;predators;The Predators;;\ncreate-structures;11077;create_structures;Create: Structures;;\nivycore;11078;ivycore;IvyCore;;\nmore-immersive-wires;11079;more_immersive_wires;More Immersive Wires;;\nventur-origin;11080;venturorigin;Ventur Origin;;\n;11081;exp_respawn;经验重生;ExpRespawn;\n;11082;sodium-blendingregistry;Sodium Blending Registry;;\nwaterphysics;11083;;WaterPhysics;;\nece-enhanced-celestial-enhancement;11084;ece;月亮事件扩展：第二月亮;ECE - Enhanced Celestial Enhancement: The Second Moon;\nplants-vs-zombies-cubed;11085;pvzmod;Plants vs. Zombies: Cubed;;\nmoa-decor;11086;moa_decor;MOA DECOR;;\nmoa-decor-toys;11087;moa_decor_toys,moa_toys;MOA DECOR: TOYS;;\nmoa-decor-cookery;11088;moa_decor_cookery,moa_cookery;MOA DECOR: COOKERY;;\nmoa-decor-science;11089;moa_decor_science,moa_science;MOA DECOR: SCIENCE;;\ncreate-guardian-beam-defense;11090;create_guardian_beam_defense,creategbd;Create Guardian Beam Defense;;\nantisocial;11091;antisocial;Antisocial;;\nexpanding-technologies;11092;expandingtechnologies;Expanding Technologies;;\ndespawning-eggs-hatch;11093;despawningeggshatch;Despawning Eggs Hatch;;\nmob-death-messages;11094;mobdeathmessages;Mob Death Messages;;\nchunkapi;11095;chunkapi;ChunkAPI;;\n;11096;betterwaystonesmenu;BetterWaystonesMenu;;\nbig-fish;11097;bigfish;Big Fish;;\nnulltal;11098;nulltal;Nulltal;;\ncaffeinefilled;11099;caffeinefilled;CaffeineFilled;;\npet-transfer-trade-your-pets;11100;pettransfer;Pet Pass: Trade Your Pets!;;\nlets-do-bakery-farm-charm-compat;11101;bakery;馥郁烘焙;Let's Do: Bakery;\nsearchables;11102;searchables;Searchables;;\ndixtas-armory;11103;dixtas_armory;Dixta's Armory;;\ningamestats;11104;ingamestats;InGameStats;;\nhanger-system-overhaul;11105;hanger_system_overhaul;Hunger System Overhaul;;\nisaidnosnow;11106;isaidnosnow;ISaidNoSnow;;\njarsauth;11107;jarsauth;客户端管理;JarsAuth;\nautoelytrapanic;11108;autoelytrapanic;AutoElytraPanic;;\nmore-crossbows-cj;11109;morecrossbows;More Crossbows CJ;;\nmultipart-machines-cooking;11110;mm_cooking;Multipart Machines: Cooking;;\n;11111;kamiwaza_program,kamiwaza_tesuto;超能程式;Kamiwaza Program;KWP\n;11112;quicktakeoff;快速起飞;Quick Takeoff;\nthepeebrain-dream-dimension;11113;dream_dimension;Dream Dimension;;\n;11114;chaosworld;混沌世界;Chaos World;\nochetgenyo;11115;ochetgenyo;Ochetgenyo;;\nqcraft-reimagined;11116;qcraft;量子物理重构;qCraft Reimagined;\n;11117;better_touch;更好的接触;Better Touch;\n;11118;todolist;待办事项;TODO List;\n;11119;arstarmorhud;Arst Armor Hud;;\nspice-of-life-onion;11120;solonion;生活调味料：洋葱版;Spice of Life Onion;\neffect-berries;11121;effectberriesmod;Effect Berries;;\ntinkersworld;11122;tinkersworld;Tinkers' World;;\n;11123;long_pickaxes;Ridiculously Long Pickaxes;;\n;11124;message_masking;消息屏蔽;message_masking;\nragdoll-corpses;11125;ragdollCorpses;布娃娃尸体;Ragdoll Corpses;\nfloatingruins;11126;FloatingRuins;浮空遗迹;FloatingRuins;\n;11127;nihonium;鉨;Nihonium;Nh\ncreeper-backguard;11128;creeperbackguard;Creeper Backguard;;\naudio-extension-for-fancymenu-forge;11129;fmextension_audio;Audio Extension for FancyMenu;;\nstay-warm-together;11130;staywarmtogether;Stay Warm Together;;\naeroblender;11131;aeroblender;AeroBlender;;\nancient-aether;11132;ancient_aether;Ancient Aether;;\nartisticcraft;11133;artisticraft;ArtisticCraft;;\nlavaboat_mod;11134;lavaboat;熔岩船;LavaBoat;\nsophisticated-wolves;11135;sophisticated_wolves;精致的狼;Sophisticated wolves;\nenderio-alloys;11136;enderioalloys;EnderIO Alloys;;\ndynamic-music;11137;dynmus;Dynamic Music;;\n;11138;item-hunt;item-hunt;;\ndynamo;11139;dynamo;Dynamo;;\ncreate-sabers;11140;createsabers,create_sabers;Create Sabers;;\nliveworldgen;11141;liveworldgen;LiveWorldgen;;\nsplit-shulker-boxes;11142;splitshulkers;Split Shulker Boxes;;\ntinkers-leveling;11143;tinkers_leveling;Tinkers Leveling;;\ncurios-compat;11144;curios_compat;Curios Compat;;\n;11145;cloudbackup;云备份;CloudBackup;\nserverpingerfixer;11146;serverpingerfixer;Server Pinger Fixer;;\n;11147;world-days;World Days;;\nsuch-a-delight;11148;suchadelight;Such A Delight;;\ncreate-compressed;11149;createcompressed,create_compressed;Create Compressed;;\nemoji-type;11150;emojitype;Emoji Type;;\nsoul-bound-enchantment;11151;soulbound;灵魂绑定;SoulBound;\ntower-defense-units;11152;;Tower Defense Units;;\nbarrier-block-mod;11153;barrierblock;屏障方块;Barrier Block Mod;\n;11154;healthorbs;SimpleHealthOrbs;;\nmisctweaks_;11155;misctweaks;MiscTweaks;;\ndynamic-music-updated;11156;dynmus;Dynamic Music Updated;;\nspartan-netherite;11157;spartannetherite;Spartan Netherite;;\nclimb-ladders-fast;11158;climbladdersfast;Climb Ladders Fast;;\ncustom-machinery-mekanism;11159;custommachinerymekanism;Custom Machinery Mekanism;;\n;11160;touhoutweaks;东方改进;Touhou Tweaks;THT\n;11161;long_swords;Long Swords;;\n;11162;;Dispenser Tweak;;\nnoraidtotems;11163;noraidtotems;NoRaidTotems;;\n;11164;snowballs_plus;SnowballsPlus;;\nbagus-mob;11165;bagus_mob;Bagus Mob;;\nchat-twix;11166;chattwix;Chat Twix;;\nshulker-meat;11167;shulkermeat;Shulker Meat;;\naccurate-block-placement-reborn;11168;accurateblockplacement;Accurate Block Placement Reborn;;\nfavorite-items;11169;favoriteitems;Favorite Items;;\n;11170;;纸灯笼;Paper Lanterns;\nsmart-villagers-follow-emeralds;11171;smart-villagers-follow-emeralds;见钱眼开;Smart Villagers Follow Emeralds;\n;11172;fps15;FPS++;;\n;11173;villager-pickup;Villager Pickup;;\nstep-it-up;11174;stepitup;Step It Up;;\nfantasy-trees;11175;fantasy_trees;奇幻树;Fantasy Trees;\n;11176;;AudioMod;;\nremodified;11177;modifiers;你再造！;Remodifier;\n;11178;;Death Chest;;\n;11179;mod_Shelf;Shelf;;\nbetter-fusion-reactor-for-mekanism;11180;bfr;Better Fusion Reactor for Mekanism;;\ngrowableores;11181;growableores;GrowableOres;;\ngolemsarefriends;11182;golemsarefriends;Golems Are Friends Not Fodder;;\n;11183;bigaxe;Bigaxe;;\n;11184;more_equipment_mod;更多的装备;MoreEquipmentMod;ME\nkeep-command-history;11185;keepcommandhistory;Keep Command History;;\n;11187;technomagic;TechnoMagic;;\ngeneratorgalore;11188;generator_galore;Generator Galore;;\nmap-shirts;11189;map_shirts;Map Shirts;;\nfireproof-boats;11190;fireproof_boats;Fireproof Boats;;\ndyenamicsandfriends;11191;dyenamicsandfriends;Dyenamics and Friends;;\n;11192;mod_Timber;Timber!;;\n;11193;;Spawnlist;;\nfusion-connected-textures;11194;fusion;Fusion (Connected Textures);;\nbluebox;11195;bluebox;BlueBox - Tardis Adventure;;\nhunger-reworked;11196;hunger_reworked;Hunger Reworked;;\narmor-status-hud-renewed;11197;armorstatushud;耐久信息显示：重生;Armor Status HUD Renewed;\nboblib;11198;boblib;BobLib;;\nplaceable-mobs;11199;placeable_mobs;Placeable Mobs;;\npotion-blender;11200;potion_blender;Potion Blender;;\nt6-auto-attack-mod;11201;autoattack;T6's Auto Attack Mod;;\nforceasciifont-backport;11202;forceasciifont;ForceASCIIFont-Backport;;\nvmtranslationupdate;11203;vmtranslationupdate;VM汉化更新;VM Translation Update;VMTU\ntrample-no-more;11204;tramplenomore;Trample No More;;\n;11205;new_chests;New Chests;;\njeremyseq-damage-indicator;11206;damageindicator;JeremySeq's Damage Indicator;;\n;11207;CheffCraft;CheffCraft;;\ndigs-dnd-origins;11208;digs_dnd_origins;Dig's DnD Origins;;\nsuperior-flat;11209;superior_flat;Superior Flat;;\nfungal-infection-spore;11210;spore;真菌感染:孢子;Fungal Infection:Spore;FIS\neasy-tweak;11211;easytweak;Easy Tweak;;\n;11212;;BitDepthFix;;\nnetherless-quartz;11213;;Netherless Quartz;;\n;11214;maple_api;Maple API;;\nreforged-fabric-api;11215;reforged_fabric_api;Reforged Fabric API;;\ncaffeinated;11216;caffeinated;Caffeinated;;\nbuildcraft-rf;11217;bcrf;BuildCraft RF: ReFluxified;;\ngilded-ingot;11218;gildedingot;Gilded Ingot;;\nchatpatches;11219;chatpatches;聊天补丁;Chat Patches;\nburning-furnace;11220;burning_furnace;Burning Furnace;;\nshadowizardlib;11221;shadowizardlib;ShadowizardLib;;\nants-unleashed;11222;ants_unleashed;Ants Unleashed;;\n;11223;seaborgium;Seaborgium;;\ngold-fish;11224;;Gold Fish;;\npipeplus;11225;pipeplus;PipePlus;;\n;11226;malilib;MaLiLib for Forge;;\n;11227;litematica;Litematica for Forge;;\ndeuf-refabricated;11228;deuf_refabricated;DEUF Refabricated;;\n;11229;moon_phase;Moon Phase Info-;;\nrunic-items;11230;runic_items;Runic Items;;\ntwist;11231;twist;诡变;Twist;\n;11232;serverlistbufferfixer;ServerlistBufferFixer;;\noil-refinery;11233;;Oil Refinery;;\nthemangonewadvent;11234;the_mango_new_advent,mango_mod;The Mango New Advent / Mango Legacy;;\n;11235;worthy_food;Worthy Food;;\n;11236;mod_Biosphere;Biosphere;;\nbetter-nether-continuation;11238;betternether;更好的下界延续;BetterNether Continuing;\nstygian-end-continuation;11239;stygian;末地：生物群系扩展延续;Stygian End Continuation;\nskill-slots;11240;skillslots;技能槽;Skill Slots;\nnovam-terram-continuation;11241;nt;Novam Terram Continuation;;\n;11242;jade_feet;玉足;Jade Feet;JF\n;11243;;物品阶段：死而复生;Item Stages Plus;\ncutting-edge;11244;cuttingedge;Cutting Edge;;\nxp-from-harvest-reworked;11245;xpfromharvest;XP From Harvest Reworked;;\npapi-project;11246;papi;Papi;;\neventhorizon;11247;eventhorizon;事件视界;EventHorizon;EH\nmob-options;11248;moboptions;Mob Options;;\nmod-sets;11249;mod-sets,mod_sets;模组集;Mod Sets;\n;11250;mr_upgraded_mobs;Upgraded Mobs;;\n;11251;;SCP 096;;\nhold-that-chunk;11252;holdthatchunk;Hold That Chunk;;\ndark-matter;11253;dark-matter;Dark Matter;;\n;11254;steal_dogs;偷狗;Steal Dogs;\nfaster-random;11255;fastrandom,faster_random;Faster Random;;\n;11256;tweakeroo;Tweakeroo for Forge;;\n;11257;minihud;MiniHUD for Forge;;\nsimply-steel-continued;11258;simplysteel;Simply Steel Continued;;\nsimple-corinthium;11259;simplecorinthium;Simple Corinthium;;\nchidori-origins-collection;11260;chidoriorigins;Chidori Origins Collection;;\nenhancedp2p;11261;enhancedp2p;增强的P2P;EnhancedP2P;\ncustom-de-upgrade-recipes;11262;defusioncraftingrecipechanges;自定义 DE 升级配方;Custom DE Upgrade Recipes;\nmode-switch;11263;mode_switch;Mode Switch;;\nbetter-clouds-forge;11264;betterclouds;Better Clouds (Forge);;\n;11265;betterclouds;Better Clouds;;\n;11266;ezmapdl;EasyMapDownload;;\n;11267;dynamic_difficulty;动态难度;Dynamic Difficulty;DD\nclient-crafting;11268;clientcrafting;Client Crafting;;\nanvil-repairing;11269;anvilrepairing;Anvil Repairing;;\nheadcrabs;11270;headcrabs;Headcrabs;;\nkubejs-industrial-foregoing;11271;;KubeJS Industrial Foregoing;;\ngensokyo-ontology;11272;gensokyoontology;幻想存有论;Gensokyo Ontology;GSKO\nrecipe-book-delight;11273;recipe-book-delight;配方书乐事;Recipe Book Delight;\nchest-hoppers;11274;chesthopper;箱子漏斗;Chest Hoppers;\n;11275;gravydelight;Gravy Delight;;\nubes-delight;11276;ubes-delight,ubesdelight;香芋乐事;Ube's Delight;UD\ntorcherino-unofficial;11277;torcherino;加速火把非官方版;Torcherino(Unofficial);\nmore-bows-and-arrows;11278;morebowsandarrows;More Bows and Arrows;;\nxp-shop;11279;xp_shop;XP shop;;\nacclaimed-origins;11280;acclaimed_origins;Acclaimed Origins;;\nreplanter;11281;replanter;Replanter;;\nzombie-infection-vaccine;11282;cure;Zombie Infection Vaccine;;\nsimple-achievements;11283;simpleachivement;Simple Achievements;;\nachievement-books;11284;achievementbooks;成就书;Achievement Books;\nvillager-enchanter;11285;villager_enchanter;村民附魔师;VillagerEnchanter;VE\ninvocore-utility-mod;11286;invocore;Invocore;;\nsaljus-quill;11287;saljus_quill;Salju's Quill;;\n;11288;Hikari-sanyousei;光之三妖精;Hikari no Sanyousei;HS\nderelict;11289;derelict;Derelict;;\n;11290;charged_charms;Charged Charms;;\ncustom-villagers;11291;customvillagers;自定义村民;Custom Villagers;\nturtlematic;11292;turtlematic;Turtlematic;;\nremove-base-origins;11293;remove_base_origins;Remove Base Origins;;\n;11294;cfarmersint;方块宝可梦乐事;Cobblemon Delights;\nredirector;11295;redirectionor,redirector;异引定址;Redirector;\nvillager-mantle-fix;11296;villagermantlefix;村民帽子修复;Villager Mantle Fix;\nrechiseled-create;11297;rechiseled_create,rechiseledcreate;Rechiseled: Create;;\nxaeroplus;11298;xaeroplus;Xaero的地图增强;XaeroPlus;XP\ncosmetic-nametags;11299;cosmetic_nametags,nametags;Cosmetic Nametags;;\nthirdlife-rts;11300;thirdlife_rts;Thirdlife RTS;;\nreal-peaceful-mode;11301;real_peaceful_mode;真正的和平模式;Real Peaceful Mode;RPM\nniftyblocks;11302;nifty;NiftyBlocks;;\nelfs-dark-dimension;11303;rk;Dimension Dark Florest;;\nxaeros-minimap-world-map-waystones-compability;11304;w2w2;Xaero's Minimap & World Map - Waystones Compability;;\njagms-kiwis;11305;jagms_kiwis;Jagm's Kiwis;;\n;11306;masaadditions;MasaAdditions;;\nimproved-trident;11307;ite;Improved Trident;;\nmystic-bows;11308;mystic_bows;Mystic Bows;;\nforgiving-world;11309;forgivingworld;Forgiving World;;\nskill-cloaks;11310;skillcloaks;Skillcloaks;;\nswordinthestone;11311;sword_in_the_stone;Sword in the Stone;;\nlazy-ae2-patch;11312;lazyae2patch;懒人AE2补丁;Lazy AE2 Patch;\n;11313;;一键砍树;Cut tree one click;\nyamato-gun-mod;11314;yamato_gun;Yamato Gun;;\n;11315;fzsd;FZ生存数据包;FZ Survival Datapack;FZSD\nars-mage-fight;11316;ars_mage_fight;Ars Mage Fight;;\n;11317;shieldexp;Shield Expansion Potato Edition;;\nepic-sanji;11318;efmpirates;Epic Sanji;;\nkubejs-entity-events-extension;11319;kubejs_entity_events_ext;KubeJS Entity Events Extension;;\nmc-dungeons-enchanting;11320;mc_dungeons_enchantments,mcd_enchantments;我的世界：地下城附魔;MC Dungeons Enchanting;MCDE\nrei-custom-command;11321;rei_custom_command;REI自定义命令;REI Custom Command;\ncopperative;11322;copperative;铜心协力;Copperative;\ngrowable-storage-cells;11323;growablecells;Growable Storage Cells;;\ngrowable-cells-2;11324;growablecells;Growable Storage Cells 2;;\ncinchcraft;11325;cinchcraft;Cinchcraft;;\nday-counter;11326;ags_day_counter;Day Counter;;\nmakecold;11327;ims;冷境;MakeCold;MC\n;11328;kazzmon.daycount;Kazzmon's DayCount;;\n;11329;kazzmon.extravanilla;Kazzmon's ExtraVanilla;;\nsculk-redstone-components;11330;sculk_redstone_components;Sculk Redstone Components;;\ntoggle-enchantments;11331;togenc;Toggle Enchantments;;\nunbreakable-enchantment-forge;11332;g_abun;Unbreakable Enchantment;;\nimmersive-portal-gun;11333;portalgun;沉浸式传送枪;Immersive Portal Gun;\ntac-craft-tactical-armor-pack;11334;pretaccraft;Tac Craft;;\n;11335;flexible_arms;灵巧双手;Flexible Arms;\nboat-tweaks;11336;boattweaks;船只调整;Boat Tweaks;\nnew-effect-descriptions;11337;effectdescriptions;Effect Insights;;ED\ndiagonal-windows;11338;diagonalwindows;Diagonal Windows;;DW\n;11339;lunar_eyes;Eyes In The Sky;;\nillager-invasion;11340;illagerinvasion;灾厄侵扰;Illager Invasion;IN\n;11341;littlemaidmodelloader;LittleMaid ModelLoader;;LMML\n;11342;;一键挖矿;One Click Dig Ore;\n;11343;touhoublessings;Touhou Origins: Blessings;;\n;11344;hard_game;较为困难的合成;HardGame;\naltorigingui;11345;altorigingui;Alternate Origin GUI;;\ndynamic-trees-pams-harvestcraft-2;11346;dtphc2;动态的树：潘马斯农场2 - 果树附属;Dynamic Trees Pam's Harvestcraft 2: Trees;\ndynamic-trees-hexerei;11347;dthexerei;动态的树：魔法巫师附属;Dynamic Trees - Hexerei;\nminium-stone;11348;minium_stone;Minium Stone;;MS\n;11349;;Kazzmon's Silk Touch Spawners;;\n;11350;fast-rtp;FastRTP;;\ntech-decorators;11351;tech_decorators;科技装饰商;Tech Decorators;TD\ndynamic-trees-ars-nouveau;11352;dtarsnouveau;动态的树：新生魔艺附属;Dynamic Trees - Ars Nouveau;\n;11353;flighthud;FlightHUD玩家移植版;;\n;11354;kazz.magecraft;Kazzmon's MageCraft;;\nmetal-bundles;11355;metal_bundles;Metal Bundles;;MB\n;11356;fuckvoid;FuckVoid;;\n;11357;kazz.extraincendium;ExtraIncendium;;\ntidal-towns;11358;tidal_towns,mr_tidal_towns;潮汐小镇;Tidal Towns;\nblossom-blade;11359;blossom_blade;Blossom Blade;;\n;11360;;Phantom Curse;;\ncute-kiwi-birds-new-animal;11361;;Cute Kiwi Birds;;\nmoremobheads;11362;more_mob_heads;More Mob Heads Mod;;\nouchies;11363;ouchies;Ouchies - Injuries mod;;\n;11364;traditional_asphalt;Traditional Asphalt;;\nthaumic-boots;11365;thaumic_boots;Thaumic Boots;;\ncarpeted-stairs;11366;carpeted;Carpeted Stairs & Slabs;;\njust-enough-archaeology;11367;just_enough_archaeology,jearchaeology;Just Enough Archaeology;;\nbedrock_world;11368;bedrock_world;Bedrock World;;\n;11369;mr_shared_life;Shared Life;;\nworld-preview;11370;world_preview;世界预览;World Preview;\nnot-enough-trinket-slots-nets;11371;nets;Not Enough Trinket Slots;;NETS\nthe-five-nights-at-freddys-mod;11372;fanf_mod;玩具熊的五夜后宫;The Five Nights at Freddy's Mod;FNAF\neasylan;11373;easylan;自定义LAN局域网联机服务器;EasyLAN;ELAN\npelagic-prehistory;11374;pelagic_prehistory;Pelagic Prehistory;;\nminerally;11375;minerally;Minerally;;\nbetter-jukeboxes;11376;betterjukebox;Better Jukeboxes;;\npatboxs-brewery;11377;;Patbox's Brewery;;\n;11378;bleach;死神;Bleach Mod;\nstackable-stew-and-soup;11379;stackabowls;Stackable Stew and Soup;;\nmindful-darkness;11380;mindfuldarkness;Mindful Darkness;;MD\n;11381;sbet;Subjectively Better Enchantment Tooltips;;SBET\nnature-arise;11382;nature_arise;Nature Arise;;\ncompression-bag;11383;compression_bag;压缩袋;CompressionBag;CB\nmore-beautiful-torches;11384;mbt,morebeautifultorches;More Beautiful Torches;;\nquality-crops;11385;quality_crops;Quality Crops;;\n;11386;individualkeepinv;Individual Keep Inventory;;\npermanent-sponges;11387;permanentsponges;Permanent Sponges;;PS\nhang-glider;11388;hangglider;Hang Glider;;HG\n;11389;auto_twerk;Auto Twerk;;\nblock-beams;11390;block_beams;Block Beams;;\nadorabuild-structures;11391;adorabuild_structures;AdoraBuild: Structures;;\n;11392;mr_limitless;Limitless;;\nmoogs-end-structures;11393;mes;Moog's End Structures;;MES\n;11394;brew_guide;Brew guide;;\none-click-crafting-fabric;11395;one-click-crafting;One Click Crafting;;\n;11396;genshinui;Genshin UI;;\nfenomena-structures;11397;phenomena;Phenomena Structures;;\nfirst-join-message;11398;firstjoinmessage;First Join Message;;\nexpansive-weaponry;11399;exw;Expansive Weaponry;;EXW\nwabi-sabi-structures-forge;11400;wabi_sabi_structures;Wabi-Sabi Structures;;\njobsaddon;11401;jobsaddon;JobsAddon;;\n;11402;partyaddon;PartyAddon;;\n;11403;combined_dynamic;复合动力;Combined Dynamic;CD\nloading-backgrounds;11404;loadingbackgrounds;加载背景;Loading Backgrounds;\ncuisine-delight;11405;cuisinedelight;料理乐事;Cuisine Delight;\niron-bookshelves;11406;ironbookshelves;Iron Bookshelves;;\n;11407;dimensional_expansion;Dimensional Expansion;;\ndanger-close;11408;dangerclose;Danger Close;;\nhardcore-wither;11409;hardcorewither;Hardcore Wither;;\nget-off-my-lawn-reserved;11410;goml;Get Off My Lawn ReServed;;GOML\n;11411;mr_hostile_mobsimproveovertime;Hostile Mobs Improve Over Time;;\ndangerousstonecutter;11412;dangerousstonecutter;Dangerous Stone Cutter;;\nnormal-damage;11413;normaldamage;Normal Damage;;\nftb-xmod-compat;11414;ftbxmodcompat;FTB XMod Compat;;\ncrabbers-delight;11415;crabbers_delight,crabbersdelight;蟹农乐事;Crabber's Delight;\nslot-cycler;11416;slotcycler;Slot Cycler;;SC\nresource-pack-overrides;11417;resourcepackoverrides;Resource Pack Overrides;;RO\nrespawning-animals;11418;respawninganimals;动物重生;Respawning Animals;RA\narcane-lanterns;11419;arcane_lanterns;Arcane Lanterns;;AL\n;11420;;Pillage the Village;;\ncrashma;11421;crashma;Crashma;;\n;11422;;🦇蝙蝠悲伤MOD;🦇 Bat Sad Mod;\nblackwolf-library;11423;blackwolflibrary;Blackwolf Library;;\nblues-scape-and-run-bosses;11424;greenfescapeandrun;Blue's scape and run bosses;;\ngigeresque;11425;gigeresque;Gigeresque;;\nlookaround;11426;lookaround;Lookaround!;;\nprojectileimmunityfix;11427;projectileimmunityfix;ProjectileImmunityFix;;\n;11428;;BetterZoom;;\nplayer-visibility;11429;player-visibility;Player Visibility;;\nbag-of-holding-forge;11430;bagofholding;Bag Of Holding;;BH\n;11431;;原初修真-法术附属;InitialImmortalSkill;\n;11432;ssfsb;四方食事;Cuisine of World;CW\n;11433;neoforge;NeoForge;;Neo\nfish-of-thieves;11434;fishofthieves;Fish of Thieves;;\nqualitys-delight;11435;qualitys_delight;Quality's Delight;;\narrows-info;11436;cr-arrows-info;Arrows Info;;\ngodly-vampirism;11437;godly-vampirism;Godly Vampirism;;\nitemlocks;11438;ktnilcks;ItemLocks;;\npick-block-pro;11439;pickblockpro;选择方块专业版;Pick Block Pro;\nskin-and-bones;11440;skin_and_bones;Skin And Bones;;\npet-armor;11441;pet-armor;Pet Armor;;\nmaxs-deco;11442;maxs_deco;Max的装饰;Max's deco;\nmore-nugget;11443;more_nuggets,morenugget;更多矿物粒;More Nugget;\ndual-swords;11444;dual_swords;Dual Swords;;\norganizable-play-screens;11445;organizableplayscreens;Folders!;;\nlabelling-containers;11446;labellingcontainers;Labelling Containers;;\ninventory-pause-forge;11447;inventorypause;Menu Pause / Inventory Pause (Forge);;\nready-player-fun;11448;readyplayerfun;Ready Player Fun;;\ncondensed-creative;11449;condensed_creative;Condensed Creative;;\nleaves-us-in-peace;11450;leaves_us_in_peace;Leaves Us In Peace;;\nyellow-snow;11451;yellowsnow;Yellow Snow;;\nno-tnt-griefing;11452;notntgriefing;No TNT Griefing;;\nmonster-plus;11453;monster_plus;Monster Plus;;\nilmusu-enchantments;11454;musuen;IlMusu's Enchantments;;\n;11455;pipeslide;管道速滑/爽滑满天;PipeSlide;\ndesired-servers;11456;desiredservers;Desired Servers;;\nattributizer;11457;attributizer;Attributizer;;\nreactive;11458;reactive_alchemy;反应炼金术;Reactive Alchemy;\nsimple-deepslate;11459;simple_deepslate_mod;SimpleDeepslate;;\nminunrpg;11460;minunrpg;MinunRPG;;\nmore-sheep-wools;11461;morewoolmod;More Sheep Wools;;\nmaturi-delight;11462;maturi_delight;祭典乐事;Maturi Delight;\n;11463;dont_destroysuspiciousblocks_mr;不要破坏可疑的方块;Don't destroy Suspicious blocks;\nforgified-fabric-api;11464;fabric_api;Forgified Fabric API;;FFAPI\nartisanat;11465;artisanat;Artisanat;;\ndevmode;11466;devmode;DevMode;;\nbetter-trims;11467;bettertrims;Better Trims;;\n;11468;doggomodoverhauled;Doggo Mod Overhauled;;\ntraders-nightmare;11469;tradersnightmare;Trader's Nightmare;;\ncommand-block-ide;11470;command-block-ide;Command Block IDE;;\n;11471;ultraman_and_universe;奥特曼与宇宙;;UAU\ngreen-thing-mod;11472;greenthing;Green thing mod;;\nlmft;11473;lmft;Load My F***ing Tags;;LMFT\nperipheralium;11474;peripheralium;Peripheralium;;\nresource-switcher;11475;resource_switcher;资源切换器;ResourceSwitcher;RS\nserializationisbad;11476;serializationisbad;SerializationIsBad;;\nclutter;11477;clutter;Clutter;;\nno-enchantment-cap-1-12-2-backport;11478;noenchantcap;No Enchant Cap (Legacy backport);;\nsurvivingtheaftermath;11479;surviving_the_aftermath;劫后余生;Surviving The Aftermath;STA\ndimension-viewer;11480;dimensionviewer;Dimension Viewer;;\nfoodmodmorefood;11481;more_food;更多食物;More Food;\ntfcgyres-orehints;11482;tfcgyres_orehints;TFCGyres OreHints;;\nbadstdout;11483;badstdout;BadStdOut;;\n;11484;zry_client_utils_mod;ZRY Client Utils Mod;;\nmultiworld-mod;11485;multiworld;Multiworld (Fabric & Forge);;MW\nmiaooo;11486;miaooo;Miaooo;;\npipeblocker;11487;pipeblocker;PipeBlocker;;\nmoa-decor-lights;11488;moa_decor_lights,moa_lights;MOA DECOR: LIGHTS;;\ncraft-saddles-forge-1-16-5;11489;craftsaddles;Craft Saddles and Horse Armor;;\n;11490;;Super Better Than Wolves;;SBTW\n;11491;elytrafix;Elytra Fix;;\n;11492;holib;HO-Library;;HOLib\nriskofrainmod;11494;riskofrain_items;雨中冒险;Risk of Rain Mod;RoR\n;11495;bpe;更好的等价交换;BetterProjectE;BPE\nbaddie-mobs-uwu;11496;baddie_mobs;Baddie Mobs UwU;;\nbeacons-revisioned;11497;beaconrevisioned;Beacon's Revisioned;;\nsilence-mobs;11498;silencemobs;Silence Mobs;;\n;11499;pineapple_recipe_book;凤梨烹饪书;Pineapple Recipe Book;PRB\nutilitix;11500;utilitix;UtilitiX;;\n;11501;nutrition;营养学：非官方版;Nutrition Unofficial;\ntiny-coal;11502;tinycoal;Tiny Coal;;\nreplaymod-for-forge-reborn;11503;replaymod;Replay for Forge Reborn;;\nflib;11504;flib;FLIB;;\n;11505;caiqiu;才囚学园-澪吹寒;Cai Qiu Xue Yuan;\ncopper-revisioned;11506;copperrevisioned;Copper Revisioned;;\nfrozen-fish;11507;frozenfish;冻鱼;FrozenFish;\nfine-tuned-calibration;11508;fine_tuned_calibration;Fine Tuned Calibration;;\nthird-person-maps;11509;third_person_maps;Third Person Maps;;\ncarpet-minitweaks;11510;minitweaks;Minitweaks;;\n;11511;kusaeater;艹;KusaEater;\ntime-in-a-bottle-forge;11512;tiab;Time in a bottle [FORGE\\NEOFORGE\\FABRIC];;\nseared-ladder-backport;11513;searedladder;Seared Ladder (Backport);;\nwall-jumped;11514;walljump;Wall Jumped;;\nred-core;11515;redcore;Red Core;;\n;11516;cakeordie;糕事无亡;Cake or Die;CoD\nrecord-days-survived;11517;record_days_survived;Record Days Survived;;\nveinminer-companion;11518;veinminer_companion;VeinMiner Companion;;\nflightem;11519;flightem;我要骑着你飞;Flightem;\n;11520;leashmod;Leashable Players;;\nsnowballs-freeze-mobs;11521;snowballsfreezemobs;Snowballs Freeze Mobs;;\nguinea-pigs;11522;guinea_pigs;Guinea Pigs;;\n;11523;smashbat;Smash Bats Mod;スマッシュバットMOD;\n;11524;;禁忌方块;Taboo Blocks;TB\nvault-research;11525;vault_research;Vault Research;;\nthe-chaser;11526;chaser;The Chaser;;\nbandaging;11527;bandaging;Bandaging;;\nenchanted-charms;11528;enchanted_charms;Enchanted Charms;;\nnether-api;11529;nether_api;Nether API;;\nmother-silverfish;11530;mother_silverfish;Mother Silverfish;;\nmcretector;11531;mcretector;MCretector;;\n;11532;TextCraft;文字 Mod;TextCraft;\nlittle-botanics;11533;little_botanics;Little Botanics;;\nex-pattern-provider;11534;expatternprovider,extendedae;AE2扩展;ExtendedAE;EAE\n;11535;integrated-circuit;Integrated circuit;;\ncreative-disk;11536;creativedisk;Creative Disk;;\nengineers-construct;11537;engineers_construct;Engineer's Construct;;\n;11538;botools;假人映射;Botools;\nwitch-mobility;11539;witch_mobility;Witch Mobility;;\ngravestones-die-classy;11540;gravestones;Subaraki's Gravestone;;\nctov-irons-spells-n-spellbooks-compat;11541;mr_ctov_ironsspellsnspellbookscompat;CTOV - Iron's Spells 'n Spellbooks compat;;\nctov-pneumaticcraft-repressurized-compat;11542;;CTOV - PneumaticCraft: Repressurized compat;;\nctov-jellyfishing-compat;11543;mr_ctov_jellyfishingcompat;CTOV - Jellyfishing compat;;\n;11544;pebbles_begone;Pebbles Begone;;\neternal-eats;11545;eternaleats;Eternal Eats;;\nzombiegame;11546;zombiegame;僵尸游戏;Zombie Game;\neffect-overhaul;11547;effect_overhaul,alms_lucidapi;Effect Overhaul / Lucid Library;;\ncasualness-delight;11548;casualness_delight;随意乐事;Casualness Delight;\n;11549;voidtech;虚空科技;VoidTech;VT\neidolon-rebrewed;11550;eidolon_patches;Eidolon Rebrewed/Eidolon Patches;;\nfruity-random-teleport;11551;fruity_tpr;紫颂果味随机传送;Fruity Random Teleport;ftpr\npipe-master-2000;11552;pipegoggles;管道专家 2000;Pipe Master 2000 / Pipe Googles;\ncommand-macros;11553;cmdkeybind;命令宏;Command Macros;\n;11554;shmetro;上海地铁装饰组件;MTR Shanghai Wetro Addon;SHM\nyuushya-modelling;11555;yuushya_modelling;方块建模;Yuushya Modelling;\ntactical-aid;11556;tactical_aid;战术医疗;Tactical Aid;TA\n;11557;clear-enchanting;Clear Enchanting;;\nfishing-bobber-detector;11558;bobberdetector;Create: Fishing Bobber Detector;;\n;11559;unfixmc181190;村民折扣叠加;Unfix MC-181190;\ncreate-balanced-flight;11560;balancedflight;Create: Balanced Flight;;\nbrewin-and-chewin-remastered;11561;brewinandchewin;B&C Fan-Made 1.20 Port;;\ncupboard;11562;cupboard;Cupboard;;\nmcinstance-loader;11563;mcinstanceloader;MCInstance Loader;;\npre-fish-feed;11564;prefishfeed;打窝;Pre Fish Feed;PFF\narmor-trim-item-fix;11565;armortrimItemfix;Armor Trim Item Fix;;\nwaffles-moss;11566;waffles_moss;Waffle's Moss;;\ndelightful-cuisine-for-woodheads;11567;;Delightful Cuisine for Woodheads;;\ntowers-of-the-wild-modded;11568;totw_modded;旷野之息高塔 : 再重制;Towers of the Wild Modded;\n;11569;;T算法库;;TAL\nwaffles-terracotta-plaster;11570;waffles_terracotta_plaster;Waffle's Terracotta Plaster;;\nwatermedia;11571;watermedia;WATERMeDIA: Multimedia API;;\n;11572;inverse_phantoms;Inverse Phantoms;;\n;11573;cactus_storage;Cactus Storage;;\n;11574;;暮色森林光影兼容;;\netcetera;11575;etcetera;Etcetera;;\n;11576;powerdrop;战斧;Tomahawks;\n;11577;bocchitherock;孤独摇滚！;Bocchi The Rock;bocchi\ngarnished;11578;garnished;机械动力：装食;Create: Garnished;\njust-a-well;11579;well;Just a Well;;\nsiberiamod;11580;;SiberiaMod;;\nspacetime;11581;spacetime;空之翼;SpaceTime;ST\n;11582;lobotomy_corporation;脑叶公司F版;Lobotomy Corporation F;LCF\n;11583;;经验书;Experience Book;\n;11584;peca;carpet 假人扩展;Player Extend Carpet Addition;PECA\ngrappling-hook-restitched;11585;grappling_hook_restitched;Grappling Hook: Restitched;;\n;11586;minceraft;Minceraft;;\nmob-animations;11587;;Mobs Animations Plus;;\nplatter;11588;platter;Platter;;\ni-want-that-back;11589;iwtb_mr;I Want That Back;;IWTB\npresence-not-required;11590;presencenotrequired;Presence Not Required;;\nwnboi;11591;wnboi;Why Not Be One Item;;wnboi\n;11592;sfssfz;四方食事-福州DLC;Cuisine of World for Fuzhou;CW-Fuzhou\n;11593;biomecompass;Biome Compass: Maya Edition;;\neye-collector-mod;11594;eyecollectormod;Eye Collector;;\n;11595;rebirth_hourglass;重生沙漏;Respawn Hourglass;RH\ncozy-foods-milk-tea;11596;cozyfoods;Cozy Foods: Milk Tea;;\nopolis-miners;11597;miners;Opolis Miners;;\nnetherite-but-cheaper;11598;nbc_mr;Netherite But Cheaper;;NBC\n;11599;fabric-hammers;Fabric Hammers;;\nitems-displayed-fabric;11600;items_displayed;物品陈列;Items Displayed;\nwood-converter;11601;woodconverter,mod_wc;Wood Converter;;\nhammer-mod;11602;;Hammer Mod;;\n;11603;enderstimecontrol;Ender的时间控制;Ender's time control;ETC\nrealisticsleepfabric;11604;realisticsleep;Steve's Realistic Sleep;;\nzombies-actually-siege;11605;actual_zombie_siege;Zombies Actually Siege;;\nhex-gloop;11606;hexgloop;咒法凝浆;Hex Gloop;\nthe-void;11607;thevoid;The Void;;\nthe-shadowlands;11608;;The Shadowlands;;\nbloxys-structures;11609;bloxys_structures;Bloxy's Structures;;\nenhanced-hordes;11610;;Enhanced Hordes;;\nniftycarts;11611;niftycarts;NiftyCarts;;\nwither-dimension-whitelist;11612;wither_spawn_control;Wither Dimension Whitelist;;\n;11613;hexlink;Hexlink;;\n;11614;shiny;异格/异色;Shiny;\nusefulfood-reborn;11615;usefulfood_reborn;有用的食物 重生;UsefulFood Reborn;UFR\nancient-warfare-2-tweaked;11616;ancientwarfare,ancientwarfareautomation,ancientwarfarenpc,ancientwarfarestructure,ancientwarfarevehicle;Ancient Warfare 2: Tweaked;;\ncuffed;11617;cuffed;监禁;Cuffed;\nrockn-roller;11618;itemscroller;Rock'n Roller;;RR\nbodies-bodies;11619;bodiesbodies;Bodies! Bodies!;;\npotionparticlepack;11620;potionparticlepack;药水粒子包;Potion Particle Pack;\ndeep-blood-evolution;11621;deep_blood_evolution;Deep Blood Evolution;;\nbetter-horses-mod;11622;BetterHorses;更好的马;Better Horses Mod;\npotion-bundles;11623;potionbundles;药水捆;Potion Bundles;\nkofi-table;11624;kofitable;Ko-fi Table;;\nbetter-mcdonalds-mod;11625;better_mcdonalds_mod;Better McDonald's Mod;;\n;11626;itemframesplus;ItemFrames+;;\nsinytra-connector;11627;connectormod;信雅互联;Sinytra Connector;\nnoclip;11628;noclip;noclip;;\ndiamond-in-the-rough-as-in-obsidian;11629;diamond_in_the_rough;Diamond In The Rough;;\nfactioncraft;11630;faction_craft;FactionCraft;;\nbetter-ender;11631;betterenderfabric,better_ender;Jawsy's Better Ender;;\nmlmanimator;11632;mlmanimator;Multi Limbed Model Animator;;MLMAnimator\nplaylist;11633;playlist;Playlist;;\n;11634;villagersffh;Villagers: Far From Home;;V:FFH\ncultivationcraft;11635;cultivationcraft;异道修仙;CultivationCraft;\n;11636;legend_sword;传说之剑;Legendary Sword;LS\n;11637;carpet-las-addition;Carpet LAS Addition;;CLA\n;11638;whoami;我是谁?;Who am i?;\nverticality-hotbar;11639;verticality;Verticality;;\nthe-slender-man;11640;slenderman;The Slender Man;;\nautomodpack;11641;automodpack;AutoModpack;;\nbella-quilt;11642;bella;Bella;;\nsimply-skills;11643;simplyskills;Simply Skills;;\n;11644;;Zombies Improve Over Time;;\n;11645;;Skeletons Improve Over Time;;\n;11646;;Upgradeable Wolves;;\ncctv-craft;11647;cctvcraft;CCTV Craft;;\nredeco;11648;redeco;重铸家具;Re:Deco;\npocket-storage;11649;pocket_storage;Pocket Storage;;\nanimated-player-mod;11650;animatedplayer;动画化玩家;Animated Player Mod;\nits-thirst;11651;itsthirst;It's Thirst;;\ninfernal-damage-indicators;11652;infernalhealth;Infernal Damage Indicators;;\n;11653;unofficial-sodium-culling-patch;Sodium非官方剔除补丁;Unofficial Sodium Culling Patch;USCP\n;11654;justenoughspeed;Just Enough Speed;;\nremion;11655;soullike_armors,remion;soul-like armors: medieval weapons & armors;;\n;11656;elytratime;Elytra Time;;\n;11657;yankailoginprotect;玩家重生保护;Yankai Login Protect;YLP\nsquaremap;11658;squaremap;squaremap;;\nroots-classic-fabric;11659;rootsclassic;Roots Classic (Fabric);;\njustenoughbreeding;11660;justenoughbreeding;Just Enough Breeding;;JEBr\nsimple-drills;11661;simpledrills;Simple Drills;;\npeculiarpieces;11662;peculiarpieces;Peculiar Pieces;;\nsdm-loot-stages;11663;lootstages;战利品阶段;SDM Loot Stages;\nplayer-stages;11664;playerstages;玩家阶段;Player Stages;\ngeneration-stages;11665;generationstages;生成阶段;Generation Stages;\nars-oscura;11666;ars_oscura;黑暗魔艺;Ars Oscura;\nbobby-reforged;11667;bobby;服务器区块缓存-重铸;Bobby Reforged;\nshut-up-gl-error;11668;shut_up_gl_error;Shut Up GL Error;;\nno-spider;11669;no-spider;No spider;;\nterracart-reloaded;11670;terracart;Terracart Reloaded;;\ndme;11671;dme;Deep Mob Evolution;;DME\nfruit-stack;11672;fruitstack;果栈丰萦;Fruit Stack;\ngregtech-drawers;11673;;Gregtech Drawers;;\nmarine-iguana;11674;marine_iguana;海鬣蜥;Marine Iguana;\nstonks;11675;stonks;Stonks;;\ndirtnt;11676;dirtnt;泥土TNT;DirTNT;\nequipable-twilight-forest-charms-tfc;11677;equipabletfc;可装备的暮色森林符咒;Equipable Twilight Forest Charms;\nburiedwrecks;11678;;Buried Wrecks;;\nzapsconverter;11679;zapsconverter;Zaps 转换器;Zaps Convertor;\namethyst-update;11680;amethyst_update;Amethyst Update;;\nangel-block;11681;angelblock;天使方块;Angel Block;\nangel-block-renewed;11682;angelblockrenewed;Angel Block Renewed;;\nda-bomb;11683;dabomb;Da Bomb;;\nquickstack;11684;quickstack;Quickstack;;\n;11685;blackholestorage;黑洞储存;Black Hole Storage;\ncollectors-album;11686;collectorsalbum;Collector's Album;;\nhead-name-fix;11687;headfix;Head Name Fix;;\nhostile-delight;11688;hostiledelight;异趣乐事/宿敌乐事;Unusual Delight (Hostile Delight);\nvivecraft-compat;11689;vivecraftcompat;ViveCraft兼容;ViveCraft Compat;\nwildlife-expanded-skunk;11690;skunk;Wildlife Expanded: Skunk;;\nkeepheadnames;11691;keepheadnames;Keep Head Names;;\nydms-fennec-fox;11692;fennec_fox;YDM's Fennec Fox;;\nydms-ostrich;11693;ostrich;YDM's Ostrich;;\ncurse-of-the-warden;11694;;Curse Of The Warden;;\nchatanimation;11695;chatanimation;聊天动画;ChatAnimation;\nmobvote-rascal;11696;mobvote_rascal;Mobvote Rascal;;\nspotiforge;11697;spotiforge;SpotiForge;;\nindypets;11698;indypets;IndyPets - Independent Pets;;\nminecraft-legends-golems;11699;legendsgolems;Legends - Golems;;\ncreate-sandpaper-overhaul;11700;create_so;Create: Sandpaper Overhaul;;\nvintage-gregtech;11701;;Vintage GregTech;;\nydms-vulture;11702;vulture;YDM's Vulture;;\n;11703;hurtfix;因为不怕痛所以不想让别人点防御啦[受伤修正];HurtFix;\nlocked-slots;11704;locked_inventory;Locked Slots;;\nwildlife-expanded-snakes;11705;we_snakes;Wildlife Expanded: Snakes;;\njline-for-minecraft-dedicated-server;11706;jline4mcdsrv;JLine for Dedicated Server;;\nblocklingcollection;11707;blocklings_collection;Blockling Collection;;\ntrenzalore;11708;trenzalore;Trenzalore;;\n;11709;dininghall;食堂;Dining Hall;\n;11710;calculation;Calculation Mod;;CLL\n;11711;hikanamlora;机原萌铁;MoeRailwayOfCR;MOCR\n;11712;qi_hypermanagement_system;齐 超管系统;Qi's Hypermanagement System;\ncreo-lib;11713;creo;Creo Lib;;\nhighlight;11714;highlight;Highlight;;\nlost-libraries;11715;mr_lost_libraries;Lost Libraries;;\n;11716;plantpearl;Plant Pearl;;\n;11717;;Calamity Mod: Astral Infection;;\nsensible-sleep;11718;;Sensible Sleep;;\n;11719;hollowtweaks;Hollows Tweaks;;\neastward-journeys;11720;eastward_journeys;Eastward Journeys;;\n;11721;;Abyssal Guardian Boss;;\nomotions;11722;omotions;Omotions;;\ninvisible-frames;11723;invisframes;Invisible Frames;;\nultimatebackportmod;11724;ultimatebackport,ubm;Ultimate Backport Mod;;UBM\n;11725;ExnihiloSequentiaPlus;无中生有：传承扩展;Exnihilo Sequentia Plus;ESP\n;11726;refined_advancements_mr;Refined Advancements;;\ntrue-ending;11727;mr_true_ending;True Ending: Ender Dragon Overhaul;;\n;11728;sorted-enchants;附魔排序;Sorted Enchants;\nconstructs-armory-1-18-2-port;11729;constructsarmory;Construct's Armory Port / Construct Armory updated;;\n;11730;;冬之纪行诗;The Poetry Of Winter;\n;11731;videotape;Video Tape;;\n;11732;enchantmenttweaker;Enchantment Tweaker;;\nlet-me-sleep;11733;letmesleep;Let Me Sleep;;\nforge-tooltip-texts-scroller;11734;tooltipscroller;Tooltip Texts Scroller;;\nwell-behaved-mobs;11735;wellbehavedmobs;Well-Behaved Mobs / Good Skeletons Don't Strafe;;\n;11736;better_pigs;更好的猪;Better Pigs;\n;11737;nruium;泛金;Nruium;Ni\nancient-realms;11738;ancient_realms3;Ancient Realms;;\nremove-terralith-intro-message;11739;cancel_terralith;Remove Terralith Intro Message;;\ngambling-style;11740;gamblingstyle;Gambling Style;;\nanimated-recipe-book;11741;animatedrecipebook;Animated Recipe Book;;\n;11742;mr_sanguine;血污;Sanguine;\n;11743;packetunlimited;Packet Unlimited;;\nbloxys-bosses;11744;bloxys_bosses;Bloxys Bosses;;\nmore-bows-cj;11745;morebows;More Bows CJ;;\n;11746;snowballshitplayers;雪球可以击退玩家;Snowballs Hit Players;\ntectonic-bop;11747;tectonicbop;Tectonic：超多生物群系兼容;Tectonic + Biomes O' Plenty compat;\ntech-enhanced;11748;tech_enhanced;Tech-enhanced;;\ngilded-netherite-cj-edition;11749;gildednetherite;Gilded Netherite CJ Edition;;\nenderite-cj-edition;11750;enderite;Enderite CJ Edition;;\n;11751;tectonic;Terratonic;;\ndangerous-oceans;11752;dangerous_oceans;Dangerous Oceans;;\n;11753;mc249136;MC-249136 修复;MC-249136 Fix;\n;11754;fefafly;Feather Fall Flying;;\n;11755;lidar;Lidar;;\nanvil-never-too-expensive;11756;ante;Anvil Never Too Expensive;;ANTE\ncrossbow-mod;11757;crossbow;弩;Crossbow;\n;11758;placeitem;Place Item;;\n;11759;infinite_storage;无限收纳;Infinite Storage;\ncontrolify;11760;controlify;Controlify;;\n;11761;bedrodium;Bedrodium;;\nxbbs-hot-air-balloon-fantasy;11762;hotairballoon;XBB的热气球幻想;XBB's hot air balloon fantasy;XBBHAB\nnetherite-horse-armor-cj;11763;netheritehorsearmor;Netherite Horse Armor CJ;;\npolydex;11764;polydex2,polydex;Polydex;;\nplenty-of-armors;11765;plentyofarmors;Plenty Of Armors;;\nspatialio-compat;11766;spatialio-compat;SpatialIO Compat;;\n;11767;mica;Mica;;\nheavy-armor;11768;heavy_armor;Heavy Armor;;\nmoss-layers;11769;moss_layer;苔藓层;Moss Layers;\nlunchboxes;11770;lunchbox;Lunchbox;;\n;11771;veigar;Autofish-chao;;\nstrawgolem;11772;strawgolem;稻草傀儡;Straw Golem;SG\n;11773;shifting-wares;Shifting Wares;;\ndynamic-trim;11774;dynamictrim;Dynamic Trim;;\ntrade-uses;11775;tradeuses;Trade Uses;;\nambient-fireflies;11776;ambient_fireflies;Ambient Fireflies;;\nnb_mod;11777;nb_mod;Nb_Decor;;\n;11778;;Bedrock Wither: Recreation for Java Minecraft;;\nstacked-armor-trims;11779;stacked_trims;Stacked Armor Trims;;\n;11780;finalbeta,mod_FinalBeta;Final Beta;;\nbetter-trim-tooltips;11781;better-trim-tooltips;Better Trim Tooltips;;\ntotally-enough-pain;11782;tep;Totally Enough Pain;;TEP\ncustom-village;11783;custom_village_hileb;村民调教;Custom Village;CVH\nanimaticareforged;11784;animatica;AnimaticaReforged;;\nshear-cows;11785;shear_cows;Shear Cows;;\nfps-boost;11786;betterfps;FPS Boost;;\nafkfish;11787;autofish;AFKFish;;\n;11788;myfantasycreatures;我的幻想生物;MetalMax;MFC\n;11789;rend;Rend;;\nmore-trails-more-tales;11790;moretrailsmoretales;More Trails & More Tales;;MTMT\ngood-tea;11791;good-tea;馥郁良茶;Good Tea;\nscreen-size;11792;screensize,screen-size;Screen Sizer;;\nbotany-ores;11793;botanyores;Botany Ores;;\n;11794;cauldronfix;炼药锅修复;Cauldron Fix;\nfoods-plants-cooking-with-mindthemoods;11795;cooking_with_mindthemoods;Foods & Plants (Cooking with Mindthemoods);;\n;11796;customgui;自定义 GUI;Custom GUI;\nkeebszs-battletowers-fabric;11797;keebsz;Keebsz's Battle Towers;;\nas-a-creeper;11798;as_a_creeper_hileb;*的和你爆了;As a creeper;AACH\nextra-boats;11799;extra_boats;Extra Boats;;\nsmarterwanderers;11800;smarterwanderers;Smarter Wanderers + Biome Explorer Maps;;\nextended-crafting-nomifactory-edition;11801;;合成拓展：延续版;Extended Crafting：Nomifactory Edition;\n;11802;mealapiaddon;Meal API Addon;;\n;11803;hbmanvauto;自动砧;AutoAnvil For HBM-NTM;\neasy-config-button;11804;easyconfigbutton;Easy Config Button;;\n;11805;kahur;Kahur;;\n;11806;ModLoaderMP;ModLoaderMP;;MLMP\nmaple;11807;maple;枫;Maple;\nseamless;11808;seamless;Seamless;;\nbundle-enchantments;11809;bundle_enchantments;Bundle Enchantments;;\nreal-time-mod;11810;realtimemod;Real Time Mod;;\nchromatic-arsenal;11811;chromatic _arsenal;Chromatic Arsenal;;\nore-visual-detector;11812;orevisualdetector;Ore Visual Detector;;\ndank-null-no_rce;11813;danknull;/dank/null/no_RCE/;;\nkubejs-additions;11814;kubejsadditions;KubeJS Additions;;\n;11815;mr_deathswap;死亡交换;Death Swap;\n;11816;mr_deathswap_gmchange;死亡交换-游戏模式切换;DeathSwap-Gmchange;\n;11817;abyq;我的世界但是除了你都很快;Anything but you quickly;abyq\nbetterdays;11818;betterdays;Better Days;;\n;11819;seedyplace;Seedy Place;;\nextradetails;11820;extradetails;ExtraDetails;;\nsigned-paintings;11821;signed_paintings;Signed Paintings;;\ninenchantment-reconstruction;11822;inenchantment_and_reconstruction;负魔与改造;Inenchantment ＆ Reconstruction;I&R\n;11823;glbshop;全球商店;;\nnae2;11824;nae2;Neeve的AE2:EL附加工具;Neeve's AE2: Extended Life Additions;NAE2\nthe-microworld;11825;;The Microworld;;\nvionta;11826;vionta;Vionta;;\nhammer-animations;11827;hammeranims;HammerAnimations;;\nblueflame;11829;blueflame;Blue Flame Burning;;\nnocubes-craftable-music-discs;11830;nocubes_craftable_md;NoCube's Craftable Music Discs;;\nenderlicious;11831;enderlicious;Enderlicious;;\nquarry-reborn;11832;quarrymod;Quarry Reborn;;\n;11834;noodlesmusketrm;面条的火枪重铸版;Noodles Musket Remastered;\nmorematerials;11835;mmt;Lopy's More Materials;;\nfoodmod-for-fabric;11836;foodmod;FoodMod;;\ncursed-mod-for-fabric;11837;cursed;Cursed Mod;;\nweaponized-baseball;11838;webasemod;Weaponized Baseball;;\nepic-paragliders-but-sekiro;11839;epicparaglidersbutsekiro;史诗狼之伞;Epic Paragliders But Sekiro;EPBS\nvisualores;11840;visualores;VisualOres;;\nd-furnace;11841;furnace3d;3D Furnace;;\nflickerfix;11842;flickerfix;闪烁修复;FlickerFix;\n;11843;spongesucc;SpongeSucc / Lava Sponge;;\n;11844;phosphor-legacy;Phosphor Legacy;;\nrandom-crits;11845;random_crits;Random Crits;;\nsimple-enchantments;11846;simple_enchantments;Simple Enchantments;;\nsimple-weapons-for-better-combat;11847;simple_weapons;Simple Weapons for Better Combat;;\nwither-drops-netherite-templates;11848;;Wither Drops Netherite Templates;;\n;11849;spinelsess;Spineless;;\nimmersive-melodies;11850;immersive_melodies;沉浸式奏乐;Immersive Melodies;\ntheobsidianboat;11851;obsidianboat;TheObsidianBoat;;\ndragon-survival-compatibility;11852;dragonsurvival_compatibility;龙之生存：兼容;Dragon Survival Compatibility;\nbettercookedaxolotls;11853;better_cooked_axolotls;Better Cooked Axolotls;;\nstateobserver;11854;stateobserver;StateObserver;;\nmighty-mail;11855;mighty_mail;Mighty Mail;;\nnature-architect-plants-and-decorations;11856;nature_architect_plants_and_decorations;Nature Architect: Plants and Decorations;;\nbetter-berries-redux;11857;better_berries;Better Berries Redux;;\nkurisus-passable-leaves;11858;passableleaves;叶间穿梭;Passable Leaves;\n;11859;gvclibsa;GVClibSA;;\narmorful;11860;armorful;Armorful;;\n;11861;aaaahidechat4s;直播隐藏聊天框;HideChatForStreaming;\n;11862;osl;Ornithe Standard Libraries;;OSL\nfix-gpu-memory-leak;11863;gpumemleakfix;修复GPU内存泄漏;fix GPU memory leak;\ncake-delight;11864;cakedelight;蛋糕乐事;Cake Delight;\nautoslabs;11865;autoslabs;AutoSlabs;;\n;11866;kabo-village-marker;Kabo Village Marker;;\nootn;11867;ootn;Overworld Ores To Nether;;OOTN\n;11868;signal;Signal;;\ni-see-what-you-did-there;11869;iswydt;I See What You Did There!;;\nhit-streak;11870;hitstreak;Hit Streak;;\nnew-slab-variants;11871;newslabvariants;New Slab Variants;;\nblue-archive-banner-and-shield-patterns;11872;blue_archive_banner_and_shield_patterns;Blue Archive Banner and Shield Patterns;;\nmimic-mod;11873;mimic;Mimic;;\nbocchium;11874;bocchium;Bocchium;;\ndespawntweaker;11875;despawn_tweaker;DespawnTweaker;;\n;11876;imperfect_magic;并不完美的魔法;Imperfect Magic;IM\non1chest;11877;on1chest;唯一箱子;Only Need One Chest;On1C\nlandmines;11878;landmines;地雷;Landmines;\n;11879;sleepanytime;我TM睡睡睡睡睡睡睡;Sleep AnyTime;SAT\npacked-up-backpacks;11880;packedup;Packed Up (Backpacks);;\nvalhelsia-furniture;11881;valhelsia_furniture;Valhelsia Furniture;;\n;11882;tic_addition;匠魂1铸模附加;Ink＆Soul's TiC Addition;TiC-A\ndimension-remote;11883;pocketdimensions;Dimension Remote;;\nbring-decay;11884;bring_decay;Bring Decay;;\nrendertypecache;11885;rendertypecache;RenderTypeCache;;\nautotag;11886;autotag-convention;AutoTag;;\nlibz;11887;libz;LibZ;;\nautotools;11888;autotools;自动工具;AutoTools;\n;11889;threadtweak;ThreadTweak;;\nnbt-editor;11890;nbteditor;NBT Editor;;\n;11891;secular_seven_reigns;尘世七执政;Secular Seven Reigns;S7R\n;11892;betterendsky;BetterEnd Sky;;\n;11893;wild_netherwart_;下界疣;The Nether Wart;TNW\ndimtpcmd;11894;dimtpcmd;维度传送指令;DimtpCmd;\nfast-ip-ping;11895;fastipping;Fast IP Ping;;\nbetter-anvil;11896;betteranvil;Better Anvil;;\nbloxys-misc;11897;bloxys_misc;Bloxy's Misc;;\n;11898;find;Find - Search For Items;;\n;11899;elytra-recast;Elytra Recast;;\ntieredz;11900;tiered;TieredZ;;\nmore-creepers;11901;;更多苦力怕;More Creepers;\n;11902;cypress;篝火：侧柏;Campfire Occupation: Cypress;GHCB\n;11903;kwastibustmonsters;Kwasti Bust Monsters;;\ncryptic-registry;11904;cmregistry;Cryptic Registry;;\nlevel-text-fix;11905;leveltextfix;Level Text Fix;;\nbinnies-mods-patched;11906;binniecore,extrabees,extratrees,botany,genetics;Binnie's Mods Patched;;\ncyanide;11907;cyanide;氰化物;Cyanide;\n;11908;particlesenhanced;Particles Enhanced;;\n;11909;atum;Random and set seed autoresetter;;Atum\n;11910;worldpreview;WorldPreview;;\n;11911;celebrations;欢庆;Celebrations;\nprehistoric-nature-precambrian-dimension;11912;;Prehistoric Nature Precambrian Dimension;;\n;11913;mc260949fix_forge,mc260949fix_fabric;MC-260949 Fix;;\npro-sniper;11914;prosniper;专业狙击手;Pro Sniper;\naether-gear-expansion;11915;aethergearexpansion;Aether Gear Expansion;;\naether-enhanced-extinguishing;11916;aether_enhanced_extinguishing;Aether Addon: Enhanced Extinguishing;;\n;11917;bookmasscraft;Recipe Book Mass Craft;;\ncreate-new-age;11918;create_new_age;机械动力：电气时代;Create: New Age;\n;11919;universal_trading;Universal Trading;;\n;11920;sauces;涂酱;Sauces;\n;11921;zenith;天顶剑;zenith;\n;11922;sophring;十泉汤;Sophring;\n;11923;ysxqtd;原神：星穹铁道;Genshin:StarRail;GSR\nnekoui;11924;dev.anye.mc.nekoui,nekoui;NekoUI;;\n;11925;grimoireofpatchouli;帕秋莉的魔法书;Grimoire of Patchouli;GoP\nxpteleporters-2;11926;xpt;经验传送器;XPTeleporters 2;\ncentered-plants;11927;centeredplants;植物居中;Centered Plants;\nshowkeybinds;11928;showkeybinds;Show Keybinds;;\n;11929;pokepatch;LostEra Coremod;;\nadvanced-tooltips;11930;advanced-tooltips;Advanced Tooltips;;\nctov-wares-compatibility-pack;11931;mr_ctov_warescompat;CTOV - Wares compat;;\nctov-monobank-compatibility-pack;11932;mr_ctov_monobankcompatibilitypack;CTOV - Monobank compat;;\nprehistoric-nature-cambrian-dimension;11933;;Prehistoric Nature Cambrian Dimension;;\n;11934;mushroommod;Mushroom Mod;;\nbetter-end-sky;11935;endsky;Better End Sky;;\nvalkyrie;11936;valkyrie;Valkyrie;;\nsoaring-structures-2;11937;soaringstructures2;Soaring Structures 2;;\nprehistoric-nature-ordovician-dimension;11938;;Prehistoric Nature Ordovician Dimension;;\nfadeless;11939;fadeless;Fadeless;;\ncreate-tweaked-controllers;11940;create_tweaked_controllers;机械动力：高级遥控器;Create: Tweaked Controllers;\n;11941;tealbricks;Teal Bricks Reborn;;\nauto-elytra;11942;autoelytra;Auto Elytra;;\nvillager-names;11943;villagernames;Villager Names;;\nbedrockwaters;11944;bedrockwaters;BedrockWaters;;\n;11945;antishulkerdupe;AntiShulkerDupe;;\nhourai-elixir;11946;houraielixir;Hourai Elixir;;\nconvivium;11947;convivium;会饮;Convivium;9v8\nprehistoric-nature-silurian-dimension;11948;;Prehistoric Nature Silurian Dimension;;\ngrass-seeds;11949;grassseeds;Grass Seeds;;\nminecraft-dungeons-weaponry;11950;;Dungeons Weaponry;;\nahznbs-naruto-mod-addon;11951;narutomodaddon;AHZNB's Naruto Mod || ADD-ON;;\nrandom-village-names;11952;randomvillagenames;Random Village Names;;\nmooshroom-tweaks;11953;mooshroomtweaks;Mooshroom Tweaks;;\ngrinners-ents;11954;grinnersents;Grinner's Ents 2.0;;\nlog-cleaner;11955;logcleaner;Log Cleaner;;\nlunar;11956;lunar;Lunar;;\ncosmetics;11957;cosmetica;Cosmetica;;\n;11958;cni;自定义NPC-拓展物品;CustomNpc Item;cni\n;11959;actionbarinfo;Action Bar Info;;ABI\ninteriors;11960;extendedseating,interiors;机械动力：内饰;Create: Interiors;\nsouls;11961;soul;Souls;;\nreal-seasons;11962;realseasons;Real Seasons;;\neternal-winter;11963;eternalwinter;Eternal Winter;;\nmove-boats;11964;moveboats;Move Boats;;\nomnom;11965;omnom;OmNom;;\nforest-x-reborn;11966;forest;Forest X Reborn;;\nexlines-bark-carpets;11967;barkcarpets;Exline's Bark Carpets;;\nexlines-more-carpets;11968;morecarpets;Exline's More Carpets;;\nsrsl;11969;srsl;星骸秘遗;Star Remains Secret Legacy;SRSL\nunearthed-journey;11970;unearthed_journey;出土之旅;Unearthed Journey;\nprehistoric-nature-devonian-dimension;11971;;Prehistoric Nature Devonian Dimension;;\nmwtis-stone-expansion;11972;stoneexpansion;Mwti's Stone Expansion;;\nsmooth-entity-light;11973;sel;Smooth Entity Light;;\ntitle-fixer;11974;titlefixer;Title Fixer;;\nexstrangefood-reborn;11975;exstrangefoodreborn;更多奇怪的食物：重生;ExStrangeFood:Reborn;ExSFR\nfrog-legs;11976;froglegs;Frog Legs;;\nohoy-scallywag;11977;;O'Hoy Scallywag;;\nylf-your-flying-pet;11978;ylf_mod;Ylf : your flying pet;;\nbedrock-edition-features;11979;bedrock_edition_features;\"基岩\"特性;Bedrock Editon Features;BEF\nsimply-hot-springs;11980;simplyhotsprings;Simply Hot Springs;;\nmagma-monsters;11981;magma_monsters;Magma Monsters;;\nmystics-biomes;11982;mysticsbiomes;Mystic's Biomes;;\nsuper-block-world;11983;SuperBlockWorld;Super Block World [Forge];;\ncreate-train-additions;11984;create_train_additions;Create Train Additions;;\nenchantery;11985;enchantery;Enchantery;;\nreplant-swamp-trees;11986;replantswamp;Replant Swamp Trees;;\nvehiclefix;11987;vehiclefix;VehicleFix;;\nfirework-frenzy;11988;fireworkfrenzy;Firework Frenzy;;\nprehistoric-nature-carboniferous-dimension;11989;;Prehistoric Nature Carboniferous Dimension;;\ncammies-combat-tweaks;11990;combattweaks;Cammie's Combat Tweaks;;\nchorus-gormandizers;11991;;Chorus Gormandizers;;\nenemy-expansion;11992;enemy_expansion,enemyexpansion;Enemy Expansion;;\nclear-void;11993;clearvoid;清澈虚空;Clear Void;\nthe-arcaneum;11994;the_arcaneum;The Arcaneum;;\n;11995;mixmetica;Mixmetica;;\nmutant-mobs-mod;11996;mutantmobsmod;The Mutant Mobs;;\nprehistoric-nature-permian-dimension;11997;;Prehistoric Nature Permian Dimension;;\n;11998;;Phosphor Legacy Forge;;\nstairdoors-extended;11999;stairdoors;StairDoors Extended;;\ninto-the-rend;12000;therenddimension;Into The Rendv / The Rend Dimension;;\nbuff-frog-mod;12001;buff_frog_mod;Buff Frogs;;\nasphere;12002;asphere;Asphere;;\nautoclick-machine;12003;clickmachine;自动连点器 (Fabric);Click Machine (Fabric);\nharvestcraft-tweaker;12004;harvestcrafttweaker;HarvestCraft Tweaker;;\nthe-legend-of-the-mudman;12005;legend_of_the_mudman;The Legend of the Mudman;;\nmutagenesis;12006;mutagenesis;Mutagenesis;;\nnether-update-backport;12007;netherbackport;Nether Update Backport;;\nl2hostility;12008;l2hostility;莱特兰-恶意;L2Hostility;\npackagedthaumic;12009;packagedthaumic;封包神秘时代;PackagedThaumic;\ncat-eyes-night-vision-toggle-mod;12010;cateyes;Cat Eyes - Night Vision Toggle Mod;;\nvtdownloader;12011;vt_downloader;VTDownloader;;\nsmithing-mod;12012;elodiseo_smithing_mod;ElOdiseo Smithing Mod;;\n;12013;rice_dumpling;粽子;With RiceDumpling;\nthe-doppelganger;12014;doppelgangermod;The Doppelganger;;\nrail-wand;12015;railwand;Rail Wand;;\ntin-ore;12016;tinore;Tin Ore;;\ndark-elves-of-underdark;12017;dark_elves;Dark Elves of Underdark;;\ntitan-biomes;12018;titan_biomes;Titan Biomes;;\ndepth-striders;12019;depth_striders;Depth Striders;;\nendercage;12020;endercage;末影笼;Endercage;\nmutationcraft;12021;mutationcraft;Mutationcraft;;\naftershock;12022;aftershock;Aftershock;;\nlimitless-concert;12023;ywzj_midi;永无止境: 音乐会;Limitless Concert;\nrealm-rpg-dragon-wyrms;12024;realmrpg_wyrms;Realm RPG: Dragon Wyrms;;\nprehistoric-nature-triassic-dimension;12025;pntriassic;Prehistoric Nature Triassic Dimension;;\nflower-power;12026;flowerpower;Flower Power;;\nvr-combat;12027;vr-combat;VR Combat;;\nembeddium;12028;embeddium,rubidium;Embeddium;;\nno-arrows-in-you;12029;naiy;No Arrows In You!;;\n;12030;unicopia;奇幻小马国;Unicopia;\nryans-zombies;12031;kevin_trophy;Ryan's Zombies;;\nlights-out-2;12032;lightsout;Lights Out 2;;\nleafper-creeping-and-cute;12033;leafper_mod;Leafper: creeping and cute;;\nthe-john-mod-reborn;12034;john_mod_reborn;The John Reborn;;\n;12035;fas;ForceAccessServer;;FAS\napothic-attributes;12036;attributeslib;Apothic Attributes (AttributesLib);;\n;12037;random_block;随机方块;Random Block;\n;12038;21points;21点;21 Points;21P\nwall-jump-txf;12039;walljump;Wall-Jump TXF;;\n;12040;fastmove;FastMove - Parkour MovementSinytra Connector;;\n;12041;genshincraft;原的世界;Genshin Craft;\nand-there-was-only-one-bed;12042;and_there_was_only_one_bed;And There Was Only One Bed;;\nhex;12043;hex;Hex;;\nlegend-of-dreemland-drakonboos;12044;dragon_bossfight;Legends Of DreamLand-DrakonBoss;;\nhanew-village;12045;hanew_village_mod;Hanew village : the little dragon's village;;\nbattle-allays;12046;battle_allays;Battle Allay's!;;\n;12047;lootbeams;LootBeams Fabric Updated;;\nstyled-chat;12048;styledchat;Styled Chat;;\ninfiniteinvo;12049;infiniteinvo;InfiniteInvo;;\nplayerex-reimagine;12050;toybox;PlayerEx - Reimagined;;\nstartup-timer;12051;startuptimer;Startup Timer;;\ncc-mb;12052;cc_mb;纸带八音盒;Music Box;\noryls-adventures;12053;oryls_adventure;Oryl's Adventures;;\nelden-craft;12054;elden_craft;Elden Craft;;\nruok;12055;ruokmod;钌;RuOK;\ndeep-dark-regrowth;12056;deep_dark_regrowth;Deep Dark: Regrowth;;\ntextrues-embeddium-options;12057;textrues_embeddium_options;TexTrue 的 Embeddium 视频界面;TexTrue's Embeddium Options;TEO\nsilent-mine;12058;silent_mine;Silent Mine;;\nheadhunter-mod;12059;headhunter_mod;Shadow's Formidable Foes: The HeadHunter;;\nyungs-better-jungle-temples;12060;betterjungletemples;YUNG的丛林神庙优化;YUNG's Better Jungle Temples;\nforlorn-beasts;12061;forlorn_beasts;Forlorn Beasts;;\nharpiccore-mod;12062;harpic;HarpicCore Eexploration;;\npoonggis-add-more-bosses;12063;poonggis_boss;Various bosses addition;;\nrottenmod;12064;rottenmod;FleshEmpire;;\nsimpletweaker;12065;simpletweaker;SimpleTweaker;;ST\n;12066;nospawningonice;No Spawning on Ice Highways;;\n;12067;pebbles;石子;Pebbles;\nzens-mmorpg;12068;zens_mmorpg;Zen's MMORPG;;\n;12069;chp,crosshairpatch;自定义准心补丁;Custom Crosshair Core Patch;CCCP\nenchantment-lore;12070;enchantment_lore;Enchantment Lore;;\nzenrecipereload;12071;zenrecipereloading;配方热重载;ZenRecipeReload;\nlovable-chest;12072;maus_chester;The Lovable Chest;;\ncritters-cryptids;12073;critters_and_cryptids;Critters & Cryptids;;\ndeathquotes-death-quotes;12074;deathquotes;DeathQuotes;;\nsaphyre;12075;saphyre;Saphyre;;\n;12076;weblocks;WEBlocks;;\ngameblocks;12077;gameblocks;GameBlocks;;\ninitial-house;12078;initial_house;初始房屋;Initial House;IH\nlivingchest;12079;livingchest;Living Chest;;\n;12080;mr_ships;Ships;;\nskibidi-toilet-stevebidop;12081;skibidi_toilet__stevedidop;Skibidi Toilet - Stevedidop;;\nbetter-impaling-fabric;12082;betterimpaling;Better Impaling (Fabric);;\nvanadium-mod;12083;vanadium;钒;Vanadium;\nloginar-storage;12084;loginar;Loginar Storage;;\njewelry;12085;jewelry;Jewelry (RPG Series);;\nelytra-drag;12086;elytradrag;Elytra Drag;;\nnight-vision-flash-be-gone;12087;nightvisionflashbegone;Night Vision flash be gone;;\n;12088;better_creepers;Better Creepers;;\nancient-return;12089;ancient_return;Ancient Return;;\nallayed;12090;allayed;Allayed;;\nhonkai-star-rail-12;12091;starrail12;星铁12;Honkai Star Rail 12;\ntorchflower-relit;12092;torchflowerrelit;Torchflower: Relit;;\nfexs-random-stuff-mod;12093;frsm;FRSM - Fex's Random Stuff Mod;;FRSM\nk9;12094;clownpierce_mod;K9;;\nthe-digimod-beta;12095;thedigimod;方块数码宝贝;The Digimod;\nex-deorum;12096;exdeorum;无中生有：神赐;Ex Deorum;\nentity-detectors;12097;entitydetectors;Entity Detectors;;\n;12098;horsearmorstandmod;Horse Armor Stand;;\ndecorative-blocks-fork;12099;decorative_blocks;装饰方块 (分叉版);Decorative Blocks (Fork);\nabove-and-below;12100;above_and_below;Above and Below;;\n;12101;ppcore;PPcore;;\nfreeze-it-and-heat-it;12102;fiahi;冷汗：食物冷冻与加热;Freeze It, and Heat It!;FIAHI\n;12103;keybindsgalore;KeybindsGalore 1.20.x (HVB007);;\nnever-full;12104;alwayshungry;Never Full;;\nnemos-blooming-blossom;12105;nemos-blooming-blossom,bloomingblossom;Nemo's Blooming Blossom;;\nlegacies-and-legends;12106;legacies_and_legends;Legacies and Legends;;\n;12107;better_trees;Better Trees;;\n;12108;travel,mr_villager_transportation;Villager Transportation;;\nyour-log-saw-something-last-night;12109;ylssln;Your Log Saw Something Last Night;;YLSSLN\ntubes-reloaded;12110;tubesreloaded;Tubes Reloaded;;\n;12111;romantictp;Romantic Tp;;\n;12112;Dimension_Items,out_of_table;布响丸啦！[维度综合禁用];Out of table;OT\nnatures-spirit;12113;natures_spirit;自然之灵;Nature's Spirit;\n;12114;natrium;Natrium;;\nbossominium;12115;bossominium;Bossominium;;\n;12116;sort_extension;Sort Extension;;\nhostile-water-monsters;12117;hostilewatermobs;Hostile Water Monsters;;\nslabs-layers;12118;moreslabs;Slabs & Layers+;;\nhistory-of-middle-earth-legacy;12119;lotrta;中洲历史：传承;History of Middle-earth: Legacy;HoME\nhostile-humans;12120;hostile_humans;Hostile Humans;;\nclassic-and-simple-status-bars;12121;classicandsimplestatusbars;经典且简易的状态栏;Classic and simple status bars;CSSB\n;12122;turtlechargingstation;Turtle Charging Station;;\nyour-reputation;12123;reputation;Your Reputation;;\nfuturediary;12124;futurediary;未来日记;FutureDiary;FD\ninfiniverse;12125;infiniverse;Infiniverse;;\nuse-item-on-block-event;12126;useitemonblockevent;Use Item on Block Event;;\ngenshin-instruments;12127;genshinstrument;Genshin Instruments;;\ngenshin-elementreactions;12128;genshinelement;Genshin : ElementReactions;;\nchatshot;12129;chatshot;ChatShot;;\ncropchance;12130;cropchance;IC2作物信息;CropChance;\n;12131;boxplusplus;BoxPlusPlus;;\nfreelook-fabric;12132;freelook;Freelook;;\nrespawn;12133;respawn;Respawn;;\ninfiniverse-utils;12134;infiniverse_utils;Infiniverse Utils;;\ncoreextensions;12135;coreextensions;核心拓展;CoreExtensions;CeX\n;12136;fastitems;Fast Items;;\ntoad-terror;12137;toadterror;Toad Terror;;\nmodern-industrialization-sound-addon;12138;mi_sound_addon;Modern Industrialization Sound Addon;;\nachievements-optimizer;12139;achiopt;Achievements Optimizer;;\nautomessage;12140;automessage;AutoMessage;;\nftb-quests-optimizer;12141;ftbquestsoptimizer,ftbqoptimizer;FTB Quests Optimizer;;\ngfarm;12142;gfarm;GFarm;;\nryoamiclights;12143;ryoamiclights;RyoamicLights;;RDL\npatchouli-rofl-edition;12144;;Patchouli ROFL Edition;;\nalfheim-lighting-engine;12145;alfheim;Alfheim Lighting Engine;;\neven-more-instruments;12146;evenmoreinstruments;Even More Instruments!;;\nmore-red-computercraft-integration;12147;morered_computercraft_integration;More Red Computercraft Integration;;\n;12148;pineapple_coffee;凤梨咖啡;Pineapple Coffee;PC\nno-invincibility-frames;12149;noinvframes;No Invincibility Frames;;\nmoderncreator;12150;moderncreater;Modern Creator;;\nbetter-questing-unofficial;12151;betterquesting;更好的任务非官方版;Better Questing Unofficial;BQU\np455w0rds-ae2-wireless-terminals-patch;12152;p455w0rdwt_patch;p455w0rd's AE2 Wireless Terminals Patch;;\nspoiledz;12153;spoiledz;SpoiledZ;;\nboss-music-mod;12154;bossthemes;♪ Boss Music Mod ♪;;\nyippee;12155;yippee;Yippee;;\nfriends-forever;12156;friendsforever;永恒的伙伴;Friends Forever;\nde-extinction-mod;12157;deextinction;De-Extinction Mod;;\ncustom-world-generation;12158;custom_worldgen;自定义世界生成;Custom World Generation;CWG\ncataclysm-tweaks;12159;cataclysmtweaks;Cataclysm Tweaks;;\neuphoria-patches;12160;euphoria_patcher;Euphoria Patches / Euphoria Patcher;;EP\npoke-ball;12161;pokeballhv;精灵球;pokeballhv;PBHV\naquamirae-mod-music;12162;aquamirae_music;♬ Aquamirae - Boss Music Tweaks ♬;;\ndeathmatch;12163;genshin_weapons;Deathmatch: Genshin Impact Weapons;;\n;12164;animatimc,smoothscroll;平滑滚动;Smooth Scrolling;\ntoad-terror-battle-music;12165;;♪ Toad Terror Battle Music ♪;;\ncalamity-mod-astral-infection-recreated-in;12166;;\"Calamity Mod: Astral Infection... recreated in Minecraft\" MUSIC EXTENSION;;\nabyssal-guardian-boss-datapack-music;12167;;Abyssal Guardian Boss Datapack MUSIC EXTENSION;;\nfusion;12168;fusion;Fusion;;\nmachines;12169;simple_machines;Machines;;\ntenms-discs;12170;tenms_discs;T_en_M's Discs;;\nnetherrocks;12171;netherrocks;Netherrocks;;\ninvasion-code-red;12172;invasion_code_red;Invasion Code Red;;\nefw-forgotten-place;12173;efw;EFW: Forgotten Place;;\n;12174;vulkanite;Vulkanite;;\nyungs-better-end-island;12175;betterendisland;YUNG 的末地岛屿优化;YUNG's Better End Island;\n;12176;;机械动力搅拌配方拓展;create mixing addition;CMA\nbellsandwhistles;12177;bellsandwhistles;Create: Bells & Whistles;;\ndaniinatorul;12178;musketmod;black powder weapons / guns;;\nuuid-offline;12179;uuidoffline;UUID offline;;\nvanilla-cubed;12180;vanilla_cubed;原版立方;Vanilla Cubed;\nrepurposed-structures-all-bark-all-bite-compat;12181;repurposed_structures_all_bark_all_bite_compat;结构变体：All Bark, All Bite Compat 兼容数据包;Repurposed Structures - All Bark, All Bite Compat Datapack;\nrepurposed-structures-monobank-compat;12182;;结构变体：Monobank 兼容数据包;Repurposed Structures - Monobank Compat Datapack;\nrepurposed-structures-wares-compat;12183;repurposed_structures_biome_wares_compat;结构变体：Wares 兼容数据包;Repurposed Structures - Wares Compat Datapack;\nrepurposed-structures-paladins-preists-compat;12184;paladins;结构变体：Paladins & Preists 兼容数据包;Repurposed Structures - Paladins & Preists Compat Datapack;\nrepurposed-structures-gazebo-compat;12185;;结构变体：Gazebo 兼容数据包;Repurposed Structures - Gazebo Compat Datapack;\nlonger-chat-history;12186;longerchathistory;Longer Chat History;;\n;12187;secret-spectator;Secret Spectator;;\nshoot-glass;12188;shootglass;Shoot Glass;;\nmodification-of-critical-hit;12189;modification_of_critical_hit,modification-of-critical-hit;暴击改革;Modification Of Critical Hit;\nimproved-damage-framework;12190;idf;Improved Damage Framework;;IDF\n;12191;shulkerplus;Shulker+;;\nbiome-tag-villagers;12192;biome_tag_villagers;Biome Tag Villagers;;\ncombat-bash;12193;combat_bash;Combat Bash;;\n;12194;simpleprivatechest;SimplePrivateChest;;\nmarbleds-villager-hats;12195;marbleds_villager_hats;Marbled's Villager Hats;;\n;12196;mm;Manningham Mills;Fabric ASM;MM\nconnector-extras;12197;connectorextras;Connector Extras;;\nakkamaddis-simple-tungsten;12198;simpletungsten;Simple Tungsten;;\njust-enough-beacons-reforged;12199;just_enough_beacons;Just Enough Beacons Reforged;;\n;12200;mr_dusk_autumnsworldgen;Dusk Autumns Worldgen;;\nsimple-cobalt;12201;simplecobalt;Simple Cobalt;;\nakkamaddis-ashenwheat;12202;ashenwheat;Ashenwheat;;\nprehistoriclegacy;12203;prehistoric_legacy;Prehistoric Legacy;;\nredyed;12204;redyed;Redyed;;\nenchantmentdisabler;12205;enchantmentdisabler;Enchantment Disabler;;\nminers-glasses;12206;miners_glasses;矿工眼镜;Miner's Glasses;\nrelics-and-alchemy;12207;relics_and_alchemy;Relics And Alchemy;;\n;12208;greate;Greate;;\nadvanced-world-selection;12209;WorldSelectionAdvanced;Advanced World Selection;;\naccelerated-decay;12210;accelerateddecay;Accelerated Decay;;\n;12211;mr_svm_jojo;JoJo Stands;;\nzots-only-silver;12212;onlysilver;Only Silver;;\niceandfire-rlcraft;12213;iceandfire;冰火传说-RLCraft版;I&F：RLCraft Edition;\nbqutweaker;12214;bqutweaker;Better Questing Unofficial Tweaker;;BQUTweaker\n;12215;discjockey;碟机;Disc Jockey;DJ\nthe-arbiter;12216;the_arbiter;调停者;The Arbiter;\nbuilders-construct;12217;buildersconstruct;Builders' Construct;;\n;12218;fix_conflict_arclight;机械动力火车实体冲突修复;Fix Create Trains Conflict On Arclight;FCTA\ntimelessivy;12219;timeless_ivy;永恒常春藤;TimelessIvy;\nakkamaddis-hadite-coal;12220;haditecoal;Hadite Coal;;\n;12221;tin_tea_tech;锡茶科技;TinTeaTech;3T\nakkamaddis-arsenic-and-lace;12222;ArsenicAndLace;Arsenic and Lace;;\n;12223;borderlessfullscreen;无边框最大化窗口;BorderlessFullScreen;BFS\nxiyus-exchanted-book;12224;enchanted_book;饩雨的附魔书;Xiyu's Enchanted Book;\nla-abuelita;12225;abuelita;La abuelita;;\nrotted-forge;12226;rotted;Rotted;;\nrustic-furniture-compat;12227;rusticcompat;Rustic Furniture Compat;;\n;12228;norealmsbutton;Realms按钮移除;No Realms Button;\n;12229;benchantments;BEnchantments;;\nepic-fight-indestructible;12230;indestructible;坚不可摧;Epic Fight - Indestructible;\n;12231;set_bonus;Set Bonus (TeamPancake);;\n;12232;chat_bar_time_display;聊天栏时间显示 (TeamPancake 版);When Was That Again（TeamPancke）;\n;12233;;逃离塔块夫-重铸;Escape From Tarcraft Reforged;EFTR\n;12234;;右键文本清除 (TeamPancake 版);Right Click Clear（TeamPancake）;\ncatwalks-llc;12235;catwalksinc;Catwalks LLC.;;\ndivine-quest-rpg;12236;tales_of_sunnrya,divine_quest;Tales of Sunnrya / Divine Quest RPG;;\n;12237;carpet-org-addition;Carpet Org Addition;;\nsimply-headshots;12238;simplyheadshots;Simply Headshots;;\nrandom-structure-land;12239;random_structure_land;Random Structure Land;;\nalmost-vanilla;12240;almost_vanilla;Almost Vanilla;;\nrandom-mobs-land;12241;random_mobs_land;Random Mobs Land;;\nclean-swing-through-grass;12242;cleanswing;Clean Swing Through Grass;;\nshrug-it-off-stb-edition;12243;;Shrug it Off - STB Edition;;\nenchantsmith;12244;enchantsmith;Enchantsmith;;\nsnifferent;12245;snifferent;Snifferent;;\njei-recipe-history;12246;jeirecipehistory;JEI Recipe History;;\n;12247;yeet;Yeet;;\nmacaws-paths-and-pavings;12248;macaws_paths_and_pavings,mcwpaths;Macaw 的小径和路面;Macaw's Paths and Pavings;\nchiseled-bookshelf-visualizer;12249;;Chiseled Bookshelf Visualizer;;\nelytra-skins;12250;elytra_skins;Elytra Skins;;\nmcv-biome-makeover-lieonlion;12251;mcv_biome_makeover_lieonlion;More Chest Variants - Biome Makeover;;\nmore-chest-variants-lieonlion;12252;more_chest_variants_lieonlion,lolmcv;More Chests Variants;;MCV\nmore-crafting-tables-mct;12253;more_crafting_tables_lieonlion,lolmct;More Crafting Tables;;MCT\n;12254;;Cobblemon Quests;;\nakkamaddis-classical-alchemy;12255;classicalalchemy;Classical Alchemy;;\n;12256;bigpony;彩虹大马;Big Pony;\nqraftyfied;12257;qraftyfied,mr_qraftyfied;Qraftyfied / qraftyfied: STRUCTURES;;\ntheft-mod;12258;theft;Theft;;\nakkamaddis-golden-glitter;12259;goldenglitter;Golden Glitter;;\n;12260;obsidianbucket;ObsidianBucket;;\nenhanced-annihilation-planes;12261;enhanced_annihilation_planes;Enhanced Annihilation Planes;;\nakkamaddis-simple-cthon;12262;cthon;Simple Cthon;;\n;12263;headshurttoo;Heads Hurt Too!;;\npooping-animals;12264;pooping_animals;Pooping Animals;;\ngood-boy;12265;goodboy;Good Boy;;\nnethers-overhaul;12266;netheroverhaul;Nether's Overhaul;;\nbetter-battle-towers;12267;battletowers;战斗高塔，启动！;Better Battle Towers;BBT\n;12268;gregicality_kids;GCY：儿童快乐🎉;Gregicality Kids;GCYK\n;12269;the_beasts;The Beasts;;\n;12270;axiom;Axiom;;\n;12271;elib;ELib;;EL\nlilac;12272;lilac;Lib Lapis Core;;LiLaC\ncolorful-enchantment;12273;custom_colorful_enchantment;自定义附魔光效;Custom Colorful Enchantment;\nrusticwitchcraft;12274;rusticwitchcraft;Rustic Witchcraft;;\n;12275;unif.logger;UniF-Logger;;\nsterling-and-black;12276;sterlingblack;Sterling and Black;;\nthe-devourer-of-skulls-datapack;12277;;The Devourer of Skulls 『𝔹𝕠𝕤𝕤 𝔽𝕚𝕘𝕙𝕥』;;\n;12278;birds_nests;鸟巢 (TeamPancake 版);Birds Nests(Team Pancake);\nboss-and-magic-raid-compat;12279;;Boss And Magic: Raid Compat;;\nanimated-mobs;12280;animatedmobsmod;Boss And Magic / AME's Mobs / Animated Mobs;;\n;12281;GTQTCore;量子跃迁：纠缠核心;GTQTcore;GTC\nbacklytra;12282;backlytra;鞘翅;Backlytra;\nneapolitans-quality;12283;neapolitansquality;Neapolitan's Quality;;\nrustic-bop-woods;12284;rusticbopwoods;Rustic BOP Woods;;\nhedgehoggery;12285;hedgehogs;Hedgehoggery;;\nyawctys-giant-squids;12286;giantsquid;Yawcty's Giant Squids;;\nno-durability;12287;nodurability;No Durability;;\npotion-of-armor;12288;potionofarmor;Potion of Armor;;\n;12289;wmf;Weapons, magic and food;;WMF\ntenebrous-lands;12290;tenebrous_lands;Tenebrous Lands;;\nsuper-better-grass;12291;superbettergrass;SuperBetterGrass;;\nconfigurable-boss-bars;12292;configurablebossbars;Configurable Boss Bars;;\nnuclearcraft-neoteric;12293;nuclearcraft;NuclearCraft: Neoteric;;\nbeastly-beacons;12294;beastly_beacons;Beastly Beacons;;\ncreeplets;12295;creeplets;Creeplets;;\nthermoo;12296;Thermoo,thermoo;Thermoo;;\nrestapi;12297;restapi;RestApi;;\n;12298;advanced-ui-scale;Advanced UI Scaling;;\n;12299;cfb,umines;Unmovable Mines / Chemistry For Blockheads;;umines\nhealth-disease;12300;health_and_disease;健康&疾病;health&disease;HAD\n;12301;ThermalExpansion;热力膨胀1;Thermal Expansion 1;TE1\n;12302;ThermalExpansion;热力膨胀2;Thermal Expansion 2;TE2\n;12303;hdskins;HD Skins;;\n;12304;holk_bell;召集之钟;Hook Bell;\nsundew;12305;sundew;Sundew;;\nundead-boxers;12306;undead_boxers;Undead boxers;;\naesthetics;12307;aesthetics;Aesthetics;;\nakkamaddis-additions;12308;steelyglint;Steely Glint;;\n;12309;harvestwalker;丰收行者/秧余稻足;Harvest Walker;\nchinese-festivals;12310;chinesefestivals,chinese_festivals;中国节;Chinese Festivals;\nultramarine;12311;ultramarine;群青;Ultramarine;\n;12312;;附魔编辑台;Enchanting Edit Table;EET\n;12313;cargo;Cargo;;\nwhen-dungeons-arise-loot-tweaks;12314;;地牢浮现之时 - 战利品修改;When Dungeons Arise - Loot Tweaks;\ndragon-nest-forge-fabric;12315;dragon_nest;Dragon Nest;;\ngeode-creatures;12316;cave_beasts;geode creatures;;\nharaldsbetterstructures;12317;haraldbetterstructures;HaraldsBetterStructures;;\nsquake;12318;squake;Squake;;\nae2-uel-extended;12319;appliedenergistics2;应用能源2非官方拓展;AE2 Unofficial Extended;\nterraria-calamity-mod;12320;tcal;泰拉瑞亚灾厄;Terraria Calamity;\ndarkrtp-rtp-random-teleport;12321;darkrtp;DarkRTP - RTP Random Teleport;;\ncute-bears;12322;cutebear;Cute Bears;;\nadditional-fruits;12323;additional_fruits;Additional Fruits;;\nbigger-better-end-cities;12324;bigendcitiesmod;更大更好的末地城;Bigger Better End Cities;\n;12325;some_peripherals;Some Peripherals;;\nreden;12326;reden;Reden;;\nsparse-structures;12327;sparsestructures;Sparse Structures;;\npersistentcooldowns;12328;persistentcooldowns;物品冷却持久化;Persistent Cooldowns;\n;12329;command_extractor;Command Extractor;;\nadvancement-based-spawning;12330;abs;Advancement Based Spawning;;\n;12331;npop;我讨厌权限修饰符-增强;NoPrivateOrProtected-Plus;NPOPP\nheroic-armory-legacy;12332;heroic;Heroic Armory Legacy;;\nyungs-menu-tweaks;12333;yungsmenutweaks;YUNG 的菜单调整;YUNG's Menu Tweaks;\nentity-distance-1-12-2;12334;entitydistance;实体距离;Entity Distance;\ncoolesthardcore;12335;hardcoremode;Coolest Hardcore;;\ntaxcastlepillager;12336;taxcp;Tax' Castle Pillager;;\npesky-pirates;12337;peskypirates;Pesky Pirates (Anti-Piracy);;\nsubterrestrial;12338;subterrestrial;Subterrestrial;;\nillusioners-tower;12339;illusioners_tower;Illusioner's Tower;;\nbad-npcs;12340;bad_npcs;Bad NPCs;;\ndarkfeather-golden-feather;12341;darkfeather;Golden Feather;;\ndarkspawn-teleport-to-spawn;12342;darkspawn;DarkSpawn - Teleport to Spawn or Lobby;;\ndarksmelting-smelt-armor-and-tools;12343;darksmelting;DarkSmelting - Smelt Armor, Tools & Weapons;;\nfast-build;12344;fastbuildyaztatili;Fast Build;;\ndigi-applimobs;12345;digi_applimobs;数码宝贝宇宙应用怪兽;Digi Applimobs;DgA\nduckies;12346;duckies;Duckies;;\ndimension-crystals;12347;dimension_crystals;Dimension Crystals;;\nmechanical-crafting;12348;mech_crafting;Mechanical Crafting;;\n;12349;enhancedbookwriting;Enhanced Book Writing;;\nplayer-events;12350;player_events;Player Events;;\nmushlings;12351;mushlings;Mushlings!;;\nsky-tribes;12352;sky_tribes;Sky Tribes;;\n;12353;strangeore;奇奇怪怪的矿石;Strangeore;\nrecipe-enhancements;12354;recipe_enhancements;Recipe Enhancements;;RE\noceanopolis;12355;oceanopolis;Oceanopolis;;\n;12356;miod;内存增减;;MIOD\npumpkillagers-quest;12357;pumpkillagersquest;Pumpkillager's Quest;;\ncryogenic-construct-heavily-incomplete;12358;;Cryogenic Construct;;\ncumulomenace-boss-fight-datapack;12359;;Cumulomenace 『Boss Fight』;;\nhydro-fury-datapack;12360;;Hydro Fury!;;\nwaterlogged-chests;12361;waterlogged_chests;Waterlogged Chests;;\nrealm-rpg-fallen-adventurers;12362;realmrpg_skeletons;Realm RPG: Fallen Adventurers;;\nuseful-bats;12363;usefulbats;Colds: Useful Bats;;\ngoat-food;12364;goat_meat_mod;Goat Food;;\nmobile-beacon-1-18-forge;12365;mobile_beacon;Mobile Beacons;;\ndurabuilt;12366;durabuilt;Durabuilt;;\ncavemans;12367;cavemans;Cavemans!;;\nballoon-box;12368;balloon_box;Balloon Box;;\ncampfire-resting;12369;campfiresleeper;Campfire Resting;;\nfizzle;12370;fizzle;Fizzle;;\nfoamflower;12371;foamflower;Foamflower;;\ngrimoire-api;12372;grimoire;Grimoire API;;\ncristel-lib;12373;cristellib;Cristel Lib;;\nmore-sleeps;12374;more_sleeps,sleepquality;睡眠质量 / 更多睡眠;Sleep Quality / More Sleeps;\no-coracao-da-selva;12375;cds;O Coração da Selva;;\ncreative-experience;12376;creative_experience;Creative Experience;;\nmore-than-50;12377;morethan50;More Than 50;;\nunderground-jungle;12378;underground_jungle;Underground Jungle;;\nforge-soul-like-armors-reworked;12379;soul_like_armors_reworked;soul like armors reworked;;\nmeds-and-herbs;12380;meds_and_herbs;Meds and Herbs;;\nsoul-like-monsters-epic-fight-addon;12381;soul_like_monsters_epic_fight_addon;soul like monsters: epic fight addon;;\nsouls-like-parry;12382;souls_like_parry;Souls-like Parry;;\ntcdcommons;12383;tcdcommons;TCDCommons API;;TCDC\n;12384;pinyined-masa;pinyined-masa;;\nautotranslation;12385;autotranslation;自动翻译;AutoTranslation;\nvillages-and-pillages;12386;villagesandpillages;Villages&Pillages;;\nnohurtcam-plus;12387;nohurtcamplus;NoHurtCamPlus / NoHurtCam+;;\ndarker-souls;12388;darker_souls;Darker Souls;;\n;12389;qraftys_japanese_villages;qrafty's Japanese Villages;;\n;12390;spooky_bats;Spooky Bats;;\nswutms-extra-dungeons;12391;swutmextradungeons;SWUTM's Extra Dungeons;;\nswutms-extra-desert;12392;swutms_extra_desert_;SWUTM's Extra Desert;;\n;12393;regeneratelootmod;Refill Chests;;\n;12394;asthumiphss_maples;Asthumiphs's 枫树;Asthumiphs's Maples;\n;12395;cookeymod;CookeyMod;;\n;12396;useful_brush;Useful Brush;;\n;12397;;Horse Spawn;;\ntw-resurrection;12398;tw_resurrection;Twilight Forest Bosses Resurrection;;\n;12399;dice;Dice;;\nany-lanterns;12400;any_lanterns;Any Lanterns!;;\nhorse-tack;12401;horse_tack;Horse Tack;;\ndarkmobs-increase-mob-difficulty;12402;darkmobs;DarkMobs - Increase Mob Difficulty;;\n;12403;apollo_the_parrot;Apollo the Parrot;;\nmob-variants;12404;frycmobvariants;Mob Variants;;\ngalvanization;12405;galvanization,necromantic_expansion;Galvanization / Necromantic Expansion;;\nbiome-grass-available;12406;biome_grass_available;Biome Grass Available;;\npigment;12407;pigment;Pigment;;\nsunrise-sunset-audio;12408;sunrise_sunset_audio;Sunrise & Sunset Audio;;SSA\nhalloween-nighmare;12409;halloween_nighmare;Halloween Nightmare;;\nlookin-sharp;12410;lookinsharp;锋芒毕露;Lookin' sharp;\nhalloween-luckyblocks;12411;halloween_luckyblock;Halloween LuckyBlocks;;\nspooky-halloween;12412;spookyhalloween;Spooky Halloween!;;\nisle-of-berk-variant-loader;12413;Isle of Berk Variant Loader;Isle of Berk Variant Loader;;\nspooky-arms;12414;spookyarms;Spooky Arms;;\nqraftys-end-villages;12415;mr_qraftys_endvillages,qraftys_endvillages;qrafty's End Villages;;\nbed-is-not-too-far-away;12416;bedisnottoofaraway;Bed Is Not Too Far Away;;\n;12417;enderman_tweaks;Enderman Tweaks;;\n;12418;mr_qraftys_bamboovillages;qrafty's Bamboo Villages;;\n;12419;mr_qraftys_junglevillages;qrafty's Jungle Villages;;\n;12420;oceanic;Oceanic;;\ndecoration-and-furniture;12421;decoration_mod;Decoration and Furniture;;\n;12422;mr_nyctophobia_dread;Nyctophobia Dread;;\nformations;12423;formations;Formations;;\nformations-overworld;12424;formations_overworld,formationsoverworld;Formations Overworld;;\nformations-nether;12425;formations_nether,formationsnether;Formations Nether;;\nhalloween-spirit;12426;halloween_spirit;Halloween Spirit;;\nglitches-halloween;12427;glitches_halloween;Glitches Halloween;;\nrei-collapsible-entries;12428;rei_collapsible_entries;REI Collapsible Entries QOL;;\n;12429;mr_brain_overbrawn;Brain Over Brawn;;\n;12430;mr_gardeners_dream;Gardener's Dream;;\nkubevs;12431;kubevs;KubeVS;;\nhalloween-expansion-food;12433;halloween_xpansion;Halloween Expansion - Food!;;\nfishy-business-more-fish;12434;fishybusiness;Fishy Business;;\n;12435;mr_ownership_improved;Ownership Improved;;\n;12436;parry;Parry;;\ncrab-mob-vote-2023;12437;crab;CRAB Mob Vote 2023;;\nbanner-flags;12438;banner_flags,mr_banner_flags;Banner Flags;;\n;12439;mr_banner_bedsheets;Banner Bedsheets;;\nenderswords;12440;enderswords;Enderswords;;\n;12441;re;随机附魔;RandomEnchant;RE\nlemons-halloween;12442;lemons_halloween_1_16_5;Lemon's Halloween;;\nmoon-reborn;12443;moon_reborn_mod;Moon Reborn;;\nthe-witching-hour;12444;witching_hour;The Witching Hour;;\nhallows-eve;12445;hallows_eve;Hallow's Eve;;\n;12446;chromaworldborder;Chroma World Border;;\njust-sword-blocking;12447;justswordblocking;Just Sword Blocking;;\n;12448;;Abandoned & Ruin Structures;;\nunifytags;12449;;UnifyTags;;\n;12450;blissful_butterflies;Blissful Butterflies;;\nmltpojectmonster;12451;mltpojectmonster;銘洛天的怪物增强;MLTPojectMonster;MLTPM\n;12452;relicsofthesky;空岛遗物;Relics of the Sky;\n;12453;hy,hiddenyears;隐藏之年²;Hidden Years²;HY\nae2-stuff-extended;12454;ae2stuff;AE2 Stuff Unofficial: Extended;;\nbutchers-delight-foods;12455;butchersdelightfoods;Butcher's Delight Foods;;\nbetter-falling;12456;better_falling;Better Falling;;\ndmcloot;12457;dmcloot;DMCLoot;;\nsimply-stacked-dimensions;12458;simplystacked;Simply Stacked Dimensions;;\n;12459;what_are_you_voting_for_2023;What Are You Voting For? 2023;;\n;12460;what_are_you_voting_for_2022;What Did You Vote For? 2022: Sniffer, Rascal and Tuff Golem;;\nimmersive-electrocution;12461;immersiveelectrocution;Immersive Electrocution;;\nadditional-compression;12462;additionalcompression;Additional Compression;;\nteam-projecte;12463;teamprojecte;等价交换团队;Team ProjectE;\ntorcherino-community-edition;12464;torcherino;加速火把社区版;Torcherino Community Edition;\n;12465;forgivingmovecheck;仁慈的移动检查;ForgivingMoveCheck;\nenchantgiver;12466;enchant_giver;EnchantGiver;;\ndiabolical-delights;12467;diabolicaldelights;Diabolical Delights;;\nore-growth;12468;ore_growth;Ore Growth;;\n;12469;real-arrow-tip;Real Arrow Tip;;\ndnt-ancient-city-overhaul;12470;mr_dungeons_andtavernsancientcityoverhaul;DnT Ancient City Overhaul / Dungeons and Taverns Ancient City Overhaul;;\nrepurposed-structures-better-jungle-temples-compat;12471;repurposed_structures_yungs_better_jungle_temples_compat;结构变体：YUNG的丛林神庙优化兼容数据包;Repurposed Structures - Better Jungle Temples Compat;\ncropariaplus;12472;cropariaplus;CropariaPlus;;\nsoularia;12473;soularia;Soularia;;\nstemaria;12474;stemaria;Stemaria;;\nspelunkers-charm-ii;12475;spelunkers_charm;Lucky's Spelunker's Charm II;;\nstructure-essentials-forge-fabric;12476;structureessentials;Structure Essentials;;\n;12477;mr_dungeons_andtavernspillageroutpostrework;Dungeons and Taverns Pillager Outpost Rework;;\ntiny-pals;12478;tiny_pals;Tiny Pals;;\n;12479;amethystcraft;紫水晶工艺;Amethyst Craft;AC\nweapons-of-miracles-epicfight;12480;wom;奇迹武器;Weapons of Miracles;WOM\n;12481;;雪球菜单懒人版;;\n;12482;hsrpaths;Honkai: Star Rail - Origins;;\n;12483;rm_portalgun;Rick & Mortys Portal Gun;;\nmcdar;12484;mcdar;我的世界：地下城法器;MC Dungeons Artifacts;MCDAR\ndnt-stronghold-overhaul;12485;mr_dungeons_andtavernsstrongholdrework;Dungeons and Taverns Stronghold Overhaul;;\ndnt-swamp-hut-overhaul;12486;mr_dungeons_andtavernsswamphutrework;Dungeons and Taverns Swamp Hut Rework;;\njaopcagt;12487;jaopca;Just A Ore Processing Compatibility Attempt: GregTech;;JAOPCAGT\nsmidgeon-o-bliss;12488;sob;片刻满足;Smidgeon O' Bliss;\ncable-flux;12489;cableflux;CableFlux;;\njet-and-elias-armors;12490;jet_and_elias_armors;Jet and Elia's Armors;;\n;12491;mr_larger_oreveinsdiamond;Larger Ore Veins: Diamond;;\n;12492;mr_larger_oreveinsnether;Larger Ore Veins: Nether;;\n;12493;mr_larger_oreveinsdeluxe;Larger Ore Veins: Deluxe;;\n;12494;mr_larger_oreveinsclassic;Larger Ore Veins: Classic;;\ndragon-ice;12495;dragonice;Dragon Ice;;\n;12496;;Undefined;;\nabnorlex-and-cryptids-delight;12497;abnorlex_and_cryptids_delight;Abnorlex and Cryptid's Delight;;\ntf2-stuff-reloaded;12498;rafradek_tf2_weapons;军团要塞2重制版;TF2 Stuff - Reloaded;\ntime-in-a-bottle-curio-support;12499;curiotiab;Time in a bottle Curio Support;;\nabsolute-ultracraft;12500;ultracraft;Absolute ULTRACRAFT;;\nmore-layered-blocks;12501;more_layered_blocks;More Layered Blocks;;\nhybrid-aquatic;12502;hybrid-aquatic;混合水生;Hybrid Aquatic;\nspectre-creep;12503;spectr;Spectre Creep;;\nresource-chickens;12504;resourcechickens;资源鸡;Resource Chickens;\n;12505;campfire_resting;休憩营火;Campfire Resting;\nstorage-crate;12506;storage_crate;板条箱;Storage Crate;SC\noculus-particle-fix;12507;oculusparticlefix;Oculus Particle Fix;;\nurkaz-moon-tools;12508;urkazmoontools;Urkaz Moon Tools;;\nthe-midnight-lurker;12509;midnightlurker;The Midnight Lurker;;\nrealm-rpg-imps-demons;12510;realm_rpg_imps_demons,realmrpg_demons;Realm RPG: Imps & Demons;;\nmultiverse;12511;multiverse;Multiverse;;\n;12512;imlib;ImGuiLib;;imlib\n;12513;redstonehooker;偷盗工程师;RedstoneHooker;Rh\nstrangecreatures;12514;strange_creatures;Strange creatures;;\nbovines-and-buttercups;12515;bovines_and_buttercups;Bovines and Buttercups (Mooblooms);;\nmooblooms;12516;mooblooms;Mooblooms;;\nwater-walking-fix;12518;wwfix;Water Walking Fix;;\nwater-walking-fix-forge;12519;wwfix;Water Walking Fix (Forge);;\n;12520;;Chicken Nerf;;\n;12521;;Explosive Breeding;;\nsquished;12522;squished;Squished;;\nrusstales-forge;12523;russtales;RUSSTALES;;\ntrident-alter;12524;ta_c;Trident Alter;;\ndisenchanter;12525;disenchanter;Disenchanter;;\nminecraft-dungeons-artifacts;12526;dungeonsartifacts;Dungeons Artifacts;;\nuniquedungeons;12527;uniquedungeons;Unique Dungeons;;\n;12528;cobbledex;CobbleDex;;\ncobblemon-info;12529;cobblemoninfo;Cobblemon Info;;\nenderman-overhaul;12530;endermanoverhaul;末影人革新;Enderman Overhaul;\ncloak-dagger;12531;cloakanddagger;Cloak and Dagger;;\noworld2create;12532;oworld2create;别创建新世界！;OWorld2Create;OW2C\ndungeons-in-dimensions;12533;dungeons_in_dimensions;Dungeons in Dimensions;;\ninfernal-bugz;12534;infernal_bugz;Infernal BugZ;;\n;12535;gts;Cobblemon GTS;;\ncobblepedia;12536;cobblepedia;Cobblemon Poképedia: Cobblepedia;;\n;12537;;Battle-Bond;;\n;12538;cobblemonintegrations;Cobblemon Integrations;;\nfruitstack-jei-support;12539;fruitstackjeisupport;果栈丰萦JEI支持;Fruitstack Jei Support;FJS\ndungeons-artifacts;12540;dungeonsartifacts;Dungeons & Artifacts;;\nthe-monkey-bomb;12541;monkey_bomb;The Monkey Bomb;;\n;12542;hunt;Cobblemon Hunt;;\nancient-animals;12543;bc;Ancient Animals;;\nbeavery;12544;beavery;Beavery;;\nqraftys-halloween-villages;12545;qraftys_halloween_villages,mr_qraftys_halloweenvillages;qrafty's Halloween Villages;;\nvemerioraptor;12546;vemerioraptor;Vemerioraptor;;\ntechnique-to-kill-thrasher;12547;technique_to_kill_thrasher;毙鲨技;technique to kill thrasher;\nthe-open-sauce-toast-killer;12548;toastkiller;The Open Sauce Toast Killer;;\n;12549;chicken_nerf;Chicken Nerf Reforged;;\nregex-search;12550;re_search;Regex Search;;\nbetter-wake-up-position;12551;better_wake_up_position;更好的起床位置;Better Wake-up Position;BWP\nrailroad-blocks;12552;railroadblocksaddon;Railroad Blocks;;\ncopper-4-beacon;12553;;Copper 4 Beacon;;\nfossilmon;12554;fossilmon;Fossilmon;;\nopack2reload;12555;opack2reload;资源包惰性重载/抑验怠载;0Pack2Reload/OPack2Reload;0P2R/OP2R\nwits;12556;wits;What Is This Structure?;;WITS\n;12557;motleysmusicdiscs;Motley's Music Discs;;\n;12558;mr_various_startingloot;Various Starting Loot;;\nhusks-drop-sand;12559;huskssand;Husks Drop Sand;;\npatched;12560;patched;Patched;;\n;12561;snakewheel_attack;Snakewheel Attack!;;\n;12562;;All mobs from the voting [Minecraft LIVE: 2023];;\n;12563;gt6scan;GT6 Scanner;;\nconfigurable-falls;12564;configurable_falls;Configurable Falls;;\njust-enough-mekanism-multiblocks;12565;jei_mekanism_multiblocks;Just Enough Mekanism Multiblocks;;\nthrowableslimeball;12566;throwable_slimeball;可投掷黏液球/圆液多掷;ThrowableSlimeball;\ndeath-note;12567;deathnote;死亡笔记;Death Note;DN\n;12568;;Frost Wolf Boss Battle [WINNER of Datapack Jam 6];;\nicicle;12569;icicle;Icicle;;\n;12570;;The Multitude Boss Datapack;;\nminecraft-shadowlands-battle-music-datapack;12571;;♪ Shadowlands Battle Music ♪ {Datapack};;\n;12572;;Shadow Lands | Boss Battle;;\nsurface-mushrooms;12573;surfacemushrooms;Surface Mushrooms;;\ndont-void-arrows;12574;dont_void_arrows;Don't void Arrows;;\npotion-cauldron;12575;potioncauldron;Potion Cauldron;;\nhippo-keystrokes;12576;keystrokeshippo;Hippo Keystrokes;;\nspiritcaller-backport;12577;spiritcaller_backport;Spiritcaller Backport;;\n;12578;;The Great Pumpkin |Boss Datapack|;;\nkindled;12579;kindled;Kindled;;\n;12580;;Sculk Hive;;\nmushroom-trees;12581;mushroom_trees;Mushroom Trees;;\n;12582;xkdeco;XK's Deco TeaCon Edition;;\n;12583;skeleton_creeper;骷髅苦力怕;Skeleton Creeper;\nbetter-chunk-loading-forge-fabric;12584;betterchunkloading;Better chunk loading;;\n;12585;;New in Town - Project Zoo addon;;\n;12586;;New in Town - Medieval Castles addon;;\n;12587;;New in Town Official Aesthetic Structures Add-on Pack;;\n;12588;;New in Town - Town Militia addon;;\nmushroomified;12589;mushroomified;Mushroomified;;\nsoul-like-boss-epic-fight-addon;12590;soul_like_boss;soul like boss: epic fight addon;;\nmoderately-enough-effect-descriptions-meed-a-jeed-addon;12591;moderately_enough_effect_desc;Moderately Enough Effect Descriptions;;MEED\nalexs-caves;12592;alexs_caves,alexscaves;Alex 的洞穴;Alex's Caves;\nbow-spam-be-gone;12593;bowspambegone;Bow Spam Be-Gone;;\nlmdcraft;12594;lmdmod;LMDCraft;;\nhardcore-survival;12595;hcs;硬核生存;Hardcore Survival;HCS\n;12596;shanhaicontinent;山海大陆;Mountain Sea and Continent;\n;12597;mr_better_deepslateoredrops;Better Deepslate Ore Drops;;\ndynamic-village;12598;dynamic_village,dynamicvillage;Create: Dynamic Village;;\n;12599;;Even Better Archeology;;\n;12600;;New in Town: Dimensions Expansion Data Pack;;\njammies;12601;jammies;Jammies;;\ndj-villager;12602;djvillager;DJ Villager;;\nars-energistique;12603;arseng;Ars Énergistique;;\nchatimages;12604;chatimage;聊天图片;ChatImages;ci\ncreate-more-drill-heads;12605;createmoredrillheads;机械动力：更多钻头;Create: More Drill Heads;\ncreate-liquid-fuel;12606;createliquidfuel;机械动力：液体燃料;Create: Liquid Fuel;\n;12607;coolarmor;Ray's Cool Armor;;\neeeabs-mobs;12608;eeeabsmobs;EEEAB's Mobs;;EMobs\n;12609;obfuscatedaccessibility;Obfuscated Accessibility;;\nregional-chat;12610;regionalchat;Regional Chat;;\nzenith-attributes;12611;zenith_attributes;Zenith Attributes;;\n;12612;smartygui;SmartyGUI;;\n;12613;chems_eco;Chem的生态;Chem's Eco;eco\ntouhoulittlemaidbe;12614;;车万女仆基岩版;Touhou Little Maid BE;TLMBE\nselectlative;12615;selectlative;选择性成就共享;Selectlative;\nbetter-compressed-blocks;12616;compressedblocks;Better Compressed Blocks;;\nfakerlib;12617;fakerlib;FakerLib;;\npotion-id-packet-fixer;12619;potionidpacketfixer;Potion ID Packet Fixer;;\nomniwand;12620;omniwand;全能魔杖;Omniwand;\n;12621;really_forge;真的锻造！;Really Forge!;RFG\n;12622;;破晓2;Dawn 2;\n;12623;upgradedbread;面包增强;Upgraded Bread;UpB\nkey-binding-patch;12624;key_binding_patch;按键绑定补丁;Key Binding Patch;KBP\npacket-fixer;12625;packetfixer;Packet Fixer;;\nnew-xenchant;12626;xenchant;New XEnchant;;\nmore-brewable-potions;12627;morepotions;More Brewable Potions;;\n;12628;tlauncher_custom_cape_skin,tlskincape;TL Skin and Cape;;\nforge-cit;12629;citresewn;Forge CIT;;\n;12630;diamethyst_golem;Diamethyst Golem;;\nyou-are-seeing-dungeons;12631;youre_seeing_dungeons;You Are Seeing Dungeons🏰;;YASD\n;12632;mobsenhancement;怪物增强附属;Mobs Enhancement Addon;MEA\njust-leveling;12633;justleveling;Just Leveling;;\ncreate-edible-belts;12634;;Create: Edible Belts;;\ntact;12635;tact;Tiny Alex's Caves Tweaks;;TACT\npaimon;12636;paimon;派蒙！;Paimon;\n;12637;cesium;铯;Cesium Storage Format;\nsalvage-furnace;12638;salvage_furnace;Salvage Furnace;;\neager-beavers;12639;beavermod;Eager Beavers;;\nlaendli-transport;12640;laendli_transport;Laendli Transport;;\nwitchs-artifacts;12641;witchs_with_a_use;Witch's Artifacts;;\nthe-pumpkin-challenge;12642;the_pumpkin_challenge;The Pumpkin Challenge;;\nloot-crates-plus;12643;lootboxesplus;Loot+;;\n;12644;Untranslator;未转译者;Untranslator;\nfuji;12645;fuji;藤;Fuji;\nlegendary-origins;12646;legendary_origins;Legendary Origins;;\nflow;12647;flow;Flow;;\nlit-it-up;12648;lititup;Lit It Up;;\nlucky-cat;12649;lucky_cat;招财猫;Lucky Cat;\nall-the-wizard-gear;12650;all_the_wizard_gear,allthewizardgear;All the Wizard Gear;;\nbetter-tools-and-armor;12651;better_tools;Better Tools and Armor;;BT+A\nberrfect;12652;berrfect;完莓无缺;Berrfect;\nmazemanicore;12653;mazemanicore;MazeManicore;;\nuncanney-valley;12654;uncanneyvalley;Uncanney Valley;;\n;12655;cannibal;Cannibal Conundrum;;\n;12656;glasscarpet;Glass Carpet;;\n;12657;qualitysounds;Quality Sounds;;\ndolt-compat;12658;dolt_compat;Compatify / Dolt Compat;;\nembeddium-extension;12659;embeddiumextension;Embeddium Extension;;\nwakes;12660;wakes;Wakes;;\ncreate-immersive-portal-integration;12661;trainportals;Create - Immersive Portal Integration;;\ndangers-in-the-darkness;12662;fear_the_dark;Dangers in the Darkness;;\nspookycraft;12663;spookycraft;SpookyCraft;;\nkelpie;12664;kelpie;Kelpie;;\n;12665;viavanillaplus;ViaVanillaPlus;;\ndead-mans-abyss;12666;bansheenight;Dead Man's Abyss;;DMA\nthe-leagues-of-the-dead;12667;the_leagues_of_the_dead;The Leagues of the Dead;;\ncentered-crosshair;12668;centered-crosshair;Centered Crosshair;;\n;12669;;Create Fabric Sodium Fix;;\nrealm-rpg-creep-crop;12670;realmrpg_creep;Realm RPG: Creep & Crop;;\nfexcraft-common-library;12671;fcl,tmt,frl;Fexcraft Common Library;;FCL\n;12672;platearmour;装备拓展;arrmor and sword;AAS\nx-autofish;12673;autofish;自动钓鱼XPlus版;XPlus AutoFish;\nbefixment;12674;befixment;Befixment;;\nmore-magic;12675;more_magic;More Magic;;\ncheat-command;12676;cheat_command;Cheat Command;;\n;12677;screenshot_organisation;Screenshot Organisation;;\npumpkinpal;12678;pumpkin_pal;Pumpkin Pal;;\n;12679;nice_boat;好船;Nice Boat;NB\njohnnys-biological-notes;12680;johnnys_biological_notes;Johnny的生物笔记;Johnny's biological notes;\nautodrop;12681;autodrop;Autodrop;;\nmutation-rerun;12682;mutation_rerun;Mutation-RERUN;;\n;12683;telekinesis;Telekinesis;;\n;12684;compactspawners;CompactSpawners;;\n;12685;whole_block_smelting;整块冶炼;Whole'block'smelting;Wbs\ncorundum-guardian-reborn;12686;corundum_guardian;Corundum Guardian Reborn;;\nautofire-bow-enchant;12687;autofire_bow_enchant;Instinctive Release;;\n;12688;;Vertical Rails with Ladders!;;\n;12689;the_project_for_the_prevention_of_cruelty_to_animals;动物保护计划;the Project for the Prevention of Cruelty to Animals;PPCA\nrideableravagers;12690;rideableravagers;Rideable Ravagers;;\npassive-searchbar;12691;passivesearchbar;被动搜索栏;Passive SearchBar;\nflower-mimics;12692;flowermimics;Flower Mimics;;\nvegan-delight;12693;vegandelight;纯素乐事;Vegan Delight;\nbloodmooncmsfix-bmcmsfix;12694;;BloodmoonCMSFix;;BMCMSFix\nevoodooers;12695;Evoodooers;Evoodooers;;\ngiant-overhaul;12696;giantoverhaul;Giant Overhaul;;\n;12697;torohealth;ToroHealth Damage Indicators (Updated);;\nboliviamod;12698;boliviamod;BoliviaMod;;\nleawind-third-person;12699;leawind_third_person;Leawind 的第三人称;Leawind's Third Person;\nfabled-weaponry;12700;fabled_weaponry;Fabled Weaponry;;\nlazurite;12701;lazurite;Lazurite;;\nlets-do-nethervinery;12702;nethervinery;下界葡园;Let's Do: NetherVinery;\nshinnyladders;12703;shinnyladders;发光梯子/洇荧登纵;Shinny Ladders;\nmacaws-holidays;12704;macaws_holidays,mcwholidays;Macaw's Holidays;;\nunions-upgrade-scrolls;12705;unions_upgrade_scrolls;Union's Upgrade Scrolls;;\ntorohealth-unofficial;12706;torohealth;ToroHealth Unofficial;;\nrebindallthekeys;12707;rebind_all_the_keys;RebindAllTheKeys;;\npocketwatch2;12708;pocketwatch;Pocketwatch;;\npick-blockstate;12709;pickblockstate;Pick BlockState;;\n;12710;epimorphism;满同态核心;Epimorphism Core;EPCore\noh-my-sherd;12711;ohmysherd;Oh My Sherd;;\narchaeology-banners;12712;archaeology_banners,archaeologybanners;Archaeology Banners;;\nmobvote-2023-crab;12713;crab;Mobvote 2023 - Crab;;\ntotem-curse;12714;totem_curse;Totem Curse;;\n;12715;playtimeremake;游戏时长计数器重置版;Playtime Counter Remake;PCR\nzombies-are-real;12716;zombiesarereal;Zombies Are Real;;\ntimeless-and-classics-guns-lesraisins-append-pack;12717;lesraisinsadd;TaC：绿葡萄附属包;TaC：LesRaisins Append Pack;LRADD\npermanent-chat-logs;12718;permanent-chat-logs;聊天记录保存;Permanent ChatLogs;\nmob-strength;12719;mobstrength;Mob Strength;;\ncatastrophemod;12720;catastrophemod;Catastrophe;;\n;12721;watergenerators;水发电机;Water Generators;\nfishes-undead-rise-ye;12722;fishesundeadriseye;Fishes! Undead, Rise Ye;;\nnameless-servers;12723;nameless_servers;Nameless Servers;;\nnoisium;12724;noisium;Noisium;;\n;12725;embedded_assets;Embedded Assets;;\ncreative-library;12726;creative_library;Creative Library;;\nindependent-gizmo;12727;independent_gizmo;Independent Gizmo;;\njackocache;12728;jackocache;Jack o'Cache;;\ncreepy-creepers;12729;creepy_creepers;Creepy Creepers;;\nmodular-item-api;12730;miapi,arsenal,archery,armory;Truly Modular;;\n;12732;action_hunger;ActionHunger;;\ntrafficcraft;12733;trafficcraft;TrafficCraft;;\nbanilla-hammers;12734;banilla_hammers;Banilla Hammers;;\neveryxhotpot;12735;everyxhotpot;一起火锅;EveryXHotpot;\n;12736;hira_hira;Hira Hira;;\ntierability;12737;tierability;Tierability;;\nsoul-collector;12738;soulcoll;Soul Collector;;\n;12739;jime;Japanese IME;;JIME\n;12740;nota;Nota;;\n;12741;cdt;Custom Display Title;;CDT\n;12742;lighty;Lighty;;\necho;12743;echo;Echo+;;\n;12744;shieldindicator;ShieldIndicator;;\n;12745;ingenuity_extra;匠心增补;Ingenuity Extra;IEx\nlanthanum;12746;lanthanum;镧;Lanthanum;\ncharge-thrower;12747;chargethrower;Charge Thrower;;\n;12748;mr_2032_worldheight;2032 World Height;;\nchampions-ashes;12749;championsashes;王者遗灰;Champion's Ashes;\n;12750;mixinextras,com_github_llamalad7_mixinextras;MixinExtras;;\njade-addons-ic2classic;12751;jadexic2c,ic2classicjade,wailaaddons_ic2c;IC2Classic: Jade Addon;;\nsit1;12752;sit;Sit!;;\nmonnef-core;12753;;Monnef Core;;\nrobs-floating-islands;12754;robs_floating_islands;Rob's Floating Islands;;\n;12755;wheatfieldwitlwolf;麦香狼田;Wheatfrild With Wolf;W3\nglasses;12756;glasses;眼镜;Glasses;\n;12757;simeplesign;简单标记;SimpleSign;SSi\ngem-crabs-1-19;12758;gem_crabs;New Gem Crabs;;\nberrybush-magic;12759;berrybush_magic;BerryBush Magic;;\narchers;12760;archers;Archers (RPG Series);;\n;12761;packrule-menus;数据包/游戏规则菜单;Datapack/Gamerule Menus;\ninventory-backups;12762;inv_logger;InventoryBackups / InventoryLogger;;\nrideablepolarbears;12763;rideablepolarbears;RideablePolarBears;;\ncobblemon-lucky-blocks;12764;cobblelucky;CobbleLucky;;\negg-delight;12765;eggdelight;鸡蛋乐事;Egg Delight;\nportable-nether;12766;portablenether;Portable Nether;;\n;12767;resourcefulblocksounds;Resourceful Block Sounds;;\n;12768;apprentice;学徒的热诚;Apprentice's Zeal;Asz\nthe-starved-stalker;12769;the_starved_stalker;The Starved Stalker;;\ntoms-peripherals;12770;toms_peripherals;Tom's Peripherals;;\ncroptopia-delight;12771;croptopia_delight;Croptopia Delight;;\n;12772;nmc;No Mining Cooldown;;\nfuturistic-decor;12773;futuristic_decor;次元科技装饰方块;Futuristic Decor;\n;12774;keybinds_galore;Keybinds Galore -  Unofficial;;\nspyglass-of-curios;12775;spyglassofcurios;饰品望远镜;Spyglass of Curios;\n;12776;olorfulfont;自定义彩字;ColorfulRender;CR\nmacabre;12777;macabre;Macabre;;\n;12778;tic_legendoftinker;匠魂-匠造之传;Tic Legend of Tinker;TLT\nssr;12779;sparsestructuresreforged;Sparse Structures Reforged;;\npetal;12780;petal;Petal;;\ncreatures-of-runeterra;12781;creaturesofruneterra;Creatures of Runeterra a League of Legends mod;;\nfrom-another-world;12782;fromanotherworld;From Another World;;\npumpkin-moon;12783;pumpkinmoon;Pumpkin Moon;;\nzombie-onslaught;12784;zombie_onslaught;Zombie Onslaught;;\nneighborly;12785;neighborly;Neighborly;;\nnatural-progressions;12786;natprog,natural-progression;Natural Progression;;\ncharged-explosives;12787;charged_explosives;Charged Explosives;;\npulver;12788;pulver;Pulver;;\n;12789;allow-editing-player-data;Allow Editing Player Data;;\napple-crates;12790;apple_crates;Apple Crates;;\n;12791;deathchestreproduce;死亡箱子再现;Death Chest Reproduce;DCR\nbetterend-forge-port;12792;betterendforge;更好的末地Forge版;BetterEnd (Forge);\nthe-corpse-stalker-cave-dweller-mod;12793;cave_dweller;The Corpse Stalker;;\nciscos-armoury;12794;cisco_mod;Cisco's Armoury;;\nportable-mobs;12795;portablemobs;Portable Mobs;;\ndeserts-and-dunes;12796;deserts_and_dunes;Deserts and Dunes;;\n;12797;minecraftfunctioncompiler;Function Compiler;;\nbox-of-horrors;12798;boh;The Box of Horrors;;BOH\nchat-colours;12799;chatcolours;Chat Colors;;\nmekanism-weapons;12800;mekaweapons;通用机械武器;Mekanism: Weapons;\nvanilla-reflex;12801;vare;Generation Reflex;;\nflighthud-reborn;12802;flighthud;鞘翅飞行抬头显示：重生;FlightHUD: Reborn;\ncelestial-enchantments;12803;celestial_enchantments;星月附魔;Celestial Enchantments;\nclearsky;12804;clearsky;洁净天空;ClearSky;\neducate-nitwit;12805;educatenitwit;Educate Nitwit;;\ndamage-spawns-mobs;12806;;Damage Spawns Mobs;;\ndecorative-sniffer-egg;12807;decorative_sniffer_egg;Decorative Sniffer Egg;;\ngilded-sherds;12808;gilded_sherds;Gilded Sherds;;\ncamps-castles-carriages;12809;camps_castles_carriages;Camps. Castles. Carriages.;;\nrainbow-reef;12810;rainbow_reef;Rainbow Reef;;\nadvancement-no-more;12811;nomoreadvancement;Advancement... No More !!!;;\naqua-geode;12812;aqua_structure;Aqua Geode;;\n;12813;jl;Jar Loader;;JL\nmedieval-buildings;12814;medieval_buildings;Medieval Buildings;;\n;12815;item_fucker;谁没做销毁？;Item Fucker;ITFK\n;12816;;You Thief!;;\n;12817;youthief;You Thief: Remastered Edition;;\n;12818;hero_proof;Hero Proof;;\nmore-slabs-stairs-and-walls;12819;more_slabs_stairs_and_walls;More Slabs Stairs & Walls;;\ncaves-canyons;12820;caves_&_canyons,mr_caves__canyons;Caves & Canyons;;\ndragon-mounts-legacy-continued;12821;dragonmounts;龙骑士重制版延续;Dragon Mounts Legacy Continued;\nchloride-mod;12822;embeddiumplus,chloride;Chloride (Embeddium++/Sodium++);;\naggro-indicator;12823;aggroindicator;Aggro Indicator;;\nhaveasoltime;12824;haveasoltime;HaveASolTime;;\ntothestars;12825;tothestars;ToTheStars;;\ntranslater;12826;translater;Translater;;\nlocalized-chat;12827;localizedchat;Localized Chat;;\nprominent-create;12828;;Prominent Create;;\nbad-lightning-no-cookie;12829;badlightningnocookie;Bad Lightning No Cookie;;\nequipment-standard;12830;equipment_standard;装备标准;EquipmentStandard;ES\nterra-armory;12831;snowly,terra_armory;Terra Armory;;\nadvanced-enchantments;12832;advanced_enchantments;进阶附魔;Advanced Enchantments;AE\noaks-nature;12833;oaks_nature;Oaks Nature;;\ngeophilic-reforged;12834;geophilic_reforged;Geophilic Reforged – Biome Additions;;\nlglegacy;12835;laggoggles;延迟监视：移植版;LagGogglesLegacy;\nsmooth-scrolling-refurbished;12836;smoothscrollingrefurbished;Smooth Scrolling Refurbished;;\nmedieval-weapons-pack;12837;medievalvrpack;Medieval Weapons Pack;;\ncelestial-artifacts;12838;celestial_artifacts;星月遗物;Celestial Artifacts;\n;12839;;史诗战斗：一染订正;efrenderfix;ERF\nperfect-mob-farm;12840;perfectmobfarm;Perfect Mob Farm;;\nsmart-render;12841;smartrender;Smart Render;;\npower-adapters;12842;poweradapters;Power Adapters;;\nsilk-api;12843;silk-api;丝绸开发库;Silk API;\n;12844;mediaworks;媒质工程;Mediaworks;\nchestesp;12845;chestesp;ChestESP;;\n;12846;ruthenium;钌;Ruthenium;\n;12847;mr_simple_hydration;Simple Hydration;;\nspirit-walker;12848;spirit-walker;Spirit Walker;;SW\ngcyr;12849;gcyr;Gregicality Rocketry;;GCYR\ngregtechceu-modern;12850;gtceu;格雷科技现代版;GregTechCEu Modern;GTM\n;12851;intarsia;咒术器具;Intarsia;\ninstant-village-building;12852;mr_instant_villagebuilding;Instant Village Building;;\nnbt;12854;nbt;Notable Bubble Text;;NBT\n;12855;mython;Mython;;\nseparated-leaves;12856;separatedleaves;Separated Leaves;;\npangteens-immortal-adventure;12857;imm;PangTeen的平凡冒险;PangTeen's Immortal Adventure;IMM\nsimple-shops;12858;simpleshops;简单商店;Simple Shops;\nforgiving-border;12859;forgivingborder;仁慈的世界边界;ForgivingBorder;\nsnow-mercy;12860;snowmercy;Snow Mercy ⛄;;\n;12861;show_key;按键显示;show key;SK\n;12862;blocky-bubbles;Blocky Bubbles;;\nairdrop-supply;12863;airdrop-supply;空投供应;Airdrop Supply;AS\nrefined-proxy;12864;refinedproxy;Refined Proxy;;\n;12865;mr_better_lushcaves;Better Lush Caves;;\npickable-pets;12866;pickablepets;Pickable Pets;;\nsnow-mercy-forge;12867;snowmercy;Snow Mercy [Forge] ⛄;;\n;12868;Protocol4;Protocol4;;\n;12869;connect_to_1_12_x;Connect to 1.12.X;;\nbouncestyles;12870;;BounceStyles;;\n;12871;betterbeds;Better Beds Reforged;;\nuntamed-delight;12872;untameddelight;不羁乐事;Untamed Delight;\nmonster-no-armor-in-slar;12873;mosnter_no_armor_in_slar;monster no armor in slar: soul like armors reworked addon;;\n;12874;notenoughverbosity;NotEnoughVerbosity;;\ndancing-hoppers;12875;dancing_hoppers;舞动的漏斗;Dancing Hoppers;DH\nanvil-crafting;12876;anvil_crafting;铁砧配方;Anvil Crafting;\nender-chested;12877;ender_chested;Ender Chested;;\n;12878;skull-vision;Skull Vision;;\nprefabricated-structures;12879;prefabricated_structures;Blueprints - Prefabricated Structures;;\nrebirth;12880;rebirth_paintings;REBIRTH: Paintings;;\namethyst-tools-2;12881;amethysttoolsmod;紫水晶工具2;Amethyst Tools Mod 1.2x;\nmob-pets;12882;mob_pets;驯兽师;Mob Pets;MP\n;12883;rl;Random Lib;;RL\nultimate-compression;12884;uc;Ultimate Compression;;UC\n;12885;Artifacts;Unique Artifacts;;\nfarming-in-rain;12886;;Farming in rain;;\nbosses-of-mass-destruction-forge;12887;bosses_of_mass_destruction;祸乱鬼魅 [FORGE];Bosses of Mass Destruction [FORGE];BOMD\narmor-set-bonuses;12888;spacecatasb;Armor Set Bonuses;;ASB\nnemos-creatures;12889;nemos-creatures;Nemo's Creatures;;\nimmersive-jungles;12890;immersive_trees;Immersive Jungles;;\n;12891;grassed;Grassed;;\nweather-2-remastered;12892;weather2remaster;局部气候&风暴 - 重置版;Weather 2 - Remastered;\n;12893;mwfexplosionfix;模块化武装爆炸修复;ModularWarfarePacketExplosionFix;\nphantoms-of-the-end;12894;esendphantoms;Phantoms of The End;;\ndiamond-and-emerald-farming-mod;12895;diamond_and_emerald_farming_mod;Diamond and emerald farming;;\nblood-and-gore;12896;blood_and_gore;Blood and Gore;;\nnonetherportal;12897;nonetherportal;No Nether Portal;;\nnecronomicon;12898;necronomicon;Necronomicon API;;\nshowcase-item;12899;showcaseitem;Showcase Item;;\neldritch-end;12900;eldritch_end;Eldritch End;;\n;12901;nitrogen_cycling;Nitrogen cycling;;\nquit-or-not;12902;quitornot;Quit or Not;;\nspelled-mobs;12903;spelling_mobs;Spelled Mobs;;\nlet-fish-love;12904;let_fish_love;Let Fish Love;;\nexposure;12905;exposure;拾光定影;Exposure;\n;12906;fast-portals,fastportalsforge;Fast Portals;;\npick-up-me-please;12907;pickupme;Pick Up Me;;\nbouncy-creepers;12908;;Bouncy Creepers;;\nsimple-death-chest;12909;deathchest;Simple Death Chest;;\n;12910;enhancermod;Enhancer;;\nheadphones;12911;headphones;Headphones;;\nyou-are-the-zombie;12912;;You Are The Zombie;;YATZ\nself-damage-knife;12913;;Self Damage Knife;;\n;12914;gardens_delight;田园乐事;Garden's Delight;GD\ncoin-craft;12915;coin_craft;Coin Craft;;\nspeedy-ladders;12916;speedyladders;Speedy Ladders;;\n;12917;youaremyhatnow;YouAreMyHatNow;;\n;12918;sol_pineapple;生活调味料：凤梨版;Spice of Life: Pineapple Edition;SoL:PE\n;12919;test;The weightless end;;\nfruits-delight;12920;fruitsdelight;果园乐事;Fruits Delight;\n;12921;rmid;Remake My IDs;;RMID\ndestruction-difficulty;12922;destructiond;破坏困难;Destruction Difficulty;\nhardcore-reborn;12923;hardcore_reborn;极限模式重生，三回啊三回;Hardcore Reborn;HR\nvillagers-follow-emeralds;12924;vfe;Villagers Follow Emeralds;;\ndredge;12925;dredge;Dredge;;\ncreate-creating-space;12926;creatingspace;Create : Creating Space;;\nasmc-performance-patch;12927;asmc;ASMC Performance Patch;;\nmrcrayfishs-gun-mod-unofficial;12928;cgm;MrCrayfish的枪：非官方版;MrCrayfish's Gun Mod: Unofficial;CGMu\n;12929;fishtanks;Fish Tanks;;\nram-compat;12930;ram_compat;Relics: Alex's Mobs Compat;;\n;12931;school,schoolnext,jscchinaschool;中国式学校;Chinese School Mod;\nzombies-reworked-forge;12932;zombies_reworked;Zombies Reworked;;\nlegendary-monsters;12933;legendary_monsters;传奇怪物;Legendary Monsters;\ncatontoasts-mobs;12934;catontoasts_mobs;CatOnToast's Mobs;;\ncombatify;12935;combatify;Combatify;;\ngo-fish-updated;12936;;一起钓鱼吧！(更新版);Go Fish (updated);\n;12937;ki;杀戮光环;Killaura;KA\nwhat-are-they-up-to;12938;watut;What Are They Up To;;Watut\nfumofumo;12939;fumo,mc;Fumo;;\n;12940;elytraplus;ElytraPlus;;\nmcaic;12941;mcaic;MCAIC;;\n;12942;zombification;Zombification;;\n;12943;stutterfix;StutterFix;;\nextrasounds-forge;12944;extrasounds;ExtraSounds Next;;\nresource-mod-loader;12945;rml;资源模载器;Resource Mod Loader;RML\nregrown;12946;mr_regrown;Regrown;;\ncarcass;12947;carcass;Carcass;;\n;12948;humility-afm;Humility AFM;;\nlet-me-use-it;12949;letmeuseit;亿用得纵;Let Me Use It!;\nepic-fight-impactful;12950;impactful;Epic Fight - impactful;;\ngrassnotfloating;12951;grassnotfloating;浮的古原草送别;GrassNotFloating;\n;12952;reclamation;填海/压液丢泽;Reclamation;\n;12953;mr_subsurface;Subsurface;;\n;12954;xmmod;小萌的皇冠;XMCROWN;XMC\nthermite;12955;therm;Thermite;;\nrestriction;12956;restriction;Restriction;;\ncollision-fix;12957;collisionfix;Collision Fix;;\n;12958;backrooms;pertaz的后室生成;pertaz'es Backrooms Generator;pBrG\ndynamic-name-tags;12959;dynamicnametags;Dynamic Name Tags;;\n;12960;profundis;Profundis: Seven New Quality Cave Biomes;;\nctlib;12961;ctlib;ctLib;;CL\n;12962;lib_for_derumin;derumin的库;lib for derumin;LFD\nbuilding-packs;12963;buildingpacks;Building Packs;;\nstyled-sidebars;12964;styled-sidebars;Styled Sidebars;;\ncut-and-colored;12965;cutandcolored;Cut And Colored;;\nthe-fence-unleashed;12966;fency;The Fence Unleashed;;\n;12967;touchup;Touch-up;;\n;12968;gipples_galore;Gipples Galore;;\n;12969;TwistSpaceTechnology;扭曲空间科技;Twist Space Technology;TST\nwood-enjoyer;12970;wood_enjoyer;入木三分;Wood Enjoyer;\n;12971;coreextensions-food-api;核心拓展-食物;Coreextensions-Food-API;CeXF\ndynamic-darker-souls;12972;dynamicdarkersouls;Dynamic darker souls;;\nfade-in-chunks;12973;fadeinchunks;Fade In Chunks;;\n;12974;durability-guard;Durability Guard;;\nsorceryfight;12975;jujutsucraft;Jujutsu Craft (Sorcery Fight);;\nbetter-lava-bucket;12976;better_lava_bucket;Better Lava Bucket;;\njust-outdoor-stuffs;12977;justoutdoorstuffs;Just Outdoor Stuffs;;\ncroptopia-additions;12978;croptopia_additions;Croptopia Additions;;\ncorniergun;12979;corniergunmod;CornierGun;;\nbetter-flight-forge;12980;betterflight;Better Flight;;\n;12981;;Run It Anyway;;\nfarmers-delight-cookbook-reforged;12982;fdcookbook;农夫乐事：烹饪书重制版;Farmer's Delight: CookBook Reforged;\napplications;12983;momentariycore2,applications;MomentariyModder' Applications;;MMA\nk-clear-lag;12984;clearlag;Clear Lag;;\n;12985;hexxy-dimensions;咒法维度;Hexxy Dimensions;\nsimple-backups;12986;simplebackups;简单备份;Simple Backups;\nsky-guis;12987;skyguis;Sky GUIs;;\nbotaniverse;12988;botaniverse;寰宇植物学;Botaniverse;\nbetterer-p2p;12989;betterp2p;Betterer P2P;;\nrecall-staffs;12990;recallstaffs;Recall Staffs;;\n;12991;glowing_fire_glow;萤火微光;GlowingFireGlow;\nenercell;12992;enercell;能量电池;Enercell;\ndefault-world-generator-ssp;12993;defaultworldgenerator-port;Default World Generator without Server Side Prompts;;\nhomyatos-explosives;12994;homyatos_explosives;Homyatos' Explosives;;\ninfchest;12995;infchest;无限箱子;Inf Chest;\n;12996;squashed;Squashed!;;\nunremovableeffects;12997;unremovableeffects;Unremovable Effects;;\ntraders-in-disguise-the-illager-assassin-project;12998;tradersindisguise;Traders in Disguise - The Illager Assassin Project;;\ncherished-items;12999;cherisheditems;Cherished Items;;\nverdantvibes;13000;verdantvibes;绿意盎然;VerdantVibes;\ncrafting-manipulator;13001;craftingmanipulator;Crafting Manipulator;;\nbeware-the-rain;13002;bwrtherain;Beware The Rain;;BTR\nthe-sasquatch;13003;the_sasquatch;The Sasquatch;;\npotion-pizzas;13004;potionpizzas;药水披萨;Potion Pizzas;\npot-breaker;13005;potbreaker;砸罐子;Pot Breaker;PBK\ndisabletools;13006;disable_vanilla_tool;禁用工具;Disable Tools;\n;13007;humility-se;Humility SE;;\n;13008;epicdragonfight;史诗战斗：萤眼订正;EpicFight DragonEye Fix;EDF\npush-everything-away;13009;push_everything_away;推开一切;Push Everything Away;\ncreate-power-loader;13010;create_power_loader;机械动力：动力加载器;Create: Power Loader;\n;13011;gptvillagemain;GPT智能对话村民;GPT Villager;GPTV\nepiceldenringweaponsarmorandmobs;13012;epiceldenringweaponsarmorandmobs;EpicEldenRing;;\nrestart-server;13013;restart-server;Restart Server;;\nsakura-cherry-additions;13014;sakura;Sakura: Cherry Additions;;\npillagers-gun;13015;pillagers_gun;掠夺者的枪;Pillager's Gun;\nhammers-and-smithing;13016;hammersandtables;Hammers and Smithing;;\n;13017;aaaahidechat4s_fix;直播隐藏聊天框修复补丁;HideChatForStreamingFix Patcher;\ntetra-booster-pack;13018;witheringboon;Withering Boon For Tetra;;\neverlasting-skins;13019;everlastingskins;Everlasting Skins;;\ndecorated-pot-minecart;13020;decorated_pot_minecart;饰纹陶罐矿车;Decorated Pot Minecart;\nlegacy-console-sounds;13021;consolesounds;Legacy Console Sounds;;\neye-spy;13022;eyespy;Eye Spy;;\nsonicraft-demons;13023;sonicraft_demons;SoniCraft Demons;;\n;13024;cmd_bansaying_scb;计分板禁言管理器;Minecraft Command Speech Manager;\nflans-mod-reloaded;13025;flansmod;Flan的枪械重制版;Flan's Mod: Reloaded;\nmod-whitelist;13026;mod_whitelist;模组白名单;Mod Whitelist;MW\nsimpledeserteagle;13027;simpledeserteagle;简单沙鹰;Simple Desert Eagle;\ndungeons-towers;13028;dungeons_towers;Dungeons+: Towers;;\ndungeons-more-spawners;13029;more_spawners;Dungeons+: More Spawners;;\n;13030;nether_depths;Nether Depths;;\ntechnical-cores;13031;technicalcores;机巧核心;Technical Cores;\nminetraps;13032;minetraps;我的陷阱;MineTraps;\n;13033;fabricated-rift;Fabricated Rift;;\nhoney-sticky-piston;13034;honeystickypistonmod;Honey Sticky Piston;;\nore-discovery-broadcast;13035;orediscoverybroadcast;Ore Discovery Broadcast;;\nalchemists-garden;13036;alchemists_garden;Alchemist's Garden: Regrowth;;\nopolis-strainers;13037;strainers;BBL Strainers;;\nbook-eater;13038;book_eater;Book Eater;;\nrotom-phone-cobblemon;13039;rotom_phone;Rotom Phone Cobblemon;;\n;13040;worldcomment;随处随笔;World Comment;\nultramarine-refabricated;13041;ultramarine;群青：重织;Ultramarine Refabricated;\nlong-nbt-killer;13042;longnbtkiller;Long NBT Killer;;\n;13043;curioattribute;饰品属性;CurioAttribute;\nconsistent-aim;13044;consistentaim;持久瞄准/移眼盯住;Consistent Aim;\n;13045;wats;无迹蜘谈/蛛灭;Where the Spiders, Get out!;WATS\narrow-pointers;13046;arrow_pointers;Arrow pointers;;\nbareback-horse-riding;13047;barebackhorseriding;Bareback Horse Riding;;\nwhat-the-gecko;13048;what_the_gecko;WHAT THE GECK'O!?;;\nforgotten-pots-forge;13049;forgottenpots;Forgotten Pots;;\nexplorations;13050;explorations;Explorations;;\n;13051;vm;Vape管理器;Vape Manager;VM\nredstone-lamps-plus;13052;rslamps_plus;Redstone Lamps Plus;;\nwool-collection;13053;wool_collection;羊毛装饰方块;The Wool Collection;\n;13054;moreautomationmod;More Automation;;\nrandom-mob-sizes;13055;random_mob_sizes;随机生物模型大小;Random Mob Sizes;\nunexperienced;13056;unexperienced;Unexperienced;;\n;13057;kir;杀戮光环重制版;KillauraReload;KIR\nbalanced-crates;13058;balanced_crates;Balanced Crates;;\n;13059;cogito;Cogito;;\nducky-periphs;13060;ducky-periphs;Ducky Peripherals;;\n;13061;norefreshscroll;No Refresh Scroll;;\nfurnitura;13062;furnitura;Furnitura;;\ncreate-ad-astra-compat;13063;createastracompat;Create Ad Astra Compat;;\n;13064;mcpatcher,MCPatcher;MCPatcher;;\nwalkie-talkie;13065;walkietalkie;Walkie-Talkie;;\ngeotagged-screenshots;13066;geotagged_screenshots;Geotagged Screenshots;;\n;13067;wafflesscythes;Waffle's Scythes;;\n;13068;LavaArmorCraft;拿瓦铠甲工艺;;LAC\n;13069;optifabric;Legacy OptiFabric;;\noneironaut;13070;oneironaut;Oneironaut;;\n;13071;pt;ETools;;ET\ncelestial-core;13072;celestial_core;星月核心;Celestial Core;\ncolorablefox;13074;colorablefox;可染色狐狸/色绘你狐革;Colorable Fox;\narcheology-api;13075;archaeology_api;Archeology API;;\nrpg-parties;13076;mmoparties;RPG Parties;;\nastikor-carts-tfc;13077;tfcastikorcarts;Astikor Carts TFC;;\nnanexs-mobs;13078;nanexmobs;Nanex's Mobs;;\nno-feather-trample;13079;nofeathertrample;No Feather Trample;;\nembers-extended-life;13080;embers;余烬非官方延续版;Embers Unofficial Extended Life;\nterrafirmajousting;13081;tfjousting;TerraFirmaJousting;;\n;13082;pvperms;Proton 原版命令权限;ProtonVanillaPermissions;PVP\njujutsu-animation;13083;jujutsuanimation;Jujutsu Animation;;\ncraftable-elytra;13084;elytra_wing;Craftable Elytra;;\nfrancium;13085;francium;钫;Francium;\ndragonwingselytra;13086;dragonwingselytra;DragonWingsElytra;;\nuniversal-grid;13087;universalgrid;通用终端;Universal Grid;\n;13088;hms;Hold More Stuff!;;\nantique-atlas-reborn;13089;antiqueatlas;Antique Atlas Reborn;;\nars-artifice;13090;ars_artifice;Ars Artifice;;\ndraconic-energy;13091;draconicevolution;Draconic Energy;;\nhamsters-plus;13092;hamsters;Hamsters Plus;;\n;13093;mr_you_bandit;You Bandit!;;\neugenes-lance;13094;eugenes_lance;Eugene's Lance;;\nthorium-reactors;13095;thoriumreactors;钍反应堆;Thorium Reactors;\neugenes-guan-dao;13096;eugenes_guan_dao;Eugene's Guan Dao;;\n;13097;chainimprove;锁链强化;ChainImprove;CI\neugenes-wealthy-plains-village;13098;mr_eugenes_wealthyplainsvillage;Eugene's Wealthy Plains Village;;\nlets-do-herbal-brews;13099;lets_do_herbal_brews,herbalbrews;煨茶酝露;HerbalBrews;\nenchant-icon;13100;enchant_icon;附魔标签;Enchant Icon;\nvintage-delight;13101;vintagedelight;腌渍乐事;Vintage Delight;\n;13102;hotshot;HotShot;;\nbiospherical-expansion;13103;biospherical_expansion;Biospherical Expansion;;\n;13104;serendustry;Serendustry;;\ncreate-connected;13105;create_connected;机械动力：创意传动;Create: Connected;\n;13106;rgml-quilt;RGML Quilt;;\nhbms-nuclear-tech-hamster-reloaded;13107;hbm;Hbm's Nuclear Tech - Hamster Reloaded;;\nnoxesium;13108;noxesium;Noxesium;;\n;13109;amaruq_makpiraaqarvik;狼之馆/狼库;Amarok Makpiraaqarvik;AM\ninfinity-cave;13110;infinity_cave;无限洞穴;Infinity Cave;\nsurface-lush-caves;13111;surface_lush_caves;Surface Lush Caves;;\ngeologic-expansion;13112;geologic_expansion;地质拓展;Geologic Expansion;\ncpas;13113;cpas;conveniently placed armor slots;;cpas\n;13114;target_show;标靶提示;Target Show;\nawesome-magic;13115;awesome_magic;Awesome Magic;;\nchristmas-dweller;13116;christmas_dweller;Christmas Dweller;;\ncreate-curio-goggles;13117;creategogglescurio;Create Curio Goggles;;\n;13118;lootballs;Cobblemon - Loot Balls;;\ndontusecommand;13119;dontusecommand;请别用指令;DontUseCommand;DUC\nbeans-backpacks;13120;beans_backpacks;Beans Backpacks;;\n;13121;projectreflection;反射计划实验室;PRLabs/Project Reflection;PRLabs\nrandomjs;13122;randomjs;RandomJS;;\npiston-push;13123;pistonpushmod;Piston Push;;\nerinium-automation;13124;erinium_automation;Erinium Automation;;\nangelica;13125;angelica;Angelica;;\nexplosiont;13126;explosiont;Explosion't;;\nspider-caves;13127;spider_caves;Spider Caves;;\ncams-lights;13128;cams_lights;Cam's Better Lights;;\n;13129;cirno_fumo;Cirno Fumo Boss;;\nlemon-kindom-artifacts;13130;lemon_kindom_artifacts;柠檬王国：传说之物;Lemon Kindom: Artifacts;\nwhen-dungeons-arise-seven-seas;13131;dungeons_arise_seven_seas;地牢浮现之时 - 海洋扩展;When Dungeons Arise - Seven Seas;\nmob-battle-extra;13132;mob_battle_extra;Mob Battle Extra / MBE Utility Tools;;\nlios-overhauled-villages;13133;lios_overhauled_villages;Lios Overhauled Villages;;\nlost-relics;13134;lost_relics,lostrelics;Lost Relics;;\nprovihealth;13135;provihealth;Provi's Health Bars;;\nmetabolism;13136;metabolism;新陈代谢;Metabolism;\nadv-js;13137;advjs;Advancement JS;;AdvJS\nteleportcraft;13138;teleportcraft;TeleportCraft;;\nfrights-delight;13139;frightsdelight;惊悚乐事;Fright's Delight;FrD\ntravelers-compass;13140;travelerscompass;旅行者指南针;Traveler's Compass;\n;13141;artemis_thin_logs;Artemis' Thin Logs;;\nconfigjs;13142;configjs;ConfigJS;;\nhardrock-tools-materials;13143;hardrock_tools_materials,htm;HardRock Tools & Materials;;\nanimals-trap;13144;animals_trap,animaltrap;Animals Trap;;\narmor-damage-scaling-forge-fabric;13145;armordamagescale;Armor & damage scaling;;\nhornet;13146;hornets;Hornets;;\nexotelcraft;13147;exotelcraft;Exotelcraft;;\njujutsu-kaisen-gt;13148;jujutsu_craft_gt;Jujutsu Kaisen GT;;\n;13149;updatedhud;UpdatedHud;;\nmapwriter;13150;MapWriter;MapWriter Mini Map;;\n;13151;realdamage;真实伤害;RealDamage;\n;13152;forge_tweak;已用得词;Forge Tweak;\n;13153;toolleveling,toollevelingrework;异延锻造;ToolLeveling: Rework / ToolLeveling: Legacy;TLR/TLL\nclickmanaita;13154;clickmanaita;ClickManaita;;\n;13155;dayshower;日期显示;Dayshower;\n;13156;gateofbabylon;王之财宝：无限版;Gate of Babylon：Infinity Editon;\ncommand-toast;13157;command_toast;指令面包机;Command Toast;\n;13158;hexresearch;咒法研究;Hex Research;\ncwg-far-plane-view;13159;cwgfarplaneview;CWG Far Plane View;;\nfirmacivilization;13160;firmaciv;群峦传说：航海文明;Firma: Civilization;\ncatbop-things;13161;catbopthings;Catbop Things;;\nplayer-mobs;13162;player_mobs;Player Mobs;;\n;13163;create-trimmed;Create: Trimmed;;\ntinkers-caramel;13164;tinkerscaramel;Tinkers' Caramel;;\nctrl-q;13165;ctrl-q;Ctrl Q;;\nrug;13166;rug;Rug;;\nglowing-tools;13167;glowing_tools;发光工具;Glowing Tools;\nhorsing-around;13168;horsingaround;Horsing Around;;\nherd-mentality;13169;herdmentality;Herd Mentality;;\ndoggy-talents-next;13170;doggytalents;Doggy Talents Next;;DTN\n;13172;booty;战利品箱;;\nmotobox;13173;motobox;Motobox;;\npottery-additional-pots;13174;pottery;Pottery;;\nbetter-dogs-x-doggy-talents-next;13175;922672;Better Dogs X Doggy Talents Next;;\n;13176;villagertimetable;VillagerTimetable;;\ntowntalk;13177;towntalk;TownTalk;;\ncumulus;13178;cumulus_menus;Cumulus;;\nepherolib;13179;epherolib;EpheroLib;;\nicterine;13180;icterine;Icterine;;\nnotrample;13181;notrample;No Farmland Trample;;\nindustrial-foregoing-souls;13182;industrialforegoingsouls;Industrial Foregoing: Souls;;\nfuelgoeshere;13183;fuelgoeshere;Fuel Goes Here;;\ninitial-dimension;13184;initial_dimension;InitialDimension;;\nyoukais-homecoming;13185;youkaishomecoming;妖怪们的归家;Youkai's Homecoming;\nutilitarian;13186;utilitarian;Utilitarian;;\nclassic-sword-blocking;13187;sword_block;剑之格挡;Classic Sword Blocking;\n;13188;flutterandflounder;Flutter & Flounder;;\n;13189;hbmqql;HBM NCO辐射兼容;HBMQOL;\njojo-world-of-stands;13190;jojowos;JoJo: World of Stands;;\nash-api;13191;ash_api;Ash API;;\ntutes-cn-sweetinks-items;13192;tutescnsitems;Tutes塔兹的物品;Tutes-cn's Items;TsI\nstylecolonies;13193;stylecolonies;Stylecolonies;;\nhyperbox;13194;hyperbox;Hyperbox;;\nrespite-creators-fabric;13195;respitecreators;Respite Creators;;\nsimple-pizzas;13196;pizzas,pizzamaking;简单比萨;Simple Pizzas;\nrapscallions-and-rockhoppers;13197;rapscallions_and_rockhoppers;Rapscallions and Rockhoppers (Penguins);;\npescatore;13198;pescatore;Pescatore;;\napocalypse-now;13199;apocalypsenow;Apocalypse Now;;\ncenozoic-nightmare-the-horror-of-prehistory;13200;cenozoicnightmare;Cenozoic Nightmare - The Horror of Ancient Prehistory;;\nquickbackupmulti;13202;quickbackupmulti;多槽位备份;QuickBackupMulti;QB\nslashblade-japanese-addon-pack-reforked;13203;ex_blades;拔刀剑日系附属重置版;SlashBlade Japanese Addon Pack Reforked;SJAPRE\n;13204;createfabricfixes;Create Fabric Fixes;;\n;13205;commandsceptre;命令方块权杖;Command Sceptre;CmdSc\ncrate-delight-forge;13206;cratedelight;装箱乐事;Crate Delight;\ncultural-creators-forge-create-and-cultural;13207;culturalcreators;Cultural Creators;;\nshuffle;13208;shuffle;Shuffle;;\nprobejs-legacy;13209;probejs;ProbeJS Legacy;;\ncreate-destroy;13210;destroy;坍毁化合;Destroy;\nflourish;13211;flourish;Flourish;;\npitch-dark;13212;pitch-dark;Pitch Dark;;\nsurvival-debug-stick;13213;survivaldebugstick;Survival Debug Stick;;\ncerbons-api-forge;13214;cerbons_api;CERBON's API;;\narcane-apprentices;13215;arcaneapprentices;Arcane Apprentices;;\nkirara;13216;kirara;Kirara;;\nnether-gold;13217;nethergold;Nether Gold;;\ncreate-interactive;13218;create_interactive;机械动力：交互学;Create: Interactive;\nthis-tsunami;13219;this_tsunami;This Tsunami;;\nblendium;13220;blendium;Blendium;;\nawakened-bosses;13221;awakened_bosses;Awakened Bosses;;\nsurvive-in-the-winter-frontier;13222;misc_twf;冬境边域：生存法则;Survive in the Winter Frontier;MISCTWF\ntoneko;13223;toneko;toNeko;;\ngoldelight;13224;goldelight;Gol Delight;;\ncosmos-portals;13225;cosmosportals;Cosmos Portals;;\ncc-vs;13226;cc_vs;CC: VS;;\n;13227;nfl;强化离线登录;Network Fortress Login;NFL\nitem-despawn-timer;13228;item_despawn_timer;Item Despawn Timer;;\n;13229;isthismodlegit;Is This Mod Legit?;;ITML\naylyth;13230;aylyth;Aylyth;;\nno-shade;13231;noshade;No Shade;;\ndimthreads;13232;dimthread;Dimensional Threading Reforked;;\njonns-trophies;13233;trophymanager;Jonn's Trophies;;\nit-follows-exclamation;13234;it-follows;It Follows!;;\n;13235;loggerhead_luminancies;Loggerhead Luminancies;;\nasteorbar;13236;asteorbar;Asteor的状态栏和血量显示;AsteorBar;\nbetter-f1-reborn;13237;betterf1;更好的F1重制版;Better F1 Reborn;\ncreate-tipsy-n-dizzy;13238;createtipsy;Create: The Kitchen Must Grow/Create: Tipsy n' Dizzy;;\nars-expanded-combat;13239;ec_ars_plugin;Ars Expanded Combat;;\n;13240;revamped_piles;Revamped Piles;;\n;13241;fluiwid;FLUIwID;;\nrevamped-phantoms;13242;revamped_phantoms;Revamped Phantoms;;\ndefaultserverlist;13243;defaultserverlist;DefaultServerList;;DSL\ngreen-delights;13244;greendelights;翠绿乐事;Green Delights;\n;13245;;Mod Menu Ornithe;;\nlost-city;13246;lostcity;Lost city;;\ndigestive;13247;digestive;Digestive;;\n;13248;;Mowzie's mobs结构重建;Rebuilt of Mowziemob's Structure;\n;13249;mistlotrtweaks;MistLotrTweaks;;\ninventory-binds;13250;inventorybinds;Inventory Binds;;\n;13251;decorativeblock;颜色方块;MoreColorsquare;MCS\nocto-lib;13252;octolib;OctoLib;;\nfarmers-plus;13253;farmersutils,farmersplus;Farmer's Plus;;\narchitects-friend;13254;ArchitectFriend;Architect's Friend;;\nbagus-cosmetic;13255;bagus_cosmetic;Bagu's Cosmetic;;\nfloatingitems;13256;floatingitems;FloatingItems;;\nars-ocultas;13257;ars_ocultas;Ars Ocultas;;\ntetracelium;13258;tetracelium;tetracelium;;\nfugue;13259;fugue;Fugue;;\ncreate-food;13260;create_food,createfood;Create: Food;;\n;13261;mr_formula_additionsaltblock,formula_additions_salt_block,fasb,RAP;配方追加-盐块;Recipe Addition - Salt Block;RASB\n;13262;dropfoodafterdied;殪焉掉则;Drop Food After Died;DFAD\na-block-of-charcoal;13263;charcoalblock;木炭块;A Block of Charcoal;\na-block-of-flint;13264;flintblock;燧石块;A Block of Flint;\ntome-of-blood-rebirth;13265;tomeofblood;Tome of Blood: Rebirth;;\nchatutils;13266;chat-utils;ChatUtils;;\n;13267;some_small_features;一些小特性;Some Small Features;SSF\nasgard-shield-reloaded;13268;asr;Asgard Shield: Reloaded;;ASR\ngobble-core;13269;gobblecore;Gobble Core;;\nexpanded-combat;13270;expanded_combat;Expanded Combat;;\n;13271;jrrp;Jrrp;;\nfancy-chests;13272;fancychests;Fancy Chests;;\n;13273;mcpatcherforge;MCPatcherForge;;\nglore;13274;glore;Glore;;\npi-privateisland;13275;private_island;私人空岛;PrivateIsland;PI\ni-didnt-mean-it-that-way;13276;ididntmeanit;I didn't mean it that way;;\nfinitewater;13277;finitewater;Finite Water;;\nserverstop;13278;serverstop;ServerStop;;\nglassential-renewed;13279;glassential;Glassential Renewed;;\ntanukidecor;13280;tanukidecor;Tanuki Decor;;\n;13281;ccm;ChatCommandsManager;;CCM\ncit-reforged;13282;citresewn;CIT Reforged;;\nultreonlib;13283;ultreonlib;Ultreon Modding Library;;\nundergarden-delight;13284;undergardendelight;深暗之园乐事;Undergarden Delight;\nmedieval-deco;13285;medieval_deco;Medieval Deco;;\nstars-debug-screen;13286;;Star's Debug Screen / MCBE Debug Screen;;\nlimi;13288;LiMI;轻量化模组名称显示;Lightweight Mod Indicator;LiMI\njtlunlocker;13289;jtlunlocker;JTLUnlocker;;\nae2-stuff-unofficial;13290;ae2stuff;AE2 Stuff Unofficial;;\n;13291;error422;ERROR422;;\nharder-underground;13292;HarderUnderground;更难的地下;Harder Underground;\nharder-wildlife;13293;HarderWildlife;更难的野生动物;Harder Wildlife;\nharder-farming;13294;harderfarming;更难的农作;Harder Farming;\nwaterproof;13295;Waterproof;Waterproof;;\nentityculling-unofficial;13296;;实体渲染机制优化：非官方版;EntityCulling Unofficial;\n;13297;syncprojecte;Sync ProjectE Knowledge;;\n;13298;ccpacrcompact;Config Custom Portal API And Create Rail Compact;;CCPA&CRC\n;13299;eventtweaker;EventTweaker;;evt\ncraft-vania;13300;craftvania2;Craft-Vania: A Castlevania Mod;;\nlotrextracoins-for-1-7-10;13301;lotrextracoins;额外魔戒银币;[Legacy] LotRExtraCoins;\ngenst;13302;genst;Genst II;;\nae2-network-analyser;13303;ae2netanalyser;AE2 Network Analyser;;\nmxtune;13304;mxtune;mxTune;;\ncustomui;13305;customui;自定义UI;CustomUI;\ndeadly-monsters-patched;13306;dmonsters;Deadly Monsters Patched;;\nglodium;13307;glodium;Glodium;;\naether-delight;13308;aetherdelight;天境乐事;Aether Delight;\n;13309;tclgraphics;Animatrix and GraphicsExpanded 1.7.10;;\ndungeonz;13310;dungeonz;DungeonZ;;\ncazfps-axolotls;13311;cazfps_axolotls_dimension;CAZfps Axolotls;;\neasy-breeding-tfc;13312;easybreedingtfc;Easy Breeding for TFC;;\npaperfixes;13313;paperfixes;PaperFixes;;\n;13314;cor;连点器;Connector;COR\nfishing-bot;13315;giacomos_fishing_bot;Giacomo's Fishing bot;;\nprogression-tweaks;13316;progressiontweaks;Progression Tweaks;;\nwhitelistfixer;13317;whitelistfixer;WhitelistFixer;;\nfleshz;13318;fleshz;FleshZ;;\ndepths-of-madness;13320;depths_of_madness;Depths of Madness;;\ntree-lib;13321;ImmersiveTrees;Tree Lib;;\n;13322;legacy-lwjgl3;Legacy-LWJGL3;;\nreal-camera;13323;realcamera;真实相机;Real Camera;RC\nunofficial-mod-name-tooltip-backport;13324;random_ports;模组名称显示：非官方版;Mod Name Tooltip Unofficial;\ncustom-health-bar;13325;colorfulhealthbar;Custom Health Bar;;\nlotrimprovements;13326;lotrimprovements;魔戒改进;LOTR Improvements;\nmousekeyinventoryfix;13327;mousekeyinventoryfix;Mouse Key Inventory Fix;;\nvisible-offhand;13328;visible_offhand;可见副手;Visible Offhand;VO\nphosphorcrashfix;13329;phosphorcrashfix;Phosphor Crash Fix;;\nsound-physics-remixin;13330;soundphysics;籁;Sound Physics Remixin;\nhextended-staves;13331;lanishextendedstaves;Hextended Staves;;\naquaculture-delight;13332;aquaculturedelight;水产乐事;Aquaculture Delight;\nvics-point-blank;13333;pointblank;Vic's Point Blank;;VPB\noccultism-kubejs;13334;occultism_kubejs;Occultism KubeJS;;\ntetra-extras;13335;tetraextrassro,TetraExtras;Tetra Extras;;\nanilink;13336;anilink;AniLink;;\noverworld-netherite-ore;13337;netherite_ore_mod;Overworld Netherite Ore;;\n;13338;rebindmykeys;RebindMyKeys;;\nkeybind-description-fix;13339;keydescfix;Keybind Description Fix;;\nkappas-forgelin;13340;;Kappa's Forgelin;;\nflesh-mod;13341;flesh_mod;Flesh & Limbs;;\n;13342;al;自动退出;AutoLog;AL\n;13343;villagerloved;村民食物自定义/欲宴端走;Villager Loved;\ncreate-bitterballen;13344;create_bic_bit;Create: Bitterballen;;\nglowing-illagers;13345;glowingraidillagers;Glowing Illagers;;\nheads;13346;heads;Heads;;\nepicdmcbossdante;13347;epicdmcbossdante;EpicDMCBossDante;;\nstep;13348;step;Step;;\nwildist;13349;wildist;Wildist;;\nhiccups-legacy;13350;hiccups_legacy;Hiccup's Legacy;;\nslave-knight-gael;13351;slave_knight_gael;slave knight gael;;\ngwyndolin-dark-souls-boss;13352;gwyndolin;gwyndolin: dark souls boss;;\nmceconomy2;13353;mceconomy2;MCEconomy2;;\nforgeservertools;13354;ServerTools-CORE;服务器工具-核心;ServerTools-CORE;\n;13355;replayfps;Replay FPS;;\nquark-delight;13356;quarkdelight;夸克乐事;Quark Delight;\nmmm-lib;13357;MMMLib;MMM-Lib / MMMLib;;\n;13358;ctplus;CrabMTR Transit+;;CTPlus\n;13359;nt;NoFire;;NT\ncreate-design-n-decor;13360;design_decor,dndecor;Create: Design n' Decor;;\n;13361;yc;YesCheat+;;YC\nenviromine-lite;13362;enviromine;更多生存要素：精简版;EnviroMine Lite;\nknaves-needs;13363;knavesneeds;Knaves' Needs;;\nazurelib-armor;13364;azurelibarmor;AzureLib Armor;;\n;13365;container-tooltips;Container Tooltips;;\nmagical-jewelry;13366;magicaljewelry;Magical Jewelry;;\nepicsoulsbosses;13367;epicsoulsbosses;EpicSoulsBosses;;\nepic-puss-in-boots;13368;epicpussinboots;Epic Puss In Boots;;\nsoul-like-npc;13369;soul_like_npc;soul like npc;;\nepic-empires;13370;epicempires;Epic Empires;;\n;13371;my_love_fish;我的爱鱼;My Love Fish;MLF\n;13372;km;按键管理器;KeyboardManager;KM\n;13373;bf;船飞;BoatFly;BF\n;13374;memory;眷恋;;\nprefabricated;13375;prefabricated;Prefabricated;;\nartorias-epicfight-addon;13376;artorias;artorias: epicfight addon;;\nsoul-like-demons-plus-epic-fight-monsters;13377;soul_like_demons;soul like demons: plus epic fight monsters;;\ndark-spirit-dark-souls-system-epic-fight-addon;13378;dark_spirit;dark spirit: dark souls system & epic fight addon;;\nsoul-like-mobs;13379;soul_like_mobs;soul like mobs;;\nuntranslateditemsaddon-alsofluids;13380;untranslateditems_alsofluids;UntranslatedItems: AlsoFluids;;UTI:AlsoFluids\nbetter-quest-pop-up;13381;better_quest_popup;Better Quest Pop-up;;\n;13382;simple_utilities;Simple HUD Utilities;;\nkiln-fabric;13383;kiln;Kiln;;\ntetra-niche-materials;13384;tetranichematerials;Tetra Niche Materials;;\nthe-accurate-backrooms;13385;accbackrooms;The Accurate Backrooms;;\ncreate-trains-on-trains;13386;create_trains_on_trains;Create: Trains on Trains;;\n;13387;campfire_torches_plus;Campfire Torches Plus;;\n;13388;armor_rack;Armor Rack;;\nflux-networks-fabric;13389;fluxnetworks;Flux Networks (Fabric);;\n;13390;surface;Surface;;\npoof-sounds;13391;poofsound;Poof Sounds;;\ncrash-pipe;13392;crash-pipe;Crash Pipe;;\ngradeus-epic-fight-addon-soul-like-boss;13393;gradeus;gradeus: epic fight addon & soul like boss;;\nzombie-more;13394;zombie_more;Zombies More;;\ngunscraft;13395;gunscraft;枪械工艺;Guns Craft;GCS\naudino;13396;audino;Audino;;\ntabbychat-unofficial;13397;tabbychat;TabbyChat Unofficial 1.7.10;;\nodyssey-quests;13398;heracles;Odyssey Quests/Heracles;;\nflapjacks-economy;13399;jackseconomy;Jack's Economy (Create Edition);;\n;13400;onion_onion;Onion Onion;;\nrelics-vivid-light-addon;13401;relics_vivid_light;Relics: Vivid Light Addon;;\ntofucreate;13402;tofucreate;Tofu Create;;\nluminite-sunilite;13403;luminite_and_sunilite;Luminite & Sunilite;;\nbetter-formatting-code;13404;better_formatting_code;Better Formatting Code;;\n;13405;apeeling;A-peel-ing;;\n;13406;bubble_boots;Bubble Boots;;\nsilkablooie;13407;silkablooie;Silkablooie;;\n;13408;belly_flop;Belly Flop;;\n;13409;milky_way;Milky Way;;\n;13410;pet_shop;Pet Shop;;\nbed-is-never-too-far-away;13411;bntfa;Bed Is Never Too Far Away;;\ncreate-oxidized;13412;create_oxidized;Create: Oxidized;;\n;13413;nf;移动工具;MoveTools;MT\ngreen-soul;13414;green_soul;Green Soul;;\neveryxdance;13415;everyxdance;一起跳舞！;EveryXDance;EXD\napplied-flux;13416;appflux;应用通量;Applied Flux;\n;13417;campfire_torch;Campfire Torches;;\ntics-delight;13418;tinkers_delight;匠魂乐事;Tinkers' Delight / TiC's Delight;TD\nunnamed-desert-structures;13419;u_desert;Unnamed Desert Structures;;\nbloomingnature;13420;bloomingnature;绽芳自然;Let's Do: BloomingNature;\nwaila-inhibitors;13421;wailainhib;Waila Inhibitors;;\nthingamabobs-and-doohickeys;13422;thingamabobs_and_doohickeys;Thingamabobs and Doohickeys;;\nwaila-events;13423;wailaevents;Waila Events;;\nmarch-of-the-ents;13424;marchofents;恩特的进军;March Of The Ents;MOE\nrndtp;13425;rndtp;RndTp;;\nin-world-buoyancy;13426;inworldbuoyancy;In-World Buoyancy;;\n;13427;starminer;Simply Starminer;;\nmacos-input-fixes;13428;macos_input_fixes;MacOS 输入修复;MacOS Input Fixes;\n;13429;maxhealthfixfixfix;MaxHealthFixFixFix;;\n;13430;astronomical;Astronomical;;\n;13431;yagate_kimi_ni_naru;终将成为你;yagate kimi ni naru;\n;13432;milletdelight;小米乐事;Millet Delight;\ntelos;13433;telos;终焉;Telos;\nmagical-torches;13434;magical_torches;Magical Torches;;\ndynamical-compass;13435;dynamical_compass;Dynamical Compass;;\nbetter-questing-lord-of-the-rings-addon;13436;bqlotr;更好的任务-魔戒扩展;Better Questing - Lord of the Rings addon;\ndimension-teleport;13437;dimensionteleport;Dimension Teleport;;\nbetter-azalea;13438;azalea;Better Azalea;;\ntetratic-combat-expanded;13439;tetratic_combat_expanded;Tetratic Combat Expanded;;\n;13440;Nyctocosm;The Nyctocosm;;\nmore-beautiful-slabs;13441;moreslabs;More Beautiful Slabs;;\ntools-complement-create;13442;create_tools;Tool's Complement: Create;;\ndemimortality;13443;demimortality;Demimortality;;\nbiome-beans;13444;biome_beans;Biome Beans;;\ncats-workshop;13445;cats_workshop;Cat's Workshop;;\nsoulless-steeds;13446;soulless_steed;Soulless Steeds;;\nopenblocks-elevator-fabric;13447;openblocks;OpenBlocks Elevator (Fabric);;\naurorasdecorations;13448;aurorasdeco;Aurora's Decorations;;\nchoups-drakvyrn-mod;13449;choups_drakvyrn_mod;Choup's Drakvyrn Mod;;\nabyssal-decor;13450;abyssal_decor;Abyssal Decor;;AD\n;13451;wheelchair;轮椅;Wheelchair;\nreddens-stone-lanterns;13452;reddensstonelantern;Redden的石灯笼;Redden's Stone Lanterns;\nheaven-burns-red;13453;hbr_mc;红烧天堂;Heaven Burns Red;HBR\n;13454;;Snowy Spirit - Doggo Compat;;\napotheosis-modern-ragnarok;13455;apotheosis_modern_ragnarok;神化：现代诸神黄昏;Apotheosis Modern Ragnarok: Zero;\n;13456;bedgoboom;BedGoBoom;;\nblock-properties;13457;BlockProperties;方块属性;Block Properties;\ncustom-chest-loot;13458;CustomChestLoot;自定义战利品箱;Custom Chest Loot;\nresource-pandas;13459;resourcepandas;Resource Pandas;;\nsniffs-weapons;13460;sniffsweapons;Sniff's Weapons;;\nmaxlermod;13461;pocketknife;Pocketknife;;\nseed-protect;13462;seedprotect;Seed Protect;;\nbow-overhaul;13463;BowOverhaul;弓箭改革;Bow Overhaul;\nzelda-sword-skills-addon;13464;zssaddon;塞尔达剑技 - 拓展;Zelda Sword Skills - Addon;\nnarrus-yeetus;13465;narrus_yeetus;NarrusYeetus;;\nlogin-shield;13466;login_shield;Login Shield;;\nblockinteraction;13467;blockinteractionmod;禁止右键方块;BlockInteractionMod;\nlinkart-fabric;13468;linkart;Linkart;;\nleftist-potions;13469;leftistpotions;Leftist Potions;;\nmore-delight-fabric;13470;moredelight;多趣乐事;More Delight;\nmceconomy3;13471;mceconomy3;MCEconomy3;;\n;13472;antique-atlas;Antique Atlas 4;;\npollution;13473;pollution;污染;Pollution;PO\ntinted-campfires;13474;tintedcampfires;Tinted Campfires;;\nadditionalstatus;13475;AdditionalStatus;AdditionalStatus;;\nepic-knights-slavic-armory;13476;slavicarmory;Epic Knights: Slavic Armory;;\ncreate-copies-cats;13477;copiescats;Create: Copies & Cats;;\nchat-signing-hider;13478;chatsigninghider;Chat Signing Hider;;\ngamma-creatures;13479;gamma_beasts;Gamma Creatures;;\nmultipart-machines-mining;13480;mm_mining;Multipart Machines: Mining;;\nhigh-version-better-structure-block;13481;better_structure_block;更好的结构方块1.20.1;;BSB\n;13482;drtech;Dr科技;DrTech;DRT\n;13483;creepermultidrop;Creeper Multidrop;;\nthe-man-from-the-fog;13484;man;雾中人;The Man From The Fog;\nsawmill;13485;sawmill;锯木机;Sawmill;\n;13486;savemod;SaveMod;;\n;13487;dudewhatsmygepeuwu;Dude, What's My GePeUWU?;;\ncreate-wizardry;13488;create_wizardry;Create: Wizardry;;\nnetherite-extras;13489;netheriteextras;Netherite Extras;;\nwfs-cave-overhaul;13490;caveoverhaul;WF's Cave Overhaul;;\n;13491;chaos;Chaos;;\ncnr-jinghu-legendary-railway;13492;;京沪十三猪;DF11G;CNR\ncit-resewn-jsonlagpatch;13493;;CIT Resewn JSONLagPatch;;\ngeckoanimfix;13494;geckoanimfix;Iris/Oculus & GeckoLib Compat;;\n;13495;dragonrealm;魔戒之龙;LOTR Dragon;\nmekanismecoenergistics;13496;mekanismecoenergistics;Mekanism EcoEnergistics;;\nwaila-blacklist;13497;waila-blacklist;Waila 黑名单;WAILA Blacklist;\nitem-blacklist;13498;itemblacklist;物品黑名单;Item Blacklist;\ncontinue;13499;continue;Continue Button;;\nthe-dawn-era;13500;dawnera;黎明纪元;The Dawn Era;TDE\n;13501;bwb;更好的世界边界;Better World Border;BWB\ntridents-n-stuff;13502;tridents_n_stuff;Tridents 'n' Stuff;;TNS\ncreate-hand-crafting;13503;create_hc;Create Hand Crafting;;\n;13504;canary;Canary;;\n;13505;softerpastels;Softer Pastels;;\n;13506;rainbethunder;RainBeThunder;;\ncreate-easy-structures;13507;create_easy_structures;Create: Easy Structures;;\nsound;13508;sounds;Sound​s;;\nskinshuffle;13509;skinshuffle;SkinShuffle;;\nreboot-lovelyrobot;13510;rlovelyr;萌化机器人：重启;Reboot LovelyRobot;\n;13511;Suffuse;硝烟四起 枪械-拓展;Point Blank-Suffuse GunSmokePack;SGS\n;13512;super_bundle;超级收纳袋;Super Bundle;\n;13513;farmland_plus;群峦附属：农田改进;TFC: FarmlandPlus;TFC:FLP\n;13514;simplefix;ExtremelySimpleFix;;\nfarmers-delight-tfc;13515;farmersdelight_tfc;Farmer's Delight TFC;;\neven-more-realistic-ore-veins;13516;realistic_ore_veins;Even More Realistic Ore Veins;;\nad-astra-extra;13517;adastraextra;Ad Astra Extra;;\nunderground-bunkers;13518;underground_bunkers;Underground Bunkers;;UB\nminemath;13519;minemath;MineMath;;\nweapon-leveling;13520;weaponleveling;Lukas' Weapon Leveling;;\nrather-more-potions;13521;rather_more_potions;稍多药水;Rather More Potions;RMP\nmuseum-curator;13522;museumcurator;琳琅满目;Museum Curator;\npixel-hud;13523;pixelhud;Pixel-Hud;;\n;13524;ImmibisMacroblocks;Immibis's Macroblocks;;\n;13525;heater;Heater;;\nnatures-melody;13526;natures_melody;自然旋律;Nature's Melody;\nastemirlib;13527;astemirlib;AstemirLib;;\ncreate-unbreakable-tools;13528;create_unbreakable;Create: Unbreakable Tools;;\ntimeless-heroes;13529;superhero;Timeless Heroes;;\nsky-origins;13530;skykid;Sky: Origins;;\nglitchcore;13531;glitchcore;GlitchCore;;\nlight-level-overlay-reloaded;13532;llor;简明直观的光照提示;Light Level Overlay Reloaded;\neirin-yagokoros-pharmacy;13533;yagokoropharmacy;蓬莱药局;Eirin Yagokoro's Pharmacy;\n;13534;;混沌降临;;\ntfc-ore-excavation;13535;tfc_ore_excavation;TFC Ore Excavation;;\ntac-interact-key;13536;tacinteractkey;TAC交互优化;TAC Interact Key;\n;13537;cement;Cement;;\n;13538;LovelyRobotsPe;LovelyRobotsPE;;\n;13539;igfileeditor;In-Game File Editor;;\ntide;13540;tide;潮汐;Tide;\ntaiga-replant-replant;13541;taiga;匠魂合金附加二次重栽版;TAIGA Replant Replant;TAIGARR\nsalmons-genesis-reincarnation;13542;salmonsgenesisreincarnation;Salmon's Genesis Reincarnation;;SGR\nloot-bundles;13543;lootbundles;Loot Bundles;;\noptifine-check;13544;opticheck;Optifine Check;;\ntfc-ie-minerals;13545;tfc_ie_minerals;TFC IE Minerals;;\nlooking-at;13546;lookingat;Looking At;;\n;13547;Pistons;Pistons;;\nrftools-continuation;13548;rftools;Rftools Continuation;;\nbetter-with-everything;13549;betterwithmods,betterwithaddons;Better With Everything;;\ncreate-clockwork;13550;vs_clockwork;Clockwork;;CW\nlomlib;13551;lomlib;LomLib;;\n;13552;qfm;快捷功能菜单;QuickFeatureMenu;QFM\n;13553;oan;Old and New;;\nsparkly-pants;13554;pantsSparkly;Sparkly Pants;;\nnether-sheep;13555;nethersheep;暗黑羊;Nether Sheep;\n;13556;pixelmondetector;Pixelmon探测器;PixelmonDetector;\nlight-level-description;13557;light_level_description;Light Level Description;;\nset-home-waypoints;13558;shw;Set Home & Waypoints;;\nleaky;13559;leaky;Leaky;;\ncurse-of-pandora;13560;curseofpandora;潘多拉之咒;Curse of Pandora;\nchampions-rotn-edition;13561;champions;Champions - RotN Edition;;\ntempest;13562;tempest;Tempest;;\nsimpledifficulty-for-underdog;13563;simpledifficulty;SimpleDifficulty for Underdog;;\n;13564;pcf;Proxy Compatible Forge;;\ntally-master;13565;tally_master;Tally Master;;\nhexagonal-heresy;13566;hexeresy;Hexagonal Heresy;;\nhardcore-spelunking;13567;hardcorespelunking;Hardcore Spelunking;;\ndark-menagerie;13568;darkmenagerie;Dark Menagerie;;\nicarus-mod;13569;icarus;Icarus Mod;;\nars-knightsnmages;13570;knightsnmages;Epic Knights'n'Mages/Ars Knights'n'Mages;;\nhotai;13571;hotai;绷带;Hotai;\nzeta;13572;zeta;Zeta;;\ntaxtowncitizen;13573;taxtc;Tax' Town Citizen;;\nkiller-rabbit-transformation;13574;killer_rabbit_transformation;转化杀手兔;Killer Bunny Tweaks;\nbronze-viking;13575;bronze__vvikings;Bronze & Viking;;\n;13576;mceconomy3compat;MCEconomy3Compat;;\nastikor-carts-redux;13577;astikorcarts;AstikorCarts Redux;;\nthird-person-shooting;13578;tp_shooting;第三人称射击：零;Third Person Shooting: Zero;\nsight;13579;sight;视域;Sight;\n;13580;mrbeastbossfight50k;The First City;;\nthreetag-palladium;13581;palladium;钯;Palladium;\n;13582;vintagium;Vintagium;;\n;13583;woodywe;伍德的工作扩展;Woody's Work Extend;\n;13584;akatzumaworldedit;生存模式的创世神;AkatZumaWorldEdit;\npermanent-status-effect;13585;permanent_status_effect;永久状态效果;Permanent Status Effect;\nworld-play-time;13587;worldplaytime;World Play Time;;\nikwila-i-know-what-im-looking-at;13588;ikwila;I Know What I'm Looking At;;IKWILA\nexperienced-crops;13589;experienced_crops;Experienced Crops;;\nmedieval-seedbags;13590;medieval_seedbags,seedbags;Medieval Seedbags;;\nfusionutil;13591;fusionutil;FusionUtil;;\n;13592;;无声无息;No Creatures;NC\n;13593;compactmachinesinfinite;压缩空间：无限;Compact Machines: Infinite;\nthaumic-sanity;13594;thaumicstability;Thaumic Sanity;;\ntfc-thermal-deposits;13595;tfcthermaldeposits;TFC Volcanoes;;\nanimals-trap-for-tfc;13596;animaltraptfc;Animals Trap for TFC;;\n;13597;ac;聊天工具;AutoChat;AC\nzzzzz-custom-configs;13598;zzzzzcustomconfigs;ZZZZZ Custom Configs;;\nweird-happenings;13599;weirdhappenings;Weird Happenings;;\nnbc-heroes;13600;heroes;NBC Heroes;;\nmelody;13601;melody;Melody;;\ncut-through;13602;cutthrough;Cut Through;;CT\n;13603;intarray.beacon;Aesthetic Beacons;;\nanvilfixreborn;13604;anvil_fix;铁砧修复重生;Anvil Fix Reborn;\n;13605;celestial_weapon;星月装备;Celestial Equipments;\napothic-curios;13606;apothiccurios;Apothic Curios;;\nhbm-ntm-lucky-blocks;13607;luckynuke;HBM NTM Lucky blocks;;\nthaumtweaks;13608;thaumtweaks;ThaumTweaks;;\nno-baby-zombies;13609;nobz;No Baby Zombies;;\ncombat-nouveau;13610;combatnouveau;Combat Nouveau;;CN\n;13611;hanging_on;Hanging On;;\n;13612;igoa;游戏内离线登录;In-game Offline Auth;IGOA\n;13613;lazymystical;神秘农业：懒人;Lazy Mystical;\nudlede;13614;udlede;港口;Udlede;\nhammer-time;13615;hammertime;Hammer Time;;\nhammerz;13616;hammerz;Hammerz;;\n;13617;pickup;物品框交互优化;Pickup;\nlivingadventuretrails;13618;livingadventuretrails;Living Adventure: Trails;;\nsuggestions-api;13619;suggestions-api,suggestionsapi;Suggestion API;;\ncolorful-subtitles;13620;colorfulsubtitles;Colorful Subtitles;;\ndashing-dashboard;13621;dashboard;Dashing Dashboard;;\nmod-control;13622;modcontrol;模组控制;Mod Control;\nextended-potions;13623;ExtendedPotions;Extended Potions;;\ndimension-inventorys;13624;DimensionInventorys;维度物品栏;Dimension Inventorys;\ndimension-rules;13625;dimensionrules;维度规则;Dimension Rules;\nerroring-entity-remover-reforged;13626;eerreforged;Erroring Entity Remover Reforged;;\n;13627;vs_tournament;瓦尔基里天空：竞赛;VS: Tournament Continued;\nfabricated-exchange;13628;fabricated-exchange;Fabricated Exchange;;\nmore-variants-and-biomes;13629;more_variants;more variants and biomes;;\nbadoptimizations;13630;badoptimizations;BadOptimizations;;\n;13631;PfaeffMod;Pfaeffs Mod;;\ngenshinimpactmod;13632;genshin;Genshin;;\ndiagonal-walls;13633;diagonalwalls;Diagonal Walls;;DW\ngravestones-die-classy-updated;13634;gravestones;GraveStones - Die Classy Updated;;\nosog-city-cars;13635;osog-city-cars;City Cars;;\nimmersive-vehicles-iav-parts-pack;13636;iav;IAV Parts pack;;IAV\ntfmg-virtual-oil-deposit;13637;tfmgvod;工业长路：虚拟油田;TFMG: Virtual Oil Deposit;\n;13638;alinlib;AlinLib;;\ncroparia-if;13639;croparia;矿石魔种 (矿石作物/魔种之咏 IF版);Croparia-IF;\n;13640;tetinker;科能工匠;Technical Engineering 3 Tinker;TEN3T\n;13641;actionlib;ActionLib;;\nsimple-close-button;13642;close-btn;Simple Close Button;;\n;13643;waystoneandlightman;传送石碑与莱特曼货币;WayStoneAndLightMan;\n;13644;neuro21;Neuro21;;\ngodzilla-rising;13645;grising;Godzilla: Rising;;\ncreate-netherless;13646;create_netherless;Create: Netherless;;\nnoguievolution;13647;noguievolution;沉浸进化;NoGuiEvolution;NGE\nbaseball-bats;13648;bats;Bats;;\ntfc-weather;13649;tfcweather,weathertfc;TFC Weather;;\n;13650;mup;EigenCraft Unofficial Patch;;\nconrad;13651;conrad;Conrad;;\nbackpack-plus-addon;13652;bps;Backpack Plus;;\nnendermoo;13653;nendermoo;Nendermoo;;\nforgedcarpet;13654;forgedcarpet;Forged Carpet;;\nframed;13655;framed;HELP! I'm being framed!;;\ngear-core;13656;gear_core;Gear Core;;\nhideasterisk;13657;hidemodded;隐藏“已修改”;HideModded;HMD\nmonster-eggs;13658;monstereggs;Monster Eggs;;\nrain-shield;13659;rainshield;驱雨盾;Rain Shield;\ncerbons-better-beacons;13660;better_beacons;CERBON's Better Beacons;;\n;13661;funny-roulette;Funny Roulette;;\n;13662;worldeditcui;WorldEdit CUI (Forge);;\nbetter-loot;13663;betterloot;Better Loot;;\ntrique-api;13664;triqueapi;TriQue API;;\nlets-do-addon-structures;13665;letsdo_addon_structures,letsdoaddon-structures;Let's Do Addon: Structures;;\ncreate-recycle-everything;13666;create_crush_everything;Create Recycle Everything;;\ntfc-ambiental-second-edition;13667;tfcambiental;TFC Ambiental: Second edition;;\n;13668;somemusicdiscs;Some Music Discs;;\ngimmetime;13669;gimmetime;GimmeTime;;\n;13670;fuzzy-commands;Fuzzy;;\nhelpfixer;13671;helpfixer;HelpFixer;;\n;13672;Litemoretica;Litemoretica;;\n;13673;shiftingwares-villagerconfig-addon;Shifting-Wares: VillagerConfig Addon;;\n;13674;coloredlamps,cl;Colored Lamps;;\n;13675;expansive_lighting;Expansive Lighting;;\ndrippedout;13676;drippedout;Dripped Out;;\neaster-rabbit-life;13677;rabbitlife;Easter Rabbit Life;;\nsticker-frames;13678;stickerframes;Sticker Frames;;\ntnt-slimes;13679;tntslimes;TNT Slimes;;\narmored-redstone;13680;armored_redstone;Armored Redstone;;\nbismuth-colors;13681;bismuth;铋;Bismuth;\nkillbind;13682;killbind;Killbind;;\nuniversal-clock-hud;13683;universalclockhud;Universal Clock HUD;;\nportaldupebegone;13684;portaldupebegone;PortalDupeBegone;;PDB\n;13685;teabridge;TeaBridge;;\nmissions;13686;missions;Create: Missions;;\nvotemission;13687;votemission;VoteMission;;\nimmersive-vehicles-automobilwerke-schwikau-veb;13688;auweschveb;VEB Automobilwerke Schwikau;;VEB\ncheesemaking;13689;cheesemaking;Cheesemaking;;\ncardiac;13690;cardiac;Cardiac;;\nmaterialtweaker;13691;materialtweaker;MaterialTweaker;;MT\nwhirlwind;13692;whirlwind;WhirlWind;;\nwarden-tools;13693;wardentools;Warden Tools;;\nroyal-guard;13694;royal_guard;皇家守卫;Royal Guard;RG\n;13695;mightyarchitect;The Mighty Architectury;;\nthemoreloyaltrident;13696;moreloyaltrident;更忠诚的三叉戟/移运兜转;TheMoreLoyalTrident;\nnotenoughresources;13697;neresources;NotEnoughResources;;NER\nelenai-dodge;13698;elenaidodge;Elenai Dodge;;\nxenon;13699;xenon;氙;Xenon;\nriftstorm-rpg;13700;riftstorm_rpg;Riftstorm RPG;;\ndata-api;13701;data_api,mr_data_api;Data API;;\nbetter-babies;13702;mr_better_babies;Better Babies;;\nmorecreativetabs-forge-fabric;13703;morecreativetabs;MoreCreativeTabs;;\n;13704;mr_trade_villagerhats;Gen's Villager Hats;;\n;13705;shove;Shove!;;\nsunflower-delight;13706;sunflower_delight;向日葵乐事;Sunflower Delight;\nmystias-izakaya;13707;mystias_izakaya;夜雀乐事;Mystias Izakaya;MI\ncarpet-everything;13708;carpeteverything;Carpet Everything!;;\n;13709;nianyefan;黏液饭;Nian Ye Fan;\nlets-do-brewery-farm-charm-compat;13710;brewery;盛节精酿;Let's Do Brewery;\nhephaestusplus;13711;hephaestusplus;Hephaestus Plus;;\nre-dimension-stages;13712;redimstages;维度阶段重制版;Re-Dimension Stages;\nlemoned;13713;lemoned;Lemoned;;\n;13714;horizonlimit;HorizonLimit;;\nfallen-paladins-and-priests;13715;fpapmod;Fallen Paladins and Priests;;\n;13716;mknutils;MKN Utils;;\n;13717;rgb;RGB;;\n;13718;virtualpump;VirtualPump;;\neverymole;13719;every_mole;EveryMole - Alex Caves Addon;;\nmutant-iceologer;13720;mutant_iceologer,mi;Mutant Iceologer;;\n;13721;days_gone;Days Gone;;\n;13722;extra-loot;额外战利品;Extra Loot;EL\nrelics-of-gaming-continued;13723;rogcontinued;游戏遗物：延续;Relics Of Gaming Continued;\nendermen-plus;13724;endermenplus;Endermen Plus;;\ntiptapshow;13725;tiptapshow;TipTapShow;;\nvillager-gunners;13726;gunners;Villager Gunners;;\n;13727;golden_psalms;麦香狼田：金色诗篇;Wheatfield With Wolf：Golden Psalms;W3-GP\nepic-fight-infernal-gainer;13728;infernal;Epic Fight - Infernal Gainer;;\nprodigy-mechanics;13729;prodigymechanics;Prodigy Mechanics;;\njust-an-end-anchor;13730;just_end_anchor;Just an End Anchor;;\n;13731;mod_MineFactory;我的工厂;MineFactory;\nmemory-usage-title;13732;memoryusagetitle;标题显示内存;Memory Usage Title;\nstring-utilities;13733;string-utilities,string_utilities;字符串工具;String Utilities;\n;13734;iis;无限物品;InfinityItems;IIS\nsweeper-maid;13735;sweeper_maid;清洁女仆;Sweeper Maid;SM\nmore-enchantments_;13736;more_enchantments;更多附魔;More Enchantments;\nbow-scope;13737;bowscope;BowScope;;\n;13738;less_caves;更少的洞穴;less caves;LECA\nfpv20;13739;fpv20;FPV20;;\nmolten-metal;13740;moltensteel;Molten Metal;;\nselling-bin;13741;sellingbin;Joe's Selling Bin;;\ntomtarus-cobblemon-farmers-delight-tweaks;13742;tmtcd;Tomtaru's Cobblemon & Farmer's Delight Tweaks;;\ncobblemon-o-plenty;13743;tmtcop;Cobblemon O' Plenty;;\ntomtarus-cobblemon-immersive-engineering-tweaks;13744;tmtceic;Tomtaru's Cobblemon & Immersive Engineering Tweaks;;\nabuseless-hardcore;13745;abuselesshardcore;Abuseless Hardcore;;\n;13746;wizstacks32;WizStacks32;;\ntconjei;13747;tconjei;TConJEI;;\nnew-xp-ore;13748;new_xp_ore;New Xp Ore;;\n;13749;not_responding;Not Responding;;\nnocubes-wilderness;13750;nocubes_wilderness;NoCube's Wilderness;;\n;13751;more_snowball;雪球弓;Snow Bow;\ncopper-great-swords;13752;copper_minecraft;Copper Great swords;;\n;13753;falling-block-particles;Better Falling Block Particles;;\nzume;13754;zume;Zume;;\nmobtimizations;13755;mobtimizations;Mobtimizations - Entity Performance Fixes;;\nillager-trader;13756;illager_trader;Illager Trader;;\n;13757;damagenumbers;Damage Numbers;;\ntoolendxp;13758;toolendxp;ToolEndXP;;\nspiritmancy;13759;spiritmancy;Spiritmancy;;\ncreate-vintage-improvements;13760;vintageimprovements;Create: Vintage Improvements;;\n;13761;nbe;无床爆;NoBedExplosion;NBE\ndragon-craft-quest;13762;dragon_quest_dai;Dragon Craft Quest;;\nsword-of-gods;13763;sword_of_god;Sword of Gods;;\nhorse-stonks;13764;horsestonks;Horse Stonks;;\njet-pack-bedrock-mod;13765;jetpack;Jet-Pack - Bedrock Addon;;\npillarger;13766;pillarger;Pillarger;;\nars-additions;13767;ars_additions;Ars Additions;;\nvisualsound;13768;visual_sound_mod;Visual Sound;;\n;13769;cyclic_crash_fixer;Cyclic Crash Fixer;;\nlavender-api;13770;lavender;Lavender;;\n;13771;mcrd;MCreator模组检测器;MCreatorDetector;MCRD\ncheatutils-by-zergatul;13772;cheatutils;CheatUtils;;\njump-sounds;13773;jump_sounds;Jump Sounds;;\nkirin;13774;kirin;Kirin;;\nllamapalooza;13775;llamapalooza;LlamaPalooza;;\ninstant-drown;13776;instantdrown;Instant Drown;;\nacsguis;13777;sqript;ACsGuis;;\ngame-phase;13778;gamephase;游戏阶段;Game Phase;GP\nno-zombie-pandemic;13779;no_zombie_pandemic;No Zombie Pandemic;;\nwitcheroo;13780;witcheroo;Witcheroo;;\ntotallybalancedcreativeflying;13781;tbcf;Totally Balanced Creative Flying;;\n;13782;res;随机鸡蛋;RandomEggs;RES\nheterorustichromia;13783;rustichromia;Heterorustichromia;;\nvideo-extension-for-fancymenu-forge;13784;fmextension_video;Video Extension for FancyMenu;;\neasy-npc;13785;easy_npc;Easy NPC;;\n;13786;mr_ns_fdrc,ns_fd_rc;自然之灵-农夫乐事配方兼容;Nature's Spirit - Farmer's Delight Recipe Compat;\nthe-dawn-era-delight;13787;dawnera_delight;黎明纪元乐事;The Dawn Era Delight;\ndynamic-craft;13788;DynamicCraft;Dynamic Craft;;\nstartinginventory;13789;StartingInventory;StartingInventory;;\nwireless-speakers;13790;speakermod;无线扬声器;Wireless Speakers;\nsagerfx;13791;safx;SA特效;SagerFX;SAFX\n;13792;noteit;NoteIt;;\njewelrycraft-2;13793;jewelrycraft2;珠宝工艺2;Jewelrycraft 2;\nfallflyinglib;13794;fallflyinglib;FallFlyingLib;;\nenchantable-spike;13795;enchantablespike;Enchantable Spike;;\n;13796;crit;Crit Arrows From Mobs;;\nxaeros-map-chest-tracker-integration;13797;xaeros-map-chest-tracker-integration;Xaero's Map Chest Tracker Integration;;\nwarden-horn;13798;wardenhorn;Warden Horn;;\ntoo-many-efficiency-losses;13799;tmel;Too Many Efficiency Losses;;TMEL\nsrp-addon-cotesia-glomerata;13800;srpcotesia;SRP：寄生蜂;Scape and Run Parasites Addon: Cotesia Glomerata;SRPC\n;13801;crf;Crash Pipe-Forge;;\ndamage-lock;13802;damage_lock;磁场天锁;Damage Lock;DL\n;13803;whdf;狼受击伤害修正;WolfHitDamageFix;WHDF\nmodular-materials;13804;modularmaterials;模块化材料;Modular Materials;\nminepal;13805;minepal;Minepal;;\ncustom-portals;13806;customportals;自定义传送门;Custom Portals;\ndirty-lens;13807;dirty_lens;Dirty Lens;;\n;13808;improved_anvils;Improved Anvils;;\nstyled-player-list;13809;styledplayerlist;Styled Player List;;\ncreate-love-war;13810;createloveandwar;Create: Love & War;;\n;13811;recasting;重铸;Recasting;\nchainmailed;13812;chainmailed;Chainmailed;;\nserious-player-animations;13813;seriousplayeranimations;Serious Player Animations;;\nabridged;13814;abridged;Abridged;;\nhexvr;13815;hex_vr;HexVR;;\nhearths;13816;hearths;Hearths;;\nprey-effect;13817;prey;Prey Effect;;\nventurer;13818;venturer;Venturer;;\nwheres-my-wither-storm;13819;wmws;Where's My Wither Storm?;;\nalexs-mobs-interaction;13820;alexsmobs;Alex 的生物：交互;Alexs Mobs Interaction;\n;13821;to_neko;toNeko Forge重制版;toNeko Reforged;\n;13822;cheesing_death;欺骗死亡;Cheesing Death;CD\n;13823;isadd;物品堆叠上限突破;ItemStack++;ISADD\n;13824;SPC;Single Player Commands;;SPC\nmyworldgen;13825;MyWorldGen;MyWorldGen;;\nmedieval-buildings-end-edition;13826;medievalend;Medieval Buildings [End Edition];;\nfarmers-combat;13827;farmerscombat;Farmer's Combat;;\norpheus;13828;orpheus;Orpheus;;\ncreate-sweets-and-treats;13829;createsweetsandtreets;Create: Sweets & Treats;;\nklsts-beyond-netherite;13830;beyond;Klsts' Beyond Netherite;;\n;13831;mcmodwiki;MC百科资料搜索：再重生;MCMOD Item Search Reborn Again;\nrefined-polymorphism;13832;refinedpolymorph;Refined Polymorphism;;\ncave-dweller;13833;cavenoise;Cave Dweller;;\ndweller-dweller;13834;dweller_dweller;Dweller dweller;;\nthermals-delight;13835;tmttd;Thermal's Delight;;\n;13836;advancedhud;Advanced HUD;;\nbetter-farmers-combat;13837;betterfarmerscombat;Better Farmer's Combat;;\n;13838;mr_missiles;Missiles;;\nsimple-butchery;13839;butcherymod;Simple Butchery;;\nbutchery;13840;butchery;Butchery;;\n;13841;smr;平滑基岩层-Forge;Smoother Bedrock-Forge;SBF\nenchantment-reveal;13842;enchantment_reveal;Enchantment Reveal;;\ncopycats;13844;copycats;机械动力：伪装方块+;Create: Copycats+;\nthe-great-alchemical-compendium;13845;the_great_alchemical_compendium;The Great Alchemical Compendium;;\ndesperate-measures;13846;desperate_measures;Desperate Measures;;\nblock-slabs-mod;13847;blockslabs;Block Slabs;;\nroost-re-hatched;13848;roost;Roost Re-hatched;;\nlifedrain;13849;lifedrain;LifeDrain;;\nmediethings;13850;mediethings;Mediethings;;\ngastrolimital-bypass;13851;gastrolimitalbypass;Gastrolimital Bypass;;\n;13852;avaritia;无尽工具升级;InfinityTools++;IIADD\nepic-core-api;13853;eca;史诗核心API;Epic Core API;ECA\natp-safe-creeper;13854;ATP-safe_creeper,safe_creeper;ATP友好苦力怕;ATP Safe Creeper;\ninfinityrpg;13855;mode_minecraft;InfinityRPG;;\nsuper-water-block;13856;superwaterblock;Super Water Block;;SWB\n;13857;stop-repeating-command-block;停！循环型命令方块;Stop Repeating Command Block;SRCB\n;13858;VVAddon;Tetra强化属性拓展;VVAddon;\npressure;13859;pressure;压强;Pressure;\namendments;13860;amendments;Amendments;;\npiglin-army;13861;piglin_army;Piglin Army;;\ndecorative-ladders;13862;decorative-ladders;Decorative Ladders;;\nmore-foods;13863;morefood;More Foods;;\n;13864;whousedrtpcommandmod;移跃当知;Who used RTP Command;WRC\nmodern-delight;13865;bakingdelight;现代乐事;Modern Delight;\nbetter-main-menu;13866;better_main_menu;Better Main Menu;;\n;13867;airoverhaul;Drowning Overhaul / Air Overhaul;;\nmore-wizards;13868;more_wizards;More Wizards;;\noldresearch;13869;oldresearch;TC4 Research Port;;\nascension-megamons;13870;ascension_megamons;Ascension Megamons;;\nsimply-sulphur;13871;simply_sulphur;Simply Sulphur;;\nfabric-hider;13872;fabric_hider;Fabric Hider;;\ncustom-cursor;13873;customcursorcomm;Custom Cursor 🖱️;;\nthe-man-from-the-void;13874;man;The Man From The Void;;\nsimpletms-tms-and-trs-for-cobblemon;13875;simpletms;SimpleTMs: TMs and TRs for Cobblemon;;\ncobblemizer;13876;cobblemizer;Cobblemizer;;\nworldtools;13877;worldtools;WorldTools;;WT\nparticle-blocker;13878;particle-blocker;Particle Blocker;;\nvehicle-hunger-bar;13879;vehiclehungerbar;Vehicle Hunger Bar;;\nspooky-scary-halloween;13880;spooky_scary_halloween;Spooky Scary Halloween;;\n;13881;attackmissed;无效攻击;Attack Missed;\n;13882;pdt;发包工具;PacketDebugTools;PDT\n;13883;mr_arm_erstands;Arm-er Stands;;\nsandbox-bootstrap;13884;sandbox_bootstrap;Sandbox Bootstrap;;\ntetra-toolbooster;13885;toolbooster;Tetra Toolbooster;;\nad-astra-proxima-plus;13886;proxima_plus;Ad Astra : Proxima Plus;;\nad-astra-extra-additions;13887;ad_astra__extra_additions;Ad Astra: Extra Additions;;\ninfinite-fluid-bucket;13888;infinite_fluid_bucket;无限流体桶;Infinite Fluid Bucket;IFB\n;13889;lcsmodpack;前期武器追加;;EWA\n;13890;voxy;Voxy;;\nvmod;13891;valkyrien_mod,the_vmod;VMod;;\nminestuck;13892;minestuck;Minestuck;;\n;13893;vpbrecipe;VPB的配方;VPB's Recipe;\nstar-wars-planets-ad-astra;13894;swplanets;Star Wars Planets;;\namethyst-equipment;13895;amethystequipment;Amethyst Equipment;;\nthe-whisperer;13896;whisperer;The Whisperer;;\n;13897;lightningrod;格雷科技0;GregTech 0 / Lightning Rod Addon;GT0\nepic-vinland-saga;13898;epicvinlandsaga;Epic Vinland Saga;;\ncat_jam;13899;cat_jam;cat_jam;;\nremastered-structure;13900;milesplayz;Fantastic Remastered Structures;;\nspacore;13901;spacore;SpACore;;\nfancyvideo-api;13902;fancyvideo-api;FancyVideo-API;;\ngoatman-jar-v2-dweller;13903;goatman;The Goatman;;\n;13904;jGO2ac1X;Horde Nights;;HN\nmineral-overhaul-shellstone-tuff;13905;molt;Mineral Overhaul: Shellstone & Tuff;;\nclear-gpu-cache;13906;cleargpucache;清除GPU缓存;Clear Gpu Cache;\nstalker-creepers;13907;stalkercreepers;Stalker Creepers;;\nthe-ghoul-remastered;13908;theghoul;The Ghoul Remastered;;\n;13909;mtrtm;TransitManager;;\nintegrated-scripting;13910;integratedscripting;Integrated Scripting;;\n;13911;nyc_subway_addition;NYC Subway;;\n;13912;highrails;MTR Utility;;\ncreativetabsearch;13913;creativetabsearch;创造物品栏检索;CreativeTabSearch;CTS\nmoonbetween;13914;moonbetween;在月亮之间;Moon Between;\ntetrachord-lib;13915;tetrachordlib;四弦琴;Tetrachord Lib;TcLib\nscreenshot-sharing;13916;screenshotsharing;屏幕截图分享/移影睹展;ScreenshotSharing;\nleaf-pile;13917;leafpile;落叶堆;Leaf Pile;\nuwufied;13918;uwufied;UwUfied;;\nsit-chill;13919;chillout;Sit & Chill;;\nblueprintvoid;13920;bpvoid;机械动力：蓝图空位;Create：Blueprint Void;\n;13921;quickonlinemod;快捷联机;QuickOnlineMod;QOM\nmo-creatures-legacy;13922;MoCreatures;Mo' Creatures Legacy;;\nsticky-hands;13923;stickyhands;Sticky Hands;;\npandalib;13924;pandalib;PandaLib;;\ninventory-spam;13925;inventoryspam;Inventory Spam;;\n;13926;mineproc;Mineproc;;\nslime-merger;13927;slimemerger;Slime Merger;;\nconfigurable-cane;13928;configurablecane;Configurable Cane;;\nhex-keys;13929;hexkeys;Hex Keys;;\nsky-lands;13930;skylands;Sky Lands;;\n;13931;csdytinker;魔法少女的匠魂词条库;Csdy Tinker;\nvocalized;13932;vocalized;Vocalized;;\n;13933;kaleidos;森罗万象;Kaleidos;\n;13934;iblis_headshots;爆头击杀：一匠到底;Iblis Headshots tic;iht\n;13935;advancedfmk;高级框架;Advanced Framework;AdF\n;13936;flammpfeil.slashblade.frostwolf;冰狼之刃;FrostWolf;\nkubejs-irons-spells;13937;irons_spells_js;KubeJS Iron's Spells;;\nrevelationaryjs;13938;revjs;Revelationary JS;;RevJS\narche;13939;arche;Arche;;\ncaverns-and-chasms;13940;caverns_and_chasms;Caverns & Chasms;;\n;13941;fl;功能列表;FunctionList;FL\n;13942;linkinfo;链接信息;Link Info;\nmixinbooster;13943;mixinbooster;Mixin Booster / MixinTransmogrifier;;\nblock-crafting;13944;blockcrafting;方块合成;Block Crafting;BCR\nzombplayer;13945;zombplayer;ZombPlayer;;\nknighys-mystical-creatures;13946;knighys_additions;Knighy's Mystical Creatures;;\nsign-button;13947;signbutton;Sign Button;;\nstartinv;13948;startinv;StartInv;;\n;13949;server-replay;ServerReplay;;\nftb-stoneblock-companion;13950;ftbsbc;FTB StoneBlock Companion;;\nash-of-sin-custom-anti-enchantment-entity;13951;ash_of_sin_custom_anti_enchantment_entity;罪业余烬：自定义反附魔实体;Ash Of Sin: Custom Anti Enchantment Entity;\nash-of-sin-anti-high-level-enchantment;13952;ash_of_sin_anti_high_level_enchantment;罪业余烬：反高级附魔;Ash Of Sin: Anti High Level Enchantment;\nash-of-sin-custom-anti-item-entity;13953;ash_of_sin_custom_anti_item_entity;罪业余烬：自定义反物品实体;Ash Of Sin: Custom Anti Item Entity;\nash-of-sin-custom-anti-seat-entity;13954;ash_of_sin_custom_anti_seat_entity;罪业余烬：自定义反坐垫实体;Ash Of Sin: Custom Anti Seat Entity;\nash-of-sin-custom-entity-anti-effect;13955;ash_of_sin_custom_entity_anti_effect;罪业余烬：自定义实体反状态效果;Ash Of Sin: Custom Entity Anti Effect;\nash-of-sin-custom-entity-effect;13956;ash_of_sin_custom_entity_effect;罪业余烬：自定义实体状态效果;Ash Of Sin: Custom Entity Effect;\nash-of-sin-custom-entity-item;13957;ash_of_sin_custom_entity_item;罪业余烬：自定义实体装备;Ash Of Sin: Custom Entity Item;\nash-of-sin-eternal-entity;13958;ash_of_sin_eternal_entity;罪业余烬：永恒实体;Ash Of Sin: Eternal Entity;\n;13959;ct;运算变速;CustomTimer;CT\nfundamental;13960;fundamental;Fundamental;;\nwendigo-jar-dweller;13961;skin_stalker;The Skinstalker;;\nthe-super-god-road;13962;TheSuperGodRoad;超神之路;TheSuperGodRoad;SGR\n;13963;backrooms;The Backrooms;;\n;13964;cad2t;区块加成;ChunkAddition;CAD2t\n;13965;exblades;ExBlades;;\nstorage-delight-forge;13966;storagedelight;存储乐事;Storage Delight;\nlightshield;13967;lightshield;光之护盾;LightShield;LShield\nsophisticated-core-unofficial-fabric-port;13968;;精妙核心 (非官方 Fabric 移植);Sophisticated Core (Unofficial Fabric port);\nsophisticated-backpacks-unofficial-fabric-port;13969;;精妙背包 (非官方 Fabric 分支);Sophisticated Backpacks (Unofficial Fabric port);\nred-power;13970;red_power;Red Power;;\n;13971;hdykwd;你怎么知道我做了什么;How do you know what I done;HDYKWD\n;13972;minihudextra;MiniHUD Extra;;MHEx\nsophisticated-storage-unofficial-fabric-port;13973;;精妙存储 (非官方 Fabric 分支);Sophisticated Storage (Unofficial Fabric port);\n;13974;oldblockdrops,oldoredropsforge;Old Ore Drops;;\nrage-mod;13975;rage;兴怒;Rage Mod;\n;13976;disableallstructers;Disable All Structures;;\ncomposite-material;13977;composite_material;复合材料;Composite Material;CM\ntimerecorder;13978;timerecorder;光阴：莉拉提娅;TimeRecorder;TR\nbettersmithingtable;13979;bettersmithingtable;Better Smithing Table;;\ntrajectory-estimation;13980;trajectory_estimation;弹道估计;Trajectory Estimation;TJE\n;13981;foxcoreneoforge,foxcorefabric;狐狐核心;FoxCore;\n;13982;interconnectionfabric,interconnectionneoforge;互联核心;Interconnection;\n;13983;interconnectionplayerneoforge,interconnectionplayerfabric;互联玩家同步;InterconnectionPlayer;\n;13984;twelve_talismans;十二符咒;TwelveTalismans;\n;13985;dragicland;老猎人;The Elder Hunter;\nns-cr-rc;13986;mr_ns_crrc;自然之灵-机械动力配方兼容;Nature's Spirit - Create Recipe Compat;\n;13987;yecorn,ye_corn;田野核心;Ye Core;YC\niron-repair-kits;13988;repair_kits;Iron Repair Kits;;\n;13989;susinsert;SusInsert;;\ncritters-n-crawlers;13990;cnc;Critters n' Crawlers;;CN'C\n;13991;FishModLoader;FishModLoader;;\nepic-stats;13992;epic_stats;Epic Stats;;\nbrew-chew;13993;brewchew;Brew'n Chew;;\n;13994;lummobs;Lummobs!;;\ncreate-ratatouille;13995;ratatouille;机械动力：齿轮与麦穗;Create: Ratatouille;CRAT\njains-desserts;13996;jains_desserts;Jain's Desserts;;\n;13997;drizzleproof;Drizzleproof;;\n;13998;thief_tweaks;Thief Tweaks;;\nthe-long-story;13999;thelongstory;The Long Story;;TLS\nsimple-hud-enhanced;14000;simplehudenhanced;Simple HUD Enhanced;;\nvocalized-ayachinene-voice-pack;14001;vocalized_ayachi_nene;Vocalized 绫地宁宁语音包;VocalizedAyachiNeneVoicePack;\nthe-between-awakens;14002;shitty_enchants;The Between Awakens;;\nemc-interface;14003;emc_interface;EMC Interface;;\ncsgo-box;14004;csgobox;Csgo 箱子;CsgoBox;\ncreate-oppenheimered;14005;create-oppenheimered;机械动力：原子计划;Create: Oppenheimered;\ndimension-update-fixer;14006;dimensionupdatefixer;Dimension Update Fixer;;DUF\ndont-keep-vanishing-items;14007;dontkeepvanishingitems;Don't Keep Vanishing Items;;\nore-processing-for-tfc;14008;tfcoreprocessing;Ore Processing for TFC;;\ncreativepagejump;14009;creativepagejump;创造物品栏页码跳转;CreativePageJump;CPJ\napril-fools;14010;aprilfools;April Fools;;\nexmoxl;14011;exmoxl;极墨核心;exmoxl;\n;14012;parasite_era;寄染之时;The Parasite Era;SRP:TPE\narmortip;14013;armortip;Armortip;;\ntrue-herobrine;14014;true_herobrine;True Herobrine;;\noam-by-choupiclou2007;14015;oreandmore,ore;Ore And More;;OAM\nsquat-grow;14016;squatgrow;Squat Grow;;\nbackported-wolves;14017;backported_wolves;Backported Wolves;;\nbetweenlands-extended-config;14018;blextcfg;Betweenlands Extended Config;;\n;14019;pixel_glasses_plus;像素墨镜Plus;Pixel Glasses Plus;\ndeeper-caves;14020;cavesanddepths;Deeper Caves;;\n;14021;Aviancraft_Seriemas;鸟语星球_叫鹤目;Aviancraft: Seriemas (Terror birds & their relatives);\n;14022;;匠魂3基岩版;Tinkers' Construct 3 Addon / Tinkers' Awakening;\n;14023;modularwarfare;逃离我的世界;EscapeFromMinecraft;EFMC\narborfirmacraft;14024;afc;林海传说;ArborFirmaCraft;AFC\nenchanting-with-tfc;14025;enchanting_with_tfc;Enchanting With TFC;;\ntfc-vessel-tooltip;14026;tfcvesseltooltip;TFC Vessel Tooltip;;\nnether-gold-ore;14027;nethergoldore;Nether Gold Ore;;\nnatural-nether-portals;14028;naturalnetherportals;Natural Nether Portals;;\n;14029;vanillapotionsplus;VanillaPotions+;;\n;14030;wait_of_the_world;Wait of The World;;WOTW\n;14031;tweezers+reweaving;镊子重制版;Tweezers Reweaving;\nhotbaaaar;14032;hotbaaaar;忄夬扌疌木兰;HotBaaaar;\nui;14033;uilib;UI Lib;;\n;14034;random_item;Random Items;;\nlucky-clover;14035;luckyclover;Lucky Clover;;\n;14036;betterportals;更好的传送门;Better Portals;BP\nsocial-distance;14037;social_distance;社交距离;Social Distance;SD\ngt;14038;gtnn;GT--;;gtnn\nhunger-plus;14039;hunger_plus;饥饿+;Hunger Plus;\nmelee-ex-machina-meexma;14040;melee_ex_machina;Melee Ex Machina;;\n;14041;swdheftpywaedf;对不起，我们没有充足的资金为您提供一只末影龙-1.18;Sorry We Don't Have Enough Funds To Provide You With An Ender Dragon in 1.18;swdheftpywaedf\narc;14042;arc;Arc Lib;;\ncores;14043;cores,dev.anye.mc.cores;Cores;;\n;14044;jkvb;消失的方块;Vanishing Blocks;\ncitymod-traffic-and-life;14045;citymod;我的城市：交通与生活;CityMod:Traffic and life;CM\ntoo-hard-to-dig;14046;h2d;隐岩砥凿;Too Hard to Dig;\nsimplecobblestonegenerator;14047;simplecobblegen;Simple Cobblestone Generator;;\nnatural-baby-animals;14048;naturalbabyanimals;Natural Baby Animals;;\nessence-mod;14049;essence;Essence;;\nshield-expansion-expansion;14050;shieldexex;Shield Expansion Expansion;;\n;14051;all_item;全物品收集进度;All Item Collection Advancements;AICA\nalexs-caves-trimmed;14052;alexscaves_trimmed;Alex's Caves: Trimmed!;;\ntotem-of-immortality;14053;toimmortality;Totem of Immortality;;\ncrystalline-ender-eye;14054;c_ender_eye;晶化末影之眼;Crystalline Ender Eye;CEE\nars-caelum;14055;ars_caelum;Ars Caelum;;\nelytra-chestplates;14056;elytra-chestplates;鞘翅之甲;Elytra Chestplates;\nvariants-and-ventures;14057;variantsandventures;变种与冒险;Variants & Ventures;\nhorseshoes;14058;horseshoes;马掌;Horseshoes;\nquestlines;14059;questlines;Questlines;;\nendofherobrine;14060;endofherobrine;The End of Herobrine;;\nthirstcanteen;14061;thirstcanteen;口渴-水壶;ThirstCanteen;\nthaumic-energistics-extended-life-fork;14062;thaumicenergistics;神秘能源延续版分支;Thaumic Energistics Extended Life Fork;\n;14063;SkillsSwitch;职业开关;Skills Switch;SS\n;14064;ymjb_login;登录;ymjblogin;\nprehistoric-nature-decorations;14065;lepidodendron;Prehistoric Nature Decorations;;\ntinyfoes;14066;tinytabs;Tiny Foes;;\npyradium;14067;;Pyradium;;\nanvilcraft;14068;anvilcraft;铁砧工艺：重力科技;AnvilCraft;ANC\ntiny-mob-farm-remastered;14069;tinymobfarm;迷你刷怪场：重制;Tiny Mob Farm Remastered;\ncreate-aquatic-ambitions;14070;create_aquatic_ambitions;机械动力：瀚海雄心;Create: Aquatic Ambitions;\nffl-trinkets-compat;14071;ffl-trinkets-compat;FFL Trinkets Compat;;\nkaleidios-guns;14072;kaleidiosguns;Kaleidio's Guns;;\nmc-vr-playground;14073;mc_vr_playground;MC VR Playground;;\ncosmopolis-mod;14074;cosmopolis;Cosmopolis Mod;;\ncaveopolis-mod;14075;caveopolis;Caveopolis;;\nflexipause;14076;flexipause;FlexiPause;;\ngeysers;14077;geysers;间歇泉;Geysers;\n;14078;moar_enchantments;MOAR Enchantments;;\n;14079;lose_your_touch;Lose Your Touch;;LYT\nman-of-many-planes;14080;man_of_many_planes;Man of Many Planes;;\ncaliber-lib;14081;caliberlib;Caliber Lib;;\nkriforfab;14082;kriforfab;Kriforfab;;\nmultiscoreboard;14083;multiscoreboard;多重计分板;MultiScoreboard;\n;14084;minerally_me;Minerally Me!;;\n;14085;cover_them_in_armor;Cover Them In Armor;;CTIA\njerotes-village;14086;jerotesvillage;Jerotes村庄-二轮世界;Jerotes Village - Second Round World;JV\n;14087;acd_nether;A Cold Day In The Nether;;\ntranslucencyfix;14088;translucencyfix;Translucency Fix;;\ncreate-ponder;14089;createponder;机械动力：思索;Create Ponder;CP\nmopeds;14090;moped;Mopeds;;\n;14091;dr;药水注入;Drugged;DR\nadvanced-shulkerboxes;14092;advanced-shulkerboxes;Advanced Shulkerboxes;;\n;14093;;Noodle World Generation Experiment;;\n;14094;mr_vanilla_stoneblock;Vanilla Stoneblock;;\nvisible-shield-cooldown;14095;visible_shield_cd;可视化盾牌冷却;Visible Shield Cooldown;VSC\nmns-moogs-nether-structures;14096;mns;Moog's Nether Structures;;MNS\nbunker-down;14097;bunker_down;Bunker Down;;\njust-enough-guns;14098;jeg;Just Enough Guns;;JEG\nbuildify;14099;buildify;Buildify;;\n;14100;bigglobe;Big Globe;;\ndragon-finder;14101;dragon-finder;Ice and Fire: Dragon Finder;;\nrespackopts;14102;respackopts;Respackopts;;\nfortuitous-feasts;14103;dffeasts;Fortuitous Feasts;;\nmonazite;14104;monazite;独居石;Monazite;\nmusic-control;14105;music_control;音乐控制;Music Control;\nstellarcore;14106;stellar_core;星核;StellarCore;\n;14107;cp;自定义Ping;CustomPing;CP\nash-of-sin-custom-entity-attack-effect;14108;ash_of_sin_custom_entity_attack_effect;罪业余烬：自定义实体攻击状态效果;Ash Of Sin: Custom Entity Attack Effect;\nash-of-sin-soul-like-boss-battle;14109;ash_of_sin_soul_like_boss_battle;罪业余烬：类魂BOSS战;Ash Of Sin: Soul Like Boss Battle;\nwh40kmc-project;14110;warhammer40kmod;Hammercraft 40k;;\n;14111;snowwaifu;雪の嫁;Snow Waifu;\npixelmon-bank;14112;pixelmonbank;Pixelmon 银行;Pixelmon Bank;\n;14113;loose-litematica;Loosen Litematica;;\nwsmc;14114;wsmc;WSMC;;\nno-gui-chest;14115;noguichest;沉浸箱子;No Gui Chest;NGC\n;14116;mr_warden_withloot;Warden With Loot;;\nfiledirector;14117;moddirector;File Director;;\nextra-compat;14118;extra-compat;Extra Compat;;\neu-converter;14119;euconverter;EU Converter;;\nroyal-variations;14120;royalvariations;Royal Variations;;\n;14121;probezs;ProbeZS;;\ntemporal-api;14122;temporal-api;Temporal API;;\n;14123;defender,underworld_magic_craftsmanship;幽法工艺;Underworld Magic Craftsmanship;UMC\n;14124;mrblade;技术革新;TRblade;\ncallable-horse-unofficial-fabric-port;14125;callablehorsefabric;召之马来 Fabric版;CallableHorse Unofficial Fabric port;\n;14126;bce;更好的战斗体验;Better Combat Experience;BCE\nshiny-horses-forge-enchantable-horse-armor;14127;shiny-horses-forge-enchantable-horse-armor;Shiny Horses Forge - Enchantable Horse Armor;;\nspartan-weaponry-simpleores;14128;spartansimpleores;斯巴达的武器：简单矿石;Spartan Weaponry: SimpleOres;\n;14129;jbplus;JointBlockPlus;;\ndye-depot;14130;dye_depot;Dye Depot;;\n;14131;pixelradar;宝可梦雷达;PixelRader;\n;14132;autoharvest;自动收获-重制;AutoHarvest-RE;\n;14133;nyan_cat_loading;Nyan Cat Loading;;\nhomeward-bound;14134;homeward-bound;Homeward Bound;;\nsword-blocking-mechanics;14135;sword-blocking-mechanics;Sword Blocking Mechanics;;SB\nelectric-mace;14136;electric-mace;Electric Mace;;\nmace-backport;14137;mace_backport;重锤移植;Mace Port;\n;14138;steveeatstew;Steve Eat Stew;;\nimaginary;14139;imaginary;Imaginary;;\nkuvalich;14140;kuvalich;赤毒玄骸;KuvaLich;KL\nae2-import-export-card;14141;ae2insertexportcard,ae2importexportcard;AE2输入输出卡;AE2 Import Export Card;\n;14142;magick_experience;Magick Experience;;\n;14143;sz;超级变焦;SuperZoom;SZ\n;14144;repairable_disc_11;Repairable Disc 11;;\n;14145;structure_export;结构渲染导出;Structure Export;SE\n;14146;fabric-permissions-api-v0;Fabric Permissions API;;\n;14147;khinkalimod;Khinkali;;\nconfigured-defaults;14148;configured-defaults;Configured Defaults;;CD\nbens-sharks;14149;sharks,benssharks;Ben's Sharks;;\n;14150;equipset;装备预设;EquipSet;EQS\ntorches-in-water;14151;torches_in_water;水中火把;Torches in Water;TiW\n;14152;olderskins;还原旧皮肤;OlderSkins;ODS\n;14153;cllb;区块渲染距离突破;ChunkLoadingLimitBreached;CLLB\npigs-with-fireworks;14154;pigs_with_fireworks;Pigs with Fireworks;;\ninfinityspell;14155;infinityspell;无限魔咒;InfinitySpell;IS\nmultipart-machines-grinding;14156;mm_grinding;Multipart Machines: Grinding;;\nash-of-sin-custom-anti-high-atk-entity;14157;ash_of_sin_custom_anti_high_atk_entity;罪业余烬：自定义反高攻击力实体;Ash Of Sin: Custom Anti High ATK Entity;\n;14158;;基洛的植物大战僵尸模组;Gero's Plants vs. Zombies Add-On;GPVZA\n;14159;bioforge;BioForge;;\nquad;14160;quad;Quad;;\n;14161;another;Another;;At\n;14162;rws;移除欢迎界面;RemoveWelcomeScreen;RWS\n;14163;badd;Boom++;;BA\n;14164;custom_boss_bar;自定义BOSS血条;Custom BOSS Bar;CBB\n;14165;cgmfix;CGM修复;CGMFix;\nmakit-better;14166;makit_better;Makit Better;;MKB\nmacaws-for-terrafirmacraft;14167;mcw_tfc_aio;All-In-One, Macaw's Mods for TerraFirmaCraft;;\n;14168;;考古幸运方块;;\n;14169;;史蒂夫在红白机;;\n;14170;muit;MusicIt;;\n;14171;blahaj-totem;Blåhaj of Undying;;\ncorals-tfc;14172;coralstfc;Corals TFC;;\ntfc-aged-alcohol;14173;tfcagedalcohol;TFC Aged Alcohol;;\ntfc-improved-badlands;14174;tfcimprovedbadlands;TFC Improved Badlands;;\n;14175;russianmetro;Russian Metro Addon;;RMA\nwinter-massacre;14176;winter-massacre;Winter Massacre;;\nghostlys;14177;ghostly;幽灵;Ghostly;\ntectonic-tweak;14178;tectonic_tweak;Tectonic-Tweak;;\nforge-essentials-client;14179;forgeessentialsclient;Forge Essentials Client;;\n;14180;world-host,world_host;World Host;;\n;14181;letsimaginate;纸袋剧场！;Let's Imaginate!;\nspit-splat;14182;splat;唾溅兽;Spit Splat;\nmaced;14183;maced;重锤一击;Maced;\nnether-villager-trader;14184;nether-villager-trader;Nether Villager Trader;;\n;14185;no-exp-enchanting-reimagined;NoExpEnchantingReimagined;;\nodyssey-claims;14186;cadmus;Odyssey Claims/Cadmus;;\nodyssey-allies;14187;argonauts;Odyssey Allies/Argonauts;;\ngiacomos-travelogue;14188;giacomostravelogue;Giacomo's Travelogue;;\ngiacomos-maps;14189;giacomos_maps;Giacomo's Maps;;\ndescension;14190;descension;Descension;;\n;14191;treasure;财宝;Treasure;\n;14192;startup-time;启动时间;Startup Time;\n;14193;orikingcraft;未知之境:装饰品;OrikingCraft;sudeco\n;14194;littermanagement;掉落物管理;LitterManagement;LM\ntoggledpickup;14195;toggledpickup;拾取控制;Toggled Pickup;\nfarmers-delight-refabricated;14196;farmersdelight;农夫乐事：重织;Farmer's Delight Refabricated;\nvillage-nullifier-terralith-mod;14197;mr_village_nullifierterralith;Village Nullifier: Terralith;;\nvillage-nullifier;14198;mr_village_nullifier;Village Nullifier;;\nstructure-credits;14199;structurecredits;Structure Credits;;\ni-wanna-travel;14200;i_wanna_travel;i Wanna Travel;;IWT\nsimple-unbreakable-tools;14201;gk_unbreakable;Simple Unbreakable Tools;;\ncat-loaf;14202;catloaf;Cat Loaf;;\ncopper-tuff-backport-fabric;14203;copperandtuffbackport;Copper & Tuff Backport;;\n;14204;f3scale;F3Scale;;\ngiacomos-teleport;14205;giacomos_teleport;Giacomo's Teleport;;\ngiacomos-farmland;14206;giacomos_farmland;Giacomo's Farmland;;\nback-weapon-slot;14207;curiosbackslot;Back Weapon Slot;;\nchested-companions;14208;chestedcompanions;Chested Companions;;\ncreate-train-perspective-fix;14209;trainperspectivefix;Create: Train Perspective Fix;;\ncreate-metalwork-fabric;14210;createmetalwork;Create: Metalwork;;\ntragicmc;14212;TragicMC;悲惨世界1;TragicMC;\nauditory-continued;14213;auditory;Auditory Continued;;\n;14214;elb;附魔等级上限突破;EnchantmentLevelBreakthrough;ELB\ncreate-more-copycats;14215;create_more_copycats;Create: More Copycats;;\nfarlands-reborn;14216;farlandsreborn;Farlands Reborn;;\n;14217;mangrove_ruins;Mangrove Ruins;;\n;14218;royge;Royge: Rainbow Dimension;;\nmomento;14219;momento;Momento;;\nodyssey-roles;14220;prometheus;Odyssey Roles / Prometheus;;\n;14221;xb;喜报--;XiBao--;XB\nwaystones-jawsawn-fork;14222;;传送石碑：Jawsawn版;Waystones Jawsawn Fork;\nnautilium-spirits;14223;nautilium_spirits;鹦鹉螺重置版;Nautilium Spirits;NS\n;14225;telluriumforge;TelluriumForge;;\naquatweaks;14226;AquaTweaks;水下微调;AquaTweaks;\naquatweaks-compat;14227;aquatweakscompat;水下微调：兼容;AquaTweaks Compat;\n;14228;potionsreglint;Potions Re-Glint;;\nsimpletextoverlay;14229;simpletextoverlay;Simple Text Overlay;;\nbountiful-fares;14230;bountifulfares;丰饶食记;Bountiful Fares;\n;14231;ancient_debris_tweak;远古残骸微调;Ancient Debris Tweak;ADT\n;14232;milk-bottle;牛奶瓶;Milk Bottle;\n;14233;wauslt;？话说好好能不能;shiT ekiL gnikaepS U erA yhW;WAUSLT\ndeer-jar-dweller;14234;crydolph;Crydolph;;\nanti-id-conflict;14235;antiidconflict;反ID冲突;Anti Id Conflict;AIC\nconfighelper;14236;confighelper;配置助手;config helper;\nbetter-boat;14237;betterboat;更好的船;Better Boat;\nweighted-inventories;14238;weighted_inventories;物品栏重量;Weighted Inventories;\ndetected-setblock-be-gone;14239;dsbg;Detected setBlock Be Gone;;DSBG\n;14240;Hexical;Hexical;;\ntabula-minecraft-modeler;14241;tabula;Tabula;;\nglow-sticks;14242;glow_sticks;荧光棒;Glow Sticks;\nalt-hud;14243;althud;Alt HUD;;\n;14244;lmrebellion;LittleMaidRebellion;;\nfurniture-expanded;14245;furnitureexpanded;Furniture Expanded;;\ncelestora;14246;celestora;Celestora;;\ngeckolib-unofficial-1-7-10;14247;;Geckolib：非官方版;Geckolib-Unofficial;\ncnpc-custom-model-addon;14248;npcgecko;自定义NPC+： Gecko 附属;CNPC+ Gecko Addon;\ncnpc-armorers-workshop-addon;14249;npcaw;自定义NPC+ ：时装工坊附属;CNPC+ Armorer's Workshop Addon;\n;14250;mr_bearedit;BearEdit;;\nfusion-smithing;14251;fusion-smithing;融锻;Fusion Smithing;FS\nleos-loot-bag;14252;loot-bag;战利品袋;Loot Bag;LB\nadventure-dimensions;14253;advdims;冒险维度;Adventure Dimensions;\nsimple-dimensions;14254;simpledimensions;简单维度;Simple Dimensions;\nportals-gui;14255;portalsgui;传送门GUI;Portals Gui;\ninterworlds-splashscreens;14256;interworldsplashscreen;进入维度加载界面;Interworlds Splashscreens;\n;14257;creativemenutweaks;Creative Menu Tweaks;;\nvampires-delight;14258;vampiresdelight;血族乐事;Vampire's Delight;\nthats-my-bed;14259;thatismybed;That's My Bed!;;\ncaligo;14260;caligo;Caligo;;\n;14261;sharpened_swords;Sharpened Swords;;\n;14262;yuzukitools;YuzukiTools;;\ndonut-and-mochi;14263;donutmod;Donut and Mochi;;\nducky;14264;ducky;Ducky;;\nlost-idols;14265;lost-idols;Lost Idols;;\nsimple-trashcan;14266;simple_trashcan;Simple Trashcan;;\ndarwin-water-ambience;14267;watermod;Darwin Water Ambience;;DWA\ndecorative-wooden-lattices;14268;decorativewoodenlattices;Decorative Wooden Lattices;;\napotheotic-additions;14269;apotheotic_additions;Apotheotic Additions;;\nlithosphere;14270;welded_,mr_lithosphere;Lithosphere;;\n;14271;futurecommands;未来指令;Future Commands;\nbuild-your-campfire;14272;better_campfires;Build Your Campfire;;\nbiome-decoration-fix;14273;alreadydecoratingfix;Biome Decoration Fix;;\nsimply-tools;14274;simplytools;Simply Tools;;\nimmersive-snowgen;14275;immersivesnow;沉浸式雪生成;Immersive Snowgen;ISg\nthe-wandering-gambler;14276;the_wandering_gambler_ii;The Wandering Gambler;;\ncheatdetector;14277;cheatdetector;作弊检测器;CheatDetector;CD\njust-another-spawner;14278;JustAnotherSpawner;Just Another Spawner;;JAS\nstack-size-edit-fabric;14279;stacksizeedit;Stack Size Edit;;\n;14280;remoteresourcepack;远程资源包;Remote Resource Pack;RRP\n;14281;luckydraw;幸运彩券;Lucky Draw;\nbattery-shield;14282;battery_shield;电池护盾;Battery Shield;\ngiacomos-chat-fix;14283;giacomos_chat_fix;Giacomo's Chat Fix;;\ngiacomos-compass;14284;giacomoscompass;Giacomo's Compass;;\ngiacomos-experience-drainer;14285;giacomos_experience_bottler;Giacomo's Experience Bottler;;\n;14286;frank;Frank;;\nload-my-resources-forge;14287;loadmyresources;Load My Resources;;LMR\napotheotic-creation;14288;apotheoticcreation;Apotheotic Creation;;\n;14289;seared_furnace_calculator;匠魂计算器;Seared Furnace Calculator;\n;14290;nature;Nature's Spirit Addon;;\neiorecipesteinductionsmelter;14291;eiorteis;EIORecipesTEInductionSmelter;;\n;14292;rit;随机物品名称;RandomItemText;RIT\nash-of-sin-better-ai;14293;ash_of_sin_better_ai;罪业余烬：更好的AI;Ash Of Sin: Better AI;\ndimensional-cake-rebuilt;14294;dimensionalcake;Dimensional Cake Rebaked;;\nmedieval-village-dimension;14295;lostworldsmpmod;Medieval Village Dimension;;\ncreate-big-contraptions;14296;bigcontraptions;Create: Big Contraptions;;\ngtnhs-forgelin;14297;;GTNH's Forgelin;;\nrose-gold-equipment;14298;rosegoldequipment;Rose Gold Equipment;;\n;14299;mr_qraftys_mangrovevillages;qrafty's Mangrove Villages;;\npantheonsent;14300;pantheonsent;PantheonSent;;\n;14301;yukkuri;Touhou Yukkuri;;\n;14302;asrp;反服务器资源包加载;AntiServerResourcesPack;ASRP\n;14303;icc;无限聊天字符长度;InfinityChat;IC\n;14304;moddetectionpreventer;Mod Detection Preventer;;\ndramatic-spawning;14305;dramatic_spawning;Dramatic Spawning;;\nall-mobs-attack-villagers;14306;allmobsattackvillagers;All Mobs Attack Villagers;;\ncicada;14307;cicada;CICADA;;\nimmersive-cvm;14309;immersive_cvm;Immersive CVM;;\nno-gui-fd;14310;noguifd;沉浸乐事;No Gui FD;NGFD\ncustom-credits;14311;customcredits;Custom Credits;;\nfishing-life;14312;fishinglife;钓鱼生活;FishingLife;\n;14313;vanilla_craft;Zyx的随意之作;Zyx's Random Creation;ZRC\n;14314;cca;Crystal Carpet Addition;;CCA\nhoes-are-scythes;14315;hoes-are-scythes;Hoes Are Scythes;;\n;14316;pehkuirandomsize;PehkuiRandomSize;;\nchat-ping;14317;chatping;Ping!;;\n;14318;lance;骑枪;Lance Mod;\ndamage-number;14319;damage_number;伤害数字显示;Damage Number;\n;14320;jkat;关于传送;About Teleport;jkat\nmineshot-reforged;14321;mineshotreforged;高清截图Forge;Mineshot Reforged;\nkrysztal-language-scala;14322;krysztal-language-scala;Krysztal's Language Scala;;KLS\nfilters-api;14323;filters;Filters API;;\n;14324;ibs;虫蚀方块;InfestedBlocks;IBS\nflavored;14325;flavored;Flavored;;\nhuesodewiki;14326;huesodewiki;HuesoDeWiki / Wiki Bone;;\nplatos-transporters;14327;platos;Plato's Transporters;;\nplushie-reeker-mod;14328;lil_reeker_mod;Plushie Reeker;;\nsemi-hardcore;14329;semihardcore;Semi-Hardcore;;\nplaceables;14330;placeables;Placeables;;\nbigbanana;14331;bigbanana;🍌大香蕉🍌;BigBanana;BB\n;14332;dabaosword;大宝刀;DabaoSword;\nlook-eye-of-cthulhu;14333;look;Look;;\n;14334;salmonberryaddition;Salmonberry Addition;;\npet-collecting;14335;petcollecting;Pet Collecting;;\n;14336;yummyglass;Yummy Glass;;\nrepurposed-structures-biome-makeover-compat;14337;repurposed_structures_biome_makeover_compat;结构变体：生物群系改造兼容数据包;Repurposed Structures - Biome Makeover Compat Datapack;\ntotallybalancedfallflying;14338;tbff;Totally Balanced Fall Flying;;\ngiacomos-experience-seedling;14339;giacomos_exp;Giacomo's Experience Seedling;;\n;14340;server-notify;Server Notify;;\nconfig-api;14341;configapi;Config API;;\nnethers-delight-refabricated;14342;nethersdelight;下界乐事：重织;Nethers Delight Refabricated;\n;14343;pumpkin_pie;Pumpkin Pie Delight;;\nruniclib;14344;tipsylib;RunicLib/TipsyLib;;\ndragon-mounts-more-dragons;14345;moredragons;龙骑士附属：更多龙;Dragon Mounts: More Dragons;\nrepurposed-structures-sawmill-compat;14346;;结构变体：锯木机兼容数据包;Repurposed Structures - Sawmill Compat Datapack;\nrepurposed-structures-irons-spells-n-spellbooks;14347;;结构变体：Iron's Spells 'n Spellbooks 兼容数据包;Repurposed Structures - Iron's Spells 'n Spellbooks Compat;\nhey-wiki;14348;heywiki;Hey Wiki;;\nturf;14349;turf;Turf;;\nad-extendra;14350;ad_extendra;Ad Extendra;;\nsmava;14351;smava;Smava Creepers;;\nzwieback;14352;zwieback;Zwieback;;\nrandom-respawn;14353;randomrespawn;Random Respawn;;\n;14354;telluriumsrandomstuff;Tellurium's Random Stuff;;\nper-aspera;14355;per_aspera;Per Aspera;;\nspice-of-life-valheim-edition;14356;sol_valheim;Spice of Life: Valheim Edition;;\ntfc-paths;14357;tfcpaths;TFC Paths;;\nbinkers-bonstruct;14358;tconstruct;Binkers' Bonstruct for TFC;;\nranged-weapon-api;14359;ranged_weapon_api;Ranged Weapon API;;\nnumismatics;14360;numismatics;Create: Numismatics;;\n;14362;sleepatease;安心睡觉;SleepAtEase;SAE\ncanned-foods-and-drinks;14363;cannedfoodanddrinks;Canned Foods and Drinks;;\nalexs-caves-dimension;14364;alexscavesdimension;Alex's Caves Dimension;;\nvillager-contracts;14365;villagercontracts;Villager Contracts;;\nbetter-agriculture;14366;betteragriculture;Better Agriculture;;\nsoul-shards-respawn-patched;14367;soulshardsrespawn;Soul Shards Respawn Patched;;\ntfc-finger-in-the-wind;14368;fitw;TFC Finger In The Wind;;\nimmersive-craft-patched;14369;immcraft;Immersive Craft Patched;;\njockeys;14370;jockeys;Spooky Scary Jockeys;;\ndisguiselib;14371;disguiselib;DisguiseLib;;\n;14372;mrp;MTR 资源包保护工具;MTR Resource Protector;MRP\nstation-architecture;14373;project_xena;Station Architecture / Project Xena;;\n;14374;jqcity;界秋MTR铁路及道路拓展;Jie Qiu Metro Addon;JMA\nenchantmentrequirements;14375;enchantmentrequirements;Enchantment Requirements;;\nenchantment-enhancements;14376;enchantment_enhancements;Enchantment Enhancements;;\nmo-enchantments;14377;enchantments_plus;Mo' Enchantments;;\nsoul-like-enchantment;14378;soullike_enchantment;soul-like enchantment: new weapon enchants;;\n;14379;gravityfix;Gravity Fix;;\n;14380;blockbench-import-library,bil;Blockbench Import Library;;\nmrcrayfish-furniture-mod-rebuilt;14381;;MrCrayfish's Furniture Mod Rebuilt;;\nbtfixes;14382;btfixes;Battle Towers Fixes;;\nvmfixes;14383;vmfixes;VM Fixes / VoxelMap Fixes;;\npillar-patched;14384;pillar;Pillar Patched;;\nnovalogin;14385;novalogin;新星登录;Nova Login;NL\ntfc-desire-paths;14386;tfcdesirepaths;TFC Desire Paths;;\ntfc-ore-washing;14387;tfcorewashing;TFC Ore Washing;;\ntfc-canes;14389;tfccanes;TFC Canes;;\nlhmob-difficulty-increase;14390;lhmobdifficultyincrease;变强的怪物;LH Mob Difficulty Increase;\n;14391;zombiesarereal;Zombies Are Real Reforged;;\nfrom-the-shadow-reborn;14392;fromtheshadows;From The Shadows Reborn;;\n;14393;life-crystals;Life Crystals;;\n;14394;awakened_creeper;苦力怕觉醒了;AwakenedCreeper;\nthe-slumbering-omen;14395;the_slumbering_omen;沉睡的预兆;The Slumbering Omen;\n;14396;advanced_equip;进阶刀剑;AdvancedEquip;AE\n;14397;more_weapons;武器多样化;More Weapons;\nftb-guides-forge;14398;ftbguides;FTB Guides;;\n;14399;titanium;钛;Titanium;\ncaelum;14400;caelum;Caelum;;\nclearspam;14401;clearspam;ClearSpam;;\nad-astra-rocketed;14402;ad_astra_rocketed;Ad Astra: Rocketed;;\nsoulbound-quilt;14403;soulbound;Soulbound;;\ntinyinv;14404;tinyinv;TinyInv;;\nanimationjs;14405;animationjs;AnimationJS;;\nraids-backport;14406;raids;Raids Backport;;\ncrossbows-backport;14407;crossbows;Crossbows Backport;;\ntfc-caelum;14408;tfccaelum;TFC Caelum;;\nepic-compat-parcool;14409;epiccompat_parcool;Epic Compat: ParCool;;\nloqui;14410;loqui;Loqui;;\nreal-worlds;14411;real_worlds;Real Worlds;;\n;14412;ba;Bilibili音频;BilibiliAudio;BA\ndragonlib;14413;dragonlib;DragonLib;;\nandromeda;14414;andromeda;群星;Andromeda;AM\n;14415;awakened_phantom;幻翼觉醒了;AwakenedPhantom;\n;14416;superauth;超级授权;SuperAuth;\nmobdisguises;14417;mobdisguises;MobDisguises;;\ninventory-generators;14418;inventorygenerators;Inventory Generators;;\ncreating-space-gravity-addon;14419;creating_space_gravity_addon;Creating space gravity addon;;\nalexs-caves-torpedoes;14420;alexs_caves_torpedoes;Alex 的洞穴：物品 & 鱼雷;Alex's Caves: Stuff & Torpedoes;\n;14421;passivesystemskill;被动熟练度系统;Passive System Skill;PSS\n;14422;pillow;枕头模组加载器;Pillow Mod Loader;\n;14423;slaviccuisine;Slavic Cuisine;;\ntfc-tumbleweed;14424;tfc_tumbleweed;TFC Tumbleweed;;\ntfc-barrens;14425;tfcbarrens;TFC Barrens;;\nchiselse-bits-for-tfc;14426;chiselsandbits_tfc;Chisels & Bits for TFC;;\ntfc-fresh-water-everywhere;14427;tfcfreshwatereverywhere;TFC Fresh Water Everywhere;;\ntfc-more-swords;14428;tfckatana;TFC More swords;;\ngalleria;14429;galleria;Galleria;;\nconstructs-casting;14430;constructs_casting;Construct's Casting;;\ntinkers-innovation;14431;tinkersinnovation;工匠创新;Tinkers' Innovation;TI\n;14432;weirdoenchantmaster;古怪附魔师;WeirdoEnchantMaster;\ndelicious-delights;14433;deliciousdelights;Delicious Delights;;\n;14434;vietnamsdelight;Vietnam's Delight;;\nharvest-tweaks;14435;harvesttweaks;Harvest Tweaks;;\ndecay-2012;14436;decay_2012;Decay 2012;;\ntfc-grooming-station;14437;tfcgroomer;TFC Grooming Station;;\nterrafirmagregtech-ores;14438;tfg_ores;TerraFirmaGregTech - Ores;;\nblaze-map;14439;blazemap;Blaze Map;;\nepic-knights-japanese-armory;14440;epic_knights__japanese_armory;Epic Knights : Japanese Armory;;\ntfcpyros-adjustedgen;14441;tfcpyros_adjustedgen;TFCPyros-AdjustedGen;;\nastikor-carts-tfc-florae;14442;astikorcartstfcf;Astikor Carts TFC Florae;;\nexp-counter-fabric-forge-neoforge;14443;expcounter;EXP Counter;;\nlightwithin;14444;lightwithin;LightWithin;;\nthe-slenderman-mod;14445;slendermod;The Slenderman Mod;;\nflanchanmod;14446;flanchanmod;Cute Flanchan !!;;\n;14447;awakened_spider;蜘蛛觉醒了;Awakened Spider;\ncartography;14448;cartography;Cartography;;\nlaser-bridges-doors;14449;laserbridges;Laser Bridges & Doors;;\nlukis-grand-capitals;14450;l_g_c,mr_lukis_grandcapitals;Luki's Grand Capitals;;\nfabric-create-netherless;14451;create_netherless;Create: Netherless (Fabric);;\ncreate-connected-fabric-experimental;14452;;Create: Connected Fabric;;\n;14453;antiquecities;Antique Cities;;\nwastify;14454;wastify;Wastify;;\nstarter-structure;14455;starterstructure;初始建筑;Starter Structure;\n;14456;safety_backpack;安全背包;Safety Backpack;\nmobblocker;14457;mobblocker;MobBlocker;;\ngiacomos-foundry;14458;giacomos_foundry;Giacomo's Foundry;;\n;14459;blocky_bass;Blocky Bass;;\n;14460;toms_mobs;Tom's Mobs;;\n;14461;homoium;Homoium;;\nalex-cave-addon;14462;alex_cave_addon;Alex Cave Addon;;\n;14463;enchanting;妙手偶得;Enchanting;\n;14464;awakened_enderman;末影人觉醒了;AwakenedEnderman;\niced-lava;14465;iced_lava;Iced Lava;;\nfabric-core-mods;14466;gud_fcm;Fabric Core Mods;;FCM\nthe-tropics-telepastry;14467;tropics_telepastry;The Tropics TelePastry;;\ncarrot-apple;14468;carrotapple;胡萝卜苹果;Carrot Apple;\nbaked-delight;14469;bakeddelight;烘烤乐事;Baked Delight;\ncreate-basic-additions;14470;create_basic_additions;Create Basic Additions;;\nhorse-stats-and-more;14471;giacomos_horse_stats;Horse Stats and More / Giacomo's Horse Stats Mod;;\ngiacomos-hud-overlays-configurator;14472;giacomos_hud_conf;Giacomo's HUD Overlays Configurator;;\ngiacomos-map-coloring;14473;giacomos_map_coloring;Giacomo's Map Coloring;;\nec-epic-fight-compat;14474;ec_ef_plugin;EC Epic Fight Compat;;\neasy-npc-epic-fight;14475;easy_npc_epic_fight;Easy NPC - Epic Fight;;\npuffish-attributes;14476;puffish_attributes;Pufferfish's Attributes;;\npufferfishs-unofficial-additions;14477;pufferfish_unofficial_additions;Pufferfish's Unofficial Additions;;\nsmart-completion;14478;smartcompletion;Smart Completion;;\nadditional-attributes;14479;additional_attributes;Additional Attributes;;\n;14480;hexed;Hexed;;\n;14481;mordenwindows;现代窗户;;MW\n;14482;elainalike;魔女之绘：重生;ElainalikeReborn;\n;14483;exnihilodraconicextendo;无中生有：传承 - 龙之研究扩展;ExNihilo-Draconic-Extend;ENDE\nsuicide-key;14484;suicide_key;Suicide Key;;\n;14485;tfcaf;群峦自动锻造;TFCAutoForging;TFCAF\nuranus;14486;uranus;Uranus;;\nannotationlib;14487;annotation_lib;AnnotationLib;;\n;14488;waxed;Waxed & Shiny;;\nnether-overload;14489;netheroverload;Nether Overload;;\nender-relay;14490;ender_relay;Ender Relay;;\nenchanted-effects;14491;enchantedeffects;Enchanted Effects;;\nemi-create-schematics;14492;emi_create_schematics;EMI: Create Schematics;;\nthe-silence;14493;the_silence;The Silence;;\ninfinite-music-fabric;14494;infinite-music;Music Delay Remover (Infinite Music);;\nkakapos;14495;kakapos;Kakapos;;\n;14496;tomereader;Tome Reader;;\nscary-creepers;14497;scary-creepers;Scary Creepers;;\n;14498;show-my-skin-parts;Show My Skin Parts;;\nsn0wfrogs-capybaras;14499;sn0wfrogs_capybaras;Sn0wfrog's Capybaras;;\nenemybanner;14500;enemybanner;敌怪旗;EnemyBanner;\n;14501;obitoblade;拟态;Mimetic;\nsouls-egg;14502;soulsegg;Souls Egg;;\nbarely-enough-enchantments;14503;barelyenoughenchantments;Barely Enough Enchantments;;BEE\ntool-kit;14504;toolkit;Tool Kit;;\n;14505;filament;Filament;;\nlijms-expanse;14506;lijms_expanse;Lijm's Expanse;;\ntinker-delight;14507;tinkersdelight;匠魂乐事;Tinkers Delight/Tinkers Construct Delight;\nbarbeques-delight;14508;barbequesdelight;烧烤乐事;Barbeque's Delight;\ncoffeecraft-by-blockbrothers;14509;coffeecraft;CoffeeCraft;;\nhearty-meals;14510;heartymeals;Hearty Meals;;\n;14511;mincar_drill;Minecar Drill;;\n;14512;;放置器、破坏器与旋转器;Placer, Breaker and Rotator;PBR\nenderfake;14513;enderban;Enderfake;;\n;14514;always-online;Always Online;;\n;14515;guide_to_afterlife;蝶火燎原;Guide to Afterlife;\n;14516;awakened_silverfish;蠹虫觉醒了;AwakenedSilverfish;\nrawore-on-1-12-2;14517;suikerawore;1.12.2的粗矿;rawOre_on_1.12.2;\nentityjs;14518;entityjs;EntityJS;;\npoem-of-star-wars;14519;poemofstarwars;Poem o' Star Wars;;PoSW\n;14520;portalhexaddon;Hex Ways;;\nvanity-aesthetic-armory;14521;vanity_aesthetic_armory;Vanity: Aesthetic Armory;;\n;14522;muffins_thaidelight;Muffin's Thai's Delight;;\n;14523;more-useful-copper;更有用的铜;MoreUsefulCopper;MUC\n;14524;did;迷宫饭;Delicious In Dungeon;DID\nanimals-plus;14525;animals_plus;Animals Plus;;\ngiacomos-map-merging;14526;giacomos_map_merging;Giacomo's Map Merging;;\ngiacomos-spyglass-10-90x15;14527;giacomos_spyglass;Giacomos Spyglass 10-90x15;;\nexquisite-packing;14528;exquisite_packing;精致包装;Exquisite Packing;\n;14529;la_cucaracha;La Cucaracha;;\ngrieflogger;14530;grieflogger;GriefLogger;;\n;14531;;贝爷数据包;BearGryllsDatapack;BGD\nthe-quest-of-entity-303;14532;entity_303;The Quest of Entity 303;;\npineapple-mending;14533;mending;经验修补：凤梨版;Pineapple Mending;\n;14534;quick_chat;快捷聊天;Quick Chat;\nmagic-vibe-decorations;14535;magic_vibe_decorations;Magic Vibe Decorations;;\nvalentines-blessing;14536;valentines_blessing;Valentine's Blessing;;\ndecent-biomes;14537;decent_biomes;Decent Biomes;;\nd-placeable-food-fabric-forge;14538;placeable_food;3D Placeable Food;;\nfan-shaped-cake;14539;fanshapecake,fan_cake;Fan-shaped cake;;FSC\nbitter-brews;14540;bitter_brews;Bitter Brews;;\nrichards-coffee-tea-mod;14541;teamod;Richard's Coffee and Tea Mod;;\nribes-adventures;14542;ribes_adventures;Ribes Adventures;;\n;14543;invisiblekeybinding;隐藏按键绑定;Invisible Key Binding;\n;14544;cell;Cell (Vanilla+);;\npacked-up;14545;packedup;Packed Up;;\nhexcircus;14546;hex_circus;HexCircus;;\ndiamond-chest-shops;14547;diamondchestshop;Diamond Chest Shops;;\narmor-stand-poses-datapack;14548;armor_stand;Armor Stand Poses;;\nbetter-clouds-reforged;14549;betterclouds;Better Clouds Reforged;;\nexcessive-building;14550;excessive_building;Excessive Building;;\nmodchu_modchulib;14551;modchulib;ModchuLib;;\n;14552;samhackers_command_gui;SamHacker's Command GUI;;\nno-slime-in-flat-world;14553;noslime_inflat_world;No Slime In Flat World;;\nicarae-origin;14554;icarae_origin;Icarae Origin;;\n;14555;pokecycles;Pokecycles;;\n;14556;consoles;Gaming Consoles;;\n;14557;clkx;翠翎恐蕈;Jadeplume Terrorshroom;JT\nfsit;14558;fsit;FSit;;\ncarbon-config;14559;carbonconfig;Carbon Config;;\nproductivetrees;14560;productivetrees;Productive Trees;;\n;14561;tsa-concrete;Toms Server Additions: Concrete!;;\n;14562;tsa-planks;Toms Server Additions: Planks!;;\n;14563;tsa-stone;Toms Server Additions: Stone!;;\nstellarview;14564;stellarview;Stellar View;;\nbiome-cows;14565;biome_cows_;Biome Cows;;\nsimple-stopwatch;14566;giacomos_stopwatch;Simple Stopwatch / Giacomos Stopwatch Mod;;\nliolib;14567;liolib;LioLib;;\ngiacomos-better-dye;14568;giacomos_better_dye;Giacomo's Better Dye;;\nash-of-sin-custom-anti-trap-cage-entity;14569;ash_of_sin_custom_anti_trap_cage_entity;罪业余烬：自定义反捕捉笼实体;Ash Of Sin: Custom Anti Trap Cage Entity;\nelementus;14570;elementus;Elementus;;\ndedicated-bud;14571;budchevsky;Dedicated BUD;;\n;14572;barrierfree;巧渡障碍;Barrier Free;\nvanity-twilight-zone;14573;vanity_twilight_zone;Vanity：晨昏线;Vanity: Twilight Zone;\ntfc-barrels;14574;tfcbarrels;TFC Barrels;;\nmelter;14575;melter;Melter;;\narmour;14576;armorplusplus;Armour++;;\nspeedometer-and-more;14577;giacomos_speedometer;Speedometer and More / Giacomos Speedometer Mod;;\nash-of-sin-anti-same-modifier;14578;ash_of_sin_anti_same_modifier;罪业余烬：反重复修饰符;Ash Of Sin: Anti Same Modifier;\ngiacomos-automatable-cauldrons;14579;giacomos_automatable_cauldrons;Giacomo's Automatable Cauldrons;;\ngiacomos-bookshelf;14580;giacomos_bookshelf;Giacomo's Bookshelf;;\nno-outbreaks;14581;no_outbreaks;No Outbreaks!;;\nnemos-woodcutter;14582;nemos-woodcutter;Nemo's Woodcutter;;\ncommand-button;14583;commandbutton;Command Button;;\n;14584;vmr;Vape管理器：重生;VapeManagerReborn;VMR\n;14585;paleozoic;是你先开的;You cheated first;YCF\n;14586;hallelugw;哈利路大旋风;Hallelu the Grand Whirlwind;\nexp-ore;14587;expore;经验矿石;Exp Ore;\n;14588;hexpigmentplus;Hex Pigment Plus;;\nstarcrop;14589;starcrop;Star Crop;;\nthe-social;14590;thesocial;The Social;;\n;14591;;炼狱模式;Infernum Mode;\ngoety-mythical-addon;14592;goety_ut;Goety Mythical Addon;;\nhuge-structure-blocks;14593;hugestructureblocks;Huge Structure Blocks;;HSB\ncalamity-sn;14594;mr_calamity_mc;Calamity MC;;\nbodyhygiene;14595;bodyhygiene;BodyHygiene;;\ncafes-birding;14596;cafesbirding;Café's Birding;;\nkafs-valentine-special;14597;kafvalentine;Kaf's Valentine Special;;\ncreate-deepfried;14598;create_deepfried;机械动力：油炸;Create: Deepfried;\nsleepable-create;14599;sleepable_create;Sleepable Create;;\ncreate-railways-navigator;14600;createrailwaysnavigator;机械动力：铁路导航;Create Railways Navigator;CRN\nspeed-drifter;14601;mcspeed;MC飞车;MCSpeed;MCS\n;14602;donothitme;好好挖矿，不要给我一镐子;DoNotHitMe;\nrearmour;14603;rearmour;REArmour++;;\nlionfish-api;14604;lionfishapi;Lionfish API;;\n;14605;thinker;沉思者;Thinker;\navaritia-universal;14606;avaritia;Avaritia Universal;;\nslash-blade-tweaker;14607;slashbladetweaker;Slash Blade Tweaker;;\nguns-without-roses-additions;14608;gunswithoutrosesadditions;Guns Without Roses Additions;;\ncleanview;14609;cleanview;CleanView;;\nbuilt;14610;built;Built;;\ndiyotweaks;14611;DiyoTweaks;DiyoTweaks;;\nextra-spell-attributes;14612;extraspellattributes;Extra RPG Attributes;;\n;14613;createcontraptioncreatures;Create : Contraption Creatures;;\nimmersive-vehicles-iv-mts-texel-arsenal-pack;14614;ma_c,ma_d,ma_m,ma_p;SMZ TexelWorks;;\ncreature-whisperer;14615;cw;Creature Whisperer;;CW\n;14616;spelling_and_crafting;魔法字母;Spelling;\n;14617;fn;FullBright;;FB\ncreate-molten-metals;14618;molten_metals;Create: Molten Metals;;\ncreate-optical;14619;create_optical;机械动力：光学;Create Optical;\n;14620;farmersdelightplus;Farmer's Delight: Plus;;\n;14621;flintloader;Flint Loader / Punch;;\n;14622;autorespawn;自动重生;AutoReSpawn;\n;14623;more_difficult_diving;更困难的潜水;More Difficult Diving;\nmob-battle-music;14624;mobbattlemusic;战斗音乐;Mob Battle Music;\nconcerto;14625;concerto;协奏曲/一起听;Concerto;\n;14626;loadingtipsfixer;LoadingTipsFixer;;\n;14627;iff;Invisible Frames for Forge;;IFF\nfactions-and-curiosities;14628;fnc;派系与奇遇;Factions And Curiosities;FNC\n;14629;anyfps;anyfps;;\ncreate-amazing-trading;14630;amazingtrading;Create: Amazing Trading;;\n;14631;release_these_fish;放生;Release These Fish!!!;\n;14632;inficraft;InfiCraft;;\nwesteroscraft-worldeditcui;14633;worldeditcui;WesterosCraft WorldEditCUI;;\n;14634;ef;ElytraFly;;EF\ndurability-display;14635;duradisplay;Durability Display;;\nhardrock-samples-tfc;14636;hardrock_samples;Samples TFC;;\n;14637;invhud_configurable;物品栏HUD+ 药水显示支持;InvHUD_Configurable;\n;14638;viaforgeplus;ViaForgePlus;;\nmodern-multitools;14639;mr_modern_multitools;现代化多功能工具;Modern Multitools;\ntreps-cars;14640;trepscars;Trep's Cars;;\n;14641;rs;重生！;Respawn!;RS\nback-up-beds;14642;backupbeds;Back Up Beds;;\n;14643;fc;FreeCam;;FC\n;14644;walksycrystaloptimizer;WalksyCrystalOptimizer;;\n;14645;supersnail;知乎蜗牛の奇妙冒险;Super_Snail;\n;14646;dc;简单交互性装饰模型支持库;Deco Creater kit;dc\n;14647;hc_enchanted_food;附魔食物;Enchanted Food;\nmountain-sea-and-continent-two;14648;shanhaicontinent;山海大陆2;Mountain Sea and Continent Two;SeaM\n;14649;lmxxc;雷鸣修仙录;;\nvinery-tfc;14650;vinery_tfc;Vinery TFC;;\ntkbs-campfires;14651;tkbscampfires;TKBS-CampFires;;\n;14652;symbiogen;共生基因;Symbiogen;sbg\nwhispers-of-the-wendigo;14654;whispers_of_the_wendigo;Whispers of the Wendigo;;\nquartz-elevator;14655;quartzelv;Quartz Elevator;;\nelytra-utilities;14656;elytrautilities;Elytra Utilities;;\ndavids-furniture;14657;davids_furniture;大卫的家具;David's Furniture;DF\nkubejs-natures-aura;14658;kubejs_naturesaura;KubeJS Nature's Aura;;\nfancymenu-system-interactions-addon;14659;fmsia;FancyMenu System Interactions Addon;;FMSIA\ncontrol-engineering;14660;controlengineering;Control Engineering;;\nwooden-furnace-fabric;14661;woodenfurnace;Wooden Furnace;;\njjthunder-to-the-max;14662;jjthunder_to_the_max;JJThunder To The Max;;\ntelepass;14663;telepass;Telepass;;\noffhandbanning;14664;offhandbanning;禁手令;Off Hand Banning;\nfantasy-craft;14665;fantasycraft;幻想工艺;FantasyCraft;FC2\nbreeding-potion;14666;breedingpotion;繁殖药水;Breeding Potion;BP\nservuxforged;14667;servux;ServuxForged;;\nloading-tips;14668;tips;加载提示;Loading Tips / Pineapple Tips;\n;14669;higher_nether;更高的下界;Higher Nether;\nbetter-one-block;14670;oneblock;更好的单方块;Better One Block;\nthe-fletching-table-mod;14671;the_fletching_table_mod;The Fletching Table Mod;;\n;14672;create_militarized;Create Militarized;;\ncustomemoji;14674;customemoji;自定义表情;CustomEmoji;CEMO\nsword-soaring;14675;sword_soaring;史诗战斗：御剑凌虚;Epic Fight - Sword Soaring;\nbigger-ae2;14676;bigger_ae2;Bigger AE2;;\nscalar;14677;scalar;Scalar;;\ntrials-chambers-backport;14678;trials;Trials Chambers;;\nblus-sign-pack-mts-iv;14679;blussignpack;Blu's Sign Pack [MTS/IV];;\nalexs-mediumcore;14680;mediumcore;Mediumcore;;\nsherdsapi;14681;sherdsapi;Sherds API;;\n;14682;kontraption;精致机械;Kontraption;\nmusketeer-illager;14683;musketeer_illager;Musketeer Illager;;\n;14684;hyjg_bag_extend_system;超大容量背包;;\ndaker-acg;14685;daker_acg;Daker ACG工艺;DakerACG;\nbananarangs;14686;bananarangs;Bananarangs;;\nremorphed;14687;remorphed;Remorphed;;\nparticle-core;14688;particle_core;Particle Core;;\nfermiumbooter;14689;fermiumbooter;FermiumBooter;;\nytech;14690;ytech;YTech;;\nbetter-biome-reblend;14691;betterbiomeblend;Better Biome Reblend;;\nmcterra;14692;mcterra;MCTerra;;\nsiren-head-the-arrival;14693;siren_head;Siren Head: The Arrival (Beta);;\nclassic-musical-discs;14694;classic_musical_discs;Classic musical discs;;\nmusic-notification;14695;musicnotification;Music Notification;;\norechidendium;14696;orechidendium;影矿兰;Orechid Endium;\nbones;14697;bones;Bones;;\nmy-nethers-delight;14698;mynethersdelight;My Nether's Delight;;\nlocalizator;14699;localizator;Localizator;;\nscorchful;14700;scorchful;焦土;Scorchful;\nmarbleds-api-mapi;14701;marbledsapi;Marbled's API;;MAPI\n;14702;better_enchantment;更好的附魔;Better Enchantments;BE\nmagma-bucket;14703;magmabucket;岩浆桶;Magma Bucket;\nropes;14704;AS_Ropes;Ropes+;;\npaniclecraft;14705;paniclecraft;PanicleCraft;;\ncpmpvc;14706;cpmpvc;自定义玩家模型Plasmo语音兼容;Customizable Player Models Plasmo Voice compat;\nydms-gunblades-mod;14707;gunblades;YDM's Gunblades;;\nwarp-pads;14708;warppads;Warp Pads;;\nmarbleds-arsenal;14709;marbledsarsenal;Marbled's Arsenal;;MA\n;14710;inventory-tabs;Inventory Tabs 4;;\nmob-spawning-fix;14711;mobspawningfix;生物生成修复;Mob Spawning Fix;\nlh-miracle-road;14712;lhmiracleroad;奇迹之路之魂系加点;LH Miracle Road;LHMR\n;14713;optifinechange;Optifine Change;;\ncompatskills-fork;14714;compactskills;CompatSkills(Fork);;\nreskillable-fork;14715;reskillable;Reskillable(Fork);;\n;14716;tcc;TCCEnchantments;;TcE\n;14717;subtick;SubTick;;\nelectroblobs-wizardry-continuation;14718;wizardry;巫术学：延续;Electroblob's Wizardry Continuation;\nagricraft-continuation;14719;;农业工艺：延续;Agricraft Continuation;\ncreate-nuke;14720;create_nuke;Create: Nuke;;\negg-particle-fix;14721;eggparticlefix;鸡蛋粒子修复;Egg Particle Fix;\nchicken-chunk-patcher;14722;cpg;Chicken Chunk Patcher;;\nachievement-detection-fix;14723;achievementdetection;成就检测修复;Achievement Detection Fix;\n;14724;autonightvision;自动夜视;Auto Night Vision;ANV\n;14725;chais-inventory-sorter;Chai's Inventory Sorter;;\n;14726;friendlypenetration;魔戒友军穿透;LOTR Friendly Penetration;\n;14727;lotrcallablehorse;召之马来：魔戒版;LOTRHorseCall;\n;14728;mysteriumlib;菌丝;Mycelium Lib;ML\n;14729;xpf;经验公式;Xp Formula;XPF\ncataclysmic-illagers;14730;cataclysmicillagers;Cataclysmic Illagers;;\nwitcherypatch;14731;witcherypatch;巫术补丁;WitcheryPatch;\npin;14732;pin;Pin;;\nsimple-pings;14733;simplepings;Simple Pings;;\ngreat-scrollable-tooltips;14734;great-scrollable-tooltips,great_scrollable_tooltips;Great Scrollable Tooltips;;\nwaterframes;14735;waterframes;WATERFrAMES: Multimedia Displays;;\neugenes-whistle-spur;14736;eugenes-whistle-spur;友止殷的马哨和马刺;Eugene's Whistle & Spur;\nquickquark;14737;quickquark;畅启速始/夸克疾速启动;QuickQuark;\nminecraft-mineralogy;14738;mineralogy;Minecraft Mineralogy;;\nseed-delight;14739;seeddelight,seedsdelight;种子乐事;Seed Delight;\ntransmog;14740;transmog;幻化;Transmog;\nnuclear-guitars;14741;ng;Nuclear Guitars;;\ncubic-racers;14742;cubicracers;Cubic Racers;;\nlotr-ucp;14743;LOTR-UCP;魔戒非官方兼容性补丁;LOTR UCP;\ndelightful-sandwiches;14744;delightfulsandwich;Delightful Sandwiches;;\nshader-fixer;14745;shadersfixer;光影修复;Shader fixer;\nresiever;14746;resiever;ReSiever;;\ncartload;14747;cartload;CartLoad;;\nstewing-baking;14748;stewing-baking;炖与烤;Stewing & Baking;\nam2playground;14749;am2pg;AM2PlayGround;;\nastrocraft-mod;14750;astrocraft;Astrocraft;;\n;14751;ats;AutoTools;;ATS\nneat-shaders-fix;14752;Neat;极简血量显示 - 光影修复版;Neat - Shaders-Fix;\nworldtooltips-shadersfix;14753;worldtooltips;掉落物信息显示 - 光影修复版;WorldTooltips - ShadersFix;\nars-repairing;14754;ars_repairing;新生魔艺：修复纤维强化;Ars Repairing;\noptions-enforcer;14755;optionsenforcer;Options Enforcer;;OE\nmacaws-bridges-biome-o-plenty;14756;macawsbridgesbop;Macaw 的桥梁：超多生物群系附属;Macaw's Bridges - Biomes O' Plenty;\nrenhider;14757;renhider;RenHider;;\nitemframeremover;14758;itemframeremover;物品展示框移除器;ItemFrameRemover;\n;14759;legacy-debug-pause;Legacy Debug Pause;;\ntrick-or-treat;14760;trickortreat;Trick or Treat;;\nninjin-entities-by-hedaox;14761;ninjinentities;Ninjin Entities;;\ndangerrpg-continuation;14762;dangerrpg;危险RPG：延续;DangerRPG Continuation;\nzelda-sword-skills-continuation;14763;zeldaswordskills;塞尔达剑技：延续;Zelda Sword Skills Continuation;\n;14764;ForgottenRelics;失落遗物学：非官方版;Forgotten Relics Unofficial;\nscreenshots-enhanced;14765;screenshots;截图增强;Screenshots Enhanced;\nthe-rings-of-power;14766;trop;力量之戒/魔戒众戒;The Rings of Power;TRoP\n;14767;voxelcam;体素相机;VoxelCam;\n;14768;fancydelight;Fancy Delight;;\n;14769;salt;Salt for Fabric;;\nsuspicious-zombification;14770;suszombification;Suspicious Zombification;;\n;14771;wmis;Where Is My Stuff-Forge;;WIMSF\nivtoolkit-arcana-rpg-continuation-edition;14772;IvToolkit;IvToolkit：延续;IvToolkit Arcana Rpg Continuation Edition;\nfarmer-s-delight-bedrock-unofficial;14773;FarmersDelightBedrockUnofficial;农夫乐事基岩版 (Unofficial);Farmer's Delight Bedrock (Unofficial);\n;14774;byzh-picgen;byzh's 麦块画图;BYZH's Picgen;\ntropicraft-continuation;14775;tropicraft;热带世界：延续;Tropicraft Continuation;\nbattle-tower-continuation;14776;AS_BattleTowers;战斗高塔：延续;Battle Tower Continuation;\nkswitch;14777;kswitch;KSwitch;;\ntfc-lumberjack;14778;tfc_lumberjack;TFC LumberJack;;\ntfc-better-stone-tools;14779;tfc_stone_tools;TFC Better Stone Tools;;\nprotons-additions;14780;protonsadditions;Proton's Create Additions;;\nkinetic-anti-cheat;14781;kinetic anti-cheat;Kinetic Anti-Cheat;;\nwhere-are-the-ores;14782;wherearetheores;Where Are The Ores?;;\nunderp-hangables;14783;underphangables;Underp Hangables;;\ndoggy-talent-continuation;14784;doggytalents;小狗天才：延续;Doggy Talents Continuation;\nmod-observer;14785;mod_observer;Mod Observer;;\nmineralogy-continuation;14786;mineralogy_continuation;Mineralogy Continuation;;\nubc-ore-registrar;14787;UBCOres;科学地质：矿石注册;UBC Ore Registrar;\ngenesis-2d;14788;genesis;创世纪;Genesis;\n;14789;nonupdate_reloaded;不再有更新重制版;NonUpdate Reloaded;NUR\nradiation-zone;14790;radiation_zone;辐辽地带;Radiation Zone;RZ\n;14791;ZeroClitedAPI;ZeroClitedAPI;;ZCLib\nadventure-backpacks-patched;14792;;探索者背包：延续;Adventure Backpacks Patched;\n;14793;tcaltarcull;ThaumcraftAltarCull;;\nbhops;14795;bhops;BHops;;\nfrom-spores;14796;fromspores;From Spores;;\njust-dire-things;14797;justdirethings;Just Dire Things;;\nhit-feedback;14798;hit_feedback;攻击反馈;Hit Feedback;\n;14799;hasoook;Hasoook;;\nmiddle-earth;14800;me;中洲;Middle-earth;ME\nwindows-keyboard-fixes-fixes-sticky-keys;14801;win_kb_fix;Windows 键盘修复 - 粘滞键;Windows Keyboard Fixes - Fixes sticky keys;\nstatuesmod-fix;14802;statuesmodfix;雕像模组-修复;StatuesMod Fix;\n;14803;numericaltabping;NumericalTabPing;;\nninjinentities-unofficial;14804;ninjinentities-Unofficial;Ninjin Entities-非官方版;ninjinentities-Unofficial;\nsanguine-arsenal-reforged;14805;sanguinearsenal;血染武装重铸版;Sanguine Arsenal Reforged;\nrespawn-obelisks;14806;respawnobelisks;Respawn Obelisks;;\nflintandsteel-improve;14807;biologyfire;FlintAndSteel Improve;;\nforge-zombie-plague-mod-2-wip;14808;zombieplague2;僵尸瘟疫2;Zombie Plague 2;\ncolored-grasses;14809;coloredgrasses;Colored Grasses;;\nmore-default-options;14810;moredefaultoptions;More Default Options;;\n;14811;lively_danmaku;缤纷弹幕;LivelyDanmaku;\n;14812;kanake;简明魔法;kanake;KNK\ntreecapitator-updated;14813;TreeCapitator,treecapitator;砍树更新版;Treecapitator Updated;\n;14814;offhandlights;副手光源;Offhand Lights;OL\ndental-handbook;14815;dental_handbook;牙科手册;Dental Handbook;\nprehistoric-nature-jurassic-dimension;14816;pnjurassic;Prehistoric Nature Jurassic Dimension;;\ntfcify-tfc-explorify;14817;;TFCify;;\nmany-more-structures;14818;morestructuresmodreforged,morestructuresmod;Many More Structures;;\nmobs-are-friends;14819;mobs_are_friends;Mobs are Friends;;\npolytone;14820;polytone;Polytone;;\n;14821;calcium;钙;Calcium;\ndelightful-burgers;14822;delightful-burgers;Delightful Burgers;;\ncinderloe-project;14823;cinder_loe;CinderLoE Project;;\nlotweakr;14824;lotweakr;魔戒微调;LOTweakR;\nlotr-renewed-extended;14825;lotrextended;魔戒：复兴拓展;LOTR: Renewed Extended;\nlotr-companions-renewed-addon;14826;lotrcompanions;魔戒：同伴;LOTR: Companions;\ndvs;14827;dvs;自定义魔戒小建筑;DVS;\nmffs;14828;mffs;Modular Force Field Systems;;MFFS\nlotr-extended;14829;lotre;魔戒大陆拓展;LOTR Extended;\nbitcrafting;14830;bcm;BitCrafting;;BCM\nfart-shit-pee;14831;fart_shit_pee;Fart Shit Pee;;\nstatuseffecthud-updated;14832;StatusEffectHUD;状态信息显示更新版;StatusEffectHUD Updated;\ncapejs;14833;capejs;CapeJS;;\nadvanced-machines-patcher;14834;advmachinespatch;高级机械补丁;Advanced Machines Patcher;\n;14835;immersive-tooltip;沉浸式提示框;Immersive:Tooltip;\n;14836;legacyfixes;LegacyFixes;;\nsound-controller;14837;soundcontroller;Sound Controller;;\ncagerium;14838;cagerium;Cagerium;;\nadvanced-inventory-mod;14839;advancedinventory;Advanced Upgradable Inventory;;\nrolling-down-in-the-deep;14840;rolling_down_in_the_deep;Rolling Down in the Deep;;\ndwarven-realm;14841;drealm;矮人诸国;Dwarven Realm;\nrenewed-eras-of-arda-addon;14842;eoa;阿尔达纪元：复兴;Eras of Arda: Renewed Addon (First and Second Age);EoA\nlegendary-item;14843;legendarium;魔戒传说物品;Legendary Item;\nlotr-middleearth-tweaks;14844;metweaks;中洲微调;MiddleEarth Tweaks;\nthaumcraft-integration;14845;tc_integraton;Thaumcraft Integration;;\nreach-display;14846;reach_display;Reach Display;;\ngravitation-suite-patcher;14847;gravisuitepatch;Gravitation Suite Patcher;;\nanti-rat-dungeon;14848;anti_rat_dungeon;Anti Rat Dungeon;;\nmodchu_defaultmodelset;14849;modchumodel;DefaultModelSet;;\ncreate-storage;14850;fxntstorage;Create: Storage;;\navaritia-item;14851;avaritiaitem;无尽特效物品;Avaritia Item;\nkingdom-of-rin-regions;14852;kingdomregions;Kingdom of RIN | Regions;;\ndurability-speed-legacy-backport;14853;durabilityspeed;Durability Speed (Legacy Backport);;\ninventory-on-death-mod;14854;inventoryondeath;Inventory on death Mod;;IOD\nberries-cherries-delight;14855;berries_and_cherries;Berries & Cherries Delight;;\nsilents-delight;14856;silentsdelight;幽匿乐事;Silent's Delight;\ndarwin-sprinting-system;14857;dss;达尔文疾跑系统;Darwin Sprinting System;DSS\ncamera-overhaul-reforged;14858;cameraoverhaul;Camera Overhaul Reforged;;\nmonolib;14859;monolib;MonoLib;;\n;14860;pyrofrost;Pyrofrost;;\nanimal-armor-trims;14861;animal-armor-trims;Animal Armor Trims - Horse & Wolf;;\n;14862;beetech;狼群：群蜂工艺;Wolves：SwarmBeeTech;SBT\nbetter-safe-bed;14863;bettersafebed;Better Safe Bed;;\nmodchu_models;14864;multimodel;Modchu Model;;\nice-and-fire-spellbooks;14865;ice_and_fire_spellbooks;Ice and Fire: Spellbooks;;\nexlines-fishing;14866;exlinefishing;Exline's Fishing;;\ncavernous-delight;14867;cavernousdelight;Cavernous Delight;;\n;14868;steep-surface-fix;Steep Surface Fix;;\nitemducts-2;14869;fluffyalien_itemducts;Itemducts;;\narthropocolypse;14870;arthropocolypse;Arthropocolypse;;\n;14871;kaleido;Kaleido;;\n;14872;tab_ppd;TABPlayerPositionDisplay;;\n;14873;igmun;模组隐藏器;ModHider;MH\ncombat-skill;14874;combat_skill;战斗技巧;Combat Skill;CS\nlife-is-precious;14875;lip;生命是宝贵的;Life is precious;LIP\n;14876;rdfim;随机难度;Random Difficulties For Improved Mobs;RDFIM\nadvanced-skills;14877;advanced_skills;进阶技能;Advanced Skills;AS\nnuts-delight;14878;cavernousdelight;Nuts Delight;;\nvegetables-delight;14879;vegetablesdelight;Vegetables Delight;;\nenderpack;14880;enderpack;EnderPack;;\ningame-mod-configs;14881;InGameModConfigs;游戏内模组配置;InGame Mod Configs;\nplayertag;14882;playertag;玩家标签;PlayerTag;\nhide-names;14883;hidenames;隐藏名称;Hide Names;\ncamo-suits;14884;camosuits;迷彩套装;Camo Suits;\nvtubruh-lotr-mobs-new;14885;vtubruhlotrmobs;Vtubruh的魔戒生物;vtubruh LOTR Mobs;\n;14886;AwakenDreams;Awaken Dreams;;AD\nadvanced-storage-network-2;14887;fluffyalien_asn;Advanced Storage Network;;\n;14888;create_more_features;Create: More Features;;\narmadillo-scute-armor;14889;armadillo-scute-armor;Armadillo Scute Armor;;\nnew-shield-variants;14890;new-shield-variants;New Shield Variants;;\n;14891;fantasycraftcomplements;幻想工艺：扩充;FantasyCraft Complements;FCC\n;14892;;What Am I Looking At?;;WAILA\nway-much-faster-oxidize-reborn;14893;way_much_faster_oxidize_reborn;Way Much Faster Oxidize Reborn;;\nbn-blood-particles;14894;bnbloodparticles;BN Blood Particles;;\nbn-weapons;14895;bn_weapons;BN Weapons;;\necotones;14896;ecotones;Ecotones;;\nfroglins;14897;froglins;Froglins;;\ncreate-armor-trim;14898;create_armor_trims;Create: Armor Trim;;\nslashblade-resharped;14899;slashblade;拔刀剑：重锋;SlashBlade: Resharped;\nspawn-mod;14900;spawn;Spawn;;\n;14901;smooth_swapping;平滑转移 1.20.4 移植;Smooth Swapping 1.20.4 Port;\n;14902;create_tab_fix;Create Tab Fix;;\nscape-and-run-origins;14903;srp_origins;Scape and Run: Origins;;SR:O\norigins-extra-keybinds;14904;extrakeybinds;Origins Extra Keybinds;;\nepic-knights-addon;14905;magistuarmoryaddon;Epic Knights: Addon;;\n;14906;silverlighting;未尽之事;SilverLighting;\n;14907;arknights-skadi-orca-plushie;【明日方舟】斯卡蒂的虎鲸抱枕;[Arknights] Skadi Orca Plushie;\nsona-survival-101;14908;sona;唢呐生存指南;Sona Survival 101;\n;14909;more_villargers;More Villargers;;\ncult-of-the-wither;14910;cotw;Cult of the Wither;;\nclochetweaks;14911;clochetweaks;ClocheTweaks;;\nreplaycraft;14912;;ReplayCraft;;\n;14913;flintapi;Flint API;;\n;14914;multiproto;Multiproto;;\nfrostbytes-improved-inventory;14915;inventory;Frostbyte's Improved Inventory;;\nmekanism-extras;14916;mekanism_extras;通用机械：扩展;Mekanism Extras;MekE\nleaf-beds;14917;leafbeds;Leaf Beds;;\nunidentified-enchantments;14918;unidentifiedenchantments;Unidentified Enchantments;;\nprotection-pixel;14919;protection_pixel;机素防护;Create: Protection Pixel;\nmore-camera-perspectives;14920;;More Camera Perspectives;;\n;14921;hcml_launch;HMCL_Launch;;\ncreatedpcompat;14922;createdpcompat;CreateDPCompat;;\n;14923;nbs;NonBlind;;NBS\nlava-waterblock;14924;lavaandwaterblock;Lava and Water Block;;\n;14925;im_tired;我很疲倦;I'm Tired;IT\nconstant-music;14926;constantmusic;Constant Music;;\nsupercore;14927;supercore;SuperCore;;\nextreme-reactors-create-compat;14928;er2create;Extreme Reactors Create Compat;;\nmodchu_playerformlittlemaid;14929;pflm;PlayerFormLittleMaid;;PFLM\ntotally-lit;14930;totally-lit;Totally Lit;;\nmo-shiz-mod;14931;ms;Mo' Shiz;;\naetheric-tetranomicon;14932;aetheric_tetranomicon;Aetheric Tetranomicon;;\narda-addon;14934;arda;阿尔达拓展;Arda Addon;\n;14935;potion-warning;PotionWarning;;\na-good-place;14936;a_good_place;A Good Place;;\naether-protect-your-moa;14937;aether_protect_your_moa;Protect Your Moa;;\nadvanced-rocketry-2;14938;advancedrocketry;Advanced Rocketry - Reworked/Advanced Rocketry - fixed;;\ncompostimprove;14939;compostimprove;Compost Improve;;\nsuperb-steeds;14940;superb-steeds;Superb Steeds;;\ncoal-piece;14941;coal_piece;Coal Piece;;\n;14942;zoom;变焦;Zoom;\n;14943;GlobalShop;全球商店3;GlobalShop_v3;\n;14944;mechmonstrosity;机械巨兽;MechMonstrosity;\nslash-illager;14945;slash_illager;Slash Illager;;\n;14946;create-spawnerboxer;机械动力：拳击刷怪笼;Create: SpawnerBoxer;\nskeletalband;14947;skeletalband;骷髅乐队乐谱;SkeletalBand;\nalexs-caves-entropy;14948;entropy;Alex 的洞穴：熵;Alex's Caves:Entropy;ACE\nmekanism-explosives;14949;mekanismexplosives;通用机械：炸药;Mekanism Explosives;\n;14950;minimalcoordshud;Minimal Coords HUD;;\n;14951;;存储乐事基岩版 (Unofficial);Storage Delight Bedrock (Unofficial);\n;14952;;蟹农乐事基岩版 (Unofficial);Crabber's Delight Bedrock (Unofficial);\n;14953;;末地乐事基岩版 (Unofficial);End's Delight Bedrock (Unofficial);\navaritiareborn;14954;avaritia;无尽贪婪：复兴;AvaritiaReborn;\ncherry-shrimp;14955;cherry_shrimp;Cherry Shrimp;;\npick-command;14956;pickcommand;俺拾的指令;Pick Command;PiC\nregalliv;14957;regalliv;regalliV;;\nextra-addition-s;14958;extra_additions;Extra Additions;;\n;14959;allmusic_server,allmusic_client,allmusic;AllMusic;;\n;14960;amethyst-shield;紫水晶盾;Amethyst Shield;\n;14961;plateplate;板板;PlatePlate;\n;14962;pb;保护方块;Protect Block;\n;14963;mirrordim;镜像维度;MirrorDim;\n;14964;difficultytweak;难度调整;Difficulty Tweak;\n;14965;customitemprojectile;自定义简单物品投掷物;CustomItemProjectileForKubejs;CIPjs\n;14966;herbal-delight;Herbal Delight;;\n;14967;;迅捷冲刺;Ctrl Sprint;CS\n;14968;randgradle;随机合成;randgradle;\n;14969;compatlib;旧版本兼容工具;LegacyCompatibilityUtils / CompatLib;\nwarriors-of-past-epoch;14970;warriorsofpastepoch;Warriors of Past Epoch;;\nthe-ghast-cow;14971;ghastcow;The Ghast Cow;;\n;14972;alternatecurrent;Alternate Current Unofficial;;\nbirds-on-a-wire;14973;bow;Birds On A Wire;;\nno-hurt-animation;14974;no_hurt_animation;No Hurt Animation;;\nincense;14975;Incense;Incense;;\nwild-lands-2;14976;wl;Wild Lands 2;;\nhullabaloo;14977;hullabaloo;Hullabaloo;;\ninvinci-dogs-ssp;14978;;Invinci-Dogs;;\n;14979;noglow;禁止发光;noglow;\ntimeless-and-classics-zero;14980;tacz;永恒枪械工坊：零;Timeless and Classics Zero;TaCZ\n;14981;sufyf;Would You Shut Up-Forge;;STFUF\n;14982;concentration;专注;Concentration;\nterra-curio;14983;confluence,terra_curio;泰拉饰品;Terra Curio;TC\n;14984;additionallatherecipes;车床配方追加;Additional Lathe Recipes;\ncobblemon-counter;14985;cobbled_counter,cobblemon_counter;Cobblemon Counter;;\ncobblemon-move-inspector;14986;cobblemon_move_inspector;Cobblemon Move Inspector;;\ncobblemon-spawn-notification;14987;cobblemon_spawn_notification,spawn_notification;Cobblemon Spawn Notification;;\ncobblemon-pokenav;14988;cobblenav;Cobblemon Pokenav;;\ncobblemontrainers;14989;cobblemontrainers;CobblemonTrainers;;\nthe-ink-arena;14990;the-ink-arena,theinkarena;The Ink Arena;;\n;14991;spd;SimplePositionDisplay;;SPD\nepicfight-integration;14992;efi_mod;史诗战斗·集成;EpicFight Integration;EFI\n;14993;stopspidersfromclimbing;Stop Spiders From Climbing!;;\nkubejs-enderio;14994;kubejs_enderio;KubeJS EnderIO;;\npolymorphic-energistics;14995;polyeng;Polymorphic Energistics;;\nwar-of-the-ring-mc;14996;wotrmc;魔戒大战;The War of the Ring MC;WotR\nredstone-clock-mod;14997;redstoneclock;红石钟;Redstone Clock;RC\ncreate-cobblestone;14998;createcobblestone;Create Cobblestone;;\n;14999;another_pickaxe;Another Pickaxe;;\nselfexpression;15000;selfexpression;Selfexpression;;\nelectricalcraft;15001;electricalcraft;ElectricalCraft;;\n;15002;elecfurn_mod;ElecFurn;;\nwhat-is-stone-colorful-caves;15003;what_is_stone;What is 'Stone'? (Colorful Caves);;\nvtones;15004;vtones;Vtones;;\nheavy-inventories;15005;heavyinventories;物品负重;Heavy Inventories;\nvs-addition;15006;vs_addition;VS Addition;;VSA\ngravity-torch;15007;gravitytorch;Gravity Torch;;\nlifesteal-forge-fabric;15008;lifesteal;Lifesteal;;\nmores-by-its-infinite;15009;mores;mOres Reloaded;;\ndragonite-gear;15010;dragonitegear;Dragonite Gear;;\nlets-do-addon-fluids;15011;lets-do-addon-fluids;[Let's Do Addon] Fluids;;\n;15012;strscan;结构扫描解锁器;STR Scan Unlocker;\n;15013;no-blocked-servers;禁用Mojang服务器封禁;No Blocked Servers;\n;15014;just_glowing_buttons_forge,just_glowing_buttons_neoforge;Just Glowing Buttons;;JGB\nrats-hats;15015;rats-hats;Rats' Hats;;\nbiome-reactors;15016;biomereactors;Biome Reactors;;\nmystical-agriculture-tiered-crystals;15017;matc;Mystical Agriculture Tiered Crystals;;MATC\ntpaplusplus;15018;tpaplusplus;TPA++;;\nwarlerys-hq;15019;warleryshq;WARLERY'S HQ;;\ncall-of-yucutan;15020;call-of-yucutan;尤卡坦的呼唤;Call of Yucatán;\nredman;15021;redman;红超人;;RM\nlets-do-addon-compat;15022;lets-do-addon-compat,letsdoaddon-compat,letsdocompat,letsdoaddon_compat;[Let's Do Addon] Compat;;\n;15023;af;AttackFix;;AF\nworldeditcui-forge-edition;15024;WorldEditCuiFe;WorldEditCUI Forge Edition;;\nslime-delight;15025;slime_delight;黏液乐事;Slime Delight;\ndarkcap;15026;darkcap;DarkCap - Cap Your XP!;;\n;15027;ryoamium;Ryoamium;;\nquality-equipment;15028;quality_equipment;Quality Equipment;;\nseals;15029;seals;Seals;;\nnocubes-better-smoker;15030;nocubes_better_smoker;NoCube's Better Smoker;;\n;15031;shutdownondeath;Shutdown On Death;;\nmekanism-energistics;15032;mekeng;Mekanism Energistics;;\nae2-network-visualiser;15033;aenetvistool;AE2 Network Visualiser;;\nahznb-sword-plus;15034;ahznbswordsplus;AHZNB sword plus;;\nanbu-bingo-book-for-ahznbs-shinobicraft;15035;anbubingobook;Anbu Bingo Book for AHZNB's ShinobiCraft;;\nahznbs-naruto-mod-reroll;15036;rerollthingy;AHZNB's Naruto Mod Reroll;;\nxycraft-override;15037;xycraft_override;XyCraft: Override;;\nxycraft-machines;15038;xycraft_machines;XyCraft: Machines;;\nxycraft-world;15039;xycraft_world;XyCraft: World;;\nbattlemusic;15040;battlemusic;Battle Music;;\n;15041;hwite;Hwite;Here's What Is Too Easy;Hwite\n;15042;embyui;EmbeddiumUI;;\nengineered-golems;15043;EngineeredGolems;Engineered Golems;;\naxifier;15044;axifier;Axifier;;\nrenderjs;15045;renderjs;RenderJS;;\n;15046;enchantmentcompat;Enchantment Compatibility;;\ndehand;15047;dehand;Dehand;;\njadecolonies;15048;jadecolonies;JadeColonies;;\n;15049;cobblemonmikeskills;Cobblemon Mike Skills;;\ndiamond-star-combat;15050;dscombat;Diamond Star Combat;;\nmodern-ae2-additions;15051;mae2;Modern AE2 Additions;;MAE2\ncreate-metallurgy;15052;createmetallurgy;机械动力：冶金学;Create: Metallurgy;\n;15053;clientinfofix;Client Info Fix;;\nthe-shy-guy;15054;shy_guy;SCP-096, The Shy Guy;;\n;15055;teatime;Tea Time;;\n;15056;cobbreeding;Cobbreeding;;\nmyths-and-legends-cobblemon-addon;15057;mythsandlegends;Myths and Legends [Cobblemon Sidemod] / Cobblemon Myths and Legends Addon;;\n;15058;rctmod;Radical Cobblemon Trainers;;RCT\ncreate-klinks-n-klangs;15059;create_klinks_n_klangs;Create: Klinks n' Klangs;;\nmo-than-enough-guns;15060;mteg;Mo'Than Enough Guns;;M'TEG\n;15061;killcup;KillCup;;\nforge-creeper-heal;15062;forgecreeperheal;Forge 苦力怕坑修复;Forge Creeper Heal;\n;15063;;玉米乐事基岩版 (Unofficial);Corn Delight Bedrock (Unofficial);\n;15064;;种子乐事基岩版 (Unofficial);Seed Delight Bedrock (Unofficial);\n;15065;skiphand;莫莫的折跃之手;Momo's SkipHand;MoSH\ncurse-of-disintegration;15066;curseofdisintegration;Curse of Disintegration;;\nadventure-backport;15067;adventure_backport;冒险模式向后移植;Adventure Backport;\n;15068;oreganized;井然有矿：重织;Oreganized Refabricated;\nyosby;15069;yosby;Your Options Shall Be Yours;;YOSBY\n;15070;zundamod;ZundaMod;;\ncommon-network;15071;commonnetworking;Common Network;;\naddurdisc;15072;addurdisc;自定义唱片;AddurDisc/Add your disc;\nenigmatic-addons;15073;enigmaticaddons;神秘遗物扩展;Enigmatic Addons;\n;15074;MITEIsTooFalse;MITE Is Too False;;MITF\n;15075;bloodscythe;BloodScythe;;\nddgourmetbattles;15076;dungeons_delight_gourmet_battles;Dungeon's Delight;;\nstronger-potions;15077;stronger_potions;更强的药水;Stronger Potions;\n;15078;accountsx;游戏内账号切换 X;Accounts X;\nvillager-knights;15079;villagerknights;Villager Knights;;\n;15080;particular;Particular ✨;;\n;15081;sgb;Say Goodbye;;SGB\nreforgedplay-mod;15082;reforgedplaymod;ReForgedPlay;;\nsqript;15083;sqript;Sqript;;\nnatures-delight;15084;natures_delight;生灵乐事;Nature's Delight;\nreimagining-potatoes;15085;reimagining-potatoes;Poisonous Potato Update;;\n;15086;rpa;Legacy Resource Pack Adapter;;\ninsane-stamina;15087;stamina;Stamina;;\nbren;15088;bren;Bren;;\n;15089;power-hud;Power HUD;;\nplushables;15090;plushables;毛绒玩偶;Plushables;\n;15091;craftguide;G键合成表-MITE;CraftGuide-MITE;\n;15092;mlc;我的世界启动器核心;MinecraftLauncherCore;MLC\n;15093;ass;自动拿取;AutoSteal;ASS\n;15094;moddev;SpeedBuilders;;RBG\n;15095;staff_of_the_king_orange;橙王的权杖;Staff of the King Orange;\n;15096;ef_irp;史诗战斗：渲染调整;EpicFight:ItemRenderPatcher;EFIRP\npath-under-gates;15097;pathundergates;Path Under Gates;;\nojs-guilds;15098;guild_mod;OJ's Guilds;;\ncommand-macro-key;15099;;命令宏键;Command Macro Key;\nrealm-rpg-quests-rewards;15100;realmrpg_quests;Realm RPG: Quests & Rewards;;\n;15101;roadworks;Roadworks;;\nunlucky-tnt;15102;unluckytnt;Unlucky TNT;;\nfastboot;15103;fastboot;FastBoot;;\n;15104;oceania;Oceania;;\ntardis-refined;15105;tardis_refined;改良的TARDIS;TARDIS Refined;\nocean-villager-trader;15106;ls_ocean_trader,oceanvillagertrader;Leon's Ocean Trader;;\n;15107;prma;机械动力：精制工艺;Create: Precise Manufacturing;PrMa\n;15108;newagealexscaves;Create: New Age / Alex's Caves Integration;;\ncreate-kryeit;15109;kryeit;Create: Kryeit;;\nanimationoverhaul;15110;animation_overhaul;Animation Overhaul;;\nstarbunclemania;15111;starbunclemania;StarbuncleMania;;\n;15112;mclogin;RE:MCLogin;;\n;15113;ep;实体保护;EntityProtection;EP\ndisplay-qien;15114;display-qien;Display-Qien;;\n;15115;villagehealthcare;Village Healthcare;;\ncubic-villager;15116;cubicvillager;村民立方;Cubic Villager;\n;15117;morecursors;更多光标;MoreCursors;\ntrue-infinity;15118;fourinchknife-trueinfinity;True Infinity;;\nnukateams-gun-lib;15119;ntgl;NukaTeam's Gun Lib;;NTGL\nfantastic-biomes-cave;15120;cavernous;Fantastic Biomes Cave;;\nnukacraft;15121;nukacraft;Nukacraft;;\nenchantment-limiter;15122;enchantlimiter;Enchantment Limiter;;\nemi-ores;15123;emi_ores;EMI Ores;;\n;15124;bread_skin;面包皮;Bread Skin;BS\nextra-gore;15125;extragore;Extra Gore;;\nchassiscore;15126;chassis_core;ChassisCore;;\n;15127;spit_splat;唾溅兽 Fabric 版;Spit Splat [Fabric];\n;15128;less_hurt_particle;LessHurtParticle;;\nnormalcore;15129;normalcore;NormalCore;;\nappliede;15130;appliede;AppliedE;;\nwaystones-teleport-pets;15131;w2pets;Waystones Teleport Pets;;\n;15132;sneaky_link;Sneaky Link;;\ncreate-mortar;15133;create_mortar;Create: Mortar;;\nforgelin-continuous;15134;forgelin_continuous;Forgelin-Continuous;;\n;15135;jojo;RotP：杀手皇后附属;Ripples of the Past: Killer Queen Addon;\nninjin-knockback-dbc-by-hedaox;15136;ninjinkb;Ninjin Knockback;;\nwings-3;15137;wings;Wings Reborn / Wings 3;;\ndstathud;15138;dstathud;DStatHud;;\nopticmanager;15139;opticmanager;OpticManager;;\nribbits;15140;ribbits;Ribbits;;\n;15141;actually-camel;Actually Camel;;\n;15142;pm;图片信息;PictureMessage;PM\n;15143;raml;REAgainMCLogin;;\n;15144;large_number;卡儿的数学库;Large Number;\nraws-visual-keybinder;15145;visual_keybinder;Raw's Visual Keybinder;;\nsynthetic-products;15146;synthetic_products;合成配方;Synthetic Products;SP\n;15147;fastlang;Fast Lang;;\nscoreboard-tweaks;15148;scoreboardtweaks;Scoreboard Tweaks;;\nrsinfinitewireless;15149;rsinfinitewireless;RSInfiniteWireless;;\nrustic-engineer;15150;rustic_engineer;Rustic Engineer;;\nartsandcrafts;15151;arts_and_crafts;Arts & Crafts;;\nmermaid-legend;15152;mermaidlegend;人鱼传说;Mermaid Legend;\nendcake;15153;endcake;末影蛋糕;EndCake;\n;15154;bettertropical;更好的热带鱼;BetterTropical;BT\n;15155;sudo;mcsudo;;\n;15156;stz;简易缩放;SimplestZoom;STZ\nscp-remake;15157;scp_foundation;SCP - REMAKE;;\n;15158;scpff;SCP Fallen Foundation;;\n;15159;anytag;天使命名牌;Anytag;\ncreate-heat-js;15160;create_heat_js;Create Heat JS;;\ncreateaddoncompatibility;15161;createaddoncompatibility;Create: Addon Compatibility;;\nmore-difficulty-levels;15162;iiz_mdl;More Difficulty Levels;;\nalternacraft;15163;alternacraft;Alterna Craft;;\nsheep-squeak;15164;sheepsqueak;Sheep Squeak;;\nfabric-simple-animated-guns;15165;animated_guns;Simple Animated Guns;;\na-v-a-in-minecraft;15166;ava;Alliance of Valiant Arms Guns;;A.V.A\ncoordinates-display;15167;coordinatesdisplay;坐标显示;Coordinates Display;\n;15168;acrylic;亚克力窗口;AcryliCraft;\ntowelette;15169;towelette;Towelette;;\nshc-pvz-zengarden;15170;pvz_zengarden;SHC PVZ ZenGarden;;\nunusual-foods-delight;15171;unusualfoodsdelight;奇食乐事;Unusual food's Delight;\nre-fruitful;15172;refruitful;RE-Fruitful;;\ndark-rites-2;15173;darkrites;Dark Rites 2;;\nadvitam-addon-for-requiem;15174;advitam;Ad Vitam: Addon for Requiem;;\npotheads;15175;potheads;PotHeads;;\n;15176;objection;异议！;Objection!;\nskyfall-meteorites;15177;skyfall;Skyfall: Meteorites;;\nfancyclear;15178;fancyclear;FancyClear;;\npathfinders-spade;15179;pathfinder;Pathfinder's Spade;;\nlegacy-minecraft;15180;legacy;Legacy4J;;\nfestive-creepers;15181;festive_creepers;Festive Creepers;;\nessential-features;15182;essentialfeatures;Essential Features;;\npiston-control-forge;15183;pistoncontrol;Piston Control;;\nblahaj-restitched;15185;blahaj;布罗艾重制版;Blahaj Restitched;\n;15186;tabbychat2;TabbyChat 2 Reforged;;\nlithostitched;15187;lithostitched;Lithostitched;;\npogfish;15188;pogfish;Pogfish;;\nlaunch-time;15189;launch_time;启动耗时;Launch Time;\nkiller-table;15190;killer_table;Killer Table;;\ntrading-table;15191;trading_table;Trading Table;;\nwinter-decorations;15192;winterdecorations;Winter Decorations;;\nfancy-snowy-weather;15193;fancy_snowy_weather;Fancy Snowy Weather;;\nwinter-ambience;15194;winterambience;Winter Ambience;;\ndralards-rock-candy-mod;15195;rockcandy;Dralard's Rock Candy Mod;;\n;15197;testbooks;可食用课本;Textbooks;\n;15198;vineplicate;生蔓聚解;Vineplicate;VaC\nexplosion-breaks-no-block;15199;explosionbreaksnoblock;爆炸不破坏方块;Explosion Breaks No Block;EBNB\nvia-romana;15200;via_romana;Via Romana: Infrastructure-Driven Fast Travel;;\nlithic-coins;15201;lithiccoins;Lithic Coins;;\ndynamic-trees-for-natures-spirit;15202;dtnatures_spirit;动态的树：自然之灵附属;Dynamic Trees for Nature's Spirit;\ngore-edition;15203;gore;Gore Edition;;\nchristmas-wish-in-a-bottle;15204;christmas_wish_in_a_bottle;Christmas Wish in a Bottle;;\nchiseled-bricks;15205;chiseled-bricks;Chiseled Bricks;;\nunethly-origin;15206;untetly;Unethly Origin;;\ndimensional-totem;15207;dimtotem;Dimensional Totem;;\nplaceable-gunpowder;15208;gunpowder;Placeable Gunpowder;;\nlibrary-for-ar-reworked;15209;libVulpes;ARLib;;\n;15210;mr_better_caveworlds;Better Cave Worlds;;\n;15211;;E.M.I 特遣队武器;;E.M.I\n;15213;aquaculture_delight;水产乐事：早产;Pre Aquaculture Delight;PAD\ncraftdev-core;15214;craftdevcore;CraftDev Core;;\ncostume;15215;costume;Costume;;\nborzoi-dogs;15217;borzoi_dogs;Borzoi Dogs;;\n;15218;scg;SimpleClickGui;;SCG\nnether-exorcism;15219;nethers_exorcism;Nether's Exorcism Reborn;;\nintegrated-villages;15220;integrated_villages;Integrated Villages;;\ntaxdeepvillager;15221;taxdv;Tax' Deep Villager;;\n;15222;rxey_bd;Voxelprint's Desert;;\nmusic-on-demand;15223;musicondemand;Music on Demand;;\nleadable-bats;15224;leadbats;Leadable Bats;;\ncanes-wonderful-spiders;15225;caneswonderfulspidersoverhaul;Canes Wonderful Spiders;;\n;15226;optifine;OptiKai;;\n;15227;mr_idlefreeze;Idle Freeze;;\nscribble;15228;scribble;Scribble;;\ntext-formatting-everywhere;15229;anvil-formatting-fix,textformattingeverywhere;Text Formatting Everywhere;;\nextended-item-information;15230;eii;Extended Item Information;;\nmod-sound-volume-options;15231;mvo76;Mod Sound Volume Options;;\n;15232;olo;OldLoadingOverlay;;OLO\nteams-hud;15233;teams;Teams HUD;;\n;15234;tpm;传送管理器;TeleportManager;TPM\n;15235;cameraobscura;Camera Obscura;;\nwzorescanner;15236;wzorescanner;WzOreScanner;;\n;15237;potion_recipes;Potion Recipes;;\nwither-totem;15238;totemmod;Wither Skeleton Totem;;\n;15239;rxey_bone;Player Bones;;\n;15240;malmomod;Project Malmö / Microsoft Malmo Mod;;\nmore-apples-hyj;15241;more_tools_hyj;更多苹果;More Apples;\nfancy-enchantments;15242;fancyenchantments;奇异附魔;Fancy Enchantments;FE\nash-of-sin-adventure-dimension;15243;ash_of_sin_adventure_dimension;罪业余烬：冒险维度;Ash Of Sin: Adventure Dimension;\npush-forward;15244;push_forward;猪突猛进;Push Forward;\nymn;15245;ymn;你现在是我的了！;You're Mine Now!;\nthermal-endergy;15246;thermalendergy;Thermal Endergy;;\nmekanism-turrets-fences;15247;mekanism_turrets;Mekanism Turrets & Fences;;\nnuka-cola-classic;15248;nuka_cola_classic;Create: Nuka Cola Classic;;\ndraconic-machinery;15249;draconicmachinery;Draconic Machinery;;\n;15250;rxey_hw;Voxelprint's Halloween;;\niap-gobber;15251;iapg;IAP [Gobber];;\niap-botania;15252;iapb;IAP [Botania];;\niap-industrial-foregoing;15253;iapif;IAP [Industrial Foregoing];;\niap-silents-gems;15254;iapsg;IAP [Silent's Gems];;\niap-silents-gems;15255;iapp;IAP [Powah];;\niap-omega-craft;15256;iapoc;IAP [Omega Craft];;\niap-mekanism;15257;iapm;IAP [Mekanism];;\nindustrial-agriculture-plugin-silents-mechanisms;15258;iapsm;IAP [Silent's Mechanisms];;\ntfc-grander-canopies;15259;;TFC Grander Canopies;;\nroads-and-roofs-tfc;15260;rnr;Roads and Roofs TFC;;\ntaxvillagearchitect;15261;taxva;Tax' Village Architect;;\ntaxoceanarchitect;15262;taxoa;Tax' Ocean Architect;;\nnethers-flora;15263;nether_flora;The Nether's Flora🌸;;\n;15264;plasma_tech;Plasma Tech;;\nmicrocosm;15265;microcosm;Microcosm;;\nchinese-flying-island-tower;15266;chineseflyingislandtower;Chinese Flying Island Tower;;\n;15267;cwsm_ncbt;Netherite tools for Crackers witherstorm mod;;\nbetter-maces;15268;mr_mace,mace;Better Mace;;\njack-o-launcher;15269;jack_o_launcher;Jack-O'-Launcher;;\nthe-otherworld-mod;15270;otherworld;The Otherworld - Reborn;;\ncraftingxp;15271;craftingxp;CraftingXP;;\ndmmttba;15273;dmmttba;Don't Make Me Turn This Boat Around;;DMMTTBA\nfzzy-config;15274;fzzy_config;Fzzy Config;;\nno-shield-delay;15275;no-shield-delay;No Shield Delay;;\nmusicpower;15276;musicpower;音乐之力;MusicPower;\nmacrocraft;15277;macrocraft;MacroCraft;;\ncamerapture;15278;camerapture;Camerapture;;\nsimple-voice-radio;15279;simpleradio;Simple Voice Radio;;\n;15280;focal-engine,focalengine;Focal Engine;;\nadorned;15281;;Adorned (Curios);;\nfence-on-slab;15282;fence_on_slab;Fence On Slab!;;\nmonochromatic-blocks;15283;monochromatic_blocks;Monochromatic Blocks;;\n;15284;forbid_sprinting_while_sneaking;禁止蹲跑;forbid sprinting while sneaking;Fsws\n;15285;followtheheart;随心起意;FollowTheHeart;\n;15286;mr_unbreakapack;无法破坏数据包;Unbreakapack;\narchaicfixfix;15287;;ArchaicFixFork;;\n;15288;inventive_inventory;Inventive Inventory;;\nboxlib;15289;boxlib;BoxLib;;\nthe-shattered-goddess-mokels-bossfight-saphyra;15290;mokels_bossfight_saphyra;Mokels The Shattered Goddess;;\nresource-explorer;15291;resource_explorer;Resource Explorer;;\n;15292;patcher;PolyPatcher;;\nautoreconnectrf;15293;autoreconnectrf;Auto Reconnect Reforged;;\ndisable-accessibility-screen;15294;disableaccessibilityscreen;Disable Accessibility Screen;;\n;15295;fly_high;Fly High;;\n;15296;tc4helper;神秘4研究助手;TC4Helper;\n;15297;fly_apple;飞苹果;Fly Apple;flyap\n;15298;;自然之灵-植树盆栽配方兼容;Nature's Spirit - Botany Trees Recipe Compat;\n;15299;hoyoi;HoYoI;;\nrogues-and-warriors;15300;rogues_and_warriors,rogues;Rogues & Warriors (RPG Series);;\n;15301;mr_trashapack;垃圾桶数据包;Trashapack;\n;15302;lryx;猎人游戏数据包;;\n;15303;yxpf;游戏配方数据包/原版配方优化;;\ntfc-support-indicator;15304;tfc_support_indicator;TFC Support Indicator;;\ntfc-poisoned-drinks;15305;poisoned_drinks;TFC Poisoned Drinks;;\nsmartercontraptionstorage;15306;smartercontraptionstorage;智能动态结构存储;SmarterContraptionStorage;SCS\nlexiconfig;15307;lexiconfig;Lexiconfig;;\n;15308;mr_no_caves;No Caves;;\n;15309;advancementsfullscreen;进度全屏;AdvancementsFullscreen;\nluckys-wardrobe;15310;wardrobe;Lucky's Wardrobe;;\n;15311;fukkit;Forkkit;;\nmore-affordable-than-ever-never-too-expensive;15312;affordable;More Affordable Than Ever - Never Too Expensive;;\nwhat-am-i;15313;inventity;What am I?;;\n;15314;boi;更好的配置选项;BetterOptionInstance;BOI\n;15315;camoverlay;CamOverlay;;\nautoplant;15316;autoplant;自动种植;AutoPlant;\n;15317;skipbackupscreen;Skip backup screen;;\n;15318;polysprint;PolySprint;;\n;15319;crashpatch;CrashPatch;;\n;15320;vanillahud;VanillaHUD;;\n;15321;damagetint;Damage Tint;;\n;15322;polyblur;PolyBlur;;\n;15323;colorsaturation;ColorSaturation;;\n;15324;polyhitbox;PolyHitbox;;\nlains-random-creation;15325;lain;LAIN的随意创作;Lain's random creation;LRC\nautochefs-delight;15326;autochefs-delight,autochefsdelight;Autochef's Delight;;\n;15327;waif;What am I Forge;;\nbindcommands;15328;bindcommands;BindCommands;;\nshort-grass;15329;short_grass;Short Grass;;\nstairs-but-chairs;15330;stairsbutchairs;Stairs But Chairs;;\nyumo-compactmachines-por;15331;yumo_cmp;压缩空间:产量推演;yumo compactmachines por;CMP\ndynamic-trees-for-fruit-trees;15332;dtfruitfulfun;动态的树：妙趣果园 🍊附属;Dynamic Trees for Fruitful Fun 🍊;\ndynamic-trees-for-better-end;15333;dtbetterend;动态的树：更好的末地附属;Dynamic Trees for BetterEnd;\ndynamic-trees-autumnity;15334;dtautumnity;动态的树：秋原附属;Dynamic Trees - Autumnity;\ndynamic-trees-for-bloomingnature;15335;dtbloomingnature;动态的树：绽芳自然附属;Dynamic Trees for BloomingNature;\ndynamic-trees-terralith;15336;dtterralith;动态的树：Terralith附属;Dynamic Trees - Terralith;\ndynamic-trees-for-meadow;15337;dtmeadow;动态的树：青青草甸附属;Dynamic Trees for Meadow;\ndynamic-trees-for-beachparty;15338;dtbeachparty;动态的树：沙滩派对附属;Dynamic Trees for Beachparty;\ndynamic-trees-for-vinery;15339;dtvinery;动态的树：葡园酒香附属;Dynamic Trees for Vinery;\ntfc-aesthetics;15340;tfc-plus-aesthetics;TFC+ Aesthetics;;\nsmooth-gui;15341;smoothgui;Smooth Gui;;\noffershud;15342;offershud;OffersHUD;;\nperspektive;15343;perspektive;Perspektive;;\nore-harvester;15344;oreharvester;Ore Harvester;;\nflightassistant;15345;flightassistant;FlightAssistant;;\nbiomefog;15346;biome-fog;Biome Fog;;\nedgeless-chat-screen;15347;edgelesschatscreen;无边界聊天框;Edgeless Chat Screen;\nvillager-death-messages;15348;villagerdeathmessages;Villager Death Messages;;\n;15349;smoothskies;Smooth Skies;;\nclist;15350;coordinatelist;CList;;\ntothestarsremake;15351;tothestarsremake;ToTheStars重制版;ToTheStarsRemake;\ngalacticraft-gravity;15352;gcg;Galacticraft Gravity;;\ndragon-mounts-even-more-dragons;15353;evenmoredragons;龙骑士附属：附加龙;Dragon Mounts: Even More Dragons;\nxplus-contingameime;15354;ingameime;游戏内输入法 XPlus 版;XPlus ContingameIME;\n;15355;watersource;水源 2;Water Source 2;\nradomraidspawn;15356;randomraid;随机袭击生成;Random Raid Spawn;\nenigmatic-legacy-legacy;15357;enigmaticlegacy;神秘遗物经典版;Enigmatic Legacy Legacy;\ntschipcrafts-dynamic-lights;15358;dynamiclights;动态光源 [数据包版];Dynamic Lights [Data Pack];\nnco-java-legacy-lib;15359;nclegacy;NCO Java Legacy Lib;;\nforge-souls-like-universe;15360;soulslikeuniverse;souls-like universe: epic fight;;\n;15361;rts;渲染工具;RenderTools;RTS\n;15362;GTNHModify;万宁NH;GTNH Modify;\n;15363;dream_core_decoration;梦核装饰;Dream core decoration;DCD\nneutral-animals;15364;neutral-animals;Neutral Animals;;\ncreate-upgraded-armor;15365;create_upgraded_armor;Create: Upgraded Armor;;\nfbp-renewed;15366;fbp;Fancy Block Particles - Renewed;;FBPR\nchat-impressive-animation;15367;chatimpressiveanimation;聊动魅影;Chat Impressive Animation;CIA\n;15368;pianoplayer;钢琴块;;\npianocraft;15369;pianocraft;PianoCraft;;\njazzymusicality;15370;musicality_legacy;Musicality;;\n;15371;boids;Boids;;\nbuckshotroulette;15372;buckshotroulette;恶魔轮盘赌;BuckshotRoulette;BR\ncustomnpcs-unofficial;15373;customnpcs;自定义NPC 非官方版;CustomNPCs Unofficial;\n;15374;fasttrading;Fast Trading;;\nsimple-wooden-pipes;15375;simplewoodenpipes;Simple Wooden Pipes;;\nsimplywalk;15376;simplywalk;SimplyWalk;;\npersistent-stuff;15377;persistent-stuff;Persistent Stuff;;\nlan-operators;15378;lanoperators;LAN Operators;;\ncompatdatapacks;15379;compatdatapacks76;CompatDatapacks;;\nthe-luminous-mod;15380;luminousworld;The LUMINOUS Mod;;\n;15381;mpwl;整合包模组白名单;ModPackWhiteList;MPWL\n;15382;mwb;MouseWheelButton;;MWB\nburst-door-open;15383;bdo;破门而入;Burst Door Open;BDO\n;15384;sheng_xi;笙汐;;\n;15385;hyperdimensionaltech;超维度科技;Hyperdimensional Tech;HT\nchain-suspension-forge;15386;chainsuspension;Chain Suspension;;\ncui;15387;cui;Colorful ui;;CUI\nemi-enchanting;15388;emi_enchanting;EMI Enchanting;;\nclientsort;15389;clientsort;Client Sort;;\nimmersive-ui;15390;immersiveui;Immersive UI;;\n;15391;customportalapi;自定义传送门 API;Custom Portal Api;\ncurios-continuation;15392;;Curios API Continuation;;\n;15393;minekimi;MineKimi;;\nblind-accessibility;15394;minecraft_access;Blind Accessibility/Minecraft Access;;\nenchanting-tree;15395;enchantingtree;Enchanting Tree;;\ndragon-enchants;15396;dragon_enchants;Dragon Enchants;;\nitem-placer;15397;itemplacer;Item placer;;\nexpandedworld;15398;expandedworld;Expanded World;;\nmonster-hunter-villager;15399;monster_hunter_villager;Monster Hunter Villager;;\njcraft-eyes-of-ender;15400;jcraft;JCraft: Eyes of Ender;;\n;15401;return_dirt_background;Return Dirt Background;;\n;15402;nmn;NotifyMe-NeoForge;;\n;15403;reset-keys-confirmation-screen;Reset Keys Confirmation Screen;;\nidle-boost;15404;idle_boost;Idle Boost;;\nbettergrassify;15405;bettergrass;更好的草地;BetterGrassify;\n;15406;lightweight-inventory-sorting;Lightweight Inventory Sorting;;\n;15407;loom-remastered;Stonecutter GUI Remastered;;\n;15408;loom-remastered;Loom GUI Remastered;;\nunseaworthy;15409;unseaworthy;Unseaworthy;;\n;15410;chromaanvils;ChromaAnvils;;\nthermal-and-space;15411;thermal_and_space;Thermal And Space;;\ncreate-curios-jetpack;15412;create-curios-jetpack,create_jetpack_curios;Create: Curios Jetpack & Backtank / Create: Jetpack Curios;;\nepic-knights-antique-legacy;15413;antiquelegacy;Epic Knights: Antique Legacy;;\nviking-samurai-knight-items;15414;ryans_vsk;Viking / Samurai / Knight Items;;\nbarely-simple-staffs;15415;barely_simple_staffs;Barely Simple Staffs;;\n;15416;;Better Vanilla Mobs;;\n;15417;;Gorify;;\nalexs-caves-delight;15418;alexscavesdelight;Alex's Caves Delight;;\nalexs-caves-adventure;15419;alexscaves_adventure;Alex's Caves: Adventure;;\ndivine-weaponry;15420;divine_weaponry;Divine Weaponry;;\n;15421;fj;FluidJump;;FJ\nhook;15422;hook;抓钩;Hook;\nfix-guard;15423;fixguard;格挡修复;FixGuard;\n;15424;gregcaves;格雷的洞穴;Greg Caves;GC\nbetter-mob-combat;15425;bettermobcombat;Better Mob Combat;;\nmob-player-animator;15426;mobplayeranimator;Mob Player Animator;;\ndepression;15427;depression;抑郁症;Depression;\nmore-rpg-classes;15428;more_rpg_classes;More RPG Library;;\nminimalism-leaf-decay;15429;minimalism_leaf_decay;极简快速落叶;Minimalism Leaf Decay;MLD\nbiome-modifier-for-fabric;15430;biome_modifier;Fabric版群系编辑器;Biome Modifier for Fabric;BM\nchunkloader-simple;15431;mychunkloader;Chunkloader;;\napollyon;15432;apollyon;Apollyon;;\nworldweaver;15433;wover;WorldWeaver;;WoVer\nnetherite-compass;15434;netherite_compass;下界合金指南针;Netherite Compass;\nthe-darkness-will-find-you;15435;the_darkness_will_find_you;The Darkness Will Find You;;\nsilent-screenshots;15436;silentscreenshots;Silent Screenshots;;\ncr3stal-updatet;15437;cr3stal;Cr³stal;;\ntoggle-subtitles;15438;togglesubtitles;Toggle Subtitles;;\nok-boomer-zoom;15439;ok-boomer;Ok Boomer;;\n;15440;flattened_dimensions,mr_superflat_dimensions,mr_flattened_dimensions;Superflat Dimensions / Flattened Dimensions;;\nforcegl2-0-remapped;15441;forcegl20;ForceGL2.0-Remapped;;\n;15442;dragon_survival_fix;龙之生存综合修复;;\nrendi;15443;rendi;重生：伤害免疫机制移除;ReNDI;\ncreate-questing;15444;create_questing;Create Questing;;\n;15445;create_train_perspective;Create Train Perspective;;\nelainalike;15446;elainalike;魔女之绘 重生：重生;ElainaLike Reborn: Reborn;\ntake-back-your-sword;15447;tbys;收回你的剑！;Take Back Your Sword;TBYS\n;15448;sk;SameKeyBind;;SK\n;15449;eww;边缘行走;EdgeWalk;EW\n;15450;reciperenderer;配方渲染;Recipe Render;RR\ntravel-friendly-food-fabric-forge;15451;travel_friendly_food;Travel Friendly Food;;\nstardew-valley-food;15452;stardewvalley_food,sdvf;Stardew Valley Food;;\nminecolonies-compatibility;15453;minecolonies_compatibility;Compatibility addon for MineColonies;;\nbettergamma;15454;bettergamma;BetterGamma;;\nmore-lectern-variants;15455;lolmlv;More Lectern Variants;;\n;15456;ase;AnotherSimpleElevator;;ASE\nlush-cave-trader;15457;lushcavetrader;Lush Cave Trader;;\noceans-enhancements;15458;oceans_enhancements;Ocean's Enhancements;;\nxp-jellies;15459;xp_jellies;Xp Jellies;;\naromatic;15460;aromatic;Aromatic;;\nonekeyminer-nf;15461;onekeyminer;一键连锁(Neo)Forge;OneKeyMiner(Neo)Forge;OKMNF\n;15462;cuf;ChatUpForge;;\nretargetato;15463;retargetato;约谊敌众;ReTargetato;\niceandfire-ce;15464;iceandfire;冰火传说社区版;IceAndFire Community Edition;IAFCE\ntomanos-vehicle-mod;15465;vehiclemod;Tomano's Vehicle Mod;;\nancient-sands;15466;ancientsand;Ancient Sands;;\nhs-bosses;15467;hs_bosses;H's bosses;;\nchubby-mobs;15468;chubby_mobs;Chubby Mobs;;\nrepair-gem;15469;repairgem;修复宝石;Repair Gem;\nbetter-weaponry-compatible-with-better-combat;15470;better_weaponry;Better Weaponry;;\ndisable-front-perspective;15471;disablefrontperspective;disable front perspective;;\n;15472;ntt;NetherTweaker;;NT\n;15473;lastserver;LastServer;;\nthaumcraft-6-aspects-for-jei;15474;thaumcraft-6-aspects-for-jei;Thaumcraft 6 Aspects for JEI;;\n;15475;up_and_away;Up and Away;;\n;15477;rotp_zhp;RotP：紫色隐者附属;Ripples of the Past: Hermit Purple Addon;\nnc-steam-additions;15478;ncsteamadditions;NuclearCraft Steam Additions;;\naura-cascade;15479;Aura;气场流注;Aura Cascade;\nrac-compat;15480;raccompat;Relics: Alex's Caves Compat;;\nspartan-weaponry-addon-toolkit;15481;spartantoolkit;Spartan Weaponry Addon Toolkit;;\nsmsn;15482;smsn;救救我的答辩网络;Save My Shaky Network;SMSN\nsneaky-tree-growing-forge;15483;sneaky_tree_growing;Sneaky Tree Growing;;\nhungry-zombies;15484;hungryZombies;Hungry Zombies;;\n;15485;gravity_gourds;Gravity, Gourds, & You;;\nusa-delight;15486;usadelight;USA Delight;;\nexperience-farm;15487;xp_farm;Experience Farm;;\ndandi2k8s-ad-extendra;15488;ad_extendra;Dandi2k8's Ad Extendra;;\nnebulus-juice-trader;15489;nebulus_juce_trader;Nebulus Juice Trader;;\ncthulhu-fishing;15490;cthulhufishing;Cthulhu Fishing;;\nlarge-super-potato;15491;largesuperpotato;大型超级马铃薯;Large Super Potato;LSP\n;15492;crafttime;Craft Time;;\nthe-orcs;15493;theorcs;The Orcs!;;\nmade-in-abyss-shroombears;15494;shroombear;Made in Abyss: Shroombears;;\nsquinkers;15495;squinkers;Squinkers;;\nmokels-bossfight-kinora;15496;mokels_boss_mantyd;Mokels Bossfight: Kinora;;\n;15497;create_high_pressure;Create High Pressure;;\ncreate-framed;15498;createframed;机械动力：多彩边框;Create: Framed;\ntrackwork;15499;trackwork;驱动工艺;Trackwork;\noritech;15500;oritech;Oritech;;\nthe-impossible-library;15501;the-impossible-library;The Impossible Library;;\nperspective-mod-redux;15502;perspectivemod;Perspective Mod Redux;;\nmore-cartography-tables;15503;lolmcgt;More Cartography Tables;;\nmore-barrel-variants;15504;lolmblv;More Barrel Variants;;\nwheelbarrow;15505;wheelbarrow;Wheelbarrow;;\nsimple-automation;15506;simple_automation;Simple Automation;;\nbetter-paragliders;15507;betterparagliders;Better Paragliders;;\nfallen-wizards-more-magic-series;15508;fallenwizardsmod;Fallen Wizards;;\n;15509;lootreplacer;LootReplacer;;\n;15510;whiffowisp;Whiff O' Wisp;;\nmango-cats;15511;mango_cars;Mango Cats;;\ntax-tree-giant;15512;taxtg;Tax' Tree Giant;;\ntaxskyvillager;15513;taxsv;Tax' Sky Villager;;\ncompostable-rotten-flesh;15514;compostable-rottenflesh;Compostable Rotten Flesh (Fabric);;\nvillager-in-a-bucket;15515;villagerinabucket;Villager In A Bucket;;\nnebulus-trader-for-the-desert;15516;nebulus_desert_trader;Nebulus Trader for the Desert;;\nnebulus-jungle-trader;15517;nebulus_jungle_trader;Nebulus Trader for the Jungle;;\nmushroom-villager-trader;15518;mushroom_villager_trader;Mushroom Villager Trader;;\nnebulus-better-portals;15519;nebulusbetterportals;Nebulus Better Portals;;\nresource-ghouls;15520;resource_ghouls;Resource Ghouls;;\navaritianeo;15521;avaritia;无尽贪婪Neo;AvaritiaNeo;\ncreate-legacy;15522;;Create Legacy;;\n;15523;bronze_age;Create: Bronze Age;;\nwind-bottle;15524;wind_bottle;风瓶;wind bottle;WindB\nlc-tech;15525;lightmanscurrency;LC Tech;;\nminecolonies-tweaks;15526;minecolonies_tweaks;Tweaks addon for MineColonies;;\ntext-placeholder-api;15527;placeholder-api;文本占位符 API;Text Placeholder API;\n;15528;gravity_changer_q;Gravity Changer (qouteall fork);;\n;15529;btt;更好的游戏标题;BetterTitle;\n;15530;tt20;TT20;;\nkasugalib;15531;kasuga_lib;KasugaLib;;Ksglib\n;15532;etshtinker;乙硫基匠魂;C2H6S Tinker;EtST\nencounter-mob-ai-tweaker;15533;encounter;遭遇/长驱速弑;Encounter;\nkubejs-mekanism-unofficial;15534;kubejs_mekanism;KubeJS Mekanism UNOFFICIAL;;\n;15535;mediaplayer;媒体播放器;MediaPlayer;MP\nbakery-villager-trader;15536;bakery_villager_trader;Bakery Villager Trader;;\nnebulus-trader-for-the-plains;15537;nebulus_plains_trader;Nebulus Trader for the Plains;;\nmokels-more-spiders;15538;mokels_spider_extension;Mokels more Spiders;;\n;15539;villagerstocker;村民补货器;Villager Stocker;\nexposure-catalog;15540;exposure_catalog;Exposure Catalog;;\nskinchanger;15541;skin_changer;SkinChanger;;\n;15543;halflifemenu;Half-Life Menu;;\nsitting;15544;sitmod;Sitting+;;\nforgotten-east;15545;forgotteneast;Forgotten East;;\nbrazil-legends;15546;brazil_legends;Brazil Legends;;\n;15547;bettertp;更好的TP指令;BetterTP;\ncustom-item-despawn-duration;15548;custom-item-despawn-duration;自定义掉落物消失时间;Custom Item Despawn Duration;\nyour-items-to-new-worlds;15549;your_items_to_new_worlds;Your Items to New Worlds;;\nenhanced-boss-bars;15550;enhanced_boss_bars;更好的Boss血条;Enhanced Boss Bars;\ncutetrade;15551;cutetrade;CuteTrade;;\n;15552;rtb;随机标题：重生;RandomTitleReborn;\nsmoothmenu-refabricated;15553;smoothmenu;平滑菜单：重制版;SmoothMenu Refabricated;\n;15554;mr_minibossbars;Miniboss Bars;;\ntoexistingstacks;15555;existing_stacks;添加到现有堆叠;ToExistingStacks;\n;15556;postmortalparticles;Postmortal Particles;;\n;15557;give-me-a-new-splash-text;Give me a new splash text!;;\n;15558;lessannoyingfire;Less Annoying Fire;;\ntreasuredistance;15559;treasuredistance;Better Treasure Map;;\naffinity;15560;affinity;Affinity;;\nchaos-moon;15561;chaosmoon;Chaos Moon;;\n;15562;healthindicator;HealthIndicator;;\n;15563;smallchisel;小凿子;Small chisel;SCL\n;15564;autosprintfix;Auto Sprint Fix;;\nlets-do-emi-compat;15565;emi_letsdo_compat;[Let's Do Addon] EMI Compat;;\nhealth-indicator;15566;healthindicators;Health Indicators;;\naiming-fix;15567;aimingfix;Aiming Fix;;\ncompasshud;15568;compasshud;指南针HUD;CompassHUD;\nmini-boss-boss-bars;15569;miniboss_boss_bars;Mini-boss Boss Bars;;\nberserker-rpg-class;15570;berserker-rpg;Berserker (More RPG Classes);;\nfromanotherlibrary;15571;fromanotherlibrary;FromAnotherLibrary;;\nmounted-pearl-bring-your-mount-along-when-you;15572;mountedpearl;Mounted Pearl;;\nbiome-id-fixer;15573;biomeidfixer;Biome Id Fixer;;\nimmortal-coral;15574;immortalcoral;Immortal Coral;;\nimmersive-guns;15575;immersive_guns;Immersive Guns;;\nguns-plusplus;15576;mr_guns;Guns++;;\nadditional-rpg-jewelry;15577;additional_rpg_jewelry;Additional RPG Jewelry (More RPG Content);;\nmekanism-ad-astra-ores;15578;mekanismaaa;Mekanism: Ad Astra Ores;;\nquestion-ducks-ambient-fireflies;15579;ambiance;Question Duck's Ambient Fireflies;;\ntravelerz;15580;travelerz;TravelerZ;;\nthe-rise-of-minerals;15581;trom;矿石崛起;The Rise Of Minerals;TRoM\ngregtech-steam-additions;15582;steamadditions;Gregtech Steam Additions;;\ngregtech-nomifactory-edition;15583;gregtech;GregTech Nomifactory Edition;;\ncreate-ethium;15584;ethuim,create_ethium;Create: Ethium;;\ncreate-contraption-terminals;15585;createcontraptionterminals;机械动力：动态终端;Create Contraption Terminals;\n;15586;;天域初始维度;SkyOverDimension;\nstrata-recipes;15587;stratarecipes;Strata Recipes;;\nstrayed-fates-forsaken;15588;mr_strayed_fatesforsaken,forsaken;STRAYED FATES: Forsaken;;\nsnowyleavesplus;15589;snowyleavesplus;SnowyLeavesPlus;;\nshield-overhaul;15590;shield_overhaul;Shield Overhaul;;\ng-weapons-mod-gargantuan-weapons-mods;15591;gweapons;G武器;G-Weapons;\nbacktools;15592;backtools;工具后置/后背工具展示：重制版;BackTools;\nepic-fight-guandao-moveset;15593;falchionmoveset;史诗战斗：关刀;Epic Fight - Guandao Moveset;\nepicfight-dual-greatsword;15594;efdg;Epicfight - Dual GreatSword;;\nspell-checker;15595;spellchecker;Spell Checker;;\nsparkle;15596;sparkle;Sparkle;;\n;15597;visibletraders;Visible Traders;;\nvillagers-inventory;15598;villagers_inventory;Villagers' Inventory;;\nremovehud;15599;removehud;RemoveHUD;;\n;15600;orthocamera;正交相机;OrthoCamera;\nthird-person-crosshair-fabric;15601;tpcrosshair;Third Person Crosshair (Fabric);;\n;15602;hitrange;HitRange;;\nblack-moon;15603;blackmoon;Black Moon;;\ndynamic-fire-overlay;15604;dynamic-fire-overlay;Dynamic Fire Overlay;;\nfirorize;15605;oscimate_soulflame;Firorize / Improved Fire Overlay;;\nsmoke-extender;15606;smokeextender;Smoke Extender;;\n;15607;nohotbarlooping;No Hotbar Looping;;\n;15608;higher-chat;Higher Chat (chat above armor bar);;\nmekanism-cardboard-tooltip;15609;mekanismcardboardtooltip;Mekanism Cardboard Tooltip;;\ndurability-notifier;15610;durabilitynotifier;Durability Notifier;;\ngregtech-extended-chemistry;15611;gtec;Gregtech: Extended Chemistry;;\ncentrifuge-tiers;15612;ctiers;Centrifuge Tiers;;\ndistant-friends;15613;distantfriends;Distant Friends;;\nthe-world-of-herobrine;15614;the_world_of_herobrine;The World Of Herobrine;;\nprimal-storage;15615;primalstorage;Primal Storage;;\n;15616;potioncore;药水核心重制;PotionCoreReloaded;PCR\nyamazakura-reforked;15617;yamazakura;山樱之刃重置版;Yamazakura reforked;\n;15618;bad_omen_potion_reborn;不祥之兆药水：重生;Bad Omen Potion:Reborn;BOPR\n;15619;;随机名字生成数据包;Name Generator Datapack;NGD\napothic-spawners;15620;apothic_spawners;神化刷怪笼;Apothic Spawners;\n;15621;dbcmobends;龙珠更多弯曲;DBCMoBends;\nsinocore;15622;sinocore;华夏核心;SinoCore;SC\ntinted-dyes;15623;tinted_dyes;Tinted Dyes;;\ndynamic-trees-ecologics;15624;dtecologics;动态的树：丰富的生态附属;Dynamic Trees - Ecologics;\ncraftablecapes;15625;craftablecapes;可合成的披风;Craftable Capes;\ntamago;15626;tamago;Tamago;;\n;15627;clickthrough;Clickthrough 2.0;;\ncat-jammies-fabric;15628;catjammies;Cat Jammies;;\nopen-computer-resurrected;15629;;Open Computer: Resurrected;;\nadvancements-reloaded;15630;advancementinforeloaded,advancements_reloaded;AdvancementInfo Reloaded;;\noc2r;15631;oc2r;OpenComputers II: Reimagined;;\neu-p2p-tunnel;15632;eup2p;EU能源P2P通道;EU P2P Tunnel;\n;15633;hold_onto_everything;Hold Onto Everything;;\nardagrass;15634;ardagrass;ArdaGrass;;\ngeocluster;15635;geocluster;Geocluster;;\n;15636;;Tinker's Masonry;;\n;15637;knives_construct;Knives Construct;;\n;15638;;Tinker's Woodworks;;\nupdating-world-icon;15639;updatingworldicon;Updating World Icon;;\nshared-resources;15640;shared-resources;Shared Resources;;\ntech-dimension;15641;techdimension;Tech Dimension;;\ntilt-break;15642;tiltbreak;Tilt Break (Old Damage Tilting);;\nitem-frament;15643;itemframent;Item Framen't;;\nwater-vision;15644;water_vision;Water Vision;;\nrandomized-default-fireworks;15645;randomized_default_fireworks;Randomized Default Fireworks;;\nglowing-torchflower;15646;glowing-torchflower;Glowing Torchflower;;\n;15647;clock-hud;Clock Hud Renew;;\ncameranoclip;15648;cameranoclip;CameraNoClip;;\nbarriers-dont-block-rain;15649;barriers-dont-block-rain;Barriers Don't Block Rain;;BDBR\nno-render;15650;no_render;No Render;;\n;15651;ncf;不诅咒-Forge;NoCurseForge;NCF\nrunningshoes;15652;runningshoes;跑鞋;Running Shoes;\nfluid-void-fading;15653;fluidvoidfading;Fluid Void Fading;;\n;15654;movement-in-gui;Movement In GUI;;\nglowing-eyes;15655;glowingeyes;Glowing Eyes;;\nyouarch;15656;litematica;YouArch;;\ntrue-darkness-fabric;15657;darkness;True Darkness Refabricated;;\nesf-entity-sound-features;15658;entity_sound_features;实体声音特性;Entity Sound Features;ESF\nnomoreglowingpots;15659;nomoreglowingpots;No More Glowing Potions;;\nsmooth-particles;15660;smoothparticles;Smooth Particles;;\n;15661;aowf;始终开放水域-Forge;AlwaysOpenWaterForge;AOWF\nunloaded-activity;15662;unloadedactivity;Unloaded Activity;;\nimproperui;15663;improperui;ImproperUI;;\nneverenoughanimation;15664;neverenoughanimations;NeverEnoughAnimation;;NEA\nreds-stagger-parry;15665;staggered;Redxzy's Parry & Stagger;;\nadditional-placements;15666;additionalplacements;Additional Placements;;\nakicaters-shelves;15667;aki;Akicater's Shelves;;\nrefurbished-furniture;15668;refurbished_furniture;MrCrayfish 的家具：重制;MrCrayfish's Furniture Mod: Refurbished;CFMR\nimproved-stations-forge;15669;improved-stations;Improved Stations;;\nelemental-wizards-rpg-class;15670;elemental_wizards_rpg;Elemental Wizards (More RPG Classes);;\npov-that-one-short-friend;15671;short_friend;POV: That one short friend;;\nonward-and-upward;15672;onward_upward;Onward and Upward!;;\nweapon-case-loot;15673;weaponcaseloot;武器箱战利品;Weapon Case Loot;\nmace-but-3d;15674;macebut3d;Mace but 3D;;\n;15675;healthindicators;Player Health Indicators (ANIMATED);;\nsmoothcoasters;15676;smoothcoasters;SmoothCoasters;;\ndo-a-flip;15677;doaflip;Do a Flip;;\n;15678;nodeathanimation;No Death Animation;;\nveindigging;15679;veindigging;VeinDigging;;\ntoo-many-entities;15680;too-many-entities;Too Many Entities;;\n;15681;cscore;CScore;;\n;15682;otf;万能翻译-Forge;OmniTranslationForge;OTF\n;15683;;更多附魔;MoreEnchantments;MES\n;15684;banitem;不准用！;BanItem;\ndatapack-breakpoint;15685;datapack-breakpoint;断点调试;Datapack Debugger;\n;15686;nuclealloy;核金2;Nuclealloy2;NA2\nendlessluckblock;15687;endless;无尽幸运方块;Endless Luck Block;\nfrosted-heart;15688;frostedheart;冬季救援：寒霜之心;The Winter Rescue: Frosted Heart;FH\ncreate-planetary-tweaks;15689;cp_tweaks;Create Planetary Tweaks;;\nentris;15690;entris;Entris: Enchanting Tetris;;\n;15691;sushi_bar;Sushi Bar;;\nlumi;15692;lumi;Lumi Lighting Engine Optimizer;;\npet-the-frog;15693;frog_petting;Pet the Frog;;\nex-nihilo-extras;15694;exnihiloextras;Ex Nihilo Extras;;\ncycle-title-screen-splash;15695;cycletitlescreensplash;Cycle Title Screen Splash;;\nhide-invisible-armor;15696;hideinvisiblearmor;Hide Invisible Armor;;\ninventory-tweaks-refoxed;15697;invtweaks;Inventory Tweaks - ReFoxed;;\nbigshot;15698;bigshot;BigShot;;\nmore-immersive-aircraft;15699;moreimmersiveaircaft;More Immersive Aircraft;;\nlegendary-survival-overhaul;15700;legendarysurvivaloverhaul;传说生存;Legendary Survival Overhaul;LSO\nmineral-delight;15701;mineraldelight;Mineral Delight;;\nnot-just-sandwich;15702;refurbished_furniture;Not Just Sandwich;;\nfantasyfood;15703;fantasyfood;幻想食物;FantasyFood;FF\nallthefood;15704;allthefood;AllTheFood;;\nvillager-names-fabric;15705;villagernames;Villager Names;;\ntrial-vault-restock;15706;trialrestock;Trial Vault Restock;;\nhot-green-fire;15707;hotgreenfire;Hot Green Fire;;\nphantom-flames;15708;phantom_flame;Phantom Flames;;\ndarktitlebar;15709;dark_title_bar;DarkTitleBar;;\ncubes-without-borders;15710;cubes-without-borders;Cubes Without Borders;;CWB\nblock-parry-dodge;15711;block_parry_dodge;Block, Parry, Dodge;;\nfargos-talismans;15712;fargos;Fargo的护身符;Fargo's Talismans;\ntoostrongmonster;15713;toostrongmonster;TooStrongMonster;;\nsimplystronger;15714;simplystronger;SimplyStronger;;\ntacz-durability;15715;gundurability;TACZ: Durability;;\nbigger-lightning-storms;15716;lightningstorms;Bigger Lightning Storms;;\neternal-limbo;15717;eternallimbo;Eternal Limbo;;\nlegoman;15718;LegoMan;LegoMan;;\npetcreepers-mod;15719;littletinyme;PetCreepers mod;;\ntugkandemans-weaponry;15720;twm;TugkanDeMan's Weaponry;;TWM\n;15721;;玩家头颅;Player skull;\n;15722;bk;老板键;BossKey;BK\n;15723;cg;运算变速;ChangeGear;CG\n;15724;osf;还原旧皮肤-Forge;OlderSkinsForge;OSF\n;15725;fisher;渔者的奇迹之肝;Fisher;\n;15726;more_cd;更多命令;;MCD\n;15727;betternether;崇祯五年的下界;Chongzhen's Nether;CZN\n;15728;noanysus;NoAnySUS;;\nrekusaeater;15729;rekusaeater;Re艹;ReKusaEater;\njsg;15730;jsg;Just Stargate Mod;;JSG\n;15731;potion_mixing;药水混合;Potion Mixing;PM\nsandfilter;15732;sandfilter;砂滤器;Sand Filter;\nresaplingslayer;15733;resaplingslayer;雁雅得枝;ReSaplingslayer;\ncerberu;15734;restarks;狱牙抵战;Cerberus;\nremoreloyaltrident;15735;moreloyaltrident;远亦得珍;ReMoreloyaltrident;\npotionlevelfix;15736;potion_level_fix;药水等级修复/药引对注;PotionLevelFix;\nreunmending;15737;unmending;以验垫砧;ReUnmending;\nreghostjump;15738;ghostjump;引殃髑至;ReGhostJump;\nrebroken-blade;15739;broken_blade;隐影刀珍;Mowzie's Mobs: ReBroken Blade;\nattackagain;15740;attackagain;又御刀追/追击;AttackAgain;\n;15741;sectionsign;SectionSign§;;\n;15742;allowchat;Allow Chat;;\nretro-damage-indicators;15743;retrodamageindicators;Retro Damage Indicators;;\n;15744;Fabric API;Improper's 3D Minimap;;\nbeddium;15745;beddium;Beddium;;\ncreate-thermal-compat;15746;create_thermal;Create Thermal Compat;;\ncreate-lagless;15747;createlagless;Create: Lagless;;\ncreate-ironworks;15748;create_ironworks;Create: Ironworks;;\n;15749;create_vibrant_vaults;机械动力：炫彩金库;Create: Vibrant Vaults;\nsteam-power;15750;steam_powered;Steam Powered;;\nkevs-library;15751;kevslibrary;Kev's Library;;\nremove-purpleprison-ads;15752;removepurpleprison;Remove PurplePrison Ads;;\ntimeoutfixes;15753;timeout_fixes;Timeout Fixes;;\nforgeskyboxes;15754;forgeskyboxes;ForgeSkyboxes;;\ncustomfishing;15755;customfishing;随液钓趣;CustomFishing;CF\nmetr0polis;15756;metropolis;Métropolis.;;MET\ncrust;15757;crust;Crust;;\n;15758;aebusblacklist;AE存储总线黑名单;AE StorageBus Blacklist;\nsc-structurecraft;15759;structurecraft;StructureCraft;;SC\nminehop;15760;minehop;Minehop;;\nplaceabletools;15761;PlaceableTools;PlaceableTools;;\nrlfoliage;15762;betterfoliage;RLFoliage;;\nbc-remastered;15763;;BC Remastered;;\n;15764;deepdrilling;Deep Drilling;;\ncopperworks;15765;copperworks;Copperworks;;CW\n;15766;;Real Hero of the Village;;HOV\n;15767;mr_village_heroplus;Village Hero+;;\nminer-villager;15768;minervillager;Miner Villager;;\nvillager-venture;15769;villager_venture;Villager Venture;;\nalekiships;15770;alekiships;aleki's Nifty Ships;;\nalekishipsbop;15771;alekishipsbop;Nifty Ships + Biomes O'Plenty Compat;;\nantique-trading-ship;15772;antique_trading_ship;Antique Trading Ship;;\nsmall-horse-stable;15773;smallhorsestable;Small Horse Stable;;\nshrek-the-ogre;15774;shrek;Shrek;;\nthe-anomaly;15776;the_anomaly;The Anomaly;;\ndesert-behemoths-sandworms;15777;sandworm_mod;Desert Behemoths: Sandworms!;;\nvermintide-rising;15779;vermintide_rising;Vermintide: Rising;;\narmor-of-the-ages;15780;armoroftheages;时代铠甲;Armor of the Ages;\n;15781;hungrycolonies;饥饿殖民地;HungryColonies;HC\n;15782;ci;引雷物品;ChannelingItem;CI\n;15783;mot;Mxpea's Multiplayer Motion API;;MMMAPI\n;15784;betterf3;BetterF3 StationAPI;;\nftb-quest-localizer;15785;ftbquestlocalizer;FTB Quest 本地化;FTB Quest Localizer;FTBQL\nmodpack-update-checker;15786;modpack-update-checker;Modpack Update Checker;;MPUC\nminimotd-fabric;15787;minimotd-fabric,minimotd;MiniMOTD;;\neffecttimerplus;15788;effecttimerplus;Effect Timer Plus;;\nthaumic-concilium;15789;ThaumicConcilium;神秘议会;Thaumic Concilium;\nequivalentexchange;15790;equivalentexchange;Equivalent Exchange / Equivalent Exchange 4;;\n;15791;gm4_apple_trees;苹果树;Apple Trees;\ncoral-up;15792;coralup;Coral Up;;\nplushie-buddies;15793;plushie_buddies;Plushie Buddies;;\n;15794;aethersdelight;Aether's Delight;;\n;15795;wheelchairs;Wheelchairs;;\ndnt-pillager-outpost-overhaul;15796;mr_dungeons_andtavernspillageroutpostoverhaul;DnT Pillager Outpost Overhaul;;\nmage-flame;15797;mageflame;Mage Flame;;\n;15798;totem-party;Totem Party Popper;;\n;15799;powerless_refinedstorage;Refined Storage: Powerless Addon;;\nforgotten-graves;15800;forgottengraves;Forgotten Graves;;\nday-zombies;15801;dayzombies;DayZombie;;\nmorecoal;15802;morecoal;更多煤炭;More Coal;\njjku;15803;jujutsucraftaddon;Jujutsu Kaisen Ultimate Rework;;JJKU\nice-and-steel;15804;sunrise;Ice and Steel;;\nimmersive-combat-feathers;15805;bettercombat;Immersive Combat Feathers;;\nghosties-monocle;15806;monocle;Ghostie's Monocle!;;\nender-dragon-loot;15807;ender_dragon_loot_;Ender Dragon Loot;;\nepic-terrain;15808;mr_epicterrain,mr_epic_terrain_compatible;史诗地形;Epic Terrain;ETN\noh-the-trees-youll-grow;15809;ohthetreesyoullgrow;Oh The Trees You'll Grow;;\noh-the-biomes-weve-gone;15810;biomeswevegone;我们走过的生物群系;Oh The Biomes We've Gone;BWG\ndblockbusterdisplays;15811;webdisplays;DBlockbusterDisplays;;\n;15812;wac;自动重连;W-AutoReconnect;WAC\n;15813;scbc;SimpleClientBaseCore;;SCBC\n;15814;waygl;WayGL;;\nno-sneaking-over-magma;15815;no_sneaking_over_magma;No Sneaking Over Magma!;;\nharmonia;15816;harmonia;Harmonia;;\nconfigurable-head-drop;15817;configurableheaddrop;Configurable Head Drop;;\n;15818;c3me;C^3M 引擎;Connectored Concurrent Chunk Management Engine;C3ME\nstructure-expansion;15819;structureexpansion;Structure Expansion;;\narmored-doggo;15820;armoreddoggo;Armored Doggo;;\nthe-newest-goatman;15821;goatman;The Newest Goatman;;\nepic-mob-siege-nightmare;15822;nightmareesm;Epic Mob Siege: Nightmare;;EMSN\njupiter;15823;jupiter;Jupiter;;\nairport;15824;mass_island_airfields;Airport Lighting;;\njust-dandy;15825;just_dandy;Just Dandy;;\n;15826;uk_roads;UK Roads;;\nmy-totem-doll;15827;my-totem-doll;My Totem Doll;;\nvoid-power-for-fabric;15828;void_power;虚空动力;Void Power;\n;15829;expansion_of_building_materials;建材拓展;Expansion of Building Materials;EBM\ndis-enchanting-table;15830;disenchanting_table;Dis-Enchanting Table;;\n;15831;al;服务器自动登录;AutoLogin;AL\n;15832;aemobile;Minecraft 赛博监工;AEMobile;\ngt-top-addition;15833;gttopaddition;格雷信息显示拓展;GT TOP Addition;GTTA\ngt-steam-tech;15834;gtsteam;格雷蒸汽科技;GT Steam Tech;GTS\nsimpleanimator;15835;simple_animator;SimpleAnimator;;\ntianjin-metro;15836;tjmetro;天津地铁;Tianjin Metro;\nmodular-flower-pots;15837;modularpots;模组化花盆;Modular Flower Pots;\nrepurposed-structures-floralis-compat-mod;15838;repurposed_structures_floralis_compat;结构变体：Floralis 兼容模组;Repurposed Structures - Floralis Compat Mod;\nrepurposed-structures-rats-compat;15839;repurposed_structures_rats_compat;结构变体：老鼠兼容数据包;Repurposed Structures - Rats Compat Datapack;\nbalanced-shield;15840;balancedshield;Balanced Shield;;\nmore-potion-effects;15841;more_potion_effects;更多药水效果;More Potion Effects;MPE\njungle-villages;15842;jungle_villages;Jungle Villages;;\ntruecherry;15843;truecherry;TrueCherry;;\n;15844;evampedtrades;Revamped Trades;;\ncreeper-fireworks;15845;creeperfireworks;烟花苦力怕;Creeper Fireworks Mod;CF\n;15846;word_overflow;Word Overflow;;\nchatnotify;15847;chatnotify;Chat Notify;;\n;15848;serverutilities;ServerUtilities;;\nnbt-tool;15849;nbttool;NBT Tool;;\n;15850;mr_mc_paint;MC Paint;;\n;15851;dscf;DebugScreenClassFinder;;\n;15852;bp;更好的活塞;BetterPiston;BP\n;15853;adventure;冒险++;Adventure++;\ndglab-x-minecraft;15854;dglab;Minecraft x DgLab;;DgLab\ntwintails;15855;twintails;双马尾;TwinTails;\ngt-wireless;15856;gtwireless;格雷无线科技;GT Wireless;GTW\nforcejava21;15857;forcejava21;强制 Java21;ForceJava21;\nblessings;15858;blessings;祝福;Blessings;\ncrafted-core;15859;craftedcore;CraftedCore;;\ncall-from-the-depths;15860;callfromthedepth_;深渊召唤;Call From The Depths;\n;15861;clover;四叶草Fabric版;Clover Fabric;\nchampions-unofficial;15862;champions;冠军/强敌再续;Champions Unofficial;\naetherworks-refracted;15863;aetherworks;Aetherworks Refracted;;\neidolon-repraised;15864;eidolon,eidolon_repraised;幻梦：再颂;Eidolon : Repraised;\ncreate-northstar;15865;northstar;机械动力：北极星;Create: Northstar;\ncreate-pattern-schematics;15866;create_pattern_schematics;机械动力：模式蓝图;Create: Pattern Schematics;\nmagic-merchant;15867;magicmerchant;Magic Coins;;\naether-villages;15868;aether_villages;天境村庄;Aether Villages;\ntricky-trials;15869;tricky_trials;Tricky Trials 1.21;;\n;15870;mr_evaporated_waterfalls;Evaporated Waterfalls;;\n;15871;clover;粥粥杰的三叶草;zhoujclover;\nthe-boys-minecraft;15872;abilities;黑袍：新世界;The Boys : New world;\n;15873;BetterCore;Better Core;;\n;15874;armor_custommodeldata;ArmorCustomModelData;;\nbetter-creative-tabs-forge-neoforge;15875;betterceativetabs;更好的创造标签页;Better Creative Tabs;BCT\nmore-upgrade;15876;update;更多锻造模板;MoreUpgrade;\n;15877;railway_accelerator;你有这么高速运转的矿车进入中国;railway accelerator;RA\n;15878;faqv;FAQ 村民!;FAQ Villagers!;faqv\navaritia-unofficial;15879;avaritia_remastered;Avaritia Remastered;;\nstellaris;15880;stellaris;群星;Stellaris;\ncommand-o-matic;15881;commandomatic;Command-O-Matic;;\n;15882;YHBotaniaWaila;植物魔法高亮信息;BotaniaWaila;BW\nrealistic-physics;15883;realisticphysics;Realistic Physics;;\ncia;15884;customitemattributes;自定义物品属性;Custom Item Attributes;CIA\nitem-wheel;15885;wheel;Item Wheel;;\ndata-attributes-directors-cut;15886;data_attributes;Data Attributes: Directors Cut;;\nbetter-walls;15887;betterwalls;Better Walls;;\nthe-pillager-legion;15888;the_pillager_legion_;The Pillager Legion;;\nthe-frozen-kingdom;15889;the_frozen_kingdom;The Frozen Kingdom;;\nstardew-fishing;15890;stardew_fishing;Stardew Fishing;;\nlets-do-a-jumpscare;15891;letsdoajumpscare;Let's do a jumpscare;;\nepic-tinkers-fight;15892;;史诗匠魂战斗;Epic Tinkers' Fight;ETF\n;15893;numenors;努门诺尔物品;Numenor Sub Mod;\nthe-quackening;15894;duckensinvasion;The Quackening;;\nbetter-trial-chambers;15895;minecraft_121_update;Better Trial Chambers;;\ncoldsweat-tfc;15896;coldsweat_tfc;ColdSweat TFC;;\ndeeper-nether-biomes;15897;deepernetherbiomes;Deeper Nether Biomes;;\nadventurers-beyond;15898;adventuresmod;Adventurer's Beyond;;\nmss-moogs-soaring-structures;15899;mss;Moog's Soaring Structures;;MSS\ngoblins-tyranny;15900;goblins_tyranny;哥布林的统治;Goblins Tyranny;\nno-32768-limit;15901;no_32768_limit;没有32768限制;No 32768 Limit;\n;15902;stf;启动时间-Forge;StartupTimeForge;STF\n;15903;nsmer;没那么多实体渲染;NotSoManyEntityRendering;NSMER\n;15904;neta;无附魔台动画;NoEnchantmentTableAnimation;NETA\nreairdrop-supply;15906;airdrop_supply;空投供应：重生;ReAirdrop Supply;RAS\n;15907;minequery;MC宝;MineQuery;\n;15908;whycooldown;为什么冷却;WhyCooldown;WC\n;15909;simply_create_model;简单动力;Simply Create Model;SCM\nmountains-poem;15910;mountains_poem;青丘辞;Mountain's Poem;MTP\n;15911;playtime;游戏时长计数器：重生;Playtime Reborn;PR\n;15912;flavor_immersed_daily;烟火凡人心;Flavor Immersed Daily;FID\nmoon-between-medicine;15913;moonbetweenmedicine;在月亮之间：药学拓展;Moon Between : medicine;\ncraftable-chainmail-reborn;15914;craftablechainmail;可制作的锁链甲：重生;Craftable Chainmail Reborn;\n;15915;automobility_fix;飞车奇匠-服务端修复;Automobility-Fix;\nthaumcraftfix;15916;thaumcraftfix;神秘时代6修复;Thaumcraft Fix;\n;15917;teleport_compass;传送指针;teleport compass;TPCP\nextended-industrialization;15918;extended_industrialization;Extended Industrialization;;EI\nsmall-tetra-additions;15919;smalltetraadditions;Small Tetra Additions;;\nlets-do-furniture;15920;furniture;家居装饰;[Let's Do] Furniture;\ntesseract-api-neoforge;15921;tesseract_api;Tesseract API;;\ntickrate-api;15922;tickrateapi,entitytickrateapi;Tickrate API;;\nspectrelib;15923;spectrelib;SpectreLib;;\n;15924;no_more_string;刷线机疑似有点城市化了;no more string;NMS\ntfc-artisanal;15925;artisanal;TFC Artisanal;;\n;15926;;群峦传说：矿石淘洗与群峦传说：矿石焙烧兼容数据包;Processing and Washing;TFCp&TFCw\n;15927;real_time;现实时间;;TRT\n;15928;stacked;堆叠++;Stacked++;\ndragonoptifix;15929;dragonoptifix;DragonBlockC 与 OptiFine 的兼容修复;DragonOptiFix;\neffortless;15930;effortlessstructure;轻松建造;Effortless Structure;\n;15931;barriers-dont-block-rain;Barriers Don't Block Rain 1.21 Fork;;\nsystem-dynamic-lights;15932;;System Dynamic Light;;\n;15933;nochathide;NoChatHide;;\ninventory-tweaks-extrafoamy;15934;inventorytweakse;Inventory Tweaks ExtraFoamy;;\nautomatic-infinite-elytra;15935;autoinfelytra;Automatic Infinite Elytra;;\nre-translator;15936;retranslator;Re: Translator;;\nyank-it-out;15937;yio;Yank It Out;;YIO\nvintage-kubejs;15938;vintage_kubejs;Vintage KubeJS;;\nemi-professions-emip;15939;emiprofessions;EMI professions;;EMIP\nimproved-grindstone;15940;grindenc;Improved Grindstone;;\n;15941;;Starlight Legacy;;\nthemetip;15942;themetip;themetip;;\nwoodwalkers;15943;walkers;Woodwalkers;;\npeaceful-moon;15944;peacefulmoon;Peaceful Moon;;\nto-tweaks-irons-spells;15945;traveloptics;T.O Magic 'n Extras - Iron's Spells Addon;;\nhorde-moon;15946;zombiemoon;Horde Moon;;\n;15947;mr_useful_mobs;Useful Mobs;;\nlaser-mod-2;15948;lasermod;Advanced Laser;;\nmekanism-lasers;15949;mekanism_lasers;Mekanism Lasers;;\nwizards-reborn;15950;wizards_reborn;Wizard's Reborn;;\ntinkervillager;15951;tinker17,tinkervillager;工匠村民;Tinker Villager;TV\ncustomnpc-contentback;15952;cnpc_contentback;CustomNPC Items;;\ndebug-stick;15953;debugstick;Debug Stick;;\nuseful-bat;15954;useful_bat;Useful Bat;;\nrunic-inscription;15955;runicinscription;Runic Inscription;;\nbamboo-stuff;15956;trollchan120;Bamboo Stuff;;\n;15957;moai_update;Moai Update;;\njunk-drawers;15958;junkdrawers;Junk Drawers;;\nplayerex-directors-cut;15959;playerex;PlayerEx: Directors Cut;;\nlets-do-camping;15960;camping;摇曳露营;[Let's Do] Camping;\n;15961;startuptime;启动时间非官方版;StartupTime Unoffical;STU\nserverconfig-updater;15962;serverconfigupdater;ServerConfig Updater;;\n;15963;epiczoomer;EpicZoomer;;\n;15964;mr_datapack_reloadhelper;Datapack Reload Helper;;\nagenta;15965;aganta;Agenta;;\n;15966;;Area of Effect (Advent of Expansion);;\nmore-beehive-variants;15967;quad-lolmbhv;More Beehive Variants;;\n;15968;jmmf;Josh's More Foods;;\npersistent-creative-inventory;15969;persistentcreativeinventory;Persistent Creative Inventory;;\nlets-do-wildernature;15970;wildernature;野性自然;[Let's Do] WilderNature;\n;15971;quitconfirm;QuitConfirm;;\n;15972;ressourcepackcommand;RessourcePackCommand;;\n;15973;sfb;简易全亮;SimplestFullBright;SFB\nneodymium-unofficial;15974;neodymium;钕：非官方版;Neodymium Unofficial;NdU\n;15975;carbon;碳;Carbon;\ncoloredglowlib;15976;coloredglowlib;ColoredGlowLib;;CGL\n;15977;better-snow-coverage;更好的雪覆盖;Better Snow Coverage;\n;15978;drg;叶压地株;Don't Render Grass;\ntrade-enchantment-display;15979;tradeenchantmentdisplay;交易附魔显示;Trade Enchantment Display;TED\neternal-starlight;15980;eternal_starlight;永恒星光;Eternal Starlight;\nastral-patch;15981;astralpatch;星辉补丁;Astral Patch;\nfast-scrolling-fabric;15982;fastscroll;Fast Scrolling;;\ngaloblender-updated;15983;galoblender_updated;Galoblender Updated;;\nmore-tetra-materials;15984;mtetm;More Tetra Materials;;\nageing-mobs;15985;ageingmobs;Ageing Mobs;;\nwarm-ice-block;15986;warm_ice;Warm Ice Block;;\nsmoke-signals;15987;smoke_signals;Smoke Signals;;\nwunderreich;15988;wunderreich;Wunderreich;;\n;15989;mr_named_villagers;Named Villagers;;\names-mobs-renew;15990;amesmobsmod;AME's Mobs Renew;;\nrefreshdisplaylists;15991;redisplaylist;RefreshDisplayLists;;\nvulkanmod-android-libs;15992;vulkanmod-an-libs;Vulkan Android Libraries;;\n;15993;blackbarconcealer;Black Bar Concealer;;\nbuilding-shift;15994;building-shift;Building Shift;;\nmob-ai-tweaks;15995;mob-ai-tweaks;Mob AI Tweaks;;\nthe-masquerade-illager-boss;15996;masquerade_mod;The Masquerade (Illager Boss);;\nbetter-armory-x;15997;better_armory;Better Armory X;;\nsweetys-horse-armor-tweaks;15998;sweety_horse_armor_tweaks;Sweety's Horse Armor Tweaks;;\nfired-pots;15999;fired_pots;Fired Pots;;\nunder-the-moon;16000;under_the_moon;驻月夜下;Under the Moon;\ncoffeegs-colorful-world;16001;mr_coffeegs_colorfulworld;CoffeeG's Colorful World;;\njeb_-wool;16002;jebwool;jeb_羊毛;jeb_ Wool;\nillage-and-spillage-finally-ported;16003;illageandspillage;Illage and Spillage: Respillaged;;\nmore-runes-more-magic-series;16004;morerunes;更多符文;More Runes;\nbattlemages-more-magic-series;16005;battlemages;Battlemages;;\neidolon-rebrewed-data-recipes;16006;eidolonrecipes;Eidolon Rebrewed Data Recipes;;\nelemental-metals-magical-metallurgy-series;16007;elemental_metals;Elemental Metals;;\ndragonsteel-more-magic-series;16008;dragonsteel;Dragonsteel & Star Alloy;;\ndragon-scale;16009;dragonscale;Dragon Scale;;\nscholar;16010;scholar;Scholar;;\n;16011;elytraswimfix;ElytraSwimFix;;\ntacz-bullet-proof-enchant-add-on;16012;bulletproofenchant;TACZ Bullet Proof Enchant Add-on;;\nno-screen-bobbing;16013;viewbobbingmod,noscreenshake;No Screen Bobbing;;\nmore-spell-attributes-more-magic-series;16014;more_spell_attributes;More Spell Attributes;;\nminos-particle-presets;16015;particlepresets;Mino's Particle Presets;;\nsomewhat-hardcore-fabric-forge;16016;somewhathardcore;You Die You Lose a Heart;;\n;16017;blurredwindow;Blurred Window;;\nplayer-list-heads;16018;playerlisthead;Player List Heads;;\n;16019;antighost;AntiGhost FORK;;\nenvirons-mod;16020;environs;Environs;;\n;16021;bfc;更好的火焰弹;BetterFireCharge;BFC\n;16022;ie;成语附魔;IdiomEnchantments;IE\nunique-magic;16023;uniquemagic;Unique Magic & Enchantments;;\n;16024;;不是所有的建树，都来自原木。;NO TREE;NT\n;16025;ani_siege;围攻/攻城;The Siege Mod;\nepic-projectile-reflection;16026;epicfightextra;史诗弹射物反射;;\nzombie-island;16027;zombie_island;Zombie Island;;\nstratagems;16028;helldivers;Stratagems;;\nsolar-hardcore;16029;solarhardcore;Solar Hardcore;;\ni-hate-flesh-reborn-ihfr;16030;ihfr;I HATE FLESH: Reborn;;IHFR\n;16031;mr_enchanted_goldenappleaddition;附魔金苹果附加;Enchanted Golden Apple Addition;\n;16032;particle-unlimit;粒子上限解除;Particle Unlimit;\naaa-particles;16033;aaa_particles;AAA Particles;;\n;16034;anno;Anno;;\ncompass3d;16035;compass3d;Compass3D;;\n;16036;clockin;Clock In;;\ndiggus-maximus-reborn;16037;diggusmaximus;连锁挖掘重制版;Diggus Maximus Reborn;\nsatisfying-buttons;16038;satisfying_buttons;Satisfying Buttons;;\nitem-on-chest-fabric;16039;itemonchest;Item On Chest;;\nindustrialreborn;16040;industrialreborn;IndustrialReborn;;\n;16041;hbm;HBM的核科技航天;HBM's Nuclear Tech Mod Space;NTM/HBM\n;16042;petrified_wood;石化木;Petrified Wood;\nseveral-food-craft;16043;foodcraft;Several Food Craft;;\ncakes-cosmetics;16044;cakescosmetics;Cake's Cosmetics;;\nboks-butterflies;16045;butterflies;Bok's Banging Butterflies;;\nimmersive-winds;16046;immersivewind;Immersive Winds;;\nreef-redux;16047;reefredux;Reef Redux;;\nsimple-quiver;16048;simple_quiver;Simple Quiver / Quiver;;\ntoo-many-paintings-forge-fabric;16049;toomanypaintings;Too Many Paintings!;;\nbuilding-but-better;16050;bbb;Building But Better;;\nexchange-fuels;16051;efuels;Exchange Fuels;;\nmore-craftings-of-runes;16052;mcrmod;More Craftings of Runes;;MCR\nmega-evolve;16053;megaevolve;Mega Evolve! (for Pixelmon);;\npixeltweaks;16054;pixeltweaks;PixelTweaks;;\nhrbs-drills;16055;hrbsdrills;HRB's Drills;;\nindex-librorum-prohibitorum;16056;index;禁书目录;Index-Librorum-Prohibitorum;Index\nbft;16057;mr_bft;Better Fletching Tables;;BFT\nheartbond;16058;heartbond;Heartbond;;\nbreakerplacer;16059;breakerplacer;Block Breaker & Block Placer;;\niron-ladders;16060;ironladders;更多梯子;Iron Ladders;\ncrumbs-deepslate-button;16061;deepslate_button;Crumbs 的深板岩按钮;Crumbs' Deepslate Button;\ncl0uds-paxels;16063;cl0uds_pixels;Cl0ud's Paxels;;\nunique-weaponry;16064;uniqueweaponry;Unique Weaponry;;\n;16065;mr_epic_spartanweaponry;Epic Spartan Weaponry;;\nboss-keys;16066;iron_bushes;Boss Keys;;\n;16067;naturalkillerbunnies;Natural Killer Bunnies;;\nmace-variants;16068;macevariants;Mace Variants;;\nshortswords;16069;shortswords;Short Swords;;\nbbs-mod;16070;bbs;Blockbuster Studio;;BBS\nmonocle;16071;monocle;Monocle;;\nmove-ui;16072;move-hotbar;Move-UI;;\nterrafly;16073;terrafly;TerraFly;;\nachievements-mod-200;16074;achievements;Achievements 240+;;\n;16075;villcurestack;Villager Cure Discount Stacking;;\nsaturation-plus;16076;saturation_plus;Saturation Plus;;\nmod-and-resource-pack-checker-abras-additions;16077;abrasadditions;Mod and Resource Pack Checker (Abra's Additions);;\ncreatestuffadditions-fix;16078;createstuffadditionsfix;CreateStuffAdditions Fix;;\nwarden-slayer-enchantment;16079;wardenslayer;Warden Slayer Enchantment;;\nmods-optimizer;16080;modsoptimizer;Mods Optimizer;;\nglowy-players-renewed;16081;glowyplayers;玩家高亮：重置版;Glowy Players Renewed;\n;16082;torcherino;加速火把GT版;TorcherinoGT;\nepic-fight-kenjis-combat-forms;16083;kenjiscombatforms;Epic Fight: Kenji's Combat Forms;;\nmekanism-unleashed;16084;mekanism_unleashed;Mekanism Unleashed;;\n;16085;llama_abilities;Ability Carpets for Llamas;;\neasy-cake;16086;easy_cakes;简易蛋糕;Easy Cake;\nweave-craft;16087;pearsmod123;Weave Craft;;\nmorefarming;16088;morefarming;更多农耕;MoreFarming;MFI\niron-bushes;16089;iron_bushes;Iron Bushes;;\nforestry-community-edition;16090;forestry;林业：社区版;Forestry: Community Edition;\n;16091;blink;瞬移;Blink;\nspecified-spawning;16092;specifiedspawning;Specified Spawning;;\nkubejs-create-for-fabric-1-20-1;16093;kubejs_create;KubeJS Create for Fabric;;\nportable-spawners;16094;ps;Portable Spawners;;PS\nbettertime;16095;bettertime;长期熟时/求闻时记/更好的时间;BetterTime;BT\n;16096;tokitotal;時之挑战;TokiTotal;TT\n;16097;recipeoverrides;原版合成覆写;;\n;16098;easytochange;简易获取注册名;EasyToChange;\n;16099;health_up;生命值增加;Health Up;\nlets-do-farm-charm;16100;farm_and_charm;沉浸农艺;[Let's Do] Farm & Charm;\n;16101;sand_basic;简易配方·进度;Easy Recipes · Progress;ER/ERP\nhunters-moon;16102;huntersmoon;Hunter's Moon;;\nminers-moon;16103;minersmoon;Miner's Moon;;\nender-moon;16104;endermoon;Ender Moon;;\nextreme-horde-moon;16105;extremehordemoon;Extreme Horde Moon;;\nspider-moon;16106;spidermoon;Spider Moon;;\n;16107;parrot-breeding;Breed Parrots With Seeds;;\npersistent-parrots;16108;persistent_parrots;Persistent Parrots;;\n;16109;ultimateat;Ultimate Autototem;;\nanti-mob-cheese;16110;anti_mob_farm;Anti Mob Farm;;\nbad-horse-fix;16111;badhorsefix;Bad Horse Fix;;BHF\nvanilla-nei-fix;16112;vanillaneifix;Vanilla NEI Fix;;\n;16113;mr_no_skylimits;No Sky Limits;;\nfconfiglib;16114;fconfiglib;FConfigLib;;\nsatin-free-wakes;16115;wakes;Satin Free Wakes;;\nblue-archive-paintings;16116;ba_painting;Sensei的珍藏;Blue Archive Paintings;\n;16117;ironspear_datapack;铁长矛数据包;;\nlunar-nether;16118;lunarnether;Lunar Nether;;\nyeehaw-towns;16119;yeehaw_towns;Yeehaw Towns!;;\n;16120;copperrails;CopperRails;;\n;16121;;阿卡什宝典 GTNH 版;Akashic Tome - GTNH;\nmana-unification;16122;manaunification;Mana Unification;;\nimmersive-energistics;16123;immeng;Immersive Energistics;;\nspecialised-cells;16124;specialised_cells;Specialised Cells;;\nrandom-chunks;16125;randomchunks;Random Chunks;;\nunique-commands;16126;uniquecommands;Unique Commands;;\n;16127;ncl;无指令限制;NoCommandLimits;NCL\n;16128;enchantview;附魔预览;EnchantView;\nflood;16129;flood;Flood;;\nblock-prints;16130;blockprints;Block Prints;;\n;16131;nech;NEI拼音搜索;NeverEnoughCharacters;NECh\n;16132;redstones_expansion;红石的扩展;Redstone's Expansion;RE\njustleveling-x-ironspell;16133;justleveling_x_ironSpell;法有所归;Just Leveling x IronSpell;\n;16134;moreweapon;更多武器;MoreWeapon;MW\nhugme;16135;hugme;抱抱我!;HugMe!;\n;16136;xp_from_rightclick_harvest;从右键收获中获取经验;XP From Right Click Harvest;\ncoins-ll;16137;coins;Coins LL;;\npurediscstrickytrials;16138;purediscstrickytrials;Pure Discs - Tricky Trials;;\ntinkers-in-the-sky;16139;tits;Tinkers' in the Sky;;\ncreate-tank-defenses;16140;create_tank_defenses;机械动力：坦克防御;Create Tank Defenses;CTD\npotato-update;16141;potato_update;Potato Update;;\ntwilight-forest-fixes;16142;twilightforestfixes;Twilight Forest Fixes (Doors & Misc);;\ndietary-statistics;16143;dietarystatistics;Dietary Statistics;;\nday-zombies-rebooted;16144;ayzombierebooted;Day Zombies Rebooted;;\nviewboboptions;16145;viewboboptions;View Bobbing Options;;\nender-pearl-anti-disappear;16146;pearlad;Ender Pearl AD;;\n;16147;thebombzenapi;ThebombzenAPI;;\ncreate-dynamic-lights;16148;createdynlight;Create: Dynamic Lights;;\ndecorative-tin;16149;tin;Decorative Tin;;\nblended-compat;16150;blended-compat;Blended Compat;;\nhold-easy-place;16151;holdeasyplace;保持简单放置;Hold Easy Place;Hep\n;16152;killaura;杀戮光环--;Killaura--;\n;16154;suonotfounds_api;SuoNotFound 的 API;SuoNotFound's API;\nendlesscompat;16155;endless_compat;无尽贪婪·伪附属;EndlessCompat;\nspartan-and-fire-lightning-edition;16156;spartanfire;斯巴达之冰与火-雷电版;Spartan and Fire - Lightning Edition;\nthe-twilight-forest-deforestation-edition;16157;;暮色森林 - Deforestation 版;The Twilight Forest - Deforestation Edition;\nminieffects-legacy-extended-life;16159;;MiniEffects-Legacy Extended Life;;\ncall-to-battle-ww2-vehicles-addon;16160;ctbvehicles;Call to Battle WW2 - Vehicles Addon!;;CTB\nthaumic_funnel;16161;thaumictinkerer_funnel;Thaumic Funnel;;\nynet-an-xnet-fork;16162;;YNet [XNet Fork];;\nrosatech;16163;rosatech;RosaTech;;\nnutrition-gtceu;16164;nutrition_gtceu;Nutrition: GTCEu;;\nnutrition-unofficial-extended-life;16165;nutrition;营养学非官方延续版;Nutrition Unofficial Extended Life;\nmanametalmod-tweaks-m3-t;16166;mmmtweaks;魔金微调;ManaMetalMod Tweaks;M3-T\nae2-crafting-tree;16167;ae2ct;合成树;AE2: Crafting Tree;ae2ct\nclient-automatic-doors;16168;client_auto_door;客户端自动门;Client Automatic Doors;\n;16169;enchanterpearl;附魔师宝珠;Enchanter Pearl;\n;16170;build_tools;Chai's Build Tools;;\nforge-relocation;16171;forgerelocation,mcframes;Forge Relocation;;\nforge-relocation-fmp-plugin;16172;RelocationFMP;Forge Relocation - FMP Plugin;;\n;16173;fish-lib;Fish Lib;;\nforgedapi-fork;16174;forgedapi;ForgedAPI Fork;;\nprioninfection;16175;prionmod;Prion Infection: Whispers From Within;;\ndark-shadow-monster;16176;dark_shadow_monster;暗影怪物;Dark Shadow Monster;\nthe-mi-alliance-invasion;16177;mialliance;The Mi Alliance: Invasion!;;\nfancy-health-bar;16178;fancyhealthbar;Fancy Health Bar;;\n;16179;nojoinleavemessage;No Join Leave Messages;;\ndata-trades;16180;data_trades;Data Trades;;\n;16181;craftden1al;CraftDen1al;;\ndraconic-alchemy;16182;draconicalchemy;Draconic Alchemy;;\nfancymenu-animation-maker-tool;16183;;[FancyMenu] Animation Maker Tool;;\njmx;16184;json-model-extensions;JSON Model Extensions;;JMX\n;16185;dooms-elytra-rebalance;Elytra Rebalance;;\nanother-dyeable-shulkers;16186;dyeable_shulkers;可染色潜影贝;Another Dyeable Shulkers;\n;16187;skullbug;重新引入头颅BUG;Skull Bug Reintroducer;SBR\n;16188;thatskyinteractions;ThatSkyInteractions;;TSI\n;16189;tinker_transplant;匠魂移植;Tinker Transplant;TTP\nchisatos-more-redstone;16190;cmrs;Chisato的更多红石;Chisato's More Redstone;CMRS\nassetmover;16191;AssetMover,assetmover;AssetMover;;\nduclib;16192;duclib;DucLib;;\n;16193;asc;AutoServerCommand;;ASC\norecrop;16194;orecrop;矿石作物;OreCrop;\n;16195;vinurl;VinURL Music;;\nnutritionz;16196;nutritionz;NutritionZ;;\nxxl-packets;16197;XXLPackets;XXL Packets;;\nuncharted;16198;Uncharted,uncharted;Uncharted;;\nmcfetch;16199;mcfetch;MC Fetch;;\nreignited-hud;16200;reignitedhud;Reignited HUD;;\nmore-crafter-variants;16201;quad-lolmcrv;More Crafter Variants;;\nsharingan-addon-ahznbs-naruto-mod;16202;sharinganaddon;AHZNB's Naruto | Sharingans;;\ncreate-tacz-automation;16203;createtaczauto;Create: TaCZ Automation;;\ntacz-plus-timeless-and-classics-feature-addon;16204;taczplus;TACZ: Plus;;\nfibers-guns-pack-for-tacz;16205;fibers;Fibers' Guns Pack for TACZ;;\n;16206;splinecart;Splinecart;;\nbeams;16207;beams;Beams;;\ndiscarnate;16208;discarnate;Discarnate;;\n;16209;fishmc;Fish Weapons (FishMC);;\nmob-armor-trims;16210;mob_armor_trims;Naturally Trimmed / Mob Armor Trims;;\nbubbles-a-baubles-fork;16211;;Bubbles;;\narst-motion-blur;16212;arst_motion_blur;Arst Motion Blur;;\n;16213;palladium;钯;Palladium;\nfirehud;16214;firehud;Firehud;;\n;16215;elytraautopilot;Elytra Autopilot;;\npersonal-worlds;16217;personalworlds;Personal Worlds;;\npearltickets;16218;pearltickets;PearlTickets;;\ncobweb;16219;cobweb;Cobweb;;\nprogression;16220;progression;Progression;;\nstop-repeating-command-block-reborn;16221;srcbr;停！循环型命令方块：重生;Stop Repeating Command Block Reborn;SRCBR\nparry-us;16222;parry;予以刀振/格挡！;Parry;\n;16223;random_clover;随机四叶草;Random Clover;RC\nfrycooks-delight;16224;frycooks_delight;油炸乐事;Frycook's Delight;\nadvancedae;16225;advanced_ae;高级AE;AdvancedAE;AAE\npretty-pipes-fluids;16227;ppfluid;更好的管道：流体;Pretty Pipes: Fluids;\nwebstone;16228;webstone;Webstone;;\nrotatable-blocks;16229;rotatableblocks;Rotatable Blocks;;\ntreasure-seas;16230;treasure_seas;宝藏海;Treasure Seas;TSeas\nambidextrous;16231;ambidextrous;Ambidextrous;;\n;16232;always-sprint;Always Sprint;;\ntea-the-story-recount;16233;teastory;茶风·纪事：追叙;Tea the Story : Recount;TSR\nxp-challenge;16234;xp_challenge;经验挑战;XP Challenge;XPC\n;16235;styled_renaming;styled renaming;;\nsimple-block-overlay;16236;simpleblockoverlay;Simple Block Overlay;;\n;16237;helpful_action_bar;Helpfull Action Bar;;HAB\nrepeatable-trial-vaults;16238;repeatable_trial_vaults;Repeatable Trial Vaults;;\nfast-async-world-save-forge-fabric;16239;fastasyncworldsave;Fast Async World Save;;\n;16240;chatmagic;Chat Magic;;\n;16241;ftbschools;FTB School;;\nunlimitedelytra;16242;unlimitedelytra;无限鞘翅;UnlimitedElytra;\nrecrafted-creatures;16243;recrafted_creatures;Recrafted Creatures;;\nreciperesearch;16244;reciperesearch;合成配方研究;RecipeResearch;\nthaumon;16245;thaumon;神秘再饰;Thaumon;\ncreate-firearms-patch;16246;kinetic_pixel;机械动力火器补丁;Create Firearms Patch(kinetic pixel);\ncreate-eureka;16247;create_eureka;Create: Eureka!;;\nforsaken-corpses;16248;lying_skeletons;Forsaken Corpses;;\ncat-variant-eggs;16249;catvarianteggs;Cat Variant Eggs;;\nmore-colorful;16250;morecolorful;更加多彩的世界;More Colorful;\nvoid-island-control-2-0;16251;voidislandcontrol;Void Island Control Inverted Edition;;\nfishing-rod-fix;16252;fishingrodfix;Fishing Rod Fix;;\n;16253;additionalentityattributes;Additional Entity Attributes;;AEA\n;16254;additionalentityattributes;Additional Entity Attributes (Forge);;AEA\nkubejs-sanity;16255;sanityjs;KubeJS Sanity;;SanJS\n;16256;worleycaves;古早沃利的洞穴;Worley's Caves Archaic;WCA\njourneymap-web-map;16257;journeymap_webmap;旅行地图：网络地图;JourneyMap Web Map;\nrain-should-extinguish-campfires;16258;rsec;Rain should extinguish campfires;;RSEC\n;16259;mobdeathsound;Mob Death Sounds;;\ngauntlets;16260;gauntlets;Gallant Gauntlets / Gauntlets;;\n;16261;TimeVial;时间之瓶;TimeVial;\n;16262;lost_time_space;失落时空;Lost Time Space;LTS\nthrown;16263;thrown;扔!;Thrown;To\ntacz-npcs-also-vpb;16264;tacz_npcs;TACZ: Npcs;;\n;16265;srpaig;逃逸：无适应性发生器;Scape and Run Parasites Addon: Immalleable Generator;SRP:AIG\n;16266;crystaldream;晶彩梦艺;Crystal Dream;\n;16267;isscustom;Iron's Spells 'n Spellbooks：遥远挡震;ISSCustom;ISSC\nthe-last-narrow-gauge-steam-locomotive;16268;;最后的蒸汽小火车;The last narrow-gauge steam locomotive;TLNT\nhotdog-delight;16269;hotdog_delight;Hotdog Delight;;\nlets-do-addon-corn-expansion;16270;cornexpansion;玉食俱馩;[Let's Do Addon] Corn Expansion;\n;16271;ornithe_carpet;OrnithedCarpet;;OC\n;16272;client_maps;Client Maps;;\ndragon-gear-renewed;16273;dragon_gear_renewed;Dragon Gear Renewed;;\n;16274;archers_plushies;Archer's Plushies;;\n;16275;hephaestusexpansion;Hephaestus Expansion;;\ncreate-structures-arise;16276;create_structures_arise;Create: Structures Arise;;\nkilt;16277;kilt;Kilt;;\ngregtech-extended-chemistry-extended;16278;gtec;Gregtech: Extended Chemistry Extended;;\nulv-covers-modern;16279;ulvcovm;ULV Covers Modern;;\nfarmers-structures;16280;farmers_structures;Farmers Structures;;\n;16281;souloflegends;只狼死亡界面;SekiroDiedScreen;\n;16282;minecartspeeddisplay;矿车速度显示;MinecartSpeedDisplay;MSD\ncrossstitch;16283;crossstitch;CrossStitch;;\n;16284;vanilla_portal;原版传送工艺;VanillaTeleportationTechnology;\nfree-camera-api;16285;freecameraapi,free_camera_api;自由相机API;FreeCameraAPI;\n;16286;flashback;Flashback;;\nbloodmagic-meteors-jei-support;16287;bmj;血魔法陨星显示;BloodMagic Meteors JEI Support;BMJ\nlimited-damage-indicator-particle;16288;ldip;伤害粒子限制;Limited Damage Indicator Particle;LDIP\nnetherite-extension;16289;netherite_ext;下界合金拓展续作;Netherite Extension;\nimmersive-farming;16290;immersivefarming;沉浸农业;Immersive Farming;\n;16291;mr_vpb_toomanyguns;VPB: Too Many Guns;;\ndeath-angels;16292;death_angels;死亡天使;Death Angels;\nthe-pale-hound;16293;pale_hound;The Pale Hound;;\nmonsters-girls;16294;monsters_girls;Monsters & Girls;;\nmonsters-girls-secrets-of-mermaids;16295;mg_secrets_of_mermaids;Monsters&Girls: Secrets of Mermaids;;\ndios-tiny-ore;16296;dio_tiny_ore;Dio's Tiny Ore;;\ndefence-craft;16297;defence_craft;Defence Craft;;\n;16298;scraping_knife;Scraping Knife;;\ncopper-equipment;16299;exlinecopperequipment;Exline's Copper Equipment;;\nterramity;16300;terramity;Terramity;;\n;16301;lovemod;结婚：芙宁娜版;LoveMod:Furina;LM\n;16302;smwl;简易模组白名单;SimplestModWhiteList;SMWL\n;16303;dt;数据包工具;Data Tools;DT\nneptune-lib;16304;neptune;Neptune;;\nsign-edit-lite;16305;signeditlite;告示牌编辑轻量版;Sign Edit Lite;\n;16306;book-formatting;Book Formatting;;\n;16307;morewaterlogging;More Waterlogging;;\n;16308;rdmr;随机死亡消息：重生;RandomDeathMessageReborn;RDMR\n;16309;csms;自定义望远镜覆盖层;CustomSpyglassMagnificationOverlay;CSMS\n;16310;chatting;Chatting;;\n;16311;polycrosshair;PolyCrosshair;;\nbackup-resapwn-point;16312;;备用重生点;Backup-Resapwn-Point;\nrespawning-structures-forge-fabric;16313;respawningstructures;Respawning Structures;;\n;16314;sticky_honey;Sticky Honey;;\nhorseman;16315;horseman;Horseman;;\n;16316;easyplacefix;轻松放置修复;EasyPlaceFix;\nscreenshot-flash;16317;flash;一闪;Screenshot Flash / Flash;\nwarp-zone;16318;warpzone;Warp Zone;;\nfaded-widgets;16319;fadedwidgets;淡出界面;Faded Widgets;\nknowledges;16320;knowledges;知识库;Knowledges;\npanda-genetics-tweak;16321;panda_genetics_tweak;Panda Genetics Tweak;;\ncolourful-llamas;16322;colourfulllamas;Colourful Llamas;;\n;16323;chenchen;Chen's Japanese Decoration;;\n;16324;netherite_scrap_plate;Netherite Scrap Piece;;\ncreate-halitosis-create-halitosis;16325;createhalitosis;Create: Halitosis;;\ncreate-unify;16326;unify;Create: Unify;;\ncreate-more-sand-papers;16327;createsandpapers;Create: More Sandpapers;;\nsawmill-house;16328;sawmillhouse;Sawmill House;;\nkaboom-moon;16329;kaboommoon;Kaboom Moon;;\nhardcore-revive-mod;16330;hardcore-revive-mod;Revivable Hardcore Players;;\n;16331;bamboo_sword;Bamboo Sword;;\n;16332;exlcore;Ex Life Core;;EXLC\npetrols-parts;16333;petrolsparts;Petrol 的机件;Petrol's Parts;\nslavic-delight;16334;slavic-delight;Slavic Delight;;\naquamirae-delight;16335;aquamirae-delight;海灵乐事;Aquamirae Delight;\ndumplings-delight-rewrapped;16336;dumplings_delight;Dumplings Delight Rewrapped;;\nmy-nethers-delight-refabricated;16337;mynethersdelight;My Nether's Delight Refabricated;;\n;16338;polyweather;PolyWeather;;\nshadow-x-axolotl-buckets-plus;16339;axolotlbucketsplus;Axolotl Buckets +;;\ndaily-shop;16340;dailyshop;Daily Shop 🫙;;\n;16341;recipes;Recipes+;;\nno-tree-to-destory;16342;notreedestory;你不准破坏木头！;No Tree To Destory;NTD\n;16343;nodiparticle;去除伤害指示器粒子;nodiparticle;\n;16344;FireproofNetherite;Fireproof Netherite;;\ngrowable-goodies;16345;growablegoodies;Growable Goodies;;\npetrolpark-library;16346;petrolpark;Petrolpark 的库;Petrolpark's Library;\n;16347;ZeroPointBugfix;ZeroPointBugfix;;\nancient-reforging;16348;ancientreforging;Ancient Reforging;;\nstackmob-fabric;16349;stackmob-fabric;StackMob Fabric;;\nitemindicator;16350;itemindicator;Item Indicator;;\nkenshiro-mod;16351;AS_Kenshiro,AS_KSM;北斗神拳;Kenshiro Mod;\nroamers;16352;roamers;Roamers;;\n;16353;les;LegacyCanEditSign;;\ntitle-screen-mobs;16354;title_screen_mobs;Title Screen Mobs;;\n;16355;chang_sheng_jue;中式内容;;\nantiblocksrechiseled;16356;antiblocksrechiseled;AntiBlocksReChiseled;;\n;16357;false_emerald;False Emerald;;\nend-ores;16358;endores;End Ores;;\ntnt-expanded;16359;tnt_expanded;TNT Expanded;;\nglimmering-tales;16360;glimmeringtales;微光纪事;Glimmering Tales;GT\nfluid-interaction-tweaker;16361;fluidintetweaker;自定义流体交互;Fluid Interaction Tweaker;FIT\nkubejs-extended-crafting;16362;kubejs_extended_crafting;KubeJS Extended Crafting;;\nkubejs-addon-blood-magic;16363;kubejsbloodmagic;KubeJS Addon - Blood Magic;;\ncustom-portal-builder;16364;customportalsmod;Custom Portal Builder;;\nbiome-difficulty;16365;biomedifficulty;Biome Difficulty;;\n;16366;viaaprilfools;ViaAprilFools;;\ntfc-weld-button;16367;tfcweldbutton;TFC Weld Button;;\ntfc-regrowing-forests;16368;tfcrf;TFC Regrowing Forests;;\nccdbridge;16369;ccdbridge;CC: Destroy Bridge;;\nwelcome-message;16370;welcomemessage;Welcome Message;;\nauto-attack-rebirth;16371;autoattack;Auto Attack Rebirth;;\nlotas;16372;lotas;LoTAS;;\ntasmod;16373;tasmod;TASmod;;\ncanopy;16374;canopy;Canopy;;\nbetterfly;16375;betterfly;飞起来！;BetterFly;BF\n;16376;leashedplayer;可拴玩家;LeashedPlayer;LP\nmelodymagic;16377;melodymagic;旋律魔法;MelodyMagic;MM\nscaling-mob-difficulty-fork;16378;scalingmobs;知时修倍;Scaling Mob Difficulty Fork;\ntrajectoryprojections;16379;trajectory;弹道预测;Trajectory;\naccessories;16380;accessories;Accessories;;\nonewholibs;16381;onewholibs;onewholibs / 1wholibs;;\nnucleus;16382;nucleus_pauljoda;Nucleus;;\n;16383;equator;Equator;;\naccessories-cc-layer;16384;cclayer;Curios Compat Layer for Accessories;;\naccessories-tc-layer;16385;tclayer;Trinkets Compat Layer for Accessories;;\n;16386;confetti;Confetti Lib;;\nunderstudy;16387;understudy;Understudy;;\n;16388;gtlitecore;GregTech Lite Core;;\nmaterial-replication;16389;materialreplication;Material Replication;;\nrsj;16390;rsjukeboxes;Redstone Jukeboxes;;\nirikana;16391;irikana;伊利康纳;Irikana;IKA\nmts-iv-aq-studios-himars;16392;m142_himars;AQ studios Himars;;\nimmersive-vehicles-iv-mts-lindqvist;16393;lindqvist;Lindqvist;;\nimmersive-vehicles-iv-mts-zroskyan-ordinary;16394;mc172_oak;Zroskyan Ordinary Vehicles;;\npetroleums-wigs;16395;petroleums_wigs;Petroleum's Wigs;;\nrevenge-mark;16396;revengemark;遗怨待战 / 复仇标记;Revenge Mark;\n;16398;nestle;贴贴;Nestle;\n;16399;timesync;时间同步;TimeSync;\n;16400;mechano;Mechano;;\n;16401;coggle;Create: Coggle;;\ncreate-enchantable-machinery;16402;createenchantablemachinery;Create: Enchantable Machinery;;\nbad-eyes;16403;badeyes;近视眼;Bad Eyes;\nbad-dreams;16404;bad_sleep;噩梦;Bad Dreams;\nturtlebois-rpg-classes;16405;turtlerpgclasses;TurtleBoi's RPG Classes;;\nthrough-the-lily-pads-gently;16406;through-the-lilypads-gently;Through the lily pads, gently;;\ndecofirmacraft;16407;dfc;DecoFirmaCraft;;DFC\n;16408;dust;Dust;;\n;16409;beeg-smol;Beeg Smol;;\nscaling-mobs;16410;scalingmobs;Scaling Mob Difficulty;;\nmultiline-mastery;16411;multilinemastery;Multiline Mastery;;\n;16412;confetti-stuff;Confetti Stuff;;\npirates-treasures;16413;pirates_treasures;Pirates & Treasures;;\nars-nouveau-delight;16414;ars-nouveau-delight;Ars Nouveau Delight;;\ncrossbow-enchants;16415;crossbow-enchants;Crossbow Enchants;;\nextendeddrawersaddon;16416;extendeddrawersaddon;ExtendedDrawersAddon;;\nminecolonies-letsdo;16417;minecolonies_letsdo;Let's Do addon for MineColonies;;\nearlystage;16418;earlystage;EarlyStage;;\nsmitherz;16419;smitherz;SmitherZ;;\nadditionz;16420;additionz;AdditionZ;;\nbjornlib;16421;bjornlib;BjornLib;;\n;16422;tsr;时间同步：重生;TimeSyncReborn;TSR\n;16423;taotiesmunchies;饕餮飨食;Taotie's Munchies;\nskewers-reflavored;16424;skewers;串起酥食/烤串：再调味;Skewers Reflavored;\nlinear-xp-forge;16425;linearxpforge;验依定值/线性经验;Linear Xp Curve;\n;16426;solidarytinker;工匠联合;SolidaryTinker;ST\n;16427;CALCULATOR;计算器;;\ndumplings-delight-reload;16428;dumplings_delight;饺子乐事：重启;Dumplings Delight Reload;\nauto-switch-elytra;16429;auto-switch-elytra,auto_switch_elytra;自动切换鞘翅;Auto Switch Elytra;\ngregtech-placeable-emitters;16430;greg_emitters;GregTech Placeable Emitters;;\n;16431;;The old Durability101;;\n;16432;;UseCount;;\n;16433;;UseCount2;;\n;16434;jeihistory;JEI History;;\nzakuro;16435;zakuro;榴;Zakuro;\ndropoff;16436;dropoff;DropOff;;\nupgraded-totems;16437;upgraded-totems;Upgraded Totems;;\n;16438;playerchains;Player Chains;;\nanti-creative-drift;16439;anticreativedrift;Anti Creative Drift;;\ndamage-incorporated;16440;damage_incorporated;Damage Incorporated;;\n;16441;gunskills;GunSkills;;GS\n;16442;totem_in_chest;箱子里的图腾;;\n;16443;hyjg_custom_healthbar;原版-血条显示;;\n;16444;potato_mace;Potato Mace;;\nexmodifier;16445;exmodifier;极墨重锻-革故鼎新;ExModifier;EM\n;16446;dr;受潮：重生;DampReborn;DR\n;16447;cbr;CreepersBurnReborn;;CBR\nlazy-dfu-forge;16448;;DFU载入优化 [FORGE];Lazy DataFixerUpper(LazyDFU) [FORGE];\n;16449;hitokotomc;MC·一言API;HitokotoMC;\ncreaturechat;16450;creaturechat;CreatureChat;;\nhelldivers-spread-democracy;16451;helldivers;Helldivers - Spread Democracy;;\nhelldivers-2-gun-pack-vics-point-blank;16452;pointblank-helldivers2-pack;Helldivers 2 Gun Pack | Vic's Point Blank;;\nvariant-tools-and-weaponry-more-weapons;16453;vtaw_mw;Variant Tools and Weaponry - More Weapons;;\nmgrr;16454;mgrr;Mine Gear Rising: Revengeance;;MGRR\narcanemysteries;16455;projectshadow;Arcane Mysteries - Secrets of the World;;\nbotanic-pledge;16456;botanicpledge;Botanic Pledge;;\nrediculous-ore-generation;16457;better_ore_generation;Ridiculous Ore Generation;;\nwitchy-wearables;16458;witchywearables;Witchy Wearables;;\nsimple-delights-recooked;16459;simpledelights;重启爽食/简单乐事：再烹饪;Simple Delights Recooked;\nbraziliandelight;16460;braziliandelight;巴西乐事;Brazilian Delight;\napplied-energistics-delight;16461;applied-energistics-delight,appliedenergisticsdelight;Applied Energistics Delight;;\nextra-meat;16462;extrameat;Extra Meat;;\npeculiar-primordials;16463;peculiarprimordials;Peculiar Primordials;;\ntime-wind-forge;16464;tawct;Time & Wind;;\n;16465;mcbrowser;MCBrowser;;\nadditional-trims;16466;additional-trims;Additional Trims;;\nupgraded-trims;16467;upgraded-trims;Upgraded Trims;;\nwater-balloon;16468;waterballoon;Water Balloon;;\ncataclysm-spellbooks;16469;cataclysm_spellbooks;Cataclysm: Spellbooks;;\ncall-of-drowner;16470;call_of_drowner;溺亡者之嚎;Call of Drowner;\n;16471;parasite_era,tper;寄染之时 · 重生;The Parasite Era: Reborn;TPER\nendofdays;16472;endofdays;终焉之日-重铸;PrejectZ;\n;16473;cleanenergy;清洁能源;Clean Energy;CE\nfastfood-delight-unofficial;16474;fastfooddelight;重启速食/快餐乐事非官方版;FastFood Delight Unofficial;\nfaster-fly-block-breaking;16475;fasterflyblockbreak;飞行速速破;Faster Fly Block Breaking;\nupgraded-additional-trims;16476;upgraded-additional-trims;Upgraded Additional Trims;;\ncreate-let-the-adventure-begin;16477;create_ltab;Create: Let The Adventure Begin;;\nbountiful-critters;16478;bountiful_critters;Bountiful Critters;;\nflashlight-mcowners;16479;mcrowners_flashlight;Flashlight MCowners;;\nsmartpot;16480;smart_pot;智能锅;SmartPot;\n;16481;dual_weapon_combat;双持战斗;;\n;16482;LibShapeDraw;LibShapeDraw;;\nmerciful-void;16483;mercifulvoid;渊佑堕者/慈悲的虚空;Merciful Void;\ngsic2;16484;gsic2;星空工业;GSIC2;\nopen-energistics;16485;openerg;Open Energistics;;OE\n;16486;friendsmod;The Friends Mod;;\n;16487;constructionwand;Construction Wand (Fabric);;\nosmiumspacetiers;16488;dfs;Osmium Space Tiers;;\nbodyguard;16489;bodyguard;Bodyguard;;\n;16490;quickmenu;快捷菜单;Quick Menu;\nlazrs-lib;16491;lazrslib;Lazr's Lib;;LL\ncute-words-chat-replacer;16492;cutewords;雅言订正/聊天内容替换;Cute Words;\n;16493;masi;魔法咏唱;;masi\n;16494;PotionColorizer;Potion Colorizer;;\nhotbath;16495;hotbath;热水澡;Hot Bath;\n;16496;no_double_sneak;No Double Sneak;;\ntfc-hammer-time;16497;tfc_hammer_time;TFC Hammer Time;;\ntfc-better-stone-age;16498;bsa;TFC Better Stone Age;;BSA\nbattery-status-info;16499;batterystatusinfo;Battery Status Info;;\nmineteam;16500;mine_team;MineTeam;;\n;16501;ncm;禁用创造模式;NoCreativeMode;NCM\n;16502;dtbf;DarkTitleBarForge;;\ncroaklib;16503;croaklib;CroakLib;;\n;16504;it_is_forbidden_to_drop_stones;禁止掉落石头;;\nnbt-copy;16505;nbt_copy;NBT Copy;;\n;16506;phtsg;虚化望远镜;Phantom Spyglass;\nmore-totem-effects;16507;more_totem_effects;More Totem Effects;;\nbakeyourbread;16508;bakeyourbread;Bake Your Bread;;\nmodern-debugify;16509;;Modern Debugify;;\nrecipe-essentials-forge-fabric;16510;recipeessentials;Recipe Essentials;;\nzombie-villager-control;16511;zombievillagercontrol;催起死尸/僵尸村民控制;Zombie Villager Control;\n;16512;pvp;1.8 PVP Mod;;\n;16513;bsq;黑屏安静;BlackScreenQuiet;BSQ\n;16514;sk;SuicideKeyReborn;;\n;16515;too_many_shortcuts;Too Many Shortcuts;;\nbetter-horse-movement;16516;betterhorses;Better Horse Movement;;\ntct-core;16517;tctcore;TCT Core;;\nobscure-tooltips-fix;16518;obscure_tooltips_fix;Obscure Tooltips Fix;;\n;16519;lazylogin;LazyLogin;;\n;16520;notica;Notica;;\nmountain-sea-and-continent-two;16521;shanhaicontinent;山海大陆3;Mountain Sea and Continent Three;\nzhonglv-12et;16522;zhonglv12et;钟吕十二律;ZhongLv-12ET;\n;16523;ding;打铁匠人;DingMod;\n;16524;furnitureplan;家具计划;FurniturePlan;FP\njust-another-random-spawn;16525;randomspawn;遥缘调址/随机出生点;Just Another Random Spawn;\n;16526;kaimyentity;KAI我的实体-C;KAIMyEntity-C;KAI\nrealistic-mobs;16527;realistic-mobs;Realistic Mobs;;\nfullthrottle-alchemist;16528;Project_Alchemy;FullThrottle Alchemist;;FTA\nfullthrottle-nei;16529;fullthrottlenei;FullThrottle NEI;;\ncreature-compendium;16530;creature_compendium;Creature Compendium;;\ndesert-update;16531;desert_update;Desert Update;;\nancient-elements;16532;ancient_elements;Ancient Elements;;\nleft-behind;16533;leftbehind;Left Behind;;\ncorrupted-witherskeleton-boss-mod;16534;corrupted_witherskeleton_boss;Corrupted Witherskeleton Boss;;\n;16535;ddd;Deadly Deadly Dungeon;;DDD\nmca-api;16536;mac;Doom Core/MAC API;;MAC\n;16537;mr_exp_leveluphealth;经验提升生命;Exp Level Up Health;ELUH\nsodium-leaf-culling;16538;sodiumleafculling;钠：树叶剔除;Sodium Leaf Culling;\nhoming-xp-orb;16539;homingxporb;引验导追/归航经验球;Homing Xp Orb;\nmultiblocked2;16540;mbd2;Multiblocked2;;MBD2\narmorstatushud-updated;16541;armorstatushud;耐久信息显示更新版;ArmorStatusHUD Updated;\ntfc-better-blast-furnace;16542;tfcbetterbf;TFC Better Blast Furnace;;\ntfc-textile;16543;tfc_textile;TFC Textile;;\ntfc-woodworking;16544;tfcwoodwork;TFC Woodworking;;\ntfc-auroras;16545;tfcauroras,auroras;Auroras;;\ngreg-paintings;16546;greg_paintings;Greg Paintings;;\ngtmthings;16547;gtmthings;GTM Things;;GTMT\ncreate-trading-floor;16548;trading_floor;机械动力：交易站点;Create: Trading Floor;\n;16549;fishycreatestuffs;Yu的机械动力附属;Fishy Create Stuffs;FCS\nbiolith;16550;biolith;Biolith;;\nhungrier;16551;hungrier;Hungrier;;\nfurnace-pickaxe-mod;16553;furnace_pickaxe;Furnace Pickaxe Mod;;\ncamouflage-doors-and-trapdoor;16554;camoflauge_doors_and_trapdoorfor;Camouflage Doors and Trapdoor;;\ndense-metals;16555;densemetals;Dense Metals;;\nactually-subtractions;16556;actuallyadditions;Actually Subtractions;;\nmechanical-botania;16557;mechanicalbotania;Mechanical Botania;;\npuddles;16558;puddles;Puddles;;\nyoyousukes-kabutoaddon-for-ahzs-shinobicraft;16559;kabutoaddon;KabutoAddon for AHZS's Shinobicraft Naruto Mod;;\nillager-world-war;16560;illager_world_war;Illager World War;;\nguns-craft-adventurers-arsenal;16561;gcaa;枪械工艺：冒险者的武器库;Guns Craft Adventurer's Arsenal;GCAA\n;16562;sins;Seven Deadly Sins Origins;;\ndifficult-raids-stronghold-survival-edition;16563;difficultraids;Difficult Raids - Stronghold Survival Edition;;\nsubnautica-flow;16564;subnautica_flow;Subnautica Flow;;\ncardinal-sins;16565;cardinal_sins;The Cardinal Sins;;\nunwrecked-ships-forge;16566;unwrecked_ships;Unwrecked Ships;;\nrevamped-shipwrecks-mod;16567;revamped_shipwrecks;Revamped Shipwrecks;;\nchristmas-culinary-desires-decorations;16568;christmas_culinary_desires;Christmas Culinary Desires & Decorations;;\n;16569;florum-sporum;Florum Sporum;;\nunofficial-bosses-of-mass-destruction-dungeon;16570;da;Unseen's Dungeon Additions;;UDA\n;16571;treasuretree;摇钱树;Treasure Tree;TT\n;16572;roadandtrafficlights;沥青路;;\n;16573;morethings_v3;更多物品;MoreThings;MT\n;16574;kill_sound;击杀特效自定义;kill_sound;\nexplosionvisualizer;16575;explosion-visualizer;爆炸信息显示;ExplosionVisualizer;\n;16576;more_vanilla_foods;更多原版食物;More Vanilla Foods;\nlet-me-feed-you;16577;letmefeedyou;吃取送食/玩家喂食;Let Me Feed You!;\n;16578;bigstru;更大的结构重制版;BiggerStructures;\ndrg-laser-pointer;16579;drglaserpointer;深岩银河激光指示器;DRG Laser Pointer;DRGLP\nspawner-head;16580;spawnerhead;Spawner Head;;\nmedieval-core-mod;16581;medieval;Medieval;;\ndarkmining;16582;darkmining;DarkMining - Better Block Drops;;\n;16583;mr_fullbright_multiplayer;Fullbright [Permanent Night Vision];;\n;16584;visualkeys;VisualKeys;;\nsodium-extra-information;16585;sodiumextrainformation;Sodium Extra Information;;\n;16586;bulktrade;Bulk Villager Trading;;BVT\nomniscience;16587;Omniscience;Omniscience;;\nillusion-onslaught;16588;illusion_onslaught;Illusion Onslaught;;\nrar-compat;16589;rarcompat;Relics: Artifacts Compat;;\nwards;16590;wards;Wards;;\nmoremcmeta-emissive-forge;16591;moremcmeta_emissive_plugin;MoreMcmeta Emissive Textures;;\n;16592;fantazia;Fantazia;;\nfalling-attack;16593;fallingattack;Falling Attack;;\nlesraisins-armor;16594;lrarmor;LesRaisins Armor;;\ngoety-spillage;16595;goety_spillage;Goety & Spillage;;\nwaifucraft;16596;waifucraft;Waifucraft;;\nsign-to-chat;16597;sst_tcs;阅言得知/聊天栏读取告示牌;Sign to Chat;\nrainimatormod;16598;rainimator;Rainimator Mod;;\nrealistic-airdrop;16599;dyairdrop;真实空投;Realistic Airdrop;\nkeepers-of-the-stones-ii;16600;power;Keepers of the Stones II;;\nwoodencog;16601;woodencog;Wooden Cog;;\nshadow-x-blocks-plus;16602;blocksplus;Blocks +;;\npet-essence;16603;petessence;Pet Essence;;\n;16604;hcrplus;Hardcore Revive+;;\nwatson;16605;watson,Watson;Watson;;\n;16606;pk_racks;Racks;;\nemerald-matcha-craft-v3;16607;emerald_matcha_craft_v3;翠茶工藝;Emerald Matcha Craft;EM-C\ncctv-camera;16608;camera_mods;CCTV Camera (CameraCraft);;\ntrash-compactor;16609;trashcompactor;Trash Compactor;;\nrustic-delight;16610;rusticdelight;乡村乐事;Rustic Delight;\ntotal-assault;16611;binah;Total Assault;;\nsky-breaker;16612;skybreaker;天穹突破;Sky Breaker;\n;16613;scala3std;Scala3Std;;S3STD\n;16615;hoofprint;Hoofprint;;\nbetter-controls;16616;bettercontrols;Better Controls;;\n;16617;optibye;Optibye;;\nkubejei;16618;kube_jei;KubeJEI;;\nactually-harvest;16619;actuallyharvest;Actually Harvest;;\ncity-craft;16620;citycraft;City Craft;;\n;16621;wpt;伍德的石化科技;Woody's Petrochemical Tech;WPT\neggs-better-vanilla-food;16622;eggsbettervanillafood;Eggs Better Food;;\nshelf-mod;16623;shelfmod;Shelf Mod;;\nnebulus-cherryblossom-tree;16624;nebuluscherryblossomtree;Nebulus' Cherryblossom Tree;;\nftb-rickety-water-wheel;16625;ftbricketyww;FTB Rickety Water Wheel;;\ncobblegen-galore;16626;cobblegengalore;Cobblegen Galore;;\n;16627;ringlesgunturret;Ringle's Gun Turret;;\ncreate-sky-village;16628;create_sky_village;Create: Sky Village;;\ncreate-crushing;16629;createcrushingresource;Create: Crushing;;\ncushy-pillows;16630;cushy-pillows;舒适枕头;Cushy Pillows;\nice-and-fire-patcher;16631;iaf_patcher;冰火传说随意修复;Ice And Fire Patcher;IAFP\ngoety-revelation;16632;goety_revelation;诡厄巫法：启示录;Goety: Revelation;GR\nthe-mandela-catalogue-alternates;16633;mandela_catalogue;The Mandela Catalogue: Alternates;;\nthe-craftela-catalogue;16634;mandela;The Craftela Catalogue:Alternates;;\nthe-ceilands;16635;ceilands;垂顶之地;The Ceilands;\n;16636;sharowings_better_dungeons;Sharowing's Better Dungeons;;\nsnowbound;16637;snowbound;Snowbound;;\nopencomputers-icbm-classic-addon;16638;eternalsoap.icbm.opencomputers;Opencomputers ICBM-Classic Addon;;\n;16639;pandoras_frontier;Ad Astra: Pandora's Frontier;;\ncosmic-horizons;16640;cosmos;Cosmic Horizons;;CH\nshadow-x-netherite-scrap-from-piglin-brutes;16641;netheritescrapfrompiglinbrutes;猪灵蛮兵掉落下界合金碎片;Netherite Scrap From Piglin Brutes;\n;16642;etcw;外置传送窗口;ExternalTeleportCommandWindow;ETCW\nbetter-modlist-neoforge;16643;better_modlist;更好的模组列表;Better ModList;\nsky-aesthetics;16644;sky_aesthetics;Sky Aesthetics;;\nheadshot-bonus;16645;headshotbonus;已无可惧 / 简单爆头;Headshot Bonus;\nenchantlevelbrek;16646;enchantmentlevelbreak;附魔等级上限突破2;EnchantmentLevelBreak;ELB2\nthe-ruler;16647;the_ruler;The Ruler;;\nbiome-borders-revived-liteloader-only;16648;ArmorsHUDRevived;Biome Borders Revived;;\nchunkborders;16649;chunkborders,ChunkBorders;区块边界;ChunkBorders;\nburnt;16650;burnt;Burnt;;\ngoatman;16651;goat_man;GoatMan;;\narmor-damage-limit;16653;armordamagelimit;甲哇！/盔甲耐久损失限制;Armor Damage Limit;\nknight-bodyguard-forge;16654;knight_mob;骑士保镖;Knight Bodyguard;\ndeeper-depths;16655;deeperdepths;Deeper Depths;;\nunseens-nether-backport;16656;nb;Unseen's Nether Backport;;UNB\ndelete-my-origins;16657;DeleteMyOrigins;内置种族禁用;Delete My Origins!;\nshadow-x-adventures-plus;16658;adventuresplus;Adventures +;;\nadventures-in-time;16659;ait;Adventures in Time;;AIT\nelementalist;16660;elementalist;Elementalist;;\n;16661;craft_slime_fabric;制作黏液球;Craft Slime Fabric;\nshadow-x-unactivated-totems;16662;unactivatedtotems;Unactivated Totems;;\ncagebox;16663;cagebox;CageBox;;\n;16664;BiomeBorders;Biome Borders;;\nrechiseled-ae2;16665;rechiseledae2;Rechiseled: AE2;;\narcheological;16666;archeological;Archeological;;\nclarity;16667;clarity;Clarity;;\nbackpacked-world-of-color;16668;backpacked-world-of-color;Backpacked: World of Color;;\nnofov;16669;nofov;NoFov;;\ncobalt-custom-chunk-renderer;16670;cobaltmod;钴;Cobalt;\nspartacus-army-uniform;16671;main_mod;Spartacus' Army Uniform;;\nsoviet-army-uniform;16672;sovietuniforms;Soviet Army Uniform;;\ngerman-uniforms-ww2;16673;germanuniforms;German Uniforms WW2;;\nfantasy-ending;16674;fantasy_ending;梦幻终焉;FantasyEnding;FE\n;16675;dual_weapon_combat_append;双持战斗扩展;;\n;16676;honghuang_xuixian;洪荒修仙;HongHuang XiuXian;HX\ntameable;16677;tameable;Tameable;;\nslashblade-gamerelic;16678;srelic;游戏扩增·异界之刃;SlashBladeGameRelic;SR\ntheovergrowth;16679;overgrowth;TheOvergrowth;;\njadens-nether-expansion;16680;netherexp;Jaden's Nether Expansion;;JNE\nconfigurable;16682;configurable;Configurable;;\n;16683;crr;ClientRegistryReborn;;\n;16684;cpd;聊天框坐标显示;ChatPositionDisplay;CPD\n;16685;macros;Macro / Keybind Mod;;\n;16686;MyCTMLib;连接纹理非官方版;ConnectedTexturesModUnofficial;CTMU\n;16687;early-loading-screen;Forge 加载屏幕移植;(Neo)Forge Early Loading Screen for Fabric;\n;16688;ebe;EnlightenedBlockEntities;;EBE\n;16689;spawn,tpa-mod;tpa传送命令;;\npatpat;16690;patpat;PatPat;;\nmodern-life-patch;16691;modernlifepatch;摩登生活补丁;Modern Life Patch;MLP\n;16692;ivr;IVR;;\nrusticdelight-refabricated;16693;rusticdelightrefabricated;乡村乐事：重织;RusticDelight: Refabricated;\nscape-and-run-meshi;16694;srpmeshi;逃逸：寄生饭;Scape and Run: Meshi;SRM\n;16695;bad_apple_world;Bad Apple World Preset;;\ngastronomy-works;16696;gastronomyworks;Gastronomy Works;;\n;16697;createairfabric;Create Air Fabric;;\n;16698;clockworkadditions;ClockworkAdditions;;\ncolorfulwater;16699;colored_water;ColorfulWater;;\nyungs-cave-biomes;16700;yungs_cave_biomes,yungscavebiomes;YUNG的洞穴生物群系;YUNG's Cave Biomes;\n;16701;legacy_command_registry;旧式命令注册;Legacy Command Registry;LCR\n;16702;ss;屏保;Screensaver;SS\n;16703;st,stf;启动提示音;StartupTone;ST\n;16705;watson_macros;Watson Macro/Keybind Support;;\n;16706;mappreset;地图模板;MapPreset;\nva-11-hall-a-drinks;16707;va11halla;Vall-11 Hall-A饮品;Vall-11 Hall-A Drinks;\nbettergive-reborn;16708;bettergive;Better Give: Reborn;;BGR\nblur-server-address;16709;blurserveraddress;Blur Server Address;;\nfoodvariations;16710;solclassic;生活调味料：经典版;Spice of Life: Classic Edition;\npizza-delight;16711;pizzadelight;Pizza Delight;;\nsimplefog;16712;simplefog;Simple Fog Control;;\n;16713;butter;butter;;\ntnt-plus-mod;16714;tntplusmod;TNT Plus;;\n;16715;fj21r;强制 Java21：重生;ForceJava21 Reborn;FJ21R\n;16716;betterplayeranimations;Kelvin's Better Player Animations (Port);;\ntokusatsurecord;16717;tokusatsurecord;特摄唱片;Tokusatsu Record;TR\n;16718;amh;模组隐藏器;AlwaysModHider;AMH\norigins-backgrounds-forge;16719;origins_backgrounds;Origins: Backgrounds;;\ncataclysm-apotheosis-addon;16720;cataclysm-apotheosis-addon;Cataclysm Apotheosis Addon;;\ncave-dweller-reimagined;16721;cave_dweller;Cave Dweller Reimagined;;\nout-of-combat;16722;out_of_combat;脱战;Out of Combat;\nsoulfragment;16723;soulfragment;灵魂碎片;Soul Fragment;\n;16724;gm4_mysterious_midnights;Mysterious Midnights;;\ncreate-peaceful;16725;create_peaceful;Create: Peaceful;;\nbaby-players;16726;babyplayers;Baby Players;;\ndynaores;16727;dynaores;动态粗矿;Dynamic Raw Ores;\nall-arrows-infinity-fix;16728;allarrowsinfinityfix;All Arrows Infinity Fix;;\nbundles-backport;16729;bundles;Bundles Backport;;\nits-magispelling-time;16730;magispelling;It's Magispelling Time;;\n;16731;attribute_extra;额外属性;Attribute Extra;AE\n;16732;wso;Windows启动界面;WindowsStartingOverlay;\n;16733;dontsneakprogress;偷跑禁止;DontSneakProgress;\n;16734;nkindled;Unkindled;;\nrecipe-generator;16735;recipe_generator;Recipe Generator;;\n;16736;carbonfltr;CarbonFLTR;;\nargonmod;16737;argon;氩;Argon;\nfoggypalegarden;16738;foggy-pale-garden;Foggy Pale Garden;;FPG\n;16739;itemsstack;Items Stack;;\narkane-domains;16740;arkane_domains;ARKANE DOMAINS;;\n;16741;zip_package;真·炸药包;C4.ZIP.Package;C4.ZIP\nyakumoblade;16742;yakumoblade;幻想之刃 / 刀藏录;yakumoblade;YA\nno-random-drop-offset;16743;norandomdropoffset;垂直落下/移除掉落随机偏移;No Random Drop Offset;\ncroptopias-chocolaterie-unofficial;16744;cacao;巧克力工坊：非官方版;Croptopia's Chocolaterie Unofficial;\nepic-fight-wukong-moveset;16745;wukong;史诗战斗：悟空;Epic Fight - Wukong Moveset;\n;16746;gntz;功能拓展数据包;;\npetphrasex;16747;petphrasex;口癖X;PetPhraseX;PPX\n;16748;identity-v-heoa;第五人格：全员幸终;Identity V: Happy Ending Of All;IVHEOA\ndust-and-ash;16749;dustandash;尘与烬;Dust and Ash;D&A\n;16750;eg_particle_interactions;Particle Interactions;;\n;16751;trialspawnertimer;试炼刷怪笼计时器;Trial Spawner Timer;\nenigmatic-delicacy;16752;enigmaticdelicacy;神秘佳肴;Enigmatic Delicacy;\nslashblade-resharped-renderfix-patch;16753;resharped_renderfix_patch;拔刀剑：重锋 渲染修复补丁;SlashBlade Resharped RenderFix Patch;SRRP\n;16754;no_weak_wheat;Invulnerable Wheat;;\nskin3d-but-not-cubic;16755;skins3d;Skin3D but not cubic;;\nleavesly;16756;leavesly;Leavesly;;\ncreate-totem-factory;16757;totemfactory;Create: Totem Factory;;\nsnad-redstone-edition;16758;snad;子沙：红石版;Snad:Redstone Edition;\n;16759;party-flame;Party Flames (Dyeable Fire);;\nsubtle-effects;16760;subtle_effects;Subtle Effects;;SE\n;16761;dma;DreamMasaAddition;;DMA\ncc-wasm;16762;ccwasm;cc wasm;;\nlegacy-patch;16763;legacy-patch;Legacy Patch;;\n;16764;gm4_shapeless_portals;Shapeless Portals;;\n;16765;glowup;GlowUp;;\n;16766;customsavedirs;CustomSaveDirs;;\njust-enough-magiculture;16767;justenoughmagiculture;Just Enough Magiculture;;\nae2-jei-integration;16768;ae2jeiintegration;AE2 JEI Integration;;\nentangled-fix;16769;entangledfix;Entangled Fix;;\n;16770;customdurabilitybar;Custom Durability Bar;;\n;16771;gm4_midnight_menaces;Midnight Menaces;;\nscape-and-run-revenants;16772;srrevenants;Scape and Run: Revenants;;SRR\nwarden-s-domain;16773;wd;Warden's Domain;;WD\nmore-wardens;16774;extrawardens;More Wardens;;\nhidden-realm;16775;hiddenrealm;Hidden Realm;;\nspells-gone-wrong;16776;spells_gone_wrong;Spells Gone Wrong (Iron's Spells N Spellbooks);;\nwandrous;16777;wandrous;Wandrous;;\ncnpc-dbc-addon;16778;npcdbc;CNPC+ DBC Addon;;\nsalmons-genesis;16779;salmons_genesis;Salmon's Genesis;;\nthe-afterdark;16780;the_afterdark;The Afterdark;;\nendlesssword;16781;endless_sword;无尽拔刀剑;Endless Sword;ES\nbh3;16782;bh3;崩坏3;;BH3\nmqs-decrafting-table;16783;mq_decrafting_table;MQ的分解台;;\nloaded-mods-checker;16784;loadedmodschecker;存档模组核对;Loaded Mods Checker;\ni-have-slept;16785;i_have_slept;朕已安寝;I have slept;\n;16786;itemstackpromax;物品堆叠ProMax;ItemStackProMax;ISPM\nitem-split-bug-fix;16787;item_split_bug_fix;物品分裂修复;Item Split Bug Fix;ISBF\nspontaneous-replace-cobwebbed;16788;spontaneous-replace-cobwebbed;自然更替：蛛丝网迹;Spontaneous-Replace: Cobwebbed;\nmodernconverter;16789;modernconverter,ModernConverter;ModernConverter;;\nfrozify;16790;frozify;Frozify;;\npotato-library;16791;potato;PotatoLib;;\nswisscaves;16792;swisscaves;SwissCaves;;\ntitle-screen-blobcat;16793;title_blobcat;Title Screen Blobcat;;\npunching-creepers;16794;punchingcreepers;Punching Creepers;;\nancient-curses;16795;ancientcurses;Ancient Curses;;\nresettleable-traders-forge-fabric;16796;resettleable_traders;流浪商人安家;Resettleable Traders;\nihearttfc;16797;ihearttfc;I ❤ TFC;;\nitemphysic-1-7-10-unofficial;16798;;物品物理掉落非官方版;ItemPhysic Legacy Unofficial;\njei-area-fixer;16799;jei_area_fixer;JEI Area Fixer;;\nars-elixirum-forge;16800;elixirum;Ars Elixirum;;\ngold-nugget-recycling;16801;mr_gold_nuggetrecycling;Gold Nugget Recycling;;\nsodium-options-api;16802;sodiumoptionsapi;Sodium/Embeddium Options API;;\n;16803;tr;TotomRegister;;TR\n;16804;satr;我TM睡睡睡睡睡睡睡：重铸;Sleep AnyTime:Reborn;SATR\ncrazyae;16805;crazyae;疯狂的AE;CrazyAE;\nae2-crafting-tree-legacy;16806;ae2ctl;AE2合成树-传统版;AE2CT-Legacy;AE2CTL\nae2-alchemistry-addon;16807;ae2alchemistryaddon;AE2 Alchemistry Addon;;\n;16808;acedium;Acedium;;\nnovaultlimit;16809;novaultlimit;NoVaultLimit;;\ngame-discs;16810;gamediscs;游戏光碟;Game Discs;\nluncheon-meat-s-delight;16811;luncheonmeatsdelight;午餐肉乐事;Luncheon Meat 'S Delight;\nvanilla-plus-resourceful-tools;16813;resourceful_tools;Resourceful Tools;;\ncreate-horse-power;16814;createhorsepower;Create Horse Power;;\nars-technica;16815;ars_technica;Ars Technica;;\nplustic-reforged;16816;plusticreforged;PlusTiC Reforged;;\nspawners-plus;16817;spawners_plus;刷怪笼拓展;Spawners+;\ndracolotl;16818;dracolotl;Dracolotl;;\n;16819;golems_tcon;Tinkers' Golems Addon Reborn;;\nsacks-n-such;16820;sns;Sacks 'N Such;;\n;16821;zbstpz;TPZ 传送命令;;tpz\n;16822;hbmaeaddon;以逸待整/汉;HBM-AEAddon;HAN\ncamellialib;16823;camellialib;山茶花核心;CamelliaLib;\ncameliaarmory;16824;cameliaarmory;花朵武器库;FlowerArmory;\n;16825;spongeneo;海绵端插件支持 NeoForge 版;SpongeNeo;\nmoonrise;16826;moonrise;Moonrise;;\ncorpsecomplex-unofficial;16827;corpsecomplex;Corpse Complex-Unofficial;;CCU\n;16828;starlish;Starlish ⭐;;\nlanguagereloadunofficial;16829;languagereload;LanguageReloadUnofficial;;\nmekanism-covers;16830;mekanismcovers;Mekanism Covers;;\nenchanted-wilderness-elegant-countryside;16831;elegant_countryside;雅致乡下;Elegant Countryside;EC\nbetter-durability-reforged;16832;betterdurability;更好的耐久度：重铸;Better Durability Reforged;\nfast-tpa;16833;fast_tpa;我来助你！;Fast TPA;\ncherry-on-1-12-2;16834;suikecherry;1.12.2的樱花;Cherry_on_1.12.2;\nscape-and-spartan-parasites;16835;swparasites;寄体斯巴达;Scape and Spartan: Parasites;SSP\n;16836;rotp_extra_dg;Ripples of the Past: Extra Stands Addon;;\nthe-knocker;16837;the_knocker;The Knocker;;\nvll-r-mystery;16838;vllr_mystery;Vll r Mystery - Creepypasta;;\nscp-origin;16839;scp_origin;SCP: Origin;;\nsniffers-delicacies;16840;sniffers-delicacies;Sniffer's delicacies;;\nfix-changed;16841;fc;Fix Changed;;FC\nvalkyrien-pirates;16842;pirates;瓦尔基里海盗;Valkyrien Pirates;VP\npathogen;16843;pathogen;Pathogen : Monster plague;;\nchrysalis;16844;chrysalis;Chrysalis;;\nalekis-ridiculously-simple-roofs;16845;alekiroofs;aleki's Ridiculously Simple Roofs;;\n;16846;mc-trails;Trails;;\nrainboows;16847;rainbows;Rainbows!;;\nparticle-effects;16848;texturized-particles,particle_effects;Particle Effects;;\nrple;16849;rple;Colored Lights (RPLE);;RPLE\nreign-of-nether-rts-in-minecraft;16850;reignofnether;下界之治;Reign of Nether RTS;RoN\nthe-secret-doors;16851;tsdpp;The Secret Doors;;\n;16852;NHUtilities;NH Utilities;;NHU\nimmersive-vehicles-iv-mts-veb-iav-improved-2000;16853;auweschvebc,auweschvebc1,auweschvebc2;“改进-2000”;;IM2K\ntame-tools;16854;tame_tools;驯兽工具;Tame Tools;TT\n;16855;bia;Bedrock Inventory Animations;;BIA\nvounierns-turrets;16856;v_turrets;Vouniern's Turrets;;\nseafarer-forge;16857;seafarer;Seafarer;;\nzombies-plus-frenzied-horde;16858;zombies_plus;Zombies+;;\n;16859;tetrismc;Tetris MC;;\necliptic-seasons;16860;eclipticseasons;节气;Ecliptic Seasons;\nxibaoxx;16861;xibaoxx;喜报 ××;XiBao ××;XBXX\n;16862;ga;杀戮光环：枪械;GunAura;GA\n;16863;;更好的匠魂战斗;Tinkers 'Better Combat;\nmurasama-daker;16864;murasama;鬼妖村正;Murasama;\nparticle-storm;16865;particlestorm;粒子风暴;Particle Storm;PS\n;16866;flat-minecraft;2D Minecraft;;\ntill-it-breaks-updated;16867;tillitbreaks;Till It Breaks Updated;;\ndebark;16868;debark;Debark;;\ntill-it-breaks;16869;tillitbreaks;Till it Breaks;;\nend-reborn-expansion;16870;end_reborn;End Reborn;;\n;16871;origins-genshin;Origins: Genshin;;\n;16873;log2me;聊天栏日志显示;Log2Me;\nthe-glitched-one;16874;man;The Glitched One;;\nprogression-reborn;16875;progression_reborn;Progression Reborn;;\n;16876;effecticularity,effective;Effective💦 (Forge);;\n;16877;coords_mod;Simple Info Display;;\n;16878;automaticmemories;AutomaticMemories;;\nzoom-camera;16879;zoom;Zoom Camera;;\ndpi-fix;16880;dpi-fix;DPI-Fix;;\n;16881;item_information_display;物品信息显示;Item Information Display;IID\nalmanac-lib;16882;almanac;Almanac LIB;;\n;16883;cbrecorder;命令方块日志记录;CBRecorder;CBR\nremove-blindness;16884;no-blindness;移除失明;Remove Blindess;\nsimplerpgcore;16885;simple_rpg_core;SimpleRPGCore;;SRC\ndroppable-villager-trades;16886;droppablevillagertrades;Droppable Villager Trades;;\nstackupper;16887;stackupper;StackUpper;;\ncolorful-orbs;16888;colorful-orbs;Colorful Orbs;;\n;16889;idmxd;I Died, My Xp Didn't.;;IDMXD\nmodernxl-2-in-survival;16890;modernxl_ii;Modernxl 2;;\ncreepercollateral;16891;CreeperCollateral,CreeperCollateralPreloader;CreeperCollateral;;\nfindmyitemsandfluids;16892;findme;Find My Items And Fluids;;\n;16893;numeric-experience-info;Numeric Experience Info;;\naquapatch;16894;aquapatch;AquaPatch;;\naviator-dreams;16895;aviator_dream;Aviator Dreams;;\nmacaws-stairs;16896;mcwstairs;Macaw的楼梯与露台;Macaw's Stairs;\ncross-stitch-colours;16897;dufxcsc;Cross Stitch Colours;;CSC\ngaming-deco;16898;gamingdeco;Gaming Deco;;\nreactive-music;16899;reactivemusic;韵律回响;Reactive Music;\neasel-does-it;16900;easel_does_it;Easel Does It!;;\nrbasamoyais-betsy-ross;16901;betsyross;rbasamoyai's Betsy Ross;;\n;16902;contingameimesuper;游戏内输入法Super;ContingameIMESuper;\n;16903;negorerouse;尼格洛兹·无尽曈曚：非官方版;NegoreRouse: unofficial;NRU\n;16905;level_strengthen_attributes;升级，加点！;;\ndropped-item-tweaks;16907;droppeditemtweaks;掉落物调整;Dropped Item Tweaks;\nmovingquickly;16908;movingquickly;禁用快速移动检查;MovingQuickly;\n;16909;soupapi;Soup API (PvP visuals);;\n;16910;tsa-decorations;Toms Server Additions: Decorations & Furniture;;\n;16911;autofarmmod;Autofarm Mod;;\ngelato-galore-2;16912;gelato_galore;意式冰淇淋盛宴;Gelato Galore 2;\ndyeable-jack-olanterns;16913;ls_djl;Dyeable Jack o'Lanterns;;\nwhite-pumpkins;16914;white_pumpkins;White Pumpkins;;\ndawn-of-the-flood;16915;dotf;Dawn of the Flood - \"Reinforcements from Hell\";;DOTF\ndeatbutthreereload;16916;deatbutthreereload;逝不过三：重开;Death But Three Reload;DBTR\ncommon-weapons;16917;common_weapons;Common Weapons;;CW\nore-silverfish-mod;16918;oresilverfish;Ore Silverfish Mod;;\ngtbcs-spellbooks;16919;gametechbcs_spellbooks;GTBC's Spellbooks - Iron's Spells Addon;;\nproject-lambda;16920;lambda;Project Lambda;;\n;16921;mr_create_bigglobecompatability;Create: Big Globe Compatability;;\n;16922;mr_big_globeyungsbetteroceanmonumentscompatibility;Big Globe - YUNG's Better Ocean Monuments Compatibility;;\n;16923;mr_big_globeyungsbetternetherfortressescompatibility;Big Globe - YUNG's Better Nether Fortresses Compatibility;;\n;16924;mr_big_globeyungsbetterwitchhutscompatibility;Big Globe - YUNG's Better Witch Huts Compatibility;;\n;16925;cushop;贸易学：通用商店;Catallactics: Universal Shop;CUshop\n;16926;prettyhitboxes_createaddition;可配置的碰撞箱显示-机械动力功能附加;Pretty Hitbox-Create Addition;\nwatersource-reborn;16927;watersource;水源：重生;WaterSource: Reborn;WSR\nlocks-unofficial;16928;locks;锁：非官方版;Locks-Unofficial;LU\nepicfight-improve;16929;epicfight_improve;史诗战斗：改进;EpicFight：Improve;EFI\njust-in-nether;16930;just_in_nether;Just-In NETHER;;\njinghui;16931;jinghui;镜回;JingHui;\n;16932;rotp_mandom;RotP：男人领域附属;Ripples of the Past: Mandom Addon;\n;16933;autogg;Auto GG;;\nbetter-brightness-slider-respawn;16934;betterbrightnesssliderrespawn;Better Brightness Slider Respawn;;BBSR\n;16935;mr_nice_villagersremastered;Nice Villagers Remastered;;\n;16936;pg;PlayerGyro;;PG\nanimatica-foxified;16937;animatica;Animatica Foxified;;\nmcchatgpt;16938;mcchatgpt;MCChatgpt;;\n;16939;aimobs;AImobs;;\nambiances;16940;ambiance;氛围！;Aurae!;\nscrew-your-items;16941;screwyouritems;Screw Your Items;;\nmace-port;16942;mace_port;Mace Combat Backport;;\nbloodlines;16943;bloodlines;Bloodlines - A Vampirism Addon;;\nvampiric-ageing-a-vampirism-addon;16944;vampiricageing;Vampiric Ageing - A Vampirism Addon;;\nextra-enchantments-and-curses;16945;extra-enchantments-and-curses;更多附魔和诅咒;Extra Enchantments and Curses;\nenchanting-tweaker;16946;enchantingtweaks;Enchanting Tweaker/Enchanting Tweaks;;\njust-a-lot-more-enchantments;16947;jlme;Just a lot more enchantments;;JLME\nalessandrvs-enchantments;16948;alessandrvenchantments;Alessandrv's Enchantments;;\narmor-abilities;16949;aabilites;Armor Abilities;;\nsleepy-hollows;16950;sleepy_hollows;沉眠空谷;Sleepy Hollows;\n;16951;epw;本地鞘翅穿墙飞行;LocalElytraPenetratesWalls;\nnametag-tweaks;16952;nametagtweaks;名称标签调整;Nametag (Display) Tweaks;\n;16953;sodium-no-alerts;Sodium No Alerts;;\nheadshot-respawn;16954;headshotrespawn;爆头：重生;Head Shot Respawn;HSR\nleafdecaystopper;16955;leaf_decay_stopper;树叶腐烂抑制器;Leaf Decay Stopper;\nanimated-loading-screen;16956;animated-logo;Animated loading screen;;\n;16957;rotp_whitesnake;RotP：白蛇附属;Whitesnake Addon (Ripples of the Past);\n;16958;rotp_mih;RotP：天堂制造附属;Ripples of the Past: Made in Heaven Addon;\n;16959;rotp_cm;RotP：新月附属;Ripples of the Past: C-Moon Addon;\nimbued-gear;16960;imbued_gear;Imbued Gear;;\ntacz-js;16961;taczjs;TaCZ JS;;\nipla;16962;ipla;Ipla;;\nbcg-util;16963;bcgutil;BCG Util;;\nsciophobia;16964;sciophobia;全局自定义文本阴影渲染;Sciophobia;\nlithic-addon-mod;16965;lithicaddon;Lithic TFC Addon;;\nlight-blocks-from-1-17;16966;light;Light Block Mod;;\nlight-and-shadow;16967;light_and_shadow;Light And Shadow;;\nexcavein;16968;excavein;QoM: Excavein;;\nspanish-delight-a-farmers-delight-add-on;16969;spanishdelight;西班牙乐事;Spanish Delight;\nars-nouveaus-flavors-delight;16970;arsdelight;新生乐事;Ars Nouveau's Flavors & Delight / Ars Delight;\noceanic-delight;16971;oceanic_delight;海之乐事;Oceanic Delight;\nthe-player;16972;the_player;The \"Player\";;\nkelka-craftable-animals;16973;craftableanimalsneo;Craftable Animals;;\nsuper-hyper;16974;hyperremaster;Hyper (Boss & More);;\nwans-ancient-beasts;16975;wan_ancient_beasts;Wan的远古异兽;Wan's Ancient Beasts;WAB\ninvocations-spell-engine;16976;invoke;Invocations (Spell Engine);;\ngearifiers;16977;gearifiers;Gearifiers;;\nrecall-potion-neoforge;16978;recall_potion;回忆药水;Recall Potion;\nflowing-fluids;16979;flowing_fluids;Flowing Fluids;;\nfrights-and-foliage;16980;frights_and_foliage;Frights and Foliage;;\nblorfstone;16981;blorfstone;Blorfstone;;\nfilinkus-s-huge-tools;16982;hugetools;FilinKus`s Huge Tools;;\nnecromancer-mod;16983;necromancer;Necromancer Mod;;\ne_kedis-electronics;16984;ekedis_electronics;E_Kedi's Electronics;;\nuprade-yourself;16985;upmod;Upgrade yourself!;;\n;16986;takeyourpills;魔法征途;Magic Adventure;MA\nsmelting-touch-enchantment;16987;smelting_touch_rebooted;Smelting Touch Enchantment;;\nshinobiorigins-utils;16988;originsutils;ShinobiOrigin's Utils;;\nunifine;16989;unifine;Unifine;;\ntrailier-tales;16990;trailiertales;Trailier Tales;;\nmixinjs-coremod;16991;mixinjs;MixinJs;;\nmcwen;16992;mcwen;MCWen;;\nrank-system;16993;rank_system;评分系统;Rank System;RS\n;16994;copf;Creative One-Punch Forge;;COPF\nbundle-inventory;16995;bundleinventory;Bundle Inventory;;\nscroll-tweaks;16996;scrolltweaks;鼠标滚轮调整;Scroll Tweaks;\nst;16997;st,dev.anye.mc.st;服务器工具;Server Tools;ST\nride-every-thing;16998;rideeverything;骑乘一切;Ride Every Thing;RET\ncit-resewn-neopatcher;16999;citresewn_neopatcher;CITResewnNeoPatcher;;\nlegendary-creatures;17000;legendarycreatures;传说生物;Legendary Creatures;\n;17001;meowcraft;Meowcraft;;\n;17002;mr_big_globeyungsbetterjungletemplescompatibility;Big Globe - YUNG's Better Jungle Temples Compatibility;;\n;17003;mr_big_globeyungsbetterdeserttemplescompatibility;Big Globe - YUNG's Better Desert Temples Compatibility;;\n;17004;mr_big_globeyungsbetterstrongholdscompatibility;Big Globe - YUNG's Better Strongholds Compatibility;;\n;17005;mr_big_globeyungsbetterdungeonscompatibility;Big Globe - YUNG's Better Dungeons Compatibility;;\nfrozenlib;17006;frozenlib;FrozenLib;;\nclientblock;17007;clientblock;ClientBlock;;\neventjs;17008;eventjs;EventJS;;\nbackup-manager;17009;backupmanager;Backup Manager;;\nthe-root-of-fear;17010;rootoffear;The Root of Fear;;\nthe-modifiger;17011;the_modifiger;The Modifiger;;\nkeypreset;17012;keypreset;键位预设;KeyPreset;\n;17013;tmm;TweakerooFeaturesMenu;;\nsanity-prequel;17014;sanity;理智：前传;Sanity: Prequel;SP\nayame-paperdoll;17015;ayame_paperdoll;Ayame 纸娃娃;Ayame PaperDoll;\n;17016;registry-helper-lib;Registry Helper Lib;;\n;17017;carpet-permissions;Carpet Permissions;;\npassive-shield;17018;passiveshield;被动盾牌;Passive Shield;\ntrap-expansion;17019;trapexpansion;Trap Expansion;;\nroost-2-flying-higher;17020;roost2;Roost 2: Flying Higher;;\n;17021;amber;Amber;;\nliteminer;17022;liteminer;Liteminer;;\n;17023;fin_better_netherz;Level Z - Better Nether;;\n;17024;fin_better_end_z;Level Z - Better End;;\ncc-shops;17025;ccshops;CC Shops;;\nmodular-machinery-reborn;17026;modular_machinery_reborn;Modular Machinery Reborn;;MMR\nflare;17027;flare;Flare;;\nrs-crafting-monitor-in-grid;17028;rs_cmig;RS: Crafting Monitor in Grid;;\n;17029;photonics;Photonics;;\nlambdynamiclights-unofficial-neoforge;17030;;LambDynamicLights [Unofficial NeoForge];;\n;17031;healthcommand;More Command;;\n;17032;heraclesbutton;Heracles Button;;\nvoicecontrol;17033;voicecontrol;声控;Voicecontrol;VC\nhex-ars-linker;17034;hex_ars_link;咒法魔艺链接;Hex-Ars Linker;\nbingolobby;17035;bingolobby;宾果游戏大厅;BingoLobby;\nworldedit-legacy;17036;worldedit;WorldEdit Legacy / WorldEdit Enhanced;;\n;17037;nohealthbar;No Health Bar;;\nasm-fixes-j-a-f-m;17038;asmfixes;ASM Fixes;;J.A.F.M\nmoestweaks;17039;moestweaks;MoesTweaks;;\nprickle;17040;prickle;Prickle;;\nfarming-3x3;17041;farming-3v3;Farming 3x3;;\ntrickery;17042;trickery;Trickery;;\nkebab;17043;kebab;Kebab;;\nfarmers-croptopia;17044;farmers_croptopia;Farmer's Croptopia;;\nall-trees-drop-apples;17045;all_trees_drop_apples;All Trees Drop Apples;;\ninfinite-dimensions;17046;infinity;Infinite Dimensions;;\n;17047;mr_big_globeadastracompatibility;Big Globe - Ad Astra Compatibility;;\nmofus-broken-constellations;17048;mofus_better_end_;Mofu's Broken Constellations;;\nstarlance;17049;vsch;Starlance;;\nbeneath;17050;beneath;Beneath;;\n;17051;scb;简易客户端：基础;SimpleClientBase;SCB\n;17052;dynamic_watermark;动态水印;DynamicWatermark;\n;17053;autologin;自动登录;AutoLogin;AL\nultimate-enchantment;17054;ultimate_enchantment;终极附魔;Ultimate Enchantment;UE\nron-settings;17055;ronsettings;Ron Settings;;\nbackpack-display;17056;backpackdisplay;背包物品内容显示;Backpack Display;\nhorrrs-pvz;17057;horrrs_pvz;Horrrs的植物大战僵尸;Horrrs Pvz;HPVZ\n;17058;optifabric;OptiBabric;;\nitem-restrictions;17059;itemrestrictions;Item Restrictions;;\nvintage-animations;17060;vintage_animations;Vintage Animations;;\nthe-one-who-watches;17061;the_one_who_watches;The One Who Watches;;TOWW\nscp-ultimate;17062;scp_ultimate;SCP: Ultimate;;\nherobrines-omen;17063;herobrinesomen;Herobrines Omen;;\nsws-wukong;17064;sw_wukong;SW's wukong: better combat & epic fight;;\ndragns-valiant-vehicles;17065;dragnvehicles;DragN's Valiant Vehicles!;;\nbio-factory;17066;biofactory;机械动力：血肉工厂;Create: Bio-Factory;\ncreate-ad-astra-compatibility;17067;create_ad_astra_compat;Create: Ad Astra Compatibility;;\ncreate-mechanical-chicken;17068;create_mechanical_chicken;Create Mechanical Chicken;;\nzbgt;17069;zbgt;Zorbatron's GT: CEu Extras;;ZBGT\ncasinocraft;17070;casinocraft;CasinoCraft;;\n;17071;msl;Max's QOL Library;;MSL\ntoadlib;17072;toadlib;ToadLib;;\n;17073;;喜报-1.8.9;xibao-1.8.9;\n;17074;mantra;口头禅;Mantra;\ncrosshair-tweaks;17075;crosshairtweaks;十字准星调整;Crosshair Tweaks;\nwhat-did-i-just-kill;17076;whatdidijustkill;What Did I Just Kill?;;\nscalablelux;17077;scalablelux;ScalableLux;;\nweather-changer;17078;weatherchanger;Weather Changer;;\niron-nugget-recycling;17079;mr_iron_nuggetrecycling;Iron Nugget Recycling;;\n;17080;picohud;PicoHUD;;\nre-exposer;17081;exposer;Exposer 重置版;Re-Exposer;\ngtmoreoreprocessing;17082;mop;GTMoreOreProcessing;;MOP\ncomputercraftedu;17083;ComputerCraftEdu;ComputerCraftEdu;;CCE\nbibliocraft-legacy;17084;bibliocraft;Bibliocraft Legacy;;\nsimple-clouds;17085;simpleclouds;Simple Clouds;;\n;17086;kakan;KAKAN;;\n;17087;bigtrees;The BigTrees Mod;;\nfurniture-modcraft;17088;furniturecraft;Several Furniture Craft;;FC\n;17089;bmcat;MCAT-免费拓展包 v1;MCAT Free Mod v1;BMCAT\nspellblades-and-such-hexblade;17090;hexblade;Spellblades and Such: Hexblade;;\n;17091;spellbooks_attribute;铁魔法属性;Spellbooks Attribute;\nsons-of-sins-organs-additions;17092;sosorgans;七罪之子：器官添加;Sons Of Sins: Organs Additions;\nsons-of-sins-organs-museum;17093;sos_organ_museum;七罪之子：器官博物馆;Sons of Sins: Organ Museum;\nmobs-of-sins-alexs-mobs-integration-forge;17094;mobs_of_sins;七罪之子: Alex的生物集成;Mobs of Sins: Alex's Mobs  Integration  [Forge];\nstitched-sins;17095;stitched_sins;缝合的罪;Stitched Sins;\n;17096;bio_delight;血肉乐事;Biomantic Delight;\n;17097;caged_mobs_integration;笼中生物联动;Caged Mobs Integration;\nthe-sculpture;17098;the_sculpture;SCP-173，The Sculpture;;\ngoop;17099;goop;Goop;;\nneepmeat;17100;neepmeat;NEEPMeat;;\ntacz-emx-arms-gunpack;17101;emxarms;EMX-逆境重科电磁武器包;;\ntacz-emx-atlas-gun-pack;17102;emxmors;EMX-ATLAS电磁武器包;;\n;17103;hybridcreatures;我的世界杂交版;Hybrid Creatures;HC\n;17104;more_monster;更多怪物;HuangSheng1's More Monster;MM\ncreeping-woods;17105;creepingwoods;Creeping Woods;;\n;17106;zombie_apocalypse_core;Zombie Apocalypse Core;;\ndragon-mounts-2;17107;dragonmounts;龙骑士2：延续;DragonMounts2: Expanded;DM2:E\nlife-fruits;17108;lifefruit;生命果;Life Fruits;\nati-structures;17109;ati_structures_fabric,ati_structures;ATi Structures;;\nfantasy-armor;17110;fantasy_armor;幻想铠甲;Fantasy Armor;\nbeautiful-enchanted-books;17111;beautiful-enchanted-books;Beautiful Enchanted Books;;\n;17112;OmniOcularUnofficial;Omni Ocular Unofficial;;OOU\nmorphe-lins-ron-rts-tools;17113;rw_fix;墨灵的RTS优化;;\nvoid-fog;17114;voidfog;Void Fog;;\n;17115;legacy_rocket_model;旧版火箭模型;Legacy Rocket Model;LRM\n;17116;autocommand;自动指令;Auto Command;\n;17117;emergencystop;急停;Emergency Stop;\n;17118;simple_tetra_js;我要造词;Simple Tetra Js;\ntinker-i-o-ce;17119;tinker_io;工匠接口社区版;Tinker I/O CE;\nmore-shield-variants;17120;lolmsv;More Shield Variants;;\n;17121;mr_no_attacktimedelay;No attack time delay;;\nbendy-items;17122;bendy_and_the_items;Bendy Items;;\npaladins-oath;17123;paladins_oath;Paladin's Oath;;PO\n;17124;squirrelmod;小松鼠;Small Squirrel;\nroundabout-the-jojo-mod;17125;roundabout;Roundabout: The JoJo Mod;;\n;17126;rotp_zwa;RotP：白色相簿附属;White Album (Ripples of the Past addon);\n;17127;rotp_waytoheaven;RotP：天堂之路附属;The Way to Heaven (Ripples of the Past addon);\nenviromine-continuation;17128;enviromine;更多生存要素：延续;EnviroMine continuation;\n;17129;toffys_hooks;Toffy's Hooks;;\nmutants-buff;17130;mutantsbuff;Mutants Plus;;\n;17131;scc;简易客户端：指令;SimpleClientCommands;SCC\n;17132;wai;玩家方块模型;BlockPlayer;BP\nterrablenderfix;17133;terrablenderfix;TerraBlenderFix;;\ncraterlib;17134;craterlib;CraterLib;;\n;17135;caramelchat;caramelChat;;\nspark-unforged;17136;;Spark Unforged;;\npack-mode-reborn;17137;packmode;PackMode Reborn;;PMR\nall-the-wood-weve-got;17138;woodwevegot;All The Wood We've Got;;WWG\nbibliowoods-legacy;17139;bibliowoods;Bibliowoods Legacy;;\nhydrological;17140;hydrol;Hydrological;;\nunilib;17141;unilib,mod_UniLib;UniLib;;\n;17142;madv;更多进度;More Advancements;MAdv\nmodchu_playerformlittlemaidfml;17143;pflmf;PlayerFormLittleMaid FML;;PFLMF\nquiet;17144;quiet;迁噪碍音/玩家禁言;Quiet;\ngalacticraft-compatibility;17145;galacticraftcompatibility;星系兼容;Galacticraft Compatibility;GCC\n;17146;better-tab;Better Tab;;\nmodular-machinery-reborn-mekanism;17147;modular_machinery_reborn_mekanism;Modular Machinery Reborn Mekanism;;\n;17148;;Smooth Swapping 1.21 Port;;\ndawn-of-time-roa-edition;17149;dawnoftimebuilder;Dawn of Time: RoA Edition;;\nbuildpaste;17150;buildpaste;BuildPaste;;\nmutils;17151;mutils;ModpackUtils;;\nbetter-campfires;17152;bettercampfires;更好的营火;Better Campfires;\n;17153;renfe;MTR Renfe Addon;;\ngt-community-additions;17154;gtca;GT Community Additions;;GTCA\nmore-create-burners;17155;moreburners;机械动力：更多燃烧室;More Create Burners;\ncreate-logistics;17156;create_logistics;Create Logistics;;\ncreate-pneumatic-equipment;17157;create_pneuequip;Create: Pneumatic Equipment;;\ncollisiondamage;17158;collisiondamage;碰撞伤害;CollisionDamage;CD\n;17159;absolutely-proprietary;Absolutely Proprietary;;\ntensura-reincarnated;17160;tensura;关于我转生变成史莱姆这档事：转生;Tensura: Reincarnated;T:R\ntransformer;17161;transformer;奥特化身：无限;Becomes Ultraman:Infinity;BUI\nhenshin;17162;henshin;变身;Henshin;HS\nridiculous-world;17163;RidiculousWorld;Ridiculous World;;\n;17164;enhancedbiomes;Enhanced Biomes;;\nquartz_craft;17165;quartzcraft;石英工艺;Quartz_Craft;QC\ncursed-stare;17166;cursed_stare;Cursed Stare;;\nlegendblade;17167;legendblade;传世之刃;LegendBlade;LB\n;17168;hypothermic;Hypothermic Reforged;;\nscape-and-run-parasites-extra;17169;srpextra;逃逸：寄生体扩展;Scape and Run: Parasites Extra;\nlauchs-shutters;17170;shutter;The New Shutters / Shutters;;\ngolden-foods;17171;golden_foods;Golden Foods!;;\n;17172;alexcaves_delight;Alex's Caves Delight;;\n;17173;spmreborn;烤地瓜：重生;Sweet Potato Reborn;SPR\ndragon-fight-config-endergetic-addon;17174;dragonfightconfigendergetic;Dragon Fight Config - Endergetic Addon;;\nhour-clock-24;17175;hourclock24;24小时制;24Hourclock;24HC\n;17176;mr_stone_disappearance,stone_disappearance;石头消失;Stone Disappearance;SD\n;17177;;DatapackTools;;\ncelestial-forge;17178;celestial_forge;星月再锻;Celestial Forge;\nmahjongcraft;17179;mahjongcraft;麻将工艺;MahjongCraft;\nformidulus;17180;formidulus;Formidulus;;\names-stranger-things;17181;stranger_things;AME's Stranger Things;;\nherobrine-origins;17182;herobrineorigins;Herobrine Origins;;\nmilkable-blazes;17183;milkable_blazes;Milkable Blazes;;\n;17184;stone_legacy;RotP：暗之遗物;Stone Legacy (Ripples of the Past addon);\nfurniture-beetle;17185;beetle_f;Furniture Beetle;;\nmekanism-x-create-northstar;17186;mek_x_star;通用机械：北极星;Mekanism x Create: Northstar;\n;17187;mr_createz;CreateZ;;\ncreate-smart-crafter;17188;smartcrafter;Create: Smart Crafter;;\ncreate-fuel-and-water-information;17189;cfwinfo;Create: Fuel & Water Information;;\nthematic;17190;thematic;🖌️ Thematic;;\nmore-composter-variants;17191;lolmcmv;More Composter Variants;;\nmore-fletching-tables;17192;lolmft;More Fletching Tables;;\nmore-grindstone-variants;17193;lolmgv;More Grindstone Variants;;\nmore-bed-variants;17194;quad-lolmbdv;More Bed Variants;;\nmore-loom-variants;17195;lolmlmv;More Loom Variants;;\nmore-feeding-trough-variants;17196;lolmcmv-aft;More Feeding Trough Variants;;\nmore-smoker-variants;17197;quad-lolmsmv;More Smoker Variants;;\nmanascore;17198;manascore;ManasCore;;\names-mobs-configuration;17199;amemobsettings;AME生物配置;Ame's Mobs' Configuration;Amc\ndisplay-delight;17200;displaydelight;Display Delight;;\nrealistic-torches-roa-edition;17201;realistictorches;Realistic Torches: RoA Edition;;\nexplorers-compass-edited;17202;explorerscompass;探险者指南针 修改;Explorer's Compass Edited;\n;17203;mr_auto_luckyblock,auto_lucky_block;自动幸运方块;auto lucky block;ALB\n;17204;slime;Slime;;\nx-backup;17205;x-backup;X Backup;;XB\niron-barrels-forge;17206;ironbarrels;Iron Barrels;;\nspectral-decorations;17207;spectral-decorations;Spectral Decorations;;\n;17208;long-dark;LongDark 黑夜求生;;LD\n;17209;yuanlinAutoDefense;园霖的自动防御模组[监控器版本];;YAD\ntrims-on-tools;17210;trims_on_tools;Trims on Tools;;\ngalacticraft-automatic-resources;17211;galacticraftresources;星系自动资源;Galacticraft Automatic Resources;\n;17212;tetrasimplyaddon;Tetra简易刀剑附属;TetraSimplyAddon;\n;17213;lx_lightassisttool;轻辅助工具;light assist tool;\n;17214;gamaos_tactical_equipment;噶猫的战术装备;Gamao's Tactical Equipment;GTE\n;17215;lucky_block_island;幸运方块空岛;Lucky Block Island;LBI\nroberts-building-pack;17216;roberts_building_pack;Robert的建筑扩展包;Robert's Building Pack;RBP\narcanus;17217;arcanuscontinuum;Arcanus;;\nultimate-chess;17218;chessmod;Ultimate Chess;;\nsparkweave;17219;sparkweave;Sparkweave Engine;;\nvisualjs;17220;visualjs;VisualJS;;\nlights-out;17221;lights_out;Lights Out;;\na-night-stalker;17222;man;A Night Stalker;;\nthe-faceless-duo;17223;the_faceless_duo;The Faceless Duo;;\nscape-and-run-parasites-meteorite;17224;srpmeteor;逃逸：祸从天降;Scape and Run: Parasites Meteorite;\ncravencrafts-bloody-bits;17225;bloodybits;CravenCraft's Bloody Bits;;\n;17226;scm;简易客户端：模块;SimpleClientModules;SCM\n;17227;ccs;撞车;CarCrash;CCS\n;17228;yuanlinautobot;园霖的自动机器人;Yuanlin's AutoBot;YAB\nfastlang;17229;fastlangmod;FastLang;;\nmin-3-hall-a;17230;min3halla;MIN-3 Hall-A;;MH\nmodular-machinery-reborn-energistics;17231;modular_machinery_reborn_energistics;Modular Machinery Reborn Energistics;;\ntweaks-for-galactic-science;17232;galacticScience_Tweaks;Tweaks for Galactic Science;;\njava-version-checker;17233;JavaVersionChecker;Java Checker;;\nchatcopyrite;17234;chatcopyrite;Chatcopyrite;;\nchat-boost;17235;chatboost;聊天增强;Chat Boost;\nterracart;17236;terracart;Terracart;;\nnoteblock-backport;17237;noteblock_backport;Noteblock Backport;;\nscape-and-run-parasite-dimension;17238;eextra;逃逸：寄染之世;Scape and Run: Dimension;\nhexdebug;17239;hexdebug;咒术调试;HexDebug;\npaths-of-sin;17240;paths_of_sin;Paths Of Sin;;\n;17241;mapperplugin;MapperPlugin;;MP\n;17242;gtb;附魔分解;Enchantment Dismantling;GTB\nhide-key-binding;17243;hide_key_binding;隐藏按键绑定;Hide Key Binding;HKB\n;17244;dfl,mr_datapackets_functionslib;数据包函数支持库;datapack function library;DFL\ninline;17245;inline;Inline;;\nfluffy-fur;17246;fluffy_fur;Fluffy Fur;;\n;17248;hugescreenshot;巨型截图;HugeScreenshotMod;HSM\nhead-cutter;17249;headcutter;玩家剪头;Head Cutter;\nsalty-s-realistic-forging;17250;realisticforging;Salty's Realistic Forging;;\n;17251;vs-wakes-compat;Wakes Compat for Valkyrien Skies;;\n;17252;vscrumbles;VS Crumbles;;\n;17253;ship-in-a-bottle;Ship In A Bottle;;\nrustic-pancakes;17254;rusticpancakes;Rustic Pancakes;;\nice-and-fire-delight;17255;ice_and_fire_delight;冰火传说乐事;Ice and Fire Delight;\nadditional-dragons;17256;additionaldragons;Additional Dragons;;\nenigmatic-unity;17257;enigmaticunity;Enigmatic Unity;;\nephemera;17258;ephemera;Ephemera;;\ntheurgy-kubejs;17259;theurgy_kubejs;Theurgy KubeJS;;\nimmersive-lanterns;17260;immersivelanterns;Toni's Immersive Lanterns;;\ntxnilib;17261;txnilib;TxniLib;;\ndefault-key-setup;17262;default_key_setup;默认按键配置;Default Key Setup;DKS\nthe-man-king;17263;man;The Man King;;\ncreate-arms-race;17264;createarmsrace;Create: Arms Race;;\npowered-flashlight;17265;powered_flashlight;Powered Flashlight;;\nvmc-mc;17266;vmc-mc;Virtual Motion Capture for Minecraft;;VMC-MC\n;17267;hexcellular;Hexcellular;;\nhex-conjuring;17268;hexconjuring;咒术构筑;Hex Conjuring;\nhexjs;17269;hex_js;HexJS;;\ncraftsense;17270;craftsense;CraftSense;;\n;17271;chimera27metroid;Metroid Cubed;;\nzenkorium-creatures;17272;leshen;Zenkorium Creatures;;\nzhengelss-techguns-addon;17273;zhengels_techguns_addon;Zhengels's Techguns Addon;;\nmore-artifacts;17274;more_artifacts;More Artifacts;;\n;17275;mr_mtimer;Timer;;\ntfcgenviewer;17276;tfcgenviewer;TFCGenViewer;;\nterra-math;17277;terramath;TerraMath;;\nlukis-crazy-chambers;17278;mr_lukis_crazychambers;Luki's Crazy Chambers;;\ntumbleweed-wasteland;17279;tumbleweed;Tumbleweed Wasteland;;\nirons-jewelry;17280;irons_jewelry;Iron的宝石与饰品;Iron's Gems 'n Jewelry;IGJ\natlas-api;17281;atlas_api;Atlas API;;AA\nloading-screen-messages;17282;loading_screen_messages;Loading Screen Messages;;\n;17283;pss;PipeSounds;;PSS\nnemos-invertory-sorting;17284;nemos_inventory_sorting;Nemo's Inventory Sorting;;\n;17285;betterux;BetterUX;;\n;17286;hudmini;HudMini;;\nribs-panning;17287;ribspanning;Ribs Panning;;\nhotkettles;17288;hotkettles;Hot Kettles;;\ngift-drop;17289;gift_drop;Gift Drop;;\ndragns-livestock-overhaul;17290;dragnlivestock;DragN's Livestock Overhaul!;;LO\nstorage-drawers-unofficial;17291;;储物抽屉非官方版;Storage Drawers Unofficial;\n;17292;;探索者背包2;AdventureBackpack2;\ncentrifuge-tiers-reproduced;17293;centrifugetiersreproduced;Centrifuge Tiers: Reproduced;;\ndense-mekanism;17294;denseores;Dense Mekanism;;\nsimple-missles;17295;simplemissiles;Simple Missiles;;\ngalosphere-spellbooks;17296;galosphere_spellbooks;Galosphere: Spellbooks;;\ndiscerning-the-eldritch;17297;discerning_the_eldritch;Discerning The Eldritch;;DTE\n;17298;hexcassettes;Hexcassettes;;\nwardens-plus;17299;wardens_plus;Wardens+;;\nender-wyrmlings;17300;ender_wyrmlings;Ender Wyrmlings;;\n;17301;draw_arrows;拔箭;Draw Arrows;\n;17302;blackflood-hardermonster;黑潮：更困难的怪物;Black Flood: Harder Monster;BFHM\nnew-classic-battle-towers;17303;new_classic_battle_towers;New Classic Battle Towers;;\nchitin-equipment;17304;chitin;甲壳装备;Chitin Equipment;\nmowzies-cataclysm;17305;mowzies_cataclysm;Mowzie的灾变;Mowzie's Cataclysm;\nstardew-fishing-fabric;17306;stardew_fishing;Stardew Fishing Fabric;;\npiglin-slayer;17307;overworldpiglins;Piglin Slayer;;\ncataclysm-tools;17308;cataclysm_tools;Cataclysm Tools;;\nwitherite-gear;17309;witherite_armor;Cataclysm Witherite Armor;;\nfaunus;17310;faunus;Faunus;;\noo-ee-a-e-a;17311;oo_ee_a_e_a;OO EE A E A;;\n;17312;translator;翻译器;Translator;\n;17313;opencmd;游戏指令窗;Open Command;OCMD\n;17314;mr_daycounter;Day Counter (Original);;\nsodium-embeddium-options-mod-compat;17315;sodiumoptionsmodcompat;Sodium/Embeddium Options Mod Compat;;\nsecureseed-reborn;17316;secure-seed-reborn;SecureSeed-Reborn;;\ncreative-eats;17317;eating;Creative Eats;;\nmazerooms;17318;mazerooms;MazeRooms;;\nfriendly-chests;17319;friendly_chests;Friendly Chests;;\n;17320;chitinous_ties;Chitinous Ties;;\nbrewable-dragons-breath;17321;brewable_dragons_breath;龙息可酿造;Brewable Dragon's Breath;\nflerovium;17322;flerovium;鈇;Flerovium;\n;17323;ender_random_creation;末影工艺;EnderRandomCreation;ERC\n;17324;cherrytinker;樱桃工匠;Cherry Tinker;CT\ngathering-torches-become-sunlight;17325;torchesbecomesunlight;薪火，造炬成阳;Gathering Torches Become Sunlight;\n;17326;spec;切旁观数据包;spec;\n;17327;bml;更多聊天记录-NeoForge;MoreChatHistoryNeoForge;\nno-arrows-in-you-respawn;17328;noarrowsinyou;No Arrows In You: Respawn;;NAYR\n;17329;naturescompassedited;生物群系指南针 修改;NaturesCompassEdited;\nglowcase;17330;glowcase;Glowcase;;\nhitbox-api;17331;hitboxapi;Hitbox Api;;\n;17332;polynametag;PolyNametag;;\nbroken-nametags;17333;broken_nametags;Broken Nametags;;\n;17334;usefullflesh;Compostable Flesh;;\na-cute-little-crock-pot;17335;crockpot;A Cute Little Crock Pot;;\n;17336;bountifulharvest;感恩节乐事;Bountiful Harvest;\nwasteland-grocers;17337;wastelandgrocers;Wasteland Grocers;;\narcane-tablet;17338;projex;Arcane Tablet;;\n;17339;unflavoured_pipes;Unflavoured Pipes;;\ncurvy-pipes;17340;curvy_pipes;Curvy Pipes;;\n;17341;SC0_SpaceCore;SpaceCore;;\n;17342;transport_to_your_death;死亡点传送;Transport To Your Death;TTYD\nclassic-bar-legacy;17343;classicbar;经典状态条 延续版;Classic Bar Legacy;\nglowing-eyeblossom;17344;glowing-eyeblossom;Glowing Eyeblossom;;\nopenlink;17345;openlink;开放式联机;OpenLink;OL\n;17346;vsgrapples;VS Grapples;;\n;17347;mydriasis;Mydriasis;;\nno-mob-spawning-on-plants;17348;nmsop;No Mob Spawning On Plants;;\ndurability-rework;17350;unbreakable;Unbreakable (Durability Rework);;\nmore-villagers-re-employed;17351;morevillagers;More Villagers : Re-employed;;\nsimilsax-transtructors;17352;similsaxtranstructors;Similsax Transtructors - Builder's Wands;;\ntcw-bedrock-breaker;17353;bedrockbreaker;TCW Bedrock Breaker;;\napothic-reforge-tokens;17354;apotheosis_reforge_token;Apothic Reforge Tokens;;\nadvanced-skills-remastered;17355;advskills_re;进阶技能：重制版;Advanced Skills: Remastered;ASRe\nmonsters-and-dungeons;17356;monstersanddungeons;Monsters and Dungeons;;\ncultivate-the-eternal-realm;17357;waifu_of_god;Cultivate the Eternal Realm;;\ntoo-much-bosses;17358;too_much_bosses;Too Much Bosses;;\nvaloria;17359;valoria;Valoria;;\nglowroot;17360;glowroot;Glowroot Caves;;\n;17361;instaminabledeepslate;Instaminable Deepslate;;\n;17362;mr_daycounter_legacy;Day Counter (Legacy);;\ngreedy-bag;17363;greedybag;Greedy Bag (ItemStages Bag);;\nnaughthirium;17364;naughthirium;Naughthirium;;\nchunkpurge;17365;ChunkPurge;区块清除;ChunkPurge;\nkubejs-tfmg;17366;tfmgjs;KubeJS TFMG;;\ngauges-and-switches-ported;17367;rsgauges;Gauges and Switches Ported;;\nmifa;17368;mifa;More Industrial Foregoing Addons;;MIFA\ntis-vs;17369;tisvs;TIS-VS;;\npistronics-2;17370;Pistronics2;Pistronics 2;;\n;17371;awakened_skeleton;骷髅觉醒了;AwakenedSkeleton;\n;17372;primeval;原初;Primeval;PL\ngladius-combat-evolved;17373;combat;Gladius - Combat Evolved;;\nhungry-chests;17374;hungrychests;Hungry Chests;;\nbetter-withered-mobs;17375;better_withered_mobs;Better Withered Mobs;;\npkgbadges;17376;cobblebadges;PKGBadges / CobbleBadges;;\n;17377;carbasa;Carbasa;;\ncountereds-settlement-roads;17378;countereds_settlement_roads;Countered's Settlement Roads;;\ncountereds-terrain-slabs;17379;terrainslabs;Countered's Terrain Slabs;;\nexplosive-enhancement-reforged;17381;explosiveenhancement;Explosive Enhancement: Reforged;;\ntmtravlrs-arrow-trails;17382;arrowtrails;Tmtravlr的箭矢轨迹;Tmtravlr's Arrow Trails;\nbiome-replacer;17383;biome_replacer;群系替换器;Biome Replacer;BR\ncustom-suspicious-stew;17384;customsuspiciousstew;自定义谜之炖菜;Custom Suspicious Stew;\n;17385;cfms;子文件夹Mod扫描器;Child Folder Mod Scanner;CFMS\n;17386;plant_spread;植物蔓延;Plant Spread;\n;17387;createstressdebug;Create Stress Debug;;\ncreate-kart;17388;create_kart;Create Kart;;\n;17389;barometry;Barometry;;\nmenufpsunlocker;17391;mfpu;MenuFPSUnlocker;;\nbogged-spawn;17392;boggedspawn;Bogged Spawn;;\nunicode-fix;17393;unicodefix;Unicode Fix;;\ninsanity;17394;insanity;InSanity;;\nsepals;17395;sepals;Sepals;;\nfrustum-culling-advanced;17396;frustumculling;Frustum Culling (More fps);;\n;17397;seat_ball;原版座位;Seat Ball;\n;17398;zombie_horse_spawning;僵尸马生成;Zombie-horse-spawning;\nstar-meow-craft;17399;smc;星喵工艺;Star Meow Craft;SMC\n;17400;defile;Defile;;\nvanishmod;17401;vmod;Vanishmod;;\n;17402;ei;Eat It;;\nf1-y-f5-mod;17403;f1_f3_f5;Perspective Lock;;\nmeddlebootstrap;17404;meddlebootstrap,exitpatch;MeddleBootstrap;;\nalltheleaks;17405;alltheleaks;AllTheLeaks (Memory Leak Fix);;\nwalk-overhaul;17406;walkoverhaul;Walk Overhaul;;\nno-strip;17407;nostrip;No Strip;;\nmcplus-remastered;17408;mc+region,mc+base;MCPlus：重置版;MCPlus Remastered;\ninstant-classic-houses;17409;instant_classic_houses;Instant Classic Houses;;\nhexparse;17410;hexparse;HexParse;;\nbetterraid;17411;better_raid;更好的劫掠;BetterRaid;\nvillagermetafix;17412;VillagerMetaFix;VillagerMetaFix;;\ncreate-better-villager;17413;create_better_villagers;Create: Better Villager;;\nfilesjs;17414;filesjs;FilesJS;;\nrobotic-parts-first-aid-compat;17415;;Robotic Parts & First Aid Compat;;\ncraftingpad-fabric;17416;craftingpad;CraftingPad;;\n;17417;ore_deposites,mr_ore_deposits;Ore Deposits;;\nexlines-sushi-mod;17418;sushimod;寿司;Sushi Mod;\ndemanding-saplings;17419;demandingsaplings;Demanding Saplings;;\ncoastal-waves;17420;waves;Coastal Waves;;\n;17421;nonadar_pyronix;No Swimming;;\n;17422;Blocks3D;Blocks3D Mod;;\nstone-zone;17423;stonezone;泛用兼容：石材;Every Compat (Stone Zone);\na-man-with-plushies;17425;a_man_with_plushies;A Man With Plushies;;\nenchantmentransfer;17426;gtb;附魔转移;Enchantment Transfer;ET\n;17427;don_it;戴它;Don it;donit\n;17428;friendship_bracelet;友谊徽章;FriendshipBracelet;\n;17429;setblock_tnt,mr_setblock_tnt;TNT生成;SetBlock-TNT;SBT\n;17430;async;Async;;\n;17431;;无名的纸板箱交通拓展;Aphrodite's Nemo's Transit Expansion;ANTE\ntouchcontroller;17432;touchcontroller;触摸控制器;TouchController;TC\n;17433;llpswhyrz;llpsw的火影忍者;llpsw Naruto Mod;lnm\nlet-me-click-and-send;17434;letmeclickandsend;Let Me Click And Send;;\n;17435;maker_namespace;玩家头颅生成器;Player Skull Maker;\ncareful-cast-corrector-ccc;17436;CarefulCastCorrector;Careful Cast Corrector;;CCC\nlagbgon-revived;17437;lagbgonrevived;Lag'B'Gon Revived;;\nproject-steam;17438;projectsteam;Age of Steam Core;;\nextended-crafting-terminals-for-applied;17439;ae2exttable;Extended Crafting Terminals for Applied Energistics 2;;\nsweety-archeo;17440;sweety_archaeology;Sweety's Archaeology;;\n;17441;musket;火帽枪;Musket;\nchamber-clarity-tac-z;17442;chamber_clarity;Chamber Clarity;;\ntechguns-ce;17443;techguns;科技枪-社区版;Techguns-CE;\n;17444;showspawntime;Show Spawn Time;;SST\n;17445;jdinpack;JDINPack;;\nselfexpression-slim;17446;selfexpression_slim;Selfexpression: Slim;;\nnothing-dimension;17447;nothing;Nothing Dimension;;\n;17448;glassitemframes;Glass Item Frames;;\nportable-stations;17449;portable-stations;Portable Stations;;\nnew-world-mod;17450;newworld;New World;;NW\n;17451;ornithe-loader;Ornithe;;\n;17452;123Technology;123科技;123Technology;123t\n;17453;;更好的任务 GTNH 非官方版;BetterQuesting - GTNH;BQ\n;17454;backhand;Backhand Unofficial;;\nbook-scroll;17455;book_scroll;Book Scroll;;\n;17456;journalmod;日志模组;Journal Mod;\n;17457;at;自动工具;AutoTool;AT\n;17458;fv;FakeVanilla;;FV\n;17460;sakura_sign_in;樱花签;Sakura Sign-In;SS\n;17461;exp_merge;经验合并;;\n;17462;easier_mc,mr_easier_mc;更简单的MC;Easier MC;EMC\nqualia-carpet-addition;17463;qca;Qualia Carpet Addition;;QCA\nmouse-tweaks-unofficial;17464;;鼠标手势非官方版;Mouse Tweaks Unofficial;\nbaubles-lts;17465;;Baubles-LTS;;\nelysium-api;17466;elysium_api;Elysium API;;\nnoisethreader;17467;noisethreader;NoiseThreader;;\n;17468;no-bats;No Bats;;\nantibat;17469;antibat;Antibat;;\nillage-and-spell-age;17470;iss_spellbook;Illage and Spell-age: Iron's Spells Addon;;\norigincore;17471;origincore;起源核心;Origincore;OC\n;17472;ImmerGears;沉浸装备0;ImmerGears0;IG0\npet-connect;17473;petconnect;收容宠物;Pet Connect;\nheaven-destiny-moment;17474;heaven_destiny_moment;天命时刻;Heaven Destiny Moment;\nindustrial-foregoing-extra-upgrades;17475;ifeu;工业先锋:更多升级;Industrial Foregoing: Extra Upgrades;IFEU\ngrowableores-extension;17476;growableores_extension;甘蔗转换机;GrowableOres Extension;\noneiricconcept;17477;oneiricconcept;梦华构想;OneiricConcept;OC\nsimelectricity;17478;simelectricity;SimElectricity;;\nsomnia-refreshed;17479;somnia;Somnia Refreshed;;\nterramoment;17480;terra_moment;泰拉时刻;TerraMoment;\nminers-diary-beyond-village;17481;md_bv;Miner's Diary - Beyond Village;;\nbiggerchathistory;17482;biggerchathistory;Bigger Chat History;;BCH\nunifier;17483;Unifier;Unifier;;\ncompass-ribbon;17484;cr-compass-ribbon;Compass Ribbon;;\nraiyons-tree-capitator-addon;17485;raiyon;Raiyon's Tree Chop-Capitator;;\n;17486;elytrahud;Elytra HUD;;\ncreeper-drops-tnt;17487;creeperdropstnt;Chokbok's creeper drops TNT !?!!;;\nvisual-novel-framework;17488;mobtalkerredux;Visual Novel Framework / Mob Talker Redux;;\n;17489;experienceprogress;Experience Progress;;\nyapping-tooltips;17490;yapping_tooltips;Yapping Tooltips;;\nbird-nests;17491;birdnests;Bird Nests;;\ntfc-purified-water;17492;purified_water;TFC Purified Water;;\n;17493;exparticle;粒子扩展;ExParticle;EP\n;17494;entityrenderdisabler;Entity Render Disabler;;\nquick-right-click;17495;quick-right-click;Quick Right-Click;;\nalchimiae-magicae;17496;alchimiae;炼金魔法师;Alchimicae Magicae;\nthaumic-forever;17497;thaumicforever;Thaumic Forever;;\ngregfluxology;17498;gregfluxology;GTCEu/GTM能源拓展;Gregfluxology;\ncreate-crowns;17499;crowns;Create: CROWNS;;\n;17500;create_modular_tools;Create: Modular Tools;;\nae2-wireless-terminal-lts;17501;;AE2 Wireless Terminal-LTS;;\n;17502;cme_suck_my_duck;并发修改什么的不要鸭;CME is Bad;CMESucks\nenchantment-harmony;17503;enchantmentharmony;和谐相处的附魔书;EnchantmentHarmony;EH\nquick-refine;17504;quick_refine;快捷锻刀;Quick Refine;QR\nrubinated-nether;17505;rubinated_nether;耀红下界;Rubinated Nether;\ncornucopia-bypizza573;17506;cornucopia;丰饶之角;Cornucopia;\nepic-fight-bow-tweak;17507;efbowtweak;Epic Fight Bow Tweak;;\nslash-blade-addon;17508;tunekeshi;Slash Blade Addon;;\njjkutilities;17509;jujutsucraftutilities;JujutsuCraft Utilities;;JJCU\ncrystal-chronicles;17510;crystal_chronicles;Crystal Chronicles;;\n;17511;dynmapblockscan;DynmapBlockScan;;\nwater-particles-render-fix;17512;particlesrenderfix;Water/Particles Render Fix;;\nray-trace-library;17513;mrdimkaraytracelib;Ray Trace Library;;\ndelayed-thunder;17514;betterlightning;Delayed Thunder;;\nbook-and-quick-save;17515;book_and_quick_save;Book and Quick Save;;\nkeybaordjs;17516;keyboardjs;Keyboard JS;;KBJS\nbetter-eating-mod;17517;bettereating;Better Eating Mod;;\n;17519;heart-effects;Heart Effects;;\n;17520;globalwind;Global Wind;;\nbotaniavisualizer;17521;BotaniaVisualizer,botaniavisualizer;BotaniaVisualizer;;\n;17522;manavisualizer;Mana Visualizer;;\nmanavisualizer;17523;manavisualizer;ManaVisualizer;;\neasierworldcreator;17524;easierworldcreator;Easier World Creator;;EWC\nkubejs-diesel-generators;17525;dg_js;KubeJS Diesel Generators;;\ncreate-slugma;17526;createslugma;Create Slugma;;\ncobblemonrider;17527;cobblemonrider;CobblemonRider;;\ncobbledex;17528;cobbledex;Cobblemon Pokedex (Cobbledex);;\n;17529;legends_untold;Cobblemon: Legends Untold;;\nitem-descriptions;17530;item_descriptions;Item Descriptions;;\nitem-production-lib;17531;itemproductionlib;Item Production Lib;;\n;17532;goawaydfu;GoAwayDFU!;;\nconnectedtexturesmod-backport;17533;;ConnectedTexturesMod Backport;;\n;17534;litematica_printer;投影打印机;Litematica-Printer-Easyplace-Extension;\nelegant-architecture;17535;elegant_architecture;雅致画栋;Elegant Architecture;EA\nextended-workbench-reborn;17536;extended_workbench;扩展工作台重置版;Extended Workbench Reborn;\n;17537;hydrogenation_tinker;氢化匠魂;HydrogenationTinker;HYTIC\n;17538;deathshot;Stan's Deathshot;;\npathunderfencegates;17539;pathunderfencegates;Path Under Fence Gates;;\n;17540;mr_ketkets_furnicraft;FurniCraft;;\nlatiao-craft-2;17541;ltc2;辣条工艺 2;Latiao Craft 2;LTC2\ncanned;17542;canned;Canned;;\nmushroooms;17543;mushrooomsmod;Mushroooms;;\ntacz-compatibility;17544;tacaid;TACZ Compatibility;;\n;17545;naturaldisasters;Baablu's Natural Disasters;;\n;17546;tacztweaks;TaCZ Tweaks;;\nun-saddle;17547;unsaddle;Un-saddle;;\n;17548;item-landing-sound;Item Landing Sound;;\nrehooked;17549;rehooked;ReHooked;;\n;17550;mr_catenary;Catenary;;\n;17551;legendarywheatmod;传奇小麦;Legendary Wheat;\n;17552;anvil_falling;铁砧掉落;Anvil Falling;AF\nbetter-night-vision;17553;betternightvision;更好的夜视;Better Night Vision;\n;17554;aoawikihelpermod;AoA WikiHelper Mod;;\nmanors-bounty;17555;manors_bounty;庄园馀话;Manor's Bounty;ZYYH\n;17556;translation_mod;AI翻译器;Kimi Message Translator;ATM\n;17557;auto_smelt,mr_auto_smelt2;自动冶炼;Auto Smelt;AS\n;17558;ftbmoney;FTB交易 延续版;FTB Money Legacy;FTBML\n;17559;;Flying Machine;;\n;17560;;Playable Piano;;\nvanilla-anvil-repair;17561;vanillaanvilrepair;Vanilla Anvil Repair;;\ntiered-depths;17562;tiereddepths;Tiered Depths;;\ntooltip-tweaker;17563;rttweaker;Tooltip Tweaker;;\neditableedibles;17564;editableedibles;EditableEdibles;;\n;17565;liar_bar;骗子酒馆 for Kyou;Liar's Bar for Kyou;LBFK\n;17566;mcdrcommand;MCDR Command Fabric;;\nbetterbuilderswandsfix;17567;betterbuilderswandsfix;BetterBuildersWandsFix;;\npigzilla;17568;pigzilla;Pigzilla;;\npale-garden-backport;17569;palegardenbackport;Pale Garden Backport: The Garden Awakens;;\nseasons-greetings;17570;seasonsgreetings;Season's Greetings;;\nex-nihilo-coloratus;17571;exnihilocoloratus;无中生有：多彩;Ex Nihilo: Coloratus;\ngranular-mob-griefing;17572;granularmobgriefing;Granular Mob Griefing;;GMG\n;17573;dashloader-portinglib-compat;Dashloader PortingLib Compat;;\nfermiumbooter2mixinbooter;17574;fermiumbooter;FermiumBooterDepoliticization;;\nimmersive-optimization;17575;immersive_optimization;Immersive Optimization;;\n;17576;no_entity_lag;实体卡顿优化;No Entity Lag;NEL\ntab-tweaks;17577;tabtweaks;Tab Tweaks;;\nfermiumasm;17578;normalasm;FermiumASM;;\nall-mobs-out-of-the-pool;17579;outofthepool;All undead out of the pool!;;\nclipboardplus;17580;clipboard;Clipboard;;\nmisty-world-unveiled;17581;mist;Misty World Unveiled;;\nbalkons-weaponmod-legacy;17582;weaponmod;更多武器：传承;Balkon's WeaponMod: Legacy;\nbetterendforge-backport;17583;betterendforge;BetterEndForge Backport;;\nepic-fight-mod-rapier-moveset-addon;17584;refm;Epic Fight - Rapier Moveset Addon;;REFM\nlegendary-warriors;17585;epicfightmultiverse;Legendary Warriors: Epic Fight Bosses / Legends Die Twice;;\nscape-and-run-holiday;17586;srpholiday;逃逸：节日;Scape and Run: Holiday;\ncrimson-steves-mutant-mobs;17587;crimsonsteves_mutant_mobs;Crimson Steve's Mutant Mobs;;\nkinetic-pixel;17588;pointblank;机素动能;Create Firearms-Kinetic Pixel;\ntactical-imbuements;17589;tactical_imbuements;Tactical Imbuements;;\npuppy-paws;17590;puppypaws;Puppy Paws;;\nae2-mega-things;17591;ae2_mega_things;AE2 MEGA Things;;\nsolar-flux-reboot;17592;SolarFluxReboot;Solar Flux: Reboot;;\nsolar-flux-additions;17593;wearsfr;Solar Flux Additions;;\nanvil-cell-workbench;17594;anvil_cell_workbench;Anvil Cell Workbench;;\n;17595;ultimatefurnacemod;Ultimate Furnace;;\ncreate-sifting-fabric;17596;createsifter;Create Sifting Fabric;;\ncreate-magics;17597;create_magics;Create: Magics;;\n;17598;crypto;Crypto;;\n;17599;polyfactory;PolyFactory;;\nbuildcraft-legacy;17600;buildcraft;Buildcraft-Legacy;;\n;17601;foolish_datapack;笨蛋数据包;;\nwither-storm-delight;17602;witherstorm_delight;风暴乐事;Wither Storm Delight;WSD\n;17603;iron_block_elevator;铁块电梯;Iron block Elevator;IBE\nx1000-botania;17604;loooxbotania;x1000 Botania;;\nangel-ring-classic;17605;doomangelring;天使指环经典版;Angel Ring Classic;\n;17606;mr_physical_fallingtrees;Physical Falling Trees;;\nbetter-animations-collection-revived-2;17607;BetterAnimationsCollectionRebuilt;Better Animations Collection Revived 2;;\nscape-and-run-parasites-tweaker;17608;srptweaks;Scape and Run Tweaker;;\nscape-and-run-parasites-medical-addon;17609;srp_medical_addon;Scape and Run: Parasites Medical Addon;;\ndurability-overhaul;17610;improved_damage;Durability Overhaul;;DO\nquick-hotkeys;17611;quick_hotkeys;Quick Elytra Reborn / Quick Hotkeys;;\n;17612;acd;永远的圣诞节;AlwaysChristmasDay;ACD\n;17613;sl;强运;StrongLuck;\n;17614;mr_whwdzgs_recipe,whwdzgs-recipe,mr_better_craftingrecipes;更好的合成配方;Better Crafting Recipes;\nuncrafted;17615;uncrafted;Uncrafted;;\nmcjtylib-refilmed;17616;mcjtylib_ng;McJtyLib Refilmed;;\nnanny;17617;nanny;NaNny (Fix NaN Health / Absorption);;\nresource-library;17618;resourcelibrary;Resource Library;;\nresource-config-api;17619;resourceconfigapi;Resource Config API;;\n;17620;threatengl;ThreatenGL;;\nhybridfix;17621;hybridfix;混合修复;HybridFix;\n;17622;disableportalchecks;Disable Portal Checks;;\neat-eat-eat;17623;eat_eat_eat;吃吃吃;Eat Eat Eat;EEE\n;17624;recipestagesjs;配方阶段;Recipe Stages JS;\ncreate-morerecipes;17625;cmr;Create More Recipes;;CMR\nportable-craft-bench;17626;pcb;Portable Craft Bench;;\nportable-tables;17627;portable_tables;Portable Tables;;\n;17628;;Flux Network Fork NeoForge;;\nresource-backpacks;17629;resource_backpacks;Resource Backpack's;;\netherology;17630;etherology;Etherology;;\nsnackpirates-aeromancy-additions;17631;aero_additions;SnackPirate's Aeromancy Additions;;\napothic-enchanting;17632;apothic_enchanting;神化附魔;Apothic Enchanting;\nmaidsoul-kitchen;17633;maidsoulkitchen;女仆厨房;Maidsoul Kitchen;\nrefinedtools;17634;rftools;ReFinedTools;;\nprogrammed-circuit-card;17635;pccard;Programmed Circuit Card;;\nae2-toogleable-view-cell;17636;ae2_toggleable_view_cell;AE2可切换显示元件;AE2 Toggleable ViewCell;\n;17637;cobblemonarmors;Cobblemon Armors;;\nletmorefishlove;17638;letmorefishlove;Let More Fish Love;;\nl_ender-s-delight;17639;lendersdelight;L_Ender 's Cataclysm Delight;;\nofaw-of-faith-and-war-1237;17640;ofaw;信仰与战争1237;Of Faith and War 1237;OFAW\nlotmoresteves;17641;lotmoresteves;LotMoreSteves;;LMS\n;17642;mr_better_enderdragon;Better Ender Dragon;;\nlegendary-armory-zelda-mod;17643;zeldamod;Legendary Armory;;\nhot-update-1-23-by-gt;17644;hot_update;Hot Update 1.23 by GT;;\nmanagement-wanted;17645;management_wanted;FNAF Management Wanted;;\nperception;17646;perception;Perception;;\nfancy-vfx;17647;fancy-vfx;Fancy VFX;;\n;17648;sprucedvanilla;Spruced Vanilla;;\npretty-rain;17649;particlerain;Pretty Rain;;\nalchemancy;17650;alchemancy;Alchemancy;;\nflavorful;17651;flavorful;Flavorful;;\nheavy-fallings;17652;heavy_fallings;Heavy Fallings;;\nerudite-tweaks;17653;eruditetweaks;Erudite Tweaks;;\nkeymap-presets;17654;keymappresets;Keymap Presets;;\nmekanism-no-thanks-nbt;17655;mekanism_ntn;Mekanism No Thanks NBT;;\n;17656;lootbeams;Loot Beams Up;;\n;17657;tekst;Tekst;;\nledger-databases;17658;ledger-databases;Ledger Databases;;\n;17659;shut_up_enderman;Enderman Death Silence;;\ninfectious-zombie-apocalypse;17660;infectious;Infectious - Zombie Apocalypse;;\nxiaoanfc;17661;xiaoanfc;小安的农业核心;Xiaoan's Farming Core;\n;17662;enchant-golden-apple-reborn;附魔金苹果重生;Enchant Golden Apple Reborn;EGAR\ncursor-mod-reload;17663;customcursormod;自定义光标：重生;Custom Cursor: Reload;CCR\n;17664;academy;学园都市 重生;AcademyCraft Reborn;ACR\ncelestial-overhaul;17665;celestial_overhaul;星月改革;Celestial Overhaul;\nsimple-faqi;17666;simplefaqi;简单法器;Simple FaQi;\nredes-coins;17667;redes_coins;Rede的硬币;Rede's Coins;RC\n;17668;teleport_commands;传送指令;Teleport Commands;\n;17669;gcl;GenericCodeLibrary;;GCL\ncosmeticarmours;17670;cosmeticarmoursmod;CosmeticArmours;;\nooo-jar;17671;ashes1ashes;000.jar / ooo.jar;;\nspawner-tweaks;17672;spawner_tweaks;Spawner Tweaks;;\nmob-brawlers;17673;mobbrawlers;Mob Brawlers;;\n;17674;transformhandlers;Transform Handlers;;\nplayer-totem;17675;playertotem;Player Totem of Undying;;\n;17676;screenshot-uploader;Screenshot Uploader;;\ncute-companions-ducks;17677;cute_companions_ducks;Cute Companions: Ducks;;\nloot-n-explore;17678;loot_n_explore;Loot & Explore;;\nepic-knights-ice-and-fire;17679;epic_knights_ice_and_fire;Epic Knights: Ice and Fire;;\nmedieval-origins-revival;17680;medievalorigins;Medieval Origins Revival;;\nconstruction-sticks;17681;constructionstick;建筑棒;Construction Sticks;\nsophisticated-storage-in-motion;17682;sophisticated-storage-in-motion,sophisticatedstorageinmotion;精妙物流;Sophisticated Storage in Motion;\nimgur-display;17683;imgurdisplay;Imgur Display;;\nextra-sponges;17684;extrasponges;更多海绵;Extra Sponges;\nmore-wolf-armors;17685;more_wolf_armors;更多狼铠;More Wolf Armors;MWA\nnot-enough-rocks;17686;ner;Not Enough Rocks;;NER\npedestal;17687;pedestals;Pedestal;;\n;17688;snowballkb;Snowball and Egg Knockback;;\nforestry-worktable-display;17689;forestryworktabledisplay;Forestry: Worktable Display;;\naccessorify;17690;accessorify;Accessorify;;\nhigh-quality-crops;17691;highqualitycrops;高品质作物;High Quality Crops;HQC\nimmortal-enchantment;17692;immortal_enchantment;不死附魔;Immortal Enchantment;IE\nawesome-storage;17693;awesome_storage;魔法存储;Awesome Storage;ams\n;17694;wp;Wallpaper;;WP\n;17695;kill_entity_strengthen_equip;击杀强化装备;;\n;17696;modifyplayerdata;Modify Player Data;;\nidcv;17697;idcv;ID冲突查看器;ID Conflicts Viewer;IDCV\neaglemixins;17698;eaglemixins;EagleMixins;;\nkubejs-thermal-augments;17699;kubejsthermalaugments;KubeJS Thermal Augments;;KTA\n;17700;minopp;Mino++;;\n;17701;;黏液乐事基岩版;Slime Delight Bedrock;\n;17702;;下界乐事基岩版;;\n;17703;randomflower;一些随意的花朵;Some Random Flower;\n;17704;atlantis;亚特兰蒂斯：水寒火暖;Atlantis: Fire Beneath Water;\ncamera-anim;17705;camera_anim;相机大师;Camera Anim;\nmekanism-upgrades-reborn;17706;mekanismupgradesreborn;通用机械升级突破：重生;Mekanism Upgrades: Reborn;\n;17707;zunpet;Zun号;ZUNPet;ZPT\nepic-fight-x-irons-spells-animation-fix;17708;efiscompat;Epic Fight x Iron's Spells Animation Fix;;\nepic-fight-x-wings-reborn-animation-fix;17709;efwingscompat;Epic Fight x Wings Reborn Animation Fix;;\nef-bs;17710;epic_fight_battle_styles;Epic Fight - Battle Arts;;\nalshanexs-familiars;17711;alshanex_familiars;Alshanex's Familiars;;\nfeur-skyland;17712;feur_skyland;Feur Skyland;;\nbalkons-expansion;17713;balkonsexpansion;Balkon's Expansion;;\nascendant-mobs;17715;ascendant_mobs;AM - RPG Mob Leveling System;;\nduarm;17716;du_arm;羽化盔甲;DuArm;\nbomd-addon;17717;bomd_addon;BOMD addon;;\ncreate-the-air-war;17718;create_the_air_wars;Create: The Air War;;\nbew76;17719;bew76;Block Entity Wrench;;\nblock-swapper;17720;blockswapper;Block Swapper;;\n;17721;sm;简易地图;SimpleMap;\n;17722;oneconfig;OneConfig;;\niron-chest-unofficial;17723;;更多箱子非官方版;Iron Chest Unofficial;\nnotenoughids-unofficial-1-7-10;17724;;增加ID上限非官方版;NotEnoughIDs Unofficial;NEIDU\n;17725;shutdown;ShutDown;;\n;17726;ejectorplus;发射器Plus;Ejectorplus;ETP\ntornado-mod-classic;17727;weather_classic;局部气候&风暴经典版;Weather and Tornadoes Classic;\nstrange-berries;17728;strangeberries;Strange Berries;;\njeffs-cursed-walking-structures;17729;jeffs_cursed_walking_structures;Jeff's Cursed Walking Structures;;\nterra-entity;17730;terra_entity;泰拉生物;Terra Entity;TE\nnot-interested;17731;not_interested;Not interested!;;\n;17732;new_soviet;New Soviet Era;;NSE\n;17733;tinkersancient;远古匠艺;Tinkers' Ancient;TA\ngt5r;17734;gt5r;GT5 Reimagined;;GT5R\nragexs-nuclear-tech;17735;;Ragex的核科技;Ragex's Nuclear Tech;\ncivcraft;17736;civcraft;CivCraft;;\ncreate-springs-loaded;17737;csl;Create: Spring Loaded;;\nmekanism-the-factory-must-grow-compatibility;17738;mekanism_tfmg_compat;Mekanism The Factory Must Grow Compatibility;;\ncfm-refurbished-circuit-breaker;17739;cfm_circuit_breaker,cfm_wap;CFM Refurbished: Watt About Power?;;\ndiving-bell-for-tfc;17740;tfcdivingbell;Diving Bell for TFC;;\n;17741;tboc,tb_oc;芝士之书;The Book of Cheese;TBoC\npiglin-safety-reburn;17742;piglinsafety;猪灵 多喝岩浆：重生;Piglin Safety: Reburn;\npapers-please;17743;papers_please;请出示证件;Papers, Please;\nthe-twilight-forest-unofficial;17744;twilightforest;暮色森林非官方版;The Twilight Forest Unofficial;\nsomnia-awoken-gui-beautify;17745;somnia;梦醒时分 - GUI美化;Somnia Awoken - GUI Beautify;\nredstonecraft;17746;redstonecraft;红石工艺;RedStoneCraft;RSC\nmowzies-mobs-baubles;17747;mmbaubles;Mowzie 的生物饰品;Mowzie's Mobs' Baubles;\n;17748;tcpl;拔刀剑：最后战线附属包;SlashBlade: The End Battle Line Addon;TCPL\n;17749;zhiling;虚无世界武器技能扩展;AoA Weapon Expansion;\ndungeons-and-combat;17750;dungeons_and_combat;Dungeons And Combat;;\n;17751;blossom-lib;BlossomLib;;\nmod-erate-loading-screen-forge;17752;mls;Mod-erate Loading Screen-Forge;;\nfastquit-forge;17753;fastquit;FastQuit-Forge;;\ningame-info-reborn;17754;ingameinfo;游戏信息显示复兴版;InGame Info Reborn;IGIR\n;17755;overlay-mod;Overlay HUD Mod;;\nbedisnottoofarawayrefoxed;17756;bintfarf;就要睡觉;Bed Is Not Too Far Away Refoxed;\ngoopreforged;17757;goop;GoopReforged;;\n;17758;deathcost;死亡惩罚;Death Cost;\n;17759;catj;Crimes Against the JVM;;CATJ\nbetter-split-stack;17760;bettersplitstack;Better Split Stack;;\nsdm-engine-core;17761;sdmcore;SDM Engine Core;;\nsdm-stages;17762;sdmgamestages,sdmstages;SDM Stages / SDM GameStages;;\n;17763;dlmc-id7-compatible_enchantments;附魔互相兼容/附魔不冲突;Compatible Enchantments;\nhuh-fabric;17765;huh;哼？;Huh?;\n;17766;acme_admin;ACME Admin Tools;;\ncustom-nausea;17767;customnausea;Custom Nausea;;\naccesstransformerjs;17768;access_transformer_js;AccessTransformerJS;;ATJS\n;17769;mr_better_mob_drop;更好的生物掉落;Better Mob Drop;\nchecklist;17770;checklist;Checklist;;\n;17771;sleep;睡！;Sleep;\n;17772;pr;PacketReSender;;PR\n;17773;kee;艹：早产;KusaEaterEarly;KEE\n;17774;aichat;AI接管聊天;AIchat;AC\npuncher;17775;puncher;Puncher;;\nsensible-stackables;17776;sensible_stackables;Sensible Stackables;;\nrei-addons-ic2classic;17777;ic2rei;IC2Classic: REI Addon;;\n;17778;blossom-tpa;BlossomTpa;;\n;17779;lithium-raycast-fix;Lithium Raycast Fix;;\n;17780;yet-another-minecraft-bingo;Yet Another Bingo;;\ntools-attack;17781;tools_attack;工具也横扫！;Tools Attack;TA\ndarkster-epic-guns-tacz-epicfight;17782;mr_darkster_epicgunstaczepicfight;Darkster Epic Guns / TaCZ + EpicFight;;\nepic-tweaks;17783;epictweaks;Epic Tweaks;;\nepic-fight-x-gliders;17784;efxgliders;[FORGE] Epic Fight X Gliders;;\nno-more-axolotl-despawns;17785;mr_no_moreaxolotldespawns;No More Axolotl Despawns!;;\n;17786;creeperspecies;CreeperSpecies;;\nfamiliar-faces;17787;familiar_faces;Familiar Faces;;\neyes-in-the-dark;17788;eyesinthedark;SCP-280, Eyes in the Dark;;\n;17789;influenza_virus;流感;Influenza Virus;\njellyfishing-delight;17790;jellyfishingdelight;抓水母乐事;Jellyfishing Delight;\nimmersive-engineering-datapack-mystical-cloche;17791;gotuxd;immersive engineering datapack mystical cloche;;\ncarrot-rarity;17792;carrot_rarity;Carrot Rarity;;\nmore-husbandry;17793;husbandry;More Husbandry;;\nchest-with-legs;17794;chest_with_legs;Chest With Legs;;\nbiodiverse;17795;biodiverse;Biodiverse;;\nrailway-wires;17796;wires;Railway Wires / Wires;;\nbullseye;17797;bullseye;靶心;Bullseye;\n;17798;chuansong;传送纸;end pearl;\n;17799;mr_randomisland;随机空岛;Random Island;RDI\ncreate-mob-spawners;17800;create_mob_spawners;Create: Mob Spawners;;\nsalvation-archives-lobotomy;17801;lobotomy;Salvation Archives: Lobotomy;;\nrlcraft-parasited;17802;srpmultiplier;RLCraft Parasited;;\ndregora-rl;17803;worldpacker,dregorarl;Dregora RL;;\nunderneath;17804;underneath,worldpacker;Underneath;;\n;17805;leafmealone;Leaf Me Alone;;\nrocket-mons;17806;rocket_mons;Rocket Mons [Cobblemon] / Just Celesteela;;\nfarsighted-mobs;17807;farsightedmobs,farsighted-mobs;Farsighted Mobs;;\n;17808;hev_suit;HEV Suit Voice System;;\nastral-sorcery-anti-anti-fake-player-asaafp;17809;asaafp;Astral Sorcery Anti Anti Fake Player;;ASAAFP\napotheosis-ascended;17810;apotheosis_ascended;Apotheosis Ascended;;\n;17811;wolves;Better Than Wolves Legacy Unofficial;;\nwarium;17812;crusty_chunks;Warium;;\n;17813;aitplus;AiT Plus;;\nfabulous-fletching;17814;fabulousfletching;Fabulous Fletching;;\nuniversal-wrench;17815;universalwrench;Universal Wrench;;\n;17816;bottle_ship;瓶中船;Bottle Ship;\nsmallblueslimes-ladders;17817;sbsladders;SmallBlueSlime's More Ladders;;\ndoomsday-decoration;17818;doomsday_decoration;末日装饰;Doomsday Decoration;DD\noverclocked-watches;17819;overclocked_watches;Overclocked Watches;;\nportable-wardrobes-easy-armor-swap;17820;portablewardrobearmorswap;Portable Wardrobes (Easy Armor Swap);;\n;17821;msrpp;MC百科资料搜索 重生 ++;;MSRPP\n;17822;dtaeml;DontThrowStitcherExceptionsStitcher;;\n;17823;cml;自定义模组列表;CustomModList;CML\neternalappetite;17824;eternalappetite;永恒食欲;Eternal Appetite;\nplatform;17826;platform;Platform;;\nwandering-cocoa;17827;wanderingcocoa;流浪的可可豆;Wandering Cocoa;\n;17828;realtotem;真实的图腾;RealTotem;RT\nno-more-potion-particles;17829;nomorepotionparticles;No More Potion Particles;;\nlarion-world-generation;17830;larion;Larion World Generation;;LWG\nblueprint-texture-issue-fix;17831;blueprintfix;Blueprint Texture Issue Fix;;\ntransparentcapespatch;17832;transparentcapespatch;透明披风修复;TransparentCapesPatch;\nir-iore;17833;;北欧12轴大力士;;IORE\n;17834;jscmetro;江双成轨道交通;;JSCMetro\nsaltyfish-vehicle;17835;saltyfishairline;SaltyFish Vehicle;;\n;17836;combatdepot;作战仓库;Combat Depot;\ntacz-leawinds-third-person-compat;17837;tacz_leawindtps_compat;TaCZ: Leawind's Third Person Compat;;\n;17838;sinobrush;华夏云墨;SinoBrush;SBR\n;17839;underworld;地下世界;Underworld;\ngtmaid;17840;gtmaid;血汗工厂;GTMaid;GM\n;17841;idwptpmm;IDontWantPeopleToPlayMyMinecraft;;idwptpmm\n;17842;damage-tilt-fix;Damage Tilt Fix;;\nillager-raid-music;17843;illagerraidmusic;Illager Raid Music;;\n;17844;apron;Apron;;\n;17845;coordinatedcompass;Coordinated Compass;;\nprinegorerouse;17846;prinegorerouse;尼格洛兹·旧日重铸/尼格洛兹·万神重归;Prinegorerouse;PN\nfeur-extension-desert;17847;feur_extension_desert;Feur - Extension Desert;;\nscape-and-run-parasites-quark;17848;srpquark;逃逸：疫染夸克;Scape and Run: Parasites Quark;SRPQ\ninfested-swarms-and-spiders;17849;infested_swarms_spiders;Infested: Swarms and Spiders;;\nintegrated-cataclysm;17850;integrated_cataclysm;Integrated Cataclysm;;\n;17851;oo_ee_a_e_a_f;OO EE A E A Forge;;\n;17852;mdf;MCreator模组检测器-Fabric;MCreatorDetectorFabric;MDF\n;17853;dydanmaku;抖音弹幕获取;DyDanmaku;\n;17854;chatheadsYG;聊天头像;Chat Heads YG;\n;17855;searchworldenhance;SearchWorldEnhance;;\ncolonies-maid-citizen;17856;colonies_maidcitizen;殖民地女仆市民;Colonies Maid Citizen;\nrelictium;17857;relictium;Relictium;;\nroyal-city-gladiolus-sweep;17858;royal_city_gladiolus_sweep;王城剑兰-清理;Royal City Gladiolus Sweep;RCGS\n;17859;eg-inventory-blur;Inventory Blur;;\npersonal-cloud-storage;17860;personalcloudstorage;个人云存储;Personal Cloud Storage;PCS\nfloral-herb-blade;17861;floral_herb_blade;花草世界/花草剑;Floral Herb Blade;FHB\n;17862;omnimobs;Omni-Mobs;;OM\n;17863;him;Him;;\nsteve-anomaly;17864;anomaly;Red's Anomaly / The Anomaly;;\nintegrated-simply-swords;17865;integrated_simply_swords;Integrated Simply Swords;;\nobsidian-equipment;17866;obsidianequipment;Obsidian Equipment;;\n;17867;mr_echo_horn;Echo Horn;;\n;17868;mr_echo_spyglass;Echo Spyglass;;\n;17869;mr_echo_trim;Echo Trim;;\nthe-deep-void;17870;the_deep_void;The Deep Void;;\n;17871;grinder_enhance;怪物磨床增强;;\ncataclysm-incinerators-try-hard;17872;incineratorstryhard;灾变：武器技参数配置/冒火的剑;Cataclysm: Weapon Skill Config/ Incinerator's Try Hard;\nscp-apollyon-scp-apollyon;17873;scp_apollyon;SCP Apollyon - SCP Mod;;\ntrickster;17874;trickster;Trickster;;\npale-garden;17875;palegarden;Pale Garden and Creaking;;\nshulker-enchantments;17876;shulker_enchantments;Shulker Enchantments;;\nxenotech;17877;xenotech;XenoTech;;\nsewing-kit;17878;sewingkit;Sewing Kit;;\nzephyr;17879;zephyr;Zephyr;;\ncreate-radars;17880;create_radar;机械动力：雷达;Create: Radars;\ncreate-more-additions;17881;create_more_additions;Create: Crafts & (More) Additions;;\n;17882;cci;仅客户端创造模式背包;ClientCreativeInventory;CCI\n;17883;Flan;Flan非官方Forge精神续作版;Flan Unofficial Forge Spirit Sequel Edition;FUE\nminecart-turning-reforged;17884;minecart-turning,minecart_turning;Minecart Turning;;\nofflimits;17885;offlimits;Offlimits;;\napotheosis-threat;17886;apothluckaffix;Apotheosis Threat Multiplier;;\n;17887;stringduperfix;String Duper Fix Remover;;\n;17888;super_resolution;超分辨率;Super Resolution;SR\nspecial-model-loader;17889;special-model-loader;Special Model Loader;;SML\nvs-ship-handler;17890;shiphandler;VS: Ship Handler;;\n;17891;ap;自动替换;AutoReplace;AP\n;17892;blue_skies_edit;蓝天修复;Blue Skies Edit;\ndice-rebuild;17893;dice;Dice Rebuild;;\nlets-do-apple-wood;17894;applewood;[Let's Do] Apple Wood;;\nascended-quark;17895;ascended_quark;Ascended Quark;;\nenchanted-vertical-slabs;17896;evs;Enchanted Vertical Slabs;;\n;17897;betterladdersmod;Better Ladders;;\nwould;17898;would;木韵自然;Would;\n;17899;mr_lectern_overhaul;Lectern Overhaul;;\ngendustry-community-edition;17900;gendustry;基因工业：社区版;Gendustry: Community Edition;\nae2fix;17901;ae2fix;AE2修复;AE2Fix;\n;17902;memoryusagebar;内存使用率条-重制;Re-MemoryUsageBar;RMUB\n;17903;aa;AutoArmor;;AA\nbetterenddelight;17904;betterenddelight;更好的末地乐事;BetterEndDelight;BED\n;17905;hiddendelight;隐藏乐事;Hidden Delight;HYD\n;17906;;Spanish Delight Refabricated;;\ncrate-delight-croptopia;17907;cratedelightcroptopia;装箱乐事：作物盛景;Crate Delight: Croptopia;\nfoodspoiling;17908;foodspoiling;Food Spoiling;;\nnatura-legacy;17909;natura;Natura (Legacy 1.12.2);;\npepsied-potted-farms;17910;potted_farms,mr_potted_farms;Potted Farms;;\n;17911;mcmoditemsearchrebornagainpro;MC百科资料搜索：再重生Pro;MCMOD Item Search Reborn Again Pro;\n;17912;;我能看什么;What Can I See;\n;17913;timesync;时间同步-重制;Re-TimeSync;RTS\n;17914;linearxpfabric;线性经验-Fabric;Linear Xp Curve Fabric;LXCF\n;17915;;彼岸幻梦;THE_OTHER_SHORE: Dream;OPAL\nriding-partners-reforged;17916;riding_partners;双人骑行：重铸;Riding Partners：Reforged;RPR\n;17917;retargetato-fabric;ReTargetato-Fabric;;\ncat-healing;17918;cathealing;Cat Healing;;\nplumbob;17919;plumbob;Plumbob;;\n;17920;variants-cit;Variants-CIT;;\n;17921;rc;RandomCrash;;RC\n;17922;enchantseries;豆浆更多附魔;More Enchantments by Soybeani;\n;17923;mr_no_collision;NoCollision;;\n;17924;clearview;Clearviews;;\n;17925;medival_weapon_sounds;Immersive Weapon Draw Sounds;;\nepic-fight-invincible;17926;invincible;无坚不摧;Epic Fight - Invincible;\nore-dict-re-order;17927;oredictreorder;Ore Dict Re-Order;;\nelectrostatics;17928;electrostatics;KJ's Electrostatics;;\n;17929;radiocraft;RadioCraft;;\nevil-captcha;17930;captcha;Evil Captcha;;\n;17931;el;EventLib;;EL\n;17932;ca;ConfigAPI;;\n;17933;ms_1144;MC百科资料搜索 - 1.14.4;MCMOD Item Search in 1.14.4;\nfpsmatch;17934;fpsmatch;通用射击游戏比赛框架;FPSMatch;FPSM\nmythic-charms;17935;mythic_charms;神话护符;Mythic Charms;\n;17936;tinkers_waste;工匠的实用小物件;Tinkers' Useful Items;TUI\n;17937;baguette;法棍工艺;Baguette craft;\n;17938;;草神赐福;;\nminecraft-add;17939;minecraft_add;原版补充;Minecraft Add;MA\nstacked-blocks;17940;stackedblocks;捆扎方块;Stacked Blocks;\nstacked-blocks-farmers-delight;17941;stackedblocksfarmersdelight;捆扎方块：农夫乐事;Stacked Blocks: Farmer's Delight;\nfowlplay;17942;fowlplay;Fowl Play;;\n;17943;mr_amethyst_corruption;Amethyst Corruption;;\nender-totem;17944;endertotem;Ender Totem;;\ndiversity;17945;diversity;Diversity;;\ntonsofenchants;17946;tonsofenchants;Tons Of Enchants;;\n;17947;fk.proximitylamp;Proximity Lamp;;\n;17948;bookofexperience;Book of Experience;;\ncave-root;17949;caveroot;KJ's Cave Root;;\n;17950;tooltrims;工具纹饰;Tool Trims;\nwhos-there-ghost-players;17951;whos_there;Who's There ?: Ghost Players;;\nthe-cuckoo-clock;17952;the_cuckoo_clock;SCP-4975, The Cuckoo Clock;;\nfinal-samurai;17953;final_samurai;Final Samurai;;\nclassic-battle-towers;17954;battletowers;经典战斗高塔;Classic Battle Towers;\nsihywtcamd-extensions;17955;sihywtcamd_extensions;SIHYWTCAMD Extensions;;\n;17956;;理智-Doge版;sanitydim;san\n;17957;heart_crystals;Heart Crystals;;\npartialhearts;17958;partialhearts;PartialHearts;;\n;17959;the_abyss;The Abyss;;\ncosy-critters;17960;cosycritters;Cosy Critters & Creepy Crawlies;;\nomnilib;17961;omnilib;Omnilib;;\nastages;17962;astages;AStages;;\n;17963;mindful-loading-info;Aloria Loading;;\n;17964;parentalcontrols;Parental Controls;;\njason-bot;17965;jasonbot;Jason机器人;Jason Bot;JB\n;17966;ex_enigmaticlegacy;神秘传说;EXEnigmaticlegacy;EXE\nfly-in-the-sky;17967;fly_in_the_sky;高空飞行;Fly in the sky;\n;17968;smart-elytra;智能鞘翅;SmartElytra;SE\nmomotinker;17969;momotinker;墨工坊;momotinker;\nelectrostaticsre;17970;electrostatics;每日任务;ElectrostaticsRE;\n;17971;staretilltheygrow;盯到它们熟为止-重生！;Stare Till They Grow Reborn!;\nbrooms-unofficial;17972;broomsmod;扫帚：非官方版;Brooms Unofficial;\nfishing-dangers;17973;fishingdangers;Fishing Dangers;;\ndistinctpaintings;17974;distinctpaintings;Distinct Paintings;;\n;17975;searchonmcmod;MC百科搜索;Search on MCMOD;\ninfinite-lava-renewed;17976;infinite-lava;Infinite Lava: Renewed;;\n;17977;better-fabric-console;更好的 Fabric 控制台;Better Fabric Console;BFC\nmine-tooltips;17978;minetooltip;Mine Tooltips;;\nfastchest-reforged;17979;fastchest;FastChest Reforged;;\n;17980;bettercaves;古早洞穴优化;Better Caves Archaic;BCA\n;17981;ves;Venti Script;;VES\nalexis-64-food;17982;alexis_food;Alexis 64's Food;;\ncosmetic-armor-revitalized-fabric;17983;cosmetic-armor;Cosmetic Armor: Revitalized;;\n;17984;walksyambience;Ambience V2;;\numapyoi-addon-pack;17985;umapyoi_addon;马儿蹦跳：拓展包;Umapyoi：Addon Pack;UAP\nthe-god-of-roadkill;17986;thegodofroadkill;The God Of Roadkill;;\n;17987;visual-snowy-leaves;[Bedrock Parity] Visual Snowy Leaves;;\nverdant;17988;verdant;葱茏绿意;Verdant;\ngrounded;17989;verdant;Grounded;;\ngallery;17990;gallery;Gallery;;\n;17991;the_titans1;The Titans Mod CLASSIC;;\nlost-cities-survive-the-dead-edition;17992;lostcities;Lost Cities - Survive the Dead Edition;;\n;17993;gss;给某人某物;Give Sb. Sth.;GSS\n;17994;gim;Give it to me;;GIM\npower-of-the-void;17995;power_of_the_void;虚空之力;Power of the Void;PofV\naerlune-rpg;17996;world;Aerlune RPG;;\n;17997;mr_epic_fightxspartanweaponry;Epic Fight x Spartan Weaponry;;\nhitboxbackport;17998;playerhitboxbackport;HitboxBackport;;\nfsmm;17999;fsmm;Fex's Small Money Mod;;FSMM\ncreate-pantographs-and-wires;18000;pantographsandwires;Create: Pantographs & Wires;;\nfootwork-api;18001;footwork;Footwork API;;\n;18002;halplibe;HalpLibe;;\nwarmachinelib;18003;wmlib;战争机器库;WarMachineLib;WML\n;18004;vcm;音控鼠标;VoiceControlledMouse;VCM\ncrash-assistant;18005;crash_assistant;Crash Assistant;;CA\n;18006;bf;撞火;BumpFire;BF\n;18007;tkf;The Open Sauce Toast Killer Forge;;\ninventorytweak;18008;inventorytweak;库存调整;InventoryTweak;\n;18009;miss;MISS;;\nlegendary-tabs;18010;legendarytabs;传说选项卡;Legendary Tabs;\nsky-breaker-blue-skies-limit-remover-1-18-2;18011;skybreaker;天穹突破_1.18.2;Sky Breaker_1.18.2;\n;18012;libves,lib-ves-server,lib-ves;Lib VES;;\n;18013;diyu_kuang;地狱矿;;\n;18014;prismarine;海晶合金;;\n;18015;stellar_radiance;星·辉;StellarRadiance;\n;18016;mxatools;MX的工具A;MXA Tools;\ncloudertinker;18017;cloudertinker;浮云工坊;Clouder Tinker;CTK\nxiyus-lucky-block;18018;luckyblocks;饩雨的幸运方块;Xiyu's Lucky Block;XYLB\n;18019;epicfight_oldsolar;史诗战斗：旧版solar;EpicFight_oldsolar;EF_OS\nreskillable-reimagined;18020;reskillable;Reskillable Reimagined;;\nrenaissance-core;18021;renaissance_core;Renaissance Core;;\n;18022;startup-chime;启动音效;Startup Chime;\ncarpet-takeneko-addition;18023;carpet-takeneko-addition;竹猫的地毯扩展;Carpet Takeneko Addition;TNCA\nchain-mod;18024;chains_mod;Chain Mod;;\nspeedrunmod;18025;sr;SpeedrunMod;;\ninsta-boom;18026;InstaBoom;Insta Boom;;\ninfinite-night;18027;infinitenight;Infinite Night;;\nnether-lake;18028;netherlake;Nether Lake;;\n;18029;create_cards;Create: Cards;;\ncreate-powerlines;18030;createpowerlines;Create: Powerlines;;\nemerald-nugget;18031;emeraldnugget;Emerald Nugget;;\nwhat-dah-dog-doin-tetra-compat;18032;whatdahdogdointetracompat;What Dah Dog Doin Tetra Compat;;\nendbiome;18033;endbiome;EndBiome;;\njasons-fly-mod;18034;flymod;Jasons Fly Mod;;\nconium;18035;conium;Conium;;\nyellowtext;18036;yellowtext;YellowText;;\n;18037;bb;信标向下移植;BiggerBeacons;\nhealight;18038;healight;Healight;;\n;18039;bodyhealthsystem;Body Health System;;\nsavemykeybinds;18040;savemykeybinds;SaveMyKeybinds;;\nnomenublur;18041;nomenublur;NoMenuBlur;;\n;18042;bh;BeaconHalo;;BH\nmore-loot-tables;18043;moreloottables;More Loot Tables;;\nnoteblocks;18044;notepp;Noteblocks++;;\n;18045;crunchy_crunchy_advancements;Crunchy Crunchy Advancements;;\nchainvein;18046;chainmining;ChainVein;;\nmobs-on-rails;18047;mobsonrails;Mobs On Rails;;\n;18048;viewmodel;View Model;;\ngravestone-x-curios-api-compat;18049;gravestonecurioscompat;Gravestone x Curios API Compat;;\nnolijium;18050;nolijium;Nolijium;;\neventslib;18051;eventslib;EventsLib;;\netst-lib;18052;etstlib;乙硫醇的匠魂词条库;EtST Lib;EtSTl\norbs-of-crafting;18053;orbs_of_crafting;Orbs of Crafting;;\n;18054;leonmtr;Leon轨道交通Mod;;LMod\n;18055;npcagent;村民智能体;npcagent;NPCA\n;18056;fastcommand;Carpet指令降级;FastCommand Carpet;FCC\n;18057;starkettleac;SkAc反作弊;StarkettleAntiCheat;SkAc\n;18058;littletail;聊天小尾巴;LittleTail;\n;18059;ac;坠机;AirCrash;AC\nwhatimpressing;18060;wip;正击何键;What I'm Pressing;\n;18061;no-enchantment-restrictions;自定义等级限制;No Enchantment Restrictions;NER\n;18062;;更改试炼密室生成维度：末地;;\nstackcraft;18063;stackcraft;Stackcraft;;\n;18064;extremeview;ExtremeView;;\n;18065;materialexcavator;Material Excavator;;\n;18066;startinthenether;StartInTheNether;;\nmekanismmixinhelp;18067;mekmixinhelp;MekanismMixinHelp;;\nsimple-warp-commands;18068;simple_warp_commands;简单传送指令;Simple warp commands;\n;18069;mojangfix;MojangFix;;\ntatercart;18070;tatercart;TaterCart;;\nirons-spell-book-mob-attribute-addon;18071;iron_att;Iron's spell book mob attribute addon;;\nepic-fightexsilium-gladius;18072;exsiliumgladius;EpicFight—放逐之刃;Epic Fight — Exsilium Gladius;\nkamenriderweaponcraft;18073;kamen_rider_weapon_craft;假面骑士武器大全集;KamenRiderWeaponCraft;KRW\n;18074;substitute_totem_for_death;替死图腾;Totem of Substitution;TS\nadvancearmy;18075;advancearmy;先遣部队;Advance Army;AA\n;18076;blood_blade;Blood Blade;;\n;18077;mineimpact;方神;MineImpact;MI\njadens-nether-expansion-delight;18078;jadensnetherexpansiondelight;Jaden's Nether Expansion Delight;;\n;18079;shadow_charlie;黑暗中的查理;Shadow Charlie;\narchery-expansion;18080;archeryexp;Archery Expansion;;\njjsk;18081;jujutsucrafts;JJSK;;\n;18082;MoreEnchants;More Enchants;;\n;18083;so_crazy_try;趣亦合成;So Crazy Try;SCTY\nash-of-sin-overlord;18084;ash_of_sin_overlord;罪业余烬：不死者之王;Ash Of Sin: Overlord;\ncarbon-strip-craft-augmented-edition;18085;carbonstrip;碳条工艺：加强版;Carbon Strip Craft:Augmented Edition;CSCA\n;18086;rotp_zbc;RotP：极恶中队附属;Ripples of the Past: Bad Company Addon;\ntrue-prime-piece;18087;trueprimepiecetwo;True | Prime Piece;;\nmaxims-core;18088;maxims_core;箴言与本源;Maxims&Core;M&C\narphex;18089;arphex;Arthropod Phobia Expansions + Horror Bosses (Spider Moth);;ArPhEx\ncubic-combat-simulator;18090;ccsm;Cubic Combat Simulator Mod;;CCSM\n;18091;groove-theft-auto-ime;去你大爷的输入法;Groove Theft Auto-IME;GTA\nwebmapview;18092;webmapview;网页地图浏览;webmapview;wmapv\nf3-is-my-shawty;18093;f3ismyshawty;F3 Is My Shawty;;\nollamachat;18094;ollamachat;ollamachat;;olm\npotion-stacker;18095;potionstacker;Potion Stacker;;\n;18096;better_weather;Better Weather;;\nbetter-gamemode;18097;bettergamemode;better gamemode;;\nbetter-command;18098;bettercommand;Better command;;\nfishermans-haven;18099;fishermans_haven;Fisherman's Haven;;\nbakeries;18100;bakeries;烘焙坊;Bakeries;\n;18101;finaldragon;终湮龙神;Final Dragon;\nfemale-plastic-surgery-bust-limit-tweaker;18102;femaleplasticsurgery;Female Plastic Surgery: Bust Limit Tweaker;;\n;18103;si;ShootIt;;SI\nfeur-pet-hat-forge;18104;feur_beauty_pet;Feur Pet Hat;;\nfeur-dimensional-skyland;18105;feur_dimensional_skyland;Feur Dimensional Skyland;;\nfeur-colors;18106;feur_colors;Feur Colors;;\nfeur-statue;18107;feur_statue;Feur Statue;;\nfeur-builder;18108;feur_builder;Feur Builder;;\nfeur-elytra-wings;18109;feur_elytra_wings;Feur Elytra Wings;;\nfeur-magic-more-enchantments-xp-bottle-forge;18110;feur_magic;Feur Magic;;\nveggies-delight;18111;veggiesdelight;蔬菜乐事;Veggies Delight;\n;18112;tinkers_delight_extra;匠魂乐事扩展;Tinkers' Delight Extra;TDE\nramadan-delight;18113;ramadandelight;斋月乐事;Ramadan Delight;\n;18115;iui_nf;ImproperUINeoForge;;\npotion-core-reborn;18116;potioncore;药水核心：重生;Potion Core Reborn;\n;18117;nitrogen_internals;氮;Nitrogen;\nrolling-gate;18118;rolling_gate;卷帘门;Rolling Gate;RG\nbettertab;18119;bettertab;更好的Tab;BetterTab;BT\nlet-me-see-see-your-code;18120;let_me_see_see;让我看看;LetMeSeeSee(YourCode);LMS\nput-mana-in;18121;put_mana_in;手充魔力;Put Mana In;PMI\nars-botania;18122;ars_botania;Ars Botania;;\nlinweiyun-mod;18123;linweiyun;Linweiyun的附魔;;\nbaublesex;18124;;BaublesEX;;\ncustom-block-highlight;18125;blockhighlight;自定义方块高亮;Custom Block Highlight;\ntinkers-simple;18126;tsimple;Tinkers Simple;;\nthreedmusket;18127;musketthreed;ThreeDMusket;;\nrtmmetro;18128;rtmmetro;真实火车：地铁扩展;RTMMetro;\nbicycles-fws;18129;;Bicycles FWS;;\nbubblevehicles-datapack;18130;bubblevehicles;Bubble Vehicles;;\nir-cr-ss8;18131;ss8;中国铁路第一速;[IR/Immersive Railroading]China Railways SS8;CRFF\nlobotomy;18132;lobotomy;Lobotomy;;\nparrot-power;18133;parrotpower;Parrot Power;;\nfortune-ores-2;18134;FortuneOres;FortuneOres;;\n;18135;tekdrone;Tek Drone;;\ndarkage-bizarre-addon-remake;18136;dk;DarkAge Bizarre;;\nfeur-dungeon-spawner;18137;feur_dungeon_spawner;Feur - Dungeon Spawner;;\nfeur-extension-fossil;18138;feur_extension_fossil;Feur - Extension Fossil;;\nfeur-extension-jungle;18139;feur_extension_jungle;Feur - Extension Jungle;;\ncybernetic-system;18140;cybernetic_system;赛博系统;Cybernetic System;\n;18141;make_the_cover_;绿幕;Make the cover;MTC\n;18142;mushroom_daydream;蘑菇幻想;Mushroom Daydream;\nliquid-dirt;18143;liquidDirt;Liquid Dirt;;\nliquid-blocks;18144;liquidblocks;Liquid Blocks;;\napothic-combat;18145;apothiccombat;Better Combat x Apotheosis Compat;;\ntoms-trading-network;18146;toms_trading_network;Tom's Trading Network;;\nsomanyapples;18147;so_many_apples;超多苹果;So Many Apples;\nstatic-block;18148;staticblock;Static Block;;\ncherryvillage;18149;cherry_village;Cherry Village;;\nductwork;18150;ductwork;管道;Ductwork;\ncooldown-coordinator;18151;cooldown-coordinator;Cooldown Coordinator;;\nlibrarianlib-continuous;18152;librarianlib;LibrarianLib-Continuous;;LLC\nluna;18153;luna,luna_minecraft;Luna;;\nloot-integrations;18154;lootintegrations;Loot Integrations;;\nrealtimescale;18155;realtimescale;RealTimeScale;;\n;18156;world2recyclebin;World2RecycleBin;;\ncrafting-recipe-exporter;18157;craftingrecipeexporter;Crafting Recipe Exporter;;\n;18158;legacy_completion;旧版命令补全;Legacy Completion;\nextra-apoth-compat;18159;extra_apoth_compat;Extra Apoth Compat;;\npathtracker;18160;pathtracker;PathTracker;;\nelytra-protection;18161;elytraprotection;Elytra Protection;;\nskip-sleep;18162;skipsleep;Skip Sleep;;\nstony-cliffs-are-cool;18163;stonycliffs;Stony Cliffs Are Cool;;\nsimple-ender-things;18164;SimpleEnderThings;Simple Ender Things;;\nmimecraft-mod;18165;warden_armor;Warden Armor;;\nwarden-sword;18166;warden_sword;Warden Sword;;\narch-bows;18167;archbows;Arch Bows;;\nfancy-trinkets;18168;fancytrinkets;精美的小饰品;Fancy Trinkets;\nblock-n-zapping;18169;create_bnz;Create: Block n' Zapping;;\ncgs;18170;cgs;Create: Gunsmithing;;\ndynamic-trees-jadens;18171;dtjadens;Dynamic Trees - Jaden's Nether Expansion;;\ndynamic-trees-croptopia-new;18172;dtcroptopia;Dynamic Trees - Croptopia;;\nstacksonstacks;18173;StacksOnStacks;Stacks on Stacks;;\nmore-of-all;18174;more_of_all;More Of All;;\n;18175;tb;Toggle Blocks;;\n;18176;battle-dream-island-mod;Battle for Dream Island;BFDI;\nchiikawa;18177;chiikawa;吉伊卡哇;Chiikawa;CKW\ntolkien-tweaks;18178;tolkientweaks;Tolkien Tweaks;;\ntolkien-tweaks-mobs-edition;18179;tolkienmobs;Tolkien Tweaks - Mobs Edition;;\nsoulplied-energistics;18180;soulplied_energetics;Soulplied Energistics;;\nvalarian-conquest;18181;valarian_conquest;Valarian Conquest;;\nmowzies-mobs-respawner;18182;mowziesmobsrespawner;Mowzie's Mobs Respawner;;\ncrafting-station-jei-edition;18183;craftingstation;合成站：JEI版;Crafting Station: JEI Edition;\nmore-waterlogging;18184;morewaterlogging;More Waterlogging;;\nthe-ancient-trinkets-index;18185;tatimod;The Ancient Trinkets Index [More Magic Series];;\nstraw-golem-rebaled-ported;18186;strawgolem;Straw Golem Rebaled: Ported;;\nhaybale-1-20-1;18187;tlib;Haybale Ported;;\ngtbcs-spelllib;18188;gtbcs_spell_lib;GTBC's SpellLib/API;;\nhaybale;18189;haybale;Haybale;;\n;18190;pk_cr_di;Creative Dimension;;\ncreative-eating-reforged;18191;creative_eating;创造也能吃;Creative Eating Reforged;\n;18192;aemodbtnfix;Better ModList 按钮修复;Better Mod List Button Fix;\nkrypton-foxified;18193;krypton;氪 NeoForge 版;Krypton Foxified;\nproximity-text-chat;18194;proximitytextchat;Proximity Text Chat;;\njustleveling-fork;18195;justlevelingfork;JustLeveling Fork;;\n;18196;fluidlogged;FluidLogged;;\nredirected;18197;redirected;Redirected;;\nasynchronous-reprojection;18198;ar_mod;Asynchronous Reprojection;;\nnot-enough-keybinds;18199;not-enough-keybinds;Not Enough Keybinds;;\ndaily-deliveries;18200;daily_deliveries;Daily Deliveries;;\nvehicular-movement-light;18201;vehicularmovement_light;Vehicular Movement Light;;\nvehicular-movement;18202;vehicularmovement;Vehicular Movement;;\nonysdvehicles;18203;;Onysd Vehicles;;\n;18204;atdhxd3b;HXD3B型电力机车;;CRIR\nzunpet-forge;18205;zunpet;Zun号非官方NeoForge/Forge版;ZUNPet NeoForge/Forge;ZPTF\nmore-hitboxes;18206;morehitboxes;更多碰撞箱;More Hitboxes;\n;18207;;更好的渲染龙;BetterRenderDragon;\nshipwreck-fix;18208;shipwreckfix;沉船修复/沉船卡顿优化;Shipwreck Fix;\n;18209;ac;AIChat;;AC\nnarcissus-farewell;18210;narcissus_farewell;水仙辞;Narcissus Farewell;NF\nmcmod-splash-plus-plus;18211;mcmodsplashpp;百科闪烁标语++;MCMod Splashes PlusPlus;BKSPP\nrandom-enchants-2;18212;randomenchants2;随机附魔 2;Random Enchants 2;\nsparkle-reforge;18213;sparkle;火花：重铸;Sparkle: Reforge;SR\nled-lighting;18214;LEDLighting;LED Lighting;;\nnot-enough-stuff;18215;NotEnoughStuff;Not Enough Stuff;;\niron-fence;18216;IronFence;Iron Fence;;\nbricks;18217;Bricks;Bricks;;\nadditional-records;18218;Records;Additional Records;;\nfirefly-bush;18219;fireflybush;Firefly Bush;;\nspring-update-snapshot-to-old-versions;18220;spring;Spring Update;;\n;18221;godotsignalsystem;Godot Signal System Lib;;\n;18222;bad_app;Bad Apple played on the custom Banner Patterns;;\nleons-minecraft-dungeons-paintings-mod;18223;ls_dungeons_paintings;Leon's Dungeons Paintings;;\nmiscellaneous-additions;18224;misc_additions;Miscellaneous Additions;;\nrupters-figure-mod-doors;18225;figure;Rupters Figure Mod;;\nsophisticated-backpack-storage-emerald-upgrade;18226;sophisticated_emerald_upgrade;Sophisticated Backpack / Storage Emerald Upgrade;;\nsophisticatedinjections;18227;sophisticatedinjections;Sophisticated Injections;;\nimmortalers-delight;18228;immortalers_delight;千古乐事;Immortalers Delight;\n;18229;axolotl_wont_asphyxia;美西螈不会窒息;Axolotl Won't Asphyxia;AWA\ncreatenuclear;18230;createnuclear;机械动力：原子核动;Create Nuclear;\nkeepthatinventoryopen;18231;keepthatinventoryopen;KeepThatInventoryOpen;;\nfalcons-single-person-sleep;18232;singlepersonsleep;Falcon's Single Person Sleep;;\nrealisticsleep;18233;realisticsleep;Realistic Sleep;;\n;18234;playerdailyshop;玩家每日商店;playerDailyShop;\navaritia-1-1x-unofficial;18235;;无尽贪婪 1.1x 非官方延续版;Avaritia 1.1x Unofficial Extended Life;\nlightsaberx;18236;LightSaberX;LightSaberX;;\nprime-punch-one-punch-mod;18237;primepunch;Prime Punch | One Punch;;\nrpgmana;18238;rpgmana;RPGMana / Arch-RPGMana;;\nopposite-dimensions;18239;oppositedimensions;Opposite Dimensions;;\nmigamigos;18240;migamigos;Migamigos;;\n;18241;cloudmusicroom;云音乐房间 / 服内一起听音乐与点歌;CloudMusicRoom;CMR\ntheonesmeagle;18242;theoneprobe;The One Smeagle;;TOS\nwitchery-companion;18243;witcherycompanion;Witchery: Companion;;\n;18244;tpa-utilities;TPA Utilities;;\n;18245;remote_player_waypoints_for_xaero;MapLink;;\n;18246;rl;RenderLib;;RL\naddonslib;18247;addonslib;AddonsLib;;\nsiliconedolls;18248;silicone_dolls;硅胶玩偶;SiliconeDolls;SD\n;18249;authshield;认证防护;AuthShield;AS\n;18250;;LeviLamina;;LL\nredstone-armoury-minus-thermal;18251;RArm;Redstone Armoury Minus Thermal;;RAMT\nelytras-plus;18252;elytras-plus;Elytras+;;\nae2uel-wireless-universal-terminal;18253;ae2wut;无线通用终端;AE2UEL Wireless Universal Terminal;ae2wut\npresents;18254;presents;Presents;;\ncreate-picky-waterwheels;18255;createpickywheels;Create Picky Wheels;;\n;18256;pestilence_of_the_marrow_;疫至深髓;Pestilence Of The Marrow;\n;18257;green_screen;Green Screen;;\nfancy-lamps;18258;fancylamps;Fancy Lamps;;\nresizing-potion;18259;resize;Resizing Potion;;\nmiku;18260;miku;Miku;;\nadvanced-smelter;18261;advanced_smelter;荒古炼金炉;Advanced Alchemical Furnace;\nextended-tinker;18262;extended_tinker;匠魂扩张;Extended Tinker;ET\n;18263;lin_perms;Linweiyun的权限管理系统;LinPerms;\n;18264;synthesis_reborn;配方复兴;Crafting_Revival;C_Rev\nbedsheet;18265;bedsheet;床单;BedSheet;BS\ndespawn-tweaks;18266;despawntweaks;Despawn Tweaks;;\nplanetguylib;18267;planetguylib;PlanetguyLib;;\n;18268;fabric_kotlin_extensions;Fabric Kotlin Extensions;;\ncrafting-table-plus;18269;craftingtableplus;Crafting Table Plus;;\n;18270;worldexport;Igrium's Replay Exporter;;\n;18271;loot4everyone;Loot4Everyone;;\ncontainerfix;18272;containerfix;ContainerFix;;\ncool-rain;18273;coolrain;Cool Rain;;\nqueqiao;18274;queqiao;鹊桥;QueQiao;QQ\nlexikon;18275;lexikon;Lexikon;;\n;18276;motion-insight;动态观察;Motion Insight;\nidmid;18277;idmid;I Died, My Items Didn't;;IDMID\nsimple-concrete;18278;simpleconcrete;Simple Concrete;;\nchunk-activity-tracker;18279;chunkactivitytracker;Chunk Activity Tracker;;\nlets-do-lilis-lucky-lures;18280;lilis_lucky_lures;[Let's Do] Lili's Lucky Lures;;\ncraftformers-prime;18281;mod_id,tfp;Craftformers Prime;;CFP\n;18282;pumpkin_moon;Pumpkin Moon;;\n;18283;vbe;Vanilla Block Enhancements;;VBE\narts-by-kev-mod-2023;18284;artsbykevmod;Kevosaurus Rex;;\nthe-lion-king-mod;18285;mod_LionKing;The Lion King Mod;;\nepic-terrain-compatible;18286;mr_epic_terrain_compatible;Epic Terrain Compatible;;\nthe-beginning;18287;the_beginning;The Beginning;;\ncreate-simple-ore-doubling;18288;create_simple_ore_doubling;Create: Simple Ore Doubling;;\ncreate-low-heated;18289;createlowheated;Create Low-Heated;;\nplanetguy_gizmos;18290;planetguy_Gizmos;Gizmos;;\n;18291;thirdpersonfix;Third Person Fix Babric;;\n;18292;retrocommands;Retro Commands;;\n;18293;manapeeper;Mana Peeper;;\nretro-sophisticated-backpacks;18294;retro_sophisticated_backpacks;复古精妙背包;Retro Sophisticated Backpacks;\ngrid;18295;grid;Grid;;\nemienchants;18296;emienchants;EMI Enchants;;\nrandom-complement;18297;random_complement;随意补充;RandomComplement;RC\nthedulling;18298;thedulling;The Dulling;;\nbatsword;18299;batsword;BatSword;;\nunidye;18300;unidye;Unidye;;\nobs-overlay;18301;obs_overlay;OBS Overlay;;\ncwhitelist;18303;cwhitelist;cwhitelist;;\n;18304;tg;TweakerooGui;;TG\nraw-mouse-input-blessed-edition;18305;rawinput;原始鼠标输入 - 祝福版;Raw Mouse Input - Blessed Edition;\n;18306;CompactDisplayHUD;Compact Display HUD;;\n;18307;togglenametags;Toggle Nametags;;\ngrass-kiss;18308;grass_kiss;GrassKiss;;\nattack-through-grass;18309;atg;Attack Through Grass;;ATG\nstructurify;18310;structurify;Structurify;;\nnetherite-beacons-forge;18311;netheritebeacons;Netherite Beacons;;\nesoteric-reforging-forge;18312;esotericreforging;Esoteric Reforging;;\ndimensional-leap;18313;dimensional_leap;Dimensional Leap;;\nreplant-it;18314;auto_replant;Replant it!;;\ntrail-tales-delight;18315;trailandtales_delight;樱途旅事;Trail&Tales Delight;TATD\npams-harvestcraft-2-fantasy;18316;pamhc2fantasy;Pam's HarvestCraft 2 - Fantasy;;\nalexs-caves-rad-fork;18317;alexscaves;Alex's Caves rad fork;;\nhongbao-red-envelope;18318;red_envelope;红包;HongBao: Red Envelope;\n;18319;keybinder;按键绑定辅助;KeyBinder;\nsortilege;18320;sortilege;Sortilege;;\n;18321;lightfallclient;LightfallClient;;\nlazymodder;18322;lazymodder;LazyModder;;\n;18323;ShockAhPI,mod_SAPI;ShockAhPI;;SAPI\n;18324;moapi,mod_moapi;Mod Options API;;MOAPI\nminecraft-skill-tree;18325;mcskilltree;The Skill Tree Mod;;\nskill-tree-mod;18326;skilltree;Skill Tree Mod;;\n;18327;ItemSpriteAPI;ItemSprite API;;\n;18328;reforged;Reforged;;\neasy-shulker-access;18329;shulker_access;Easy Shulker Access;;\n;18330;kaleidoscope;森罗物语;Kaleidoscope Mod;\ntorch-slabs-renewed;18331;torchslabmod;更好的火把放置重制版;Torch Slabs Renewed;\n;18332;TravelDokodemoDoor;Anywhere Travel Door / 遠征どこでもドア;;\nwormhole-artifact;18333;wormhole_artifact;Wormhole Artifact;;\n;18334;teleporters;Teleporters;;\n;18335;superaxes;Super Axes;;\n;18336;GreyGoo;Grey Goo Mod Revival;;\n;18337;mr_weather_control;Survival Weather Control;;\ncreate-diamond-factory;18338;create_diamond_factory;Create: Diamond & Factory;;\ncreate-realism;18339;realism;Create Realism;;\ncreate-sound-of-steam;18340;pipeorgans;Create: Sound of Steam;;\n;18341;TheMoon;The Moon;;\nbig-explosives;18342;big_explosives;Big Explosives;;\nice-cream-creepers;18343;icecreamcreepers;The Ice Cream Sandwich Creeper Mod;;\naquatic-craft;18344;aquaticcraft;Aquatic Craft;;\nvillage-taverns;18345;village_taverns;乡村酒馆;Village Taverns;\nf-a-rebirth-and-expand-genesis;18346;fossilsaddon;Fossils and Archeology Rebirth and Expand: Genesis;;\nfossils-archeology-origins;18347;fossilsorigins;考古与化石：起源;Fossils & Archeology Origins;\nfaunify;18348;faunify;Faunify;;\n;18349;jct;Armadillo Armor by JayCubTruth;;\nwills-hedgehogs;18350;williams_hedgehogs;Will's Hedgehogs!;;\ncryptography;18351;cryptography;Metaphysics / Cryptography;;\n;18352;spasm;Straightforward, Pure ASM;;SpASM\nsketch;18353;sketch;Sketch;;\n;18354;envirosound;EnviroSoundBeta;;\n;18355;keybindsgaloreplus;KeybindsGalore Plus;;\n;18356;BiomeWorldTypes;生物群系世界类型;Biome World Types;\n;18357;smoothbeta;SmoothBeta;;\nno-fall;18358;nofall;No Fall;;\n;18359;kyoyu;共有;Kyoyu;\nscopic;18360;scopic;Scopic;;\nmemory-leak-fix-unofficial-remastered;18361;;Memory Leak Fix Unofficial Remastered;;\n;18362;distancecalculation;Distance Traveled Calculation;;\n;18363;betamoonphases;Moon Phases Backport;;\nvalkyrien-skies-control-craft;18364;vscontrolcraft;控制学;Control Craft;\nreforbidden-magic;18365;forbiddenmagicre;禁忌魔法：重生;ReForbidden Magic;\nmoses-mod;18366;mosesmod;Moses Mod;;\ncraftable-animals;18367;craftableanimals;可合成动物;Craftable Animals;\navaritia-1-1x-kedition;18368;;Avaritia 1.1x Kedition;;\n;18369;more_avaritia;更多无尽物品;MoreAvaritia;\nthe-block-box;18370;blockbox;方块百宝箱;The Block Box;\nthe-dirtbike-mod;18371;dirtbike;The Dirtbike Mod;;\nore-seeds;18372;moreseeds;M'Ore Seeds;;\n;18373;GrygrFlzr_GlowstoneWire;Glowstone Wire;;\nmana-visualizer;18374;manavisualizer;Mana Visualizer;;\ncustom-tnt-igniter;18375;customTNTIgniter;Custom TNT Igniter;;\n;18376;PrinterBlock;Printer Block;;\n;18377;Pseudo;伪格雷普·终境之刃;Pseudo Edge;\n;18378;mod_MedievalWeaponsMod;Medieval Weapons Mod;;\njaiz;18379;jaizmod;Jaiz Mod;;\nharder-illage-and-spillage;18380;harder-illage-and-spillage;Harder Illage and Spillage;;\ndagercraft;18381;dagermod;旧超神之路;DagerCraft;DGC\n;18382;deus_ex__npcs_little_pack;杀出重围;Deus Ex Minecraft's Conspiracy;DXMC\nthaumic-staff;18383;thaumicwands;神秘法杖;Thaumic Staff;\nthaumic-bases-mystical-rose-rework;18384;tbmr;神秘基础学玫瑰重制;Thaumic Bases Mystical Rose Rework;\nrhyme-pvz;18385;rhyme;PVZ：润;Rhyme PVZ;PVZR\nichorium-kit;18386;ichoriumkit;Ichorium Kit;;\nreforgotten-relics;18387;forgotten_relics;失落遗物学：重生;ReForgotten Relics;\nin-your-world;18388;in_your_world;In Your World;;\nwarpapocalypse;18389;warptheory;Warp Apocalypse;;\n;18390;mr_withering_heights;Withering Heights: Revamped Wither;;\narcane-arteries-kedition;18391;arcane_arteries;奥法献祭学非官方版;Arcane Arteries Kedition;\nthaumic-tinkerer-unofficial;18392;thaumictinkerer;神秘工匠非官方版;Thaumic Tinkerer Unofficial;TTU\n;18393;tt;话疗;TalkTherapy;TT\nasyncparticles;18394;asyncparticles;异步粒子;AsyncParticles;\n;18395;dragonfly;DragonFly Model Library;;\n;18396;terrain-api;TerrainAPI;;\nmnmutils;18397;mnmutils;MnmUtils;;\nbetterend-elytra-fix;18398;betterendelytrafix;BetterEnd Elytra Fix;;\nplayer-swap;18399;playerswap;Player Swap;;\n;18400;configurable_everything;Configurable Everything;;\nno-hearts-ui;18401;no_hearts_ui;No Hearts UI;;\n;18402;nothighenough;Not High Enough;;\n;18403;areanamedisplay;区域名称显示;AreaNameDisplay;\nold-loading-progress-bar;18404;og-progress-bar;旧版加载进度条;Old Loading Progress Bar;\nraw-tp-chat-command;18405;raw-tpchat;Raw TP Chat Command;;\n;18406;waterplayer;WaterPlayer;;\n;18407;home-utilities;HOME Utilities;;\nmultiplayer-button;18408;multiplayer-button;Multiplayer Button;;\n;18409;bmdmod;离线服白名单修复;WhiteList Fix;wlf\n;18410;kelui;KelUI;;\nbrick-lib;18411;brick_lib;Brick Lib;;\n;18412;noslowmotion;无拘速行;No Slow Motion;\n;18413;minecartsloadchunks;MinecartsLoadChunks;;\nguideme;18414;guideme;GuideME;;\nneoculus;18415;;NeOculus;;\nchatwithmobs;18416;cwm;与生物聊天;ChatWithMobs;CWM\n;18417;kiss-mod;Kiss Mod;;\nextra-armor-info;18418;extra_armor_info;Extra Armor Info;;\nfeature-recycler;18419;featurerecycler;Feature Recycler;;\nore-hammer;18420;ore_hammer,ore_duplication;Ore Hammer;;\n;18421;condensed_blocks;Condensed Blocks Mod;;CBM\nexp-chest;18422;expchest;Exp Chest;;\nsophisticated-backpack-allthemodium-tier;18423;backpack_allthemodium_upgrade;Sophisticated Backpack Allthemodium Tier;;\nsophisticated-storage-allthemodium-tier;18424;storage_allthemodium_upgrade;Sophisticated Storage Allthemodium Tier;;\nthe-amazing-world-of-mobs-renovated;18425;the_amazing_world_of_mobs;The Amazing World Of Mobs! renovated;;\nash-of-sin-tempest;18426;ash_of_sin_tempest;罪业余烬：特恩佩斯特;Ash Of Sin: Tempest;\ngenerations-core;18427;generations_core;Generations-Core;;\n;18428;cauldronfix,cf;铁锅工艺;Cauldronfix;CF\nspelunker;18429;spelunker;Spelunker;;\nlil-tater-reloaded;18430;ltr;Lil Tater Reloaded;;\nno-modern-industrialization-efficiency;18431;miefficiencyremover;No Modern Industrialization Efficiency;;\n;18432;emitechreborn;EmiTechReborn;;ETR\n;18433;better-mipmaps;Better Mipmaps;;\nshowmyskin;18434;show_my_skin;ShowMySkin;;\nrhizo;18435;rhizo;Rhizo;;\ndeimos-fabric-forge-neoforge;18436;deimos;Deimos;;\n;18437;;RaidFix;;\nexcavation;18438;excavation;挖掘;Excavation;\nbasic-end-ores;18439;beo;基础末地矿石;Basic End Ores;BEO\n;18440;portable_crafting;Portable Crafting;;\n;18441;gofish;一起钓鱼吧！(Forge 版);Go Fish Forge;\nsimple-bbq-unofficial;18442;simplebbq;简易烧烤非官方版;Simple BBQ Unofficial;\n;18443;carystanley_extremefarming;Extreme Farming;;\neggy-goodness;18444;EggyGoodness;Eggy Goodness;;\n;18445;slideshow;幻灯片：增强;Slide Show Pro;\nalternative-twilight;18446;alternative_twilight;暮色：另类;Alternative Twilight;AT\n;18447;WarpTheory;神秘扭曲学：非官方版;Warp Theory Unofficial;\n;18448;StardustBlock;Stardust Block / スターダストブロック;;\npepsied-item-glow;18449;mr_item_glow;Pepsi's Item Glow;;\ncreate-bigger-storage;18450;create_bs;Create: Bigger Storage;;\ntacz-create;18451;tacz_c;Create: Timeless and Classics Zero;;\n;18452;barrels;The Barrels Mod;;\nterrific-trash-cans;18453;terrific_trash_cans;Terrific Trash Cans;;\n;18454;sokoban;推箱子;Sokoban;\ncolourful_portals_mod;18455;colourfulportalsmod;Colourful Portals;;\ngud_coolers;18456;gud_coolers;Coolers;;\nsuppergerrie2s-drone-mod;18457;sdrones;Suppergerrie2's Drone Mod;;\ndata-essence;18458;datanessence;Data & Essence;;DnE\n;18459;CSS;圆石与石头盔甲;Cobblestone and Stone Armor;\nnautec;18460;nautec;海蓝科技;NauTec;\n;18461;programmablehatches;可编程仓室;Programmable-Hatches-Mod;PH\nthorny-bush-protection;18462;thornybushprotection;Thorny Bush Protection;;\ngudlib;18463;gud_lib;GudLib;;\nreflex-antilag;18464;reflex;Reflex AntiLag;;\ntfc-nei;18465;TerraFirmaCraftNEIplugin;TFC+ NEI;;\n;18466;qz_miner;爆破连锁;Qz-Miner;\nnolan;18467;nolan;NoLAN;;\n;18468;repeatersound;Repeater Sound;;\naudio-hotkeys;18469;audio_hotkeys;Audio Hotkeys;;\nvoicechat-soundboard;18470;soundboard;Voice Chat Soundboard;;\n;18471;moonmod;The Moon-BTA;;\nftb-ocean-mobs;18472;ftboceanmobs;FTB Ocean Mobs;;\nfake-ores;18473;fakeores;Fake Ores;;\n;18474;;Serene Seasons Backport;Serene Seasons;SS\n;18475;mystical;Mystical;;\ncrackedzombie;18476;crackedzombiemod;CrackedZombie;;\n;18477;cubeworld;Cube World;;\nerror-404-not-found-horror-entity;18478;glitchmanv;Error 404 Not Found (Horror Entity);;\namelet-totem;18479;amelet;Amelet Totem;;\n;18480;ElementalCaves;Elemental Caves;;\n;18481;hMod;hMod;;\ntbone;18482;tbone;TBone;;\nfastfurnace-decreased;18483;fastfurnace;熔炉性能优化--;FastFurnace--;\n;18484;furnace_xp_storage;熔炉经验存储;Furnace XP Storage Reforged;\n;18485;cdnperspective;CDNPerspective 3D;;\n;18486;volumefix;Volume Fix;;\nchorus-fruit-drops-nearby;18487;chorusfruitdropsnearby;紫颂果集中掉落;Chorus Fruit Drops Nearby;\nbetter-first-aid-visual;18488;bfa;Better First Aid Visual;;BFA\ntpms;18489;tpmaster;传送命令·简;Teleport Master Simplified;\n;18490;scoreboard-overhaul-common,scoreboard-overhaul;Scoreboard Overhaul;;\n;18491;yvanchuxiuzhen;原初修真人间界;yvanchuxiuzhen;\ntensura-mysticism;18492;trmysticism;Tensura: Mysticism - Tensura: Reincarnated;;\nfurnection;18493;ls_furniture;LYIVX的家具;Furnection;\n;18494;colourful_portals;Colourful Portals Reimagined;;\n;18495;mr_mingen;MinGen;;\ncraft-of-the-wild-cotw;18496;craft_of_the_wild;Craft Of The Wild;;COTW\npiglands-mod;18497;mod_PigmanMod;The Piglands Mod;;\nvanilla-extension;18498;vanillaextension;Vanilla Extension;;\nfloating-items-mod-datapack;18499;fiet;Floating Items Enchanting Table;;\nbutterfly-mania;18500;ButterflyMania;Butterfly Mania;;\n;18501;rensumo_enchanted_books_addition;Rensumo 的附魔书附加包;Rensumo Enchanted Books Addition;REBA\nkbi-mmd;18502;kbimmd,mod_kbidiscs;KBI More Music Discs;;\nsowdispenser;18503;SowDispenser;SowDispenser;;\nmetalchests;18504;metalchest;Metal Chests;;\njumppad;18505;jumppads;JumpPad++;;\nobsidian-boat;18506;ObsidianBoat;黑曜石船;Obsidian Boat;\nmoderner-beta;18507;moderner_beta;Moderner Beta;;\nthe-animals-plus;18508;AnimalsPlus;Animals Plus;;\ncreate-fluid-stuffs;18509;createfluidstuffs;机械动力：流体附加;Create:Fluid Stuffs;\ncreate-goggle-placement-lieonlion;18510;goggleplacement;Create: Goggle Placement;;\nminecraft-multi-purpose-mod;18511;;minecraft multi-purpose mod;;\nkubeloader;18512;kubeloader;KubeLoader;;KL\nupdatefixerupper;18513;updatefixerupper;UpdateFixerUpper;;UFU\nconfigurable-data-fixers;18514;configurabledatafixers;Configurable Data Fixers;;\nlenient-death;18515;lenientdeath;Lenient Death;;\nshort-circuit;18516;short_circuit;Short Circuit;;\ntensura-kumodesu;18517;kumodesu;Tensura: KumoDesu;;\ntinkers-advanced;18518;tinkers_advanced;前沿匠艺系列;Tinkers' Advanced;TiAc\nmore-structure-processors;18519;moreprocessors;More Structure Processors;;\nquick-stack-to-nearby-chests;18520;quickstack;快速堆叠;QuickStack / Quick Stack To Nearby Chests;\n;18521;qcm;快捷聊天信息;QuickChatMessages;QCM\n;18522;k_all_fix,k_multi_threading;KAllFix;;KAF\nyggdrasilproxy;18523;yggdrasil-proxy-fabric,yggdrasil_proxy_forge;YggdrasilProxy;;\n;18524;sakura_sign_in;樱花签-Fabric;Sakura Sign-In Fabric;SSF\nthaumic-meme;18525;thaumicmeme;神秘吐槽;Thaumic Meme;TM\nstarlightxae;18526;starae;星光xAE2修复;StarlightxAE;\n;18527;mr_dimension_detector;Dimension Detector;;\n;18528;levelclear;Level Clear Mod;;\n;18529;drops-into-shulker;Drops into Shulker;;\nftb-team-dimensions;18530;ftbteamdimensions;FTB Team Dimensions;;\nepicfight-resurrection;18531;cdmoveset;史诗战斗-重生;EpicFight : Resurrection;EFR\nepic-fight-dual-axes;18532;efds;Epic Fight - Dual Axes;;\nepic-fight-dual-spears;18533;efds;Epic Fight - Dual Spears;;\nrealistic-fire-spread;18534;realisticfirespread;Realistic Fire Spread;;\npalehell;18535;palehell;Palehell;;\nelainasbroom;18536;elainabroom;伊蕾娜的扫帚;Elaina's Broom;\nextinction-z;18537;extinction;Extinction Z;;\npomkots-mechs;18538;pomkotsmechs;Pomkots Mechs;;\n;18539;sonicmod;刺猬索尼克;Sonic the Hedgehog Mod;\nreplication;18540;replication;Replication;;\nbetter-sugar-cane;18541;bettersugarcane;更好的甘蔗;Better Sugar Cane;\napogee;18542;apogee;Apogee;;\nbtrultima-tensura;18543;btrultima;BTRUltima - Tensura;;\ndragon-block-c-additions;18544;dbcadditions;Dragon Block C Additions;;\ntwilight-forest-final-boss;18545;twilight_forest_final_boss;Twilight Forest Final Boss;;\ngenerations-structures;18546;generations_structures;Generations Structures;;\noceanic-realms;18547;oceanicrealms;Oceanic Realms;;\nzoomers-extended-apotheosis;18548;ze_apotheosis;Zoomers Extended Apotheosis;;\nthe-god;18549;god;The God;;\nrelics-rpg;18550;relics_rpgs;Relics (RPG Series);;\nmacaws-quark;18551;mcwquark;Macaw's Quark;;\nmacaws-sajevius;18552;mcwsajevius;Macaw's - Sajevius;;\nmacaws-biomes-o-plenty;18553;mcwbiomesoplenty;Macaw's Biomes O' Plenty;;\nmacaws-aurora;18554;mcwaurora;Macaw's - Aurora;;\nmacaws-byg-bwg;18555;mcwbyg;Macaw's Oh The Biomes You'll Go / We've Gone;;\nmacaws-abnormals;18556;mcwabnormals;Macaw's Abnormals;;\nmacaws-modding-legacy;18557;mcwmoddinglegacy;Macaw's Modding Legacy;;\nfancy-spawn-eggs;18558;fancyspawneggs;Fancy Spawn Eggs;;\n;18559;minepainter;雕刻与像素画;Mine Painter;\n;18560;mr_tce;The Car Engine;;TCE\n;18561;pc;PhotoChat;;PC\n;18562;nmsc;不再有服务器配置;NoMoreServerconfig;NMSC\n;18563;queryinfo;QueryInfo;;\nftb-pause-menu-api;18564;ftbpmapi;FTB Pause Menu API;;\n;18565;jeiii;JEI Ingredient Info;;JEIII\nmodifyjs;18566;modifyjs;ModifyJS;;\n;18567;bettershields;更好的盾牌;BetterShields;\n;18568;better-selection;Better Selection;;\n;18569;random_world;随机错乱的世界;Random World;RW\nytgld-better-beacon;18570;better_orb;亚特格拉德的信标;Ytgld : Better Beacon;\ncolorfultools;18571;colorfultools;Colorful Tools;;\ncolorful-armor;18572;colorfularmor;Colorful Armor;;\nibench;18573;ibench;iBench;;\ncrafting-table-on-a-stick;18574;ctoasmod;手持工作台;Crafting Table on a Stick;\n;18575;mochickens;Mo' Chickens;;\nvillager-anti-doors;18576;villager_anti_doors;Villager Anti Doors;;\nliquipacks;18577;liquislots;Liquipacks;;\ncreate-advanced-crafting;18578;create_advanced_crafting;Create: Advanced Crafting;;\ncreate-stone-generators;18579;create_generators;Create: Easy Stone Generators;;\ncreate-cold-sweat;18580;create_cold_sweat;机械动力：冷汗;Create: Cold Sweat;\nanother-unbreakable-enchantment;18581;aue;Another Unbreakable Enchantment;;AUE\npassive-piglins;18582;passivepiglins;Passive Piglins;;\nvoid-miners;18583;voidminers;Void Miners;;\ngwycraft-a-colored-blocks-mod;18584;gwycraft;Gwycraft - A colored blocks mod;;\ncreative-sandbox;18585;creative_sandbox;创造沙盒;Creative Sandbox;\nsignboard-preview;18586;signboardpreview;告示牌预览;SignBoard Preview;SBP\n;18587;riftwind;圣痕裂风;Sacred Riftwind;\nhexoverpowered;18588;hexoverpowered;HexOverpowered;;HexOP\nhexflow;18589;hexflow;HexFlow;;\n;18590;mr_dragonkind_evolved;Dragonkind Evolved | End Battle Reborn;;\n;18591;chihaya_anno;MyGO!!!!!：千早爱音的前置装甲方案;Chihaya Anno's steel sheet;\nmoremekasuitmodules;18592;moremekasuitmodules;更多 MekaSuit 模块;MoreMekaSuitModules;\neternal-attributes;18593;eternal_attributes;Eternal Attributes;;\n;18594;advanceddispensers;Advanced Dispensers;;\neasy-pickings;18595;easypickings;Easy Pickings;;\nrelics-in-chaos;18596;relics_in_chaos;Relics in Chaos;;\narchers-expansion;18597;archers_expansion;Archers Expansion (More RPG Classes);;\nwitcher-rpg-class;18598;witcher_rpg;Witcher (More RPG Classes);;\nforcemaster-rpg-class;18599;forcemaster_rpg;Forcemaster (More RPG Classes);;\ndeath-knights;18600;death_knights;Death Knights (RPG Series);;\n;18601;oretogo;矿石快取 [测试版];Ores To Go;ORTG\nimmersive-slumber;18602;immersiveslumber;沉浸睡眠;ImmersivelySleep;IS\nbountifulbaubles-reforked;18603;bountifulbaubles;丰富的饰品：重铸;BountifulBaubles:Reforked;\n;18604;anviluses;铁砧使用次数显示;Anvil Uses;\n;18605;lucidity;我的视界;Lucidity;\n;18606;moremekasuitunits,moremekasuitmodules;通用机械：更多单元;Mekanism: More Modules;MMM\n;18607;betterarmorswap;Better Armor Swap;;\n;18608;advancedgenetics;Advanced Genetics;;\nthe-man-from-the-shadow;18609;man_from_the_shadow;The Man From The Shadow;;\n;18610;spex;Space Race;;\nmythological-craft-addon;18611;mc;Mythological Craft;;\n;18612;gojidraw,goji;Kaiju Craft;;\n;18613;op_asa;ワンピースアドオン / ONE PIECE addon;;\narcanmira;18614;arcanmira;Arcanmira;;\ncomparties-reborn;18615;comparties;组队兼容：重生;Comparties Reborn;\ntape-stop;18616;tapestop;停碟;Tape Stop;\npersistent-inventory-search;18617;persistentinventorysearch;Persistent Inventory Search;;\n;18618;nompmenu;No Multiplayer Menu / Nompmenu;;\n;18619;litematica-enderchest-materials;Litematica Enderchest / Litematica Enderchest Materials;;\n;18620;mobprotect;Mob Protect;;\nonly-bags;18621;only_bags;Only Bags;;\nthe-broken-script-official;18622;thebrokenscript;The Broken Script;;TBS\ntorojimas-buildhelper;18623;buildhelper;Torojima's Buildhelper;;\nexpanded-armory-legacy;18624;exparmory;扩展装备：传承;Expanded Armory: Legacy;\nloquat-forge;18625;loquat;Loquat 🧩;;\n;18626;waterbeds;Waterbeds;;\n;18627;customhealthhud;CustomHealthHUD;;\n;18628;eg_fix_horizontal_camera_lag;Fix Horizontal Camera Lag;;\ncreate-cyber-goggles;18629;create_cyber_goggles;机械动力：赛博护目镜;Create: Cyber Goggles;CCG\nmekanism-elements;18630;mekanismscience,mekanismelements;通用机械：元素;Mekanism Elements;\nbots-lib;18631;bots_lib;Bot's Lib;;\n;18632;nls;无加载界面;No Loading Screen;NLS\nbotaniaoverpowered;18633;botania_overpowered;BotaniaOverpowered;;\n;18634;simple_survive;蓟县生存;Simple ? Survival;\n;18635;cwm;卡通武器;Shadow's Cartoon Weapons/Cartoon Weapons;\n;18636;pvz;HTPVZ 重构;HungTeen's Plants vs. Zombies Reconstruction;HTPVZR\nauto-pillar-generator;18637;apg;一键柱子生成;Auto Pillar Generator;APG\nvanilla-delight;18638;vanilladelight;Vanilla Delight;;\njade-vs;18639;jade_vs;Jade VS;;\n;18640;custom_ender_dragon;Draconic Ascension - Custom Ender Dragon Fight;;\nvics-modern-warfare-patched;18641;;维克的现代战争 (修复版);Vic's Modern Warfare (Patched);VMW\nwaterframesbackported;18642;waterframes;WATERFrAMES: Backported Edition;;\n;18643;sihywtcamc-unofficial-port;So I heard you were talking crap about Minecraft's combat? - 非官方移植;sihywtcamc unofficial port;sihywtcamc\ntweakeroo-update-port;18644;tweakeroo;Tweakeroo Update Port;;\nxkballs-auto-translate;18645;xkball_s_auto_translate;xkball的自动汉化;xkball's Auto Translate;\n;18646;play_time_statistics;玩家在线时间统计;PlayTimeStatistics;\nblock-replacement-mc1-7-10;18647;blockreplace;Block Replacement;;\n;18648;mekanismqq;Mekanism: Quantum Quarries;;\n;18649;ecoaeextension;ECO AE Extension;;\nconfluence;18650;confluence,terra_furniture,terra_guns;汇流来世;Confluence: Otherworld;\nconfluence-crafting;18651;confluence_cft;汇流工坊;Confluence Crafting;CCft\n;18652;createplus;创造Plus;CreativePlus;CP\n;18653;trinkets-client-optional;Trinkets Client Optional;;\ndata-packs-helper;18654;datapacks;数据包助手;Data Packs Helper;\n;18655;gokiskills;技能专精;GokiSkills;\n;18656;cleaner;我是小小清洁工;Cleaner;\ntconstructjs;18657;tconstruct_js;有头脑的工匠;TConstructJs;\nmaid-storage-manager;18658;maid_storage_manager;女仆仓管;Maid Storage Manager;\n;18659;mystia_izakaya;夜雀食堂;Mystia's Izakaya;\nlush-scented-paradise;18660;lushscentedparadise;芬芳茶园;Lush Scented Paradise;LSP\n;18661;paunch;Paunch;;\nhold-my-items;18662;hold-my-items;Hold My Items;;\nachievement-extreme;18663;AchievExtrem;Achievement Extreme;;\nadvancement-extreme;18664;advancementextreme;Advancement Extreme;;\nrecipe-machine-stages-zs-js;18665;recipemachinestage;Recipe Machine Stages ZS/JS;;RMS\nconfigurable-keepinventory;18666;keepinventory;Configurable KeepInventory;;\nbetter-chat;18667;betterchat;Better Chat;;\nmekalus-oculus-fork-with-fixed-mekanism-mekasuit;18668;;Mekalus;;\nview-emc;18669;viewemc;View EMC;;\nfortitude;18670;fortitudemod;Fortitude;;\nexperience-ore;18671;ExperienceOre;经验矿石;Experience Ore;\npassive-torch;18672;passivetorch;Passive Torch;;\nproductive-metalworks;18673;productivemetalworks;Productive Metalworks;;\nbetter-leveling-respawn;18674;betterleveing;更好的升级：重铸;Better Leveling: Reforge;BLR\ndrive-by-wire;18675;drivebywire;线缆控制;Drive-By-Wire;\nspirit-of-fight;18676;spirit_of_fight;战魂;Spirit of Fight;\ngrim-bleak;18677;grim_and_bleak;Grim & Bleak;;\ncarrymyboss;18678;carrymyboss;Carrymyboss;;\nranged-damage-limit;18679;rangeddamagelimit;远域挡招/远程伤害保护;Ranged Damage Limit;\nmagichem;18680;magichem;魔法炼金;MagiChem;\niglee-library;18681;igleelib;Iglee's Library;;\ncomponent-model-hider;18682;component_model_hider;Component Model Hider;;\n;18683;aicomm,aichat;AI对话-自定义AI来源;AIComm;AC\n;18684;dontcuttripwire;Don't Cut Tripwire;;\nextra-achievements;18685;extraachievements;Extra Achievements;;\nkeybindjs;18686;keybindjs;KeyBindJS;;\n;18687;autototem;Autototem;;\n;18688;totemcooldown;Totem Cooldown;;\nsound-physics-legacy;18689;;物理声效：遗产;Sound Physics Legacy;\nastatine;18690;astatine;砹;Astatine;\n;18691;tnt_parrot;TNT鹦鹉;TNTparrot;\nmarblegates-exotic-enchantment-reborn;18692;flowingagony_reborn;白门的奇异附魔：苦痛长河：重生;MarbleGate's Exotic Enchantment: Flowing Agony: Reborn;\n;18693;MMMLib,lmmx;LittleMaidMob kai;;\n;18694;musicdiscs;Beta 1.7.3 Music Discs;;\n;18695;spawneggs;刷怪蛋;Spawn Eggs;\n;18696;SC0_Wings;Survival Wings;;\ncreate-waystones-recipes;18697;create_waystones_recipes;Create Waystones Recipes;;\nmekanistic-routers;18698;mekanisticrouters;Mekanistic Routers;;\njust-healerblock;18699;hb;Just HealerBlock;;HB\nftb-stuff-things;18700;ftbstuff;FTB Stuff & Things;;\ninfinibows;18701;InfiniBows;InfiniBows;;\ntacz-labs;18702;taczlabs;TaCZ: Labs;;TL\n;18703;mineshock_cybernightmare;网络奇兵;MineSHOCK: CyberNightmare;SSMC\nmake-a-wish;18704;make_a_wish;Make a Wish;;\narchaeological-research-exploration;18705;archaeological_research_exploration;考古研究：探寻;Archaeological Research：Exploration;ARRE\nforgotten-nunchakus;18706;fn;Forgotten Nunchakus;;FN\ncreate-big-cannons-advanced-technologies;18707;cbc_at;机械动力火炮：先进技术;Create Big Cannons: Advanced Technologies;CBCAT\nquality-food;18708;quality_food;Quality Food;;\nsnekcraft;18709;snekcraft;Snekcraft;;\nhearth-and-harvest;18710;hearthandharvest;暖灶丰年;Hearth and Harvest;\natp-miner;18711;atp_miner;ATP高能连锁;ATP miner;ATPM\n;18712;enchanted;更好的附魔;;ICTOG\n;18713;kuchiyoseno;通灵之术;Kuchiyose;\nwjys;18714;wjys;无尽药水;WJYS;\nschematicpreview;18715;schematicpreview;原理图预览;SchematicPreview;\n;18716;worldtools;世界工具;World Tools;\n;18717;collect-emerald;聚灵石;Collect Emerald;CE\nsakura-tinker;18718;sakuratinker;魂樱工匠;Sakura Tinker;\n;18719;appspec;Applied Spectrometry;;appspec\nheart-crystal;18720;hearts;心之水晶;Heart Crystal;\ndefault-skin;18721;defaultskin;Default Skin;;\nletyourfriendeating;18722;letyourfriendeating;让你的朋友吃！;Let Your Friend Eating!;\n;18723;extra_information_enchanted;额外附魔信息显示;extra information enchanted;\nftb-jei-extras;18724;ftbjeiextras;FTB JEI Extras;;\nshader-reload;18725;shaderreload;Shader Reload;;\nsimple-menu;18726;simplemenu;Simple Menu;;\n;18727;asahi;Asahi;;\nkeybindspurger;18728;keybindspurger;KeybindsPurger;;\niguana-lib;18729;iguana_lib;Iguana Lib;;\nmap-making-tools;18730;mapmakingtools,MapMakingTools;地图制作工具;Map Making Tools;\n;18731;simpocotton;简单棉花;SimpoCotton;\nturkeyutil;18732;TurkeyUtil;TurkeyUtil;;\ndrcyanos-wonderful-wands-wizarding-robes;18733;wonderfulwands;Dr. Cyano's Wonderful Wands & Wizarding Armor;;\ndragon-survival-griffin-addon;18734;griffin_ds;Dragon Survival - Griffin Addon;;\nrpg-skillpower;18735;rpgsp;RPG_SkillPower;;\nunknown-null;18736;unknown;Unknown Null;;\nslime-origin;18737;slimeorigin;Slime Origin;;\n;18738;slimeorigin;Slime Origin;;\nslime-origin-remastered;18739;slimeorigin;Slime Origin Remastered;;\nalchemy-kingdom;18740;ak;炼金王国;Alchemy Kingdom;AK\ntcondiadema;18741;tcondiadema;匠魂领域;TconDiadema;\n;18742;reterraforged;地形重锻：重置;ReTerraForged;RTF\ntome-of-knowledge-sharing;18744;knowledgetome;Tome of Knowledge Sharing;;\n;18745;GregsLighting,mod_GregsLighting;Greg's Lighting;;\nmacaws-terraformersmc;18746;mcwterraformersmc;Macaw's TerraformersMC;;\n;18747;decorativemarble;Decorative Marble;;\n;18748;decorativechimney;Decorative Chimney;;\n;18749;;重铸：编辑器;Reforge Editor;\n;18750;;重铸;Reforge;\narchers-helicopter;18751;archers_helicopter;阿辰的直升机;Archer's Helicopter;acheli\nfels-machine-guns-wwi;18752;fels_mgr;Fel's Machine Guns WWI;;\nfels-machine-guns-wwii;18753;fels_mgrwwii;Fel's Machine Guns WWII;;\nsteamatic-locomotion;18754;steamatic_locomotion;Steamatic Locomotion;;\ndungeon-revival;18755;dungeon_revival;Dungeon Revival;;\nnew-order;18756;new_order;The New Order;;\ncreate-more-recipes-addon;18757;additional_recipes;Create: More Recipes Addon;;\ncbc-nuclear;18758;cbc_nukes;CBC: Nuclear;;\nsmarter-zombies;18759;links_zombie_awareness;Smart Zombies;;\n;18760;better_dungeons;Better Dungeons: STRAWBERRY EDITION;;\n;18761;fm;FeaturesManager;;FM\nlibguifoxified;18762;libgui;LibGuiFoxified;;\nnovelty;18763;novelty;Novelty API;;\nvalkyrien-relogs;18764;valkyrienrelogs;Valkyrien Relogs;;\n;18765;section31;简单监控;Section 31;\nchatalert;18766;chatalert;ChatAlert;;\naaa-particles-world;18767;aaa_particles_world;AAA Particles: World;;\npick-up-tnt;18768;pick_up_tnt;Pick Up TNT;;\nymadditions;18769;ymadditions;YM-Additions;;\nimmersive-machinery;18770;immersive_machinery;Immersive Machinery;;\nxp-synthesiser;18771;xp_synthesiser;XP Synthesiser;;\noredictionaryconverter;18772;fodc;Ore Dictionary Converter;;\ncbc-modern-warfare;18773;cbcmodernwarfare;机械动力火炮：现代战争;CBC: Modern Warfare;CBCMW\nshulker-charm;18774;shulkercharm;Shulker Charm;;\nvillager-license;18775;vilicense;村民证件;Villager License;\ndescription-tags;18776;descriptiontags;Description Tags;;\nharvestwithdispenser;18777;dcs_dispenser;HarvestWithDispenser;;\nsound-physics-fabric;18778;soundphysics;物理声效 Fabric 版;Sound Physics Fabric;\ncleanroom-relauncher;18779;cleanroom-relauncher;Cleanroom Relauncher;;\nfast-item-frames;18780;fastitemframes;Fast Item Frames;;FF\nhammering;18781;hammering;Hammering;;\nvanity-elementals;18782;elemental;Vanity: Elementals;;\nfmprojecte-advanced-alchemy-fmpeaa;18783;PEAA;FMProjectE Advanced Alchemy;;FMPEAA\npeex;18784;peex;PEEX;;\nanimal-origins;18785;animal_origins;Animal Origins;;\nnarutoto-ninja-world;18786;narutoto;Narutoto: Ninja World;;NNW\nthe-ender-scrolls-skyrimcraft-the-revival;18787;skyrimcraft;末影卷轴 - 天际工艺;The Ender Scrolls - Skyrimcraft;\nkingdom-of-rin-mobs;18788;conskeleton;Kingdom of RIN | Mobs;;\nscape-and-run-parasites-mutant-beasts;18789;srpmutantbeasts;逃逸：寄染突变体;Scape and Run: Parasites Mutant Beasts;SRPMB\nalexscavesexemplified;18790;alexscavesexemplified;Alexs Caves Exemplified;;\nakutolib;18791;akutolib;AkutoLib;;\n;18792;glassnetworking;Glass Networking;;\n;18793;alwaysmoreitems;Always More Items;;AMI\n;18794;mostlymodernrecipes;Mostly Modern Recipes Station API Edition;;\ntoomuchloot;18795;TooMuchLoot;TooMuchLoot;;\ntoomuchtime;18796;TooMuchTime;TooMuchTime;;\ncs2-box;18797;csgobox;CS2箱子;CS2 Box;\nedit-mob-drops;18798;editmobdrops;Edit Mob Drops;;\n;18799;m3t;M3 Tweaker;;M3T\ntea-aroma;18800;tea_aroma;茶香;Tea Aroma;\nui-queue;18801;ui_queue;UI Queue;;\nkamii-s-script-configuration;18802;;Kamii's Script Configuration;;\n;18803;npcvisible;CustomNpcs-VisibleCondition;;CNPC-VC\noverpowered-inventory;18804;powerinventory;Overpowered Inventory;;\nbbrains-umfixes;18805;;[BBrains] Unsupported Mods Fixes;;\n;18806;gf;GeneralFeatures;;GF\nfoodstats;18807;foodstats;食物增强;foodstats;\nhxchud;18808;hxchud;HxCHUD;;\nsorted-enchantments;18809;sortedenchantments;排序附魔;Sorted Enchantments;\nassassins-creed-parkour-mod;18810;ParkourMod;刺客信条跑酷;Assassin's Creed Parkour Mod;\ncritter-armory;18811;critterarmory;Critter Armory;;\nthe-harvest;18812;the_harvest;The Harvest;;\nbetter-minoshroomtaur;18813;better_minoshroomtaur;Better 米诺菇;Better Minoshroomtaur;\nmoobloom;18814;moobloom;哞菇与哞花;Mooblooms and Mooshrooms;\nmooblooms-and-mooshrooms-bop-edition;18815;mooshroombloombop;哞菇与哞花BoP版;Mooblooms and Mooshrooms (BoP Edition);\nthe-pure-suffering-mod;18816;puresuffering;The Pure Suffering Mod;;\ndooglamoo-jr-archaeology;18817;dooglamoojuniorarchaeology;Dooglamoo 的考古学;Dooglamoo Jr. Archaeology;\nlegionary;18818;legionary;Legionary;;\nvoiceless-survival;18819;ezvcsurvival;寂静求生;Voiceless Survival;\nfabulous-blades;18820;fabulous_blades;Fabulous Blades;;\n;18821;rtt;重置时间科技;Reset Time Technology;RTT\nprism-plan;18822;prismplan;棱镜计划;Prism Plan;PP\nae2cc-bridge;18823;ae2cc;AE2CC Bridge;;\nae2-mousetweaks-fix;18824;ae2mtfix;AE2 + Mouse Tweaks Fix;;\nae2-qol-recipes;18825;ae2qolrecipes;AE2 QoL Recipes;;\ncyberspace;18826;cyberspace;Cyberspace;;\n;18827;glass_item_frame;透明物品展示框;Glass Item Frame;GIF\ndisplayed;18828;display;展示;Display;DIS\nengineered-compatibility;18829;engineeredcompatibility;Engineered Compatibility;;\ncreate-abyss-catalysis;18830;sculkcatalyticchamber;机械动力：深渊催化;Create: AbyssCatalysis;CnAC\n;18831;survival;连锁挖矿数据包;KuKuSurvival;KKS\n;18832;worlddownloader;世界下载器旧版 Forge;WorldDownloaderLegacy;\nlevel-up-rpg-skills-more;18833;levelup;Level Up! - RPG Skills & More;;\n;18834;betterpaties;BetterParties;;\n;18835;creeper-healing;Creeper Healing;;\nrender-control;18836;RenderControl;渲染控制;Render Control;\nchemical-tweaker;18837;chemicaltweaker;Chemical Tweaker;;\nhealth-bar;18838;healthbar;Health Bar;;\nsee-the-rarities-str;18839;str;查看稀有度;See The Rarities;STR\ncraftpilot;18840;craftpilot;Craftpilot;;\n;18841;game_of_reborn;复活赛;Game of Reborn;\nrough-blade;18842;legendary_slash;无锋道;Rough Blade;RB\n;18843;dota;Dota 2;;\n;18844;appledog;苹狗;Appledog;\nsuperb-warfare;18845;superbwarfare;卓越前线;Superb Warfare;SBW\nvampire-golem;18846;vi;吸血傀儡;Vampire Golem;\naetherworks-extended-life;18847;aetherworks;Aetherworks Unofficial Extended Life;;\npomkots-mechs-extension-pack;18848;pomkotsmechsextension;Pomkots Mechs Extension Pack;;\n;18849;monkeyconfig,monkey-utils;Monkey Utils;;\nsome-more-blocks;18850;somemoreblocks;Some More Blocks;;\nrepurposed-structures-create-dynamic-village;18851;repurposed_structures_dynamicvillage_compat;Repurposed Structures - Create: Dynamic Village Compat;;\nprojecte-advanced-alchemy;18852;PEAA;ProjectE Advanced Alchemy;;PEAA\nnff-girls-grimoire-of-gaia-extension;18853;nffgirlsgaia;NFF: Girls - 盖亚魔典扩展;NFF: Girls - Grimoire of Gaia Extension;\nreplicore;18854;replicore;复制核心;RepliCore;ReC\ncreate-reforged-foundations;18855;create_reforged_foundations;Create: Reforged Foundations;;\n;18856;real_compost;下蹲堆肥;Real Compost;\n;18857;jgfe;我踏马吃吃吃！;Just Go Fxxking Eat;JGFE\nbetter-experience;18858;better_experience;更好的体验;Better Experience;BE\n;18859;player_drop_head;玩家掉落头颅;Player Drop Head;\nthdilos-fox-origin;18860;thorigins;ThDilos的狐狸起源;ThDilos Fox Origin;\nnarutofix;18861;narutofix;火影修复;NarutoFix;\nsbr-i-need-more-enchantment;18862;sbr_inme;重锋：施魔更哥;SlashBlade Resharped: I need more Enchantment!!!;\nconfigurable-health;18863;confighealth;可配置的生命值;Configurable Health;\ncreate-better-fps;18864;createbetterfps;Create Better FPS;;\ncreate-security-program;18865;create_security;Create: Security Program;;\nfurnies;18866;furnies;Furnies;;\ncobblefurnies;18867;cobblefurnies;CobbleFurnies;;\nthdilos-fox-origin-expanded;18868;thorigins2;ThDilos的狐狸起源拓展;ThDilos Fox Origin Expanded;\npotion-sauce;18869;potionsauce;Potion Sauce;;\ncopious-dogs-reforged;18870;copiousdogs;Copious Dogs Reforged;;\n;18871;slotcustomizationapi;Slot Customization API;;\nhqm-keybind;18872;hqmkeybind;HQM Keybind;;\nzoomer-simple-zoom;18873;zoomermod;Zoomer - Simple Zoom;;\n;18874;better TC combat;更好的战斗：匠魂扩展;;\ncurioenchantment;18875;curio_enchantment;饰品附魔;CurioEnchantment;\nwall-jump-continuation;18876;;Wall-Jump. Continuation!;;\narmor-stand-shift-swap;18877;armorstandshiftswap;Armor Stand Shift Swap;;\nmoveui-forged;18878;moveui;Move UI Forged;;\nharvest-level-config;18879;harvestlevelconfig;Harvest Level Config;;\nbrewing-helper;18880;brewinghelp;Brewing Helper;;\ntheatrical;18881;theatrical;Theatrical;;\netlib;18882;emberthral;淬火煉刀通用库;Emberthral;ET\n;18883;tinkers_rapiers;匠魂西洋剑：延续;Tinkers' Rapier: Continuation;\ntacz-addon;18884;taczaddon;TaCZ addon;;\n;18885;heartcanister;心之容器;Heart Canisters;\ngunswithoutroses-expansions;18886;gwrexpansions;枪炮“没”瑰：扩展;Guns Without Roses : Expansions;GWRE\nati-structures-datapack-vanilla;18887;ati_structuresv;ATi Structures - Default / ATi Structures - Vanilla Edition;;\ncreate-backpack-pixel;18888;backpack_pixel;机素背包;Create: Backpack Pixel;\ncrazy-alloy;18889;cl;Crazy Alloy;;\nspellbound-enchantments;18890;spellbound;Spellbound Enchantments;;\n;18891;cs;自定义树苗;CustomSapling;CS\n;18892;DYTL;百年滇越行;Yunnan-Vietnam Railway;DYTL\nultramarine-rekindled;18893;ultramarine;群青：重明;Ultramarine Rekindled;\nfortunas-anvil;18894;fortunas_anvil;Fortuna's Anvil;;\ncreate-deep-dark;18895;create_deep_dark;Create: Deep Dark;;\ncreate-ranged;18896;create_ranged;Create: Ranged Compat;;\ncreate-enlightend;18897;create_enlightend;Create: Enlightend;;\ncreate-winery;18898;create_winery;Create: Winery;;\ncreate-filters-anywhere;18899;createfiltersanywhere;Create: Filters Anywhere;;\ncreate-cheese;18900;create_cheese;Create: Cheese Factory;;\ncreate-copper-zinc;18901;create_copper_and_zinc;Create: Copper & Zinc;;\ncreate-ultimate-factory;18902;create_ultimate_factory;Create: Ultimate Factory;;\n;18903;findit;Findit - GTNH;;\nlyivxs-core;18904;ls_core;LYIVX's Core;;\nexclusions-lib;18905;exclusions_lib;Exclusion Lib;;\njson-paintings;18906;jsonpaintings;JSON Paintings;;\nkeybind-unconflict;18907;keybindunconflict;Keybind Unconflict;;\napothic-compats;18908;apothic_compats;Apothic Compats;;\napothic-materials;18909;apothic_materials;Apothic Materials;;\npepsied-easy-multi-item-sorter;18910;mr_easy_multiitemsorter;Pepsi's Easy Multi Item Sorter;;\ntetra-apotheosis-fix;18911;tetra_apotheosis_fix;Tetra Apotheosis Fix;;\nron-timer;18912;rontimer;RON Timer;;\nempty-air-bubbles;18913;empty_air_bubbles;Empty Air Bubbles;;\nbundles-throw-potions;18914;btpro;Bundles Throw Potions;;\nftb-team-bases;18915;ftbteambases;FTB Team Bases;;\nraworesmelting;18916;raworesmelting;RawOreSmelting;;\ndpt;18917;dpt;Default Potion Tweaks;;\npotion-effect-enchantment;18918;potion_effect_enchantments;Potion Effect Enchantments;;\nmythical-origins;18919;mythicalorigins;Mythical Origins;;\nassorted-armaments;18920;assorted_armaments;百般武艺;Assorted Armaments;\n;18921;the_skeletons;The Skeletons;;NBSK\nspanksters-craft;18922;spanksters_mod;SpanksterCraft;;\nheavenlyabyss;18923;azurite;Heavenly Abyss / Azurite RPG / Celestial Runes;;\nslashblade-twin-attack;18924;sbtwinattack;拔刀剑：连携攻击;SlashBlade Twin Attack;STA\ndungeon-crawl-dimensional-patch;18925;dc_dp;Dungeon Crawl - Dimensional Patch;;\napothic-supplementaries-enchanting;18926;apothic_sups_enchanting;Apothic Supplementaries - Enchanting;;\napothic-amendments-enchanting;18927;apothic_amendments__enchanting;Apothic Amendments - Enchanting;;\napothic-mutant-heads;18928;apothic_mutant_heads;Apothic Mutant Heads;;\napothic-science-moa-stats;18929;apothic_science_moa_stats;Apothic Science: MOA Stats;;\nye-gamol-chattels-reborn;18930;yegamolchattels;Ye Gamol Chattels Reborn;;\n;18931;cot_complement;Cot补充;Cot Complement;CC\n;18932;ol;配置列表;OptionsList;OL\nmme;18933;mme;我的更多附魔;My More Enchantments;MME\n;18934;little_enchants;一些小附魔;Some Little Enchant;SLE\nminepulse;18935;minepulse;一脉相挖;Mine Pulse;\n;18936;commandconfigurator;命令配置器;Command Configurator;CCRT\n;18937;;重铸：魔径通幽;Reforge: A Magic Way;\ntrinkets-and-baubles-reforked;18938;trinketsandbaubles;饰品与小玩意：重铸;Trinkets and Baubles Reforked;\n;18939;mace_expand;绵阳的重锤拓展;SunSheep's MaceExpand;SME\nastral-dimensions-create-compats;18941;create_astral_dimensions_compat;Astral Dimension Create Compats;;\ncbc-more-shells;18942;cbcmoreshells;机械动力火炮：更多炮弹;CBC More Shells;CBCMS\ncreate-tools-n-weapons;18943;createtoolsnweapons;Create: Tools n' Weapons;;\ncreate-factory-logistics;18944;create_factory_logistics;机械动力：流体物流;Create Factory Logistics;\nwiidudes-custom-origin-mod;18945;wiicustomorigins;Wiidude's Custom Origins;;\nwolfkin-origin;18946;wolfdr;Wolfkin Origin;;\nhorrorgins;18947;horrorgins;Horrorgins;;\n;18948;mr_entropys_chosen;Origins: Entropy's Chosen;;\naspects-origins-addon;18949;aspects;Aspects;;\n;18950;origins_minus;Origin Minus;;\nhapi-pillager;18951;hapipillager;Hapi Pillager;;\n;18952;stoppicking;Stop Picking;;\npepsied-super-hopper;18953;mr_super_hopper;Pepsi's Super Hopper;;\npepsied-item-catcher;18954;mr_item_catcher;Pepsi's Item Catcher;;\npepsied-auto-harvest;18955;mr_auto_harvest;Pepsi's Auto Harvest;;\n;18956;bulkcompost;Bulk Compost;;\ncentered-item-frames;18957;centered-item-frames;物品展示框居中;Centered Item Frames;\nundergarden-botany-pots-compats;18958;undergarden_botany_pots_compats;Undergarden Botany Pots Compats;;\n;18959;mr_simple_magnet;简单磁铁;Simple Magnet;\na-magic-way;18960;amw;A Magic Way;;\nbbl-flight-blocks;18961;flightblocks;BBL Flight Blocks;;\nmjs-raw-ores;18962;raw_ores;MJ's Raw Ores Backport;;\ncreate-gunpowder-crafting;18963;potassium_and_sulfurs;Potassium & Sulfur's Gunpowder Mod;;\nluminax;18964;luminax;Luminax;;\ncable-facades;18965;cable_facades;Cable Facades;;\nezstorage-new;18966;;简易存储;Simple Storage;\n;18967;tellmewhatineed;问君何所需;TellMeWhatINeed;TMWIN\nelrols-gui-elevator;18968;guielevator;Elrol's GUI Elevator;;\nbackrooms-found-footage;18969;spb-revamped;Minecraft Backrooms Found Footage;;\nexposure-polaroid;18970;exposure_polaroid;Exposure: Polaroid;;\nsurpass;18971;surpass;超限;Surpass;\nfallingalchemy;18972;fallingalchemy;坠落炼金;FallingAlchemy;\n;18973;mcopenp2p;更好的P2P联机;MCOpenP2P;op2p\nfluxloading;18974;fluxloading;FluxLoading;;FL\ninventory-swapping;18975;hotbarclear;Inventory swapping;;\nseamless-trading;18976;seamlesstrading;Seamless Trading;;\nsophisticated-auto-walk;18977;autowalk;Sophisticated Auto Walk;;\nmmmmmmmmmmmmpatch;18978;m12patch;试验假人补丁;MmmMmmMmmMmmPatch;\nglobal-datapacks;18979;global-datapack;Global Datapacks;;\nworld-guard;18980;worldguard;World Guard;;\nmore-murphy-pumpkins;18981;morepumpkins-mod;更多南瓜;morepumpkins;MP\ntetradelight;18982;tetradelight;Tetradelight;;\n;18983;wenyan_nature;吾有一术;WenyanProgramming;\ndragon-mounts-2;18984;;Dragon Mounts 2;;\nheroes-of-might-and-magic-3;18985;homm3;Heroes of Might and Magic 3;;HoMM3\nanother-changed;18986;a_changed;Another Changed Mod;;\nrandomly-tp;18987;randomly_tp;随意传送;Freely/Randomly TP;\nillager-war-trireme;18988;illager_war_trireme;Illager War Trireme;;\nhominid;18990;hominid;Hominid;;\nanvians-lib;18991;anvianslib;Anvian's Lib;;\n;18992;creo-api;Creo API;;\namblekit;18993;amblekit;AmbleKit;;\n;18994;ForgeAuth;ForgeAuth;;\nnocubes-better-fletching-table;18995;nocubesbetterfletchingtable;NoCube's Better Fletching Table;;\nbetter-fletching-table;18996;bft;更好的制箭台;Better Fletching Table;BFT\njsconfig;18997;jsconf;JasonConfig;;\n;18998;lightspeedboat;光速船;Lightspeed Boat;\nnomissingmods;18999;NoMissingMods;NoMissingMods;;NMM\nurban-decor;19000;urban_decor;Urban Decor;;\nnospawnzone;19001;nospawnzone;No Spawn Zone;;\nkeybind-bundles;19002;keybindbundles;KeyBind Bundles;;\n;19003;tcneiadditions;Thaumcraft NEI Additions / TCNEIAdditions;;\ncloudflared;19004;cloudflared;Cloudflared;;\n;19005;aikoyoritweaks;Aikoyori Tweaks;;\nflowingfluidfixer;19006;flowingfluidfixer;FlowingFluidFixer;;\nabsolutely-stuffed-uncapped-hunger-saturation;19007;absolutely_stuffed;Absolutely Stuffed! Uncapped Hunger/Saturation;;\ngluttony-oversaturation-overeat;19008;mandatory;Gluttony;;\novereat;19009;overeat;overeat;;\noversaturation;19010;oversaturation;Oversaturation;;\n;19011;;星辰幻想3:永恒传说;XINGCHEN RPG 3;\netheria;19012;etheria;Etheria;;\n;19013;tconbreakthrough;匠魂突破;TconBreakthrough;\nlegends-of-slugterra;19014;slugterra;Legends Of Slugterra;;\ngussdx-decoration;19015;jammy_furniture;Jammy Furniture;;\n;19016;mr_talentium;Talentium;;\nprojectiles;19017;projectiles;Projectiles;;\nspace-patrol-delta;19018;space_patrol_delta;Space Patrol Delta;;S.P.D.\n;19019;mtr;纸板箱特色我的世界铁路;NeoMTR;\n;19020;contigency_contract;轻量合约;Contigency Contract Lite;\nassorted-lib;19021;assortedlib;Assorted Lib;;\ntaterlib;19022;taterlib;TaterLib;;\n;19023;client-flight-mod;客户端飞行;Client Flight Mod;\nnocapes;19024;nocapes;NoCapes;;\ninventory-sorter-configurable;19025;inventorysorter;Inventory Sorter (Configurable);;\nsmartcursor;19026;SmartCursor;智能指针;SmartCursor;\nwither-spawn-animation;19027;wither_spawn_animation;Wither Spawn Animation;;\n;19028;boomenchantment;Boom Enchantment;;\nprioritiser;19029;Prioritiser;Prioritiser;;\nengineered-schematics;19030;engineered_schematics;Engineered Schematics;;\n;19031;mojangfixstationapi;MojangFix Station API Edition;;\n;19032;nedologin;Nedologin;;\n;19033;entityrectify;EntityRectify;;\n;19034;mod_SprintKey,SprintKey;Sprint on CTRL;;\nkill-counter-kill-streak-announcer-mob-death;19035;killcounter;连杀计数;Kill Counter;\ncalm-down-dog;19036;calmdowndog;Calm down, dog!;;\nkeybinding-hider;19037;key_binding_hider;按键绑定隐藏器;KeyBinding Hider;\ndarkerer;19038;darkerer;Darkerer;;\nno-more-sleep-problems;19039;nmsp;No More Sleep Problems;;NMSP\nconfigbackuper;19040;configbackuper;Config Backuper;;\n;19041;whatgugumod;什么咕咕模组;WhatGuGuMod;\n;19042;aether_tinker;天选工匠;Aether Tinker;AT\ninhrtburys-gilding;19043;gildingmod;Inhrtbury 的镀金;Inhrtbury's gilding;\n;19044;tree_miner;矿树节;Tree Miner;TM\nbeyond-dimensions;19045;beyonddimensions;超越维度;Beyond Dimensions;\nthroughput-quest;19046;throughput_quest;ME吞吐量检测任务;Throughput Quest;\n;19047;anvilcraftoddities;铁砧工艺：奇思妙想;AnvilraftOddities;\nlord-of-the-rings-qualities-of-middle-earth;19048;qualities_of_middleearth;魔戒：中洲特质;Lord of the Rings: Qualities of Middle-Earth;QoME\nlord-of-the-rings-qualities-of-middle-earth-1-7-10;19049;qome;魔戒-中洲特质：传承;Lord of the Rings – Qualities of Middle-earth: Legacy;\nhappy-ghast-backported;19050;happyghast;Happy Ghast Backported;;\nhappy-ghast-backport;19051;dried_ghast;Happy Ghast Backport [And Dried Ghast/Ghastling];;\nsnowy-tents;19052;snowy_tents;Snowy Tents;;\nnuhayuponosiks-bosses;19053;sigmabosses;nuhayuponosik's bosses;;\ngoety-cataclysm;19054;goety_cataclysm;诡厄灾变;Goety Cataclysm;\ncave-delight;19055;cavedelight;洞穴乐事;Cave Delight;\n;19056;forkcart;Forkcart;;\nsilentgear-compat;19057;silentcompat;SilentGear Compat;;\ntimeless-ivy-reborn;19058;timeless_ivy_reborn;永恒常春藤：重生;Timeless Ivy:Reborn;\nsimply-more;19059;simplymore;Simply More;;\nwetland-whimsy;19060;wetland_whimsy;奇湿妙想;Wetland Whimsy;\n;19061;prf;快速联机;PrfAgent;\n;19062;knife_fast;快刀无感/快到无感;The knife fast;\n;19063;nojbput;求你别放了;NoG8Put;\nileafcore;19064;ileafcore;IleafCore;;\ndirt-tools-and-armor;19065;dirtequipment;泥土工具与盔甲;Dirt Tools and Armor;\nthaumic-rings-of-power;19066;trop;神秘众戒;Thaumic Rings Of Power;TROP\nlotr-valkyrien-compat;19067;vlcompat;魔戒：瓦尔基里兼容;Lotr+Valkyrien Compat;\nimprovedcrashreports;19068;improvedcrashreports;Improved Crash Reports;;\ncolorful-paradise;19069;colorful_paradise;Colorful Paradise;;\noracle-index;19070;oracle_index;Oracle Index;;\ncome-back-my-villagers;19071;come-back-my-villagers;Come Back My Villagers;;\nscreenshot-with-coords;19072;screenshot_with_coords;带标截图;Screenshot with Coords;SWC\n;19073;mfb;More Fancy Blocks;;MFB\nccddi-damage-indicator;19074;ccddi;Customizable Client-side Directional Damage Indicator;;CCDDI\nlidar;19075;LIDAR;LIDAR;;\n;19076;segmented;Segmented;;\ngoety-gotta-summon-em-all;19077;vividerusmoregoetysummons;Goety: gotta summon em all!;;\naerlune-rpg-2;19078;aerlunerpg;Aerlune RPG 2;;\norigin-furs;19079;originfur;Origin Furs;;\ngrounded-origins-forge;19080;grounded_origins;Grounded Origins;;\n;19081;nofall;禁止下落;Nofall;NF\ntinkers-tool-leveling-2;19082;tleveling;匠魂工具升级2;Tinkers Tool Leveling 2;\nremodifier-reborn;19083;remodifier;你别造了！！！;Remodifier Reborn;\nexporbrecall;19084;exporbrecall;经验球召回;ExpOrbRecall;\nfunctional-economy;19085;functional_economy;功能性钱币;FunctionalEconomy;FE\nranckors-rings-of-power;19086;the_rings_of_power;Ranckor的力量之戒;Ranckor's Rings of Power;\nlotr-improvements-lite;19087;lotrimprovementslite;魔戒改进：轻量版;LOTR Improvements Lite;\n;19088;;海洋乐事基岩版 (Unofficial);Ocean's Delight Bedrock (Unofficial);\n;19089;;幽匿乐事基岩版 (unofficial);Silent's Delight Bedrock (Unofficial);\nastounding;19090;astounding;Astounding;;AUD\ncarpets-core;19091;carpetscore;Carpets Core;;\ncarpet-variants;19092;carpetvariants;Carpet Variants;;\n;19093;mineable_unmineable;Mineable Unmineable;;\ntetra-addition;19094;tetra_addition;Tetra Addition;;\nstyled-boss-bar-api;19095;styled-boss-bar-api;自定义Boss栏;Styled Boss Bar API;\n;19096;memoryaddition;忆-战斗拓展;MemoryAddition;\nrandom-enchant-fix;19097;randomenchantfix;Random Enchant Fix;;\nimmersive-armor-hud;19098;immersivearmorhud;沉浸式盔甲 HUD;Immersive Armor HUD;\n;19099;guzhenren;蛊真人;guzhenren;gu\nyeast-n-feast;19100;yeastnfeast;Yeast 'n Feast;;\n;19101;;The Silk Road;;\nvampirism-tinker;19102;vampirismtinker;血族工匠;VampirismTinker;\n;19103;thaumcarpentry;神秘木工;Thaumcarpentry;\nessentia-pipes;19104;essentiapipes;源质管道;Essentia Pipes;\nmsp-backpacks;19105;msp_backpacks;MSP Backpacks;;\nsimple-block-breaker;19106;simpleblockbreaker;Simple Block Breaker (and Placer);;\nsalis-arcana;19107;salisarcana;Salis Arcana;;\nthaumic-mixins;19108;thaumicmixins;Thaumic Mixins;;\nsanguimancypatch;19109;SanguimancyPatch;SanguimancyPatch;;\nsanguimancy;19110;Sanguimancy;Sanguimancy;;\njungle-villager-trader;19111;jungle_villager_trader;Jungle Villager Trader;;\norigin-furs-unofficial-forge-fork;19112;origin_visuals;Origin Furs - Unofficial Forge Fork;;\nephemeral-wither-skulls;19113;suikeephemeralwitherskulls;消逝的凋灵之首;Ephemeral Wither Skulls;\nfox-friend;19114;suikefoxfriend;狐友;Fox Friend;\nfamily-life-addon;19115;sunrise;Family life;;\nsimple-difficulty-reforged;19116;simple_difficulty;Simple Difficulty Reforged;;\ncreate-recycling-everything-neo;19117;create_recycle_everything;Create: Recycling Everything [NeoForge Port];;\n;19118;mr_create_manofmanyplanes;Create Man of Many Planes;;\naquaticamod;19119;aquatica;Aquatica_;;\nhell-ntegrations;19120;hellmod;Hell Integrations;;\n;19121;more-food-enchantment;更多的附魔食物;More Food Enchantment;\nmapletree;19122;mapletree;枫树;Mapletree;\ndrink-it-refoxed;19123;drinkit;喝！重生;Drink It! Refoxed;\n;19124;Eggs Lay Eggs;蛋孵蛋;Eggs Lay Eggs;\ngalospheric-delight;19125;galospheric_delight;Galospheric Delight;;\ngrowmeal-fabric;19126;growmeal-fabric;Growmeal (Fabric);;\n;19127;;Scorched Earth;;\nkatharite-slimes;19128;sfslime;Slimes Of Kathare;;\nbotania-ceu;19129;;Botania CEu;;\nmagic-from-the-east;19130;iss_magicfromtheeast;Magic From The East;;\nblatapi;19131;blatapi;BlatAPI;;\n;19132;materiallib;Materials Lib;;\ncavelib;19133;cavelib;CaveLib;;\neconomy-api;19134;economy;Economy API;;\n;19135;ainulindale;创世录API;Ainulindalë;\n;19136;pbc;PrankBrewCraft;;PBC\nno-enchantment-cap-level;19137;noenchantmentcaplevel;No Enchantment Cap Level;;\ncreate-ultimine;19138;createultimine;Create Ultimine;;\nserene-wild;19139;serenewild;Serene Wild;;\nkitchen-decorations;19140;kitchen_decorations;Kitchen decorations;;\nrefined-storage-curios-integration;19141;refinedstorage_curios_integration;Refined Storage - Curios Integration;;\nrefined-storage-trinkets-integration;19142;refinedstorage_trinkets_integration;Refined Storage - Trinkets Integration;;\nrefined-storage-emi-integration;19143;refinedstorage_emi_integration;精致存储 - EMI 集成;Refined Storage - EMI Integration;\nrefined-storage-rei-integration;19144;refinedstorage_rei_integration;精致存储 - REI 集成;Refined Storage - REI Integration;\nrefined-storage-jei-integration;19145;refinedstorage_jei_integration;精致存储 - JEI 集成;Refined Storage - JEI Integration;\nrefined-storage-mekanism-integration;19146;refinedstorage_mekanism_integration;精致存储 - 通用机械集成;Refined Storage - Mekanism Integration;\n;19147;;极限末地生存 GTNH 版;Hardcore Ender Expansion - GTNH;HEE\n;19148;;更真实的地图生成 GTNH 版;Realistic World Gen - GTNH;\n;19149;;真实地形生成 GTNH 版;Realistic Terrain Generation - GTNH;\nlukis-ancient-cities;19150;mr_lukis_ancientcities;Luki's Ancient Cities;;\nungebauts-wildlife;19151;ungebauts_wildlife;Ungebaut's Wildlife;;\nchanged-mcreator-slop;19152;changed_mcreator_slop;Changed: MCreator slop;;\nforgotten-battle-towers;19153;fbt;Forgotten Battle Towers;;FBT\n;19154;backatitagain;Back At It Again!;;\nsummer-cottage;19155;sc;Summer Cottage;;SC\nmarius0x-furniture;19156;JammyFurniture;marius0x Furniture;;\nyoussefs-currency;19157;youssefs_currency;Youssef's Currency;;\nbibliobiomes-legacy;19158;bibliobiomes;Bibliobiomes Legacy;;\nsniffers-delight;19159;sniffers_delightfabric;Sniffer's Delight;;\neasters-delight;19160;eastersdelight;Easter's Delight;;\ngo-kart-world;19161;;Go-Karts;;\n;19162;;Craig's Cosmetics;;\n;19163;;Mutant Creatures Add-on;;\nbetter-carryon-maid;19164;better_carryon_maid;更好的女仆搬运;Better Carryon Maid;\ntensura-no-game-no-life;19165;tennogamenolife;转生：游戏人生;Tensura: No Game No Life;\nsummoner-scrolls;19166;summonerscrolls;Summoner Scrolls;;\nrefined-storage-quartz-arsenal;19167;refinedstorage_quartz_arsenal;Refined Storage - Quartz Arsenal;;\ncreate-advanced-logistics;19168;createadvlogistics;Create: Advanced Logistics;;\ncreate-electric-drive;19169;create_electric_drive;Create: Electric Drive;;\n;19170;lachryvision;Lachryvisions;;\nthe-heaven-sword-and-dragon-saber;19171;yitiaotulong;倚天屠龙;The Heaven Sword and Dragon Saber;YTTL\ndreamtinker;19172;dreamtinker;工匠幻梦;DreamTinker;\n;19173;fuiofd,fuiofd_modb;fuiofd的模组;;\n;19174;mattock;鹤嘴锄;Mattock;\nwolfs-enchantment-lib;19175;wolfenchantmentlib;狼王的附魔库;Wolf's Enchantment Lib;\n;19176;cao;槽;CaoPlus;Cao\ndisturb-not-my-dream;19177;dnmd;勿扰吾梦 / 起床气;Disturb Not My Dream;DNMD\n;19178;contigency_shop;轻量兑换所;Exchange Site Lite;\nlootbagsrenewal;19179;lootbags;战利品袋重制版;LootBagsRenewal;LBR\nnumberblocks;19180;;数字方块;Numberblocks;\n;19181;m3l;Magic Mojo Mod Loader;;M3L\nautoswaplowdurabilityelytra;19182;wiamautoswaplowadurabilityelytra;自动鞘翅替换;AutoSwapLowDurabilityElytra;\nno-elytra-equip;19183;noelytra;Elytras Disabled;;\nno-elytra;19184;no_elytra;No elytra;;\ntitle-changer;19185;titlechanger;Title Changer;;\nclickthrough-plus;19186;clickthrough;穿牌开箱+;ClickThrough Plus;\nitemzoomer;19187;itemzoomer;Item Zoomer;;\n;19188;surveystones;Surveystones;;\nparticular-reforged;19189;particular;Particular ✨ Reforged;;\ncloud-revive;19190;cloud_revive;云生;Cloud Revive;\nparanoia;19191;paranoia;偏执狂;Paranoia;\nthaumic-brewing;19192;thaumicbrewing;神秘酿造学;Thaumic Brewing;\naether-treasure-reforging;19193;aether_treasure_reforging;Aether Addon: Treasure Reforging;;\nanimations;19194;overflowanimations;OverflowAnimations;;\n;19195;pet_necropolis;Pet Necropolis;;\npaddle-boats;19196;pedalo;Paddle Boats;;\ntrue-darkness-elementriy;19197;truedarkness;True Darkness Elementriy;;\n;19198;;Craig's Cosmetics: Halloween Edition;;\n;19199;;Craig's Cosmetics: Lunar Edition;;\nsophisticated-storage-create-integration;19200;sophisticatedstoragecreateintegration;Sophisticated Storage Create Integration;;\n;19201;;NEI 拼音搜索 GTNH 版;NotEnoughCharacters - GTNH;\n;19202;removespeeddetection;移除服务端速度检测消息;RemoveSpeedDetection;\nshared-advancements;19203;sharedadvancements;Shared Advancements;;\n;19204;polytime;PolyTime;;\ngolden-meadows;19205;golden_meadows;Golden Meadows;;\nssmlegacy;19206;ssmlegacy;Super Sound Muffler: Legacy;;\nsuper-sound-muffler-revived;19207;supersoundmuffler;Super Sound Muffler: Revived;;\ncurrent-game-music-track;19208;currentgamemusictrack;Current Game Music Track;;\ncreate-mobile-packages;19209;create_mobile_packages;机械动力：无人机物流;Create: Mobile Packages;CMP\ncreate-cardboarded-conveynience;19210;create_cardboarded_conveynience;Create: Cardboarded Conveynience;;\nblacksmith-weapon;19211;blacksmith_weapon;铁匠武器;Blacksmith_Weapon;\nearth-ring;19212;;地球环;Earth Ring;\nmrqx-s-slashblade-core;19213;sbr_core;墨染琴弦的拔刀剑核心;mrqx's Slashblade Core;\ngoldblade;19214;godblade;神刀;GodBlade;\n;19215;tentAndWall;帐篷与围墙;Tent And Wall;TAW\nshanghai-metro-line-one-mod;19216;shanghai-metro-line-one-mod;上海地铁一号线;Shanghai Metro Line One Mod;\n;19217;deepseekchat;接入DeepSeek;MineDeepSeekChat;MDC\nthetrackers;19218;the_trackers;全追踪;TheTrackers;TT\ncat-burger;19219;catburger;猫猫汉堡;Cat Burger;\nmineral-backpacks;19220;mineral_backpacks;Mineral Backpacks;;\n;19221;furry_bohe;兽薄荷;Furry Bohe;\nnarcissusfarewell-reflorescence;19222;narcissus_farewell;水仙辞：重绽;NarcissusFarewell: Reflorescence;NFR\nbedrock-energistics-core;19223;;Bedrock Energistics Core;;\nbibliocraft-bibliowoods-forestry-edition;19224;BiblioWoodsForestry;BiblioCraft: BiblioWoods Forestry Edition;;\nbibliocraft-bibliowoods-biomes-oplenty-edition;19225;BiblioWoodsBoP;BiblioCraft: BiblioWoods Biomes O'Plenty Edition;;\nbibliocraft-bibliowoods-natura-edition;19226;BiblioWoodsNatura;BiblioCraft: BiblioWoods Natura Edition;;\nbibliocraft-bibliowoods-terrafirmacraft-edition;19227;BiblioWoodsTFC;BiblioCraft: BiblioWoods TerraFirmaCraft Edition;;\nbibliocraft-bibliowoods-highlands-edition;19228;BiblioWoodsHighlands;BiblioCraft: BiblioWoods Highlands Edition;;\nbibliocraft-bibliowoods-extrabiomesxl-edition;19229;BiblioWoodsEBXL;BiblioCraft: BiblioWoods ExtraBiomesXL Edition;;\n;19230;blockprintr;BlockPrintr;;\n;19231;combatmodethirdperson;战斗模式第三人称;CombatModeThirdPerson;CMTP\ncustomized;19232;customized;食尚臻制;Customized;\nassorted-tools;19233;assortedtools;Assorted Tools;;\nassorted-core;19234;assortedcore;Assorted Core;;\ntlotd;19235;tlotd;TLOTD;;\ninfinity-golem-boss-fight-beta;19236;infinitygolem;Infinity Golem Boss Fight;;\nactually-divisions;19237;actually_division;Actually Divisions;;\nextra-trinkets;19238;extra_trinkets;Extra Trinkets;;\nportable-tanks;19239;portabletanks;Portable Tanks;;\nhappier-ghasts;19240;happierghasts;Happier Ghasts;;\ntinker-in-caves;19241;tinker_in_caves;洞穴工匠;Tinker in Caves;\n;19242;better_tfcr;更好的群峦：重生;Better TFCR;BTFCR\n;19243;dontbetohigh;请不要爬的太高;Don't Be Too High;\nmobreaker;19244;mob_breaker;坚壁重构;Mobreaker;\nman-from-the-fogs-delight;19245;manfromthefogsdelight;雾中人乐事;Man From The Fog's Delight;\nforsaken-items;19246;forsakenitems;被遗忘的物品：重生;Forsaken Items Reforked;\n;19247;fun_Item;豆浆你提我造;Soybean Fun Item;\nepic-fight-mesh-model;19248;efmm;史诗战斗网格模型模组;Epic Fight Mesh Model;EFMM\nmonster-of-the-night-skies;19249;mobb;Monster of the Night Skies;;\nspooky-doors;19250;spookydoors;Spooky Doors;;\n;19251;mclientapi;MClientAPI;;\nmoffs-addonapi-dynload;19252;addonapi;AddonAPI;;\n;19253;shiv;Shiv;;\n;19254;tablist;Tab List;;\ndokimobs;19255;dokimobs;萌化生物;DokiMobs;DM\nbookling-gear;19256;booklinggear;Bookling Gear;;\nneuracraft;19257;neuracraft;NeuraCraft;;NC\nmoves-like-mafuyu;19258;moveslikemafuyu;高速企鹅;Moves Like Mafuyu;\n;19259;ponder;思索;Ponder;\nvanillin;19260;vanillin;Vanillin;;\nchain-mining-reforged;19261;cmr;连锁采集重铸;Chain Mining Reforged;CMR\nhrtffix;19262;hrtffix;HRTF修复;HRTF Fix;\niammusicplayer-renewed;19263;iammusicplayer;Iam Music Player Renewed;;\nmisc-tweaks;19264;misctweaks;Misc Tweaks;;\nftb-filter-system;19265;ftbfiltersystem;FTB Filter System;;FFS\nneonium;19266;neonium;Neonium;;\n;19267;superfastmath;Super Fast Math;;\n;19268;repother;RePother;;\n;19269;netherportalmod;增强型下界传送门;Enhanced Nether Portal Mod;\nsimple-loot-viewer;19270;simplelootviewer;Simple Loot Viewer;;\n;19271;dasii;Dasii;;\ngreg-tech-chisel;19272;;凿子 GTNH 版;Chisel - GTNH / Greg Tech Chisel;\nbaubles-expanded-gtnh;19273;;饰品栏 GTNH 版;Baubles-Expanded-GTNH;\ngregs-tinkering;19274;gregstinkering;Greg's Tinkering;;GTC\nmetaworlds-mixins;19275;metaworlds;元世界 - Mixin 重制版;MetaWorlds - Mixins;\ncreate-cobblemon-potion;19276;create_cobblemon_potion;宝可梦动力药水;Create Cobblemon Potion;CCP\n;19277;necrotempus;NecroTempus;;\njarjar;19278;jarjar;JarJar;;\njavascriptjs;19279;javascriptjs;JavaScript JS;;JSJS\nblood-n-particles-mod;19280;blood_n_particles_mod;Blood N' Particles Mod;;\n;19281;sheeps_tools;绵阳的特效工具;Sun Sheep's Tool;SST\nxiaofanweis-items;19282;xfws_someitems;小烦维的物品;xiaofanwei's items[Iron's Spellbooks and Terra Curios];XFWI\n;19283;lob_mod;脑叶模组;Lob Mod;\neasycolony-addon-for-minecolonies;19284;easycolony;简易殖民地;Easycolony;\nblades-derby;19285;slashblade;骐骥刀剑风云;Blades Derby;UBD\n;19286;;暮色森林 Addon / 暮色森林 Classic 附加包;The Twilight Forest Classic Addon;\ncreate-simple-generator;19287;create_simple_generator;机械动力：简单发电机;Create: Simple Generator;\nsophisticated-backpacks-create-integration;19288;sophisticatedbackpackscreateintegration;Sophisticated Backpacks Create Integration;;\nticex-tinkers-construct-ex;19289;ticex;Tinkers Construct EX;;TiCEX\neternal-starlight-vanilla-ores;19290;eternal_starlight_vo;Eternal Starlight Vanilla Ores;;\nsupercritical;19291;supercritical;Supercritical;;\nsupercritical-gtqt;19292;supercritical;Supercritical-GTQT;;\ngtconsolidate;19293;gtconsolidate;GTConsolidate;;\nimplosionnobomb;19294;inb;Implosion No Bomb;;INB\nvillager-golem-healer;19295;villagergolemhealer;Villager Golem Healer;;\nyakurum-mod;19296;yakurum;Yakurum Mod;;\n;19297;fantasytools;FantasyTools;;\nmetaworlds-mod;19298;metaworlds;元世界 / 变幻世界;Metaworlds Mod;\ncreate-dragons-plus;19299;create_dragons_plus;机械动力：龙+;Create: Dragons Plus;CDP\nappliances;19300;appliances;Appliances;;\n;19301;twelvefoldbooter;Twelvefold Booter;;\nomniconfig;19302;omniconfig;Omniconfig;;\n;19303;stopstopdigging;停！别再挖了！;Stop! Stop digging!;SSD\n;19304;linearxp;线性经验值;LinearXP;\n;19305;betterthunder;更好的雷声;Better Thunder;\n;19306;enhancedpacketcompression;增强数据包压缩;Enhanced Packet Compression;\ngnetum;19307;gnetum;Gnetum;;\naccessible-step;19308;accessible-step;Accessible Step;;\nterrastorage-icons;19309;terrastorageicons;Terrastorage: Icons;;\nterrastorage;19310;terrastorage;Terrastorage;;\n;19311;euphoriacompanion;Euphoria Companion;;\njump-bridge-mod;19312;jump_bridge_mod;Jump Bridge;;\nhuskhomes;19313;huskhomes;HuskHomes;;\nblahajasm;19314;normalasm;BlahajASM;;\nmireoles-gtceu-additions;19315;mgtceua;Mireole's GTCEu Additions;;\nexpanded-ae;19316;expandedae;Expanded AE;;\nmodular-machinery-reborn-ars-nouveau;19317;modular_machinery_reborn_ars;Modular Machinery Reborn Ars Nouveau;;\nbonsai-crops;19318;bonsaicrops;盆栽作物;Bonsai Crops;BC\nplaying-cards-chips;19319;playingcards;扑克牌与筹码;Playing Cards & Chips;\nenderdrives;19320;enderdrives;EnderDrives;;\nlucky77;19321;lucky77;Lucky77;;\n;19322;ctrlql;Ctrl-QL;;\ni-dont-see-you;19323;i_dont_see_you;I Don't See You;;\npoint-blank-official-extension-doom-pack;19324;;Point Blank Official Extension || Doom Pack;;\nepic-fight-valour-guard;19325;valourguard;Epic Fight - Valour Guard;;\nsweet-calamity;19326;sweet_calamity;Sweet Calamity;;\nvanillabackport;19327;vanillabackport;VanillaBackport;;\nstylish-stiles;19328;stylishstiles;Stylish Stiles;;\nstylish-stiles-renewed;19329;stylishstiles;Stylish Stiles - Renewed;;\nundermod;19330;undermod;Undermod;;\nspitting-image;19331;spittingimage;Spitting Image;;\n;19332;repo-heads;REPO Heads;;\ncyberpunk-2077-guns-for-vics-point-blank;19333;;Cyberpunk 2077 Guns for Vic's Point Blank;;\n;19334;create_blueprint_tweaks;机械动力蓝图微调;Blueprint Tweaks: A Create Addon;\nmob-journal;19335;journal;生物图鉴;Mob Journal;\n;19336;chromatifixes;ChromatiFixes;;\nsdm-shop;19337;sdmshoprework;SDM Shop;;\neconomy-coins-bills-and-gems;19338;economy;Economy: Coins, Bills and Gems!;;\nmulti-hotbar-core;19339;multihotbarcore;Multi-Hotbar Core;;\nmulti-hotbar;19340;multihotbar;多重快捷栏;Multi-Hotbar;\nproperties-panel;19341;qwedshuxingmianban;属性面板;Properties Panel;\nwhimcraft;19342;whimcraft;奇思妙想;WhimCraft;\nrecipeconflict-fixer;19343;;合成冲突消除：修复版;RecipeConflict Fixer;\ndooglamoo-painter-mod;19344;dooglamoopaintermod,dooglamoopainter;Dooglamoo 的绘画;Dooglamoo Painter Mod;\nnotafreaks-plushies;19345;freaksplushes;NotAFreak's Plushies;;\nenchanting-vanilla;19346;enc_vanilla;原韵新生;Enchanting Vanilla;\npoint-blank-official-extension-half-life-pack;19347;;Point Blank Official Extension || Half Life Pack;;\n;19348;rh;Reinforced HeadhunterEX;;\nquest-items-rpg;19349;quest_items;Quest Items for FTB;;\nexperimental-mobs;19350;cartoon_soul;Experimental Mobs;;\nadvanced-combat-patched;19351;;高级战斗装备修复版;Advanced Combat Patched;\n;19352;authproxy;AuthProxy;;\nezstorage-2-patch;19353;ezstorage2patch;EZ 存储 2 补丁;EZStorage 2 Patch;\nsquake-patched;19354;;Squake - patched;;\nimmersive-hotbar;19355;immersive-hotbar;Immersive Hotbar;;\ncreate-classic-blaze-enchanter;19356;create_classic_blaze_enchanter;机械动力：经典烈焰人附魔室;Create: Classic Blaze Enchanter;\n;19357;lootbags;战利品袋：重置再重织;LootBagsRenewal-Refabriced;LBRR\nifpatcher;19358;ifpatcher;工业先锋补丁;IFPatcher;\nsakura-orihime;19359;sakura;樱：织姬;Sakura-Orihime;\n;19360;thc;The Halloween Challenge 2015;;THC\ntinkers-antique;19361;tconstruct;匠魂怀古;Tinkers' Antique;\nex-fusile;19362;ExFusile;Ex Fusile;;\nbetter-player-respawn;19363;betterplayerrespawn;Better Player Respawn;;\n;19364;;[Vic's Point Blank] The Last of Z Weapon Pack;;\n;19365;;[Vic's Point Blank] Ballistic Knife;;\nanother-experiment-researcher;19366;labyrinth;Experimental bosses;;\nspiders-1-999;19367;;Spiders 1.999;;\nvillager-comfort-updated;19368;villagercomfort;村民舒适度更新版;Villager Comfort Updated;\nflight-affinity;19369;flightaffinity;Flight Affinity;;\nemcgadgets;19370;emcgadgets;EMC Gadgets;;\n;19371;litematica_printer_forge;Litematica-Printer-Forge;;\n;19372;engineeredition;Minecraft: Engineer Edition;;\n;19373;eyelib;Eyelib;;\ncustom-armor-bar;19374;colorfulhealthbar;Custom Armor Bar;;\nroughly-enough-input-methods;19375;roughlyenoughinputmethods;Roughly Enough Input Methods;;REIM\npirates-doom;19376;piratesdoom;Pirate's Doom;;\ntechrebornjei;19377;techrebornjei;科技复兴JEI;TechRebornJEI;TRJEI\nlittle-joys;19378;littlejoys;Little Joys;;\nblasting-raw-metal-blocks-into-blocks-fabric-forge;19379;blastingraw;Blasting Raw Metal Blocks;;BRMB\nmodern-conflict-rearmed;19380;;[VPB] Modern Conflict: Rearmed;;\n;19381;micwands;重制：怪物控制魔杖;Re: Mob Control Wands;\n;19382;mr__basbetterarmorstand;更好的盔甲架;Better Armor Stand;BAS\nnekoration-reborn;19383;nekoration;猫咪装饰：重生;Nekoration-Reborn;NR\nmine-reputation;19384;minereputation;失去的声望;Mine Reputation;MR\nruined-lighthouse;19385;ruined_lighthouse;Ruined Lighthouse;;\nfrogs-medieval-paintings;19386;frogsmedievalpaintings;Frog's Medieval Paintings;;\nbad-apple;19387;badapple;Bad Apple!!;;BA\npodium-sodium;19388;podium;Podium (Pojav x Sodium);;\ncompressed-create-recipes;19389;compressed_create_recipes;Compressed Create Recipes;;\naether-overworld-ores;19390;aether_overworld_ores;Aether Addon: Overworld Ores;;\nslashbalde-useful-addon;19391;slashbalde_useful_addon;拔刀剑实用扩展;Slashbalde Useful Addon;SUA\n;19392;ysm_ws;是，史蒂夫模型：创意工坊;YSMWorkShop;YSMWS\npowerutilites-unofficial;19393;powerutils;电力转换非官方版;PowerUtilites Unofficial;\n;19394;gamerule_please;GamerulePlease;;\ncustom-fov;19395;customfov;Custom FOV;;\napotheosis-x-irons-spellbooks-compat;19396;irons_apothic;Apotheosis x Iron's Spellbooks Compat;;\ncustomtab;19397;customtab;CustomTAB;;\nnightmares;19398;nightmares;噩梦;Nightmares;\ngraveyards;19399;graveyards;Graveyards;;\nsky-arena;19400;skyarena;Demi's Sky Arena;;\nprogressive-mechanics-library;19401;pml;Progressive Mechanics Library;;PML\n;19402;shy;Shy;;\n;19403;yuzu;千恋万花标题界面;YuZuUI;\ntpa-datapack;19404;;TPA数据包;TPA Datapack;TPA\ntradepreview;19405;tradepreview;交易预览;TradePreview;\nenhanced-celestials-shader-support;19406;enhanced_celestials_shaders;月亮事件光影支持;Enhanced Celestials Shader Support;\nincendium-biomes-only;19407;ibo;Incendium Biomes Only;;IBO\nthaumic-tweaks;19408;thaumictweaks;Thaumic Tweaks;;\nselectivebounds;19409;selectivebounds;SelectiveBounds;;\n;19410;bactromod;BactroMod;;\n;19411;exit-on-startup;一启动就退出;Exit On Startup;\n;19412;;Kotlin For Forge Unofficial Extended Support;;\n;19413;circlemc;CircleMC;;\nnearbymobchecker;19414;nearbymobchecker;Nearby Mob Checker;;NMC\ngputape;19415;gpu_tape,gpu_booster;GPUBooster / GpuTape;;\n;19416;neul;铁砧限制修复;Limitless Anvil;\ncarry-on-extend;19417;carryonextend;搬运：拓展;Carry On Extend;COE\nkaleidoscope-doll;19418;kaleidoscope_doll;森罗物语：玩偶;Kaleidoscope Doll;\nkamen-tinker;19419;kamen_tinker;蓝钢匠艺/假面匠艺;Kamen Tinker;InoTC\nagritech-automated-crops;19420;agritech;AgriTech: Crops;;\n;19421;realpotidea;脑洞瓦工;Pot's Idea;\nkogtyv-tav;19422;kogtyvtav;Kogtyv 的城镇与村庄;kogtyv-Towny and Village;\ngothic-church;19423;gothic_church;哥特式教堂;Gothic Church;\nsky-whale-ship;19424;sky_whale_ship;Sky Whale Ship;;\nno-mans-land;19426;nomansland;无人之境;No Man's Land;\nbindable-mana-pool;19427;bindable_mana_pool;可绑定魔力池;Bindable Mana Pool;\n;19428;blood_mending;Blood Mending;;\ncraziness-awakened;19429;craziness_awakened;Craziness Awakened;;\nmischief-illagers;19430;mischief_illagers;Mischief Illagers;;\n;19431;quenching;淬火;Quenching;\ndynamic-trees-caverns-and-chasms;19432;dtcavernsandchasms;动态的树：Caverns & Chasms 附属;Dynamic Trees - Caverns & Chasms;\n;19434;minechat;MineChat;;\napex-a-proper-equipment-expansion;19435;apex;A Proper Equipment Expansion;;APEx\ndimension-of-caves;19436;dimension_of_caves;Alex的洞穴维度;Dimension of Caves;DC\nherobrine-a-friend;19437;hbaf;Herobrine: A Friend;;\nstattweaker;19438;stattweaker;StatTweaker;;\n;19439;mushroom_concept;蘑菇构想;Mushroom Concept;MCP\nthe-super-blue-archive-pack-vpb;19440;;[VPB]THE SUPER BLUE ARCHIVE PACK;;\natsassistmod;19441;atsassistmod;ATSAssistMod;;ATS\n;19442;i-cant-believe-its-not-1215;I Can't Believe It's Not 1.21.5;;\n;19443;serverlistbufferfixer;服务器列表缓冲崩溃修复版;ServerlistBufferCrashFixer;\ndynamic-tooltips;19444;dynamictooltips;动态提示框;Dynamic Tooltips;\n;19445;;Chat Ranks and Name Addon;;\nmixed-litter;19446;mixed_litter;Mixed Litter;;\ngroovyduvet;19447;groovyduvet;GroovyDuvet;;\ncreate-egg-production;19448;createeggproduction;机械动力：鸡蛋产线;Create: Egg Production;CEP\ncreate-fishery-industry;19449;createfisheryindustry;机械动力：渔业;Create: Fishery Industry;CFI\ncreate-rustic-structures;19450;create_rustic_structures;机械动力：乡村结构;Create: Rustic Structures;\nslightly-newer-art-of-forging;19451;art_of_forging;Slightly Newer Art of Forging;;\ncustom-treasure-maps;19452;customtreasuremaps;Custom Treasure Maps;;\nevolved-mekanism;19453;evolvedmekanism;通用机械：进化;Evolved Mekanism;\nbrain-delight;19455;brain_delight;脑子乐事;Brain Delight;\nvillager-library;19456;villager_library;村民图书馆;Villager Library;\ntrade-enchant-overhaul;19457;teo;Trade & Enchant Overhaul;;TEO\nmore-additional-paintings;19458;vanilla__pixel_paintings;More Additional Paintings;;\nsimplebackport;19459;simplebackport;SimpleBackport;;\nbiomebeats;19460;biomebeats;Biome Beats;;\ndecorative-core;19461;decorative_core;Decorative Core;;\n;19462;keybindingskeeper;按键绑定不减;Key Bindings Keeper;\n;19463;mekanismoutputfaster;通用机械加速输出;Mekanism Output Faster;\n;19464;infcapcell;无限容量元件;Infinite Capacity Cell;\n;19465;mcutilities;MCUtilities;;\npickuptorches;19466;pickuptorches;PickUpTorches;;\n;19467;tconjei;TconJEI Fork;;\npointblank-magazines-vpb;19468;vpbm;Pointblank Magazines (VPB);;\npointblank-projectiles-vpb;19469;vpb_projectiles;Pointblank Projectiles (VPB);;\ndeathly-hallows;19470;dh;死亡圣器;Deathly Hallows;DH\ntetra-loopback;19471;tetra_loopback;Tetra Loopback;;TLb\nappliedenergisticsoddities;19472;ae_oddities;Applied Energistics Oddities;;AEO\ntweakermoreforge;19473;tweakermoreforge;TweakerMoreForge;;\nvs2-cbc-armor-blocks;19474;vscarmor;VS2/CBC Armor Blocks;;\ncreate-integrated-farming;19475;create_integrated_farming;机械动力：集成农业;Create: Integrated Farming;CIF\nring-of-blink-forge;19476;ring_of_blink;闪现戒指;Ring of Blink;\ngolem-overhaul;19477;golemoverhaul;傀儡革新;Golem Overhaul;\nvoile;19478;voile;Voile;;\ntitle-tweaks;19479;titletweaks;Title Tweaks;;\nugoblock;19480;ugoblock;动块;UgoBlock;\n;19481;apoli;Apoli;;\n;19482;calio;Calio;;\nscriptasm;19483;shotaasm,scriptasm;ShotaASM;;\n;19485;autoaim;自动瞄准;AutoAim;AA\naimbot;19486;aimbot;瞄准修正;Aim Bot;AB\n;19487;server_chat_log_history;Server Chat Log History;;\nhud-compass;19488;hudcompass;Hud Compass;;\nvertical-slabs;19489;stylax;Vertical Slabs;;\nfamiliar-friends;19490;familiar_friends;亲密伙伴;Familiar Friends;\ngolem-spawn-animation;19491;golem_spawn_animation;Golem Spawn Animation;;\nrealistic-night-vision;19492;realisticnightvision;Realistic Night Vision;;\nstackable-items;19493;stackableitems;Stackable Items;;\n;19494;rawinput;RawInputMod;;\ndiscrafted;19495;discrafted;Discrafted;;\nsurvival-plus-plus;19496;survival_plus_plus;Survival++;;\nserver-plus-plus;19497;server_plus_plus;Server++;;\nhappier-ghast;19498;better_happy_ghast;更好的快乐恶魂;Better Happy Ghast;BHG\nbhgc;19499;bhgc;更好的快乐恶魂控制;Better Happy Ghast Control;BHGC\n;19500;dominatus;Dominatus: Refinement;;\nunfocused;19501;unfocused;Unfocused;;\njerotes-warehouse;19502;jerotes;Jerotes实用仓库;Jerotes Warehouse;\nhappy-ghast-legacy;19503;happy_ghast_legacy;Happy Ghast Legacy;;\nspider-jockey;19504;spider_jockey;Spider Jockey;;\ndigger-helmet;19505;digger_helmet;Digger Helmet;;\nacecraft;19506;acecraft;AceCraft;;\n;19507;mr_create_decoadditions;Create Deco Additions;;\n;19508;familiar_magic;Familiar Magic;;\nlumenized;19509;lumenized;流光溢彩;Lumenized;\nchili-bullet-weapons;19510;chilibulletweapons;Chili Bullet Weapons;;\napplied-web-terminal;19511;appwebterminal;AE网络终端;Applied Web Terminal;AWT\n;19512;keymap;Dust's Keymap;;\nitemzoommore;19513;;ItemZoomMore;;\n;19514;noitemtooltips;NoItemTooltips;;\nshuttfup;19515;stfu;Stfu;;\nbaublesreforked;19516;baublesreforked;饰品重铸;BaublesReforked;\n;19517;wills-lively-villages;Wills's Lively Villages;;\ntmrv;19518;toomanyrecipeviewers;TooManyRecipeViewers;;TMRV\n;19519;codmodern;COD击杀信息显示;COD Modern Warfare Information;CMW\ntwilight-treehouses;19520;twilight_treehouses;Twilight Treehouses;;\nimproved-rails;19521;irails;更好的铁轨 (可转弯の动力铁轨！);Improved Rail (TURNABLE POWERED RAILS);IRail\nredstone-signal-bind-wand;19522;wlrt;非Create版无线红石;Redstone Signal Bind Wand;RSBW\n;19523;mr_custom_packetaggregates;定制数据包集合体;Custom packet aggregates;CPA\n;19524;trator;游戏内聊天翻译;In game Translator;IGTL\ncarnivorous-plants-addition;19525;carnivorous_plants_addition;Carnivorous Plants Addition;;\nmekanism-curios;19526;mekanismcurios;Mekanism Curios;;\ntetra-compat;19527;tetracompat;Tetra Compat;;\nhex-casting-additions;19528;hexcastingadditions;Hex Casting Additions;;\nghasts-crash;19529;maxs_tweaks;Ghasts Crash;;\ntrek;19530;mr_trek;Trek;;\nfuturecherry;19531;futurecherry;未来的樱花;FutureCherry;FC\nfood-fix;19532;foodfix;Food Fix;;\nmining-speed-tooltips;19533;miningspeedtooltips;Mining Speed Tooltips;;\nsmithing-template-viewer;19534;smithingtemplateviewer;Smithing Template Viewer;;\npicks-of-power;19535;picks_of_power;Picks of Power;;POP\n;19536;customcoins;自定义硬币;Custom Coins;CTC\nsuper-hot;19537;superhot;Super Hot;;\nchill-thrill;19538;chill_thrill;Chill Thrill;;\npyrellium;19539;pyrellium;Pyrellium;;\nluminous-nether;19540;luminous_nether;LUMINOUS: NETHER;;\nbrush-mining-machine;19541;brush_mining_machine;刷矿机;Ore Generator;\n;19542;spawner-egg-recipes;刷怪笼蛋配方;Spawner Egg Recipes;SRER\n;19543;night_vision_amulet;夜视护符;NightVisionAmulet;NVA\nset-effects;19544;set_effects;套装效果;Set Effects;SEJS\ntramways;19545;tramways;Create: Tramways;;\nprojecte-kubejs;19546;;ProjectE - KubeJS;;\nactually-additions-v2-unofficial;19547;;实用拓展 V2 咖啡版;Actually Additions V2 Coffee Edition;\ncreate-ender-link;19548;createenderlink;Create: Ender Link;;\nprogressive-tweaks;19549;progressive_tweaks;Progressive Tweaks;;\nportable-blueprints;19550;portable_blueprints;Portable Blueprints;;\ncarrasconlib;19551;carrasconlib;CarrasconLib;;\nblockparticlespawning;19552;bps;方块粒子生成;Block Particle Spawning;BPS\n;19553;chromatinei;ChromatiNEI;;\n;19554;threadsafetyfixes;ThreadSafetyFixes;;\n;19555;ancient_one;Ancient One;;\naether-dragon-dragon-survival-addon;19556;ds_aether_addon;Aether Dragon - Dragon Survival addon;;\ntundra-dragon-dragon-survival-add-on;19557;tundradragon;Tundra Dragon;;\nnaturalist-delight;19558;farmerd_naturalist_compat;Naturalist Delight;;\ncivilizations;19559;civilizations;Civilizations;;\nblock-factory-biomes;19560;bf_biomes;Block Factory's Biomes;;\nunderwater-swim-fix-mc-220390;19561;underwater-swim-fix;水下游泳修复;Underwater Swim Fix;\nscaffolding-backported;19562;backported;Scaffolding Backported;;\nhold-my-items-reforged;19563;holdmyitems,holdmyitemsnf;Hold My Items - Reforged;;\n;19564;lifefruit;生命果 - 重制版本;Life Fruits - Reforked;\ndistraction-free-recipes;19565;distraction_free_recipes;Distraction Free Recipes;;\nseeking-immortals-nightmare;19566;seeking_immortals;月之石：噩梦之章/求仙者：噩梦之章;Moonstone :Nightmare/Seeking Immortals : Nightmare;\nenchanted-crystal-arrow;19567;enchantedcrystalarrows;魔法水晶箭;Enchanted Crystal Arrows;ECA\ntouhou-danmaku-kagura-core;19568;amara;东方弹幕神乐核心;Touhou Danmaku Kagura core;\nhah-ueuh;19569;hah_ueuh;Hah! Ueuh~;;\neternal-nether;19570;eternalnether;永恒下界;Eternal Nether;EN\n;19571;we_need_more_milk;更多牛奶源;We Need More Milk;WNMM\n;19572;shxiuxian;山海仙途;shxiuxian;\n;19573;bettercommand;更好的原版命令;BetterMinecraftCommand;BC/BMC\ngui-followers;19574;guifollowers;GUI Followers;;\nomnidirectional-sound-ods;19575;omni-directional-sound;Omnidirectional-Sound;;ODS\n;19576;warriorrage;Warrior Rage - Reforked;;\ndiligentstalker;19577;diligentstalker;勤劳跟踪狂;Diligent Stalker;DS\nmaid-useful-tasks;19578;maid_useful_task;女仆实用任务;Maid Useful Task;\n;19579;titlechanger;标题切换器 Next;TitleChanger Next;\nembers-underground-rooms;19580;underground_rooms;Ember's Underground Rooms;;\n;19581;mineral_addition,ducktech;矿物追加;Mineral Addition;MA\ndontstarve4;19582;ds4;不要饿死4;Don't Starve 4;DS4\nspark-core;19583;spark_core;星火核心;Spark Core;\nnether-core-useful-nether-rock;19584;corep;下界核心;Nether Core;NCRE\nslashblade-js;19585;sbjs;拔刀剑 JS;SlashBlade JS;SBJS\ncaerula-arbor;19586;caerula_arbor;方块与深蓝之树;Caerula Arbor;\nalices-doll;19587;alice_mad_tea_party;元歌;Alice's Doll;\nbedrock-redux;19588;bedrock_redux;基岩再现;Bedrock Redux;BR\nitem-entity-control;19589;itementitycontrol;掉落物清理;Item Entity Control;IEC\nwishing-fountain;19590;wishing_fountain;许愿泉;Wishing Fountain;\n;19591;unbelievable;令人不可置信的附魔;Unbelievable;UME\n;19592;hgwsspyglasses;Hgw的望远镜;Hgw's Spyglasses;HS\nwe-do-copyright;19593;wedocopyright;We Do Copyright;;\nuseful-clipboard;19594;usclb;Useful Clipboard;;\nseeking-immortals-virus;19595;seeking_immortal_virus;月之石：基因计划/求仙者：基因计划;Moonstone :Virus/Seeking Immortals :Virus;\ngambler-illager-irons-spells-and-spellbooks-addon;19596;gambler;Gambler Illager - Iron's Spells and Spellbooks Addon;;\nchimericlib;19597;chimericlib;ChimericLib;;\npokecube-alternative;19598;pokecube_alternative;Pokecube Alternative;;\n;19599;intuneth;Intuitive Netherite;;\ncreate-propulsion;19600;createpropulsion;机械动力：推进工程;Create: Propulsion;\n;19601;Well;水井;Well Well Well;WWW\n;19602;apathyutils;ApathyUtils;;\nevent-wrapper;19603;eventwrapper;事件包装器;Event Wrapper;\n;19604;sub;ShutUpBro;;SUB\n;19605;mod_whitelist;模组白名单;ModWhiteList;\n;19606;instant_build;瞬时建造;Instant Build;IB\nbeacon-conduit-tweaks;19607;beacon-conduit-tweaks;信标与潮涌核心调整;Beacon & Conduit Tweaks;BCT\ntfc-tools-trim;19608;tfc_trim;群峦传说：工具纹饰;TFC Tools Trim;TTT\n;19609;killyoukaiwithknives;Kill Youkai With Knives;;\nmo-bees;19610;mobees;更多蜜蜂;Mo' Bees;\n;19611;blackflood-loweragriculture;黑潮：低产种养;Black Flood: Lower Agriculture;BFLA\nfaux-custom-entity-data;19612;faux-custom-entity-data;Faux Custom Entity Data;;\n;19613;fl;FasterLanguage;;FL\npoopsky;19614;poopsky;空中厕所;PoopSky;\n;19615;solarus;Solarus - Classic;;\n;19616;linlangmanmu;琳琅满目;Lots of Clothes;LoC\ncreate-unlimited;19617;createunlimited;Create Unlimited;;\nskeleton-ai-fix;19618;skeletonaifix;骷髅 AI 修复;Skeleton AI Fix;SF\nclimate-rivers;19619;climaterivers;Climate Rivers;;CR\ndungeonsdelight;19620;dungeonsdelight;地牢乐事;Dungeons Delight;\ncleanroom-relauncher-unofficial;19621;relauncher;Cleanroom 模组式启动器非官方版;Cleanroom Relauncher Unofficial;\nsilents-power-scale;19622;powerscale;Silent's Power Scale;;\ntrident-return-fabric;19623;trident_return;Trident Return Fabric;;\nresource-world;19624;resource_world;资源世界;Resource World;\nijm-tweaks;19625;ijmtweaks;IJM's Tweaks;;\nno-mans-delight;19626;nomans_delight;No Man's Delight;;\n;19627;uwabami_breakers;农夫乐事：黄昏酒场;Uwabami Breakers;UB\napplemilktea2-tweaked;19628;;AppleMilkTea2 Tweaked;;\n;19629;carpetblueaddition;Carpet Blue Addition;;\n;19630;yetanothercarpetaddition;毯上添花;Yet Another Carpet Addition;YACA\n;19631;myfirstmod;钢铁;;\nmovie-discs;19632;minecraft_movie_discs;Minecraft 大电影插曲唱片;A Minecraft Movie Discs;\n;19633;wallpapermod;壁纸Mod;WallpaperMod;\n;19634;connect;以连结缘;Connect;\nmaterials-integration;19635;materials_integration;资源统合;Materials Integration;\n;19636;chatremindermod;聊天提醒;Chat Reminder Mod;CR\n;19638;rwfj;彩虹扳手;RainbowWrenchForJei;RWFJ\nmodular-machinery-reborn-create;19639;mmrcreate;Modular Machinery Reborn Create;;\nsheep-variety;19640;sheepvariety;Sheep Variety;;SV\n;19641;easydims;简单维度;;\n;19642;worldtweaks;世界调整;WorldTweaks;\n;19643;tetraclip;易验锻造;TetraClip;TC\n;19644;rozhud;RozHUD;;\nthe-cosmic-spokespersons;19645;the_cosmic_spokespersons;宇宙代言者;The Cosmic Spokespersons;TCSP\nall-in-zero;19646;allinzero;一切于零;All In Zero;AIZ\nyunzhu-transit-extension;19647;yte;云竹交通拓展;Yunzhu Transit Extension;YTE\n;19648;botania_retro;植物魔法：怀旧;Botania Retro;\ngoodbye-dirt-screen;19649;goodbye_dirt_screen;澄清素视 / 去你的泥土屏幕;Goodbye Dirt Screen;GDS\ncreate-stock-bridge;19650;createstockbridge;Create Stock Bridge;;\ncreate-escalated;19651;escalated;机械动力：自动扶梯;Create: Escalated;\ncreate-mechanical-confection;19652;createmechanicalconfection;机械动力：蛋糕工坊;Create: Mechanical Confection;CMC\nfarmers-delight-compat;19653;farmersdelightcompat;Farmer's Delight Compat;;\ngtwoodprocessing;19654;gtwp;GTWoodProcessing;;GTWP\n;19655;duradisplay;DuraDisplay;;\nvintagedisplay;19656;duradisplay;VintageDisplay;;\nvoidless-framework;19657;voidlessframework;Voidless Framework;;\nmixin-args;19658;mixin_args;Mixin Args;;\nmelonlib;19659;melonlib;MelonLib;;\n;19660;no-kebab;No Kebab;;\nmechanicals-lib;19661;mechanicals;Mechanicals Lib;;\ncreate-mechanical-cow;19662;mechanical_cow;Create Mechanical Cow;;\nglobal-options;19663;global_options;Global Options;;\nkubejs-projecte-fork;19664;;KubeJS ProjectE Fork;;\nuntoggle-sprint;19665;untoggle-sprint;Untoggle Sprint;;\nvndialog;19666;dialog;VN对话框引擎;VNDialog;VND\n;19667;fixelytrabug;elytraBugFix;;\n;19668;ezbowls;ezbowls;;\nsoundtrack-events;19669;Ambience;Soundtrack Events;;\ncommandkeys;19670;commandkeys;Command Keys;;\niron-chests-with-netherite-chest;19671;;Iron Chests (with Netherite Chest);;\nemerald-on-a-stick-villager-follower;19672;eoas;Emerald On A Stick;;\nfishing-indicators;19673;fishingindicators;Fishing Indicators;;\n;19674;mr_horses_canfloat;Horses can Float;;\njust-another-witchery-remake;19675;;Just Another Witchery Remake;;\n;19676;iorigins;Inexorable Origins;;iorigins\ncybersus;19677;cybersus;Cybersus;;\n;19678;passivearmour;PassiveArmour;;\nzettai-rpg;19679;zettai_rpg;Zettai RPG;;\nmd-units;19680;mindustry_units;MD Unit Robots;;\nextreme-core;19681;extremecore;EXTREME CORE;;\ndreamless-spells-and-spellbooks;19682;dreamless_spells;Dreamless Spells and Spellbooks;;\ngregtech-meowmelmuku;19683;;格雷科技非官方版 - 喵呜酱版;GregTech - MeowmelMuku;GTCEm\n;19684;namedloot;Name Loot;;\n;19685;enchantment_decay;Enchantment Decay;;\ncreate-jet-boots;19686;create_jet_boots;Create Jet Boots;;\ntrain-deco;19687;traindeco;机械动力：地铁列车装饰;Train Deco;\npacked-packs;19688;packed_packs;Packed Packs;;\ndyed-flames;19689;dyedflames;Dyed Flames;;DF\nhopper-gadgetry;19690;hoppergadgetry;Hopper Gadgetry;;HG\n;19691;mcmodsss;MC百科闪烁标语--;MCMOD Slogan--;MSSS\n;19692;msr,searchonmcmod;MC百科资料搜索：再重生Pro mini;MCMOD Item Search Reborn Again Pro mini;MISRAPM\nquickcopy;19693;quickcopy;迅摹天工;QuickCopy;QC\nitem-informations;19694;iteminf;物品信息;Item Informations;\nshootscreen;19695;shoot_screen;拍屏;ShootScreen;\n;19696;server_expansion;扫地小狐;Server Expansion;\n;19697;vsdaddon;瓦尔基里：防御;Valkyrien_Skies: defense;VSD\n;19698;elaphe_carinata;王锦蛇;Elaphe carinata;\ntinkerslashblade-patch;19699;tinker_slashblade_patch;刀锻冶匠魂修复;TinkerSlashBlade Patch;\nalienevo;19700;alienevo_pack;Alien Evolution;;\n;19701;ai_command;AI指令;AI Command;AIC\nbanit;19702;banit;干掉它！;BanIt;\nalexs-mobs-naturalist-compat;19703;alexsmobsnaturalistcompat;Alex's Mobs - Naturalist Compat;;\n;19704;pokeball;精灵球;Pokeball;\ntinkers-ingenuity;19705;tinkers_ingenuity;匠心之作;Tinkers' Ingenuity;\ncuriosities;19706;curiosities;趣味物品;Curiosities;\n;19707;;D更好的Schedule;D's Better Schedule;\n;19708;qis4c4;Qis的C4;C4;\nbomb-disposal-expert;19709;bde;拆弹专家;Bomb Disposal Expert;BDE\nsprintify;19710;sprintify;长疾速适;Sprintify;\nshield-system;19711;shield_system;Shield System;;\n;19712;burythelight;BuryTheLight;;\ncommand-aliases;19713;commandalias;Command Aliases;;\npigium-performance-mod;19714;pigium;Pigium - Performance;;\nspectrumjei;19715;spectrumjei;光谱世界JEI;SpectrumJEI;\n;19716;itemban;ItemBan;;\n;19717;skiesclear;SkiesClear;;\nvminus;19718;vminus;VMinus;;\n;19719;villagertalk;Villager Talk;;\ndebug-utils-forge;19720;debugutils;DebugUtils;;\nslimefun-essentials-port;19721;slimefun_essentials;Slimefun Essentials Port;;\numa-little-maid;19722;uma_maid;马儿小女仆;Uma Little Maid;ULM\ni-like-wood-twilight-forest-plugin;19723;ilikewoodxtwilightforest;I Like Wood - Twilight Forest Plugin;;\ni-like-wood-oh-the-biomes-youll-go-plugin;19724;ilikewoodxbyg;I Like Wood - Oh The Biomes You'll Go Plugin;;\ni-like-wood-immersive-engineering-plugin;19725;ilikewoodximmersiveengineering;I Like Wood - Immersive Engineering Plugin;;\nclick2pick;19726;click2pick;Click2Pick;;\nhudless;19727;hudless;Hudless;;\ndungeons-perspective;19728;dungeons_iso;Dungeons Perspective;;\nminiutilities-flight-fix;19729;;MiniUtilities Flight Fix;;\nlinked-chests;19730;linkedchests;Linked Chests;;LC\ncreate-direct-chute;19731;direct_chute;Create: Direct Chute;;\ncreate-packagers-psi-compat;19732;packagerspsic;Create: Packagers PSI Compat;;\ncreate-more-packages;19733;ccomunityboxes;Create More Packages / CComunityBoxes;;\ncreate-extra-gauges;19734;extra_gauges;机械动力：更多仪表;Create: Extra Gauges;\nships-legacy;19735;;Ships Legacy;;\ntraders-backport;19736;traders;Traders Backport;;\nbanner-patterns-backport;19737;patternbanners;Banner Patterns Backport;;\nconvenient-effects;19738;convenienteffects;Convenient Effects;;CE\nbaguettelib;19739;baguettelib;BaguetteLib;;\nfast-event;19740;fast_event;FastEvent;;\n;19741;citreforged;CIT Reforged;;\nbetter-meteorite;19742;better_meteorite;更好的陨石;Better Meteorite;BM\nparagraphs-reforged;19743;paragraphs;Paragraphs Reforged;;\nbetter-end-island-valkyrien-skies-2-fixer;19744;yungvscrystalfix;Better End Island Valkyrien Skies 2 Fixer;;\ntpsbooster;19745;tpsbooster;TPSBooster;;\ncustom-portal-api-reforged;19746;cpapireforged;Custom Portal API Reforged;;CPAR\nforgedpaginatedadvancements;19747;paginatedadvancements;分页进度 (Neo)Forge 版;ForgedPaginatedAdvancements;\ncolorfulanvils-reforged;19748;colorfulanvils;ColorfulAnvils Reforged;;\nkeymap-maintained;19749;keymap;Keymap [Maintained];;\ncreate-mechanical-companion;19750;createmechanicalcompanion;Create: Mechanical Companion;;\ncrazy-ae2-addons;19751;crazyae2addons;Crazy AE2 Addons;;\nschizophrenia;19752;schizophrenia;精神分裂症;Schizophrenia;\n;19753;rotp_mwp;Ripples of the Past: Mobs With Powers Addon;;\nwolf-sword-mod;19754;wolfswordmod;WolfSwordMod - Wolf equips Swords and Spins!;;\ntrue-survival-zombie-apocalypse-addon;19755;true;True Survival - Zombie Apocalypse;;TSZA\ncharged-mobs;19756;chargedmobs;Charged Mobs;;\nthe-mangled-hound;19757;themangledhound;The Mangled Hound;;\navp;19758;avp;AVP;;\n;19759;gts;Giantess Toki & More;;\nenchant-limiter;19760;enchant_limiter;Enchant Limiter (RPG Tweaks);;\ncrafter-backport;19761;crafter_port;Crafter Backport;;\ncitadel-fix;19762;citadel_fix;Citadel优化修复;Citadel Fix;\nknit;19763;knit;Knit;;\ngallant-tireless-altruist;19764;gallanttirelessaltruist;热心市民;Gallant Tireless Altruist;GTA\n;19765;dynamic-day-time;动态时间;Dynamic Day Time;DDT\ncerulean;19766;cerulean;Cerulean;;\ncolor-format;19767;colorformat;Color Format;;\nswim-in-lava;19769;lava_swim;Swim In Lava;;\norevolution;19770;good_ores;Orevolution;;\neven-better-nether;19771;evenbetternether;Even Better Nether;;\nplastoid-armor;19772;plastoid_armor;Plastoid Armor;;\ncorpse-x-curios-api-compat;19773;corpsecurioscompat;Corpse x Curios API Compat;;\n;19774;;Trinkets Canary;;\n;19775;aswwli;A Strange World We Live In;;ASWWLI\nspellhud-addon;19776;spellhud-addon;SpellHud Addon;;\nreplication-ae2-bridge;19777;rep_ae2_bridge;Replication AE2 Bridge;;\n;19778;deltarune;Deltarune mod;;\neternalpotions;19779;eternalpotions;Eternal Potions;;\n;19780;catclocks;L-Man's Cat Clocks;;\nlife-leech-enchantment;19781;lifeleech;生命汲取附魔;Life Leech Enchantment;\nsakaz-hand;19782;sakaz_hand;奇语之手;Sakaz Hand;\nclayium-unofficial;19783;clayium;粘土工业非官方版;Clayium Unofficial;\nmekanism-weaponry;19784;mekanism_weaponry;Mekanism Weaponry;;\nwardends-revenge;19785;wardendmod;Wardend's Revenge;;\nmob-control-rod;19786;mobcontrolrod;Mob Control Rod;;\nunlimitedperipheralworks;19787;peripheralworks;UnlimitedPeripheralWorks;;\nverdant-wonders;19789;verdant_wonders;Verdant Wonders;;\nunderlay;19790;underlay;Underlay;;\n;19791;torohealthmod;ToroHealth 伤害显示 GTNH 版;ToroHealth Damage Indicators-GTNH;\nlazy-item-generator;19792;lazy_item_generator_neoforge,lazy_item_generator,lazy_item_generator_1_19_2;咸鱼物品生成器;Lazy Item Generator;LIG\ncrab-street-light;19793;crab_street_light;螃蟹的路灯;Crab Street Light;CSL\nluncheon-meat-s-delight-bedrock;19794;;午餐肉乐事基岩版;Luncheon Meat 's Delight Bedrock (Unofficial);\nundertale-delight;19795;undertale_delight;决心乐事;Undertale Delight;UTD\nunusual-foods-delight;19796;unusualfoodsdelight;奇食乐事：重生;Unusual Food's Delight：Rebirth;\n;19797;oap;起源：算法幻影;Origins：Algorithmic Phantom;OAP\nir-hxd3c-a-8161-locomotive-resource-package;19798;atdhxd3ca8161;英雄运征程;Heroic Freight Voyage;HFV\nspawn-egg-information;19799;spawn_egg_information;刷怪蛋信息显示;Spawn Egg Infomation;SEI\ncalypsos-candy-workshop;19800;ccw;卡里普索的糖果工坊;Calypso's Candy Workshop;CCW\nfill-tool;19801;fillchevsky;Fill Tool;;\ngrowth-tonic;19802;tonicchevsky;Growth Tonic;;\nquivermob;19803;quivermobchevsky;Quiver Mob;;\n;19804;Explodables;Explodables;;\n;19805;ServerCommandGUI;Server Command GUI;;SCG\nworld-drop;19806;dropchevsky;World Drop;;\n;19807;MoreMeat2;MoreMeat 2;;\n;19808;FearTweakPack;TweakPack;;\nshell-armor;19809;armorchevsky;Shell Armor;;\ncart-livery;19810;CartLivery;Cart Livery;;\nterritorial-dealings;19811;territorychevsky;Territorial Dealings;;\npotion-packs;19812;packchevsky;Potion Packs;;\narmor-smelter;19813;smeltchevsky;Armor Smelter;;\n;19814;StealthwareCore;StealthwareCore;;\ncarpet-sls-addition;19815;carpet-sls-addition;Carpet SLS Addition;;\nforge-string-unpatched-powered-permanent-string;19816;stringunpatched;(Neo)Forge 常亮绊线特性恢复;Forge String Unpatched - Powered Permanent String;FSU\nquests-kill-task-tweaks;19817;questkilltask;Quests Kill Task Tweaks;;\n;19818;lpctools;LPCTools;;\nimmersive-messages-api;19819;immersivemessages;Immersive Messages API;;\nimmersive-damage-indicators;19820;immersivedamageindicators;Immersive Damage Indicators;;\neasy-disenchanting;19821;easydisenchanting;Easy Disenchanting;;\nrespect-my-trims;19822;respectmytrims;Respect My Trims;;\n;19823;zoom;Sodium Zoom;;SZ\ncloudgolem-edited;19824;cloudgolem_edited;湫渃云魔修改;CloudGolem Edited;\nmaidaddition;19825;maidaddition;MaidAddition;;\nwebcam;19826;webcam;网络摄像头;Webcam;\nprojecteteams;19827;peteams;ProjectETeams;;PETEAMS\nbattle-frenzy;19828;battle-frenzy;越战越勇;Battle Frenzy;\n;19829;planeadvancements;Plane Advancements;;\ncosmopolitan;19830;cosmopolitan;四海一家;Cosmopolitan;cosmo\nredtassel;19831;redtassel;红缨;redtassel;\nsridr;19832;sridr;拔刀剑锻造增伤改革;SlashBlade Refine Increased Damage Reform;SRIDR\n;19833;trolnpc;永恒枪械工坊：甲匠;Timeless and Classics Zero: Shield;TaC:S\nmianbaos-modernwarfare;19834;mianbaos_modernwarfare;面包军火库;Mianbao's ModernWarfare;\ncreator-sword;19835;createsword;机械动力：机械师之剑;Create: Creator Sword;\ncreate-limited-draining;19836;createlimiteddraining;机械动力：流体抽取限制;Create: Limited Draining;\ncreate-no-touching;19837;create_no_touching;Create: No Touching;;\ncreate-aether-industry;19838;createaether;机械动力：天境工业;Create Aether Industry;\ncreate-entitycontroller;19839;createentitycontroller;机械动力：实体控制;Create: Entity Control;CEC\nbetter-advancements-blessed-edtion;19840;;更好的进度 - 祝福版;Better Advancements - Blessed Edtion;\n;19841;gloom_raiders;Gloom Raiders;;\nherobrine-prime;19842;herobrine_prime;Herobrine Prime;;\nlegend-of-the-dwerden;19843;legend_of_the_dwerden;Legend of The Dwerden [The Dwerden Reborn];;\ndnt-stronghold-overhaul-lite-edition;19844;mr_dnt_strongholdoverhaulliteedition;DnT Stronghold Overhaul LITE Edition;;\ndnt-nether-fortress-overhaul;19845;mr_dungeons_andtavernsnetherfortressoverhaul;Dungeons and Taverns Nether Fortress Overhaul;;\ngtfocraftprotocol;19846;gtfo_craft;GTFO Craft: Protocol;;\ntrue-survival-zombie-apocalypse-lite;19847;;True Survival - Zombie Apocalypse LITE;;TSZALITE\nkeerdm-zombie-apocalypse-essentials;19848;keerdm_zombie_essentials;Keerdm Zombie Apocolypse Essentials;;\nchovys-apocalypse;19849;chovys_apocalypse_mod;Chovy's Apocalypse;;\nbexlas-enhanced-ores;19850;enhancedore;Bexla's Enhanced Ores;;\nstarve-library;19851;slib;饿死图书馆;Starve Library;SLib\n;19852;autosweep;Autosweep;;As\n;19853;limitless-enchantment;附魔大师;Limitless Enchantment;\ninfinite-refills;19854;infinite_refills;无限续杯;Infinite Refills;IR\nsystrace;19855;systrace;一导即出;Systrace;STE\nfluxed-core;19856;fluxedcore;Fluxed-Core;;\nmyluckyblock;19857;myluckyblock;我的幸运方块;MyLuckyBlock;MLB\n;19858;Canal;水渠;Canal;\nbackpack-plus;19859;backpackplus;背倍加;Backpack Plus;\n;19860;hydration;Hydration;;\n;19861;gzrex;蛊真人扩展;;\ndefiled-lands-reborn;19862;defiled_lands_reborn;污秽之地重置版;Defiled Lands Reborn;\n;19863;bulb-early;铜灯补充;Copper Bulb Early;\nfuture-fireproof;19864;futurefireproof;未来防火;Future Fireproof;\n;19865;lowdurabilityswitcher;Low Durability Switcher;;\ndimensional-potions;19866;mr_dimensional_potions;Dimensional Potions;;\nghasts-drop-records;19867;gastsdroprecords,ghastsdroprecords;恶魂掉落唱片;Ghasts Drop Records;\nftb-chunks-compatibility-mod;19868;ftbchunkscompatibility;FTB Chunks Compatibility;;\ncharm-forked;19869;charm;Charm Forked;;\nearly-bedtime;19870;earlybedtime;Early Bedtime;;\nmqs-appleskin;19871;;MQ的苹果皮;MQ's AppleSkin;\ninstant-picking;19872;instantpicking;Instant Picking;;\n;19873;quick-craft;Quick Craft;;\nkubejs-curios;19874;kubejs_curios;KubeJS Curios;;\ncopper-carrots;19875;coppercarrots;Horizontal Items;;\ntinkers-jewelry;19876;tinkersjewelry;工匠珠宝;Tinkers' Jewelry;\nthief;19877;thief;Thief;;\napplied-schematicannon;19878;appliedschematicannon;Applied Schematicannon;;\nmore-functional-storage;19879;morefunctionalstorage;更多功能性存储;More Functional Storage;\nvital-herbs;19880;vital_herbs;Vital Herbs;;\n;19881;escape_assist_module_phase_1;蛊真人扩展蛊虫;;\nhbm-ntm-structure-1-12-2;19882;ntmdopolnenie;HBM/NTM Structure;;\nillusioners;19883;illusioners;Illusioners;;\ncataclysms;19884;cataclysm;Cataclysms;;\nbas-cave-enemies;19885;bcenemies;Ba's Cave Enemies;;\nsniffer-ride;19886;snifferride;Sniffer Ride;;\naesthetic-shelving;19887;aestheticshelving;Aesthetic Shelving;;\narboreal-nature;19888;arborealnature;Arboreal Nature;;\n;19889;mr_stellarity_lite;Stellarity ~ Lite;;\nwarborn-resurgence;19890;mandatory;Warborn Resurgence;;\n;19891;ceres;Ceres;;\n;19892;affix;词缀;Affix;\nsuper-creeper;19893;super_creeper;智能爆破专家;Creeper Super;CS\nexpanded-experience;19894;expanded_experience;Expanded Experience;;\nreworked-phantoms;19895;reworkedphantoms;Reworked Phantoms;;\nex-nihilo-fabricio;19896;ex-nihilo-fabricio;Ex Nihilo Fabricio;;\nhybrid-delights;19897;hybrid-delights;Hybrid Delights;;\ncreate-clothes;19898;createclothes;机械动力：服装;Create: Clothes;\ncreate-delivery-director;19899;delivery_director;Create: Delivery Director;;\ncreate-cardboard-things;19900;createcardboardthings;Create: Cardboard Things;;\n;19901;svfe;见钱眼开Again;Smart Villagers Follow Emeralds Again;\nwatkinsbase;19902;watkinsbase;WatkinsBase;;\nfarmer-s-delight-bedrock-unofficial-port;19903;;Farmer's Delight Bedrock (Unofficial Port);;\nnethers-delight-bedrock-unofficial-port;19904;;Nether's Delight Bedrock (Unofficial Port);;\nends-delight-bedrock-unofficial-port;19905;;End's Delight Bedrock (Unofficial Port);;\ntoggleable-enchantments;19906;toggleable_enchantments;Toggleable Enchantments;;\n;19907;hgwsspyglasses;Hgw的望远镜-乙;Hgw's Spyglass-B;HSB\n;19908;custom_villager_cure;自定义村民治愈;Custom Villager Cure;\n;19909;sorry-we-dont-have-enough-funds-to-provide-you-with-an-ender-dragon-lite-edition;对不起，我们没有充足的资金为您提供一只末影龙-精简版;Sorry We Don't Have Enough Funds To Provide You With An Ender Dragon-Lite Editon;SWDHEFTPYWAED-LE\nscape-and-run-parasites-deep-sea-danger;19910;srpdeepseadanger;Scape and Run: Parasites Deep Sea Danger;;\n;19911;fireballgame;火焰弹魔法;Fireball Magic;\ncraft-in-shadow;19912;craft_in_shadow;Craft In Shadow;;\n;19913;mr_jewelryz;JewelryZ;;\nbetter-armory-mod;19914;better_armory_mod;Seth's Better Armory;;\ntwilight-forest-dungeons-villages;19915;tf_dnv;暮色森林 - 地牢与村庄;Twilight Forest - Dungeons & Villages;\nmice;19916;mice;Mice;;\nancient-golems;19917;ancient_golems;上古傀儡;Ancient Golems;\nmodel-citizens;19918;modelcitizens;Model Citizens;;\nbotanical-machinery-extra;19919;botanicalextramachinery;Botanical Machinery Extra;;\nflowerary;19920;flowerary;Flowerary;;\nextrabotany-reburn;19921;extrabotany;额外植物学：重燃;Extrabotany: Reburn;EXBOT\npineapple-delight-bedrock-unofficial;19922;;凤梨乐事基岩版;Pineapple Delight Bedrock (Unofficial);\nsunflower-delight-bedrock-unofficial;19923;;向日葵乐事基岩版;Sunflower Delight Bedrock (Unofficial);\nfastfood-delight-bedrock-unofficial;19924;;快餐乐事基岩版;FastFood Delight Bedrock (Unofficial);\numapyoi-delight;19925;umapyoidelight;马儿跳乐事;Umapyoi Delight;\nlne-rogues;19926;lne_rogues;LNE Rogues Add-On;;\nlne-archers;19927;lne_archers;LNE Archers Add-On;;\nlne-wizards;19928;lne_wizards;LNE Wizards Add-On;;\nlne-paladins;19929;lne_paladins;LNE Paladins Add On;;\n;19930;pmweather;ProtoManly's Weather;;\ntinkers-mechworks-legacy;19931;;Tinkers' Mechworks (Legacy 1.12.2);;\nbbl-core;19932;bblcore;BBL Core;;\ncooparticlesapi;19933;cooparticlesapi;空栈粒子框架;CooParticlesAPI;CPAPI\nfdlib;19934;fdlib;FDLib;;\npotion-icons;19935;potionicons;Potion Icons;;\ncatvision;19936;cat_vision;CatVision;;\nspectre-things;19937;spectrethings;Spectre Things;;\nchatplus;19938;chat-plus;Chat Plus;;\nritual-enchanting;19939;ritualenchanting;Ritual Enchanting;;\npeaceful-progression;19940;peaceful-items;Peaceful Progression;;\n;19941;antiwurst;AntiWurst;;AW\nusefulmagic;19943;usefulmagic;实用魔法;UsefulMagic;UM\nkeepinventorynext;19944;keepinventorynext;死亡不掉落增强;Keeping Inventoiry Next;\n;19945;ftbultimine_indicator;连锁破坏指示器;FTB Ultimine Indicator;\ntinkers-jewelry-ex;19946;tinkers_jewelry_ex;工匠珠宝拓展;Tinkers' Jewelry EX;\nlovely-sparkle-pieces;19947;lovely_sparkle_pieces;璀璨饰品;Lovely Sparkle Pieces;LSP\n;19948;summerduanyang;粽夏端阳;ZongCraft Festival;\nyuns-weather;19949;weather;Yun's Weather;;\nqliphoth-awakening;19950;fdbosses;逆卡巴拉：觉醒;Qliphoth Awakening;\n;19951;fry;锻汝砧上重生;ForgeYourselfReborn;FYR\n;19952;starvedelight;饿死乐事;Starve Delight;SD\nuniversal-mod-localizer;19953;uml;通用模组汉化器;Universal Mod Localizer;UML\nsmart-creeper;19954;smartcreeper;智慧苦力怕;Smart Creeper;\npeaceful-depletion;19955;peaceful_depletion;和平消耗;Peaceful Depletion;PD\n;19956;everything-is-tnt;Everything is TNT;;\nstructure-pool-api;19957;structure_pool_api;Structure Pool API;;\ncreate-bluemap;19958;create-bluemap;Create BlueMap;;\ntragicmc-2-continuation;19959;;悲惨世界2：延续;TragicMC 2 Continuation;\nnebula-parasites;19960;nebulaparasites;Nebula: Parasites;;\nbonsai-trees-unofficial-extended-life;19961;bonsaitrees;盆栽非官方延续版;Bonsai Trees Unofficial Extended Life;\n;19962;its_as_shrimple_as_that;It's as Shrimple as That;;\nworldedit-items;19963;worldedit_items;WorldEdit Items;;\nslashblade-skin;19964;slashbladeskin;拔刀皮肤;SlashBlade Skin;SBS\niron-door-key;19965;irondoorkey;Iron Door Key;;\njust-enough-fishing-jef;19966;justenoughfishing;Just Enough Fishing;;JEF\ncontainersearch;19967;containersearch;Container Search;;\nmousepets;19968;mouse_pets;MousePets;;\nbackported-minecarts;19969;minecart-backport;Backported Minecarts;;\nsimple-incubator;19970;simple_incubator;Simple Incubator;;\namethyst-stairs-slabs-and-walls;19971;amethyst_expansion;Amethyst Stairs, Slabs, and Walls;;\n;19972;vessels;Vessels;;\nultimate-scaler;19973;ultimatescaler;Ultimate Scaler;;\nlegendary-weapons-byititor;19974;legendarymod;Legendary Weapons;;\nslender-reborn;19975;slenderreborn;Slender Reborn;;\ncraft-vania-lite;19976;craftvanialite;Craft-Vania Lite;;\npollution-of-create;19977;pollutionofcreate;Pollution Of Create;;\ncreate-chocolate-fountain;19978;create_chocolate_fountain;机械动力：巧克力喷泉;Create: Chocolate Fountain;\ncreate-currency-shops;19979;create_currency_shops;机械动力：货币商店;Create: Currency Shops;\ncreate-nether-industry;19980;createnetherindustry;机械动力：下界工业;Create: Nether Industry;CNI\nbackported-diamond-ore-gen;19981;backporteddiamondgen;Backported Diamond Ore;;\ntfthreadsafetyaddon;19982;tfthreadsafetyaddon;TwilightForest Thread Safety Addon;;\nserverautosaver;19983;auto_save_mod;ServerAutoSaver;;\ninfinistack;19984;infinistack;无限堆叠;InfiniStack;\npowerful-dummy;19985;powerful_dummy;强力假人;Powerful Dummy;PD\nbeyond-bosses;19986;bb;Beyond Bosses;;\nmini-utilities-y;19987;;Mini Utilities Y;;\nconcoction;19988;concoction;田园调酿！;Concoction！;\nzombietactics2;19989;zombietactics2;Zombie Tactics 2;;\nradiation-zone-reborn;19990;radiation_zone_reborn;辐射荒原：再行;Radiation Zone: Reborn;\n;19991;blocker;Blocker;;\n;19992;crumbling_hearts;Crumbling Hearts;;\nmca-master-controller-add-on;19993;mca;Master Controller Addon/Minecraft Transportation Controller;;MCA/MTC\ndye-the-world;19994;dye_the_world;Dye The World;;\nmodernmarkings;19995;ags_modernmarkings;ModernMarkings;;\nhololive-product;19996;holoproduct;Hololive Product;;\nfetzis-displays;19997;fetzisdisplays;Fetzi's Displays;;\nshadered;19998;shadered;Shadered;;\nsweety-garden;19999;sweety_garden;Sweety's Garden;;\n;20000;paintingofmcmod;百科艺术;PaintingOfMCMOD;\ntwilight-forest-aurora-palace-regeared;20001;gearsaw_palace;Twilight Forest - Aurora Palace Regeared;;\nchangshengjue;20002;chang_sheng_jue;长生诀;ChangShengJue;CSJ\nnethergates;20003;nethergates;Nethergates;;\nmedieval-buildings-nether-edition;20004;medieval_nether;Medieval Buildings [Nether Edition];;\n;20005;ribbits_croaks;Ribbits: Croaks;;\nechoes-of-time;20006;echoes_of_time;Echoes Of Time;;\nhorror-alexbrine;20007;alexbrinereturn;Horror - Alexbrine;;\nzzz-craft;20008;zzz;绝区零·星见雅;ZZZ Miyabi;\n;20009;;起源计划;Origins Program;\ncozy-cabins-and-cottages;20010;cozy_cabins_and_cottages;Cozy Cabins and Cottages;;\nservereye;20011;servereye;ServerEye;;SE\ndeepseek;20012;deepseek;DeepSeek;;DS\nai-command;20013;aicommand;AI指令生成;AI Command Generator;AICG\n;20014;ailocalizer;AI汉化器;AILocalizer;AIL\nflighthud-do-a-barrel-roll-fix;20015;flighthud;FlightHUD: Do a Barrel Roll fix;;\n;20016;pca-protocol;PCA-protocol;;\nfast-container-placement;20017;fast-container-placement,fast_container_placement;快速容器放置;Fast Container Placement;FCP\n;20018;teleport-mod;Teleport Mod;;\n;20019;what-meme;What Mod;;\ncrackers-wither-storm-mod-v-sides-soundtrack;20020;cwsm_soundtrack__vsides;Cracker's Wither Storm Mod: V-Sides Soundtrack;;\n;20021;od_updater;初梦更新资源同步;ODUpdater;ODU\nroberts-game-tweaks;20022;roberts_game_tweaks;Robert的游戏调整;Robert's Game Tweaks;RGT\n;20023;aifix;Chromaticraft-AIFixes;;\ndatapackloaderrorfix;20024;datapackloaderrorfix;数据包错误修复：重生;Datapack Load Error Fix Reborn;DLEFR\n;20025;whatsthis;Whats This;;\n;20026;fin_additional_jewelryz;Additional JewelryZ;;\ntacz-tes-compat;20027;taczdd;TaCZ: TES Compat;;\nquality-of-life-features-for-vics-point-blank;20028;;Quality of Life Features for Vic's Point Blank;;\nday-counter;20029;mr_dc_xbsc0kgo;Day Counter;;\ndirectionhud;20030;DirectionHUD;DirectionHUD;;\nraw-netherite-reforged;20031;raw_netherite;Raw Netherite Reforged;;\nblast-travel-reborn;20032;blasttravelreborn,blasttravel;旅行火炮·重生;Blast Travel Reborn;BTR\nshanxi-skeleton;20033;shanxiskeleton;山西骷髅;Shanxi Skeleton;SS\n;20034;gunpowder_overhaul;火药改革;Gunpowder Overhaul;GPO\nsmoking-stuff-a-wizards-hobby;20035;smokingstuff;Smoking Stuff - A Wizard's Hobby;;\n;20036;alloy_smelter;Alloy Smelter;;\n;20037;crafter;Autocrafter Early;;\ntime-in-a-bottle-charger;20038;tiab_charger;Time In A Bottle Charger;;\njust-a-sculk-vial;20039;justasculkvial;Just A Sculk Vial;;\nlockylocks;20040;lockylocks;LockyLocks;;\nserver-i18n-api;20041;server_i18n_api;Server I18n API;;\nfragmentum-forge;20042;fragmentum;Fragmentum;;\n;20043;monumentdatafix;Monument 数据修复;Monument Data Fix;\n;20044;better_tp;Better TP;;\nwaystone-currencys;20045;wscurrencys;消币则至 / 传送石碑：货币;Waystone: Currencys;\n;20046;armorstand;盔甲架;ArmorStand;\nthreadtweak-reforged;20047;;ThreadTweak (Neo)Forge 移植版;ThreadTweak Reforged;\n;20048;environmentalcreepers;Environmental Creepers Unofficial Backport;;\ncom-bat;20049;com_bat;Com-Bat;;\ntouhou-little-maid-rpg-class-task;20050;maid_rpg_task;Touhou Little Maid RPG Class Task;;\nunknown5473lib;20051;unknown5473lib;Unknown5473lib;;U5473\nheat-and-climate-patch;20052;dcs_climate_patch;热量与气候修复;Heat And Climate Patch;HACP\n;20053;dragoncoreresourcesync;DragoncoreResourceSync;;DRS\n;20054;clientupdater;Client Updater;;\n;20055;alienday;我蛮夷也;Alien Day;\nmodern-lucky-block;20056;lucky;Modern Lucky Block;;MLB\nfoxrecipemaker;20057;reciper;FoxRecipeMaker;;\n;20058;globalhealthmod;全局生物血量倍率;Global Mob Health Multiplier;\nchatbox-for-mc;20059;chatbox;对话框;ChatBox;\nperfectly-balanced-bows;20060;perfectlybalancedbows;Perfectly Balanced Bows;;\n;20061;;PlayerEx 酒精版;PlayerEx: Alcohol;\nhak-minepiece;20062;hakminepiece;HakminePiece;;\ngrease;20063;grease;Grease;;\ncleaver-compedium;20064;cleaver_compedium;Cleaver Compedium;;\nphayriosis-parasite-infection;20065;phayriosis;Phayriosis Parasite Infection;;PHA\nsrp-calamity-catastrophe-cataclysm;20066;srpcalamity;逃寄：无妄之灾;SRP: Calamity Catastrophe Cataclysm;SRP:CCC\nmoss-overgrowth;20067;mossovergrowthregrown;Moss Overgrowth;;\ncreate-pillager-arise;20068;create_pillagers_arise;Create: Pillager Arise;;\ncreate-qol;20069;createqol;Create: Quality Of Life;;\ncreate-remote-terminal;20070;createterminal;机械动力：远程终端;Create: Remote Terminal;\nfallen-fungus;20071;mr_fallen_fungus;Fallen Fungus;;\n;20072;Aviancraft_Penguins;鸟语星球_企鹅目;Aviancraft: Penguins;\nsongs-of-war;20073;sow;战争之歌;Songs of War;SoW\ncataclysm-summons;20074;cs;Cataclysm Summons;;\ncutepuppymod;20075;cutepuppymod;CutePuppyMod;;\ngiant-swamp-tree;20076;giant_swamp_tree;沼泽巨树;Giant Swamp Tree;\nbig-oak-tree;20077;big_oak_tree;大橡树;Big Oak Tree;\ngiant-tree-stump;20078;giant_tree_stump;大树桩;Giant Tree Stump;\n;20079;;船长皮卡丘;Pikachu-Captain[Cobblemon];\npolars-mad-tweaks;20080;polars_mad_tweaks;Polar's Mad Tweaks;;\ncobblemon-ranked;20081;cobblemon_ranked;方块宝可梦-排位赛;Cobblemon Ranked;\neiramoticons;20082;eiramoticons;Chat Emotes (formerly EiraMoticons);;\nhorse-breeding-fix;20083;horse-breeding-fix;Horse Breeding Fix;;\ntablistextension;20084;tablistextension;TAB 列表扩展;TabListExtension;TLE\nrally-of-the-guard-guardvillagers;20085;rallyguard;Rally of the Guard;;\nbncci;20086;brutalnightmarecci;Brutal Nightmare Config Integration;;\n;20087;recipemaker;Recipe Maker;;\nportaltransform;20088;portaltransform;传送门嬗变;PortalTransform;PT\n;20089;morepaintings;更多的画;More Paintings;MP\nunusual-furniture;20090;unusual_furniture;Unusual Furniture;;\nxiaoanmooncake;20091;xiaoanmooncake;小安的月饼狂热;Xiaoan's Mooncake Fever;\nforagers-insight;20092;foragers_insight;巧觅巧食;Forager's Insight;\npalestinian-delight;20093;paldelight;Palestinian Delight;;\nmekanism-more-machine;20094;mekmm;通用机械：更多机器;Mekanism: More Machine;MekMM\ncustom-battleroyale;20095;battleroyale;自定义大逃杀;Custom BattleRoyale;CBR\nimproved-pillager-outpost;20096;ipo;Improved Pillager Outpost;;\nrebalanced-enchanting;20097;rebalancedenchanting;Rebalanced Enchanting;;\ndark-doppelganger;20098;darkdoppelganger;Dark Doppelganger;;\nenders-spellsandstuff;20099;gtbcs_spell_lib;Ender's Spells and Stuff;;\nhazen-n-stuff;20100;hazennstuff;Hazen 'N Stuff;;\nspongeforge;20101;;SpongeForge LTS;;SFLTS\n;20102;fireball-boom;仿布吉岛烈焰弹;Fireball Boom;\n;20103;dont_die;补药趋势啊！;Don't Die!;DTD\nsdm-market;20104;sdm_market;SDM Market;;\n;20105;omnilook;Omnilook;;\n;20106;better_torh;村民烧烤匠职业;Better Torh;\nsurvivalist-essentials;20107;survivalistessentials;Survivalist Essentials;;\nvillager-trading-plus;20108;villager_trading_plus;Villager Trading Plus;;\nmending-fix;20109;mendingfix;经验修补修复;Mending Fix;\n;20110;chatai;chatAI;;\ncolorwheel;20111;colorwheel;Colorwheel;;\ntlib;20112;tlib;TLib;;\nsmoothscreen;20113;smoothscreen;Smooth Screen;;\n;20114;hv;良商;HappyVillagers;HV\n;20115;fuzz;Fuzz;;\nadvanced-loot-info;20116;ali;高级战利品信息显示;Advanced Loot Info;ALI\nresourcepackcached;20117;rpc;ResourcePackCached;;RPC\naotake-sweep;20118;aotake_sweep;竹叶清;Aotake Sweep;AS\n;20119;guilib;Davidee's GUI Library;;\neffectual;20120;effectual;Effectual;;\ncold-light;20121;cold_light;冷晖之枪;Cold Light;CL\n;20122;llpswzombie;llpsw的丧尸乐园;llpswZombie;lzom\nhuanles-weapons;20123;huanle;Huanle的武器;Huanle's weapons;HW\n;20124;csr;Clay Soldiers Remake;;CSR\nthreateningly-mobs;20125;threateningly_mobs;Threateningly Mobs;;\nbasic-spinosaurus;20126;basicspinosaurusmod;Basic Spinosaurus;;\neuropean-wildlife;20127;europeanwildlife;European Wildlife;;\nmarvelous-menagerie;20128;marvelous_menagerie;Marvelous Menagerie;;MM\nroman-weaponary;20129;roman_weaponary;Roman Weaponary;;\ncompat-structure;20130;compatstructures;Compat Structure;;\nflower-bed;20131;flowerbedmod;Flower Bed;;\narchery-expansion-squared;20132;archexpsquared;Archery Expansion Squared;;\ncreate-more-linked-remote;20133;cmlinkedremote;Create More: Linked Remote;;\n;20134;ender_bags;末影袋;EnderBags: Continue;\n;20135;MITE-Legend;MITE魂环传说;MITE-Legend;\ngambagoons-better-emeralds;20137;betteremeralds;GambaGoon's Better Emeralds;;\ningameime-vintage;20138;ingameime;游戏内输入法-1.12.2;IngameIME-Vintage;IMEV\nextreme-sound-muffler-legacy;20139;;Extreme Sound Muffler: Legacy;;ESM:Legacy\namazing-trophies;20140;amazingtrophies;Amazing Trophies;;\n;20141;;基岩版灵魂出窍;Out-of-Body;\n;20142;revengeance;复仇模式;Revengeance;Rvg\nglobal-server-configs;20143;global_server_config;全局服务器配置;Global Server Configs;\nexpandedbonemeal;20144;expandedbonemeal;ExpandedBonemeal;;\n;20145;aira;AI回复助手;AIReplyAssistant;aira\nutiljs;20146;utiljs;UtilJS;;\nmoon-info-updated;20147;moonphaseu;Moon Info Updated;;\n;20148;server-market;服务器市场;Server Market;\nnbt-tooltip;20149;nbttooltip;NBT Tooltip;;\ndelicate-dyes;20150;delicate_dyes;Delicate Dyes;;\n;20151;modernmixins;Modern Mixins;;\nsdm-economy;20152;sdm_economy;SDM Economy;;\nsdm-ui-lib;20153;sdm_ui_lib;SDM UI Lib;;\nmcllm;20154;mcllm;MCLLM;;\n;20155;inventorypausemod;Inventory Pause (Fabric 1.20.1);;\nchestsearch;20156;chestsearch;容器检索;ChestSearch;\nclicktor;20157;clicktor_autoclicker;Clicktor连点器;Clicktor - Auto Clicker;CAC\nextended-bone-meal;20158;extendedbonemeal;Extended Bone Meal;;\nshow-me-the-enchantment;20159;showmetheenchantment;怎魔，你不附器;Show Me The Enchantment;\nbetter-sign-edit;20160;better-sign-edit;Better Sign Edit;;\ndisc-recipes;20161;craftable_discs;唱片合成;Disc Recipe;\n;20162;;更多背包 GTNH 版;Backpacks - GTNH / Backpack Editted for ModdedNetwork;\n;20163;backportsupplement;移植补充;backportsupplement;BS\nscoutreforked;20164;scoutreforked;侦察兵 重构;Scout Reforked;\nimmersion-in-traffic-context;20165;immersion_in_traffic_context;沉浸交通道路;Immersion in Traffic Context;ITC\narknightsfurniture;20166;arknights-furniture,arknights_furniture;明日方舟家具;ArknightsFurniture;\ngunsmith-lib;20167;gunsmithlib;Gunsmith Lib;;\n;20168;emixx;EMI++;;\nexperiencelib;20169;experiencelib;ExperienceLib;;\nmystical-mobs;20170;mysticalmobs;Mystical Mobs;;\n;20171;gucreative;蛊真人扩展;gucreative;GFKZ\nprogressive-difficulty-chaos-integration;20172;difficultychaosintegration;Progressive Difficulty: Chaos Integration;;\nottapomos-scps;20173;ottapomo_scps;Ottapomo's SCPs;;\nscp-foundation-requisites;20174;scpnewblocksandstairs;SCP: Foundation Requisites;;SCP:FR\nthermal-construct;20175;thermalconstruct;Thermal Construct;;\nwar-wither;20176;warwither;战争凋灵;War Wither;\nstatus-effect-bars-reforged;20177;statuseffectbars;Status Effect Bars Reforged;;\ndark-tower-maze;20178;chainmail;Dark Tower Maze;;\nthe-occult;20180;occult;The Occult;;\nwolftailui;20181;wolftailui;WolfTailUI;;WUI\nbetter-client;20182;better_client;更好的客户端;Better Client;\nclientharvest;20183;clientharvest;ClientHarvest;;\nauto-afk;20184;autoafk;Auto AFK;;\nparticle-effects-reforged;20185;;Particle Effects Reforged;;\nhold-my-items-refoxed;20186;holdmyitems;Hold My Items - ReFoxed;;\ndont-claim-our-structures;20187;dont_touch_our_structures;Don't Claim our Structures ! (FTB Chunks/OPaC);;\ncosy-critters-creepy-crawlies;20188;cosycritters;Cosy Critters & Creepy Crawlies [Neo];;\nthin-air-ad-astra-compat;20189;thinairastra;Thin Air - Ad Astra Compat;;\nyyzsbackpack;20190;yyzsbackpack;yyz的背包;Yyz's Backpack;\n;20191;i_want_to_die_at_home;I want to die at home：重生！;I want to die at home: Rebirth!;\nvital-delight;20192;vital_delight;Vital Delight;;\n;20193;planties_delight;莳蔬乐事;Planties Delight;\n;20194;mininginplace;Mining In Place;;\npathfinder-navigation;20195;pathfinder_navigation;寻路者导航;Pathfinder Navigation;PFN\nadorable-eggs;20196;adorable_eggs;Adorable Eggs;;\nfood-addition;20197;foodaddition;Food Addition;;\neverything-totem;20198;everything_totem;万物皆可图腾;Everything Totem;\nthaumic-renaissance;20199;TCReborn;Thaumic Renaissance;;\n;20200;executive;Executive;;\n;20201;makei18ngreatagain;Make I18N Great Again;;MIGA\nsteve-goes-fishing;20202;stevegoesfishing;Steve Goes Fishing;;\nvoltaic-api;20203;voltaic;Voltaic;;\nboat-delete-begone;20204;boatdeletebegone;Boat Delete Begone;;\nzs-packet;20205;zspacket;ZS Packet;;\nraise-them-hotbars;20206;hotbarslider;Raise Them Hotbars;;\nquick-leafdecay;20207;quickleafdecay;Quick LeafDecay+;;\nfootprint-particle-returns;20208;;Footprint Particle Returns;;\ninventory-crafter-returns;20209;inventory_crafter;Inventory Crafter Returns;;\ncolorwheel-patcher;20210;colorwheel_patcher;Colorwheel Patcher;;\n;20211;fancyhealthbar;Fancy Health Bar：重生;Fancy Health Bar: Rebirth;\n;20212;eggtab;独立刷怪蛋列表：重生;Egg Tab Rebirth;\n;20213;netzach;胜利;Netzach;NLib\n;20214;llpswsounds;llpsw的大喇叭;llpswsounds;lsound\ndifficulty-lock;20215;difficultylock;Difficulty Lock;;\nryymr;20216;ryymr-fabric;ryymr;;\n;20217;lazybeefix;LazyBeeFix;;\n;20218;jeiae2compact;JEI AE2 Compact;;\nlazy-ae2-tweaks;20219;lazyae2tweaks;Lazy AE2 Tweaks;;\ncoordinates-hud;20220;coordhud;Coordinates HUD;;\njust-enough-botania;20221;botaniajei;Just Enough Botania;;\nfumo-fumo;20222;fumofumo;Fumo Fumo;;\ntouhoupixelcanteen-a-farmers-delight-plugin;20223;gensokyoizakaya,touhoupixelcanteen;幻雀妖;Gensokyo Izakaya;\nperfect-parity-the-garden-awakens-edition;20224;perfect-parity-pg;Perfect Parity: The Garden Awakens Edition;;\nperfect-parity-spring-to-life-reforged;20225;;Perfect Parity: Spring To Life Reforged;;\nhypertubes;20226;create_hypertube;机械动力：超级管道;Create: Hypertubes;\nperfect-parity-spring-to-life-edition;20227;perfectparity;Perfect Parity: Spring To Life Edition;;\ncreate-geography;20228;creategeography;机械动力：地理学;Create: Geography;CG\ncreate-apothic-enchanting;20229;create_apothic_enchanting;Create: Apothic Enchanting;;\ndemis-enigmatic-dice;20230;enigmaticdice;Demi's Enigmatic Dice;;\n;20231;kiss-mod,kiss-mod-transplant;Kiss Mod：移植;Kiss Mod Transplant;\n;20232;mpem;月夜性能优化;Moonlit Performance Enhancement Module;MPEM\n;20233;eventbenchmark;事件性能测试;EventBenchmark;EB\nno-more-garbage-in-cell;20234;nmgic;No More Garbage In Cell;;NMGIC\npick-up-filter-puf;20235;pickupfilter;Pick Up Filter;;PUF\n;20236;mekanism_rc;通用机械配方兼容;Mekanism Recipe Compatibility;\nidentity-fix-morph;20237;;Identity Fix (Morph);;\n;20238;player_action_monitor;玩家行为监控;Player Action Monitor;PAM\nstick-it;20239;stickit;Stick It!;;\naimod;20240;aimod;AI Chat Mod;;AIC\nepca;20241;epca;终末-归寄万物;End-Parasitize and Convert All;E-PCA\nlegendary-relics;20242;legendary_relics;传说遗物;Legendary Relics;LR\n;20243;fpsbio;第一人称射击生化房间;First Person Shooter Biological Warfare;FBIO\ntrue-power;20244;truepower;真正的力量;True POWER;\nender-villages;20245;endervillages;末地村庄;Ender Villages;\ntwilight-forest-villages;20246;tf_villages;暮色森林村庄;Twilight Forest Villages;\npoland-nature;20247;poland_culture_and_nature;Poland Nature;;\namethyst-extra;20248;amethystmore;Amethyst Extra;;\nvillagers-windmill;20249;villager_windmill;Villager's Windmill;;\nextended-wrenches;20250;extendedwrenches;Create: Extended Wrenches;;\nwhy-stack-only-64;20251;wso64;为什么才64个呢？;Why Stack Only 64?;wso64\n;20252;digcooldown;挖掘冷却控制;DigCoolDown;DigCD\n;20253;improved_animations;Improved Animations;;\n;20254;;更改试炼密室生成维度：永恒星光;;\npotentials;20255;potentials;Potentials;;\ndeltaboxlib;20256;deltaboxlib;Deltabox Lib;;\ngobbobs-animated-blocks;20257;animatedblocks;Gobbob 的动画方块;GobBob's Animated Blocks;\nglowingfootsteps;20258;glowingfootsteps;发光足迹粒子;GlowingFootsteps;\nsimply-swords-overhaul;20259;simply_swords_overhaul;Simply Swords: Overhaul;;\nfarms-and-foods;20260;farms-and-foods;Farms and Foods / Farms & Foods;;\n;20261;achievement-fix;成就修复;Achievement Fix;\n;20262;lpq;语言优先队列;Language Priority Queue;LPQ\nlang-magic-tweaker;20263;lang_magic_tweaker;Lang Magic Tweaker;;\nspawn-dimension-choice;20264;spawn_dimension_choice;诞生维度自选;Spawn Dimension Choice;\nreforge-stone;20265;reforge_stone;重铸石;Reforge Stone;\nnumericalbotany;20266;numericalbotany;NumericalBotany;;\ncameraoverhaul-vintage;20267;vintagecam;CameraOverhaul (Vintage);;\n;20268;kingvillager;村民之王：重生;King of the Villagers: Reborn;\n;20269;fusion-smithing,fusion_smithing;融锻Forge版;Fusion Smithing-Forge;FS\nadvanced-hoppers;20270;advanced_hoppers;Advanced Hoppers;;\n;20271;anime_final_fantasy;次元最终幻想;Anime Final Fantasy;AFF\nars-meteorites;20272;arsmeteorites;新生陨星;Ars Meteorites;\nthe-hard-herobrine-mod;20273;herobrinemod;The Hard Herobrine Mod;;\n;20274;;挖矿与砍杀：双持与战备 2 - GTNH 副手兼容版;Mine and Blade : Battlegear - 2 - GTNH - Backhand Edition;\ntektopia-addons;20275;tektopiaaddons;Tektopia Addons;;\njason-voorhees;20276;jason;Jason voorhees;;\nbiome-improvement-mindthemoods-pile-of-rocks;20277;plants_and_rocks;Biome Improvement (Mindthemood's Pile of Rocks);;\nmochila;20278;mochila;简彩跃光;Mochila;\nsoulytra;20279;elytranerfage;Soulytra;;\n;20280;advancedtfcblueprint;高级群峦蓝图;Advanced TFC Blueprint;\n;20281;accesswidener-loader;AccessWidener Loader;;\nimproperui-forged;20282;iui_forge;ImproperUI Forged;;\n;20283;sorrywehavetooenoughfundstoprovideyouwithenderdragon;对不起，我们有过于充足的资金为你提供末影龙;SorryWeHaveTooEnoughFundsToProvideYouWithEnderDragons;SWHTEFTPYWED\nenchantment-to-attack-faster;20284;attackspeedenchantment;并非环取/攻击速度附魔非官方版;Attack Speed Enchantment Unofficial;\n;20285;stopgrowth;停止生长;StopGrowth;\nsquid-no-glitch;20286;squidnoglitch;Squid No Glitch;;\n;20287;permanentnightvision;无痕夜视;Permanent Night Vision;\n;20288;remscarpetadditions;REMS-Carpet-Addition;;REMS\n;20289;multihotbar;Multihotbar-Reborn;;\ncolorful-hex;20290;colorful_hex;Colorful HEX;;\n;20291;modularui2;ModularUI 2;;\nbbl-no-rain-fog;20292;rainfog;BBL No Rain Fog;;\n;20293;;Inventory Bogo Sorter - GTNH;;\n;20294;be_quite_negotiator;Be Quiet Negotiator;;\nlink15s-no-i-frames;20295;links_no_i_frames;Link15's No I Frames;;\njei-integration-reborn;20296;jei_integration_reborn;JEI扩展：重生;JEI Integration: Reborn;\nappliedsync;20297;appliedsync;AppliedSync;;\njei-uu-assembler;20298;jei_uu_assembler;JEI UU Assembler;;\nominous-overlays;20299;ominous_overlays;Ominous Overlays;;\ncreate-nuclear-shell;20300;canonnukes;Create: Nuclear Shell;;\nworld-of-wonders;20301;create_and_explore_alexs_caves;World of Wonders;;\nhorrible-meteor-shower;20302;meteormod;陨石雨;Horrible Meteor Shower;\ndesertification;20303;desert;Desertification;;\ncinchs-abandoned-houses;20304;cinchsabandonedhouses;Cinch's Abandoned Houses;;\ncurse-breaker;20305;curseking;Curse Breaker;;\nhundred-years-warfare;20306;hundred_years_war;百年战争;Hundred Years Warfare;HYW\nbiologica;20307;biologica;Biologica;;\noritech-things;20308;oritechthings;Oritech Things;;\npotato-vehicle-industries;20309;potatpacc;Potato Vehicle Industries;;\nwarfare-wings;20310;warfare_wings;Warfare Wings;;\navaritias-delight;20311;avaritia_delight;无尽乐事;Avaritia's Delight;AD\nmamas-herbs-and-harvest;20312;herbsandharvest;Mama's Herbs and Harvest;;\nvs-orbit;20313;vs_orbit;瓦尔基里：轨道学;VS Orbit;\ntouhou-little-maid-spell;20314;touhou_little_maid_spell;车万女仆：万法皆通;Touhou Little Maid: Spell;\ndespawn-notifier;20315;despawnnotifier;余影待终/掉落物消失提醒;Despawn Notifier;\ncustomnpcs-unofficial-from-betazavr;20316;;自定义 NPC BetaZavr 非官方版;CustomNPCs Unofficial from BetaZavr;\n;20317;luxsskitmod;鹿先生的工具;Mr.Lu's Kit Mod;MLK\nefficient-core-automatic-mining;20318;chunkoreautominer;Efficient Core自动挖掘器;Efficient Core Automatic Mining;ECAM\n;20319;bento_box;便当盒;Bento Box;INOBB\ncustom-100-day-boss;20320;custom_100day_boss;自定义100天Boss;Custom 100-Day BOSS;CTB\n;20321;ending_library;终焉图书馆;EndingLibrary;EL\ncinchs-extra-torches;20322;cinchsextratorches;Cinch's Extra Torches;;\ncorpse-x-cosmetic-armor-reworked-compat;20323;cosmeticcorpsecompat;Corpse x Cosmetic Armor Reworked Compat;;\nmodular-angel-ring;20324;modular_angelring;Modular Angel Ring;;\nmagic-feather-replumed;20325;magicfeather;Magic Feather: Replumed;;\nessentialcraft-magic-reflux;20326;;EssentialCraft Magic Reflux;;\nad-astra-star;20327;ad_astra_star;Ad Astra: Star;;\nneomopeds;20328;moped;NeoMopeds;;\ncreate-more-parallel-pipes;20329;cmparallelpipes;Create More: Parallel Pipes;;\ncreate-garnished-reworked;20330;creategarnished;Create: Garnished Reworked;;\ncreate-shimmer;20331;create_shimmer;机械动力：微光;Create: Shimmer;CS\nblue-archive-school-banners;20332;baschoollogobanners;Blue Archive School Banners;;\ncialloblade;20333;cialloblade;重锋:cia出如llo;CialloBlade;CLB\nwindows-keyboard-fixes-returns;20334;;Windows Keyboard Fixes Returns;;\naquatic-torches-returns;20335;aquatictorches;Aquatic Torches Returns;;\ncratetech-crate-storage;20336;cratetech;CrateTech: Crate Storage;;\nbigger-and-better;20337;bigger_and_better;Bigger and Better?;;\nblabber;20338;blabber;Blabber;;\nreducedores;20339;reducedores;Reduced Ores;;\ndurabilityrarity;20340;durabilityrarity;Durability Rarity;;\nironfist-lite;20341;;IronFist Lite;;\nshear-chicken;20342;sheerchickens;Shear Chicken;;\nno-moon-jar;20343;no_moon;no_moon.jar;;\n;20344;deep_space;Deep Space;;\nblue-grove;20345;mr_blue_grove;Blue Grove;;\nquarkponders;20346;quark_ponders;QuarkPonders;;\nsussy-sniffers;20347;sussysniffers;Sussy Sniffers;;\nfirefly-bush-backport;20348;firefly_bush_backport;Firefly Bush Backport;;\nagritech-trees-automated-trees;20349;agritechtrees;AgriTech: Trees;;\nagritech-evolved;20350;agritechevolved;AgriTech: Evolved;;\nwealizh;20351;wealizh;工业故事;Wealizh;WLH\n;20352;hydrometallurgy_copper;湿法冶金学;Hydrometallurgy;HYD\nmekanism-ecoenergistics-unofficial;20353;mekanismecoenergistics;Mekanism EcoEnergistics - Unofficial;;\n;20354;equivalentenergistics;FMEquivalent Energistics;;FMEE\nwoodlandfoxverses;20355;woodlandfoxverses;林间狐咏;WoodlandFoxVerses;\n;20356;englishfight;英语战斗;EnglishFight;EF\n;20357;unkillablegamemodes;弑神无效;Unkillable Gamemodes;\nswaying-garden;20358;swaying_garden;摇曳花园;Swaying Garden;\ntime-feeds-villager;20359;time_feeds_villager;逝者如饲：村民;Time Feeds: Villager;TFV\nftb-backups-3;20360;ftbbackups3;FTB 备份 3;FTB Backups 3;\nkurolib;20361;kurolib;KuroLIB;;KL\ntrimmed;20362;trimmed;Trimmed;;\nfree-camera-api-tripod;20363;free_camera_api_tripod;自由相机API-三脚架;Free Camera API Tripod;\nabelia;20364;abelia;Abelia;;\n;20365;mr_player_locatorsettings;Player Locator Settings;;\nsimple-weather;20366;simple_weather;Simple Weather;;\nsimple-copy;20367;simplecopy;Simple Copy;;\nnatures-aura-render-fix;20368;narenderfix;Nature's Aura Render Fix;;\ncreakingchopperenchantment;20369;creakingchopper;Creaking Chopper Enchantment;;\ndaily-rewards;20370;daily_rewards;Daily Rewards;;\ninitialdimensionreborn;20371;initial_dimension;InitialDimensionReborn;;\n;20372;math_dragon;数学龙;MathDragon;MD\n;20373;oo_ee_a_e_a_fabric;OO EE A E A Fabric;;\n;20374;wdmla;What DreamMaster looks at;;WDMla\nredstone-chans-enchantment-expansion-data-pack;20375;redstone_enchants;红石酱的附魔拓展;Redstone-chan's Enchantment Expansion;\nthe-plugin-mod;20376;PluginMod;The Plugin Mod;;\ncave-vision;20377;mr_cave_vision;Cave Vision;;\n;20378;elretrievetoslot;EL Addon Retrieve To Slot;;\naether-gloves-for-all;20379;aether_gloves_for_all;Aether Addon: Gloves For All;;\nreducedloot;20380;reducedloot;Reduced Loot;;\n;20381;irisfixes;Iris 修复;IrisFixes;\nmusicjs;20382;musicjs;MusicJS;;\n;20383;WitcheryExtras;WitcheryExtras / Witchery++;;\nsuspendtheserver;20384;suspendtheserver;自动挂起服务器;Suspend The Server;\nscreenshot-layers;20385;screenshot-layers;Screenshot Layers;;\nanti-spider-climbing;20386;anti-spider-climb;Anti Spider Climbing;;\nrechiseled-chipped;20387;rechiseled_chipped;Rechiseled: Chipped;;\n;20388;mr_rechiseled_buildersdelight;Rechiseled: Builder's Delight;;\nweatherdisplay;20389;weatherdisplay;WeatherDisplay;;\nradium-fabric;20390;;Radium;;\ncinnabar;20391;cinnabar;Cinnabar;;\n;20392;opencurios;饰品栏打开工具;OpenCurios;\npquality;20393;pquality;pquality;;\nmobility-enchants;20394;mr_mobility_enchants;Mobility Enchants;;\nmte-patches;20395;mtepatches;MTE Patches;;\nhappy-ghast-breeding;20396;happyghastbreeding;Happy Ghast Breeding;;\nhappy-ghast-flight;20397;happyghastflight;Happy Ghast Flight;;\nthe-angry-ghast;20398;the_angry_ghast;The Angry Ghast;;\n;20399;morfonica;Morfonica in Minecraft;;\nfor-vanilla-flowers;20400;fvfro;For Vanilla Flowers;;\n;20401;skillpractise;技能练习;SkillPractise;SKP\n;20402;tenzins_guild;丁真的公会;Tenzin's Guild;\n;20403;extraium;私货妙妙工具;Extraium;\n;20404;mh_fs,mh_f;模组隐藏器-ForgeSuper;ModHiderForgeSuper;MHS\nbedrock-hotbar-offset;20405;bedrock_hotbar;Bedrock Hotbar Offset;;\nmss-more-slot-swap;20406;more_slot_swap;More Slot Swap;;MSS\nshow-all-advancements;20407;showalladvancements;显示所有进度;Show All Advancements;\n;20408;disable-composter;禁用堆肥桶;Disable Composter;\nmana-display;20409;manadisplay;Mana Display;;\ncurse-forge;20411;curseforge;诅咒锻造;Curse Forge;\n;20412;chinese_chest;语文战利品;ChineseChest;CC\n;20413;gene_weapon;基因武器;Gene Weapon;\ndisconnect-packet-fix;20414;disconnect_packet_fix;Disconnect Packet Fix;;\nhappyghast-on-1-12-2;20415;suikehappyghast;1.12.2的快乐恶魂;HappyGhast_on_1.12.2;\nautomationera;20416;automationera;自动化时代;Automation Era;AtE\n;20417;tears_backport;Tears & Lava Backport;;\nlava-chicken-disc;20418;lava_chicken_music_disc;Lava Chicken Disc;;\nlava-dennis;20419;lavadennis;Lava Dennis;;\nalbum-go-wall;20420;testistest;Album Go Wall!;;\nforgotten-java;20421;the_forgotten_java;Forgotten Java;;\njohanness-renaissance-rebirth;20422;johannessrenaissance;约翰内斯的文艺复兴：火器之重生;Johannes's Renaissance: Musket Rebirth;JRMB\nswarm-infection;20423;swarm_infection;Swarm Infection;;\nnecro-rooted;20424;rootmod;Necro Rooted!;;\nmycelial-crisis;20425;myceliumwar;菌丝危机：崛起;Mycelial Crisis: Mushroom Rising;MCMR\n;20426;excalibur;契约胜利之剑;Excalibur;\nlethalsteelproject;20427;lethal_slash_project;致命拔刀计划;Lethal Steel Project;LSP\nlovecraft-girlfriends-3;20428;cringymod;LoveCraft - Girlfriends <3;;\n;20429;faithfulmace;Faithful Mace;;\nbard-craft;20430;bardcraft;Bard Craft;;\n;20431;bilibili_media;哔哩哔哩WaterMedia兼容模组;;BM\nitems-timer;20432;items_timer;Items Timer;;\nneo-language-reload;20433;neo_language_reload;Neo Language Reload;;\n;20434;oxidizium;Oxidizium;;\nixeris;20435;ixeris;Ixeris;;\nsubstrate-sodium-addon;20436;substrate;Substrate;;\nhide-item-frame;20437;hide_item_frame;隐藏物品展示框;Hide Item Frame;\n;20438;smoothmaps;SmoothMaps;;\nneoessentials;20439;neoessentials;NeoEssentials;;\nstructure-layout-optimizer;20440;structure_layout_optimizer;Structure Layout Optimizer;;\nmutant-creatures-legacy;20441;;Mutant Creatures Legacy;;\ndynamites-overhaul;20442;dynamites_overhaul;Dynamites Overhaul;;\ndynamite-addon;20443;dynamite_addon;Dynamite Addon;;DA\nmaid-mana-source;20444;maid_mana_source;女仆魔源;Maid Mana Source;\nmaidens-merry-making;20445;merrymaking;Mama's Merrymaking;;\nbbl-invisible-lights;20446;bbllights;BBL Invisible Lights;;\nsuperior-trees;20447;;Superior Trees;;\nunderground-rivers;20448;underground_rivers;Underground Rivers;;\nimproved-village-placement;20449;improved_village_placement;Improved Village Placement;;\n;20450;photosynthesis;Photosynthesis;;\n;20451;mineno;MineNo;;\nslashbladetetra;20452;slashbladetetra;SlashBladeTetra;;SBT\n;20453;gcyl;GCYL: CEu;;\nirons-spells-n-spellbooks-dynamic-skill-trees;20454;irons_spells_dynamic_skilltree;Iron's Spells 'n Spellbooks Dynamic Skill Trees;;\nspell-elemental;20455;spellelemental;Spell Elemental;;\nward-ring-extension-for-irons-spells-n-spellbooks;20456;wre;Ward Ring Extension for Iron's Spells 'n Spellbooks;;WRE\ndungeons-and-combat-x-irons-spells;20457;spellpowerboost;Dungeons And Combat x Iron's Spells;;\nsmallcolonies;20458;smallcolonies;SmallColonies for Minecolonies;;\nfarmers-cutting-regions-unexplored;20459;mr_farmers_cuttingregionsunexplored;Farmer's Cutting: Regions Unexplored;;\nserene-landscapes;20460;;OTG: Serene Landscapes;;\nblood-arsenal-reawakened;20461;bloodarsenalreawakened;Blood Arsenal Reawakened;;\ntacz-emx-gunsmith-gunpack;20462;;CUST-民间枪匠涂装拓展包;CUST-Gunsmith Gunpack;\ntacz-tris-dyna-gunpack;20463;;TRIS-天枢动力能量武器枪包;TRIS-dyna Gunpack;\nspell-colonies;20464;spell_colonies;Spell Colonies;;SC\nnature-reimagined;20465;ature_reimagined;Nature Reimagined;;\nominous-bottle-add;20466;ominous_bottle;Ominous Bottle Add;;\nonly-hammers;20467;onlyhammers;Only Hammers;;OH\nauto-resource;20468;autoresource;自动资源;Auto Resource;\niu-additions;20469;industrialupgrade additions;[IU] Additions;;\nrubiks-cube;20470;rubiks_cube;魔方;Rubiks Cube;\nclockworklib;20471;clockwork;ClockworkLib;;\n;20472;quicmc;quicmc;;\nfabridge;20473;fabridge;Fabridge;;\n;20474;forge5160;Forge 5160 Backport;;\ntrue-fullbright;20475;truefullbright;True Fullbright;;\nquickslots;20476;quickslots;QuickSlots;;\nhide-hud-in-third-person-view;20477;hide_hud;Hide HUD in Third-Person View;;\nautomlg;20478;auto-mlg;AutoMLG;;\nelytraautopilot;20479;elytra-fly;ElytraAutopilot;;\nsimple-conveyor-belts;20480;belts;Simple Conveyor Belts;;\nsmidgeon-o-bliss-remastered;20481;sob;片刻满足 重置版;Smidgeon o' Bliss REMASTERED;\nxl-food-reborn;20482;;超多食物：重生;XL Food Mod Reborn;\ncreate-spin-spawner;20483;spin_spawner;Create: Spin Spawner;;\nvs-rotanator;20484;rotanator;VS: Rotanator;;\ncreate-refilling-boxes;20485;create_refilling_boxes;Create: Refilling Boxes;;\ncbc-chemical-warfare;20486;cbc_cw;机械动力火炮：化学与生物战争;CBC: Chemical Warfare;CBCCW\ncbc-warium-projectiles;20487;shupapium;机械动力火炮：Warium炮弹;CBC: Warium Projectiles;CBCWP\ncreate-colored;20488;create_colored;Create: Colored;;\ncreate-law-and-order;20489;createlawandorder;机械动力：法律与秩序;Create: Law and Order;\nblockexporter;20490;blockexporter;Block Exporter;;\nfix-attack-lag;20491;fix_attack_lag;修复攻击卡顿;Fix Attack Lag;FAL\n;20492;clienthings;ClienThings;;\n;20493;misinput;Misinput;;\nlavablocker;20494;lava-blocker;SafeNetherTunnel;;\ngildedarmors;20495;gildedarmor;鎏金盔甲;Gilded Armor;GA\ninventory-management-deluxe;20496;;Inventory Management Deluxe;;\nrunningmapmusic;20497;running_map_music;跑图音乐;RunningMapMusic;\nlava-chicken-music-disc;20498;lavachickenmusicdisc;Lava Chicken Music Disc;;\nporting-dead-libs;20499;portingdeadlibs;Porting Dead Libs;;\npanoramica;20500;panoramica;Panoramica;;\ncreate-mt-integration;20501;create-mt-integration;资源统合-机械动力配方附加;Create Mt Integration;\nvalkyrien-skies-war-and-peace;20502;vswap;瓦尔基里：战争与和平;Valkyrien War and Peace;VSWAP\nsillys-auto-totem;20503;auto-totem;Silly's Auto Totem;;\nrepairables-anvil;20504;repairablesanvil;可修复的铁砧;Repairables Anvil;\n;20505;throwable_bricks;Throwable Bricks;;TB\n;20506;nowtime;准点报时;Now Time;\nfluidjs;20507;fluidjs;FluidJS;;\nhybridcompat;20508;hybridcompat;混合兼容;HybridCompat;HC\n;20509;recipecooldown;配方冷却;RecipeCooldown;\ncounter-mod;20510;countermod;Counter Mod;;\nlotr-renewed-class-mod;20511;lotrclasses;魔戒种族;LOTR Races;\n;20512;NettyPatch,NettyPatch_ASM;Netty-Patcher;;\ncraftable-chain-armors;20513;craftablechainarmor;Chain Armor Recipe;;\nitem-cooling-reduction;20514;item_cooling_down;物品冷却缩减;Item Cooling Reduction;ICR\nyosbs;20515;yosbs;Your Options Shall Be Saved;;YOSBS\n;20516;mr_drop_head;Drop Head;;\n;20517;mr_norollback;NoRollback;;\n;20518;nobugs;No Bugs;;\ndark-window-bar;20519;darkwindowbar;Dark Window Bar;;\nsodium-core-shader-support;20520;sodiumcoreshadersupport;Sodium Core Shader Support;;\n;20521;headtap;铁砧工艺：敲你脑袋;AnvilCraft: HeadTap;\nhearts-in-a-bottle;20522;hearts_bottle;Hearts In A Bottle;;\ncopper-tools-and-shears;20523;simplecoppertoolsmod;Copper Tools and Shears;;\nwindchime-unofficial-continued;20524;windchimes;WindChime Unofficial Continued;;\n;20525;infinite;无限;Infinite;IFT\npotionsplus;20526;potionsplus;PotionsPlus;;\ntinkers-wood;20527;tinkers_wood;Tinkers' Wood;;\nilluminated;20528;illuminated;Illuminated;;\ngalosphere-argent-edge;20529;argentedge;Galosphere: Argent Edge;;\nbrutality-a-terramity-addon;20530;brutality;Brutality - A Terramity Addon;;\nfree-epic-games;20531;freeepicgames;FE 游戏框架;FreeEpicGames;FEG\n;20532;hotbarredirect;快捷栏路径配置;HotbarRedirect;HR\nfabric-database-h2;20533;fabric-database-h2;H2数据库;Fabric Database H2;H2\ntokien-theme-bq;20534;bqt_tolkien;更好的任务-托尔金主题;Tolkien Theme [BQ];\ntpa-utilities;20535;tpautilities;TPA Utilities Neo;;\nshutup-opengl;20537;shutupgl;Shutup OpenGL!;;\nmaid-command;20538;maid_command;女仆指令;Maid Command;\nspeedy-hopper-vanilla;20539;speedyhopper;Speedy Hopper 8×;;\nsuggestions-fix-fabric;20540;suggestion-provider;Suggestions Fix Fabric;;\nno-ixapi;20541;noixmodapi;No.IXAPI;;No.IX\n;20542;legolaskuai;中华古刃;Chinese Ancient Blade;CAB\nepicfight-zylob;20543;zylob;史诗战斗：刀光修改;EpicFight Sword Light Modifications;EFSLM\nperfect-parity-pale-garden-awakens-neo;20544;perfectparitypg;Perfect Parity Neo: Pale Garden Awakens;;\nexpressive-advanced;20545;tacticalmovement;Tactical Movement Renewed;;\nphilia-amulet;20546;philiaamulet;友爱护符;PhiLia Amulet;\nstronger-fire-boss;20547;stronger_fire_boss;原焰帝臻;Stronger Fire Boss;\nmyec;20548;myec;更多渴望：附魔与制造;More of Yearn: Enchants & Crafts;myec\nabandoned-urban;20549;abandoned_urban;废弃城市;Abandoned urban;\nfungal-hazard;20550;fungal_hazard;蕈骸迷途;Fungal Hazard;FH\nmore-useful-copper-2-0;20551;more_useful_copper;更有用的铜;More Useful Copper;\nboom-bombs;20552;bombs;Boom!;;\npolars-exploration-additions;20553;polars_exploration_additions;Polar's Exploration Additions;;\n;20554;happy_ghast;Happy Ghast: Upgrade Snowballs;;\nozymandias-sundries;20555;ozymandias_sundries;Ozymandias' Sundries;;\ntensura-enigmatic;20556;tensuraenigmatic;Tensura: Enigmatic;;\ncreatetraincontrol;20557;create_train_control;机械动力：列车控制;CreateTrainControl;CTC\ncreate-collection-bundles;20558;create_collection_bundles;Create: Collection Bundles;;CCB\ncreate-enhanced-mastery;20559;enhancedmastery;Create: Enhanced Mastery;;\ncreate-industrial-tools;20560;create-industrial-tools;Create: Industrial Tools;;\ncreate-smart-bounds;20561;smart_bounds;Create: Smart Bounds;;\ncreate-fantasizing-again;20562;create_fantasizing;机械动力：又在幻想了;Create: Fantasizing Again;\ncrafting-station-jei-edition-updated;20563;craftingstation;Crafting Station: J/EMI Edition Updated;;\nuncraft-everything;20564;uncrafteverything;Uncraft Everything;;\n;20565;videoplayer;VideoPlayer;;\nnet-music-play-list;20566;net_music_list;网络音乐机：播放列表;NetMusic: Play List;\n;20567;ccoins;贸易学：货币;Catallactics: Coins;CCoins\ngregs-modern-construct;20568;gm_construct;Greg's Modern Construct;;\n;20569;ssis;SimpleSuperItemStack;;SSIS\nsmart-key-prompts;20570;smartkeyprompts;灵活按键提示;SmartKeyPrompts;SKP\n;20571;zerowhitelist;Zero白名单;Zero Whitelist;ZWL\nmilometer;20572;milometermod;计程器;Milometer;\n;20573;pearl;珍珠加载区块移植;Pearl Chunk Loading;\nclean-drops;20574;clean-drops;Clean Drops;;\nauto-replace;20575;autoreplacemod;Auto Replace;;\n;20576;footprint;Footprint;;\n;20577;mr_nice_actions;Nice Actions;;\nsimple-villager-follow;20578;simple_villager_follow;Simple Villager Follow;;\ntinkers-experience;20579;tinkerleveling;Tinkers Experience;;\nhedgehog-companion;20580;jojo;Hedgehog Companion;;\ni-want-to-spin;20581;datl;我要开转;I Want To Spin;IWTP\nkiss;20582;kissmod,kiss;Kiss;;\n;20583;mc_bds;北斗导航;MC BDS;MBDS\nneo-the-titans;20584;thetitans;新泰坦生物：撼世之物;Neo Titans Mod：Earthshak Creature;NTM\n;20585;blue_archivescraft;蔚蓝档案工艺;Blue Archives Craft;BA\nnuggets-repair-items;20586;nuggets-repair-items,nuggets_repair_items;矿粒修复物品;Nuggets Repair Items;NRI\n;20587;simpletp;简单传送;SimpleTP;\n;20588;xercapaint;绘画之乐 1.20.1 重制版;Joy of Painting for 1.20.1;\nterra-atmos;20589;terra_atmos;群峦环境音效;Terra Atmos;TA\ntlm-true-power;20590;true_power_of_maid;车万女仆：真正的力量;TLM: True POWER;\n;20591;tboc;芝士之书：重铸;The Book of Cheese: Reforged;TBoCR\n;20592;redstoneextra;红石拓展;RedStoneExtra;\n;20593;equipmenteffect;套装效果;Equipment Effect;EE\n;20594;splash_window;Splash Window;;\nneoenchant;20595;mr_neoenchant;Neo Enchant+;;\n;20596;showcase;Showcase;;\nenhancedtooltips;20597;enhancedtooltips;EnhancedTooltips;;\nlocate-fixer;20598;locatefixer;Locate Fixer;;\njourneymap-fixes;20599;journeymapfix;JourneyMap Fixes;;\nancient-structure;20600;ancientstructures;Ancient Structures: Cultural Buildings;;\nwizard-samurai-irons-spells-addon;20602;wizard_samurai;Wizard Samurai - Iron's Spells Addon;;\nsurvival-instinct;20603;survival_instinct;Survival Instinct;;\nenderzoo-patched;20604;;EnderZoo : Patched;;\nillager-keep;20605;illager_keep;Illager Keep;;\ncodex-of-champions;20606;codex_of_champions;Codex Of Champions;;\nars-controle;20607;ars_controle;Ars Controle;;\nmedieval-craft-historical-firearms;20608;historical_guns;Medieval Craft: Historical Firearms;;\ntoo-many-withers;20609;toomanywithers;Too Many Withers;;\nreliquified-l-ender-s-cataclysm;20610;reliquified_lenders_cataclysm;Reliquified L_Ender 's Cataclysm;;\n;20611;goooooooooood;命令方块在哪里？;Where is the command block?;\nfindshit;20612;findshit;找史大王 / 日志溯源;find log's creator / findshit;\nenchantment-lookup;20613;enchantment_lookup,enchantment-lookup;附魔查询;Enchantment Lookup;ELU\n;20614;screenshotuploader;截图上传器;Screenshotuploader;SUP\n;20615;bannervisualizer;旗帜保护覆盖显示;Banner Visualizer;\n;20616;mcft;MCFaceTracking;;MCFT\n;20617;mr_stackable_heads;Stackable Heads;;\n;20618;mods-checker;模组校验;ModsChecker;MSC\nno-enchanting-xp-requirment;20619;noenchantxp;No Enchanting XP Requirment;;\nclient-dynamic-light;20620;clientdynamiclight;客户端动态光源;Client Dynamic Light;\n;20621;easyice;简单冰块;EasyIce;\nmultiyggdrasil;20622;multiyggdrasil;MultiYggdrasil;;\nthaumcraft-4-patched;20623;TC4Patched;Thaumcraft 4 : Patched;;\npatchful;20624;patchful;Patchful;;\ngtnh-customizer;20625;gtnh_customizer;GTNH Customizer;;\nview-sounds;20626;view_sounds;View Sounds;;\nae-inventory-profiles;20627;ae_inventory_profiles;AE Inventory Profiles;;\ntooltips-reforged;20628;tooltips_reforged;提示框：重铸;Tooltips Reforged;\n;20629;wyr;Would You Rather;;WYR\nsoaring-phantoms;20630;soaring_phantoms;Soaring Phantoms;;\nritchies-projectile-library;20632;ritchiesprojectilelib;Ritchie's Projectile Library;;RPL\nmodopedia;20633;modopedia;Modopedia;;\n;20634;translate_allinone;一体化翻译;Translate All in One;TAIO\nbutton-fix;20635;button-fix;Button Fix;;\n;20636;birthday-fabric;生日模组;Birthday Fabric;\nbucketstacking;20637;bucketstacking;Bucketstacking;;\ncreeper-no-break-blocks;20638;nocreeperexplosion;Creeper no Break Blocks;;\ncreate-cog-hud;20639;create_cog_hud;机械动力：齿轮 HUD;Create: Cog HUD;CCH\n;20640;create_tools_n_weapons;Create: Tools n Weapons;;\ncreate-pipe-networks;20641;pipe_networks;Create: Pipe Networks;;\njiagu-reappearance;20642;jiagureappear;岁在甲骨;Jiagu Reappearance;Jiagu\nmulticolored-redstone-lamps;20643;multicoloredredstonelamps;Multicolored Redstone Lamps;;\nreliquified-twilight-forest;20644;reliquified_twilight_forest;Reliquified Twilight Forest;;\nwitch-tower;20645;witch_tower;Witch Tower;;\nthe-nightmare-000;20646;nightmer;The Nightmare 000;;\nmeowmere;20647;meowmere;彩虹猫之刃;Meowmere;MM\n;20648;server_economy;Fallenflower的服务器经济;Fallenflower's Server Economy;FFSE\ndeadout-medical-supplies;20649;deadout;死亡突围医疗用品;Deadout Medical;DTM\n;20650;thermal_shock;Thermal Shock;;\nbetter-questing-npc-integration;20651;bq_npc_integration;更好的任务-NPC扩展;Better Questing - NPC Integration;\n;20652;bmc;“最好的”MC;The Best Minecraft;BMC\neu2emc;20653;eu2emc;EU转EMC能量转换器;EU2EMC;EU2EMC\n;20654;dragonnose;龙鼻子;DragonNose;DNE\nsimplesorter;20655;simple_sorter;简单整理;SimpleSorter;SS\nspawncursion;20656;spcurs;生成式入侵;SpawnCursion;\ngrass-be-gone;20657;grassbegone;不要草;Grass Be Gone;\ntacz-unidict;20658;tacz_unidict;TACZ：铳械协议;TACZ: Unidict;TUD\ngem-lichens;20659;gemlichens;宝石地衣;Gem Lichens;\nlethality-a-terramity-addon;20660;lethality;Lethality - A Terramity Addon;;\nlootr-fixes;20661;lootrfix;Lootr Fixes;;\nno-name-provideds-potion-fruit;20662;nnp_mm_potion_fruit;Potion Fruit;;\nfungus-stew;20663;fungus_stew;Fungus Stew;;\nauto-sapling;20664;AutoSapling,autosapling;自动树苗种植;Auto Sapling;\nalans-bees;20665;mmred;Alan's Bees;;\nsnack-sticks;20666;snacksticks;Snack Sticks;;\nfish-fix;20667;fishfix;Fish Fix;;\nstuttering-screen-lag-protection;20668;lagprotection;Stuttering Screen/Lag Protection;;\ntilefinder;20669;tilefinder;TileFinder;;\nimmersive-furniture;20670;immersive_furniture;沉浸式家具;Immersive Furniture;\nmodernnetworking;20671;modernnetworking;Modern Networking;;\nlike1-8;20672;LIKE1.8;Like1.8;;\nspoduos-future-mineration;20673;spoduos_future_minecraft;Spoduo's Future Mineration;;SFM\nenderio-continuous;20674;;末影接口延续版;EnderIO Continuous;EIOC\nenchantment-broker;20675;enchantmentbroker;Enchantment Broker;;\nlavalogged-blocks;20676;lavalogged;Lavalogged Blocks;;\nnew-dawn;20677;newdawn;New Dawn;;\nenhanced-glass;20678;enhanced_glass;Enhanced Glass;;\nhospitals;20679;hospitals;Hospitals - Medical Blocks & Items;;\ncomfy-cozy;20680;comfycozy;Comfy Cozy;;\nepicnpc;20681;epicnpc;史诗战斗NPC;EpicNpc;\nepic-fight-script-extension;20682;;史诗战斗：脚本扩展版;Epic Fight Script Extension;\n;20683;undead_transformation;Undead Transformation;;\n;20684;mr_rainy_endermans;Rainy Endermans;;\nskullskinless;20685;skullskinless;头颅无皮肤/易颜得帧;SkullSkinless;\nstaynight;20686;staynight;永夜定驻;Stay Night;\nblockblocker;20687;blockblocker;方块放置阻止/抑用挡置;BlockBlocker;\nshoving-shovels;20688;shoving_shovels;Shoving Shovels;;\nelectric-coating;20689;notonlybattery;不止电池 / 电力镀层;NotOnlyBattery / Electric Coating;NB\naraxers-bestiary;20690;araxers_bestiary;Araxer's Bestiary;;\nbetter-questing-immersive-engineering-multiblock;20691;bqimm;Better Questing - Immersive Engineering Multiblock Forming;;\nbloodyquests;20692;bloodyquests;BloodyQuests (BetterQuesting | Blood Magic Bridge);;\ncroaker;20693;croaker;Croaker;;\ncrawl-on-demand;20694;crawlondemand;Crawl on Demand;;\ngaze-a-malum-addon;20695;gaze;Gaze;;\nosce;20696;;矿石菌种社区版;OreSpawn Community Edition;OSCE\nexplosive-arrow;20697;mr_tnt_arrow;TNT Arrow / Explosive Arrow;;\nmore-relics;20698;morerelics;More Relics;;\nno-sun;20699;no_sun;暗无天日;No Sun;\nopposing-force;20700;opposing_force;Opposing Force;;\n;20701;dhmpemfixer;DHMPEMFixer;;\n;20702;pingfix;FIX MY PINGGGGG;;\n;20703;volumescroll;VolumeScroll;;\ntalk-balloons;20704;talk_balloons;Talk Balloons;;\nhealth-indicator-txf;20705;healthindicatortxf;Health Indicator TXF;;\nquickbackupmulti;20706;quickbakcupmulti;多槽位备份重制版;QuickBackupMulti-Reforged;QBMR\nsnowy-weather;20707;snowy-weather,snowy_weather;Snowy Weather;;\n;20708;mojangster;加载界面动画;Mojangster;\ndimensionlock;20709;dimensionlock;Dimension Lock;;\nfluidium;20710;fluidium;锍;Fluidium;\n;20711;Kaolin;Kaolin;;\nperitia;20712;peritia;Peritia;;\n;20713;reapi;Redstone Energy API;;REAPI\ninventory-sort;20714;inventory_sort;背包整理;Inventory Sort;\ncorpse-kibou-no-hana;20715;kibounohana;希望の花;Corpse: Kibou no Hana;\nredyeable;20716;redyeable;Redyeable;;\n;20717;naturalmotionblur;Natural Motion Blur;;\n;20718;ModpackChecker,modpack-checker;Modpack Checker;;\nconvenient-achievements;20719;convenientachievements;Convenient Achievements;;\nvintage-vibes;20720;vintagevibes;Vintage Vibes;;\n;20721;mafuyusflashlight;真冬的手电筒;Mafuyu's Flashlight;\n;20722;smelterhedgehog;Smelter The Hedgehog;;\n;20723;hopperhedgehog;Hopper The Hedgehog;;\ncustom-trades-mod;20724;ctrades;Custom Trades Mod;;\ntameable-zombie-horse-and-skeleton-horse;20725;tameablezombieskeletonhorse;可驯服的僵尸马和骷髅马;Tameable Zombie Horse And Skeleton Horse;\nmi-tweaks;20726;mi_tweaks;MI Tweaks;;\nqa-villager-tweaks;20727;qa_extends;Qa 村拓展 & 废物革新;Qa Villager Extends & Waste Innovations;QVW\nmultiblocked-extended-compatibility;20728;mbdextcompat;Multiblocked Extended Compatibility;;\nenum-extender-js;20729;enum_extender_js;EnumExtenderJS;;\n;20730;btc;Better Trial Chambers;;BTC\n;20731;fuckclientmodscore;F**kClientMods;;FCM\nno-pigs;20732;nopigs;No Pigs;;\nvanimals;20733;vanimals;Vanimals;;\nwind-charged-weaponry;20734;wind-charged-weaponry;Wind Charged Weaponry;;\nuntagged-mobs;20735;untagged;UNTAGGED MOBS;;\nenchanted-potato;20736;enchantedpotato;Enchanted Potato;;\ncit-for-nbt;20737;citnbt;CIT for NBT;;\n;20738;toggle-keybinds;Toggle Keybinds;;\nbonemealtweaker;20739;bonemealtweaker;Bonemeal Tweaker;;\nhellstone-golem;20740;hellstone_golem;狱岩魔像;Hellstone Golem;\nnetherstar-crafting;20741;craftablenetherstar;可合成的下界之星;Netherstar Crafting;\ntutes-cn-sweetinks-items;20742;tsitems;塔兹与甜墨的物品;Tutes-cn & Sweetink's Items;TSI\ni-am-steve;20743;iamsteve;I.. am steve;;\nbackported-stufff;20744;backported_stuff;Backported Stuff;;\n;20745;lumenwild;流明旷野;Lumen Wild;\ntumbleweeds;20746;tumbleweeds;Tumbleweeds;;\nmending-mirror;20747;mendingmirror;破镜重圆;Mending Mirror;\n;20748;powerfulrecipes;More Vanilla Weapon Recipes;;\nnatures-life;20749;natures_life;Nature's Life;;\nyggdrasils;20750;mr_yggdrasil_structure;Yggdrasil;;\n;20751;akatzumafixstack;修复Arclight上的Bigger Stacks;AkatZumaFixStack;\n;20752;aichatmod;AIChat Mod;;ACM\n;20753;deepseeksenchant;DeepSeek的附魔;DeepSeek's Enchant;\nlazy-crafting;20754;lazy_crafting;懒人合成;Lazy Crafting;LC\n;20755;xiyuslogin;Xiyu's Login;;\n;20756;mark;Mark;;\nisitwaxed;20757;isitwaxed;IsItWaxed;;\nsimple-hp-bar;20758;simplehpbar;Simple Hp Bar;;\nlogin-hp-fix;20759;loginhpfix;Login HP Fix;;\nshape-shifter-curse;20760;shape-shifter-curse;幻形者诅咒;Shape Shifter Curse;SSC\n;20761;happy_cactus;欢乐仙人掌;Happy Cactus;hlxrz\n;20762;thirst_fix;口渴修复;Thirst Fix;\ntinkers-planner-reborn;20763;tconplanner;匠魂蓝图：重燃;Tinker's Planner Reborn;TPR\nhunger-overflow;20764;hungeroverflow;Hunger Overflow;;\nnocreeper;20765;nocreeper;NoCreeper;;\nmo-creatures-extended;20766;mocreatures;Mo' Creatures Extended;;\nskippy-pearls;20767;mr_skippy_pearls;Skippy Pearls;;\nspelunkery-plus;20768;spelunkeryplus;Spelunkery+;;\nforge-better-redstone;20769;betterredstone;Better Redstone;;\njeb-just-enough-book;20770;jeb;Just Enough Book;;JEB\npal;20771;playerabilitylib;PlayerAbilityLib;;Pal\ndata-anchor;20772;dataanchor;Data Anchor;;\nelytra-hopping;20773;elytra_bounce;Elytra Hopping;;\noh-more-wandering-trades;20774;omwt;更多游商交易;Oh More Wandering Trades;OMWT\nbeacon-anti-phantom;20775;beacon_anti_phantom;不时轻轻肘击幻翼的邻座信标同学;Beacon Anti-Phantom;\n;20776;flexible_painting;灵活的画;Flexible Painting;\n;20777;maidcode;女仆网管;MaidCode;\ncooler-animations;20778;cooleranims;Cooler Animations;;\n;20779;yc_auto_fishing;YourCraft自动钓鱼;YC Auto Fishing;YCAF\nhardcore-architect;20780;hardcore_architect;Hardcore Architect;;\n;20781;enchantmentdoesnotconflict;附魔不冲突;Enchantment Does Not Conflict;EDNC\ncarpet-neoforged;20782;;Carpet: NeoForged;;\nno-campfire-smoke;20783;nosmoke;No Campfire Smoke;;\ntiny-tasks;20784;tinytasks;Tiny Tasks;;\nfancy-outline;20785;fancyoutline;Fancy Outline;;\necho-compass-hud;20786;echo_compass_hud;Echo Compass HUD;;\ndecorative-stands;20787;decorative_stands;Decorative Stands;;\nconfluence-otherworld-gun-fix;20788;confluencegunanimationfix;Confluence: Otherworld Gun Fix;;\ncauldronpp;20789;cauldronpp;Cauldron++;;\nsbm-cardboardboxes;20790;cardboardboxes;[SBM] Cardboard Boxes;;\n;20791;selfdeprecating_bear;自嘲熊;Self-deprecating Bear;\n;20792;reinforcedtoolsreloaded;强化工具重置版;ReinforcedToolsReloaded;RTR\nbedrock-age;20793;bedrock-age,bedrock_age;基岩时代;Bedrock Age;BA\n;20794;tinkersqingxvan;清泫的匠魂古代武器附加;Tinkers QingXvan;TQ\nreturn-scepter;20795;return_scepter;回程魔杖;Return Scepter;\n;20796;koniava;娜拉工艺;Koniavacraft;KONA\nemojicraft;20797;emojicraft;Emoji工艺;Emojicraft;emoji\nkylin-arm;20798;kylin_arm;麒麟臂;Kylin Arm;\nserver-hats;20799;server-hats;Server Hats;;\nforgotten-ruins;20800;forgottenruins;Forgotten Ruins;;\nremove-water-and-lava-springs;20801;;涌泉大扫除;Remove Water And Lava Springs;RWLS\ncreeperhost-presents-steves-carts;20802;stevescarts;CreeperHost Presents Steve's Carts;;\nperspatium;20803;perspatium;Ad Astra: Per Spatium Et Tempus;;\nface-stealer;20804;skinscary;Face Stealer;;\nofflawn-legacy;20805;offlawn;OffLawn! Legacy;;\nmetalborn;20806;metalborn;Metalborn;;\n;20807;wizards-mod;Wizards Mod;;\nmo-creatures-nostalgia-edition;20808;mocreatures;Mo' Creatures: Nostalgia Edition;;\nmocreatures-awakened;20809;mocreaturesawakened;Mo' Creatures Awakened;;\nsterling;20810;sterling;Sterling;;\ncuisine-tfc;20811;cuisinetfc;料理群峦;Cuisine TFC;\n;20812;natural-temperature;Natural Temperature;;\nsilent-gear-metalworks;20813;sgearmetalworks;Silent Gear Metalworks;;\ntreetap;20814;treetap;Tree Tap;;\nbetter-cave-update;20815;bettercaveupdate;Better Cave Update;;\n;20816;affix;Affix;;\nstarry-end;20817;starryend;Starry End;;\ndynamic-trees-for-cobblemon;20818;cobblemontrees;Dynamic Trees for Cobblemon;;\nimmersive-portal-gun-for-forge;20819;portalgun;Immersive Portal Gun for Forge;;\nadorable-hamster-pets;20820;adorablehamsterpets;Adorable Hamster Pets;;\ninfinite-lava;20821;infinitelava;无限熔岩;Infinite Lava;\nethereal-shots;20822;ethereal_shots;无限箭制;Unlimited Arrow Works;UAW\n;20823;mr__wsswitherstonesword;凋零石剑;Wither Stone Sword;WSS\nepic-fight-super-golem;20824;super_golem;超级铁傀儡;Epic Fight - Super Golem;\nfallen-log-hideout;20825;fallen_log_hideout;Fallen Log Hideout;;\npastel;20826;pastel;彩绘世界;Pastel;\nmob-catcher-unofficial;20827;mobcatcher;Mob Catcher Unofficial;;\nretro-pet-items;20828;retropetitems;Retro Pet Items;;\nconfluence-dimension-patch;20829;confluence_dimension_patch;汇流来世维度补丁;Confluence Dimension Patch;CDP\nconvenient-storage;20830;convenient_storage;便捷存储;Convenient Storage;CvS\nlegendreliclib;20831;legendreliclib;传世遗产库;LegendRelicLib;LRL\njauml;20832;jauml;Jauml;;\nnemexlib;20833;NemexLib;NemexLib;;\ncryonicconfig;20834;cryonicconfig;Cryonic Config;;\nknight-lib;20835;knightlib;Knight Lib;;\n;20837;tickstop;TickStop;;TS\n;20838;one-kil---one-message;One Kill - One Message;;OKOM\n;20839;who_hurt_me_and_who_i_hurt;谁害了我而我又害了谁？;Who Hurt Me And Who I Hurt;WHMAWIH\ncraft-slabs-back-into-blocks;20840;slabstoblocks;Craft Slabs Back into Blocks;;\nhighland-statue;20841;highland_statue;Highland Statue;;\nrestore-ore;20842;restore_ore;复原矿石;Restore Ore;\nfactory-blocks;20843;factory_blocks;工厂方块;Factory Blocks;\ncamouflaged-doors;20844;hidden_doors,camouflaged_doors;Camouflaged Doors;;\nrotten-planks-and-logs;20845;rotten_planks_and_logs;Mar Mar's Rotten Planks & Logs;;\ncreate-extended-bogeys-borked-addon;20846;extendedbogeys;Create: Extend Bogeys;;\ncreate-unify-updated-to-create-6;20847;cutc6;Create: Unify updated to Create 6;;CUTC6\ncreate-arm-made-cuisine;20848;create_cuisine;机械动力：臂力臂力炒菜;Create: Arm-made Cuisine;\ncreate-cultivation-craft;20849;create_cultivation;机械动力：栽培工艺;Create: Cultivation Craft;CCC\nkaleidoscope-cookery;20850;kaleidoscope_cookery;森罗物语：厨房;Kaleidoscope Cookery;\nretroemi-forge;20851;;retroEMI Forge;;rEMIF\n;20852;;PearlEcho;;\n;20853;day-counter;Day Counter;;\nminecolonies-war-n-taxes;20854;wnt;Minecolonies: War 'N Taxes;;\nfalling-down;20855;fallingdown;Falling Down;;\nmore-red-x-cc-tweaked-compat;20856;moreredxcctcompat;More Red x CC:Tweaked Compat;;\n;20857;pickblocknbt;复制方块NBT1.7.10版;Pick Block NBT 1.7.10;\n;20858;speed_uuuuuuup;加加加加加加加速！;Speed Uuuuuuup;SU7\n;20859;pjwto;猪灵贸易;PiglinJoinWTO;PJWTO\nwither-reincarnated;20860;wither_reincarnated;Wither: Reincarnated;;\n;20861;biome-snapshot;Biome Snapshot;;\ntoros-auto-attack;20862;torosautoattack;Toro's Auto Attack;;\ncreeperfire;20863;creeperfire;自定义烧货 / 苦力怕阳光下燃烧;CreeperFire;\nfastspawner;20864;fastspawner;刷怪笼渲染优化/产器速算;FastSpawner;\nsophisticated-storage-plus;20865;sophisticatedstorageplus;存取思深/精妙存储扩容;Sophisticated Storage Plus;\nwhaleborne;20866;whaleborne;鲸帆远航;Whaleborne;\nuniful;20867;unify;Uniful;;\nnewspaper-big-book-bibliocraft-legacy-backport;20868;newspaper;Newspaper (Big Book Bibliocraft Legacy Backport);;\nbic-clipboard;20869;bic_clipboard;BiC Clipboard;;\ntemplates-2;20870;templates;Templates 2;;\n;20871;;LunatriusCore GTNH 版;;\n;20872;;Schematica GTNH 版;;\n;20873;external_command;外部指令输入重制版;Re External Command;\n;20874;the_real_peaceful_mode;真正的和平模式;The real peaceful mode;TRPM\nworkstation-recipe-exporter;20875;visual_recipe_editor;可视化配方编写;Workstation Recipe Exporter;WRE\nsimple-auto-attack;20876;autoattackmod;Simple Auto Attack;;\nhostile-to-neutral;20877;nohostilemobs;Hostile to Neutral;;\ntracer;20878;tracer;Tracer;;\nfishing-tick-fix;20879;fishtickfix;Fishing Tick Fix;;\nfzmm;20880;fzmm;FZMM;;\npasteljei;20881;pasteljei;彩绘世界JEI;PastelJEI;\ne4mc-retro;20882;e4mc_retro_minecraft;e4mc Retro;;\n;20883;startres;Startres;;\nmantlejs;20884;mantlejs;MantleJS;;\none-enough-hand;20885;oneenoughhand;只手遮天;One Enough Hand;OEH\npersistent-moon-phases;20886;moonphase;Persistent Moon Phases;;\nsakurapack-compatability-patch;20887;sakuracompat;SakuraPack Compatability Patch;;\ncattlestrophic;20888;cattlestrophic;Cattlestrophic!;;\nae-recipe-tool;20889;ae_recipe_tool;AE Recipe Tool;;\nminecraft-cursor;20890;minecraft-cursor;Cursors Extended / Minecraft Cursor;;\nstalker-creepers-fabric;20891;stalkercreepers;Stalker Creepers Refabricated;;\nepiv-fight-l-enders-cataclysm-compatibility;20892;epicfight_cataclysm;史诗战斗灾变兼容;Epic Fight & L_Enders Cataclysm compatibility;\nlunar-nether-backport;20893;lunarnether;Lunar Nether Backport;;\nlookinmyeyes;20894;lookinmyeyes;Look In My Eyes;;\nmore-relics-more-rpg-content;20895;more_relics;More Relics (More RPG Content);;\nbackstab-reforged;20896;backstabreforged;Backstab Reforged;;\narmamentarium;20897;armamentarium;Armamentarium;;\n;20898;mr_katters_structures;Katters Structures;;\nremnant-bosses;20899;remnant_bosses;Remnant Bosses;;\nchoccos-mobs;20900;choccos_mobs;Chocco's Mobs;;\n;20901;restore_ore;复原矿石|高版本移植;Restore Ore;\n;20902;bathappypro;🦇蝙蝠快乐Mod Pro;BatHappy Pro;\nuncrafting-table-xiaoyu-2009;20903;uncrafting_table;拆解台;Uncrafting Table;UT\n;20904;sire;简改焕新;Simple Improvement Renew;SIRe\naewirelesschannel;20905;aewirelesschannel;AE无线频道;AE Wireless Channel;AEWC\ninfinitepower;20906;infinitepower;无尽能源 / 能量叠生;Infinite Power;\nhoshikima;20907;hoshikima;随意作品;Hoshikima;HK\ncreate-bugfix-clipboard-patch;20908;clipboard_patch;机械动力：剪贴板修复;Create Bugfix: Clipboard Patch;\ntool-disassemble;20909;tooldisassemble;拆器碎散/匠魂工具拆解;Tool Disassemble;\nsummer-to-life;20910;summer_to_life;夏意盎然;Summer to Life;\n;20911;freedraw;FreeDraw;;\ngeckojs;20912;geckojs;GeckoJS;;\nsanandreasps-manager-pack;20913;sapmanpack;SanAndreasP's Manager Pack;;\n;20914;endless;无尽;Endless;EDS\ntouhoulittlemaid-orihime;20915;touhou_little_maid;车万女仆：织姬;Touhou Little Maid: Orihime;TLMO\nmore-mod-tetra;20916;more_mod_tetra;更多Tetra模组锻造材料;More Mod Tetra;MMT\ncataclysm-dimensions;20917;cataclysm_dimension;灾变：维度;Cataclysm Dimensions;\n;20918;;余烬之心：非官方延续版;Aetherworks: Unofficial Extended Life;AUEL\nmisttaczskills;20919;misttaczskills;TaCZ：圣化技能扩展;MistCloud Tacz Skills Extension;MTSE\nquality-railway;20920;qr;品质铁路;Quality Railway;QR\n;20921;onlytp;仅传送;OnlyTP;OTP\ncustom-mob-attributes;20922;custom-mob-attributes;Custom Mob Attributes;;\ncustom-durability;20923;custom-durability;Custom Durability;;\n;20924;dragons_eggs;Dragon's Egg(S);;\nzombie-variants;20925;zombie_variants;Zombie Variants;;\nextra-carts;20926;extracarts;Extra Carts;;\nfairy-factions-revived;20927;fairyfactions;Fairy Factions Revived;;\nnetherited;20928;netherited;Netherited (Fireproof Items);;\ncreeperhost-presents-soul-shards;20929;soulshards;CreeperHost Presents Soul Shards;;\nmyths-n-legends;20930;mythsandlegends;神话与传说;Myths & Legends;\n;20931;snackcraft;零食工艺;SnackCraft;SC\n;20932;funprops;趣味道具;Funprops;FP\ncopper-backport;20933;leafscopperbackport;Leaf's Copper Backport;;\n;20934;gunonhorse;TaCZ：马鞍上的武器;TaCZ:Gun on Horse;\n;20935;shieldindicator;盾牌指示器;Shield Indicator;SI\na-bastion-remnant-treasure-reset;20936;bastion_remnant_treasure_reset;堡垒遗迹藏宝室重置;Bastion Remnant Treasure Reset;BRTR\na-bastion-remnant-hoglin-stable-reforged;20937;bastion_remnant_hoglin_stable_reforged;堡垒遗迹疣猪兽棚重铸;Bastion Remnant Hoglin Stable Reforged;BRHSR\ncolonial-arcana-minecolonies-addon;20938;colonial_arcana;殖民地魔法;Colonial Arcana;\n;20939;highly_toxic_water;水是剧毒的;Highly Toxic Water;HTW\nfires-ender-expansion;20940;firesenderexpansion;Fire's Ender Expansion;;\nthetitansneo;20941;thetitansneo;泰坦生物Neo;TheTitansNeo;\nvectorientation-reneoforge;20942;vectorientation;Vectorientation: ReNeoForge;;\ncopyid;20943;copy_id;复制ID;CopyID;CID\n;20944;a_light;A light: Color beacon;;\n;20945;surveyor;Surveyor Map Framework;;\n;20946;create_portable_packaging;Create: Portable Packaging;;\ncreate-more-pipe-bombs-in-packages;20947;cmpackagepipebomb;Create More: Pipe Bombs in Packages;;\ncreate-bugfix-schematic-patch;20948;schematicsfix;机械动力：蓝图补丁;Create Bugfix: Schematic Patch;\nstuff-and-additions-tag-based;20949;;Stuff And Additions tag based;;\ncreate-ornithopter-glider;20950;createornithopterglider;Create: Ornithopter Glider;;\ncreate-train-parts;20951;create_train_parts;Create Train Parts;;\n;20952;breakableblocks;Breakable Blocks;;\nflux-network-x-cc-tweaked;20953;fncct;Flux Network x CC: Tweaked;;FNCCT\ntwotility;20954;TwoTility;TwoTility;;\nopticubes;20955;opticubes;OptiCubes;;\nclosets-by-smplesco;20956;opticubes;Closets by smplesco;;\nvariety-chests-mod;20957;varietychests;Variety Chests Mod;;\nlight-detector;20958;lightdetectortool;Light Detector;;\nquality-hails;20959;qualityhails;Arbalest Golems;;\nancient-structures-edo-japan;20960;edo_japan,asedojapan;Ancient Structures: Edo Japan;;\ntiger-shark;20961;tiger_shark;海虎;TigerShark;TS\n;20962;sunproof_zombies;防晒僵尸;Sun-Proof Zombies;\nbastion-remnant-bridge-reforged;20963;bastion_remnant_bridge_reforged;堡垒遗迹桥重铸;Bastion Remnant Bridge Reforged;BRBR\nbastion-remnant-units-reforged;20964;bastion_remnant_units_reforged;堡垒遗迹居住区重铸;Bastion Remnant Units Reforged;BRUR\nvanilla-better-end-city;20965;betterendcities,betterendcitiesvanilla;更好的末地城;Better End Cities;\nfallen-gems-affixes;20966;fallen_gems_affixes;Fallen Gems & Affixes;;\nskill-tree;20967;skill_tree_rpgs;Skill Tree (RPG Series);;\nblatium;20968;blatium;Blatium;;\ntrimica;20969;trimica;Trimica;;\nfantasy-weapons-medieval-series;20970;fantasy_weapons;Fantasy Weapons (Medieval Series);;\n;20971;lsktp;磷光传送;LSKTP;\nksit;20972;ksit;KSit;;\nbestia;20973;bestia;Bestia;;\n;20974;itemfinder;ItemFinderInAll-items;;\nfilterchat;20975;filterchat;FilterChat;;\nkit-keep-inventory-timer-keepinventorytimer;20976;keepinventorytimer;Keep Inventory Timer;;KIT\nuseful-luck;20977;usefulluck;Useful Luck;;\n;20978;sneakfertilizingmod,fertilizermod;Sneak Fertilizing;;\np1neros-epic-fight-leawind-compat;20979;eflw;P1nero's Epic Fight Leawind Compat;;EFLC\ncustom-entity-leveling;20980;custom_entity_leveling;自定义实体等级;Custom Entity Leveling;\nno-monster-rooms;20981;removemonsterrooms;No Monster Rooms;;\n;20982;carpet-rms-addition;Carpet RMS Addition;;\n;20983;fireworkindicator;Firework Duration Indicator;;\nsqueedometer-reforged-special-port;20984;squeedometerspecialport;Squeedometer Reforged Special Port;;\n;20985;;Vintage Horizons;;\ndifficulty-patch;20986;difficultypatch;Difficulty Patch;;\nsquake-reforged;20987;squakereforged,squake;Squake Updated;;\nsquake-reforged-special-port;20988;squakereforgedspecialport;Squake Reforged Special Port;;\nbrigo;20989;brigo;Brigo;;\nae2-web-integration;20990;ae2webintegration-core;AE2 Web Integration;;\nskillet-man-core;20991;skillet_man_core;Skillet Man Core;;\np1neros-dialogue-lib;20992;p1nero_dl;P1nero's Dialogue Lib;;\n;20993;headshots;爆头;Headshots Mod;\n;20994;MCRenderCrashFix;渲染崩溃修复;MCRenderCrashFix;\ncreate-arm-made-barbeque;20995;create_arm_made_bbq;机械动力：臂力臂力烧烤;Create : Arm-made Barbeque;\nindestructible-blocks;20996;indestructible_blocks;Indestructible Blocks;;\n;20997;DeathChest;死亡箱子;Death Chest;\nepic-fight-super-chef;20998;super_chef;Epic Fight - Super Chef;;\nepicfight-nightfall;20999;efn;史诗战斗：夜幕;EpicFight-Nightfall;EFN\ngenesis-official;21000;genesis;Genesis;;\nfeaturecreep;21001;featurecreep;FeatureCreep;;\n;21002;hudapi;HUDAPI;;\nfabric-orm-jimmer;21003;fabric-orm-jimmer;Fabric ORM Jimmer;;\naces-spell-utils;21004;aces_spell_utils;Ace's Spell Utils;;\ntargeteventbackport;21005;targetevent;TargetEventBackport;;\nsilk-kt;21006;silk-all,silk-core;Silk;;\n;21007;nabanmod;机器码封禁;NABan;\n;21008;BetterAnvil;更好的铁砧;Better Anvils;\n;21009;;(Old MC) Commands;;\nlegends-forge-series-vikings-n-northerners;21010;lfs_vikings_n_northerners,lfs_vikings_n_northerners_fabric;Legends Forge Series - Vikings N Northerners;;\ntinkers-battle-spades;21011;tinkers_battle_spades;Tinkers' Battle Spades;;\n;21012;itsnotreal;ITS NOT REAL [Legacy];;\nly-meteors;21013;mr_meteors;Meteors;;\nashvehicle;21014;ashvehicle;[SBW] AshVehicle;;\n;21015;smart-absorption;Smart Absorption;;\n;21016;mooshees;Mooshees;;\ncity-of-chambers;21017;city_of_chambers;City of Chambers;;\ntower-of-chambers;21018;tower_of_chambers,tower_of_chambers_neoforge;Tower of Chambers;;\nloot-bags-plus;21019;lootboxesplus;Loot+ [Loots Bags + Crates];;\nancient-structures-chinese;21020;aschinese;Ancient Structures: Chinese;;\neypipes;21021;eypipes;Eypipes;;\nshipping-bin;21022;shippingbin;Shipping Bin;;\nhbm-nuclear-tech-mod-community-edition;21023;hbm;HBM的核科技社区版;HBM's Nuclear Tech Mod: Community Edition;HBMCE\ngte2-coremod;21024;gtexpert;GTExpertCore;;\njapan-props;21025;jpp;Japan Props;;\n;21026;yaoyaocoin;存收堆携/妖妖币;yaoyaoCoin;yyc\none-enough-item;21027;oneenoughitem;One Enough Item;;OEI\no123456789;21028;o123456789;O123456789;;\nfriendly-chests-no-fluid;21029;friendly_chests;Friendly Chests: No Fluid;;\nherios-floral-expansion;21030;herios_floral_expansion;Herios' Floral Expansion;;\npinkcabbage-transparent-betterscreen;21031;cabbagejellypack;粉红大包菜的屏幕透明度优化;PinkCabbage Transparent BetterScreen;PKBS\nwastify-returns;21032;;Wastify Returns;;\nsillys-crossbow-fix;21033;sillys-crossbow-fix;Silly's Crossbow Fix;;\nthaumpotion-tweaker;21034;ThaumPotionTweaker;ThaumPotion Tweaker;;\nnovoatlas;21035;novoatlas;NovoAtlas;;\nresource-pack-tweaks;21036;res_tweaks;Resource Pack Tweaks;;\nnight-vision-decay-fix;21037;nvfix;夜视衰减修复;Night Vision Decay Fix;NVFix\nroid-tweaker;21038;roidtweaker;Roid's Tweaker;;\ntranslatorpp;21039;translatorpp;Translator++;;\nmodular-machinery-community-edition-addons;21040;modularmachineryaddons;Modular Machinery Community Edition: Addons;;\nrandomaddon;21041;random_addon;随意之作;Random Addon;RA\nmyhead;21042;myhead;持器射首 / MyGO 你个头;BanG Dream! It's MyHead!!!;\nboss-dps-tracker;21043;bossdpstracker;查其所伤/Boss伤害追踪器;Boss DPS Tracker;BDT\nwandering-trader-may-leave;21044;wanderingtradermayleave;Wandering Trader May Leave;;\nmod-tabs;21045;modtabs;Mod Tabs / Legendary Tabs - Neoport;;\ntotem-bar;21046;totembar;Totem Bar;;\nvisualores-meowmel;21047;visualores;VisualOres-Meowmel;;\nsavebabystriders;21048;savebabystriders;Save Baby Striders;;\nbygone-nether-fixes;21049;bygone_nether_fixes;逝去的下界修复;Bygone Nether Fixes;\nbossesrise;21050;block_factorys_bosses;Bosses'Rise;;\n;21051;nohitdelay;禁用点击间隔;NoHitDelay;\nadvancementinfo-update;21052;advancementinfo;AdvancementInfo Update;;\nexpanded-mace-enchanting;21053;expanded_mace_enchanting;Expanded Mace Enchanting;;\nsilky-spawners;21054;silky_spawners;Silky Spawners;;\nstarhud;21055;starhud;StarHUD;;\nceleritas-dynamic-lights;21056;celeritasdynamiclights;Celeritas Dynamic Lights;;\nresistance-balancer;21057;resistance_balancer;Resistance Balancer;;\nsilkier-touch;21058;silkiertouch;Silkier Touch;;\nwither-spawn-fix;21059;wither_spawn_fix;凋灵生成修复;Wither Spawn Fix;\naccelerated-rendering;21060;acceleratedrendering;加速渲染;Accelerated Rendering;AR\ncii;21061;cii;Custom Item Interactions;;CII\nsimpler-wooden-pipes;21062;simplewoodenpipes;Simpler Wooden Pipes;;\ngarden-cloche-backport;21063;gardencloche;Garden Cloche Backport;;\nbh-boss-hatred;21064;boss_hatred;首领仇恨;Boss Hatred;BH\nheros-journey;21065;hsj;Hero's Journey;;\ncamol;21066;camol;Camol;;\nbbb-unforged;21067;betterbiomeblend;Betterer Biomer Blender;;\nperformance-monitoring-enhancer;21068;performancemonitoringenhancer;Performance Monitoring Enhancer;;\ncreate-upsizing;21069;upsizing;Create: Upsizing;;\nbetter-world-loading-forge;21070;betterworldloading;Better World Loading;;\n;21071;blue_archives_craft_core;Blue Archive Craft Core;;\napocalypse-minimap-hud;21072;apocalypseminimaphud;启示录：小地图Hud;Apocalypse Minimap Hud;\nadvancementnomore;21073;advancementnomore;进度移除;AdvancementNoMore;\ncollisium;21074;collisium;Collisium;;\ndypsis;21075;dypsis;Dypsis;;\nchat-plus;21076;chatplus;Chat Plus;;\nmcreatormemfix;21077;mcreator_mem_fix;MCreatorMemFix;;\n;21078;atlas;Atlas;;\nlegacy-tweaks;21079;legacy_tweaks;Legacy Tweaks;;\neasy-elevators;21080;easyelevators;简单电梯;Easy Elevators;\ndavincing;21082;davincing;DaVincing;;\naeronautical-radio;21083;aeronautical_radio;航空无线电;Aeronautical Radio;ATR\nhunter-vs-mice;21084;huntervsmicemod;猎人鼠鼠游戏;Hunter Vs Mice;HVM\ncitymod-one-block-one-building;21085;citymod;CityMod: One Block - One Building;;\ngedrite-mod;21086;gedrite;Gedrite Mod;;\n;21087;shorkie;Shorkie;;\nenchantable-boat;21088;enchantableboat;Enchantable Boat;;\nthrow-bricks;21089;throwbricks;Throw Bricks;;\nanimal-pens;21090;animal_pen;Animal Pens;;\n;21091;steponyellow;不能踩黄色;Step On Yellow;\n;21092;mc_lb;我的世界创作者的幸运方块;Minecraft Creator's Lucky Block;MCLB\n;21093;rider_jade;自定义骑士;RiderJade;\n;21094;gly_can;GLY的罐头;GLYCan;\nfargo-soul;21095;fargo_soul;Fargo魂石;Fargo Soul;\n;21096;brokenbows;破损弓;Broken Bows;Bb\nmekanism-trimming;21097;mekanism_trimming;通用机械纹饰;Mekanism Trimming;\nteleportcakes;21098;teleportcakes;跃迁蛋糕;Teleport Cakes;\nrepair-amulet;21099;repair_amulet;Repair Amulet;;\nalpine;21100;alpine;Alpine;;\nclientchatclean;21101;clientchatclean;客户端消息清理;ClientChatClean;CCC\nclickmobs;21102;clickmobs;Click Mobs;;\nvlib;21103;vlib;VLib;;\nwabbanode-companion;21104;wabbanodecompanion;Wabbanode Companion;;\n;21105;mcmti;麦克风语音识别文本输入;Microphone Text Input;\npowernexus-menu;21106;custommenuv4;PowerNexus-Menu;;\nhit-delay-fix;21107;hitdelayfix;Hit Delay Fix;;\nbetterexcavate;21108;betterexcavate;更好的挖掘机制;BetterExcavate;\noptimization-of-campfire-smoke;21109;ocs;营火烟雾优化;Optimization of Campfire Smoke;\narmored-combat-enhancer;21110;armoredcombatenhancer;Armored Combat Enhancer;;\n;21111;mr_biome_strongholds;Biome Strongholds;;\nanimal-hats;21112;animal_hat;Animal Hats;;\n;21113;raidrestore;Raid Restorer;;\nasgardandelion-for-botania;21114;botaniaasgard;Avaritia Asgardandelion For Botania;;\ntrotting-wagons;21115;trotting_wagons;Trotting Wagons;;\ngrindstonehoning;21116;grindstone_honing;GrindstoneHoning;;GH\nrainbowwrenchforjei-reforge;21117;rwfj;彩虹扳手：Forge版;RainbowWrenchForJei: ReForge;RWFJR\nbetter-locate-structure-command;21118;betterlocate;更好的结构定位指令;Better Locate Structure Command;\natt-miss;21119;att_miss;闪避攻击;Att Miss;\nenergizexp;21120;rfxp;EnerGizeXP;;\nno-more-magic-choices;21121;nomoremagicchoices;更好的铁魔法UI;No More Magic Choices;NMMC\npatternbetter;21122;patternbetter,pattern_better;更好的ME样板供应器;Pattern Better;PB\nlegacy-skins;21123;legacyskins;Legacy Skins;;\n;21124;oh_my_pccccccccc;OH MY PC;;\nvsprinter;21125;vsprinter;VSPrinter;;\nclickvillagers;21126;clickvillagers;ClickVillagers;;\n;21127;button_packet_id_fix;Button Packet ID Fix;;\naccessories-compat-layer;21128;accessories_compat_layer;Accessories Compatibility Layer;;\nworld-downloader;21129;worlddownloader;World Downloader;;\ntameable-guinea-pigs;21130;guinea_pigs;Tameable Guinea Pigs;;\nfamiliarslib;21131;familiarslib;FamiliarsLib;;\ndmr;21132;dmr;龙骑士重制版;Dragon Mounts Remastered;DMR\n;21133;scarecrow;盔甲架：引诱敌者;scarecrow;\navaritia-expand;21134;avaritia_expand;无尽贪婪 : 拓展;Avaritia : Expand;\npolaristrading;21135;polaristrading;北极星市场方块;PolarisTrading;\necliptic-seasons-multimod-patch;21136;eclipticseasons_multimodpatch;Ecliptic Seasons: MultiMod Patch;;\nrandom-things-returns;21137;randomthings;Random Things Returns;;\ninfinite-storage-cell;21138;infinitestoragecell;Infinite Storage Cell;;\ndelta-technology;21139;delta;八宝粥行动;Delta Technology;Dt\nelegant-spells;21140;elegant_spells;雅致咒术;Elegant Spells;ES\n;21141;seelescraft;希儿的工坊;Seele's Craft;\nreadyhomes;21142;ready_homes;随时可住;ReadyHomes;\narknights-weapon;21143;arknights_weapon;方舟武器;Arknights Weapon;\n;21144;fire_water;人间地狱;Fire Water;FW\nillager-warship;21145;illagerwarship;Illager Warship;;\nasian-fountains;21146;asianfountain;Asian Fountains;;\nillager-chef-trader;21147;illager_chef;Illager Chef Trader;;\nskyblock-villager;21148;skyblock_villager;Skyblock Villager;;\n;21149;ye;Redstoneer Villager;;\nperrys-urban;21150;perrys_urban;Perry's Urban;;\nneo-blahaj;21151;;Neo-Blåhaj;;\n;21152;easyjava;Easy Java;;\nteleportation-crystal;21153;teleportation_crystal;传送水晶;Teleportation Crystal;\nmajo-spell-enchantment;21154;majospellenchantment;魔女的魔咒;Majo's Spell Enchantment;\noaks-capes;21155;oakscape;Oaks Capes;;\n;21156;mr_unlock_theend;Unlock The End;;\ncobblemon-ride-on;21157;cobbleride;Cobblemon：骑乘！;Cobblemon: Ride On!;CRO\nclayium-additions;21158;clayiumadditions;Clayium Additions;;\nfarmers-tfc;21159;farmerstfc;农夫群峦;Farmers TFC;\noaks-delight;21160;oaksdelight;Oaks Delight;;\n;21161;;咕咕工具：非官方版;GuGu Utils Unofficial;\npathfinding-edition-for-minecolonies;21162;colonypathingedition;殖民地寻路大修;Pathfinding Edition For Minecolonies;\nclicksigns;21163;clicksigns;Click Signs;;\n;21164;salted_fish;咸鱼;Salted Fish;SF\nherios-helian-mod;21165;herios_helian_mod;Herios' Helian Mod;;\ndecorative-food;21166;dfoof;装饰性食物;Decorative Food;DF\n;21167;gzrfj;蛊真人附加;;GZRFJ\nanvilcraft-alloyextension;21168;anvilcraft_alloyextension;铁砧工艺：合金扩展;Anvilcraft: Alloy Extension;\narkifs-hoverboard-mod;21169;hoverboardmod;Arkif's Hoverboard Mod;;\nglow;21170;glow;Glow;;\nleaf-villager-trader;21171;leaf_villager;Leaf Villager Trader;;\nguard-villager-trader;21172;guard_villager_trader;Guard Villager Trader;;\nhunter-villager-trader;21173;hunter_villager_trader;Hunter Villager Trader;;\ngarbage-collector-villager;21174;garbage_collector_villager;Garbage Collector Villager;;\nmore-traders;21175;more_traders;More Traders;;\nae2-better-villagers;21176;ae_better_villagers;AE2: Better Villagers;;\nharvester-trader;21177;gravekeeper_trader;Harvester Trader;;\nrailman-villager;21178;railmanvillager;Railman Villager;;\ndimensionaltraders;21179;dimensional_traders;Dimensional Trade Villagers;;\ntaxbiometrader;21180;taxbt;Tax' & Leon's Biome Trader / T&L's Biome Trader;;\nquickelytraswapperplus-client;21181;elytraswapperplus;ElytraSwapper+ / Quick Elytra Swapper;;\n;21182;elytrakey;ElytraKey;;EK\nelytraswapperplus;21183;elytraswapperplus;Elytra Swapper + [Server];;\ntaste-fatigue;21184;taste_fatigue;味觉疲劳;Taste Fatigue;\nrealistic-nametag;21185;realisticnametag;Realistic Nametag;;\nhitboxplus;21186;hitboxplus;HitBox+ / HitBoxPlus;;\n;21187;hittable;Hittable;;\nhitmarker;21188;hitmarker;Hitmarker;;\nautolandingwater;21189;auto-landing-water;自动落地水;AutoLandingWater;\nskygrid-reloaded;21190;skygrid_reloaded;SkyGrid Reloaded;;\n;21191;stonewood-industry;石木工艺;Stone & Wood Industry;SWI\nroomopolis;21192;roomopolis;Roomopolis;;\nbbl-placers;21193;placers;BBL Placers;;\ndraconic-legacy-models;21195;olddraconicmodels;龙之进化旧版模型;Draconic Legacy Models;\nfurnaces;21196;furnacesplus;Furnaces+;;\ngtconstruct;21197;;GregTech-Construct;;\nrain-be-gone-ritual;21198;rainbegoneritual;Rain Be Gone Ritual;;\nthe-vegan-option;21199;veganoption;素食者的选择;The Vegan Option;\ngensokyo-delight-youkais-feasts;21200;youkaisfeasts;幻想乡乐事 ~~ 妖怪们的飨宴;Gensokyo Delight ~ Youkais' Feasts;\n;21201;mcreator_detector;MCreator模组检测器NeoForge版;;\ntuttas-doors;21202;alltuttasneeds;All Tutta's Needs / Tutta's Doors;;\n;21203;effectlivingdead;行尸走肉;Effect Living Dead;\nvs-chunkloader;21204;vschunkloader;VS区块加载器;VS ChunkLoader;\ncreate-blocks-bogies;21205;create_bb;Create: Blocks & Bogies;;\ncreate-netherite-additions;21206;create_nj;Create Stuff & Netherite Additions;;\ncreate-sophisticated-backpacks-compat;21207;create_sophback_compat;Create: Sophisticated Backpacks Compat;;\ncreate-item-pets;21208;item_pets;Create: Item Pets;;\nc-bc-nukes-6-0-port;21209;canonnukes;C:BC Nukes 6.0 port;;\nplushieverse;21210;plushieverse;PlushieVerse;;\nthavma;21211;thavma;Thavma;;\narsarms;21212;arsarms;ArsArms - Ars Nouveau & Timeless and Classics Zero;;\nyorupo-mod;21213;yorupo;PEPOYO Yorupo;;Yorupo\nyande-mod;21214;yande;PEPOYO Yande;;Yande\ngtcefucontent;21215;gtcefucontent;Frivolously Unofficial Content;;GTCEFU\nmcheli-overdrive-ext;21216;;Mcheli-O-Ext;;\niron-biplane;21217;iron_biplane;Iron Biplane;;\nrealistic-computers;21218;realisticcomputers;Realistic Computers;;\nboss-mob-enhancer;21220;bossmobenhancer;Boss Mob Enhancer;;\nflight-api;21221;flightapi;飞行 API;Flight API;\nunlimited-spawners;21222;unlimited_spawners;无限刷怪笼;Unlimited Spawners;\n;21223;recallhealth;RecallHealth;;\n;21224;minecartvisualizer;敞骑速视 / 矿车信息可视化;MinecartVisualizer;MV\n;21225;shadowboarders;ShadowBorders;;\nauthenticate;21226;authenticate;认证;Authenticate;\n;21227;mr_ore_plus;Ore Plus;;\nunbinilium;21228;unbinilium;Unbinilium;;\nveinbreaker;21229;VeinBreaker,veinbreaker;VeinBreaker;;\nactions;21230;actions;Actions;;\ni-have-no-mouth-but-i-must-chat;21231;ihnmbimc;I Have No Mouth But I Must Chat;;\nrenderscale;21232;renderscale;RenderScale;;\nproportional-destruction-particles;21233;pdp;Proportional Destruction Particles;;PDP\ntacz-ammo-query;21234;taczammoquery;TaCZ Ammo Query;;\nfast-happy-ghasts;21235;fast_happy_ghasts;Fast Happy Ghasts;;\n;21236;starhudforged;StarHUDForged;;\n;21237;slotted_armor_hud;Slotted Armor HUD;;\n;21238;TCAutoResearchByEmil;神秘时代4研究笔记自动解锁;;TC4HelpFix\n;21239;saysorry;Say Sorry!;;\n;21240;playerautoma;Playerautoma;;\nim-fast;21241;imfast;I'm Fast;;\n;21242;unpath;Unpath;;\nno-more-popups;21243;no-more-popups;No More Pop-ups;;\nsussypatches;21244;sussypatches;SussyPatches;;\n;21245;simple-item-editor;Simple Item Editor;;\nbaby-zombie-removal;21246;babyzombieremoval;Baby Zombie Removal;;\nimmersive-sounds;21247;mr_immersive_sound;Immersive Sounds;;\nboatview360;21248;boatview360;BoatView360;;\n;21249;myresourcepack;My Resource Pack, My Choice;;\noaks-bits;21250;oaksbits;Oaks Bits and Bops;;\n;21251;starsourceblade;星源拔刀剑;Star Source Blade;\nsnow-waifu-spell;21252;snowwaifuspell;雪下的誓言;Snow Waifu Spell;\nmob-farm-trap;21253;ms;香草刷怪塔陷阱强化：涌渊敌冢;Mob Farm Trap;MFT\ntwograve;21254;TwoGraves;TwoGrave;;\nperrys-decorations;21255;errys_decorations;Perry´s Decorations;;\nperry-s-decorations-2;21256;perrys_decorations_2;Perry´s Decorations 2;;\nbookshelf-extended-enchanting-reach;21257;beer;Bookshelf Extended Enchanting Reach;;BEER\nenchanter-fix;21258;enchanter_fix;Enchanter Fix;;\ndynamic-regen-enhancer;21259;regenenhancer;Dynamic Regen Enhancer;;\nguinea-pig-variety;21260;guinea_pig_mod;Guinea Pig Variety;;\n;21261;project_light;光之种计划;Project Light;\n;21262;areas-hint;域名;Areas Hint;AH\nyammo;21263;yammo;Yet Another Memory Optimization;;YAMO\n;21264;mr_bring_spawnchunksback;Bring Spawn Chunks Back;;\nfps-display-doubler;21265;fpsdisplaydoubler;FPS Display Doubler;;\ninventory-stages;21266;inventory_stages;背包阶段;Inventory Stages;IS\ngregmek;21267;gregmek;格雷矿物通用机械支持;GregMek;GM\n;21268;armor_hud;Armor Hud;;\n;21269;;uku's Armor HUD;;\nmob-flow-utilities;21270;flowtech;FMob Flow Utilities / FlowTech: Mob Systems;;\n;21271;marveloustinker;奇妙工匠;Marvelous Tinker;MT\nftb-materials;21272;ftbmaterials;FTB Materials;;\nthe-great-below;21273;thegreatbelow;The Great Below;;\nillagerblabber;21274;illagerblabber;IllagerBlabber;;\n;21275;proof_of_hono;荣誉的证明;Proof of Honor;rydzm\nserene-season-extended-fix;21276;sereneseasonsextended;Serene Seasons Extended;;\ncreate-enchantment-industry-plus;21277;create_enchantment_industry_plus;Create Enchantment Industry Plus;;\nboiled-one-unleashed;21278;boiled_one_unleashed;Boiled One: Unleashed;;\ncreaturecraft-plus;21279;creaturecraftplus;CreatureCraft Plus;;\nwar-elephants;21280;elephantmount;War Elephants;;\n;21281;all_eat;万物皆可吃;All Eat;\none-click-one-block-1c1b;21282;oneclickoneblock;点到为止;One Click One Block;1C1B\n;21283;translatabledebugoptions;可翻译调试选项屏幕;TranslatableDebugOptions;TDO\n;21284;easy_player_action_monitor;简易玩家行为日志;EasyPlayerActionMonitor;EPAM\nsuppressopengl1280;21285;suppressopengl1280;OpenGL错误隐藏;Suppress OpenGL Error 128(0-2);\nfexs-vehicle-mod;21286;fvtm;Fex's Vehicle and Transportation Mod;;FVTM\notterlib;21287;otterlib;OtterLib;;\ndimban;21288;dimban;维度禁用;DimBan;\nonlyoneitem;21289;only_one_item;仅存一物;Only One Item;OOI\nbedload;21290;bedload;自定义区块加载器;BedLoad;\n;21291;unreachablenether;Unreachable Nether;;\n;21292;nether-bedrock-cracker;Nether Bedrock Cracker Mod;;\n;21293;detailabreconst;Detail Armor Bar Reconstructed;;\n;21294;autofish;Auto Fishing;;\n;21295;cutomslashblade;自定义拔刀剑;Custom Slash Blade;\nindrevjei;21296;indrevjei;工业革命JEI;IndRevJEI;IRJEI\nfpresearchpatcher;21297;fpresearchpatcher;飞向未来研究数据包兼容;Futurepack Research Patcher;FPRP\nme-infinity-cell;21298;meinfinitycell;ME无限元件;ME Infinity Cell;\nair-jump;21299;airjump;Air Jump;;\nmore-sniffer-flowers;21300;moresnifferflowers;更多嗅探兽花卉;More Sniffer Flowers;\nso-many-enchantments;21301;somanyenchantments;更多附魔 1.12.2 重写版;So Many Enchantments 1.12.2;\nenchantability;21302;enchantability;附魔能力;Enchantability;\nender-lead;21303;enderlead;末影拴绳;Ender Lead;EL\n;21304;iib;无限玩家背包;InfinityInventoryBackpack;IIB\n;21305;hipb;Hidden in Plain Blocks;;HIPB\ncreate-growableores-compat;21306;growable_ores_create_compat;Create: GrowableOres Compat;;\n;21307;mr_create_renewableegapples;Create: Renewable Notch Apples;;\ngears-n-kinetics;21308;gnkinetics;Create: Gears n' Kinetics;;\nvalkyrien-sails;21309;vs_sails;Valkyrien Sails;;\n;21310;vs_oddoties;VS Oddities;;\nmoo-fluids-modern;21311;moofluids;MooFluids Modern;;\nyan-from-ff14;21312;m6syan;FF14的羊;Yan from FF14;\ndumplings-delight-bedrock-unofficial;21313;;饺子乐事基岩版;Dumplings Delight Bedrock (Unofficial);\ncasualness-delight-bedrock-unofficial;21314;;随意乐事基岩版;Casualness Delight Bedrock (Unofficial);\ntfc-cuisine;21315;tfc_cuisine;群峦料理;TFC Cuisine;\ntfc-no-collapse;21316;tfcnocollapse;群峦传说：塌方禁止;TFC No Collapse;TFCNC\nno-sleep-chat-overlay-nsco;21317;no-sleep-chat-overlay;No Sleep Chat Overlay;;NSCO\nfunkys-fancy-furnaces;21318;fancyfurnaces;Funkys Fancy Furnaces;;\ntimber-enchantment;21319;mr_timber_enchantment;Timber Enchantment;;\n;21320;alternativechunkloading;Alternative Chunkloading;;\n;21321;particulate;Particulate💦;;\nhaydenapi;21322;haydenapi;HaydenAPI;;\nspore-inquisition;21323;;Spore Inquisition;;\nice-and-fire-spartan-weaponry-extras;21324;spartanfireextras;Ice and Fire + Spartan Weaponry: Extras;;\n;21325;vanity_dungeons;Vanity: Dungeons;;\n;21326;fcitx5-enhancer;Fcitx5 Enhancer;;\nkubejs-actually-additions;21327;kubejs_actuallyadditions;KubeJS Actually Additions;;\nvibrative-voice;21328;vibrativevoice;Vibrative Voice;;\nspawnlock;21329;spawnlock;SpawnLock;;\nenchantment-custom-table;21330;enchantment_custom_table;自定义附魔台;Enchantment Custom Table;\n;21331;newnew_attributes;牛牛属性;NewNew Attributes;NNA\n;21332;navigator;Navigator;;\n;21333;xi_bao;喜报Beta1.7.3;XiBaoB1.7.3;\nkeeping-inventory-reborn;21334;keeping_inventory_reborn;Keeping Inventory Reborn;;\ncamel-extend;21335;camelextend;骆驼拓展;Camel Extend;\nconfluence-music;21336;confluence_music;汇流音乐;Confluence Music;\ncancel-attack-cooldown;21337;cancel_attack_cooldown;取消冷却间隔;Cancel Attack Cooldown;\nsurprising-loot;21338;surprisingloot;藏奇速释/惊喜战利品;Surprising Loot;\n;21339;onlykeys;OnlyKeys;;\nflair;21340;flair;Flair;;\nminecraft-comes-alive-reforged;21341;;凡家物语·重制;Minecraft Comes Alive Reforged;MCAR\ncave-mita-dweller;21342;cave_dweller;Mita Dweller;;\neeeabs-mobs-death-mode;21343;eeeabs_ut;EEEAB's Mobs Death Mode;;\nrevivetoken;21344;revivetoken;复活币;ReviveToken;\nreal-filing-reborn-cabinet-based-item-storage;21345;realfilingreborn;Real Filing Reborn - Cabinet Storage;;\nquivers;21346;quivermod;Quivers+;;\ninfused-crystals;21347;infused_crystals,infused_crystals_2;Infused Crystals;;\nmendingremover;21348;mendingremover;MendingRemover;;\nauto-vanilla;21349;autosmithingtable;AutoVanilla;;\nunderground-villages-stoneholm;21350;underground_village;Underground Village, Stoneholm;;\ngep;21351;gep;General Essentials Pack;;GEP\n;21352;mr_superfastghasts;Super Fast Ghasts;;\nmore-harnesses;21353;moreharnesses;更多挽具;More Harnesses;\nvanilla-aiots;21354;vanillaaiots;Vanilla AIOTs;;\nnether-villager-merchant;21355;nether_merchant;Nether Villager Merchant;;\nend-villager-trader;21356;end_villager_trader;End Villager Trader;;\ndeep-dark-trader;21357;deep_dark_trader;Deep Dark Trader;;\nrandom-generator-block;21358;randomgeneratorblock;Random Generator Block;;\ndevour;21359;devour;DEVOUR;;\nquantum-storage-reborn;21360;quantumstoragereborn;Quantum Storage Reborn;;\nlibrary-section;21361;;Every Compat (Library Section);;\nmove-subtitles;21362;movesubtitles;Move Subtitles;;\n;21363;modrenf3;Modren F3;;\nmodern-inline;21364;moderninline;Modern Inline;;\nculler;21365;culler;Culler;;\n;21366;contrats;PlayerNoticeBoard;;\nextendedae-plus;21367;extendedae_plus;ExtendedAE Plus;;EAEP\n;21368;;AE2UEL Extra;;\n;21369;mr_animated_doors;Animated Doors;;\ngems-realm;21370;gemsrealm;Every Compat (Gems Realm);;\narrowcollect;21371;arrowcollect;箭矢回收修复;ArrowCollect;AC\n;21372;traveler;PrimitiveMobsRevival;;\nsteampunk-clocktower;21373;steampunk_clocktower;Steampunk Clocktower;;\npiglin-outpost;21374;piglin_outpost;Piglin Outpost;;\nnolando-structurez;21375;nolandostructurez;Nolando StructureZ;;\nskinlayers3d-customskinloader-bridge;21376;skinlayers3d_customskinloader_bridge;SkinLayers3D-CustomSkinLoader-Bridge;;\npuffish-biome-dither;21377;puffish_biome_dither;Pufferfish's Biome Dither;;\nvoracious;21378;voracious;Mods Against Humanity: Voracious;;\n;21379;deadly_weapons;Minecraft Dungeons Weaponry;;\ngentech-block-generation;21380;gentech;Gentech: Block Generation;;\nbyzantine-styles-pack-for-minecolonies;21381;byzantine;Byzantine Styles Pack for Minecolonies;;\nvillager-taxes;21382;village_tribute;村民税;Villager Taxes;\naquaculture-tinkers;21383;aquaculture-tinkers;Aquaculture Tinkers;;\ncups;21384;cups;Cups;;\nblue-archive-halo;21385;blue-archive-halo,blue_archive_halo;仿碧蓝档案光环;Blue Archive Halo;\nminebackup;21386;minebackup;存档时光机;MineBackup;MBu\necological-replenishment-station;21387;ers;生态补给站;Ecological Replenishment Station;ERS\n;21388;advancedhostility;进阶仇恨;Advanced Hostility;\naggro-fix;21389;aggrofix;Aggro Fix;;\nchest-curio-items;21390;chest_item;箱子里的小饰品;Chest Curio Items;\ngalaxysimagination;21391;galaxysimagination;Galaxy的奇思妙想;Galaxy's Imagination;\ndelightful-cuisine-for-woodheads-reforged;21392;;懒人厨房·重制;Delightful Cuisine for Woodheads Reforged;\n;21393;loans_cinema;Loan's Cinema;;\nbanana-baking;21394;bananabaking;Banana Baking;;\nduacan-delight;21395;duacandelight;岩城乐事;Duacan Delight;\nslashblade-refabricated;21396;slashblade;拔刀剑：重织;SlashBlade: Refabricated;\nender-dragon-fix;21397;dragon_tweak;Ender Dragon Tweak;;\n;21398;;便携信标核心;Portable Beacon Core;\ndetailab-compat;21399;detailab_compat;细节盔甲兼容;Detail Armor Bar Compat;\ncalypsos-mobs;21400;calypsos_mobs;卡里普索的生物;Calypso's Mobs;\ndirtcraft-unoffical;21401;dirtcraft;泥土工艺 - 非官方版;DirtCraft - Unofficial;\nspecialmaterials;21402;materials;材料;Materials;\n;21403;onlytpnext;仅传送Next;OnlyTPNext;OTPN\neasy-survival-command;21404;easysurvivalcommand;简单生存指令TP;EasySurvivalCommand;\n;21405;reverie_dreams;梦隐的幻想乡;Gensokyo Reverie of Lost Dream;RoLD\nreliquified-ars-nouveau;21406;reliquified_ars_nouveau;Reliquified Ars Nouveau;;\nrequesting-interface;21407;irequesting;Requesting Interface;;\napplied-experienced;21408;appex;Applied Experienced;;\nplain-cars-pack;21409;plaincars;Plain Cars Pack;;\ngt-craft-iv-mts;21410;gtcraft;GT CRAFT;;GTC\nad-astra-more-structures;21411;ad_astra_more_structures;Ad Astra: More Structures;;\nperfect-dodge;21412;perfect_dodge;完美闪避;Perfect Dodge;PD\nsimple-paraglider;21413;simple_paraglider;简易滑翔翼;Simple Paraglider;\nhuman-gunner;21414;humangunner;Human Gunner;;\ntacz-headshot-extension;21415;tacz_headshot_extension;TaCZ 爆头机制扩展;TaCZ Headshot Extension;\ndream-in-a-manhunt;21416;dreaminamanhunt;Dream in a Manhunt;;\nsecret-rooms-fabric;21417;secretblocks;Secret Blocks;;\n;21418;;Things;;\n;21419;sunrise;The Sunrise;;\nmore-critters;21420;more_critters;更多异兽;More Critters;\noelib;21421;oelib;One Enough Lib;;OEL\nitemblacklistfixed;21422;;物品黑名单修复版;ItemBlackListFIXED;\n;21423;;行尸走肉更合理的配方;Creafthing Dead More Recipe;CDMC\nvoxelmap-patch;21424;voxelmappatch;VoxelMap Patch;;\nxaero-train-map;21425;xaerotrainmap;Xaero Train Map;;\ntraversable-leaves-neoforge;21426;traversable_leaves;Traversable Leaves;;\n;21427;etched_extension;Etched-Extension;;\nvs-addition-continue;21428;vs_addition;VS Addition Continue;;VSAC\nclavis;21430;clavis;Clavis;;\nluckyblock-lib;21431;lbl;LuckyBlock Lib;;LBL\n;21432;litematicafix;投影修复;Litematica Fix;\nnopackcompatcheck;21433;nopackcompatcheck;禁用资源包兼容性检查;NoPackCompatCheck;\n;21434;;Hardcore Item Stages：修复版;;\n;21435;fid_support;烟火凡人心-收割拓展;;\ncrabbers-delight-expand;21436;crabbers_delight_expand;蟹农乐事：渔冶锭整;Crabber's Delight Expand;CDE\n;21437;dunxia-shifei;下蹲堆肥 Mod 版;Real Compost Mod;\nofflinegrowth;21438;offlinegrowth;OfflineGrowth;;\n;21439;privateseed;保密种子;Private Seed;\nfauna-and-orchestra;21440;faunaandorchestra;Fauna & Orchestra;;\n;21441;hardcorerevive;极限模式复活;Hardcore Revive;HR\n;21442;levelenhanced;等级增强;LevelEnhanced;LE\ncontact-love-rekindled;21443;contact;往来：旧情复燃;Contact: LoveRekindled;\nautosow-by-neni-o;21444;autosow;AutoSow by Neni-o;;\npiglin-join-wto;21445;pjwto;猪灵加入了WTO;Piglin Join WTO;PJWTO\nkeyboard-detector;21446;keyboarddetector;键位检测器;Keyboard Detector;KDM\npotion-pill;21447;potion_pill;药水药丸;Potion Pill;PP\ninhrtburys-coin;21448;inhrtburyscoin;Inhrtbury的金币;Inhrtbury's Coin;\n;21449;firesight;Firesight;;\nepic-villages;21450;epic_villages;Epic Structures: Villages;;\n;21451;bfminecraft;BrainF**k In Minecraft;;\n;21452;aviator_dream;Aviator Dreams (Reloaded);;\nrussian-dream;21453;russian_dream;Russian Dream (Immersive Vehicles);;\n;21454;mr_way_ofthecrafter;Way of the Crafter;;\ncbc-ballistics;21455;cbc_ballistics;CBC Ballistics;;\ncreate-programmable-pals;21456;create_programmablepals;Create: Programmable Pals;;\ncreate-ore-excavation-plus;21457;create_ore_excavation_plus;Create Ore Excavation Plus;;\n;21458;createtreadmill;机械动力：跑步机;Create:Treadmill;CT\ncreate-more-package-couriers;21459;cmpackagecouriers;Create More: Package Couriers;;\nregius;21460;regius;Regius;;\n;21461;chickenbeautiful;鸡你太美;ChickenBeautiful;\n279479-owls;21462;owls;Owls;;\n;21463;translatemod;自动翻译;Auto Translate;\ndont-make-me-see-red;21464;dontmakemeseered;别让我红温;Dont Make Me See Red;\n;21465;time_manager;更好的时间回溯;Time Manager;\npeyros-scythe;21466;peyroscythe;佩伊洛的镰刀;Peyro's Scythe;\n;21467;exaid;假面骑士：艾克赛德;EX-AID;EA\n;21468;readstar;占星术;ReadStar;\nillage-and-legend;21469;illage_ut;Illage and Legend;;\ngiant-dead-oak-tree;21470;giant_dead_oak_tree;Giant Dead Oak Tree;;\n;21471;mr_lankasters_nether;Lankasters Nether;;\nascension-nether;21472;acd_nether;Ascension Nether;;\nascension-end;21473;ascension_end;Ascension End;;\n;21474;echovoids;Echovoids;;\nsoulful-nether;21475;soulfulnether;Soulful Nether;;\nzefs-nether;21476;bones_n_hogs;Zef's Nether;;\nthe-end-update-concept;21477;end_update;The End Update Concept;;\n;21478;ending_biomes;Ending Biomes;;\ncharred-horizons;21479;charred_horizons;Charred Horizons;;\n;21480;nue;Nether Update Expanded;;NUE\ni-see-my-armored-hand;21481;ismah;I See My Armored Hand;;\nnaturally-spawning-withers;21482;naturallyspawningwithers;自然生成凋灵;Naturally Spawning Withers;\nwither-infection;21483;wither_infection;Wither Infection;;\ncreeper-infection;21484;creeperinfection;Creeper Infection;;\nlong-books;21485;long_books;Long Books;;\nftb-chunks-recoloured;21486;ftbchunks_modded;FTB Chunks  Modded Support;;\njei-hover-search;21487;jei_hover_search;JEI Hover Search;;\nrollable;21488;rollable;Rollable;;\ninvisibility-aggro-fix;21489;invisibility_aggro_fix;Invisibility Aggro Fix;;\nquick-skin;21490;quickskin;Quick Skin;;\ncountryball;21491;countryballmod;Countryball;;\ntinkers-construct-emergence;21492;tconstruct_emergence;Tinkers Construct: Emergence;;\npointblank-jelly;21493;;PointBlank: Jelly;;PB:J\nterrabnormals;21494;terrabnormals;Terrabnormals;;\nextra-mining-world;21495;miningworld;Extra Mining World;;\nherobrine011840s-nether-biomes;21496;herobrine011840_nether_biomes;Herobrine011840's Nether Biomes;;\nmy-end;21497;my_end;My End;;\nupdate-ender-a-reimagine-of-the-end;21498;update_ender;Update Ender - a Reimagine of the End;;\n;21499;air-puffers;Air Puffers;;\ncompanions-mod;21500;companions;同伴！;Companions!;\n;21501;chatai;AI聊天;AI Chat;\napothic-attributes-extension;21502;apothic_attributes_extension;神化属性拓展;Apothic Attributes Extension;\ndatabank;21503;databank;Databank;;\nrandomtp;21504;randomtp;RandomTP;;\ninstantunify-reforged;21506;instantunify;InstantUnify Reforged;;\n;21507;blockfire;BlockFire;;\nmorphoverlay;21508;morphoverlay;MorphOverlay;;\ndisplay-prior-work-count;21509;priorworkdisplay;Prior Work Display;;\nextended-scripts;21510;extendedscripts;Extended Scripts;;\nnatures-aura-simplified;21511;;自然灵气：简化;Nature's Aura Simplified;\nbed-spawn-backport;21512;bedspawnmod;想设就设;Bed Spawn Backport;BSB\nvisible-effect;21513;zve;药水效果显示;Visible Effect;VE\n;21514;config-manager;设置管理器;Config Manager;\nsimple-datapacks;21515;simple_datapacks;Simple Datapacks;;\nshulkerrrt;21516;srrt;ShulkerRRT;;SRRT\ncustom-icon-sets;21517;custom_iconsets;Custom Icon Sets;;\ngregic-probe-ceu;21518;gregicprobe;Gregic Probe: CEu;;\n;21519;antilootgame;AntiLootGame;;\ngraphene;21520;graphene;石墨烯性能优化;Graphene;\n;21521;cacardwar;牌牌大作战;cacardwar;\ntalents-fabric;21522;talents;Talents;;\n;21523;combat_maid;Combat Maids;;\nvoxelfurni;21524;voxelfurni;VoxelFurni Furniture;;\nvoxels;21525;voxels;Voxels;;\nvoxel-pairing;21526;voxel_pairing;Voxel Pairing;;\nno-stuck-healing;21527;nostuckhealing;No Stuck Healing;;\npixelcompress;21528;pixelcompress;PixelCompress;;\nmatrinoxes-village-names;21529;villagenames;Matrinoxe's Village Names;;\nredlink-wireless-redstone-control;21530;redlink;RedLink: Wireless Redstone Control;;\n;21531;shizuku;雫拔刀剑;ShizukuBlade;\nxtones-reworked;21532;xtonesreworked;Xtones Reworked;;\n;21533;ticench;Tinkers' Enchanting;;\nnew-woods;21534;new_woods;New Woods;;\nennuis-bigger-inventories;21535;ennuis_bigger_inventories;Ennui's Bigger Inventories;;EBI\n;21536;soldisco;生活调味料：探索;Spice of Life: Discovery;\nmissing-cookery;21537;vanilla_cookery;Missing Cookery;;\n;21538;;Farmer's Tea Resteeped;;\nperuvians-delight;21539;peruviansdelight;Peruvian's Delight;;\nproductivefarming;21540;productivefarming;Productive Farming;;\nbrewin-compat-delight;21541;brewincompatdelight;鸡尾酒乐事;Cocktails Delight;\nkubejs-tweaks;21542;kubejstweaks;KubeJS Tweaks;;\nkrypton-fnp;21543;krypton_fnp;Krypton FNP;;\nneoauth;21544;neo_auth;NeoAuth;;\ncrystalix;21545;crystalix;Crystalix;;\nchisel-chipped-integration;21546;chisel_chipped_integration;Chisel Chipped Integration;;\nrefined-types;21547;refinedtypes;Refined Types;;\nmystical-automation;21548;mysticalautomation;Mystical Automation;;\nutility-vest;21549;utilityvest;Utility Vest;;\nlittle-big-redstone;21550;little_big_redstone;Little Big Redstone;;\n;21551;stone-is-stone;Stone Is Stone;;\nalmost-unified-ie;21552;almostunified_ie;AU: Immersive Engineering / Almost Unified: IE;;\n;21553;trident-are-weapon;Trident are Weapon;;\ncollect-seeds;21554;collect_seeds;Collect Seeds;;\ncreate-delicious-teas;21555;createteas;Create: Delicious Teas;;\n;21556;showtime;Showtime;;\n;21557;windup_music_box;Wind-up Music Box;;\nbabylonian-empire-origins;21558;be_origin;Babylonian Empire origins;;\nall-the-arcanist-gear;21559;allthearcanistgear;All The Arcanist Gear;;\nepitaphs;21560;epitaphs;Epitaphs;;\nbiologist;21561;biologist;生物学家;Biologist;\nwandering-trapper2;21562;wandering_trapper;流浪猎人2;Wandering Trapper2;\nskiing2;21563;skiing;滑雪;Skiing2;\nbackported-animal-variants;21564;backported_animal_variants;Backported Animal Variants;;\nexplore-ruins-aether;21565;;Explore Ruins: The Aether - Dungeons & Structures;;\nwarpdrive-extras;21566;warpdrive_extras;WarpDrive Extras;;\n;21567;hest_transport_reloaded;Chest Transport Reloaded;;\nlrs-blockollection;21568;lrblockollection;LR's Blockollection;;\n;21569;the_what_tools;The What Tools;;\ntacz-chaoscrafts-apocalyptic-arsernal;21570;apocalypticarsenal;[TaCZ] ChaosCraft's Apocalyptic Arsernal;;\ndetail-armor-bar-re-equipment;21571;detailab;细节盔甲条：重装;Detail Armor Bar Re-equepment;DABR\njustenoughanvilcraft;21572;jeac;JustEnoughAnvilCraft;;JEAC\nboat-fly;21573;boatfly;船飞行;Boat Fly;\nfinite-water-infinite-lava;21574;finitewater;Finite Water & Infinite Lava;;\ntransfer-labels;21575;transfer_labels;Transfer Labels;;\ngrowth-accelerator-tiers;21576;growthacceleratortiers;Growth Accelerator Tiers;;\nastages-curios;21577;astages_curios;AStages Curios;;\nastages-pufferfishs-skills;21578;astagespufferfishskills;AStages Pufferfish's Skills;;\nsound-physics-perfected;21579;sound_physics_perfected;Sound Physics Perfected;;\nthe-sound-of-rain;21580;soundofrain;The Sound of Rain;;\n;21581;under_the_blue;深蓝之下;Under the blue;\ngreekfantasy-unofficial;21582;;GreekFantasy-Unofficial;;\nmusketeer-illager-port;21583;musketeer_illager;Musketeer Illager Port;;\ncataclysm-apotheosis-addon-neo;21584;cataclysm_ut;Cataclysm Apotheosis Addon NEO;;\niron-apples-iron-series;21585;is_ironapples;Kal's Iron Apples [Iron Series];;\n;21586;mango_compat_delight;Mango's Compat Delight;;MCD\ntinkers-oreganized;21587;tinkers_oreganized;Tinkers Oreganized;;\nsimple-basketballs;21588;basketball;SimpIe Basketball;;\ndecorative-blocks-repurposed;21589;decorative_blocks;Decorative Blocks Repurposed;;\ncreate-mechanical-botany;21590;mechanical_botany;Create: Mechanical Botany;;\n;21591;Insecticide;Insecticide;;\n;21592;config_opener;万能配置钥匙;ConfigOpener;\nnaked-gauntlet;21593;naked_gauntlet;焰眼掉装;Naked Gauntlet;\nonly-one-item;21594;oneitem;Only One Item;;\n;21595;pozo;pozo's RPG;;\naimbow-bow-hit-indicator;21596;aimbow-mod;弓箭瞄准;AimBow;\n;21597;metachanger;MetaChanger;;\n;21598;tt20forged;TT20Forged;;\n;21599;worldfarview;WorldFarView;;\n;21600;fix-mc-stats;FixMCStats;;\nsoul-return-scroll;21601;soulreturn;还魂符;Soul Return Scroll;\nslashblade-murasame;21602;murasame;拔刀剑：丛雨丸;Slashblade Murasame;MSB\n;21603;kardsui;KARDS标题界面;KARDS UI;KUI\ndungeon-tools-2;21604;DungeonTools;地牢工具 2;Dungeon Tools 2;\ngregtech-beyond-coremod;21605;gtb;GregTech Beyond Coremod;;GTB\ncreate-compatible-storage;21606;create_compatible_storage;Create: Compatible Storage;;\nantique-rockets;21607;antique-rockets;Antique Rockets;;\nmeta-changer;21608;metachanger;Meta Changer;;\nchromatic-enchantments;21609;chromaticenchantments;Chromatic Enchantments;;\n;21610;morecritterszhcntranslation;More Critters简体中文翻译包;More Critters Zh_cn Translation;\n;21611;optirefine;OptiRefine;;\ntfc-detail-armor-bar;21612;tfc_detailab;群峦传说：细节盔甲条;TFC Detail Armor Bar;TFCDAB\n;21613;;Tinkers' Melting: The Aether;;\ntinkers-compatibility;21614;tcompat;Tinker's Compatibility;;\nfabric-trade;21615;trade;Fabric Trade;;\ngiant-nether-tree;21616;giant_nether_tree;Giant Nether Tree;;\ngamerule-doblockupdates;21617;suppressblockupdates;Gamerule doBlockUpdates;;\nterralith-restoned;21618;terralith_restoned;Terralith ReStoned;;\nguitas-woodworks;21619;gwoodworks;guita's Woodworks;;\npower-grid;21620;powergrid;机械动力：交错电网;Create: Power Grid;\n;21621;forge_config;Forge Config BTW;;\naudaki-cart-engine;21622;audaki_cart_engine;Audaki Cart Engine;;\njava-tnt;21623;;Java TNT;;\n;21624;force-beacon-load;信标强制加载;Force Beacon Load;\nhumansmod;21625;humans;Humans Mod;;\naquaacrobaticslegacy;21626;aquaacrobatics;Aqua Acrobatics Legacy;;\n;21627;;ModMenu BTW;;\narmored-arms;21628;armoredarms;Armored Arms;;\narmored-golems;21629;armored_golems;Armored Golems;;\ndisable-fog;21630;disablefog;Disable Fog;;\naccrelatedrendering-refabricated;21631;acceleratedrendering;加速渲染：重织;AcceleratedRendering-reFabricated;ARR\nminemenureforged;21632;;我的菜单 重制;MineMenuReforged;\nfunction-command-deduplicator;21633;func_cmd_dedup;函数命令去重器;Function Command Deduplicator;\n;21634;hide-armour,hidearmour;Hide Armour;;\nftb-quests-freeze-fix;21635;ftbquestsfreezefix;FTB任务冻结修复;FTB Quests Freeze Fix;\nplayeranimatorapi;21636;playeranimatorapi;Zigy's Player Animator API;;\nstrictly-origins;21637;strictly_origins;Strictly Origins;;\nborn-in-configuration;21638;borninconfiguration;Born In Configuration;;\neyes-of-ice-and-fire;21639;eyesoficeandfire;Eyes of Ice and Fire;;\nbetter-hp;21640;better_hp,betterhp_fabric;BetterHP;;\njsonate;21641;jsonate;Jsonate;;\nplsgodie;21642;plsgodie;残血了就给我乖乖去死啊！;PlsGoDie;\ntetra-patchouli-book;21643;tetra_patchouli_book;Tetra帕秋莉手册;Tetra Patchouli Book;TPB\n;21644;combp;指令向后移植;Command Backport;CB\nconfigurable-death;21645;configurabledeath;Configurable Death;;\ninvenaudio;21646;invenaudio;InvenAudio;;\nclient-curse-of-binding-prevention;21647;nobinding;Client Curse of Binding prevention;;\nsimpleclockmod;21648;;SimpleClockMod;;\n;21649;better-elytra-render;更好的鞘翅渲染;Better Elytra Render;\naudio-improvements;21650;audioimprovements;Audio Improvements;;\n;21651;supernausea;更好的反胃;SuperNausea;\n;21652;ice;冰简化-指令扩展简化;;ICE\n;21653;;Norin的魔法合成;;\nsimple-crop-harvester;21654;sch;简易农作物收割;Simple Crop Harvester;SCH\ndarkness-curse;21655;darkness_curse;暗影诅咒·重生;Darkness Curse • Reborn;DCR\nscroll-wheel-command;21656;scroll_whell_command;滚轮命令;Scroll Wheel Command;SWC\ninfoplus;21657;infoplus;InfoPlus;;\n;21658;bin-slot;Bin Slot;;\ncobblemon-utils;21659;cobblemonutils;Cobblemon Utils;;\nflamingo-mania;21660;flamingoisland;Flamingo Mania;;\n;21661;sol2f,food_advancement;生活调味料：胡萝贝版;Spice of Life: Fabric Flavor;\nendangered-plants;21662;endangeredplants;濒危植物;Endangered Plants;\n;21663;desert_reclamation;沙漠治理;Desert Reclamation;DR\nbetter-melon;21664;qa_better_melon;更好的西瓜;Better Melon;BM\nbetter-snifferty;21665;qa_better_sniffer;更好的嗅探;Better Snifferty;\n;21666;arkcraft;鲍勃之源石梦想;BOB's Originium Dream;\nminibloom;21668;minibloom;Minibloom;;\nteatime;21669;teatime;Teatime;;\ngarden-trails;21670;gardentrails;Garden Trails;;\n;21671;ft;假货;FakeThings;FT\ngd656killicon;21672;gd656killicon;GD656击杀图标;GD656Killicon;KI\nitemcleaner;21673;itemcleaner;掉落物清洁者;ItemCleaner;IC\n;21674;tinkers_dolabrae;Tinkers' Dolabrae;;\nmetallic-tinkering;21675;tinkers_compat_metals;Metallic Tinkering;;\nkubejs-draconic-evolution;21676;kubejsde;KubeJS Draconic Evolution;;\nkubejs-mystical-agriculture;21677;kubejs_mystical_agriculture;KubeJS Mystical Agriculture;;\nmodern-beta-forge-islands;21678;modernbetaislands;Modern Beta Forge Islands;;\nmodern-beta-forge-release-plus;21679;modernbetareleaseplus;Modern Beta Forge Release Plus;;\npomkots-world;21680;pomkotsworld;Pomkots World;;\nblack-dragon-armor;21681;monster_hunter_world;怪物猎人·世界：黑龙盔甲;Black Dragon Armor;\nkimetsu-no-yaiba-reload;21682;knyr;鬼灭之刃 重制;Kimetsu No Yaiba Reload;KNYR\nresonantisshelomoh;21683;resonantisshelomoh;同调造物·所罗门;Fabrica Resonantis: Shelomoh;FRS\nwithering-away-reborn;21684;withering_away_reborn;Withering Away: Reborn;;\nvillager-extend-localbully;21685;villagerextend;村民拓展：地头蛇;Villager Extend - LocalBully;\ndancing-villagers;21686;villagerdance;Dancing Villagers;;\nnatures-of-witchery;21687;natureswi;Natures of Witchery;;\ntct-flashlight;21688;flashlight;TCT Flashlight;;\nmace-attack-assistance;21689;maceattackassistance;Mace Attack Assistance;;MAA\nchanged-doomsday;21690;changed_doomsday;Changed: Doomsday;;\nchanged-ice;21691;changed_ice;Changed Ice;;\ntile-entity-breaker;21692;tebreaker;Tile Entity Breaker;;TEBreaker\nfrozen-ocean;21693;frozenocean;Frozen Ocean;;\nportable-pet-sphere;21694;petcapturetool;Portable Pet Sphere;;\n;21695;thaumicthermae;ThaumicThermae;;\nbbl-routers;21696;routers;BBL Routers;;\nbed-sapwnpoint;21697;spawnpointmod;Bed Spawn Point;;\nwans-storage-shelves;21698;storage_shelves;Wan's Storage Shelves;;\nwinefoxs-spellbooks;21699;winefoxs_spellbooks;酒狐的魔法书;Winefox's Spellbooks;\nbetter-allay;21700;betterallay;Better Allay;;\ncodechicken-lib-cre;21701;codechickenlib;CodeChicken Lib CRE;;\nsolcarrot-fix;21702;solcarrot_fix;生活调味料：胡萝卜版 - 死吃修复;Solcarrot Fix;\n;21703;farmermisery;蟑螂乐事/农夫悲事;Roach's Delight/FarmerMisery;\nnanexs-cherry-biome;21704;nanexscherrybiome;Nanex's Cherry Biome;;\ndigital-botania;21705;digital_botania;Digital Botania;;\n;21706;englishsearch;英文搜索;EnglishSearch;\ncrab-city-facilities;21707;crab_city_facilities;螃蟹的城建;Crab city facilities;CCF\nbbl-casting;21708;casting;BBL Casting;;\nbbl-smart-crafting;21709;smartcrafting;BBL Smart Crafting;;\nfoolproof;21710;foolproof;Foolproof;;\nrealistic-terrain-generation-community;21711;rtgc;真实地形生成：社区版;Realistic Terrain Generation Community;RTGC\nmore-outlines;21712;more-outlines;More Outlines;;\nphantomtweaks;21713;removephantoms;移除幻翼;RemovePhantoms;\nsimple-item-blacklist;21714;item_blacklist;简易物品黑名单;Simple Item Blacklist;\ntitlescreen-cleaner;21715;titlescreencleaner;TitleScreen Cleaner;;\n;21716;defendking;保卫国王;Defend The King;DTK\nultimate-skeletons-and-titans;21717;ultimateskeletons;终极骷髅;Ultimate Skeletons And Titans;UST\nmanaitaplus-legacy;21718;manaita_plus;砧板传承;ManaitaPlus Legacy;MPL\n;21719;terrablade;泰拉刃;TerraBlade;\nblockoffensive;21720;blockoffensive;方块攻势;BlockOffensive;BO\n;21721;brpv;更好的资源包版本管理;Better Resource Pack Versioning;BRPV\nhugme-forge-edition;21722;hugme;抱抱我！Forge 版;Hugme Forge Edition;\n;21723;moreharvestinglevel;更多挖掘等级;MoreHarvestingLevel;MHL\n;21724;saosaodi;掉落物清理;SaoSaoDi;SSD\n;21725;autocompassmenu;自动指南针菜单;Auto Compass Menu;ACM\ntool-upadate;21726;tool_update;工具附魔等级升级;Tool Update;\n;21727;bettergtae;更好的格雷科技与应用能源;Better GregTech And Applied Energistics;BetterGTAE\n;21728;aerosol;气雾剂;Aerosol;\ntprt-sakuratinker-expand;21729;tprt;挚爱——魂樱工匠补充;myLove（sakuratnker expand）;tprt\nbetter-turtle-scute;21730;qa_turtle_back_shell;更好的海龟壳;Better Turtle Scute;\n;21731;infinitewater;无限水源方块;Infinite Water Block;IWB\nfloatable-pressure-plates;21732;fpp;可浮空压力板;Floatable Pressure Plates;FPP\n;21733;redlantern;红石灯笼;Redstone Lantern;\n;21734;;现代电梯与扶梯重制版;Modern Elevators And Escalators Reforged;MEAER\nmore-rust;21735;rust;锈;Rust;\ncryofloric-arts;21736;cryofloric_arts;严寒插花术;Cryofloric Arts;CA\ntacz-refabricated;21737;;TaCZ：重织;Timeless and Classics Zero: Refabricated;TaCZR\n;21738;statscrystal;属性水晶;Stats Crystal;\nashen-witch-broom;21739;ashenwitchbroom;魔女的扫帚：重制版;Ashen Witch Broom;\nmaidswordsoaring;21740;maid_sword_soaring;女仆御剑;Maid Sword Soaring;\nlembrary;21741;lembrary;Lembrary;;\nskyboxify;21742;skyboxify;Skyboxify;;\ncreate-furnace-lava-adapter;21743;create_furnace_lava_adapter;Create Furnace Lava Adapter;;\ncreate-trainutilities;21744;trainutilities;Create Train Utilities;;\n;21745;createcustomportalapiintegration;Create Trains + CustomPortalAPI (1.20.1);;\ncreate-xptool;21746;xptool;机械动力：经验工具;Create: XPTool;CXPT\ngraveyard-death-mode;21747;gravel_ut;Graveyard Death Mode;;\nbeautiful-campfires;21748;beautifulcampfires;Beautiful Campfires;;\nkimetsu-no-yaiba-final-hope;21749;fh;Demon Slayer: Final Hope;;FH\nlegend-ary;21750;legendary;Legend-ary / Legendary;;\ntfc-hourglass;21751;tfc_hourglass;床起速时 / 梦该醒了;TFC Hourglass;TFCH\n;21752;fucklocatorbar;定位栏去死;;\n;21753;moretoys;一些玩具;More Toys;MT\n;21754;make_ammo;TaCZ子弹制作;Make Ammo;\n;21755;tf2_gun;TaCZ：军团要塞附属包;Team Fortress2's Gun;TFG\n;21756;myfirstmod;钻石建筑;Diamond Buildings;DB\nalans-unified-ui;21757;auui;Alan's Unified UI;;\ncntiertagger;21758;cntiertagger;CntierTagger;;\npick-up-the-hint;21759;pick_up_the_hint;物品拾取提示;Pick Up the Hint;Puth\njei-tetra;21760;jeitetra;JEI Tetra;;\neyes-in-the-shadows;21761;eyesintheshadows;Eyes in the Shadows;;\nbandagez;21762;bandagez;Bandagez;;\nkineticdamage;21763;kineticdamage;KineticDamage;;\nunderwater-village;21764;underwater_village;Underwater Village - Oceanic Structures;;\ndimension-parasite;21765;dimension_parasite;维度寄生者;Dimension Parasite;\n;21766;rstamina;Realistic Stamina;;\nciscos-content;21767;cisco_mod;Cisco's Content;;\npet-home;21768;pet_home;Pet Home;;\nchanged-xtras;21769;changed_xtras;Changed: Xtras;;\nalexs-caves-enriched;21770;alexscavesenriched;Alex 的洞穴：浓缩;Alex's Caves: Enriched;\n;21771;frametothetrash;FPS to the Trash;;\n;21772;entity-desync-viewer;Entity Desync Viewer;;EDV\nre-entity-outliner;21773;reentityoutliner;Re:Entity Outliner;;\n;21774;c2me;Concurrent Chunk Management Engine for Forge;;C2MEF\nhellspawn-starting-dimension;21775;hellspawn;Hellspawn | Starting Dimension;;\njj-weapon-config;21776;jjweaponconf;JJ Weapon Config;;\nbbl-compat;21777;bblcompat;BBL Compat;;\ntinkertime-geology;21778;tinkertime_geology;TinkerTime: Geology;;\nspectral-arrow-tweaks;21779;spectralarrowtweaks;Spectral Arrow Tweaks;;\ntoo-many-bows;21780;too_many_bows;Too Many Bows;;\n;21781;mcgougoucraft;狗勾工艺;McgougouCraft;MGGC\n;21782;soulbinding;灵魂绑定;Soulbind;\n;21783;mr_quickly_mineturtleeggs;快速挖掘海龟蛋;Quickly Mine Turtle Eggs;QMTE\n;21784;mr__villagersregeneration,mr_villagersregeneration;村民回复;Villagers Regeneration;VR\n;21785;doll;吴氏玩偶;Wudoll;Wdoll\nmaid-beside;21786;maid_beside;女仆伴行;Maid Beside;\nsnkey;21787;key;键;Sn Key;SK\nvzlinglib;21788;vzling;VzlingLib;;\n;21789;signfinder;告示牌查找器;Signfinder;\n;21790;mod_thx;THX Helicopter Mod;;\n;21791;explosives_pp;Explosives++;;\n;21792;iiz_tntmod_4_2;TNT Mod;;\n;21793;colorfulleaves;Colorful Leaves!;;\ndecorative-computers;21794;decorativecomputers,decorative_computers;装饰性电脑;DECORATIVE COMPUTERS;\nlandk-furniture;21795;landk_furniture;LandK Furniture;;\nderfl007s-road-mod;21796;df-roads;derfl007's Road Mod: Repaved;;\nmedieval-paintings;21797;medieval_paintings;Medieval Paintings;;\nglymworld-deepslate;21798;gwdeepslate;GW Mods | Deepslate;;\narchitectual-blocks;21799;;Architectual Blocks;;\nroadarchitect;21800;roadarchitect;RoadArchitect;;\n;21801;dirtcraftremade;泥土工艺：重铸;DirtCraft: Remade;DCR\n;21802;broadcast_killing;击杀播报;Broadcast Killing;BK\nlazybank;21803;lazybank_1211,lazybank_1201,lazybank_1192;咸鱼物品银行;Lazybank;LZB\n;21804;mr__damageoptimization;伤害优化;Damage Optimization;DO\n;21805;mr__mobsautonomousride,mr_mobsautonomousride;生物自动骑乘;Mobs Autonomous Ride;MAR\n;21806;mr__vanillaenchantmenttweaker,mr_vanillaenchantmenttweaker;香草附魔调整;Vanilla Enchantment Tweaker;VET\n;21807;mr__arrowding,mr_arrowding;箭矢叮;Arrow Ding;AD\n;21808;projectileding;ProjectileDing;;\nclient-time;21809;clienttime;Client Time;;\nreplanter-plus;21810;replanter-plus;Replanter Plus;;\nae2-retex-port;21811;ae2retexport;AE2 Retex Port;;\ntinker-tantrum;21812;tinkertantrum;Tinker Tantrum;;\namethyst-additions;21813;amethyst_tools,amethyst_additions;Amethyst Additions!;;\n;21814;tetravsikt;tetravsikt;;\n;21815;advancedupgradetemplatesmod;Advanced Netherite Upgrade Tem;;\n;21816;fallingsand;Falling Sand;;\ngregified-energistics;21817;gregifiedenergistics;Gregified Energistics;;\nscalable-storage-ceu;21818;scalablestorageceu;Scalable Storage CEu;;\nluminous-blocks;21819;luminousblocks;Luminous Blocks;;\nepic-structures-witch-huts;21820;epicwitchhuts;Epic Structures: Witch Huts;;\n;21821;lunkun;伦堃;LunKun;LK\nvoid-block-fabric;21822;voidblock;Void Block;;\nmore-stews-mod;21823;MoreStewsMod;More Stews Mod;;\n;21824;ported;Ported;;\nbetter-music-player;21825;musicplayer;Better Music Player;;\nberries-and-mobs;21826;berries_and_mobs_mod;Berries and Mobs;;\napplied-soul;21827;appliedsoul;Applied Soul;;\npackagedfluidcrafting;21828;packagedfluidcrafting;封包流体合成;Packaged Fluid Crafting;\npackagingprovider;21829;packagingprovider;封包供应器;Packaging Provider;\nmts-iv-recurring-air-war;21830;rainairwar;Recurring Air War;;RAW\n;21831;mr_sit_command;坐下指令;Sit Command;SC\n;21832;xycommands;简单命令;XYCommands / XYTool / SimpleCommand;\n;21833;mr_mini_explosives;Mini Explosives;;\n;21834;explodee;Explodee;;\nswift-wheel;21835;swiftwheel;迅捷轮盘;Swift Wheel;SW\n;21836;clienttimeandweathercontrolmod;客户端时间与天气控制;ClientTimeAndWeatherControlMod;CTWC\nbetter-spectral-arrows;21837;better_spectral_arrows;Better Spectral Arrows;;\npolymorphic-tom;21838;polymorphic_tom;Polymorphic Tom's Simple Storag;;\nshoulder-surfing-ultimine;21839;shouldersurfingultimine;Shoulder Surfing Ultimine;;\nartisan-pottery;21840;pottery;Artisan Pottery;;\nepic-fight-avalon;21841;epic_fight_avalon;Epic Fight - Avalon;;\n;21842;neospeedzero;自定义速通挑战：Zero;NeoSpeed Zero;NS0\nfluffy-dandelions;21843;fluffy-dandelions;Fluffy Dandelions;;\nnether-update-concept-mod;21844;nether_update_concept;Nether Update Concept Mod;;\n;21845;touhou_little_mad;车万女仆：情绪萌动;Little Maid: Mood Flutter;\nmobvotes-sniffer;21846;sniffer;Mobvotes Sniffer;;\nae-wireless-transceiver;21847;aewireless;AE无线收发器;AE Wireless Transceiver;AWT\ncreate-train-lights;21848;ctl;Create Train Lights;;CTL\nkabooms;21849;kaboom;Kabooms;;\nzoo-architect;21850;za;Zoo Architect;;\n;21851;xyclear;简单清理;Xyclear;\n;21852;fixchatverify;无验证聊天;NoVerifyChat / FixChatVerify;\ngiant-cherry-blossom-bonsai-tree;21853;giant_cherry_blossom_bonsai_tree;Giant Cherry Blossom Bonsai Tree;;\nlemonskin;21854;lemonskin;柠檬皮;LemonSkin;\nwater-erosion;21855;watererosion;Water Erosion;;\n;21856;undertale_healthbars;Undertale HealthBars;;\n;21857;ingameinfoxml;InGameInfoXML;;\ntetra-gui-compat;21858;tetra_gui_compat;Tetra Holo GUI Compat;;\n;21859;shortratio;ShortRatio Mod;;\nquantumchunks;21860;mentalchunks;QuantumChunks;;\n;21861;igiextended_reborn;IGI 信息扩展：重生;IGIExteneded: Reborn;IGIE\n;21862;citrus;Citrus;;\n;21863;accessoryapi;Accessory API;;\n;21864;catalyst-all,catalyst-core;Catalyst;;\n;21865;daycounterenhanced;Day Counter Enhanced;;\nmessenger-crow;21866;messenger_crow;Messenger Crow;;\nepic-fight-super-warden;21867;super_warden;史诗战斗：超级监守者;Epic Fight - Super Warden;\narmageddon;21868;armageddon_mod;Armageddon;;\ncobblemon-mega-showdown;21869;mega_showdown;Cobblemon: Mega Showdown;;\ngolem-dungeons;21870;golemdungeons;傀儡地牢;Golem Dungeons;GD\n;21871;crazybombs;Crazy Bombs;;\nexplosion-overhaul-a-new-level-of-destruction;21872;explosionoverhaul;Explosion Overhaul: A new level of destruction;;\n;21873;skillz;SkillZ;;\nhadean-breathe;21874;hadean_breathe;幽焰之息;Hadean Breathe;\ncan-you-survive;21875;sf_scp;Can You Survive?;;\neternal-shield-bead;21876;eternal_shield_bead;永御盾珠;Eternal Shield Bead;ESB\nslashnouveau;21877;slashnouveau;Slash Nouveau;;\n;21878;;Aether on Station API;;\n;21879;not-enough-minecraft;NotEnoughMinecraft;;\noriginiumarts;21880;ognmarts;源石技艺;OriginiumArts;OGNA\nsimplebackhome;21881;simplebackhome;简单回家;SimpleBackHome;SBH\n;21882;qismorecommands;齐的更多命令;Qi's More Commands;\npolymorphic-travelers-backpack;21883;polymorphic_travelersbackpack;Polymorphic Traveler's Backpack;;\nplayernbt-quests-ftb-quests;21884;playernbtftb;PlayerNBT Quests (FTB Quests);;\ninteractive-block-prompt;21885;ibp;可交互方块提示;Interactive Block Prompt;IBP\n;21886;timeshift;Timeshift;;\n;21887;weathershift;Weathershift;;\nchat-binds;21888;chat-binds;Chat Binds;;\ntridentperf-1-0-0;21889;tridentperf;TridentPerf;;\nmaid-s-bakeries;21890;maid_bakeries;女仆的烘焙坊;Maid's Bakeries;\n;21891;bullet_recovery;子弹回收;Bullet Recovery;\nelytraautocollect;21892;elytraautocollect;鞘翅自动搜集;ElytraAutoCollect;\n;21893;banitems;物品封禁;Banitems;\n;21894;sdmshopextended;SDM Shop：拓展;SDM Shop Extended;SDMSE\ntt20-forged;21895;tt20forged;TT20-reforged;;\ndefault-server-properties;21896;dsp;默认服务器属性;Default Server Properties;DSP\nf3-lite;21897;f3lite;F3 Lite;;\ntext-animator;21898;textanimator;Text Animator;;\n;21899;cser;ChatSearcher;;\nyet-another-furniture;21900;yaf;Yet Another Furniture;;\n;21901;pixelmon_master_ball_addon;宝可梦大师球追加;pixelmon master ball addon;PMBA\nqarsans-insurgency;21902;insurgency;Qarsan's Insurgency;;\nyum-delicious;21903;yum;Yum！美味！;Yum! Delicious!;yum\nlunarinispread;21904;lunarinispread;丰盛晚宴;Lunarini Spread;\n;21905;potmod;阁下的大锅;The Cauldron Of Excellence;\nrs-backpack;21906;backpack_mod;R's Backpack;;\nno-template-netherite;21907;nonetheritetemplateneo;No Template Netherite;;\n;21908;unparalleled;无双;Unparalleled;\n;21909;mobcapture;MobCapture;;\nkoifish;21910;koifish;Koifish;;\n;21911;multimine_mod;多人开采;Multi Mine;\n;21912;mindshaft;Mindshaft;;\n;21913;mcinspects;MCInspects;;\n;21914;csgo-inspects;CS:GO Inspects;;\narmor-hud-by-mcjunky33;21915;armor_hud;Armor Hud by Mcjunky33;;\n;21916;can-i-sleep-now;Can-I-Sleep-Now;;\n;21917;damagevignette;Damage Vignette;;\nsimple-teleport-commands;21918;teleportg;Simple Teleport Commands;;\ntoo-many-binds;21919;toomanybinds;Too Many Binds;;\ntoo-many-binds-plus;21920;tmb;Too Many Binds Plus;;\nastral-dragon;21921;astral_dragon;Astral Dragon;;\nmiddle-earth-additions-npc;21922;me-npc;中洲拓展：NPC;Middle-Earth Additions: Npc;\nmiddle-earth-additions-factions;21923;me-factions;中洲拓展：派系;Middle-Earth Additions: Factions;\nmiddle-earth-additions-war;21924;me-war;中洲拓展：战争;Middle-Earth Additions: War;\nbrews-and-bevs;21925;bab;酿造与饮料;Brews and Bevs;\n;21926;demon_hashira;Kimetsu No Yaiba x Epic Fight Compaibity;;\n;21927;ani_koto;Kingdoms of the Overworld;;\nlrs-dynamic-dungeon;21928;lrdynamicdungeon;LR's Dynamic Dungeon;;\nbuff-mobs;21929;buffmobs;Buff Mobs;;\nbanner-claim;21930;bannerclaim;旗帜圈地;Banner Claim;\n;21931;ravensmod;不死图腾重制;ReformUndyingTotem;RUT\n;21932;estop;急停按钮;E-stop;\nmore-world-crafting;21933;more_world_crafting;更多世界合成;More World Crafting;\n;21934;simplelocks;简单的锁;SimpleLocks;SL\nluminous-helmet;21935;luminous_helmet;Luminous Helmet;;\nsteel-armor-blocks;21936;s_a_b;机械动力：钢铁装甲方块;Create: Steel Armor Blocks;SAB\ncreate-armor-blocks;21937;createarmorblocks;机械动力：装甲方块;Create: Armor Blocks;\nrolled-homogenous;21938;rha;轧压均质装甲;Rolled Homogenous;RHA\nelemental-end-portal;21939;elementalend;Elemental End Portal;;\nunseens-better-stronghold;21940;bt;Unseens Better Stronghold;;\nsalutation;21941;salutation;Salutation;;\nminecraft-1-7-10-reloaded;21942;mc17101;One Point Seven Reloaded;;\nglobal-customnpc-data;21943;globalcustomnpcdata;Global CustomNPC+ Data;;\nmodern-beta-forge-rivers;21944;modernbetarivers;Modern Beta Forge Rivers;;\nzombie-improvements;21945;zombieimprovements;Zombie Improvements;;\nrewithered;21946;rewithered;Rewithered;;\nbetter-lost-candle;21947;lostcandletweaks;Better Lost Candle;;\nopenblocks-reopened;21948;openblocks;OpenBlocks Reopened;;\n;21949;rc_toys;RC Toys;;\nfancy-toasts;21950;fancytoasts;Fancy Toasts | Better Advancements;;\n;21951;player_trading_dmzlwkssb;玩家交易;Player Trading;\n;21952;blindcraft;BlindCraft;;\n;21953;trueuuid;正版离线共存;TrueUUID;\nowapote-bc-exlaser;21954;bc_ex_laser;BuildCraft EXLaser;;\ntruly-custom-horse-tack;21955;truly_custom_horse_tack;Truly Custom Horse Tack;;\ntacz-applied-ammo-box;21956;appliedammobox;[TaCZ] Applied Ammo Box;;\nazurum-miner;21957;azurum_miner;Azurum Miner;;\nthermal-recycling;21958;recycling;热力回收;Thermal Recycling;\n;21959;appmachine;AppMachine;;\nrevoted-wildfire;21960;revotedwildfire;Revoted: Wildfire;;\nbiome-makeover-altar-fix;21961;bmfixer;Biome Makeover Altar Fix;;\ntechgunsmixins;21962;techgunsmixins;TechgunsMixins;;\nopen-persistence;21963;openpersistence;Open Persistence;;\nhungerstrike-extended-support;21964;;HungerStrike: Extended support;;\n;21965;flora;Flora's Delight;;\nfintastic;21966;fintastic;鳍妙至极;Fintastic;\nauto-reload-crossbow;21967;autoreloadcrossbow;弩自动装填;Auto Reload Crossbow;ARC\naimassiste;21968;aimassist;辅助瞄准;AimAssiste;\nzombie-dinosaur;21969;prehistoricexploration;A Zombie Dinosaur;;\nmonster-expansion;21970;monster_expansion;Monster Expansion;;\nmonster-girl;21971;monstergirl;怪物女孩;Monster Girl;\n;21972;offlineauth;离线认证;OfflineAuth;OA\nclaxels-classic-paxels;21973;claxels;Claxels - Classic Paxels;;\nmeowmeldrawers;21974;storagedrawers;MeowmelDrawers;;\n;21975;perrys_botany;Perry's Botany;;\nwarforge-factions-remaintained;21976;warforge;Warforge Factions: Remaintained;;\nlunar-moon-reborn;21977;lunar_moon;Lunar Moon: Reborn;;\nminefactorial;21978;minefactorial;MineFactorial;;M!\ncreate-universal-ores;21979;create-universal-ores;Create: Universal Ores;;\ncreate-crystallized;21980;crystallized;Create: Crystallized;;\nchorus-expansion;21981;chorus_expansion;Chorus Expansion;;\n;21982;lootshare;LootShare;;\nclojure-lib;21983;clojurelib;Clojure Lib;;\n;21984;stripcustomdata;扔掉\"custom_data\"!;StripCustomData;\nwheres-my-brain;21985;wmb;Where's My Brain;;WMB\nchampions-alter;21986;;冠军-重塑;champions-alter;CA\nslashblade-hf-blade;21987;energyblade;拔刀剑：高频刀;SlashBlade: HF Blade;\nvirtual-witch-arrangement;21988;virtualwitcharrangement;虚拟魔女排布法;Virtual Witch Arrangement;VWA\n;21989;player-move-detector;Player Move Detector;;\nvalkyrien-space-war;21990;valkyrien_space_war;瓦尔基里太空战争;Valkyrien Space War;VSSW\n;21991;blazerod-model-assimp;BlazeRod Model Assimp;;\n;21992;bindfirework_hotbar;BindFirework Hotbar;;\n;21993;new_soul;New Soul;;\nbeautiful-potions;21994;beautiful_potions;Beautiful Potions;;\nyukamis-sophisticated-backpack-tab;21995;yukamibackpacktab;Yukami's Sophisticated Backpack Tab;;\ncrouch-to-block;21996;crouch_to_block;Crouch to Block;;\n;21997;epflimitremover;魔咒保护系数上限解除器;EPF Limit Remover;EPFLR\njuv;21998;juv_preserve;永雏;Juvenile Preserve;JUV\nhooklib;21999;hooklib;HookLib;;\noasis2;22000;decart-oasis;Oasis 2.0;;\nblue-ice;22001;blueice,bi;蓝冰;Blue Ice;BI\njson-model-legacy;22002;jsonmodel;Json Model Legacy;;\nlidar-laser-mod;22003;LIDAR;LIDAR Laser;;\nsleep-tighter;22004;extendeddaycycle;Sleep Tighter;;\nbutterflies-extension;22005;butterflies_expansion;Butterflies Expansion!;;\nback-in-slime-honey;22006;bis;Back in Slime & Honey;;\nalans-basalt-generator;22007;basaltgenmod;Alan's Basalt Generator;;\ndemis-better-stealth;22008;betterstealth;Demi's Better Stealth;;\nstony-shores-tectonic-geophilic-compat-fix;22009;;Stony Shores : Tectonic / Geophilic compat fix;;\n;22010;celeritas;Celeritas;;\n;22011;coords-hud;坐标 HUD;Coords HUD;\n;22012;ds4;不要饿死4：重生;Don't Starve 4: Reborn;DS4R\n;22013;eternisstarrysky;星河传承;Eternis Starry Sky;ESS\n;22014;safe-respawn;安全重生;Safe(ish) Respawn;\npalmon;22015;palmon;帕鲁梦;Palmon;\n;22016;spectators-generate-loot;旁观者生成战利品;Spectators Generate Loot;\ndeath-reduces-life;22017;deathpenalty;死亡减少生命值;Death Reduces Life Mod;DRL\nmaid-chunk-loader;22018;maid_chunk_loader;车万女仆：区块加载;Maid Chunk Loader;\n;22019;muhc;女仆摇曲柄;MaidUseHandCrank;MUHC\nhard-pity;22020;hard_pity;大保底：凋灵骷髅/下界合金/三叉戟/龙蛋;Hard Pity;\ngenetics-resequenced;22021;geneticsresequenced;Genetics: Resequenced;;\nmoo-fluids;22022;moofluids;Moo Fluids;;\ncreate-balanced-flight-forked;22023;balancedflight;Create: Balanced Flight Forked;;\nallthemodium-enderio-capacitor-additions;22024;capacitoradditions;Allthemodium - EnderIO Capacitor Additions;;\nironsarms;22025;ironsarms;IronsArms - Iron's Spells 'n Spellbooks & Timeless and Classics Zero;;\nburst;22026;burst;Burst;;\nchat-calculator;22027;chatcalculator;Chat Calculator;;\n;22028;AdvancedPowerManagement,ChargingBench;高级电力管理;Advanced Power Management;APM\nunbreakable-leads;22029;unbreakable_leads;Unbreakable Leads;;\nmortality-a-terramity-addon;22030;mortality;Mortality - A Terramity Addon;;\n;22031;denLib;denLib;;\n;22032;slashbladeapi;SlashBladeAPI;;\nchatjs;22033;chatjs;ChatJS;;\nlet-me-peek;22034;peekmod;Let Me Peek;;\npersistent-players;22035;persistent_players;Persistent Players;;\ntorchmaster-cobblemon-compat;22036;torchmastercobblemoncompat;Torchmaster Cobblemon Compat;;\nnemos-backpacks;22037;nemos_backpacks;Nemo's Backpacks;;\nhomy-tp;22038;homemod;Homy TP;;\n;22039;shellphone;Shellphone;;\nfaded-conquest-ii;22040;faded_conquest_2;Faded Conquest II;;FC2\n;22041;meter-ui;Meteor UI / Frame;;\n;22042;wurstui;Wurst Frame / UI;;\nchosenlib;22043;chosenlib;ChosenLib;;\n;22044;recipesapi;配方 API;Recipes API;RsAPI\nrender-blender;22045;renderblender;渲染搅拌机;Render Blender;RB\nve-fluidhandling;22046;vefluids;VE-FluidHandling;;\n;22047;clientexport;客户端数据导出;Client Export Helper;\n;22048;;Distant Horizons GTNH 版;Distant Horizons Standalone;\n;22049;disc_jockey;NeoDiscJockey;;\novergeared;22050;overgeared;Overgeared;;\nrain-after-long-drough;22051;shamoxiayu;久旱逢甘霖;Rain after long drough;Rald\n;22052;qubit;丘比特 (量子比特);Qubit;Qbit\n;22053;clarity;Clarity;;\nspawn-egg-preview;22054;eggpreview;刷怪蛋预览;Spawn Egg Preview;SEP\nvulkanmod-extra;22055;vulkanmod-extra;VulkanMod Extra;;\nvramo;22056;ramchunks;Vramo;;\n;22057;afar;Afar;;\n;22058;cmyk;CMYK;;\nmiddle-key-ping;22059;middle_key_ping;中键标点;MiddleKeyPing;MKP\nscape-and-run-randomizer;22060;srprandomizer;Scape and Run: Randomizer;;\namnesia-core;22061;amnesia;Amnesia Core;;\n;22062;sqlite_jdbc;Minecraft SQLite JDBC;;\nsqlite-jdbc;22063;;SQLite JDBC for Minecraft;;\nforbidden-arcanus-js;22064;forbidden_arcanus_js;Forbidden Arcanus JS;;\n;22065;registerhelper;配方编辑器;RecipeHelper;\n;22066;SHA1Redemption;SHA-1 Redemption;;\nf-k-create-world;22067;fcw;不要创建世界;F**k Create World;\nmaid-task-scrolling-helper;22068;maidtaskscroll;女仆任务滚动助手;Maid Task Scrolling Helper;\nrozutils;22069;rozutils;RozUtils;;\ninstantly-interact-internally;22070;instantlyinteractinternally;随点随用;Instantly Interact Internally;III\n;22071;;Json格式化实体模型·多加载器;Json Entity Models Muit;JEMM\ngadothaumy;22072;gadothaumy;Gadothaumy;;\n;22073;crossgamemod;跨游戏互通;CrossingMod;CrM\ndimensional-teleporter;22074;iwalinersmod;维度传送器;Dimensional Teleporter;\navaritia-integration;22075;avaritia_integration;无尽贪婪：集成;Avaritia:Integration;\nsuper-lead;22076;superleadrope;超级拴绳;SuperLead;SLP\nsolo-leveling-mod;22077;solo_leveling;Cromta's Solo Leveling;;\nplatanitos;22078;platanitos;Platanitos - Food & Adventure;;\n;22079;spiceoflifelatiao;生活调味料：辣条版;Spice Of Life: Latiao;\nspicy-species;22080;spicy_species;Spicy Species;;\nthaumic-isorropia-kedition;22081;isorropia;Thaumic Isorropia - Kedition;;\nthaumic-concilium-kedition;22082;thaumicconcilium;Thaumic Concilium Kedition;;\nznet;22083;;ZNet;;\n;22084;PluginsforForestry;Plugins For Forestry;;PFF\nrailcraft-diesel-locomotive-addon;22085;railcraft_diesel_locomotive;Railcraft Diesel Locomotive Addon;;\n;22086;DenPipes;DenPipes;;\n;22087;DenPipes-Emerald;DenPipes-Emerald;;\n;22088;DenPipes-Forestry;DenPipes-Forestry;;\n;22089;GateCopy;GateCopy;;\nalchemistry-redox;22090;;Alchemistry - Redox;;\n;22091;Cogs;机器齿轮;Cogs of the machine;\nextra-hostile-neural-networks;22092;extrahnn;Extra Hostile Neural Networks;;\nextended-crafting-expanded;22093;extendedcrafting;Extended Crafting: Expanded;;\npackagedexexcrafting;22094;packagedexexcrafting;PackagedExExCrafting;;PExxC\nscp-reborn;22095;scp;SCP：重生;SCP: Reborn;\nspiritual-flame-firearms;22096;binmusket;灵焰炽铳;Spiritual Flame Firearms;SFF\na-weird-bow;22097;modimen;奇奇怪怪的弓;A Weird Bow;AWB\n;22098;blazingbamboo;Blazing Bamboo;;\nbacchanalian-mobs;22099;bacchanalianmobs;Bacchanalian Mobs;;\ntoxony;22100;toxony;毒术;Toxony;\n;22101;;获取原版服务端;GetVanillaServer;\nncm-for-mc;22102;ncm;NCM播放器;Ncm for Mc;NFM\nlavamelon;22103;lava_melon;熔岩西瓜;Lavamelon;\n;22104;;香蕉皮;BananaSkin;\nworn-tear;22105;worntear;磨痕;Worn Tear;WT\ncool-rain-reforged;22106;;Cool Rain Reforged;;\ntwilight-forest-cave-fix;22107;twilightfixes;暮色森林洞穴修复;Twilight Forest Cave Fix;\n;22108;nethercraft;机械动力：下界工艺;Create: Nether Craft;\ncreate-fluid;22109;fluid;机械动力：流体;Create: Fluid;\ncreate-fly;22110;;机械动力飞越版;Create Fly;\nsbm-wooden-buckets;22111;woodenbucket;木桶;[SBM] Wooden Buckets;\nsea-of-chests;22112;sea_of_chests;Sea of Chests;;\nflammable-tweaker;22113;flamabletweaker;Flammable Tweaker;;\nchanged-armor-and-weapons;22114;changed_armors_and_weapons_addon;Changed armor and weapons;;\nabominationsinfection;22115;abominations_infection;Abominations Infection;;\nly-target-dummy;22116;mr_target_dummy;目标假人;Target Dummy;\nunique-accessories;22117;uniqueaccessories;Unique Accessories;;\n;22118;pvzmcw;Plants vs Zombie: Minecraft Warfare | PvZ;;\n;22119;mr_almost_hadyoumychild;Almost had you, My child.;;AHYMC\ngoners-armor-trims;22120;goners_armor_trims;Goner's Armor Trims;;\ntrade-gui;22121;;自定义商店界面;Trade GUI;\nmuffins-slime-golem;22122;muffins_slimegolem;Muffin's Slime Golem;;\ndontstarve-moss-edition;22123;dontstarve;不要饿死：Moss版;Don't Starve: Moss Edition;DSM\njerotes-delight;22124;jerotesdelight;Jerotes乐事;Jerotes Delight;\nstellar-striker;22125;stellar-striker;星神之怒;Stellar Striker;SS\nptp;22126;ptr;Projectile Trajectroy Preview;;PTP\nthe-mad-hatter;22127;hatter;The Mad Hatter;;TMH\nmagic-bees-kedition;22128;MagicBees;Magic Bees Kedition;;\ncomplicated-backpacks;22129;complicated_backpacks;Complicated Backpacks;;\nretro-mario-worlds;22130;retromarioworlds;Retro Mario Worlds;;\n;22131;;Guard Villagers (Fabric/Quilt) [1.21.1];;\n;22132;;Guard Villagers (Fabric) Fork;;\nvillager-pagoda-house;22133;villager_pagoda_house;Villager Pagoda House;;\n;22134;maid_function_extension;女仆！给我阎魔刀;Maid Function Extension;\nxfoxsword;22135;xfoxmod;狐狐圣剑;XfoxSword;\nsoul-pact;22136;soul_pact;灵魂契约;Soul Pact;SoP\n;22137;culllessleaveslegacy;Cull Less Leaves Legacy;;\nresolution-controll;22138;;分辨率控制++;ResolutionControl++;\n;22139;shulkerfix;潜影盒堆叠修复;Stackable Shulkers Fix;\nspectrum-rush;22140;spectrum_rush;色谱竞速;Spectrum Rush;SR\nlibertys-villagers-revived;22141;libertyvillagers;Liberty's Villagers Revived;;\nblue-skies-tweaks;22142;bstweaks;Blue Skies Tweaks;;\ndrpgspawnfix;22143;drpgspawnfix;DivineRPG Ocean Spawn Fix;;\neat-yourself;22144;eat_yourself;Eat Yourself!;;\nkaiju-calamity;22145;kaiju_calamity;Kaiju Calamity;;KC\n;22146;hattened;The Mad Hatter;;\nender-christmas-plus;22147;ender_christmas_plus;Ender Christmas Plus;;\njuniors-open-smash-bats;22148;opensmashbats;Junior's Open Smash Bats;;\nchoppers-delight;22149;choppersdelighr;Chopper's Delight;;\ndelighto-flight;22150;delighto_flight;云端之乐;Delight o' Flight;\nxbbs-roaring-knight;22151;xbbsroaringknightmod;XBB的呼啸骑士;XBB's Roaring Knight;XBBRK\nsuixingxiugai;22152;suixingxiugai;随性修改;Suixing Xiugai;\n;22153;guzhenrenxindeshuxie;蛊真人心得书写模组;GuZhenRenXinDeShuXie;XDSX\nbetter-melon-addons;22154;better_melon_addons;更好的西瓜附加包;Better Melon Addons;BMA\navaritia-tweak;22155;avaritia_tweak;无尽贪婪：魔改;Avaritia: Tweak;\ngregtech-easy-core;22156;gtecore;GregTech Easy Core;;\npolishedlib;22157;polishedlib;PolishedLib;;\nminecraftapi;22158;minecraftapi;Minecraft API (unofficial);;\neasy-gun-mod;22159;easygunmod;Easy Guns;;\nlightroot;22160;lightroot;Lightroot;;\nmiscmfrcircuits;22161;miscmfrcircuits;MiscMFRcircuits;;\ndartpatch;22162;DartPatch;DartPatch;;\npolar-bears-frozen-oceans-backport;22163;frozenocean;Polar Bears + Frozen Oceans Backport;;\nuptodatemod;22164;uptodate;UpToDateMod;;\nconcrete-and-marble-mod-2;22165;concreteandmarblemod2;Concrete And Marble Mod 2;;\nkaminari-motor-works;22166;gvp;Kaminari Motor Works;;KMW\nblock-highlightfx;22167;blockhighlightfx;Block Outline HightlightFX;;\nbetter-compass-hud;22168;better-compass;Better Compass [HUD];;\n;22169;mendmeter;MendMeter;;\nsmallstairs;22170;smallstairs;小楼梯;Small Stairs;\nmana-crystals;22171;manacrystals;Mana Crystals;;\nabstract-rpg;22172;arpg;Abstract RPG;;\nmore-pylons;22173;more_pylons;More Pylons;;\nthermal-parallel;22174;thermal_parallel;热力并行;Thermal Parallel;TP\nmcpatcher;22175;mcpatcher;Right Proper MCPatcher;;\nspindles-tfc;22176;tfc_spindles;Spindles TFC;;\ntfc-more-items;22177;tfc_items;TFC More Items;;\ndynamic-trees-tfc-seeds;22178;dttfc_seeds;Dynamic Trees TFC - Seeds;;\nfarmer-enchants;22179;farmerenchants;Farmer Enchants [Light QoL for Farmers] (Fabric);;\ntiny-tree;22180;tiny_tree;Tiny Tree;;\n;22181;myworld;宁静之地;myworld;\nxnet-zhcn-edition;22182;;XNet 汉化版;XNet-zhCN edition;\nmtr-yomi-utility-mod;22183;mtryum;Yomi的MTR实用拓展;MTR Yomi Utility Mod;YUM\ntacz-classic-battleroyale-gun;22184;cbrg;经典大逃杀枪械包;Classic BattleRoyale Gun;CBRG\nlaseas-wilderness-traders;22185;wildernesstraders;Lasea的荒野商人;Lasea's Wilderness Traders;\n;22186;blockpower;BlockPower;;\nmtr-moogs-temples-reimagined;22187;mtr;Moog's Temples Reimagined;;MTR\n;22188;fiskmobfusion;Mystical Creatures / Mob Fusion;;\nperennial-pastries;22189;perennial_pastries;Perennial Pastries;;\nnarrativecraft;22190;narrativecraft;NarrativeCraft;;\nmixinbooter2fermiumbooter;22191;;MixinBooter2FermiumBooter;;\nmoogs-structure-lib;22192;moogs_structures;Moog's Structure Lib;;MSL\nchickens-can-cast-fireball;22193;spellmob;SpellMob / Chickens Can Cast Fireball;;\nbad-sound;22194;bad_sound;Bad Sound;;\nheirlooms;22195;heirlooms;Heirlooms;;\nwarborn-capture-points;22196;wrbcapturepoint;WarBorn Capture Points;;\ntacz-x-gunslightsaddon-addon;22197;taczxgunlightsaddon;TaCZ x Guns Lights Addon [NEW];;\nhunger-tweaks;22198;hunger_tweaks;Hunger Tweaks;;\nit-takes-a-pillage-continuation;22199;takesapillage;It Takes a Pillage Continuation;;\nswansong;22200;swansong;SwanSong Shaders;;\nno-creativity-and-adventure-datapack;22201;ncaa;无创造和冒险;No creativity and adventure;NCAA\ncroparium;22202;cp_lib;矿石种植工艺;Croparium;CP\nindigestion-tablet;22203;indigestion_tablet;健胃消食片;Indigestion Tablet;\nme-beam-former;22204;me_beam_former;ME光束;ME Beam Former;MEBF\ncreate-fast-schematiccannon;22205;createfastschematiccannon;机械动力：更快的蓝图加农炮;Create: Fast Schematic Cannon;CFC\n;22206;more_eggs;更多的刷怪蛋;More Eggs;MoE\ntrail-of-light;22207;trailoflight;Trail of Light;;\nabominations-apocalypse;22208;truly_feral_abominations;Abominations: Apocalypse;;\n;22209;hc2nerf;Pam's HarvestCraft 2 Nerf;;\npommel-held-item-models;22210;pommel;Pommel;;\nechoes-of-battle;22211;echoes_of_battle;Echoes of Battle;;\nthaumic-wonders-unofficial;22212;thaumicwonders;神秘奇境非官方版;Thaumic Wonders Unofficial;\nmystical-expansion;22213;mysticalexpansion;Mystical Expansion;;\nrefraction-continuation;22214;refraction;折射：延续;Refraction Continuation;\nexpanded-arcanum;22215;ea;Expanded Arcanum;;EA\n;22216;tpatools;TPA工具集合;TPATools;TPAT\nhud-manager;22217;hudmanager;界面管理器;HUD Manager;\nsimple-smithing-overhaul;22218;simple_smithing_overhaul;Simple Smithing Overhaul;;\ntinyfish-death-penalty;22219;death_penalty;死亡惩罚;Death Penalty;\n;22220;dine;动态NBT编辑器;Dynamic In-Game NBT Editor;DINE\n;22221;wecuifix;WeCuiFix;;\ntime-control;22222;timecontrol;时间控制;Time Control;\nembers-text-api;22223;emberstextapi;Embers Text API;;\nstageenchantment;22224;stage_enchantment;阶段附魔;StageEnchantment;SE\n;22225;black_sun;Black Sun;;\nthe-many-faces-of-enderman;22226;the_many_faces_of_enderman;The Many Faces of Enderman;;\n;22227;;胸腔：蛊真人拓展;;\n;22228;eternal_sky_orbs;Eternal Sky Orbs;;\nantiqueatlas-recurrentcomplex-compatability;22229;aarcaddon;AntiqueAtlas - RecurrentComplex Compatability;;\nglobal-shaderpack-properties;22230;globalshaderpackproperties;全局光影包配置;Global Shaderpacks Properties;GSP\nstrawberrylib;22231;strawberrylib;StrawberryLib;;\n;22232;ibukigourd;Ibuki Gourd;;\nonekeyminer-chainable-visuals;22233;onekeyminer_chainable_visual;一键连锁：可视化;OneKeyMiner Chainable Visuals;OCV\nprecision-mining;22234;precisionmining;Precision Mining;;\nbetter-capes-x;22235;bettercapes;Cape | Better Capes;;\nbotanic-cheater;22236;botaniccheater;Botanic Cheater;;\nvillagerxp;22237;villagerxp;VillagerXP;;\nsilk-suspicious-sand;22238;silksuspicioussand;Silk Suspicious Sand;;\ntrial-liberator;22239;trialliberator;Trial Liberator;;\nnycto;22240;nycto;Nycto;;\n;22241;villagers_who_dont_study_well;不好好读书的村民;Villagers Who Don't Study Well;VWDSW\n;22242;GuiAPI,mod_GuiAPI;GuiAPI;;\nhealing-bed;22243;healingbed;Healing Bed;;\n;22244;cleardrops;清道夫;ClearDrops;CD\n;22245;craftaudit;方块审计;CraftAudit;CA\nkiss-mod-unofficial;22246;;Kiss Mod：非官方版;Kiss Mod Unofficial;\nrawinput;22247;rawinput;Raw Input;;\nloot-beams-retro;22248;lootbeamsretro;Loot Beams Retro;;\nam2pga;22249;am2pga;AM2PGA;;\ngtbcs-geomancy-plus;22250;gtbcs_geomancy_plus;GTBC's Geomancy Plus - Iron's Spells Addon;;\n;22251;RPTweaks;RedPower Tweaks;;\n;22252;;Mod Menu Babric;;\n;22253;cnusername;CnUsername;;\n;22254;carpet-wuhu-addition;Carpet WuHu Addition;;\n;22255;hiirosakura;Hiiro Sakura;;HS\nuniversalsummoning;22256;universal_summoning;通用召唤;Universal Summoning;US\n;22257;first_one_backpack;第一目标;First One Backpack Weight;\n;22258;gambac;Gambac [deAWT & M1 Fix];;\ncad-editor;22259;cadeditor;CAD Editor;;CADE\n;22260;unitweaks;UniTweaks;;\n;22261;nyalib;NyaLib;;\n;22262;NEIPlugins;NEI插件;NEI Plugins;\nnei-mystcraft-plugin;22263;NEI-Mystcraft-Plugin;NEI Mystcraft Plugin;;\njei-mystcraft-plugin;22264;mystcraft-jei-plugin;Mystcraft JEI Plugin;;\nbubblesex;22265;;BubblesEX;;\nlegendary-stages;22266;legendarystages;Legendary Stages;;\ntesseract-optimizations;22267;tesseract_optimizations;Tesseract Optimizations;;\n;22268;uwufied;UwUfied;;\n;22269;uniwrench;UniWrench;;\nuseful-ribbits;22270;useful_ribbits;Useful Ribbits;;\n;22271;PlasticCraft,mod_PlasticCraft;塑料工艺;PlasticCraft;PC\n;22272;drceph.petrogen;石油发电机;Petroleum Generator;\npower-advantage;22273;poweradvantge;能量优势;Power Advantage;\n;22274;eu-mj_engine;EU-MJ Engine;;\ncreate-transmission;22275;createtransmission;Create: Transmission!;;\n;22276;CCLights2;CCLights 2;;\n;22277;LiquidUU;液体UU;LiquidUU;\n;22278;moducomp;Modular Computing;;\n;22279;BuildcraftAPS;Advanced Power Systems;;APS\napplied-pneumatics;22280;appliedpneumatics;应用气动;Applied Pneumatics;AP\n;22281;bratwurst;Bratwurst;;\nneoa-ban;22282;naban;机器码封禁Neo版;NeoABan;NB\ntrashpandas-ascension;22283;trashpandas_ascension;Trashpanda's Ascension;;\n;22284;foodneededtorespawnatbed;Food needed to respawn at bed;;\nblock-cannon;22285;block_cannon;方块投射炮;Block Cannon;\npocket-space-teleporter;22286;pocket_space_teleporter;口袋空间传送器;Pocket Space Teleporter;\nre-from-the-ground-up;22287;rftgumod;Re From the Ground Up;;RFTGU\n;22288;stacksarestacks;Stacks Are Stacks;;\nvc-vocacraft;22289;vocacraft;乐音奇旅;VocaCraft;VC\nresource-fish;22290;resourcefish;Resource Fish;;\n;22291;rcdusts;Rock Crusher Metal Dusts Add-On;;\n;22292;GPEBTWTweak;BTWTweak;;\n;22293;NanoLaser;Nano Laser;;\n;22294;ImmibisPeripherals;Immibis's Peripherals;;\nhorde-beacon;22295;hordebeacon;尸潮信标;Horde Beacon;HB\n;22296;toomanyplants;更多植物;TooManyPlants;TMP\nextreme-blocks;22297;extremeblocks;极限方块;Extreme Blocks;EB\n;22298;EER;Equivalent Exchange Reborn;;EER\ntalecraft;22299;talecraft;TaleCraft;;TC\nnew-crimson-revelations;22300;crimsonrevelations;New Crimson Revelations;;\nmakeae2better;22301;makeae2better;Make AE2 Better;;\n;22302;elementalarrows;Elemental Arrows;;\n;22303;nyabags;NyaBags;;\n;22304;past_el_palettes;Past-el Palettes;;\n;22305;blowing-bubbles;Blowing Bubbles;;\nmobs-n-growths-origins;22306;mngo;Mobs & Growths: Origins / Reborn;;MnGO\nroadarchitect-encounters;22307;roadarchitect_roadencounters;RoadArchitect: Encounters;;\n;22308;extended-drawers-polymer-patch;Polymer Patch for Extended Drawers;;\n;22309;mystcraft_ageless;Mystcraft: Ageless;;\nthaumcraft-return-of-the-ancients;22310;returnoftheancients;Thaumcraft: Return of the Ancients;;\n;22311;swdheftpywatpaaap;对不起，我们没有充足的资金为您提供暮色森林传送门和天境传送门;SorryWeDon'tHaveEnoughFundsToProvideYouWithATwilightPortalAndAnAetherPortal;swdheftpywatpaaap\n;22312;MLCore;Matchlighter Core;;MLCore\n;22313;pinyinsearch;拼音搜索;Pinyin Search;PS\n;22314;xtrablocksbuildings,xtrablocksdiyadmin,xtrablocksdiycloth,xtrablocksdiydecorations,xtrablocksdiydiscs,xtrablocksdiyfencing,xtrablocksdiyfood,xtrablocksdiyglass,xtrablocksdiyglow,xtrablocksdiyliquids,xtrablocksdiymetal,xtrablocksdiypanels,xtrablocksdiypowered,xtrablocksdiysoundblocks,xtrablocksdiystone,xtrablocksdiystorage,xtrablocksdiyweapons,xtrablocksdiywood,xtrablocksdiyxtras,xtrablocksdiyfurniture,xtrablocksdiymiscmodels,xtrablocksdiytombs,pixelartist,xtrablocks;更多方块;XtraBlocks;\nneocontinuity;22315;continuity;NeoContinuity;;\ncontinuity-unofficial-port-1-16;22316;;Continuity Unofficial Port 1.16;;\n;22317;safefarmland;耕地保护;SafeFarmland;\n;22318;;Bone Meal Can spawn Fern;;\nae2-cell-upgrade-compat;22319;ae2cuc;AE2 : Cell Upgrade Compat;;AE2CUC\niris-shader-folder;22320;iris_shader_folder;Iris/Oculus Shader Folder;;\nrpcdrive;22321;rpcdrive;rpcDrive;;\n;22322;xulielizimod;序列粒子帧图;Sequence Frame Particle Mod;\nmixintrace-resmithed;22323;mixintracereforged;MixinTrace Reforged / MixinTrace Resmithed;;\ntetra-musket;22324;tetra_musket;火枪锻造;Tetra Musket;TM\ntudigong-wayfinder;22325;tudigong;土地公;TuDiGong;TDG\nmob-statues;22326;;Mob Statues;;\n;22327;Boxes;盒子;Boxes;\ncharset-cre;22328;;Charset CRE;;\nlegacy-farms;22329;legacyfarms;Legacy Farms;;\nthaumictweaker;22330;thaumictweaker;ThaumicTweaker;;\ncovers1624-lib;22331;Covers1624Lib,Covers1624Core;Covers1624 Lib;;\n;22332;adv-netherite-upgrade;高级下界合金 X 龙战利品;Advanced Netherite X DragonLoot;AND\none-enough-block;22333;oneenoughblock;One Enough Block;;OEB\n;22334;butterchat;更好的聊天框;Better Chat;\n;22335;nps;拼音搜索Neo;NeoPinyinSearch;\n;22336;renderlimiter;渲染抑制器;RenderLimiter;\n;22337;;丰饶之角：重生;Cornucopia Reborn;\nlongwings;22338;longwings;Longwings;;\nmore-ae2-additions;22339;mae2a;More AE2 Additions;;MAE2A\ncongrega-mystica;22340;congregamystica;Congrega Mystica;;\n;22341;no-screenshot-message;无屏幕截图消息;No Screenshot Message;\nfast-industrial-platform;22342;fast_industrial_platform;快速工业平台;Fast Industrial Platform;FIP\n;22343;rolling_gate_rzf_addition;人哉肥的卷帘门附加包;Rolling Gate RZF Addition;RGRA\ntensura-mortal-cultivation;22344;mortal_cultivation;转生：凡人修仙;Tensura: Mortal Cultivation;\npickup-limits;22345;pickuplimits;Pickup Limits;;\n;22346;;Ars Magica - NEI Plugin;;\n;22347;buildcraftAddon;BuildCraft Addon;;\narmorycache;22348;armorycache;军械库;Armory Cache;\ngregtech-storage-drawers;22349;gtstoragedrawers;GregTech Storage Drawers;;\n;22350;chiseled;Chiseled;;\n;22351;MetallurgyBees;Metallurgy Bees;;\n;22352;area_lib;Area Lib;;\nunordinaryextras;22353;unordinaryextras;UnordinaryExtras;;\nawakened-craziness;22354;1342419;Awakened Craziness;;\nvillager-market-nbt-fix;22355;villagermarket;村民市场 - NBT 修复版;Villager Market - NBT Fix;\nillager-expansions;22356;illager_expansions;Illager Expansions;;\n;22357;;Tropicraft [StationAPI];;\nbbl-shops;22358;shops;BBL Shops;;\n;22359;dronebox;DroneBox;;\n;22360;volare;Volo Volare;;\n;22361;customanvilrenameable;自定义铁砧改名权;CustomAnvilRenameable;CAR\n;22362;forgematicatool;全息蓝图工具;Forgematica Tool;\n;22363;supercannon;超级加农炮;SuperCannon;\nanvilcraft-extra-power;22364;anvilcraftextrapower;铁砧工艺：额外电力;Anvilcraft:Extra Power;AnEP\ncreate-prismatic-shine;22365;createprism;机械动力：流光溢彩;Create: Prismatic Shine;CPS\n;22366;dream-stone,dream_stone;梦之石;Dream Stone;\n;22367;;传说生存-Neoport;;LSO\nenhance;22368;enhance;增幅;Enhance Buffs;\nrecipes-of-the-lost;22369;uncraftable,recipesofthelost;失落配方;Recipes of the Lost;ROTL\n;22370;huhobot;HuHoBot;;\n;22371;pcl;PCL;;\ntacz-pack-upgrader;22372;taczpackupgrader;TaCZ Pack Upgrader;;\n;22373;coremod_api;CoreModAPI;;\n;22374;;DashLoaderForge;;\n;22375;pingview;Ping View;;\n;22376;no-creative-void-death;No Creative Void Death;;\n;22377;sinofestival;华夏节令;Sinofestival;\nextravagant-delight;22378;extravagantdelight;Extravagant Delight;;\ndodos-mobs;22379;dodosmobs;Dodo's Mobs;;\npillager-caravans;22380;pillager_caravans;Pillager Caravans;;\n;22381;new-mod;世界之低语;World's Whisper;WSW\n;22382;fabricbus;FabricBus;;\ncreate-soul;22383;create_soul;机械之魂;Create Soul;CS\n;22384;net_music_login_need;网络音乐机：登登你的;NetMusic: LoginNeed;\nauto-trade-cap;22385;at_td_cp;自动交易帽子;Auto Trade Cap;\nquality-equipment-reforked;22386;quality_forked;Quality Equipment Forked;;\nmetalmith;22387;metalmith;铜铁新生;Metalmith;\n;22388;convenient_malum;更方便的灵灾;Convenient Malum;\nnautilus-spears-mounts-of-mayhem;22389;mounts_of_mayhem;鹦鹉螺&矛 | 群骑纷争;Nautilus & Spears | Mounts of Mayhem;\ntelepathic-maid;22390;telepathicmaid;电波女仆;Telepathic Maid;TM\ntombmanygraves-2-api;22391;tombmanygraves2api;TombManyGraves 2 API;;\nmytown;22392;MyTown;MyTown;;\n;22393;unlittorch;Unlit Torches;;\n;22394;lanterns;Lanterns and Spotlights;;\n;22395;vertical_slab;竖半砖;Lin Vertical Slab;\n;22396;;千恋万花标题界面Neo;NeoYuZuUI;\n;22397;pearlchunkloader;珍珠区块加载器;PearlChunkLoader;PCL\n;22398;wings;lv wings / Wings 4;;\n;22399;foxbox;Foxbox;;\n;22400;more_than_a_foxbox;More Than A Foxbox;;\n;22401;fox_plushies;FOX PLUSHIES!!!!!;;\necliptic-seasons-oh-the-biomes-weve-gone;22402;es_x_bwg;Ecliptic Seasons - Oh The Biomes We've Gone;;\ntacz-1-21-1;22403;;TaCZ NeoForge Port;;\ntacz-special-forces;22404;taczsf;TaCZ: Special Forces;;TaCZ:SF\nsculpture;22405;sculpture;生物雕像;Sculpture;\naps-repowered;22406;apsrepowered;Advanced Power Systems: Repowered;;\ncitizen-pot;22407;citizenpot;市民锅;Citizen Pot;\nlinear-experience;22408;linearxp;线性经验;Linear Experience;LE\n;22409;durabilityviewer;耐久指示器：延续;Durability Viewer Continuation;\n;22410;veinminer;Veinminer;;\nhuaji-age-astral-regenesis;22411;huajiage;滑稽纪元：轮回星启;HUAJI Age: Astral Regenesis;\nslashblade-fabric-addons;22412;slashblade_fabric_addons;拔刀剑：重织附属包;SlashBlade Fabric Addons;SFA\n;22413;clear_sword;清除剑;Clear Sword;C_S\nproof-of-honor;22414;proof_of_honor;荣誉的证明;Proof of Honor;POH\nsplendour-ablaze-epoch;22415;splendourablazeepoch,splendour_ablaze_epoch;棠煌纪元;Splendour Ablaze Epoch;SAE\nspellbooks-attributes-reforged;22416;spellbooks_attributes_reforged;铁魔法属性：重铸;Spellbooks Attributes：Reforged;SAR\n;22417;happy_archer;HappyArcher;;\nsnows-bosses-mechasent;22418;snows_bosses_mechasent;Snow的Boss：机械遣使;Snow's Bosses: Mechasent;\ncreate-crafter-integration;22419;create_crafter_integration;Create: Crafter Integration;;\ncreate-schematicchecker;22420;createschematicchecker;机械动力：蓝图校验;Create: Schematic Checker;CSC\nae-terminal-view-cell-fix;22422;terminal_view_cell_fix;AE2显示元件槽位修复;AE Terminal View Cell Fix;TVCF\npearload;22423;pearload;实体区块加载;Pearload;\nmbc-projectez;22424;;ProjectEZ;;\ncomfy-cozy-brickery;22425;;Comfy, Cozy & Brickery (Patched);;\nspear-downport;22426;spear_downport;矛：向下移植版;Spear Downport;\naccurate-spears;22427;spear;‌精准长矛‌;Accurate Spears;\nsoundreload;22428;soundreload;SoundReload;;\nworld-mod-cacher;22429;cachemod;World Mod Cacher;;\norbital-railgun-by-mishkis;22430;orbital_railgun;Orbital Railgun;;\n;22431;enablecheats;开启作弊1.7.10;Enable Cheats 1.7.10;\n;22432;dlmc-id4-control_panel;大佬萌茶的快捷控制面板;Control Panel;\n;22433;world_silence;你吵到我眼睛了;Your F**k Is Bothering My Eyes;\nfile-director-incremental-mod-downloading;22434;;File Director - Incremental Mod Updates;;\npaytheprice;22435;paytheprice;Dont play nice, pay the price;;\n;22436;silicon;硅;Silicon;\n;22437;;氮;Nitrogen;\n;22438;autochest;自动箱子;AutoChest;\n;22439;freelook;自由视角;FreeLook;\nextended-itemview-eiv;22440;eiv;Extended ItemView;;EIV\n;22441;danger_api;DangerAPI;;\n;22442;mod_TMIRecipe;TooManyItems Recipe Addon;;TMIRecipe\nprojecte-teams-sync;22443;teamesync;ProjectE Teams Sync;;\nsequence-pathways-lotm-mod;22444;lotmmod;Sequence Pathways (LOTM);;\nyung-structures-addon-for-loot-integrations;22445;lootintegrations_yungs;Yung Structures Addon for Loot Integrations;;\nbackpacks-for-dummies;22446;backpacks;Backpacks for Dummies;;\n;22447;egafixer;附魔金苹果修复;Enchant Golden Apple Fixer;EGAF\n;22448;fixtconstructpickaxe;匠魂挖掘等级修复;FixTconstructPickaxe;\nco-lib;22449;common_ore_library;Common Ore Library;;COLib\nomni-cells;22450;ae2omnicells;OMNI 存储元件;OMNI Cells;OMNI\n;22451;CalclaviaCore;Calclavia Core;;\nsdm-gamestages-helper;22453;sdmgamestageshelper;SDM GameStages Helper;;\nmangos-multiblock-library;22454;mangomultiblock;Mango's MultiBlock Library;;\n;22455;sincere;认主的投掷武器;Sincere;\n;22456;fvlib;FvLib;;\n;22457;a177_added_modified_enchantments;A177增修附魔;A177 Added Modified Enchantments;A177_AME\nqa-enchants;22458;qa_enchants;Qa 附魔;Qa Enchants;QaE\n;22459;MetalMech;Metal Mechanics;;\ncreate-golems-galore;22460;creategolemsgalore;Create: Golems Galore;;\nvmh;22461;vmh;Variable Mob Height;;VMH\nore-stages-reborn;22462;sdm_orestages;SDM Ore Stages / Ore Stages Reborn;;\njourney-map-stages-reborn;22463;journeymapstages;JourneyMapStages Reborn;;\nthe-one-prode-stage;22464;theoneprodestages;The One Prode Stage;;\nsdm-mob-stages;22465;sdmmobstages,mobstages;SDM Mob Stages / Mob Stage Reborn;;\npassive-skill-tree-stage;22466;pststage;Passive Skill Tree Stage;;\nsdm-loot-overhaul;22467;sdm_rpg;SDM Loot Overhaul;;\n;22468;area_tools;Area Tools;;\nlightbind;22469;lightbind;亮度绑定;LightBind;\nqg-geocraft;22470;geocraft;天圆地方;GeoCraft;\ntimberreplant;22471;timberreplant;Timber Replant;;\nhunting-dimension-reforged;22472;hunting_dimension_reforged;Hunting Dimension Reforged;;\nbetter-angel-ring;22473;better_angel_ring;Better Angel Ring;;\ntcompat-tinkers-construct-addon;22474;tcompat;TCompat / Tinker's Construct Addon;;\nj-a-c-m-just-another-chisel;22475;justanotherchiselmod;J.A.C.M Just Another Chisel;;\nacuisine;22476;assortedcuisine;Assorted Cuisine;;\njuniors-troll-tnt;22477;juniortrolltnt;Junior's Troll TNT;;\ncompact-mekanism-machines;22478;compactmekanismmachines;Compact Mekanism Machines;;\ncompact-mekanism-machines-plus;22479;compactmekanismmachinesplus;Compact Mekanism Machines Plus;;\nwoot-revived;22480;woot_revived;Woot Revived;;\n;22481;ElectricExpansion;电力扩展;Electric Expansion;EE\ncroparium-the-twilight-forest;22482;cp_tf;矿石种植工艺：暮色森林;Croparium: the Twilight Forest;CPTF\n;22483;leafs-crucible;Leaf的炼金炉 - 冰与火;Leafs Crucible - Ice and Fire;\n;22484;ironspellomg;Iron's Spell: OMG;;\nstarcatcher;22485;starcatcher;捕星者;Starcatcher;\nthermaloot;22486;thermaloot;Thermaloot;;\nrs-insert-export-upgrade;22487;rsinsertexportupgrade;RS Insert Export Upgrade;;\nvoidminer-forked;22488;voidminers;VoidMiners Reforged;;\nsanguine-neural-networks;22489;sanguine_networks,snn;Sanguine Neural Networks / Hostile Neural Networks Addon;;\nchiseler;22490;chiseler;Chiseler;;\n;22491;mr_version_detector;Version Detector;;\n;22492;;Mod Menu (BTA Port);;\nxp-cost;22493;stupid_xp_cost_mod;Xp Cost;;\nshut-up-realms;22494;shut_up_realms;Shut Up Realms;;\nnarrator;22495;narrator;Narrator;;\nmc-truststore;22496;mc-truststore;mc-truststore (Let's Encrypt & Sessionserver Fix);;\nrlcraft-dregora-srp-arachnida-tweak;22497;ssf;RLCraft Dregora SRP Arachnida Tweak;;SSF\nbroken-heart-undershirt-statistic;22498;bhutracker;Broken Heart & Undershirt Statistic;;\nstitchedsnow;22499;stitchedsnow;StitchedSnow;;\n;22500;moderndeco;ModernDeco;;\n;22501;vendingmachine;自动售货机;Vending Machine;\ncreate-henry;22502;create_henry;Create: Henry;;\nrecipe-history-addon-for-jei;22503;recipehistory;Recipe History Addon for JEI;;\n;22504;phgirl;新生心智;Proto Hivemind Girl;\nwooden-tools-are-dumb;22505;woodentoolsremoved;Wooden Tools are Dumb;;\nlaserio-uel;22506;;LaserIO Unofficial Extended Life;;\nhamtastic-hamsters;22507;hhamsters;Hamtastic Hamsters!;;\ngateways-to-eternity-more-gateways;22508;moregateways;Gateways to Eternity - More Gateways;;\ncursed-earth-returns;22509;cursedearth;Cursed Earth Returns;;\nnot-enough-trials;22510;notenoughtrials;Not Enough Trials;;\norbital-railgun-reforged;22511;;Orbital Railgun Reforged;;\n;22513;death_note;死亡笔记;Death Note;DN\n;22514;jianzhu;更多功能方块;;\ncreate-heat;22515;createheat;机械热力;Create: Heat;\n;22516;dream_enchantment;梦之咒;Dream Enchantment;\n;22517;eatalways;一直吃;EatAlways;EA\nrehurttime;22519;rehurttime;伤害免疫机制重做;ReHurtTime;\nnaraka;22520;naraka;Naraka;;\n;22521;algaecraft,mod_AlgaeCraft;海藻工艺;AlgaeCraft;\nminechem-working-with-better-builders-wands;22522;;MineChem (working with Better Builder's Wands);;\ndungeons-mobs-port;22523;;Dungeons Mobs Port;;\nobtainable-end;22524;obtainable-end;Obtainable End;;\nslideshowplus;22525;slide_show_plus;Slide Show Plus;;SSP\nclassic-and-simple-status-bars-rebuild;22526;classicandsimplestatusbars;经典且简易的状态栏：重制版;Classic and simple status bars rebuild;CSSBRe\n;22527;the_last_crystal_cup;The Last Crystal Cup;;LTCC\n;22528;createworldui;新版创建世界界面;CreateWorldUI;CrWUI\n;22529;whiteout;Whiteout;;\nmining-quakes;22530;mining_quakes;Mining Quakes;;\ntpw;22531;tpw;TPW;;\ninspectability-forge;22532;;Inspectability-Forge;;\n;22533;orbital_railgun_sounds;Orbital Railgun Sounds;;\nbeneathmixins;22534;beneathmixins;BeneathMixins;;\ncutting-delight;22535;cuttingdelight;Cutting Delight;;\nlocate-command-backport;22536;locatebackport;Locate Command Backport;;\nthe-copperier-age;22537;thecopperierage;The Copperier Age;;\n;22538;veinminer-client;Veinminer Hotkey;;\n;22539;veinminer-enchantment;Veinminer Enchantment;;\n;22540;vertigo;Vertigo;;\npowercrystalscore;22541;PowerCrystalsCore;PowerCrystals Core;;\ntwilightsoul;22542;twilightsoul;暮色之魂;TwilightSoul;TS\n;22543;mr_better_enchantment;更好附魔;Better Enchantments;\nendfield-industry;22544;arknights_endfield;终末地工业;Endfield Industry;EI\nmodclassifier;22545;modclassifier;模组分类器;ModClassifier;\nlightningsuperpeashooter;22546;lightningsuperpeashooter;电能机枪豌豆;Lightning Super Peashooter;LSP\ndisplay-delight-fabric;22547;;Display Delight Fabric;;\n;22548;largestomach;大胃袋;LargeStomach;\n;22549;lifeessence;生命精油;Life Essential Oil;LEO\nst-drive;22550;stdrive;STDrive;;\nroadweaver;22551;roadweaver;阡陌交通;RoadWeaver;RW\nxemos-cluckshroom;22552;xemoscluckshroom;Xemo's Cluckshroom;;\n;22553;the_past_ages;The Past Ages;;TPA\ntake-back-the-night;22554;take_back_the_night;Take Back the Night;;\nwinds-of-requiem;22555;jojowor;JoJo: Winds of Requiem;;\nclassjs;22556;classjs;ClassJS;;\nc3-blocks;22557;c3blocks;C3 Blocks;;\n;22558;allium;Allium;;\ncreate-netherless-ported;22559;create_netherless;Create: Netherless Ported;;\nmechanical-healing-machine;22560;cobblemonmechanicalhealingmachine;Mechanical Healing Machine;;MHM\n;22561;potionenchant;药水附魔;PotionEnchant;PE\ntongda-way;22562;tongdaway;通达路;TongDaWay;\n;22563;ysmu;Yes Steve Model Unofficial;;YSMU\nenigmatic-legacy-cursed-ring;22564;;Enigmatic Legacy: Cursed Ring;;\nelemental-synergies;22565;elemental_synergies;Elemental Synergies;;\nsimple-netherite-horse-armor;22566;netherite_horse_armor;Simple Netherite Horse Armor;;\nproject-mmo-farmers-delight-compat;22567;pmmo_farmers_compat;Project MMO: Farmer's Delight Compat;;\ndarling-decorations;22568;darlingdecorations;Darling Decorations!;;\ncute-n-sweet;22569;cute_n_sweet;Cute n' Sweet!;;\ninspectability;22570;inspectability;Inspectability;;\n;22571;crtadd;CraftTweakerAdditions;;CrtAdd\nhunting-dimension-remake;22572;hunting_dimension_remake;Hunting Dimension Remake;;\nlootify;22573;lootify;Lootify (Structures);;\nportable-storage;22574;portable-storage,portablestorage;随身仓库;Portable Storage;\nlootstories;22575;lootstories;遇言典著;LootStories;\notherworld-core;22576;otherworld;Otherworld Core;;\nsimple-corpse;22577;simplecorpse;Simple Corpse;;\nbotania-mana-fluid;22578;manafluid;Botania Mana Fluid;;\ncreate-ae-generator;22579;create_ae_generator;Create: AE Generator;;\ncreate-additional-logistics;22580;createadditionallogistics;机械动力：物流附加;Create: Additional Logistics;CAL\nissrf-range-fix-irons-spells-n-spellbooks;22581;issrangefix;铁魔法溢出射程修复;Range Fix - Iron's Spells 'n Spellbooks;ISSRF\ngoety-delight;22582;goetydelight;诡厄乐事;Goety Delight;GD\nadvanced-little-garbage;22583;advanced_little_garbage;高级小垃圾;Advanced Little Garbage;ALG\n;22584;tpa;TPA-TP;;TPA\n;22585;miteequilibrium;MITE：宁静;MITE: Equilibrium;\ntetrawhetstone;22586;tetrawhetstone;Tetra磨刀石;TetraWhetstone;\nbrain-stealer-dragon-addon;22587;brain_stealer_dragon;龙之生存：夺脑龙;Brain Stealer Dragon - Addon;\nofficial-epic-fight-x-parcool;22588;epicparcool;Epic ParCool;;\njump-efficiency;22589;jump_efficiency;跳跳效率;Jump Efficiency;\nsmooth-scrolling-reforged;22590;;Smooth Scrolling Reforged;;\n;22591;PlayerVisibility;PlayerVisibility Mod;;\none-enough-fluid;22592;oneenoughfluid;One Enough Fluid;;OEF\njerksteve;22593;jerksteve;JerkSteve;;\n;22594;yes_steve_model,ysmu;Yes Steve Model 1.12.2;;\nydms-mobhealthbar-mod;22595;mobhealthbar;YDM's MobHealthBar;;\notherworld-origins;22596;otherworldorigins;Otherworld - Origins;;\ndragon-mounts-patches;22597;dragon_mounts_patches;Dragon Mounts Patches;;\ngregtech-modern-ore-generation-standalone;22598;gtmogs;GregTech Modern Ore Generation: Standalone;;GTMOGS\ncape-banners;22599;capebanner;Cape Banners;;\nbftp;22600;blastfromthepast;Blast from the Past;;BFTP\ncompact-crafting-updated;22601;;Compact Crafting Updated;;\nappliedchisel;22602;ae_chisel;应用凿子;AppliedChisel;AC\nitem-obliterator-despawn-fix;22603;itemobliteratordespawnfix;Item Obliterator Despawn Fix;;\n;22604;borukva-food;Borukva Food;;\ndynamic-difficulty-mod;22605;dynamic_difficulty;Dynamic Difficulty;;\nobituary-exchange;22606;obituaryexchange;Obituary Exchange;;\nfrom-the-caves;22607;from_the_caves;From The Caves;;\nlumen-alveary-patch;22608;lumenalvearypatch;Lumen Alveary Patch;;\nconfigurable-tooltips;22609;hideattributetooltip;Configurable Tooltips;;\nmodlistmemory;22610;modlistmemory;模组列表记忆;Mod List Memory;MLM\nmacu-lib;22611;macu_lib;macu lib;;\n;22612;cred;CommandRedirect;;\nbetter-end-crashed-ships-tweaks;22613;betterend-crashed-ships;BetterEnd Crashed Ships Tweaks;;\n;22614;Redrock;ExtrabiomesXL Red Rock Plus;;\nno-caves-mod;22615;no_caves;无洞穴;No Caves Mod;\nsimply-desire-paths;22616;paths;Pxl's Paths;;\nhappy-ghasts-boost;22617;happy_ghasts_boost;Happy Ghasts Boost;;\ncbr-addon;22618;cbraddon;自定义大逃杀扩展;CBR Addon;CBRA\ncompact-expansion;22619;compactexpansion;Compact Expansion;;\narmor-quick-swap;22620;armorquickswap;盔甲快速切换;Armor Quick Swap;QS\n;22621;dirty_craft;泥土的工艺;DirtyCraft;DC\nmixing-and-cutting;22622;mixing_cutting;混合切制;Mixing and Cutting!;\nyumo-isk-stackstackstack;22623;yumo_isk;拼好组;Yumo isk-Stackstackstack;ISK\n;22624;tongtian_ability;通天术;Tongtian Ability;TTA\nflourfood;22625;betterfood;面食;Flour Food;\n;22626;exprespawnrework;经验重生重制版;Exp Respawn Reworked;ERR\nneo-ae2-toogleable-view-cell;22627;ae2_toggleable_view_cell;AE2可切换显示原件-Neo;Neo AE2 Toogleable View Cell;\nmtt-more-tetra-tools;22628;more_tetra_tools;更多Tetra工具;More Tetra Tools;MTT\n;22629;tapecomcraft;磁带计算机装饰;Tapecomputer Decroation;\ncompat-delight;22630;compat_delight;Compat Delight;;\n;22631;MyTownLib;MyTownLib;;\n;22632;ForgePerms;ForgePerms;;\nthermal-processing;22633;thermal_processing;Thermal Processing;;\nbotanical-machinery-extra-reforked;22634;botanicalextramachinery;Botanical Machinery Extra Reforked;;\nbotanical-machinery-reforked;22635;;Botanical Machinery Reforked;;\narmorplus-reforged;22636;;ArmorPlus-Reforged;;\n;22637;mr_pale_pillagerwatchtower;Pale Garden Pillager Watchtower;;\n;22638;better_tooltips;Better Tooltips;;\n;22639;repulsor_skyblade_armor_dsnuvwau;Iron Man Armor;;\nvs-genesis;22640;genesis;VS Genesis;;\nelement-endow;22641;element_endow;元素赋予;Element Endow;\nresource-bar-api;22642;resourcebarapi;Resource Bar API;;\ngoety-iron;22643;goety_iron;诡厄铁魔法;Goety Iron;GI\ni-feel-bad;22644;i_feel_bad;恙 | 疫恙待诊;I Feel Bad;IFB\n;22645;createbackoff;Create: Back Off!;;\n;22646;allthingsflying;万物皆可飞行;All Things Flying;\nall-creepypasta-in-one;22647;all_creepypasta_in_one;All Creepypasta in One;;\ndisable-structures;22648;disablestructures;禁用结构;Disable Structures;\nwindowlogging-renewed;22649;windowlogging;WindowLogging Renewed;;\nleaderboards;22650;leaderboards;Leaderboards;;\n;22651;beacon-dragon-egg;信标龙蛋;Beacon Dragon Egg;BDE\nmaid-travel-camera;22652;maid_travel_camera;女仆旅行相机;Maid Travel Camera;\nnieih;22653;nieih;No Infinite Effects in Hud;;Nieih\n;22654;newnew_origins;牛牛起源;NewNew Origins;NNO\norigins-furs-updated;22655;originsfurs;Origins Furs Updated;;\n;22656;tgears;匠械;Tinkers' Gears;TiG\n;22657;basicmachines;Dr. Cyano's Basic BuildCraft Machines;;\nmoreeverything;22658;mod_moreEverything;More Fuel, Tweaks, and Everything;;\nepic-fight-skill-tree;22659;epicskills;Epic Fight: Skill Tree;;\ndtk-update;22660;dtkupd;Pale & Lively Update;;\ngoety-tetra-fix;22661;goetytetrafix;魔力剑聚晶Tetra兼容;GoetyTetraFix;GTF\notherworld-apotheosis;22662;otherworldapoth;Otherworld - Apotheosis;;\nmana-attributes;22663;manaattributes;Mana Attributes;;\nazalea-growth-control;22664;azealeagrowthcontrol;Azalea Growth Control;;AC\nnatural-waters;22665;naturalwaters;Natural Waters;;NW\nhealth-bars;22666;healthbars;Health Bars;;HB\nenchantment-insights;22667;enchantmentinsights;Enchantment Insights;;ES\nbloomcraft;22668;bloomcraft;Bloomcraft;;BC\nbmc-pale-garden-patcher;22669;bmcpalegardenpatcher;BMC Pale Garden \"Datafixer\";;\n;22670;polluted-rain;Polluted Rain;;\nmodernfix-mvus;22671;;ModernFix-mVUS;;\nstutterfix-refursbished;22672;;StutterFix - Refurbished!;;\nnoisiumforked;22673;;NoisiumForked;;\ngolems-kill-creepers;22674;golems_kill_creepers;Golems Kill Creepers;;\nno-animal-tempt-delay;22675;noanimaltemptdelay;No Animal Tempt Delay;;\nwearable-items;22676;rowi;Wearable Items+;;\npixelshot;22677;pixelshot;Pixelshot;;PS\norigins-plus;22678;originsp;Dan's Re-Worked Origins / OriginsPlus;;\nchangeling;22679;changeling;Changeling;;\nancient-scribes;22680;ancient_scribes;Ancient Scribes;;\nkamenriderperfectionproject;22681;krp_i_wanna_be_your_hero;假面骑士补完计划;Kamen Rider Perfection Project;KRP\ngoety-iron-link;22682;goetyironlink;法厄通;Goety Iron Link;\nsuperaim;22683;super_aim;SuperAim;;SA\n;22684;backcraft;我的后室;Backcraft;BC\ntaczattributeadd;22685;taa;Tacz 属性添加;TaczAttributeAdd;TAA\n;22686;itemtocoin;以物易币;ItemToCoin;\npet-rocktm;22687;petrock;宠物石;Pet Rock™;\ngunfire-overhaul-echoes-of-the-battlefield;22688;gunfireoverhaul;Gunfire Overhaul: Echoes of the Battlefield;;\nstamina-attributes;22689;staminaattributes;Stamina Attributes;;\nclanging-howl;22690;clanging_howl;Clanging Howl;;\nlimbus-company-mod-lcm;22691;lcm;边狱公司：麦块部门;Limbus Company Mine Section;LCM\n;22692;balmung;巴鲁蒙格;Balmung;\nrealistic-bird-behavior;22693;realisticbirdbehavior;真实鸟类行为;Realistic Bird Behavior;\nwing-kirin;22694;wing_kirin;翼麒麟;Wing Kirin;\nmcheli-reforged-mch-r;22695;mcheli;MCHeli-Reforged;;MCHR\n;22696;gugugu;GuGuGu的实用工具;GuGuGu Mod;gugugu\n;22697;batch-fakeplayers;批量假人;BatchFakeplayers;\n;22698;no-cooldown-on-attack;攻击无冷却;No cooldown on attack;ndoa\nenigmatic-legacy-plus;22699;enigmaticlegacyplus;神秘遗物+;Enigmatic Legacy+;\n;22700;sunken_spires,mr_sunken_spires;Sunken Spires;;\n;22701;speedrunapi;Speedrun API;;\nnovacore;22702;novacore;NovaCore;;\nsnowycrescentcore;22703;sccore;雪月核心;Snowy Crescent Core;SCC\nhostile-neural-industrialization;22704;hostile_neural_industrialization;Hostile Neural Industrialization;;\nclockwork;22705;clockwork;Clockwork;;\nvista;22706;vista;Vista;;\nkismet;22707;kismet;Kismet;;\nsea-life;22708;sealife;Sea Life;;SL\narmored-foes;22709;armoredfoes;Armored Foes;;AF\nvehicle-upgrade;22710;vehicleupgrade;Vehicle Upgrade;;VU\nparty-creepers;22711;partycreepers;Party Creepers;;PC\nall-the-heads;22712;alltheheads;All The Heads;;AH\nfantastic-wings;22713;fantasticwings;Fantastic Wings;;FW\nvillage-masquerade;22714;villagemasquerade;Village Masquerade;;VM\nnew-shroomcraft;22715;shroomcraft;Shroomcraft;;SC\ntinker-and-better-combat;22716;tinkers_better_combat;Tinker and Better Combat;;\n;22717;mixinsquared;MixinSquared;;\nextralib;22718;extralib;ExtraLib;;\nwhats-your-build;22719;whats_your_build;What's Your Build;;\nwhere-did-i-die;22720;wheredididie;Where Did I Die?;;\n;22721;;MCSR Sodium;;\ntooltips-txf;22722;tooltipstxf;Tooltips TXF;;\n;22723;allowdisconnect;Allow Disconnect;;\ntarget-locks;22724;target_lock;注视锁定;Target Lock;\nfleeing-animals;22725;fleeinganimals;Fleeing Animals;;\npeacegiver;22726;peacegiver;和平方块;PeaceGiver;\nbetter-books;22727;betterbooks;Better Books / Better Enchantment Tooltips;;\n;22728;chest-on-ghast;Chest on a Ghast;;\n;22729;seedqueue;SeedQueue;;SQ\ngeneral-feedback;22730;generalfeedback;通用意见反馈;General Feedback;GF\nraid-counter;22731;raidcounter;Raid Counter;;\nmodern-startup-qol-reforked;22732;modernstartupqol;Modern Startup QOL Reforked;;\nloot-integrations-dungeon-crawl;22733;lootintegrations_dungeoncrawl;Loot Integrations: Dungeon Crawl;;\nvanilla-loot-addon-for-loot-integrations;22734;lootintegrations_vanilla;Loot Integrations: Randomized Loot Compatibility;;\nloot-integrations-cataclysm;22735;lootintegrations_cataclysm;Loot Integrations: L_Ender 's Cataclysm;;\naquamirae-x-better-combat-compatibility;22736;aquamiraexbettercombat;Aquamirae x Better Combat Compatibility;;\nice-and-fire-dragons-x-better-combat;22737;iceandfirexbettercombat;Ice and Fire Dragons X Better Combat;;\neternal-currencies;22738;eternalcurrencies;Eternal Currencies;;\ncomfortable-campfires;22739;comfortable_campfires;Comfortable Campfires;;\nembers-floating-islands;22740;floating_islands;Ember's Floating Islands;;\nfarmers-cutting-oh-the-biomes-weve-gone;22741;mr_farmers_cuttingohthebiomeswevegone;Farmer's Cutting: Oh The Biomes We've Gone;;\nbeds-dont-explode;22742;bedsdontexplode;Beds Don't Explode;;\nenchantment-library-at-home;22743;enchlibathome;Enchantment Library at Home;;\nturtlearmor;22744;turtlearmor;Turtle Armor;;\nwandering-collector-forge;22745;wandering_collector;Thom's Wandering Collector;;\ncreate-immersive-aircrafts-data-pack;22746;;Create Immersive Aircrafts - Recipes;;\njaffactory;22747;jaffactory;Jaffactory;;\nhorses-can-swim;22748;horsescanswim;Horses Can Swim;;\nmob-population-control;22749;mobpc;Mob Population Control;;MobPC\nblighted-beasts;22750;blighted_beasts;Blighted Beasts;;\n;22751;;魔女的扫帚-Fabric移植版;Majo's Broom;\n;22752;anvilinnovate;Firma：锻铁革新;Firma: Anvilinnovate;FAI\nattribute-sculptor;22753;gameplay_sculptor;属性雕刻家;Attribute Sculptor;AS\nnotexturerotations;22754;notexturerotations;无纹理旋转;NoTextureRotations;\nbaked-enchants;22755;bakedenchants;Baked Enchants;;\n;22756;priority;Priority;;\ncombatproperties;22757;combatproperties;Combat Properties;;\n;22758;conditional-mixin;Conditional Mixin;;\njudgement-day;22759;judgementday;Judgement Day;;\n;22760;originsplus;Origins+;;\nsky-villages-waystones-compat;22761;;天空村庄 - 传送石碑兼容;Sky Villages - Waystones Compat;\nsky-villages-supplementaries-compat;22762;;天空村庄 - 锦致装饰兼容;Sky Villages - Supplementaries Compat;\nsky-villages-farmers-delight-compat;22763;;天空村庄 - 农夫乐事兼容;Sky Villages - Farmer's Delight Compat;\ndynamic-rpg-resource-bars;22764;dynamic_resource_bars;Dynamic RPG Resource Bars;;\nlwjgl3ify-colorful;22765;;LWJGL3ify Colorful / LWJGL3ify (1.12.2);;\n;22766;lib39;叁玖库;Lib39;\n;22767;mr_nightml_qualityadvancements;Quality Advancements;;QA\n;22768;simplepaperdoll;简单的纸娃娃;Simple Paper Doll;SPD\n;22769;fuck_namemc;F**k NameMC;;\n;22770;destroying-minecraft;Destroying MC;;\n;22771;bullseye;正中靶心;Bullseye;\n;22772;trophy_mod;奖杯模组;Trophy Mod;TM\ncreate-brassworks-missions;22773;brassworksmissions;Create: Brassworks Missions;;\nelysian-event;22774;elysian_event;Elysian Event;;\nsticky-enchanting-lapis;22775;stickyenchantinglapis;Sticky Enchanting Lapis;;\nhungry-pouches;22776;hungrypouches;Hungry Pouches;;\nantiquities;22777;antiquities;Antiquities: Furniture;;\n;22778;ztech;Ztech;;\npams-harvest-the-nether;22779;harvestthenether;Pam's Harvest the Nether;;\ncertain-questing-additions;22780;certain_questing_additions;Certain Questing Additions;;\nalexs-mobs-capsid-fix;22781;capsidfix;Alex生物衣壳体修复;Alex's Mobs Capsid Fix;AMCF\n;22782;parcoolskill;跑酷技能;ParCoolSkill;PSK\nmerchantjs;22783;merchantjs;MerchantJS;;MJS\nfetchjs;22784;fetchjs;FetchJS;;FJS\nall-the-mafuyu;22785;allthemafuyu;万象瑞雪;All the Mafuyu;ATM\ngoety-end-biomes;22786;goetyendbiomes;Goety End Biomes;;\nstructure-spawn-area-fix;22787;structure_spawn_area_fix;结构生成区域修复;Structure Spawn Area Fix;\naloof;22788;aloof;Aloof!;;\n;22789;tweakerge_compat;Tweakerge Compat;;\narmory-rpg-series;22790;armory_rpgs;Armory (RPG Series);;\nmoving-light-source;22791;movinglightsource;Moving Light Source;;\nftb-mighty-ender-chicken-rehatched;22792;mighty_ender_chicken_rehatched;FTB Mighty Ender Chicken Rehatched;;\ncyborg-robot-chicken-returns;22793;crc;Cyborg Robot Chicken: Returns!;;\ndragon-wings;22794;dragon_wings;龙翅膀;Dragon Wings;\n;22795;somefunstuff;葬送的魔法;Frieren's Magic;\nfurnitures-o-plenty;22796;furnituresoplenty;Furnitures O' Plenty;;\nwindswept-delights;22797;windswept_delights;Windswept Delights;;\nbeltborne-lanterns-accessories-layer;22798;bl_accessories_layer;Beltborne Lanterns: Accessories Layer;;\nbeltborne-lanterns;22799;beltborne_lanterns;Beltborne Lanterns;;\nmesophils-cities;22800;mesophils_cities;Mesophil's Cities | City Dimension 🏢;;\nmesophils-cities-classic;22801;mesophils_cities_classic;Mesophil's Cities Classic | Official City Style Pack 🏢;;\nvillagers-reborn;22802;slimpatch;Villagers Reborn;;\n;22803;simpleplayerlogger;Simple Player Logger;;\nmint-lib;22804;mint_lib;MINT Lib;;\nbetter-fishtanks;22805;betterfishtanks;更好的鱼缸;Better Fishtanks;\n;22806;roman-mod;Roman Minimap;;\naccelerated-recoiling;22807;acceleratedrecoiling;加速碰撞;Accelerated Recoiling;AR\nthank-your-eggs;22808;thankyoureggs;谢个蛋/鸡谢动力;Thank Your Eggs;tye\nstar-rail-apocalyptic-shadow;22809;sras;星穹铁道：末日幻影;Star Rail: Apocalyptic Shadow;SRAS\nunusual-adventures;22810;unusual_adventures;Unusual Adventures;;\ngui-scaler;22811;guiscaler;GUI Scaler;;\nae2-id-sorter;22812;ae_id_sorter;AE2 ID Sorter;;\n;22813;despairbook;魔女论破;DespairBook;\nmaids-return;22814;touhou_lost_maid;女仆归心;Maid's Return;TLM:MR\nboosted-brightness-remastered;22815;boostedbrightness;Boosted Brightness Remastered;;\nhelpfulcampfires;22816;helpfulcampfires;Helpful Campfires;;\ngeophilic-backport-vanilla-backport-compact;22817;geophilic_backport;Geophilic Backport - Vanilla Backport Compact;;\nnexus-layer;22818;nexus_layer;Nexus Layer;;\nzipline;22819;zipline;Zipline;;\nfathomless-crimson;22820;fathomless_crimson;Fathomless Crimson (Tribute to Horror);;\n;22821;hexconnect;HexConnect;;\nphase-journey;22822;phase_journey;PhaseJourney;;PJ\nlucky-pot;22823;theshore;Lucky Pot;;\n;22824;ench-tog;附魔控制器;Enchantment Controller;\nauroraslanterns;22825;auroraslanterns;Aurora's Lanterns;;\npocket-repose;22826;pocket-repose;Pocket Repose;;\nfishing-plus;22827;fishing_plus;Fishing +;;\nchaoscrafts-devices;22828;chaoscrafts_device_mod;ChaosCraft's Devices;;CDM\nobsessed;22829;the_obsessed;The Obsessed;;\narsenal-rpg-series;22830;arsenal;Arsenal (RPG Series);;\nmod-change-log;22831;modchangelog;模组变更记录;Mod Change Log;\n;22832;entityglow;实体发光;Entity Glow (Backport);\nsophisticated-sorter;22833;sophisticatedsorter;精妙整理;Sophisticated Sorter;SS\nabi-forge;22834;abi;Advanced Block Interactions;;ABI\ndisplay-edit;22835;display_edit;Display Edit;;\narthys-elemental-engine-aee-forge;22836;arthys_elemental_engine;Arthys' Elemental Engine;;AEE\nbobby-legacy;22837;;Bobby Legacy;;\nambience-mini;22838;ambience_mini;Ambience Mini;;\narthys-ebonite-ae-forge;22839;arthys_ebonite;Arthys' Ebonite;;AE\nenigmatic-arcana;22840;enigmatic_arcana;Enigmatic Arcana;;\n;22841;mekceumoremachine;通用机械：更多机械;Mekanism-CE-Unofficial-MoreMachine;\noptifine-cit-enchantment-patch;22842;optifinecitpatch;OptiFine CIT Enchantment Patch / OptiFine CIT Patch;;\ncontroller-mod;22843;controllermod;控制器支持;Controller Support Mod;\nnew-enchantment-switch;22844;enchantmentswitch;Enchantment Switch;;ES\nftgu-from-the-ground-up;22845;ftgu;From The Ground Up;;FTGU\noptions-profiles;22846;optionsprofiles;Options Profiles;;\nforgematicaprinter;22847;forgematica_printer;ForgematicaPrinter;;\nbetter-biome-blend-continued;22848;betterbiomeblend;Better Biome Blend Continued;;\nfurnace-xp-storage;22849;furnacexpstorage;Furnace XP Storage;;\nrecased;22850;recased;ReCased;;\n;22851;;致旧铁;zhi_jiu_tie;\nelemenics;22852;elemenix;元质学;Elemenics;\npale-bloom;22853;pale_bloom;Pale Bloom;;\nzip-fetcher;22854;zipfetcher;Zip Fetcher;;\ncreate-kinetic-illuminator;22855;create_kinetic_illuminator;Create: Kinetic Illuminator;;\n;22856;tagban;TagBan;;TB\n;22857;talent;魔女论破附属：超高校级才能;;\ntrade-tweaks;22858;tradetweaks;交易优化;Trade Tweaks;\n;22859;time;24小时;;\n;22860;;[Vic's Point Blank] Iconic 80s-90s Guns;;\nbryszirake;22861;bryszirake;The Rake;;\nno-worldgen-5-you;22862;noworldgen5you;No Worldgen 5 You;;\nno-worldgen-5-you-continued;22863;noworldgen5you;No Worldgen 5 You Continued;;\nspiral-tower-villages;22864;spiral_tower_village;Lios Spiral Tower Villages;;\ncare-to-share;22865;care_to_share;Care to Share;;\n;22866;hamster_mod,hamster;仓鼠模组;Hamster Mod;HM\n;22867;statictnt;静态TNT;StaticTNT;\nfloating-damage-numbers;22868;floatingdamagenumbers;Floating Damage Numbers;;\nvoice-messages;22869;voicemessages;语音消息;Voice Messages;\n;22870;millmix;MillMix Jubi;;\n;22871;dietaryreform;生活调味料：西瓜版;Spice of Life: Watermelon;\n;22872;garden_farming;园林耕作;Garden Farming;\nlets-do-tfc-farm-charm;22873;tfc_farm_charm;群峦农艺;[Let's Do] TFC Farm Charm;\nride-casually;22874;ride_casually;随意骑乘;Ride Casually;\n;22875;work_platform;工作平台;Work Platform;WP\nnight-repair;22876;nightrepair;夜视修复;Night Repair;\n;22877;disabletab;禁止私聊;Disable Tab Mod;\n;22878;body_transportation;遗体搬运;Body Transportation;\n;22879;minersglasses_curios_addon;矿工眼镜附属模组;Miner's Glasses Curios Addon;\n;22880;Loom;真正的织布机！;Loom;\n;22882;mine_paper_engine;MC桌面壁纸;MinePaper Engine;\noriginjs;22883;originjs;起源JS;OriginJs;\n;22884;ghost;Ghost;;\nancient-nature-a-prehistoric-experience;22885;ancientnature;Ancient Nature | A Prehistoric Experience!;;\nmte-more-tetra-effects;22886;more_tetra_effects;更多Tetra词条;More Tetra Effects;MTE\n;22887;vertical-bobbing;Vertical Bobbing;;\nextraquests;22888;extraquests;ExtraQuests;;\ncrashexploitfixer;22889;crashexploitfixer;崩溃漏洞修复;CrashExploitFixer;\nsticky-it;22890;sticky_it;Sticky It!;;\nyou-have-treasure;22891;you_have_treasure;你有宝！;You Have Treasure!;\nnether-portal-configurator;22892;netherportalconfigurator;Nether Portal Configurator;;\nlavasweeper;22893;lava_sweeper;熔岩清道夫;LavaSweeper;\n;22894;more_people;More People;;\nsimply-improved-terrain;22895;simplyimprovedterrain;简单地形改进;Simply Improved Terrain;SIT\n;22896;rustforforge;Rust for Forge;;\nfaewulfs-lib;22897;faewulf_lib;Faewulf's Lib;;\nmce2-lib;22898;mce2lib;MCE2 Lib;;\nbop-legacy-reborn-croparium;22899;cp_bop;超多生物群系往昔重生 & 矿石种植工艺;Biomes O' Plenty Legacy Reborn & Croparium;BOPCP\nlotr-reworked-new-beginning;22900;;魔戒：重置;LOTR Reworked;\nkelp-fertilizer;22901;kelpfertilizer;海带肥料;Kelp Fertilizer;\ncrystalwing;22902;CrystalWing;水晶之翼;CrystalWing;\n;22903;item_labels;Label My Items;;\nlightoverlay;22904;light-overlay;Light Overlay;;LO\nrcw;22905;rcw;Re-Crystallized Wing;;RCW\n;22906;HukidashiChat;HukidashiChat;;\n;22907;;Modern Lucky Block Addon;;\nobituary;22908;deathmessager;Obituary;;\nworn-horseshoes;22909;wornhorseshoes;Worn Horseshoes;;\nantiqueatlasautomarker;22910;antiqueatlasautomarker;Antique Atlas Auto Marker;;AAAM\nconnectorlib;22911;connectorlib;ConnectorLib;;\nmilkable-creepers;22912;milkablecreepers;Milkable Creepers;;\n;22913;villager_clothes;Villager Clothes;;\nmonsters-girls-witchy-stuff;22914;mg__witchy_stuff;Monsters&Girls: Witchy Stuff;;\ngrimoire-of-gaia-spells;22915;gogspells;盖亚魔法;Grimoire of Gaia Spells;\npsi-oddities;22916;psi_oddities;Psi-奇思妙想;Psi Oddities;\n;22917;schempaste;SchemPaste;;\nmining-progress-hud;22918;block-breaking-progress;挖掘进度 HUD;Mining Progress HUD;\ncreativetabs;22919;creativetabplus;CreativeTabs+;;CT+\ncreate-missiles;22920;createmissiles;Create Missiles;;\nexpansive-ores-addon;22921;;Expansive Ores;;\nnuclear-bomb-bay;22923;nuke_bay;Nuclear Bomb Bay;;\nlightning-rod-backport;22924;lrbp;Lightning Rod Backport;;LRBP\ncomplicated-bees;22925;complicated_bees;Complicated Bees;;\nwerdens-illagers;22926;wip;沃尔顿的刌民;Werden's Illager +;\nthaumic-attempts;22927;thaumicattepmts;Thaumic Attempts;;\nhorseshoes-forge;22928;horseshoes;Horseshoes [Forge];;\nreplication-matter-overflow-tank;22929;replication_matter_overflow;Replication: Matter Overflow Tank;;\nspaceploitation;22930;spaceploitation;SpacePloitation;;\n;22931;storagebox;StorageBox;;\n;22932;naturecraft;自然工艺;NatureCraft;NC\npathfinder-api;22933;pathfinderapi;Pathfinder API;;\nno-resource-pack-warnings-forge;22934;no_resource_pack_warnings_forge;No Resource Pack Warnings Forge;;NRPWF\ndbc-expansion;22935;dbcexpansion;DBC扩展;DBC-Expansion;\ncutt-promix;22936;cutt_promix;砧板乐事;Pro Assistant Cutting Board;PACB\ndirty-bowls-delight;22937;dirtybowlsdelight;脏碗乐事;Dirty Bowls Delight;\ncorpsetweaker;22938;corpse_tweaker;遗体调整;CorpseTweaker;\nconvenient-interface;22939;convenient_interface;便捷接口;Convenient Interface;CvI\nbrimm-armors-tactical-military-armors;22940;brimm;Brimm Armors | Tactical Military Armors;;\n;22941;shadow_tapes;The Shadow Tapes;;\nstarter-bunker;22942;starter_bunker;Starter Bunker;;\nshortbow-revamped;22943;shortbow;Shortbow Revamped;;\nplaces;22944;places;Places;;\npets-pillage;22945;petsandpillage;Pets & Pillage;;\nultimine-rewind;22946;ultimine_rewind;连锁撤回;Ultimine Rewind;\nproject-time-master;22947;projecttimemaster;Project Time Master;;\nlet-me-click-and-send-for-server;22949;letmeclickandsendforserver;Let Me Click And Send for Server;;\nextra-hoppers;22950;extrahoppers;Extra Hoppers;;\nquick-pack;22951;quick-pack;Quick Pack;;\narmored-paws-backport;22952;wolfarmor;Armored Paws Backport (Wolf Armor);;\narmored-paws-backport-bop-compat;22953;mr_armored_pawsbackportbopcompat;Armored Paws Backport (BOP Compat);;\narmored-paws-backport-natures-spirit-compat;22954;mr_armored_pawsbackportnscompat;Armored Paws Backport (Nature's Spirit Compat);;\ncreate-mining-laser;22955;create_mininglaser;Create: Mining Laser;;\ncreate-who-touched-my-train;22956;who_touched_my_train;机械动力：谁动了我的列车！;Create: Who Touched My Train!;WTMT\n;22957;soletssettheoreonfire;So Let's Set The Ore On Fire;;\ntab;22958;tab;TAB;;\narmor-tooltip;22959;armortooltip;Armor Tooltip;;\nmodefite-item-definition-backport;22960;modefite;Modefite - Item Definition Backport;;\nhobbit-hill-village;22961;hobbit_hill_village;Lios Hobbit Hill Village;;\nalexs-caves-better-combat;22962;alexscavesbettercombat;Alex's Caves Better Combat;;\nnuclear-bomb-bay-balance-datapack;22963;;Nuke Bay Balance pack-Less Drop type weapons per ship;;\nalltheimbaium;22964;alltheimbaium;Alltheimbaium;;ATI\nstorage-drawers-extra-reloaded;22965;storagedrawersextrareloaded;Storage Drawers Extra: Reloaded;;\nstartup-qol;22966;startupqol;Startup QoL;;\nmodern-startup-qol;22967;modernstartupqol;Modern Startup QOL;;\ndomestication-innovation-fixed;22968;;驯养革新修复版;Domestication Innovation Fixed;\n;22969;clear-despawn-reworked;ClearDespawn Reworked;;\nspartancataclysm;22970;spartancataclysm;斯巴达的武器：灾变;Spartan Weaponry: Cataclysm;\n;22971;metalrender;Metal Renderer;;\nnirvana-library;22972;nirvana_lib;Nirvana Library;;\n;22973;mod_MinecraftAPI;MinecraftAPI;;MCAPI\nanimated-mojang-logo;22974;animated-mojang-logo;Animated Mojang Logo;;\nhardcoreastagesitem;22975;hardcoreastagesitem;Hard Core Astages Item;;\n;22976;morbidrebornrebornreborn;The Morbid Reborn Reborn Reborn;;\npoint-blank-official-gun-gale-pack;22977;;Point Blank Official || Gun Gale Pack;;\nfullbright-ultimate;22978;fullbright;Fullbright Ultimate;;\nfast-pipes;22979;fastpipes;Fast Pipes;;\nimmersive-enchanting;22980;immersiveenchanting;沉浸式附魔;Immersive Enchanting;\nlios-universal-village-compat-pack;22981;;Lios Universal Village Compat Pack;;\nboss-checklist;22982;boss_checklist;Boss Checklist;;\nfire-resistance-tiers;22983;fireresistancetiers;Fire Resistance Tiers;;\ncursedinfuserbycage;22984;cursedinfuserbycage;诅咒之笼注入;CursedInfuserCage;\nenchantedsails;22985;enchantedsails;Enchanted Sails;;\npale-monarch;22986;pale_monarch;Pale Monarch;;\nfirmanutrition;22987;firmanutrition;FirmaNutrition;;\ncalypsos-night-vision-goggles;22988;calypsos_nightvision_goggles;卡里普索的夜视仪;Calypso's Night Vision Goggles;\nthe-apiarists-terminal;22989;ocgendustry;The Apiarist's Terminal;;\nbee-mastery;22990;beemastery;Bee Mastery;;\nnonupdate-retro;22991;NonUpdate-Retro;不再有更新：Retro 版;NonUpdate-Retro;\ncustom-message;22992;custommessage;自定义消息;Custom Message;\nkjsutils;22993;kjsutils;KJSutils;;\ncreate-access-denied;22995;create_access_denied;Create: Access Denied;;\ncreate-better-motors;22996;create_better_motors;Create: Better Motors;;\n;22997;createcclogistics;Create: CC Total Logistics;;\ncreate-fuel-motor;22998;create_fuel_motor;Create: Fuel Motor;;\ncreate-extra-casing;22999;create_extra_casing;Create Extra Casing;;\n;23000;createmetalogistics;机械动力：元物流;Create: Meta Logistics;\nbush-master-core;23001;bushmastercore;Bush Master Core;;\n;23002;tritium_configuration;氚配置;TritiumConfiguration;TC\n;23003;;宝可梦数值显示;Pixelmon Pokemon Stats Display;\n;23004;mob_effect_display_fix;状态效果显示修复;Mob Effect Display Fix;MEDFix\n;23005;smallphone;小手机;Small Phone;SP\n;23006;interfereddimension;干涉次元-虹色七剑;Interfered Dimension - The Seven Swords;IDSS\nanvilcraft-pigsplus;23007;anvilcraft_pigsplus;铁砧工艺：猪+;AnvilCraft: PigsPlus;\n;23008;manaita_plus_neo;砧板：革新;ManaitaPlus Neo;MPN\n;23009;kaleidoscope_compat;森罗物语：兼容;Kaleidoscope Compat;\n;23010;ItemDumper;ItemDumper;;\n;23011;teasofficial;Eh5の茶会：随意作品;Teas Official Random Things;\n;23012;;替换式汉化补丁;;\n;23013;xczltools;山海的工具箱;;\n;23014;chatscreen;聊天屏幕;Chat Screen;\n;23015;;传送晶塔;Pylons;\n;23016;mod_CreativeGui;CreativeGui;;\nsunny-riptide-trident;23017;sunnyriptide;Sunny Riptide Trident;;\n;23018;lastwords;鸳鸯董卓;LastWords;\n;23019;ForestryExtras;林业扩展;Forestry Extras;FE\n;23020;bubblecolumntweaks;气泡柱调整;Bubble Column Tweaks;\n;23021;time_slow;加护;GuardianMod;GM\n;23022;villager-inventory-hwyla-plugin;Villager Inventory Plugin for Hwyla forks;;\nblessfulled;23023;blessfulled;Blessfulled: Damage Indicators+;;\nirregular-implements;23024;irregular_implements;Irregular Implements;;\nsounds-be-gone;23025;soundsbegone;Sounds Be Gone!;;\n;23026;coppergratesbubblethru;Copper Grates Bubble;;\n;23027;fireworkdurability;Firework Durability;;\ncreate-colored-chain-conveyor;23028;create_colored_chain_conveyor;Create: Colored Chain Conveyor;;\ncreate-dimension-steamworks-realm;23029;create_dimension;Create: Dimension, Steamworks Realm;;\npunchy;23030;punchy;Punchy!;;\n;23031;quickswap;快速切换物品栏;Quick Swap;\n;23032;blockd;繁殖抑制&实体冻结器;Block Dynamics;BlockD\narmor-durability-hud;23033;armor-hud;Armor Durability HUD;;\ng3-doors;23034;g3project_doors;G3 Doors;;\narcane-convergence;23035;arcane_convergence;奥法归一;Arcane Convergence;AC\nae2-ftbquest-detector;23036;ae2_ftbquest_detector;AE2-FTB任务检测器;AE2-Ftbquest-Detector;\nbetter-library;23037;better_lib;Better Library;;\n;23038;mr_mob_hearts;生物之心;Mob Hearts;MH\ndimensions-of-alexs-caves;23039;alex_caves_dimensions;Alex的洞穴：维度;Dimensions-of-Alex's-Caves;DAC\nbetter-combat-particle;23040;malfu-mods;更好的战斗粒子;Better Combat Particle;\nvsplit;23041;vsplit;V裂;VSplit;\ntaczcurios;23042;tcc;Tacz饰品;TaczCurios;TCC\njeai;23043;jeai;Just Enough Artificial Intelligence;;JEAI\nutilitycraft;23044;;UtilityCraft;;\n;23045;metal_revolution;魔金革新;Metal Revolution;MR\nunofficial-variedcommodities;23046;variedcommodities;Unofficial VariedCommodities;;\nliteral-sky-block-neo;23047;;Literal Sky Block Neo;;\ncnpc-cobblemon-integration;23048;cnpccobblemonaddon;CNPC-Cobblemon-Integration;;\ncnpc-gecko-addon;23049;cnpcgeckoaddon;CNPC-Gecko-Integration / CNPC-Geckolib-Addon;;\nminecards;23050;minecards;我的卡牌;Minecards;\n;23051;;地表熔岩湖消失之日;;\nconstruction-wand-kots;23052;constructionwand;建筑手杖-耕云钓月版;Construction Wand - KOTS;\nthaumcraft-nei-plugin-patched;23053;thaumcraftneipluginpatched;Thaumcraft NEI Plugin : Patched;;\nreplication-rs2-bridge;23054;replication_rs2_bridge;Replication RS2 Bridge;;\nplayer-attribute-commands;23056;playerattributecommands;玩家属性指令;Player Attribute Commands;PAC\nlumis-lots;23057;lumis_lots;Lumi's Lots;;\nchange-vein-size-exp-ore;23058;;Change Vein Size Exp Ore;;\nomnisearch;23059;omnisearch;万象搜索;Omnisearch;OS\nftb-unearthed;23060;ftbunearthed;FTB Unearthed;;\n;23061;colored-obsidian;Colored Obsidian;;\ncollectors-caravan;23062;collectors_caravan;Collector's Caravan;;\n;23063;sellbinfixplus;出货箱服务器修复;sellbinfixplus;\n;23064;itemagnetic;Itemagnetic;;\n;23065;mr_sword_blockingplus;Sword Blocking Plus;;\nsoul-candles;23066;soulcandles;灵魂蜡烛;Soul Candles;\nlunchbox-sustenance-project;23067;lunchboxsustenanceproject;好好吃饭;Lunchbox Sustenance Project;LSP\njust-some-more-crops;23068;justanothercropsmod;农务多多;Just Some More Crops;\ncobblemon-spawn-alerts;23069;obblemon_spawn_alerts;方块宝可梦生成提醒;Cobblemon Spawn Alerts;CSA\nhex-text;23070;hextext;Hex Text;;\nhand-snow-ball;23071;hand_snow_ball;给我一个雪球;Hand Snow Ball;\nextended-shaders;23072;extendedshaders;Extended Shaders;;\ntitles-and-timing;23073;ch-mod;称号与计时;Titles and Timing;CH\nbiomespy;23074;biomespy;群系间谍;BiomeSpy;\nrandom-dungeon-spawners;23075;random_dungeon_spawners;随机地牢刷怪笼;Random Dungeon Spawners;\nversionerreborn;23076;;整合包版本工具：重生;VersionerReborn;\ncritical-strike;23077;critical_strike;Critical Strike;;\nghostblocks;23078;ghostblock;幽灵方块;GhostBlock;\n;23079;crossbow_scoping;Crossbow Scoping;;\ncreate-threaded-trains;23080;createthreadedtrains;Create: Threaded Trains;;\nraing-row;23081;raing_row_mod;雨后生草;Rain Grow Mod;\n;23082;underwatergras;水下草;UnderwaterGrass;\n;23083;seafloor_gardening;海底花园;Seafloor Gardening;\nnemos-vertical-slabs;23084;nemos_vertical_slabs;Nemo's Vertical Slabs;;\nelectric-advantage;23085;electricadvantage;电力优势;Electric Advantage;\nfroglightsreimagined;23086;froglightsreimagined;Froglights Reimagined;;\nflower-pots-plus;23087;fpplus;Flower Pots Plus;;\n;23088;diversity;Diversity;;\n;23089;dew_drop_farmland;Sturdy Farmland;;\nbettervannilafishes;23090;bettervannilafishes;更好的原版鱼类;Better Vannila Fishes;BVF\nezunclear-reactor-big-explosion-preventer;23092;ezunclear;易研电炸/核抑危;EzUnclear;\n;23093;;禁用苦力怕破坏;No Creeper Griefing;NCG\nkubejs-goety;23094;kubejs_goety;KubeJS Goety;;\ncreate-stuff-and-addition-tank-fix;23095;createsa_tank_fix;Create: Stuff 'N Additions - Tank Fix;;\ncreate-ratatouille-fried-delights;23096;ratatouille_fried_delights;机械动力：齿轮与麦穗的炸物拓展;Create：Ratatouille Fried Delights;CRFD\nspellcasters-archives;23097;spellarchives;施法者档案馆;Spellcaster's Archives;\nenders-spells-and-stuff-requiem;23098;ess_requiem;Ender's Spells and Stuff: Requiem;;\nironrust;23099;ironrust;IronRust;;\nweb;23100;web;Web;;\nletmecc;23101;letmecc;让我看看;Let Me See See;LMCC\n;23102;autoclicker;自动攻击|点击|种植;Auto Clicker;AC\n;23103;callofduty_hitsound;使命召唤命中反馈;Call oF Duty HitMarker;CODHM\n;23104;jel;真的一点不贵;Just Enough Levels;JEL\nfancy-trail;23105;fancy_trail;梦幻刀光;Fancy Trail;FT\ncaster-curios-bonus;23106;caster_curios_bonus;灿奇术饰;Caster Curios Bonus;CCB\nanyblade;23107;anythingslashblade;万物皆为刃;Anything SlashBlade;ASB\n;23108;setdimension;维度传送;SetDimension;\n;23109;worse_finder;更坏的索敌;WorseFinder;\ntongda-railway;23110;tongdarailway;通达铁路;TongDa Railway;\ncrystcursed-dragon;23111;crystcursed_dragon;晶咒龙;Crystcursed Dragon;\nbrutal-mobs;23112;brutalmobs;狂暴生物;Brutal Mobs;\nsuper-headcrab;23113;superheadcrab;超级猎头蟹;Super Headcrab;\n;23114;ark_rhode_island;明日舟舟：罗德岛;Ark Rhode Island;\n;23115;locations;简单位置;Simple Locations;SiLo\nlios-seafaring-dungeons;23116;lios_seafaring_dungeons;Lios Seafaring Dungeons;;\n;23117;InfinityChest;InfinityChest;;\ninductive-automation;23118;Automation;Inductive Automation;;\nsteam-advantage;23119;steamadvantage;蒸汽优势;Steam Advantage;\n;23120;betterblockentities;Better Block Entities;;BBE\n;23121;structurelib;StructureLib;;\nrs-logical;23122;rs_logical;RS Logical;;\nindustrial-revolution-by-redstone-rebooted;23123;jp-plusplus-ir2;Industrial Revolution by Redstone Rebooted;;IR3\n;23124;nullvoid;The Void;;\nomoshiroi-kamo;23125;omoshiroikamo;Omoshiroi Kamo;;\ndeath-taxes;23126;deathtaxes;Death & Taxes;;\n;23127;emc;Emotional Support Creeper;;\nly-ender-compass;23128;mr_ly_endercompass;Ender Compass;;\nwalking-crops;23129;;Walking Crops;;\nharken-scythe-resharpened;23130;harkenscythe;Harken Scythe: Resharpened;;\n;23131;FarmCraftory,FishCraftory,FoodCraftory,FruitCraftory,GuiCraftory,WateringCraftory;农场主;Craftory;\n;23132;Turbo;Turbo;;\nresonant-engine;23133;ResonantEngine;Resonant Engine;;\n;23134;LiquidXP;液体经验;Liquid XP;LXP\n;23135;mod_Arrows;Risugami的元素箭;Risugami's Elemental Arrows;\nxlvs-locks;23136;xlvslocks;Xlv 的锁;Xlv's Locks;\nthe-legend-of-2900-core;23137;create_fluid;The Legend Of 2900 Core;;\n;23138;fot;摸金-核心;Fire Of Timeless;FOT\ngraphene-extension;23139;grapheneextension;Graphene Extension;;\ndr-stone-kingdom-of-science;23140;drstone;Dr. Stone: Kingdom of Science;;\nkinetic-pixel-dup-bug-fix;23141;kinetic_pixel_patch;Kinetic Pixel Dup Bug Fix;;\n;23142;custom_shutdown;自选关机;Custom Shutdown;\nformatless;23144;formatless;格逝;Formatless;\ninvisiblocks-1-2-1;23145;invisibleblockmod;隐形方块;Invisiblocks;\nmage;23146;mage;MAGE (Graphical Tweaks);;MAGE\nspore-addon-nightmare;23147;sporeaddon;孢子扩展包：噩梦;Spore Addon: Nightmare;\nthe-biomass-must-grow;23148;tbmg;生物质必须生长;The Biomass Must Grow;TBMG\ncreate-nuka-cola;23149;create_nuka;Create Nuka Cola / Create Nuka;;\ndyson-cube-project;23150;dysoncubeproject;戴森立方计划;Dyson Cube Project;\n;23151;mod_NihongoInput,mod_NihongoMOD,NihongoMod;中文输入/颜色编辑;NihongoMod;\nannoying-villagers-herobrine-invasion;23152;annoyingvillagers;烦人的村民;Annoying Villagers: Herobrine Invasion;\n;23153;netmusicperipheral;网络音乐机：电脑外设;Net Music: Peripheral;\n;23154;simple-translation;简单翻译;SimpleTranslation;\ntacz-golem-compat;23155;taczgolemcompat;傀儡装配-战术人形;TaCZ Golem Compat;\ncustomizable-player-health;23156;customizable_player_health;可自定义的玩家生命值;Customizable Player Health;\n;23157;icyharvest;霜凝采撷;IcyHarvest;IH\ngoety-twilight;23158;goetytwilight;诡厄暮色;Goety Twilight;GTF\nattribute-achievement-rewards;23159;playerattributemanagement;成就属性提升;Attribute Achievement Rewards/Achievement attribute rewards;\nkaleidoscope-deco;23160;kaleidoscope_deco;森罗物语：装饰;Kaleidoscope Deco;\n;23161;easyentitylib;EasyEntityLib;;\ncoolcatlib;23162;coolcat_lib;CoolCatLib;;\n;23163;mam;Myths and Monsters;;MAM\n;23164;chronograph;计时仪;Chronograph;\ncnpc-additions;23165;cnpcadditions;CNPC-Additions;;\ncomplicated-bees-genetic-industry;23166;genindustry_bees;Complicated Bees: Genetic Industry;;\nsquish;23167;squish;Squish;;\ntameableminisheep;23168;tameableminisheep;TameableMinisheep;;\ntameablemossbloom;23169;tameablemossbloom;TameableMossbloom;;\nwolf-armor-and-storage-legacy;23170;wolfarmorandstoragelegacy;Wolf Armor & Storage Legacy;;\n;23171;fast;服从调剂;Inject;IJT\n;23172;mcedia;Mcedia;;\nmeowfish;23173;MeowFish;喵鱼-自动钓鱼;MeowFish;MF\nphonk-edit;23174;phonkedit;Phonk Edit;;\nae2addonlib;23175;ae2addonlib;AE2AddonLib;;\ninfinity-mending-datapack-mod;23176;mr_infinity_mending;Infinity and Mending;;\nepic-knights-ores-and-alloys;23177;epic_knights_ores_and_alloys;Epic Knights: Ores and Alloys;;\nepic-knights-dark-ages-addon;23178;darkagesarmory;Epic Knights: Dark Ages Addon;;\nindestructible-server-fix;23179;indestructible_server_fix;坚不可摧服务端修复;Indestructible Server Fix;\nchromosome-lib;23180;chromosomelib;Chromosome Lib;;CLib\nturtlebois-core-library;23181;turtlecore;Turtleboi's Core Library;;\nsimple-voice-chat-group-player-names;23182;svcgroupplayernames;Simple Voice Chat Group Player Names;;\ncinematic-zoom-neoforge;23183;cinematiczoom;CinematicZoom;;\nbusy-villagers;23184;busy_villagers;Busy Villagers;;\n;23185;mr_flat_dimensions;Flat Dimensions;;\nars-affinity;23186;ars_affinity;Ars Affinity;;\nars-hex-unity;23187;ars_hex;Ars Hex Unity;;\npretty-in-pink;23188;pretty_in_pink;Pretty In Pink;;\nchatcalc-neoforge;23189;chatcalc;ChatCalc;;\ndust-and-ore;23190;dustandore;Dust & Ores;;\nmatrix-minecraft-bridge;23191;matrixminecraftbridge;Matrix Bridge;;\n;23192;quickping;快捷宣告;QuickPing;\n;23193;entity-chat;Entity Chat;;\njei-copy-recipe-json;23194;jei_copy_recipe_json;JEI随意复制;JEI Copy Recipe Json;JCRJ\nconfigured-burn-time;23195;configured_burn;可配置的燃烧时间;Configurable Burn Time;\ntime-controller;23196;timecontrol;Time Controller / Time Control;;\n;23197;melting-snow-blocks;Improved Snow Melting;;\nridebattle-lib;23198;ridebattlelib;骑士战争库;RideBattleLib;\ngamma-tweaks;23200;gammatweaks;Gamma Tweaks;;\n;23201;reminder;神秘健忘男;Reminder;\nwandering-trader-express;23202;wandering_trader_express_delivery;流浪速递;Wandering Trader Express;\nstewme;23203;stew_me;铁锅炖自己;Stew Me;\nkubejs-powah-reborn;23204;kubejspowahreborn;KubeJS Powah Reborn;;\ninheritable-coat-color;23205;inheritable_coat_color;可继承的绵羊毛色;Inheritable Coat Color;ICC\npouch-of-unknown-nirvana;23206;pouchofunknownnirvana;未知之袋：涅槃;Pouch of Unknown: Nirvana;\nae2-auto-pattern-upload;23207;ae2_auto_pattern_upload;AE2自动样板上传;AE2 Auto Pattern Upload;APU\n;23208;spore_mini_additions;真菌感染孢子：MINI扩展包;Fungal Infection Spore: MINI Additions;\nclassbioarsenal;23209;classbioarsenal;职业自定义：词条与生物;ClassBioarSenal;cbs\npetite-inventory;23210;petiteinventory;真实物品栏 / 娇小物品栏;Petite Inventory;PI\ncipher-wright;23211;cipherwright;Cipher Wright;;\nshort-players;23212;shortplayers;矮玩家;Short Players;\nbackported-spears;23213;spears;Backported Spears;;\nufo-future;23214;ufo;UFO FUTURE;;\ncopper-age-backport;23215;copperagebackport,coppergolemlegacy;Copper Age Backport;;\nanimal-garden-sea-otter;23216;animalgarden_seaotter;Animal Garden - Sea Otter;;\nanimal-garden-porcupine;23217;animalgarden_porcupine;Animal Garden - Porcupine;;\nanimal-garden-red-panda;23218;animalgarden_redpanda;Animal Garden - Red Panda;;\nanimal-garden-owl;23219;animalgarden_owl;Animal Garden - Owl;;\nbytebuddies;23220;bytebuddies;ByteBuddies;;\nentity-enchantment;23221;entity_enchantment;实体附魔;Entity Enchantment;\nsmoothwater;23222;smoothwater;SmoothWater;;\ninsanity-assorted-magics;23223;jp-plusplus-fbs;Insanity: Assorted Magics;;\nbonded;23224;bonded;Bonded;;\nindexer;23225;indexer;Indexer;;\ndimensional-worldborder-continuation;23226;dimensionalworldborder;Worldborder (dimensional);;\ndimensional-worldborder;23227;dimensionalworldborder;Dimensional Worldborder;;\ndamage-tracker;23228;damagetracker;Mine and Slash Damage Tracker;;\nthe-growling-man;23229;growling_man;The Growling Man;;TGM\nmapwright;23230;mapwright;Mapwright;;\n;23231;simple_campfire;简易营火;Simple Campfire;SC\nwandering-ribbit;23232;wandering_ribbit;Wandering Ribbit;;\nkinetic-minecart;23233;kinetic-minecart;Kinetic Minecart;;\n;23234;totallyreasonable;靠帽荫阳 / 荫阳挡灼;TotallyReasonable;\nclassic-pipes;23235;classicpipes;Classic Pipes;;\n;23236;recursivecraft;递归合成;RecursiveCraft;\ndust-and-ore-raw-ore-expansion;23237;dustandore_raworeexpansion;Dust & Ore: Raw Ore Expansion;;\n;23238;;Minecraft Sigma;;MCS\n;23239;pricksnplanks;Pricks 'n' Planks;;\narchogenum;23240;archogenum;Archogenum;;\ngiant-drops;23241;giant_drops;Giant Drops;;\nkubejs-data-component;23242;kubejs-data-component;KubeJS Data Component;;DCJS\nquantum-things;23243;randomthings;Quantum Things;;\nsimple-emotes;23244;simpleemotes;Simple Emotes;;\nflux-ducts;23245;fluxducts;Flux Ducts;;\nvalley;23246;valley;Valley;;\n;23247;mr_still_life;Still Life;;\nwizard-tower;23248;wizard_tower;Wizard Tower;;\nthe-dyson-sphere-project;23249;dysonsphere;戴森球计划;The Dyson Sphere Project;\nbronze-mod;23250;bronze;青铜;Bronze;\nlib39;23251;lib39,lib39-core;Lib39;;\ncompanions-dogfolk;23252;companions_dogfolk;Companions - Dogfolk;;\nthe-block-keeps-ticking;23253;the-block-keeps-ticking;The Block Keeps Ticking;;\nbetter-pet-teleporting;23254;betterpetteleporting;更好的宠物传送;Better Pet Teleporting;\nbeacon-aura;23255;beacon-aura;Beacon Aura;;\ncreate-ender-storage;23256;createenderstorage;Create Ender Storage;;\nportalfarm-despawn-fix;23257;portalfarm-despawn-fix;Portalfarm Despawn Fix;;\n;23258;kitchenprojectiles;Kitchen Projectiles;;\neternally-overburdened;23259;eternallyoverburdened;Eternally Overburdened;;\nrobot-dinos;23260;robot_dinos;Robot Dinos;;\nstellar-decay;23261;stellar_decay;繁星枯朽;Stellar Decay;SD\nannihilationblade;23262;annihilationblade;湮灭之刃;Annihilation Blade;\nacceleratedblade;23263;slashblade_acceleratedrendering;拔刀剑：加速渲染兼容;SlashBlade: AcceleratedRendering;\nheartbladeunoffical;23264;heartbladeunoffical;心之所向非官方版;HeartBladeUnOffical;HBU\nslashblade-japanese-addon-pack-adder;23265;sjap_adder;拔刀剑·日系附属·扩展包;SJAPA·The SlashBlade EXtra;SJAPA\nproject-saligia-reborn;23266;project_saligia_re;炼金重铸计划·七罪新约;Project Saligia Reborn;PRS\nlostbladereborn;23267;lostbladereborn;遗忘之刃重生;LostBladeReborn;LBR\nslashblade-shinku-and-blood-katana;23268;shinkubloodkatana;炼狱真红之刃;SlashBlade: Shinku And Blood Katana;\nfoxbladeextrare;23269;foxextra;狐月刀改·重生;FoxBladeExtraRe;FBER\nspelunkers-palette;23270;spelunkerspalette;Spelunker's Palette;;\n;23271;seasons-api;季节 API;Seasons API;\nmavity-lib;23272;mavity_lib;Mavity Lib;;\nvoid-lib;23273;void_lib;Void Lib;;\neasy-platform;23274;easyplatform;简易平台;Easy Platform;\ndont-get-hurt;23275;dgh;无伤大雅;Dont Get Hurt;DGH\n;23276;witw;墙中低语/跳货;WhispersInTheWalls;WITW\nflight-ring;23277;flightring;飞行戒指;Flight Ring;\n;23278;ai;AI追杀;AI Hunter;\nautorepairhandler;23279;unbreakablestart;坚不可摧;AutoRepairHandler;ARH\nwonderous-sea-endlessocean;23280;wonderous_sea_endlessocean;Wonderous Sea - An Endless Ocean Adventure / Wonderous Sea - Endless Ocean;;\n;23281;bettertp;更好的传送;BetterTP;BT\nftb-echoes;23282;ftbechoes;FTB Echoes;;\nmakeup-aesthetics-decor;23283;aesthetic_decor;Makeup | Aesthetics Decor;;\nopencomputers-rescaled;23284;;OpenComputers Rescaled;;\nentityguardian;23285;entityguardian;EntityGuardian;;\ndeep-dark-dimensional-dungeons;23286;deepdarkdimdungeons;Deep Dark Dimensional Dungeons;;\ninfinite-light;23287;infinite_light;无限光源;Infinite Light;IL\n;23288;list;List Core;;\nthermal-tweaks-dynamic-temperature-survival;23289;thermaltweaks;Thermal Tweaks;;\ndebloat;23290;debloat;Debloat;;\northocamera-unofficial-neoforge-port;23291;;OrthoCamera (Unofficial NeoForge Port);;\ncrossroads-legacy;23292;crossroads;Crossroads Legacy;;\n;23293;enderwild;Wilder Enderscapes;;\ncharcoalplus;23294;charcoalplus;Charcoal+;;\nmodern-decor-outdoor;23295;modern_decor_outdoor;Modern Decor Outdoor;;\nshadeless-blocks;23296;shadelessblocks;Shadeless Blocks;;\nmodern-decor-wooden;23297;mordern_decor_wooden;Modern Decor Wooden;;\nshadeless-mobs;23298;shadelessmobs;Shadeless Mobs;;\nherobrine-old-edition;23299;herobrine_mod_alpha_version;Herobrine Mod Alpha Version / Herobrine Old Edition;;\nmultiplayer-spawners;23300;multiplayerspawners;Multiplayer Spawners;;\ncompact-crafting-fork;23301;;Compact Crafting Fork;;\n;23302;banner;Ban Enforcer;;BE\ncustom-machinery-ars-nouveau;23303;custommachineryars;Custom Machinery Ars Nouveau;;\npeaceful-hunger;23304;peaceful_hunger;Peaceful Hunger | UPDATED;;\nftb-ez-crystals;23305;ftbezcrystals;FTB Ez Crystals;;\n;23306;enderioaddons;末影接口拓展非官方版;EnderIOAddons-Unofficial;\nfalling-blocks-anim;23307;falling_blocks_anim;Falling Blocks Anim;;\nimmersive-overlays;23308;immersiveoverlays;Immersive Overlays;;\nvolcano;23309;volcano;Volcano;;\n;23310;mr_santas_delight;Santa's Delight;;\nnbtstructurelib;23311;nbtlib;NBTStructureLib;;\n;23312;enderlib;EnderLib;;\ncoalapi;23313;coalapi;CoalAPI;;\nhomo-http-router;23314;homohttprouter;野兽先辈的HTTP路由器;Homo Http Router;HHR\nfastconfigapi;23315;fastconfigapi;Fast Config API;;\n;23316;awakeapi;Awake API;;\n;23317;anti_offhand;Anti Offhand;;\n;23318;so-tuff;So Tuff;;\n;23319;lootswap;LootSwap;;\nmoreeventsjs;23320;more_events_js;MoreEventsJS;;MEJS\nspudiversity-more-and-more-potato;23321;spudiversity;土豆盛宴-更多的土豆！;Spudiversity - More And More Potato;Spu\n;23322;cobblemon_jei_integration;方可梦战利品信息;Cobblemon JEI Integration;CJI\nflans-mod-tyrants-and-plebians-edition;23323;;FMU : Tyrants and Plebians Edition;;\nconnected-caves;23324;connected_caves;Connected Caves;;\ninvasion-code-red-reborn;23325;invasioncodered;Invasion Code Red Reborn;;\nmecha-pillager-boss;23326;mechapillagerboss;Mecha Pillager Boss;;\n;23327;zgmobs;ZG Mobs;;\ninvincibleconquest;23328;invincible_conquest;Invincible: Conquest;;\n;23329;arknights_races;明日方舟种族;Arknights origins races;\ngulliver-lite;23330;gulliverlite;Gulliver Lite;;\nmojo-redmetal;23331;mojo-redmetal;Mojo-Redmetal;;\nlogistics-bridge;23332;logisticsbridge;Logistics Bridge;;\nsourceblock;23333;sourceblock;源方块;Source Block;\naec;23334;AetherCraft;以太工艺;AetherCraft;AeC\nanimal-garden-sugar-glider;23335;animalgarden_sugarglider;Animal Garden - Sugar Glider;;\nanimal-garden-meerkat;23336;animalgarden_meerkat;Animal Garden - Meerkat;;\njust-sit;23337;justsit;Just Sit!;;\ntameable-endermen;23338;tameableendermen;Tameable Endermen;;\nvanilla-shears;23339;vanillashears;Vanilla Shears;;\nlogibots;23340;logibots;Logibots;;\nalways-eat;23341;always_eat;Always Eat;;\nspice-of-life-valheim-reforged;23342;sol_valheim_reforged;Spice of Life: Valheim Reforged;;\nenchantedstorage;23343;enchantedstorage;附魔收纳;EnchantedStorage;ES\nultimine-addition;23344;ultimine_addition;FTB Ultimine Addition;;\nimmersive-tips;23345;immersive-tips;沉浸式提示;Immersive Tips;\nlightspeedre-launch-optimizations;23346;lightspeed;LightspeedRe;;\nenhancedgunsounds;23347;enhancedgunsounds;EnhancedGunSounds;;\n;23348;mcsrranked;MCSR Ranked;;\n;23349;createcafe;机械动力：咖啡馆 非官方分支;Create Cafe Unofficial Version;\nrealm-rpg-treasure-balloons;23350;realmrpg_balloons;Realm RPG: Treasure Balloons;;\n;23351;BioMaterials;Bio Materials;;\n;23352;alexs_caves_delight;Alexs Caves Delight;;\nkitchen-tools;23353;kitchentools;厨师宝具;Kitchen Tools;\ncamphor;23354;camphor;Camphor;;\njade-magnifying-glass;23355;jademagnifyingglass;Jade Magnifying Glass;;\nc418-music-restored;23356;c418musicrestored;C418 Music Restored;;\nender-uranium;23357;;Ender Uranium;;\ncrab-claws;23358;crabclaws;Crab Claws;;\ndraconic-evolution-render-patcher;23359;derenderpatcher;Draconic Evolution Render Patcher;;\ntouhou-little-maid-epistalove;23360;touhou_little_maid_epistalove;车万女仆：纸短情长;Touhou Little Maid: Epistalove;TLME\nomnicore;23361;omnicore;OmniCore;;\n;23362;voxelbridge;VoxelBridge;;VB\nimmersive-fixes;23364;immersivefixes;沉浸工程修复;Immersive Fixes;\nschematic-energistics;23365;schematicenergistics;Schematic Energistics;;\nboss-rush;23366;bossrush;老板冲;Boss Rush;BR\nmekanism-bigger-teleporters;23367;mekanismbiggerteleporter;Mekanism Bigger Teleporters;;\n;23368;carpet_igny_addition;Carpet IGNY Addition;;IGNY\nregistry-blocker;23369;registry_blocker;Registry Blocker;;\nmore-ornamental-plants;23370;more_orn_plants;观赏植物;More Ornamental Plants;\npresence-footsteps-neoforge;23371;presencefootsteps;脚步声NeoForge版;Presence Footsteps (NeoForge);\n;23372;villager_translator;村民语言翻译器;Villager translator;\nai-player;23373;ai-player;AI玩家;AI Player;\naaron;23374;aaron;Aaron;;\n;23375;hextech_lib;海克斯;Hextech Lib;\n;23376;ore_farm;矿物农场;Ore Farm Mod;OF\nmissing-trees;23377;missingtrees;Missing Trees;;\npk-craft;23378;mpekka;P.E.K.K.A. Craft;;\neasy-craft-tweaker;23379;easy_tweaker;Easy CraftTweaker;;\nwenxins-warp-pipes;23380;warp_pipes;WenXin's Warp Pipes;;\nforgelin-legacy;23381;;Forgelin: Legacy;;\nanothertips;23382;anothertips;AnotherTips;;\n;23383;SaveMoney;Save Money;;\ncopper-drops;23384;copperdrops;Copper Drops;;\ncooks-collection;23385;cookscollection;Cook's Collection;;\nno-end;23387;noend;我的末地呢？！;No End;\nome-tweaks;23388;ometweaks;OME Tweaks;;\nclutter-no-more;23389;clutternomore;Clutter No More;;\ndecocraft-nature;23390;decocraft_nature;Decocraft Nature;;\nfrex;23391;frex;FREX Rendering Extensions Library;;FREX\nopen-terrain-generator-continued;23392;;OTG: Open Terrain Generator Continued;;\nduplicationless;23393;duplicationless;复演结/コピーの終結;Duplicationless;\n;23394;swjc;死亡继承&保命;;\ncustom-mob-targets;23395;cmt;Custom Mob Targets;;CMT\n;23396;betterfurnacefix;更好的熔炉：容器修复;Better Furnace Container Fix;BFCF\nbopintegration-catized;23397;bopintegration;超多生物群系集成：非官方版;BOPIntegration - Catifized;BOPIC\n;23398;rawinput;Raw Mouse Input;;\nbuildnotes;23400;buildnotes;建筑笔记;BuildNotes;\n;23401;mr_japanese_temple;日式寺庙;Japanese Temple;\n;23402;mitenewworld;MITE：新世界;MITE: NewWorld;MNW\nhbms-nuclear-tech-modernized;23403;hbm_m;HBM的核科技现代化;HBM's Nuclear Tech Modernized;\nmtr-france-addon;23404;mtrfranceaddon;MTR France Addon;;MFA\nminions-remastered;23405;minionsremastered;Minions Remastered;;\napplied-enough-items;23406;appliedenoughitems;应用定整;Applied Enough Items;\nmilkspray;23407;milkspray;牛奶喷雾;Milk Spray;\n;23408;morestatusbar;进化之路;Road to Evolution;RTE\n;23409;totem_of_homing;归家图腾;Totem of Homing;\nbetter-foliage-legacy-edition;23410;;Better Foliage - Legacy Edition;;\n;23411;replace_block;方块替换;ReplaceBlock;RB\n;23412;mr_gun_core;Gun Core;;\n;23413;adventure-platform-fabric,adventure_platform_neoforge;adventure-platform-mod;;\npedestal-crafting;23414;pedestalcrafting;Pedestal Crafting;;\npedestal-crafting-patched;23415;;Pedestal Crafting - Patched;;\nputbuildings;23416;putbuildings;建筑放置;PutBuildings;\n;23417;no_air_mining_penalty;去除浮空挖掘惩罚;No Air Mining Penalty;\n;23418;MoreTurtles;More Turtles;;\n;23419;Almagest;The Almagest;;\nreader;23420;mr_reader;Reader;;\n;23421;jp-plusplus-mp;Mass Production;;\nimproved-cleanroom-relauncher;23422;improved-relauncher;Improved Cleanroom Relauncher;;\nqbithop;23423;bithop;QBitHop;;\ncatalogue-vintage;23424;catalogue;Catalogue Vintage;;\nwilder-flowers;23425;wilderflowers;Wilder Flowers;;\nrender-held-item-while-a-wolf-witchery-addon;23426;wolfitemrender;Render Held Item While A Wolf - Witchery Addon;;\neconomicalmilktea;23427;DCsEcoMT;EconomicalMilkTea;;\nunnamed-monster;23428;unnamed_monster;Unnamed Monster;;\nhalf-bite;23429;halfbite;Half-Bite;;\nmerry-snow;23430;merrysnow;瑞雪物语;Merry Snow;MS\n;23431;RedstonePipesR;Redstone Pipes Reloaded;;\nhdd-continuation;23432;headdowndisplay;Head-down Display Continuation;;\nheaddowndisplay;23433;headdowndisplay;Head-down Display;;\nrefined-equivalence;23434;refined_equivalence;Refined Equivalence;;\n;23435;PocketDimension;Pocket Dimension;;\namtweaker;23436;AMTweaker;AMTweaker;;\n;23437;VillagerCannon;VillagerCannon;;\n;23438;AMTAddonJP;JapaneseAddonForAMT2;;\nfluidityfoodstuffs;23439;FluidityDC;FluidityFoodstuffs;;\nchisel-modern;23440;chisel;Chisel Modern;;\nrmscore;23441;nomorescore;No More Score;;\nmagic-coins-tweak;23442;magiccoinstweak;Magic Coins Tweak;;\nirons-apothic-invaders;23443;irons_apothic_invaders;Iron's Apothic Invaders;;\nmekagenjei-mekanism-generator-addon;23444;mekagenjei;MekaGenJei / Mekanism Generator Addon;;\nboatfly;23445;boatfly;船只飞行;BoatFly;\nibicf;23446;ibicf;I Believe I Can Fly;;IBICF\nkube-utils;23447;kubeutils;Kube Utils;;\nlaputan;23448;laputan;Laputan;;\nlecovian;23449;lecovian;Lecovian;;\npops;23450;pop;Pop (up);;\nbad-ores;23451;badores;Bad Ores;;\nreactor-plus;23452;reactorplus;Reactor Plus;;\n;23453;DCsShowcase;ShowcaseAddon2;;\n;23454;DCIronChain;IronChain2;;\n;23455;DCsBoat;DestroyerBoat;;\npetrock-mod;23456;petrock;Petrock - Forge;;\nbettertalk;23457;bettertalk;BetterTalk;;\nautocrafter2000;23458;AutoCrafter2000;AutoCrafter2000;;\n;23459;MultipleCamera;Multiple Camera;;\nbaby-mobs-rebrushed;23460;babymobs;Baby Mobs Rebrushed;;\nanimal-garden-mouse;23461;animalgarden_mouse;Animal Garden - Mouse;;\nbetter-sleeping;23462;bettersleeping;更好的睡眠;Better Sleeping;\namtgenerators;23463;amtgen;AMTGenerators;;\nblooming-biosphere;23464;mr_blooming_biosphere;Blooming Biosphere;;BB\nextrabees-community;23465;extrabees;更多蜜蜂：社区版;ExtraBees: Community Edition;\nprogrammable-magic;23466;programmable_magic;可编程魔法;Programmable Magic;PrM\nbetter-farming;23467;better_farming_plus;Better Farming + +;;\nevil-seagull;23468;evilseagull;邪恶海鸥;Evil Seagull;ES\nhbm-galacticraft-companion;23469;hbmgccompanion;HBM Galacticraft Companion;;\ngamingbarns-guns;23470;mr_gamingbarns_guns;Gamingbarn's Guns;;\njas-compatability-addon;23471;JASCompatability;Just Another Spawner Compatibility Addon;;JASCA\n;23472;icylifetracker;霜迹;IcyLifeTracker;ILT\nad-astra-auto-lander;23473;adastra_autolanding;Ad Astra: Auto Lander;;\n;23474;fuckcrafter;机械动力：解放合成器;Create: FuckCrafter;CFC\ncreate-cobblemon-balls-overhaul;23475;createmonballsoverhaul;Create: Cobblemon Balls Overhaul;;\ncreate-air-ducts;23476;create_airducts;Create: Air Ducts;;\ngrimoire-of-gaia-redux;23477;gogredux;Grimoire of Gaia: Redux;;\n;23478;mr_create_compatbetterendbetternether;Create: Compat BetterEnd & BetterNether;;\ncreate-wrapped;23479;create_wrapped;Create: Wrapped;;\ncreate-cogs-invaders;23480;create_cogs_invaders;Create Cogs Invaders;;\nnotenoughscaffold;23481;notenoughscaffold;Not Enough Scaffold;;\nore-trees-reboot;23482;ore_trees_reboot;Ore Trees : Reboot;;\nre-barked;23483;re_barked;RE: Barked;;\ncomposing;23484;composing;Composing;;\nantique-armory;23485;;匠魂盔甲怀古;Antique Armory;\ncataclysm-ignis-soulfires;23486;ignissoulfires;Cataclysm: Ignis Soulfires;;\njust-dyna-things;23487;justdynathings;Just Dyna Things / Just Dire Things addon;;\nmetal-detector;23488;metaldetector;Metal Detector;;\nreplication-addon-tiered;23489;replication_addon_tiers;Replication Addon : Tiered;;\n;23490;schr0cleaver,Schr0sPeelCleaver;Cleaver / Schor'sPeelCleaver;;\nsavethehorse;23492;savethehorse;哦齁齁齁斯;SaveTheHorse;\nhamsters-delight;23493;hamstersdelight;仓鼠乐事;Hamster's Delight;HD\nthe-ultimate-ore-mod;23494;tuom;The Ultimate Ore Mod;;TUOM\nanimal-garden-fennec-fox;23495;animalgarden_fennecfox;Animal Garden - Fennec Fox;;\nanimal-garden-common-raven;23496;animalgarden_commonraven;Animal Garden - Common Raven;;\n;23497;nether_portal_remastered;Nether MASTER Remastered;;\nredstone-recharged;23498;recharged;Redstone: Recharged;;\ntoil-and-trouble;23499;toil_and_trouble;Toil and Trouble;;\nbasicweapons;23500;basicweapons;基础武器;Basic Weapons;\n;23501;whanktrslt;聊天翻译;WhankTrslt;\nyoukaishomecomingcurios;23502;youkaishomecoming_curios;妖怪们的归家：饰品兼容;Youkais Home Coming Curios;\ndrop-force;23503;drop_force;掉落力;Drop Force;\nsacrifice;23504;sacrifice;祭品/掉落不死亡;Sacrifice;\n;23505;ultra_suit_maneuver;奥特装甲·机动;Ultra Suit Maneuver;USM\ngetoutofmyway;23506;tacz_pierce;别挡我的路！;GetOutOfMyWay;GOMW\nstronger-snowballs;23507;strongersnowballs;Stronger Snowballs;;\n;23508;xujiadaibu-mod;更多附魔—易;;YZ\nsimply-swords-epicfight-repair;23509;;简易刀剑|史诗战斗修复;Simply Swords | Epicfight Repair;\nsimply-swords-epicfied;23510;simply_epicfied;简易刀剑 | 史诗战斗;Epic Fight | Simply Swords EpicFied 2;\ncotton-resources;23511;cotton-resources;Cotton Resources;;\n;23512;soulcraft;灵魂工艺;SoulCraft;\ntalents;23513;talents;Talents;;\nrain-rot;23514;rainrot;Rain Rot;;\nlothryas-spell-additiions;23515;lothryaspells;Lothrya's Spell Additions;;\nshershavy;23516;shershavy;谢尔沙维;Shershavy;\ncraycrafting;23517;craycrafting;CrayCrafting;;\n;23518;rain_world_weaponry;Rain World Weaponry;;\ncustomtitlescreen;23519;customtitlescreen;CustomTitleScreen;;\nunusual-prehistory-2;23520;unusual_prehistory;不寻常的史前史2;Unusual Prehistory 2;UP2\nshield-api;23521;shield_api;Shield API;;\nbrick-lib-api;23522;brick_lib_api;Brick Lib API;;BLA\nwhatversioning;23523;whatversioning;申必版本号;WhatVersioning;\nyourender;23524;yourender;完了;YourEnder;\n;23525;mana_jade;以玉定植;Mana Jade;\nvillagerapi;23526;morevillagers;Villager API;;\npeeping-angels;23527;peeping_angels;Peeping Angels;;\nsimple-teleporters-reforged;23528;;Simple Teleporters Reforged;;\nstructures-tweaker;23529;structures_tweaker;Structures Tweaker;;\ntooltip-overhaul;23530;tooltipoverhaul;Tooltip Overhaul;;\n;23531;packetregister;PacketRegister;;\npre-menu-screen;23532;premenuscreen;Pre Menu Screen;;\ntitlescreennotice;23533;titlescreennotice;TitleScreenNotice;;\nspellboundskiescustom;23534;spellboundskies;SpellboundSkiesCustom;;\nkind-of-nice-weapon;23535;kind_of_nice_weapon;Kind of Nice Weapon;;\ncopperized;23536;copperized;Copperized;;\n;23537;mr_enchantments_encore;Enchantments Encore;;\ncomes-alive-reworked;23538;;凡家物语：重制;Comes Alive Reworked;\nbetter-animal-feeding;23539;betteranimalfeeding;Better Animal Feeding;;\ntiny-dragons;23540;tiny_dragons;Tiny Dragons;;\n;23541;only_winter_dream;永冬幻境;Eternal Winter Mirage;EWM\ntightfire;23542;tightfire;Tightfire;;\nwax-item-frames;23543;wax-item-frames;Wax Item Frames;;\nobese-crops;23544;obese_crops;Obese Crops;;\n;23545;frame_changer;Frame Changer;;\n;23546;schr0chastmob;ChastMob;;\n;23547;schr0slingblock;SlingBlock;;\n;23548;schr0chakram;Chakram;;\ntfcgurman;23549;tfc_gurman;TFC Gurman;;\nfungi-delight;23550;fungidelight;Fungi Delight;;\nleaves-youll-go;23551;plcompat;Leaves You'll Go;;\nyagens-attributes;23552;yagens_attributes;Yagen的属性;Yagen's Attributes;\nterrarium-world;23553;terrarium;Terrarium;;\ndaylightchangerstruggle;23554;daylightchangerstruggle;DaylightChangerStruggle;;\n;23555;appliedwebhook;AppliedWebhook;;AWH\n;23556;eclipsestweakeroo;Eclipse's Tweakeroo Additions;;\nutamis-futon;23557;futonsmod;Utami's Futon;;\n;23558;nef;Not Enough Furniture;;NEF\nceruleans-mystical-smithery;23559;ceruleansmithery;Cerulean's Mystical Smithery;;\nmutated-mobs-mod;23560;mutatedmobs;Mutated Mobs Mod;;\nftbxaerocompat;23561;ftbxaerocompat;FTB Chunks x Xaero's Compat;;\naesthetic-windows;23562;aesthetic_windows;Aesthetic Windows;;\nbagels-palette-blocks;23563;paletteblocks;Bagel's Palette Blocks;;\nastrochem;23564;astrochem;AstroChem;;\ndynamic-portals;23565;;Dynamic Portals;;\nheralds-ii;23566;heralds_luna;Heralds II;;\nindustrialized-architecture;23567;i_architecture;Create: Industrialized Architecture;;\nbetter-starting-armors;23568;better_starting_armors;Better Starting Armors;;\n;23569;frostbite_snowbloom;拔刀剑：雪蚀绽霜;SlashBlade: Frostbite Snowbloom;\nraspberry-core;23570;raspberry;Raspberry Core;;\nadvancedcamera-by-lostmortal;23571;advancedcamera;Advancedcamera by LostMortal;;\nclassic-peripherals;23572;classicperipherals;Classic Peripherals;;\nsbm-punt-animal;23573;sbmpuntanimal;[SBM] Punt Animal;;\nsbm-baggable-mobs;23574;baggablemobs;[SBM] Baggable Mobs;;\nsbm-helm-bucket;23575;helmbucket;[SBM] Helm Bucket;;\nanimal-garden-spotted-hyena;23576;animalgarden_spottedhyena;Animal Garden - Spotted Hyena;;\nanimal-garden-western-gorilla;23577;animalgarden_westerngorilla;Animal Garden - Western Gorilla;;\nmore-concrete;23578;moreconcrete;More Concrete;;\nlukis-strongholds;23579;lukis_strongholds,mr_lukis_strongholds;Luki's Strongholds;;\nlukis-woodland-mansions;23580;lukis_woodland_mansions;Luki's Woodland Mansions;;\nthaumic-model;23581;thaumicmodel;Thaumic Model;;\ngtse;23582;gtse;GregTech Simple Extension;;GTSE\nancore;23583;ancore;AnCore Lib;;\nxdonplugins;23584;xdonplugins;XDonPlugins;;XDP\n;23585;capture_entity;实体捕捉;capture_entity;\nsimply-more-epic-fight;23586;;简易刀剑附属 | 史诗战斗;Simply More | Epic Fight;\nexpanded-storage-kots;23587;expandedstorage;扩展存储 - 耕云钓月版;Expanded Storage - KOTS;\nragdolls;23588;ragdolls;Ragdolls;;\nmar-mars-enhanced-ore-variety;23589;enhanced_ore_variety;Mar Mar's Enhanced Ore Variety;;\nsuperflat-progression;23590;superflat-progression;Superflat Progression;;\n;23591;tinkers_origin;始源匠心;Tinker's Origin;\nrenxin-music-player;23592;cp-mod;荏心音乐机;Renxin Music Player;RY\nslotify;23593;slotify;Slotify;;\ntextile;23594;textile;Textile;;\nclash;23595;clash;Clash;;\nmorebatsvariants;23596;morebats;More Better Bats Plus;;\nhxcperipherals;23597;hxcperipherals;HxCPeripherals;;\nyori3os-grappling-hooks;23598;yo_hooks;Yori3o's Grappling Hooks;;\nseasonal-decorations;23599;seasonal_decorations;🎄 季节性装饰;🎄 Seasonal Decorations;\nneon-sentry-keeper;23600;neon_sentry_keeper;Neon Sentry Keeper;;\n;23601;song_of_ending_damage_limiter;终焉颂歌：自定义实体限伤;Song of Ending: Custom Entity Damage Limiter;SE:CEDL\nmaid-please-help-me-forge;23602;maidforge;女仆，请帮我打铁;Maid Please Help Me Forge;\n;23603;Wuppy29_peacefulpackthaumcraftaddon;PeacefulPack Thaumcraft Addon;;\n;23604;SpaceBees;Space Bees;;\nchemcraft-more-backpacks;23605;redgear_morebackpacks;更多林业背包;More Backpacks;\ne-flux;23606;EFlux;E-Flux;;\nthaumcraft-extras;23607;ThaumcraftExtras;神秘扩展;Thaumcraft Extras;TCE\n;23608;IC2Tweaks;IC2 Tweaks;;\npeacefulpack;23609;Wuppy29_Peacefulpack;The Peacefulpack;;\nsulfur-and-nitre;23610;sulfurnitre;Sulfur & Nitre;;\ncombat-evolution;23611;combat_evolution;Combat Evolution;;\nshaderfixerragecraftver;23612;;ShaderFixerRagecraftVer;;\nbrokenentity-0;23613;brokenentity_01;BrokenEntity: 0;;\nthe-cardboard-box-extension;23614;cardboardbox;The Cardboard Box Extension;;\nkubejs-cc;23615;kjscc;KubeJS + CC: Tweaked;;\nsbm-elevators;23616;sbmelevators;[SBM] Elevators;;\nsbm-cacti-bucket;23617;cactibucket;[SBM] Cacti Bucket;;\nsbm-grappling-hook;23618;smbgrapplinghook;[SBM] Grappling Hook;;\nsbm-hand-held-piston;23619;handheldpiston;[SBM] Hand-Held Piston;;\npetting;23620;petting;Petting - Tame any mob!;;\ndatapatched;23621;datapatched;Datapatched;;\nno-villages;23622;novillages;No Villages;;\nhbms-nuclear-tech-leafias-cursed-edition;23623;;HBM's Nuclear Tech - Leafia's Cursed Edition;;\ncell-chemistry;23624;cellchem;Cell Chemistry;;\nae2-all-encompassing;23625;ae2allinone;AE2：包罗万象;AE2: All Encompassing;AE2A\ntiptool;23626;tiptool;TipTool;;\nburrowers;23627;burrowers;Burrowers;;\nskyislands;23628;skylands;Sky Islands;;\nfastflyblockbreaking-reworked;23629;fastflyblockbreak;FastFlyBlockBreaking Reworked;;\naether-ruined-portals;23630;mr_aether_ruinedportal;Aether Ruined Portals;;\n;23631;flame_chase_artifacts;星穹饰品;Flame Chase Artifacts;FCA\n;23632;mobackup;更多背包升级;More Backpack Upgrades;MBU\n;23633;daedalus;代达罗斯附魔;DaedalusEnchantments;\n;23634;anti_player_high_def;终焉颂歌：反玩家高防御;Song of Ending: Anti Player High DEF;SE:APHD\nbetter-lock-on;23635;betterlockon;史诗战斗 - 更好的锁定;Epic Fight - Better Lock On;\nnightfall-invade;23636;nightfall_invade;史诗战斗：夜幕入侵;NightFall : Invade;\n;23637;;Bosses'Rise | 史诗战斗;Bosses'Rise | Epic Fight;\ncopper-friend-backport;23638;copper_friend_backport;铜器时代的小家伙;Copper Friend Backport;\nslab-topographic;23639;slab_topographic;自然台阶;slab topographic;\ntag-editor;23640;tag_editor;贴标签;Tag Editor;\n;23641;fvck;Fvck;;\nomnitool;23642;omnitools;全能工具;OmniTools;\n;23643;magic;织法者;Spellweaver;\n;23644;miztinker;米工匠;miztinker;\n;23645;syncmatia_r;共享原理图增强;Syncmatica Revolution;\nquantumhue-visualizer;23646;quantumhue;量子色域 视界;QuantumHue Visualizer;QH\n;23647;thip_enemy_lucky_blook;特摄英雄补完计划-附属;THIP Expansion;\nsbw-drr;23648;dragonrise_reforge;[SBW]龙之崛起：重铸;DragonRise: Reforge;\nnine-nether-regions;23649;nine_nether_regions;九幽引;Nine-nether Regions;NnR\nhandheld-moon;23650;handheldmoon;掌中明月;Handheld Moon;HHM\ncarpenters-blocks-ce;23651;;木匠方块-社区版;Carpenter's Blocks-CE;\n;23652;;木匠方块 GTNH 版;Carpenter's Blocks - GTNH;\n;23653;yos-coins;Yo's Coins;;\nae-infinity-disk;23654;ae_infinity_disk;AE无限磁盘;AE Infinity Disk;\nsurvivalplus-primer;23655;survivalplus_primer;SurvivalPlus Primer;;\nnorthstar-redux;23656;northstar;机械动力：北极星 - 重制版;Create: Northstar - Redux;\ncreate-igneodynamics;23657;igneo;Create: Igneodynamics;;\ncreate-numismatics-utils;23658;numismatics_utils;Create: Numismatics Utils;;\ncreate-vintage-unofficial-port;23659;createvintageneoforged;Create: Vintage;;\ncreate-bits-n-bobs;23660;bits_n_bobs;Create: Bits 'n' Bobs;;\nccb-tweaks;23661;ccbtweaks;CCB Tweaks;;CCBT\ngregtechceu-modern-extra-unofficial;23662;gtceu_extra;GregTechCEu Modern Extra Unofficial;;\ngeotetraarmor;23663;geotetraarmor;GeoTetraArmor;;\nmore-more-tooltips;23664;more_more_tooltips;More More Tooltips;;\nmore-tooltips;23665;more_tooltips;More Tooltips;;\nelemental-creepers-redux;23666;elementalcreepers;Elemental Creepers Redux;;\nallthecreepers;23667;allthecreepers;All The Creepers;;\nwikiful;23668;wikiful;Wikiful;;\natlatls;23669;atlatl;Atlatls;;\n;23670;item_access_restrictor;物品存取限制器;Item Access Restrictor;\nkaguyafd;23671;kaguya;辉夜姬的五难题：重置;KaguyaFD;\n;23672;antibot;你是人类吗？;AntiBot;\nahh-pvp;23673;pvp;AhhPvP;;PvP\ntoggle-sneak-sprint;23674;togglesneak;Toggle Sneak & Sprint;;\nmonstie-hunter-baby-monster-collector;23675;monstie_hunter;Monstie Hunter: Baby Monster Collector!;;\nenchants;23676;so_many_enchants;So Many Enchants;;\nhubris;23677;hubris;Hubris;;\ncustom-nether-portals;23678;custom_nether_portals;Custom Nether Portals;;\ndamagenumberlegacy;23679;damagenumber;DamageNumberLegacy;;\nhelios-divinity;23680;helios;Helios Divinity;;\nitem-marks;23681;itemmarks;物品标记;Item Marks;IM\nmossylib;23682;mossylib;MossyLib;;\n;23683;giftcodemod;服务器兑换码;GiftCodeMod;\n;23684;srclient;SRClient;;SRC\nbetter-trading-menu;23685;bettertradingmenu;更好的交易菜单;Better Trading Menu;\neasy-npc-config-ui;23686;easy_npc_config_ui;Easy NPC: Config UI;;\nevolved-mekanism-extras;23687;emextras;Evolved Mekanism Extras;;\n;23688;music_hud;Music Hud;;\nbackups;23689;backups;Backups;;\ninventory-particles;23690;inventory_particles;物品栏粒子;Inventory Particles;\nbackport-recipes;23691;backport_recipes;BackPort Recipes;;\nprimal-survival;23692;primal;Primal: Survival;;\nelectrical-age-eln;23693;;Electrical Age - jrddunbr's edition;;\n;23694;showfps;显示FPS;ShowFPS;\nmagia-daemonica;23695;magia-daemonica;Magia Daemonica;;\ndeath-n-taxes;23696;deathntaxes;Death 'n' Taxes;;\nf5-camera-control-f5;23697;summer_xtjl;F5视角控制器;F5 Camera control;F5\nsuper-factory-manager-unofficial-backport-sfm-ub;23698;;Super Factory Manager: Unofficial Backport;;SFM:UB\ncart-complete;23699;metaltransport;Cart Complete;;\nveilings;23700;veilings;Veilings;;\n;23701;your_best_friend_alice_chan;你最好的朋友爱丽丝酱！;Your Best Friend Alice;\nnarutoloading;23702;narutoloading;NarutoLoading;;\nvisual-keymap;23703;visual-keymap;可视化键位图;Visual Keymap;VK\n;23704;unicrawl;统一的爬;UniCrawl;UC\nelytra-chestplate;23705;mr_elytra_chestplate;鞘翅胸甲;Elytra Chestplate;\ngrindstoneplus;23706;grindstone_plus;更好的砂轮;GrindstonePlus;\nanvilcraft-transducers;23707;anvilcrafttransducers;铁砧工艺：并入电网;AnvilCraft Transducers;\nconfluence-weapon-modification;23708;cowm;汇流来世：武器修改;Confluence: Weapon Modification;COWM\nsome-useless-things;23709;useless_mod;无用之物;Some Useless Things;\nhmag-spells;23710;hmagspells;藏妻术式;HMaG Spells;\ntsb-tetra-spell-book;23711;tetra_spell_book;Tetra法术书;Tetra Spell Book;TSB\nlucky-fishing-rod;23712;luckyfishingrod;幸运钓竿;Lucky Fishing Rod;\ngilded-krmor;23713;tnb_gildedarmor;Gilded Krmor;;\n;23714;BloodUtils;Blood Utils;;\ntraces-of-the-fallen;23715;totf;Traces of the Fallen;;TOTF\nlycanite-mobs-additions;23716;lycaniteaddition;Lycanite Mobs Additions;;\nrevmobs;23717;revmobs;RevMobs;;\nkaleidoscope-cookery-refabricated;23718;kaleidoscope_cookery;森罗物语厨房：重织;Kaleidoscope Cookery Refabricated;\ntimelesslib;23719;timelesslib;TimelessLib;;\nnorfrieds-eldritch-takeover;23720;eldritch_takeover;Norfried's Eldritch Takeover;;NET\nminecolonies-modernitzation-update;23721;minecolonies_modernization_update;殖民地现代化更新;Minecolonies Modernization Update;\nsetcraftjs;23722;setcraftjs;套装效果;SetCraftJS;SCJS\nxymultichat;23723;xymultichat;聊天分栏;XYMultiChat;\nall-of-slashblades;23724;allofslashblade;All of SlashBlades;;\n;23725;thaumicenergistics;神秘能源 GTNH 版;Thaumic Energistics - GTNH;\ntexture-locale-redirector;23726;texturelocaleredirector;Texture Locale Redirector;;TLR\neidolon-edoni;23727;eidolon_edoni;幻梦：欢宴;Eidolon : Edoni;\nmekanism-morecapacity;23728;mekanismmorecapacity;Mekanism: MoreCapacity;;\nmodular-machinery-cryogenic-simulation-edition;23729;;Modular Machinery (Cryogenic Simulation Edition);;\nsbm-wooden-rails;23730;woodenrails;[SBM] Wooden Rails;;\nproject-fruit;23731;projectfruit;水果地形;Project Fruit;\n;23732;mcef2;MCEF2;;\ntlm-native-power;23733;native_power_of_maid;车万女仆：原生的力量;TLM: Native POWER;\nhogcraft;23734;hogcraft;Hogcraft;;\nawesome-sheep-swell;23735;awesomesheepswell;Awesome Sheep Swell;;\n;23736;casks;桶;Casks;\nluxurious-misfortunes;23737;luxuriousmisfortunes;Luxurious Misfortunes;;\ntransparent-entities;23738;transparent-entities;Transparent Entities;;\npaintings-n-discs-backport;23739;trickytrialspaintingsplusmusic;Paintings N' Discs Backport;;\nfallen-elves;23740;fallen_elves;Fallen Elves;;\n;23741;pv-addon-flashback;pv-addon-flashback;;\n;23742;betterback;BetterBack;;\nae2fluidcraft-rework-unofficial;23743;ae2fc;AE2流体合成套件重置非官方版;AE2 Fluid Craft Rework Unofficial;AE2FCRU\nannihilation-recreated;23744;mr_annihilation_recreated;Annihilation Recreated;;\nsimple-kelpies;23745;simplekelpies;Simple Kelpies;;\ntacz-turrets;23746;tacz_turrets;TACZ Turrets;;\npoint-blank-official-point-blank-extended-edition;23747;;Point Blank Official || Point Blank Extended Edition Pack;;\nhealth-scoring;23749;healthscoring;Health Scoring;;\nsupearlposition;23750;supearlposition;Supearlposition;;\ndaisy;23751;daisy;Daisy;;\nno-fall-mod;23752;nofalldamage;No fall-damage;;\n;23753;farmersassortment;Farmer's Assortment;;\nfoggy-world;23754;foggyworld;Foggy World;;\namr-bot;23755;;自动化模块机器人;Automatic Modular Robot;AMRbot\nthieves;23756;thieves;Thieves!;;\nwitt;23757;WITT;What Is That Thing;;WITT\ntechrot;23758;techrot;Techrot;;\ncreate-easyfilling;23759;create_easyfilling;Create: EasyFilling;;\nmortar-and-pastels;23760;mortar;Mortar (and Pastels);;\n;23761;delicate_little_garbage;精致小垃圾;Delicate Little Garbage;DLG\nelemental-arsenal;23762;elementalcraft;属性锻造;Elemental Arsenal / ElementalCraft;\n;23763;ae2taczintegration;AE2-TaCZ Integration;;AE2TACZIG\nkubejs-recipes-create;23764;kubejs_recipes_create;KubeJS配方生成;KubeJS Recipes Create;\nhamstercore;23765;hamstercore;仓鼠核心;HamsterCore;HC\nshowercore;23766;showercore;洗浴核心;Shower Core;\nplaysound-pro;23767;summer_xtjl_audio;音频播放专业版;Playsound Pro;\nomnisprint;23768;omnisprint;四向疾跑;OmniSprint;OS\n;23769;totemofluck;幸运图腾;Totem of Luck;\ninfinite-provider;23770;infinite_provider;无限供应器;Infinite Provider;IP\nannuus;23771;annuus;Annuus;;\nmyosotis;23772;myosotis;Myosotis;;\n;23773;;Wanion Lib - GTNH;;\n;23774;;无尽贪婪 GTNH 非官方版;Avaritia Unofficial - GTNH;\n;23775;;无尽收容 / 更多空间箱子 GTNH 版;Avaritiaddons - GTNH;\n;23776;;把未来带回现实 GTNH 版;Et-Futurum-Requiem - GTNH;\n;23777;;匠魂 GTNH 版;Tinkers' Construct - GTNH;TiC-GTNH\n;23778;;开放式电脑 GTNH 版;OpenComputers - GTNH;OC-GTNH\n;23779;;工匠防御 GTNH 版;Tinkers' Defense - GTNH;\nmagistics;23780;magistics;魔法物流;Magistics;\nlittlefix;23781;LittleFix;LittleFix;;\n;23782;mod_Voice,legacyvoicechat,legacy_voice_chat;LegacyVoiceChat;;\nfast-recipe-search;23783;fastrecipesearch;快速配方查找;Fast Recipe Search;\n;23784;;Kill Attack Cooldown;;KAC\nspeech-recognition;23785;mcrecog;Speech Recognition;;\nfaster-block-placement;23786;fasterblockplacement;更快的方块放置;Faster Block Placement;\ninstantsleep;23787;instant_sleep;InstantSleep;;\nchunk-save-fix;23788;chunksavefix;区块保存修复;Chunk Save Fix;\npeacefulsurface;23789;PeacefulSurface,peacefulsurface;PeacefulSurface;;\nmazons-quarks;23790;mazonsquarks;Mazon's Quarks;;\nlucisrenderlib;23791;lucisrenderlib;光 渲染库;LucisRenderLib;LRL\nno-ai-guess;23792;no_ai_guess;禁止 AI 胡扯;No AI Guess;NAG\nitemnamecopier;23793;itemnamecopier;复制物品名称;ItemNameCopier;CN\nping-like-r2;23794;;Ping Like R2;;PLR\nsuper-bosses;23795;superbosses;Super Bosses;;\nslashblade-patchouli;23796;slashblade_patchouli;SlashBlade Patchouli;;\nender-dragon-suit;23797;kgears;Ender Dragon Suit;;\necliptic-seasons-lets-do;23798;es_x_letsdo;Ecliptic Seasons - Let's Do;;\nlets-do-hearth-timber;23799;hearth_and_timber;[Let's Do] Hearth & Timber;;\nimmersive-vehicles-vdeco;23800;vdeco;VDeco;;\nnetherized-kedition;23801;netherized;Netherized Kedition;;\nwizardry-and-fire;23802;wizandfire;Wizardry and Fire;;\npepsied-item-name-recolorer;23803;mr_item_namerecolorer;Pepsi's Item Name Recolorer;;\npepsied-auto-droppers;23804;mr_auto_droppers;Pepsi's Auto Droppers;;\npepsied-copper-item-pipes;23805;mr_copper_itempipes;Pepsi's Copper Item Pipes;;\npepsied-exp-vacuum;23806;mr_exp_vacuum;Pepsi's EXP Vacuum;;\npepsied-pocket-dimension;23807;mr_pocket_dimension;Pepsi's Pocket Dimension;;\npepsied-mining-quarries;23808;mr_mining_quarries;Pepsi's Mining Quarries;;\nsbm-red-cow;23809;redcow;[SBM] Red Cow;;\nfangs-textiles-and-trinkets;23810;textiles_and_trinkets;Fang's Textiles and Trinkets;;\n;23811;neicustomdiagram;NEI Custom Diagram;;\n;23812;;AE2 流体合成套件重置 GTNH 版;AE2 Fluid Crafting Rework - GTNH;AE2FC-GTNH\n;23813;;地幔 GTNH 版;Mantle - GTNH;\nsaurialib;23814;;SauriaLib;;\nriftlibrary;23815;riftlib;RiftLibrary;;\nclean-portals-randomportals-cleanroom-edition;23816;;Clean Portals (Randomportals Cleanroom Edition);;\nspinning-items-displayer-mod;23817;spinningitemsdisplayermod;Spinning Items Displayer;;\nattack-range-attribute;23818;attackrangeattribute;Attack Range Attribute;;\nkryptonfnp-patcher;23819;fnp_patcher;KryptonFNP Patcher;;\nriautomobility;23820;riautomobility;RIAutomobility;;\ncyberware-reforged;23821;cyber_ware_port;Cyberware: Reforged;;\nproject-plastic;23822;projectplastic;Project Plastic;;\nblocky-file;23823;blockyfile;方块文件;Blocky File;\n;23824;entitybinder;生物束缚;EntityBinder;EB\nkaleidoscope-nether;23825;kaleidoscope_nether;森罗物语：下界;Kaleidoscope Nether;\nme-placement-tool;23826;meplacementtool;ME放置工具;ME Placement Tool;MEPT\npepsied-slow-hopper;23827;mr_slow_hopper;Pepsi's Slow Hopper;;\nnocubes-food-desire;23828;food_desire;NoCube's Food Desire;;\n;23829;dense-flowers;Dense Flowers;;\nprehistoric-rift;23830;prift;Prehistoric Rift;;\n;23831;structuretool;结构工具;StructureTool;ST\n;23832;findmyblock;找到我的方块;FindMyBlock;FB\nmore-slashblade-ex-enchantment-effects;23833;more_slashblade_ex_enchantment_effects;超多附魔增效;More SlashBlade Ex Enchantment Effects;MSBEEE\ntinkers-emc;23834;tinkersemc;匠魂EMC;Tinker's EMC;\n;23835;taczcustomdeath;TACZ 高级死亡信息;TACZ Advanced DeathInfo (Kill Banners);\nsummon-totem;23836;summon_totem;召唤图腾;Summon Totem;\ntraditionalcraft;23837;traditional_craft;古法工艺;Traditional Craft;\n;23838;;Ancient Totems;;\nchristmas-music-discs;23839;christmasmusicdiscs;圣诞音乐唱片;Christmas Music Discs;\nfriends-for-life;23840;friendsforlife;Friends For Life;;\n;23841;displaystand;物品展示台;Display Stand;\nwool-pressure-plates;23842;woolplates;羊毛压力板;Wool Pressure Plates;\nprotect-mobs-from-daylight;23843;protectmobsfromdaylight;Protect Mobs From Daylight;;\npepsied-more-professions;23844;mr_more_professions;Pepsi's More Professions;;\npepsied-chaos-core;23845;mr_chaos_core;Pepsi's Chaos Core;;\npepsied-auto-bridger;23846;mr_auto_bridger;Pepsi's Auto Bridger;;\ncustom-sapling;23847;custom_sapling;自定义树苗;Custom Sapling;\nchestlie;23848;chestlie;箱子是个谎言;ChestLie;\npatternprovidersave;23849;patternprovidersave;样板供应器保存;PatternProviderSave;\ncreate-lazytick;23850;createlazytick;机械动力：懒惰刻;Create: LazyTick;CLT\ncreate-sentry-mechanical-arm;23851;sentrymechanicalarm;机械动力：哨戒动力臂;Create: Sentry Mechanical Arm;\njust-enough-fuels;23852;justenoughfuels;JEI匠魂燃料;Just Enough Fuels;\ntrade-refresh-legacy;23853;trade_refresh;交易刷新：旧版;Trade Refresh Legacy;\n;23854;alloysmelterfuelchecker;冶炼炉燃料桶返还;Alloy Smelter Fuel Checker;\naquatic-breeding;23855;fisheggs;水产养殖;Aquatic Breeding;\npack-up;23856;pack_up;场取速拾;Pack Up;\nfox-white-technologies;23857;foxwhitetechnologies;Fox White Technologies;;\nthe-watching-deer;23858;the_watching_deer;The Watching Deer;;\n;23859;itemtagging;职业武器属性;ItemTagging;it\nsketches-blueprints;23860;sblue;Sketches & Blueprints;;\nwhitefoxlib;23861;foxlib;WhiteFoxLib;;\n;23862;prehistoric;前古代优化;Prehistoric;PH\ncompost;23863;compost;Compost;;\n;23864;copper-coins;Copper Coins;;\nbons-odds-n-ends;23865;oddsnends;Bon's Odds N' Ends;;\ni-f-amphithere-mod;23866;amphitheremod;I&F Amphithere Mod;;\n;23867;;植物魔法 GTNH 版;Botania - GTNH;\nunu-civilian-pack-for-mts;23868;unucivil;UNU Civilian Pack;;\nrlcraft-manhunt;23869;rlcraft_manhunt;RLCraft Manhunt;;\nancient-forgemastery;23870;ancient_forgemastery;Ancient Forgemastery;;\nrelentless-undead;23871;relentlessundead;Relentless Undead;;\n;23872;classicfarlands;经典边境之地;Classic Farlands;\nlevelled-mobs-legendary-loot;23873;;等级怪物与传奇战利品;Levelled Mobs & Legendary Loot;LM&LL\nthe-morbid-unreborn;23874;morbid;The Morbid Unreborn;;\n;23875;crop_critters;Crop Critters;;\n;23876;mr_nether_reactor;下界反应器;The Nether Reactor;\nclash-of-cards;23877;clashcards;Clash Of Cards;;\nre-animal;23878;re_animal;Re:Animal;;\ntfc-ruins;23879;tfc_ruins;TFC Ruins;;\n;23880;airraid;Air Raid;;\n;23881;easytp;便捷传送;EasyTp;ET\n;23882;everysingleday;每日运势;Every Single Day;\nstardew-hud;23883;stardewhud;星露谷物语HUD;Stardew HUD;\nenhance-bionics;23884;enhancebionics;增强仿生;Enhance Bionics;\n;23885;;车万女仆：呼唤终结;;\nprofile-cached;23886;profile_cached;Profile Cached;;\ngoety-awaken;23887;goetyawaken;诡厄巫法：觉醒;Goety: Awaken;GA\niss-food-spelling;23888;iss_food_spelling;食施法;ISS: Food Spelling;\none-enough-enchantment;23889;oneenoughenchantment;One Enough Enchantment;;OEE\none-enough-value;23890;oneenoughvalue;One Enough Value;;OEV\n;23891;coordinate-manager;坐标管理器;Coordinate Manager;\n;23892;;红石计划 GTNH 版;ProjectRed - GTNH Fork;\n;23893;legacy-fabric-api-btw;Legacy Fabric API - BTW;;LFAPI-BTW\n;23894;gravestonesfix;墓碑修复;Gravestones Fix;\nwhimsy-deco;23895;whimsy_deco;Whimsy Deco;;\nincendium-translation-fix;23896;incendium_translation_fix;Incendium Translation Fix;;ITF\n;23897;sinfo;SInfo;;\ntarkovcraft-core;23898;tarkovcraft_core;TarkovCraft: Core;;\nsaro-s-road-blocks-mod;23899;saro_s_road_blocks_mod;Saro's Road Blocks;;\nsaro-s-road-signs-mod;23900;saros_road_signs;Saro's Road Signs;;\n;23901;peripheralsplusone;PeripheralsPlusOne;;\nmore-mc-music-discs;23902;morediscs;更多音乐唱片;More Music Discs;\n;23903;mr_terralith_biomesaplings;自定义群系树苗;Custom Biome Saplings;\nastikorcarts-deluxe;23904;;AstikorCarts Deluxe;;\nvillage-spirits;23905;village_spirits;Village Spirits;;\nimmersive-engineering-js;23906;immersive_engineering_js;Immersive Engineering JS;;\nbygone-fortress;23907;bygonefortress;Bygone Fortress;;\n;23908;mr_radii;Radii;;\nextra-shiny;23910;extra_shiny;Extra Shiny;;\npack-cen-header-fix;23911;pchf;PackCENHeaderFix;;PCHF\ntmagriculture;23912;Agriculture;Agriculture;;\n;23913;sheepmod;Sheep Rebellion;;\n;23914;zombie_rush;Zombie Rush+;;\nfoxae2upgrade;23915;foxae2upgrade;FoxAE2Upgrade;;\nwasteland-expanded;23916;;Wasteland Expanded;;\nwakes-reforged;23917;;Wakes Reforged;;\ndank-null-legacy;23918;;/dank/null legacy;;\nsubtle-fix;23919;subtle_fix;Subtle Fix;;\nhyxcate;23920;;宙蚀之刻;Hyxcate (Night/Day Events and Meteors);\n;23921;reach-entity-attributes;Reach Entity Attributes / Reach API;;\nbucket-hat;23923;bucket-hat;Bucket Hat;;\nchristmas-sweaters;23924;christmassweaters;Christmas Sweaters;;\nchristmas-delight;23925;farmers_delight_christmas_editon;Christmas Delight;;\nautumnal-winterly;23926;autumnal_winterly;Autumnal & Winterly;;\nzombie-virus-antidote;23927;cure;Zombie Virus Antidote;;\n;23928;letmespeak;LetMeSpeak;;\notyacraft-engine-renewed;23929;otyacraftengine;Otyacraft Engine Renewed;;\n;23930;ForgeMultipart;ForgeMultipart GTNH 版;ForgeMultipart GTNH Fork;\nopeninventory;23931;openinventory;OpenInventory;;\nzombies-break-glass;23932;zombieglassbreaker;僵尸破坏玻璃;Zombies Break Glass;\n;23933;vintagetag;VintageTag;;\nfloodlights;23934;FloodLights;FloodLights;;\nic2c-additions;23935;ic2caddons,ic2cadditions;IC2C Additions;;\nic2additions;23936;ic2additions;IC2Additions;;\nuranium-big-reactor;23937;uraniumbigreactor;Uranium Big Reactor;;\ncustommachinery-fork;23938;;CustomMachinery (Fork);;\nplayer-animation-library;23939;player_animation_library;Player Animation Library;;\ncnpc-epicfight-integration;23940;cnpcefaddon;CNPC-EpicFight-Integration;;\n;23941;;魔戒：重置版NEI插件;NEI LOTR ReWorked;NEILR\n;23942;papi-extras;Text Placeholder API Extras;;\n;23943;vulcansrevenge,vrm;Vulcan's Revenge;;VRM\ncreate-hamsterwheel;23944;createtreadmill;Create: Hamsterwheel;;\nguerreros-del-mundo-2;23945;guerreros_elementales;Warriors Of World 2;;\ntacz-additions;23946;taczadditions;TaCZ Additions;;\npotion-replacer;23947;potionreplacer;Potion Replacer;;\nhome-dust;23948;homedust;回城之尘;Home Dust;\n;23949;hagds_tpa;HAGD's TPA;;\noeuvre;23950;oeuvre;Oeuvre;;\n;23951;;Hkey_Windows12 的背包;Hkey_Windows12's Backpack addon;HWBPA\n;23952;kaleidoscope_addons;森罗物语：玩偶拓展;Kaleidoscope Addons;\n;23953;bte;藍月光的交通扩展;BML's Transit Expansion;BTE\ntongdarailway-for-forge;23954;tongdarailway_for_forge;通达铁路-for-Forge;TongDaRailway for Forge;TGRF\ncounter-strike-grenades;23955;csgrenades;反恐精英：投掷物;Counter Strike Grenades;\n;23956;tacz_cheat;TaCZ-Cheat;;\nsync-forge;23957;;Sync (Forge);;\n;23958;;Sync Re-Ported;;\nplayer-shells;23959;playershells;玩家躯壳;Player Shells;\ncataclysm-ametyst-crab-temple;23960;cataclysm_amethyst_crab_temple;Cataclysm - Ametyst Crab Temple;;\n;23961;elytrafix;Entity 0 Elytra Fix;;\ngoety-mastery-of-magic-addon;23962;goety_mastery_of_magic;巫法精通;Goety: Mastery of Magic Addon;GM\nmalisis-doors-revived;23963;malisis_doors_revived;Malisis Doors Revived;;\nakuto-engine;23964;akutoengine;Akuto Engine;;\n;23965;;Better Ice;;BI\n;23966;omnicrossbow;OmniCrossbow;;\nknightperipherals;23967;knightperipherals;KnightPeripherals;;\n;23969;thingsmadeeasy;Things Made Easy;;\ntool-trims-mod;23970;;Tool Trims Mod;;\n;23971;classiccaves;经典洞穴;Classic Caves;\njust-parry;23972;just_parry;Just Parry;;\ncolored-light-core;23973;coloredlightscore,easycoloredlights;Colored Light Core;;\n;23974;gtocore;GTOCore;;\nthe-zenith-sword;23975;zenith;天顶剑;The Zenith Sword;\nbetter-f3-reborn;23976;f3hud;Better F3 Reborn;;\nblockcraftery-refurbished;23977;;Blockcraftery: Refurbished;;\nwonderland-jar;23978;the_wonderland;Wonderland.jar;;\nblock-duplicator-tree;23979;duplicatortree;Block Duplicator Tree;;\nthe-enderwoman-mod;23980;enderwoman;The Enderwoman;;\n;23981;healthy_gamer;健康游戏助手;Healthy Gamer Protocol;HGP\n;23982;onlyafleshwound;No Hurt Flash;;\nno-hurt-flash-reforged;23983;nohurtflash;No Hurt Flash (Reforged);;\n;23984;spv_addon;The Volatile Addon;;\n;23985;arknights_originiumarts;起源：明日方舟源石技艺;;\nquincys-plate;23986;quincyplate;Quincy的盘子;Quincy's Plate;\nvisiontoggle;23987;night_vision_keybine;VisionToggle+;;\nregions-unexplored-expansion;23988;ru_expansion;Regions Unexplored: Expansion;;RUEX\ncyberbop;23989;cyberbop;Cyberbop;;\nadorable-dolls;23990;playerdoll;可爱多;Adorable Dolls;AD\n;23991;tpmod;随意传送;No permission tp;\nqliphoth-armaments;23992;qliphoth_armaments;Qliphoth Armaments;;\n;23993;water;水;Water;\ndragon-rings;23994;dragon_rings;龙之赛环;Dragon Rings;\nviscriptshop;23995;viscript_shop;ViScriptShop;;VSS\n;23996;PumpkinPatch;PumpkinPatch;;\n;23997;sciencenotleisure;格雷不休闲;GT Not Leisure;GTNL\n;23998;create_ergonomic;Create：实用功能;Create: Ergonomic;CErg\nmending-gems;23999;mendinggems;Mending Gems;;\nenergy-meters;24000;energymeters;Energy Meters;;\n;24001;;失落的城市Neo1.21自动生成版本;The Lost Cities 1.21.1 fix;\n;24002;forever_love_sword;永爱之刃;ForeverLoveSword;FLS\n;24003;tuning_fork;共振音叉;Tuning Fork;\n;24004;;弩+！;Crossbow Plus!;\nmaid-attributes;24005;maid_attribute;女仆属性;Maid Attributes;MA\n;24006;storageandoneclicksynthesis;存储和一键合成;Storage and One-Click Synthesis;\nrcg-lib;24007;rcg_lib;王城剑兰 Lib;Rcg Lib;RCG\n;24008;crosshairhider;隐藏准星;Crosshair Hider;\nfast-fps-display;24009;fast-fps-display;快速帧率显示;Fast FPS Display;FFD\ncatzilla;24010;catzilla;Catzilla;;\nwy-s-item;24011;wysmod1182;小狼夜瑛的物品;Wolf_Yeying_s_Item;WY\nthebladeofchaos49;24012;the_blade_of_chaos49;混乱处刑;Blade of Chaos;BC\nmaophone-trial-version;24013;maophone_trial;MaoPhone体验版;MaoPhone - Trial Version;\n;24014;expansionpack;塔科夫式背包 / 格子背包;Expansion Pack;\n;24015;floatmenu;服务器3D菜单;Float Menu;FM\nmrmc;24016;mrmc;世界之心;Aether Cores;MrMC\n;24017;;观音土;Guan Yin Tu;GYT\n;24018;arcanevortex;秘法涡流;Arcane Vortex;AV\n;24019;favoriteitems;物品收藏;Favorite Items;FI\ncontactquests;24020;contactquests;往来任务;ContactQuests;CQ\n;24021;worldresetmod;重启世界净化之战;Worldreset;\ncustom-potion-color;24022;custompotioncolor;自定义药水颜色;Custom Potion Color;\nmodular-forcefields;24023;modularforcefields,physicaforcefields;Modular Force Fields Systems;;MFFS\nshinyfood;24024;shinyfood;ShinyFood;;\n;24025;ChatLog,info.varden.chatlog,chatlog;ChatLog;;\nintegrated-lucky-blocks;24026;integrated_lucky_blocks;Integrated Lucky Blocks;;\ndoublingtable;24027;doublingtable;Doubling Table;;\nimmersive-ores;24028;immersiveores;Immersive Ores;;\nspartan-compatibility;24029;spartancompat;Spartan Compatibility;;\nlooters-compass;24030;looters_compass;Looter's Compass;;\n;24031;KM;Kasslim's Mazes;;KM\n;24032;KoadPirates;Koadmaster's Pirates;;\nthe-creep;24033;the_creep;The Creep;;\nbettermineteam;24034;better_mine_team;Better Mine Team;;BMT\nstopwatch;24035;stopwatch;Stopwatch;;\nas-you-wish;24036;as_you_wish;As you Wish;;\n;24037;;Primordial Shores add-on for William Wythers' Overhauled Overworld;;\n;24038;;More Structures - add-on for William Wythers' Overhauled Overworld;;\nclifftree-mod;24039;clifftree;Cliff under a Tree / CliffTree;;\nmarvelous-menagerie-paradoxical;24040;marvelous_menagerie;Marvelous Menagerie: Paradoxical;;\ncallable-horse-compatible;24041;;长骑速驰 · 召之马来兼容版;Callable Horse compatible;\nspiritborne-ascendants;24042;ascendents;Spiritborne Ascendants;;\nrgb-chat-vintage;24043;rgbchat;RGB Chat Vintage;;\n;24044;danzi_mca_tacz;RMCA & TaCZ;;R&T\nbloody-bits-fix;24045;bloodybitsfix;Bloody Bits Fix;;\ndescendants-weaponry;24046;descendants_weaponry;Descendant's Weaponry;;\nchemicalscience;24047;chemicalscience;Chemical Science;;\nhud-control;24048;hudcontrol;HUD 控制;HUD Control;\n;24049;;地牢浮现之时 - 数据包版;Dungeons Arise Datapack;\nmchce;24050;mcheli;MC 直升机：社区版;McHeli: Community Edition;\n;24051;ic2stuff;IC2Stuff;;\neasier-proud-soul-acquisition;24052;easier_proud_soul_acquisition;更容易的耀魂碎片获取;easier proud soul acquisition;\n;24053;slashblade;拔刀剑：再重织;SlashBlade: RE-Refabricated;\nmystical-agriculture-community-edition;24054;;神秘农业社区版;Mystical Agriculture Community Edition;\n;24055;;LazyDFU Reloaded;;\n;24056;blackcard;IAM黑卡;IAMBlackcard;\ntacz-slings;24057;tacslings;[TaCZ] Slings;;\neasydoll;24058;easydoll;简易玩偶;EasyDoll;\n;24059;more_potion;更多药水;More Potion;MP\ngaiatweaks;24060;gaiablossom;GaiaTweaks;;\n;24061;;我痛友灭;;\nluckier-clover;24062;luckier-clover,luckier_clover;更幸运的四叶草;Luckier Clover;LC\n;24063;electroenergetics;机械动力：电力学;Create: Electro Energetics;\n;24064;clearmybackground;Clear My Background;;\nuniversal-trinkets;24065;universal_trinkets;Universal Trinkets;;\nroguelike-tower;24066;roguelike_tower;肉鸽塔;Roguelike tower;Rt\n;24067;;Cavernous;;\nstranger-things-the-upside-down;24068;upsidedown;Stranger Things - The Upside Down;;\n;24069;cavernouslite;Cavernous Lite;;\nprims-stranger-things-mod;24070;prims-upside-down-mod;Prim's Stranger Things;;\n;24071;initial_dimension_fix;初始维度修复;;\nsuper-backpack;24072;super_backpack;超级背包;Super Backpack;SB\n;24073;teleportresurrection;Teleport Resurrection;;TPR\n;24074;leafia;NTM-Cursed-Addon;;\nradiance;24075;radiance;Radiance;;\nxaerolib;24076;xaerolib;XaeroLib;;\n;24077;slashblade_fabric_RE_addons;拔刀剑：再重织附属包;SlashBlade Refabricated RE-Addons;SFRA\numt-universal-metal-tools;24078;universal_metal_tools;通用金属工具;Universal Metal Tools;UMT\nsteel-spin-balls;24079;spinballmod;Steel Spin Balls;;\nbosses-delight;24080;bosses_delight;BOMD乐事;Bosses Delight;\nemptiableboxes;24081;emptiableboxes;清空编辑框;Emptiable Boxes;\ngadgets-n-goodies-mod;24082;gadgetsngoodies;Gadgets n' Goodies Mod;;\nlag-spike-profiler;24083;lagspikeprofiler;Lag Spike Profiler;;\njabelars-moving-light-sources;24084;movinglightsource;jabelar's Moving Light Sources;;\njust-enough-torches;24085;jetorches;Just Enough Torches;;\nquantum-lights;24086;quantumlights;Quantum Lights;;\ncurios-slot-for-ammo-boxes;24087;curiosable_ammo_box;[TaCZ] Curios Slot For Ammo Boxes;;\nlumenized-continued;24088;;流光溢彩延续版;Lumenized Continued;\nthirdpersoncrosshair;24089;tpc;ThirdPersonCrosshair;;TPC\nhiresshot;24090;hiresshot;高清摄影;HiResShot;HRS\n;24091;swappingaffinity;SwappingAffinity;;SA\ndamage-correction;24092;damage_correction;伤害补正;Damage Correction;\nno-fly-zone;24093;noflyzone;No-fly Zone;;\n;24094;avaritianerf;无尽贪婪Neo防熊补丁;AvaritiaNerf;AVN\ntwod-projectiles;24095;twod_projectiles;2D 投掷物 ➵;2D Projectiles ➵;\nmorethermalevaporation;24096;morethermalevaporation;Mekanism: More Thermal Evaporation;;\nautofish-for-tic;24097;autoticfish;自动钓鱼Forge版-匠魂兼容;AutoFish for Tic;\nwtc-wizardterracurios;24098;wizard_terra_cuiros;泰拉饰品拓展;Wizard Terra Curios;WTC\ninvitemmark;24099;invsee;物品标记;InvItemMark;IIM\nbook-of-dragons;24100;bookofdragons;Book of Dragons;;\n;24101;;MrTJPCore - GTNH;;\nnexuslib;24102;nexuslib;NexusLib;;\nformless;24103;formless;无相;Formless;\nstoragebox-adapter;24104;storageboxadapter;StorageBox Adapter;;\nnondirectionaldamagetiltfix;24105;nondirectionaldamagetiltfix;Non-Directional Damage Tilt Fix;;\nmekanism-atmospheric-stabilizer;24106;mekanismatmosphericstabilizer;Mekanism Atmospheric Stabilizer;;\nmekajadeupgrades;24107;mekajadeupgrade;MekaJadeUpgrades;;\nre-avaritia-io-neo;24108;avaritiaio;Avaritia IO [Re/Neo];;\nmore-capacitor;24109;morecapacitor;Ender IO More Capacitor;;\n;24110;easybot;EasyBot;;\n;24111;lexforgefix;LexForgeFix;;\n;24112;aa_mbvd;泥砖菜盘 (逆转裁判);AA-MBVD;\n;24113;infinite_strings;无限刷线;Infinite Strings;InS\n;24114;mr_sea_levelrise;海平面上升;Sea Level Rise;\nthe-wax-gourd;24115;the_wax;东垂瓜;The Wax Gourd;\n;24116;oneinventory;共用背包;One Inventory;OI\n;24117;eternal-firework-rocket;永恒烟花火箭;Eternal Firework Rocket;\narse-ars-extensions;24118;ars_extensions;魔艺拓展;Ars Extensions;ArsE\n;24119;gemstoneengraving;宝石属性;GemAttribute;\nenhancedquarries;24120;enhanced_quarries;增强采石场;Enhanced Quarries;\n;24121;ncmplayer;NCMPlayer;;\n;24122;fakesight;FakeSight;;\nextraloader;24123;extraloader;Extra Loader;;\nskyrims-skeletons-meme;24124;skyrim_skeletons;Skyrims Skeletons Meme;;\npoint-blank-official-destiny-pack;24125;;Point Blank Official || Destiny Pack;;\n;24126;tkze;3K08 (SCP-3008);;\nspacecube;24127;spacecube;空间立方体;Space Cube;\ncinematic-cataclysm;24128;cinematiccataclysm;Cinematic Cataclysm;;\ncall-you-by-your-name;24129;callyou;呼朋引伴/你的名字;Call You By Your Name;CY\n;24130;techutils;Technical Utilities;;\nstoragebox-mod;24131;storagebox;StorageBox Mod;;\nstoragebox-fabric;24132;storagebox;StorageBox for Fabric;;\nextended-tools;24133;extendedtools;Extended Tools;;\ndynamic-electricity;24134;dynamicelectricity;Dynamic Electricity;;\nmaptip;24135;maptip;Maptip;;\nastikorcarts-tfc;24136;;AstikorCarts [Horse Carts] for TFC;;\n;24137;nvidium;Nvidium (NeoForge) UNOFFICIAL;;\nitem-flicker;24138;itemflicker;Item Flicker;;\nvamppatch;24139;vamppatch;VampPatch;;\nspeedy-path;24140;speedypath;Speedy Path;;\n;24141;corelib;Core Lib API;;\n;24142;placeholder-api-expr;Text Placeholder API Expressions;;\niridium-story;24143;iridium;铱物语;Iridium Story;\njei-enhancements;24144;jei_enhancements;JEI Enhancements;;JEIE\n;24145;mr_strong_arms;Strong Arms;;\ndlc-manager;24146;dlc_manager;DLC管理器;DLC Manager;DLCM\n;24147;Ebon;The Ebon Mod;;Ebon\n;24148;map_png;Map.png;;\nbluelib;24149;bluelib;BlueLib;;\nbluedude-dragons;24150;bdd;Bluedude Dragons;;BDD\n;24151;native_nicknames;Native Nicknames;;\n;24153;kaishan_stick;开山棒;KaiShanStick;\nhell-yeah-grass;24154;hyg;Hell Yeah Grass;;\nsmall-stairs119;24155;smallstairs;Small Stairs Updated;;\nnuclear-boiler;24156;nuclearboiler;Nuclear Boiler;;\n;24157;;Too Many Bows|史诗战斗;Too Many Bows Epic Fight;\nkaleidoscope-doll-workshop;24158;kaleidoscope_doll_workshop;森罗物语：玩偶工坊;Kaleidoscope Doll Workshop;\nskybeam-block-revival;24159;skybeam;Skybeam Block Revival;;\nmechanix;24160;mechanix;Mechanix;;\n;24161;LegendGear;LegendGear;;\nfruitydelight;24162;fruitydelight;水果乐事;FruityDelight;\nthe-architects-bypass;24163;architectbypass;The Architect's Bypass;;\nterran-expansion;24164;texp;Terran Expansion;;\nanvilcraft-kubejs;24165;anvilcraft_kubejs;铁砧工艺：KubeJS;AnvilCraft: KubeJS;\nsteam-n-rails-neoforge;24166;;Steam 'n' Rails NeoForge;;\nfoxified-dense-flowers;24167;;Foxified Dense Flowers;;\nchickens-2;24168;chickens;Chickens 2: The Beakquel;;\nender-io-conduit-replacer;24169;enderio-conduit-replacer;Ender IO Conduit Replacer;;\nvalkyrien-logistics;24170;valkyrien_logistics;瓦尔基里：逻辑仪表;Create: Valkyrien Logistics;\nanvilcraft-better-beacons-compatible;24171;anvilcraft_better_beacons_compatible;铁砧工艺：更好的信标兼容;AnvilCraft: BetterBeaconsCompatible;\nanvilcraft-curios;24172;anvilcraft_curios;铁砧工艺：饰品;AnvilCraft: Curios;\nanvilcraft-patchouli;24173;anvilcraft_patchouli;铁砧工艺：帕秋莉手册;AnvilCraft: Patchouli;\n;24174;illusion;幻形;Illusion;\ntnt-duper;24175;tnt_duper;TNT复制器;TNT Duper;\ncreate-storage-neo-forge;24176;;Create: Storage [Neo/Forge];;\ncreate-springs;24177;createsprings;Create: Springs;;\n;24178;;Create More: Parallel Pipes - Fabric;;\nbaby-animals;24179;babyanimals;Baby Animals;;\n;24180;infernum_effugium;Infernum Effugium;;\n;24181;dream_sakura;梦之樱;Dream Sakura;Drk\nworld-reloader;24182;worldreloader;息壤;World Reloader;WR\nascension-mastery;24183;ascension-mastery;飞升：精通;Ascension Mastery;AM\n;24184;astrafix;AstraFix;;\n;24185;skipserverpacks;Skip Server Resource Packs;;\nalexs-mobs-1-21-1-port;24186;;Alex's Mobs (Unofficial Port);;\nalexs-caves-unofficial-port;24187;;Alex's Caves (Unofficial Port);;\nbetterend-neoforge;24188;;BetterEnd NeoForge;;\nbetternether-neoforge;24189;;BetterNether NeoForge;;\n;24190;create_wells;Create Wells;;\nwunderlib;24191;wunderlib;WunderLib;;\nwunderlib-neoforge;24192;;WunderLib NeoForge;;\ncitadel-1-21-1-port;24193;;Citadel (Unofficial Port);;\nbclib-neoforge;24194;;BCLib NeoForge;;\nworldweaver-neoforge;24195;;WorldWeaver NeoForge;;\nmod-menu-neoforge-edition;24196;;模组菜单 (NeoForge 版);Mod Menu (NeoForge Edition);\nbugfixerupper;24197;bug-fixer-upper;BugFixerUpper;;\n;24198;;Create: Storage Refabricated;;\n;24199;;Lively Lily Pads;;\ncritters-delight;24200;critters_delight;Critters Delight;;\n;24201;;上天入地;Until reach the top of the mountain;URTTOTM\nstars-good-life;24202;sgl;星光的养成模组;Stars Good Life;SGL\n;24203;xcz_target_directed;[黎氏科技] 指向靶点;XCZ Target Directed;XCZ\n;24204;diexvcreeper;Diexv苦力怕;DiexvCreeper;\nkaleidoscope-cookery-automation;24205;kaleidoscopecookery_automation;森罗厨房：自动化;Kaleidoscope Cookery: Automation;\nbastion-remnant-renovation-project;24206;bastion_remnant_renovation_project;堡垒遗迹翻新工程;Bastion Remnant Renovation Project;BRRP\n;24207;;区块载入器 - LITW Refined 版;ChickenChunks - LITW Refined;\n;24208;missingtex;贴图缺失;MissingTex;MT\n;24209;wither_music;凋灵音乐;Wither Music;\ncompostbin;24210;compostbin;堆肥箱;Compostbin;\nanvilcraft-ponder;24211;anvilcraft_ponder;铁砧工艺：思索;AnvilCraft: Ponder;\nanvilcraft-guideme;24212;anvilcraft_guideme;铁砧工艺：GuideME;AnvilCraft: GuideME;\nanvilcraft-delta;24213;anvilcraft_delta;铁砧工艺：三角化;AnvilCraft: Delta;\nanvilcraft-lite;24214;anvilcraft_lite;铁砧工艺：青春版;AnvilCraft: Lite;\n;24215;emosupertemplate;Emo的超级样板终端;Emo Super Template;\nme-proxy;24216;meproxy;ME Proxy;;\nkubejs-oritech-kjs;24217;kubejs_oritech;KubeJS Oritech;;\n;24218;forgeautoshutdown;AutoShutdown;;ASD\n;24219;oredetector;矿石探测器;Ore Detector;\nenchanted-wands-tomes;24220;enchanted_wands_tomes;Enchanted: Wands & Tomes;;\n;24221;;机械动力：咖啡馆 (Fabric);Create Cafe Fabric;\ncreatejeicompat;24222;createjeicompat;Create JEI Compat;;\njeiores;24223;jei_ores;JEI Ores;;\nenhanced-mob-variants;24224;astryxions_mob_variants;Enhanced Mob Variants;;\nsquickens;24225;eyeq_squicken;Squickens;;\nadditional-small-stairs;24226;additionalsmallstairs;Additional Small Stairs;;\n;24227;golem_dandori;Golem Dandori;;\n;24228;private-horses;Private Horses;;\n;24229;dropbar;DropBar;;\n;24230;vanilla-outsider-better-dogs;VO: Better Dogs;;\n;24231;oceansdelight-port;Ocean's Delight (Polymer Port);;\n;24232;storagedelight-patch;Polymer Patch for Storage Delight;;\n;24233;cratedelight-patch;Polymer Patch for Crate Delight;;\n;24234;moredelight-patch;Polymer Patch for More Delight;;\n;24235;spanishdelight-patch;Polymer Patch for Spanish Delight;;\n;24236;choppers-delight-port;Chopper's Delight (Polymer port);;\n;24237;frightsdelight-patch;Polymer Patch for Fright's Delight;;\n;24238;ubesdelightpatch;Polymer Patch for Ube's Delight;;\n;24239;farmers-delight-polymer-patch;Polymer Patch for Farmer's Delight;;\nrepair-box;24240;repair_box;Repair Box;;\n;24241;dummmmmmy;试验假人非官方移植版;MmmMmmMmmMmm / Target Dummy;\n;24242;outofsightoutofmind;Out Of Sight Out Of Mind;;\n;24243;channelchat;聊天频道;ChannelChat;CH\nomocha-no-shugosha;24244;uut;Omocha no Shugosha / おもちゃの守護者;;UUT\nspice-of-life-maid;24245;solmaid;生活调味料：酒狐萝贝;Spice of Life: Maid;\nbbsdbz-mobs;24246;bbsdbz_mod;BBSDBZ Mobs;;BZ\nonlylooking-updated;24247;onlylooking;OnlyLooking Updated;;\nvelocity-blur;24248;velocityblur;Velocity Blur;;\n;24249;keepmymodmenuscroll;KeepMyModMenuScroll;;KMMMS\nrpg-style-more-bosses-early;24250;rpg_style_more_dungeons;RPG Style More Bosses: Early;;\n;24251;;更多唱片;More Records;MR\nancient-city-renovation-project;24252;ancient_city_renovation_project;远古城市翻新工程;Ancient City Renovation Project;ACRP\nwarship-arise;24253;warshiparise;战舰崛起之时;Warship Arise;\n;24254;vs_tfc_compat_fix;瓦尔基里X群峦传说兼容;Valkrien Skies TFC Compat Fix;VSTFC\nprometheus;24255;prometheus;普罗米修斯;Prometheus;\ndynamic-registrar;24256;dynamicregistrar;动态注册;Dynamic Registrar;\n;24257;blue_oceans;蔚蓝之海;BlueOceans;BO\none-click-one-block-legacy;24258;oneclickoneblock;点到为止：遗产;One Click One Block Legacy;1C1BL\n;24259;alwaysallowtp;始终允许传送;AlwaysAllowTP;\n;24260;maidbeacon;车万女仆：移动信标;Touhou Little Maid: Beacon;\nluyubaubles;24261;luyubaubles;语录签名;LuyuBaubles;\nanvilcraft-kaleidoscope;24262;anvilcraft_kaleidoscope;铁砧工艺：森罗物语;AnvilCraft: Kaleidoscope;\nmodular-bees;24263;modularbees;模块化蜜蜂;Modular Bees;\nmonster-tinker;24264;tinkers_monster;怪物工匠;Monster Tinker;\nforever-babies;24265;foreverbabies;稚子长留;Forever Babies;\nmaingraph;24266;mgmc;麦块主图;Maingraph for MC;MGMC\ncommand-block-ui-overhaul;24267;commandblockuioverhaul;命令方块界面重构;CommandBlockUIOverhaul;\n;24268;chaintogether;链在一起;ChainTogether;\n;24269;glowmyblocks;光耀千万块;GowingBlockOutlines;\n;24270;ziptoit;Zip To It!;;\n;24271;soundproofmod;我想静静;Soundproof Mod;SPM\nfork-twilightforest-thread-safety-addon-for-forge;24272;tfthreadsafetyaddon;暮色森林线程修复 (Forge);Twilight Forest Thread Safety Addon Fork for Forge;TFTSF\n;24273;homura_lightning_enchantment;Homura的闪电附魔;Homura Lightning Enchantment;\n;24274;homura_lifesteal_enchantment;Homura的吸血附魔;Homura Life Steal Enchantment;\neasy-auto-cycler;24275;easyautocycler;Easy Auto Cycler;;\n;24276;WaslieCore;WaslieCore;;\nf34;24277;f34;F34;;\nbiome-hud;24278;biomehud;Biome HUD;;\none-stack;24279;onestack;One Stack;;\nanimights;24280;animights;Animights;;\n;24281;801-Library;801-Library;;\nctnh-core;24282;ctnhcore;CTNH Core;;\ndebugify-reforged;24283;debugify;Debugify Reforged;;\n;24284;ic2_extra_fuels;IC2 Extra Fuels;;\n;24285;lotrkingsaddon;Lord of the Rings Kings Addon;;\n;24286;mirage_gfbs;G.F.B.S.-Mirage;;GFBS\nae2-tangible-bookmarks;24287;ae2tangilblebookmarks;AE2 Tangible Bookmarks;;\ndeadbyskillcheck;24288;skillcheckmod;DeadBySkillcheck;;\nstormies-spiders;24289;stormiespiders;Stormie's Spiders;;\nsaints-dragons;24290;saintsdragons;Saint's Dragons;;\n;24291;compactkineticgenerators;压缩动能发电机;Compact Kinetic Generators;\n;24292;GemBlocksForGreg;GemBlocksForGreg;;\nbuildtech;24293;buildtech;建筑科技;BuildTech;\nforge-multipart-integration-fmpi;24294;fmpintegration;Forge Multipart Integration;;FMPI\nchemlab;24295;hoelib,chemlab;ChemLab;;\n;24296;gik;Grand Indestructible Kingdom;;GIK\n;24297;hiddenworldborder;隐藏的世界边界;Hidden World Border;\ncraftingtable-iv;24298;craftingtableiv;智能工作台4;CraftingTable IV;\n;24299;thip;特摄英雄补完计划-进化;Tokusatsu Hero Instrumentality Project-Evolution;THIP-Evo\nbuddy-drones;24300;drones;Buddy Drones;;\n;24301;tlskin;TLSkin;;\nshels-minechat;24302;mine_chat;Shel's MineChat;;\nlegacy-gamemode-switcher;24303;legacy_gm_switcher;Legacy F3-F4 Game Mode Switcher;;\n;24304;borukva-food-exotic;Borukva Food Exotic;;\nbetterblockz;24305;betterblockz;BetterBlockZ;;\npackagedtacz;24306;packagedtacz;封包TaCZ;PackagedTacz;\nlotas-light;24307;lotaslight;LoTAS-Light;;\nbetter-keepinventory;24309;better_keepinventory;Better Keepinventory;;BKI\nmob-locator;24310;moblocator;Mob Locator;;\n;24311;endersuitsmod;末影套装;Ender Suits;\nzoesteria;24312;valoegheses_be;Zoesteria;;\n;24313;mod_NephsMissingBlocks;消失方块;Missing Blocks;\n;24314;randomoptimization;随意优化;Random Optimization;RO\nembergetic-expansion-embers-rekindled;24315;embers_extended;Embergetic Expansion;;\nirons-spell-books-arcane-essence-blocks;24316;arcaneessenceblock;Iron's Spell Books: Arcane Essence Blocks;;\nvampirism-irons-spells-compatibility;24317;vampire_spells_addon;Vampirism Iron's Spells Compatibility;;\nunofficial-irons-spells-n-spellbooks-skill-tree;24318;irons_spells_skill_tree;Unofficial Iron's Spells 'n Spellbooks Skill Tree;;\n;24319;zamega;ZA超级进化附属包;[Cobblemon] Navas' ZAmega;\n;24320;;魔戒工艺;Lord Of The Rings Craft;\nlord-of-the-rings-craft-remastered;24321;;魔戒工艺：重制版;Lord Of The Rings Craft Remastered;\nitem-rarity;24322;item_rarity;万象棱镜;Item Rarity;IR\nforgesoul;24323;forgesoul;魂锻;ForgeSoul;\nzhipeiweidu;24324;zhipei_dimensions;我的支配维度;Zhipeiweidu;\ntfc-sophisticated-backpacks-upgrade;24325;tfcsbu;群峦精妙背包升级;TFC Sophisticated Backpacks Upgrade;\nexplosives-plus-plus;24326;explosives_plus_plus_mod;Explosives Plus Plus;;\nshape-shifter-curse-addon;24327;ssc-addon;幻形者诅咒扩展包;Shape Shifter Curse Addon;\nmine-trigger;24328;world_trigger;Mine Trigger;;\npower-converters3;24329;PowerConverters;能源转换3;Power Converters3;\n;24330;stickerbomb;StickerBomb!;;\n;24331;unlockedcamera;Unlocked camera;;\nstygian-end-recontinuation;24332;stygian;Stygian End ReContinuation;;\nundead-variants;24333;undeadvariants;Undead Variants;;\nclone-items;24334;cloneitemsfabric,cloneitems,cloneitemsforge;克隆物品;CloneItems;\nmore-compostables;24335;more-compostables;More Compostables;;\nlegacy-duplicator-mod;24336;legacyduplicatormod;Legacy Duplicator;;\nftbquest-slot-rewards;24337;ftbquest_slot_rewards;FTB Quest Slot Rewards;;\n;24338;locateo;locateo;;LO\n;24339;carpet_gugu_addition;Carpet GUGU Addition;;\n;24340;be_a_doll;Would you be a doll?;;\n;24341;missingi18n;我居然忘记翻译了;Missing i18n;MI18N\n;24342;mr_add_morerecipes;添加更多配方;Add more recipes;\nguardian-chests;24343;guardianchest;守护者之箱;Guardian Chests;\nindustrial-wires-classic;24344;;工业线缆经典版;Industrial Wires Classic;\nindustrial-wires-reengineered;24345;;Industrial Wires ReEngineered;;\n;24346;additionalentries;宝石词条/重铸;Additional Entries;\n;24347;super-stealth-mod;万物隐藏;;\n;24348;pickup_rules;物品拾取规则;Pick Up Rules;\n;24349;hidehitbox;HideHitbox;;\nae2-crystal-science;24350;ae2cs;应用能源：水晶科技;AE2 Crystal Science;AE2CS\npc-compat;24351;pccompat;PC Compat;;\n;24352;ExplosivesPlusLite,mod_ExplosivesPlus;Explosives+;;\ntaov-core;24353;taov_core;瓦基大冒险核心;TAOV Core;\nnatural-philosophy;24354;naturalphilosophy;Natural Philosophy;;\nmonster-spellbooks;24355;monsterspellbooks;Monster & Spellbooks;;\nadvanced-arsenal;24356;advanced_arsenal;Advanced Arsenal;;\nfirst-person-view;24357;firstpersonmod;First Person View;;\neat-whenever-you-want;24359;eat_whenever_you_want;Eat Whenever You Want;;EWYW\n;24360;mr_ketkets_playershops;玩家商店;Player Shops;\nvisiting-villagers;24362;visiting-villagers;Visiting Villagers – Bring Your Villages to Life!;;\nloot-beams-refork;24363;lootbeams;战利品光束：Refork;Loot Beams: Refork;\n;24365;mr_ketkets_displaycases;Display Cases;;\ntoo-many-tinkers;24366;toomanytinkers;TmT 匠魂材质修复;Too Many Tinkers;TmT\nrosettaenchanttable;24367;rosettaenchanttable;RosettaEnchantTable;;\n;24368;thehard;这是什么东西?;Huh?What Is This;\n;24369;akatzumapick;汤姆的简易存储附属;AkatZumaPick;\nspice-of-life-maids-dream;24370;solmaiddream;生活调味料：女仆之梦;Spice of Life: Maids' Dream;\nimmersive-optimization-edition;24371;immersive_optimization;Immersive Optimization Edition;;\nshoulder-surfing-irons-spells-integration;24372;shoulder_surfing_irons_spells_integration;Shoulder Surfing: Iron's Spells Integration;;\ntf-storage;24373;tfstorage;TF存储;TF Storage;\nplayeraffixes;24374;playeraffixes;玩家天赋;PlayerAffixes;pa\nwild-mobs-mod;24375;wildmobsmod;野生生物;Wild Mobs;\nchest-cavity-beyond;24376;chestcavitybeyond;Chest Cavity Beyond;;CCB\nloading-protection;24377;loadingprotection;加载中保护;Loading Protection;\n;24378;somethoughts;一些想法;Somethoughts;ST\n;24379;forgeautoshutdown;ForgeAutoShutdown;;\nlets-do-lilis-pottery;24380;lilis_pottery;[Let's Do] Lili's Pottery;;\nhotbarlock-reborn;24381;hotbarlock;HotbarLock Reborn;;\ncarry-me;24382;carryme;Carry Me;;\nae-tetra;24383;ae_tetra;AE Tetra;;\nimproved-botania-pools;24384;improved_botania_pools;Improved Botania Pools;;\nimproved-botania-sparks;24385;improved_botania_sparks;Improved Botania Sparks;;\nimproved-botania-spreaders;24386;improved_botania_spreaders;Improved Botania Spreaders;;\nftb-quests-lang-splitter;24387;ftbquestslangsplitter;Quests Lang Splitter;;\ncircumnavigate;24388;circumnavigate;Circumnavigate;;\nsculkandjaw;24389;ycpk;幽匿与钳合;Sculk And Jaw;\nraxiores-of-raxiore;24390;ror;Raxiores Of Raxiore;;ROR\n;24391;AntiPlantVirusMod,mod_AntiPlantVirus;森林破坏 / 虫MOD;Anti Plant Virus;\nfsang18s-heropack;24392;fsang;FSang18's Heroes;;\nponder-for-kaleidoscope-cookery;24393;ponderforkc;森罗厨房：思索;Ponder For KaleidoscopeCookery;\n;24394;sanity;Sanity Neo;;\nsomnia-ce;24395;somnia;梦醒时分：社区版;Somnia: Community Edition;\nvs-hose-connectors;24396;vsfluidlink;VS Hose Connectors;;\nbeautifultnts;24397;beautifultnts;BeautifulTNTs;;\ncreate-rubberworks;24398;rubberworks;Create: Rubberworks;;\ncreate-utilities-j;24399;createutilities;机械动力：实用物品 J版;Create Utilities J;\ncreate-item-drawers;24400;create_item_drawers;机械动力：物品抽屉;Create: Item Drawers;\nglass-remastered;24401;glassier;G.L.A.S.S Remastered;;\nthe-rpg-inventory-and-classarmor-mod;24402;rpginventory;RPG Inventory / The Rpg Inventory and ClassArmor Mod;;\ndragonforged;24403;dragonforged;Dragonforged;;\n;24404;fumi-fabric,fumi_neoforge;Fumi;;\nnebulalib;24405;nebulalib;Nebula: Lib;;\nwunderlib-forge;24406;;WunderLib Forge;;\nbclib-forge;24407;;BCLib Forge;;\nundertale-death-screen;24408;undertale_death_screen;Undertale Death Screen;;\nskibidi-toilet-skibidi-doom-craft;24409;skibidi_doom_craft;Skibidi Toilet - Skibidi Doom Craft;;\nroberts-tooltips;24410;roberts_tooltips;Robert的物品提示;Robert's Tooltips;RT\n;24411;chunk-debug;区块调试;ChunkDebug;\nbetternether-forge;24412;;BetterNether Forge;;\nbetterend-forge;24413;;BetterEnd Forge;;\nspitfire-throwable-fire-charges;24414;spitfire;Spitfire: Throwable Fire Charges;;\nlava-chicken-boss;24415;lavachickenboss;Lava Chicken Boss;;\nbetter-water-breathing;24416;better_water_breathing;Better Water Breathing;;\nbrick-break-pickaxe-cracking;24417;brick_break;Brick Break (Pickaxe Cracking);;\ncrawling-chaos;24418;crawling-chaos;Crawling Chaos;;\nsearchcarefully;24419;searchcarefully;仔细搜刮;Search Carefully;\nscape-and-run-parasites-halloween;24420;srphalloween;Scape and Run: Parasites Halloween;;SRPH\n;24421;slashblade.yukari;结月刀改;Yukari;\n;24422;flammpfeil.slashblade.terra;大地之刃;TerraBlade;\n;24423;flammpfeil.slashblade.kirisaya;无神无刃;Kirisaya;\n;24424;flammpfeil.slashblade.Kamuy;神威刀;Kamuy;\n;24425;flammpfeil.slashblade.slasharts;刀之艺术;Slash Arts;\n;24426;flammpfeil.slashblade.wanderer;风来剑;Blade of Wanderer;\n;24427;flammpfeil.slashblade.blademaster;剑圣之刃;Blademaster;\n;24428;flammpfeil.murasamablade;千鹤村雨;MURASAMA Blade;\n;24429;flammpfeil.slashbladewa;和风拔刀剑;SlashBladeWa;\n;24430;SlashBladeTemplate;刀剑定制;SlashBladeTemplate;\n;24431;flammpfeil.slashblade.darkraven;暗鸦;DarkRaven;\n;24432;flammpfeil.fluorescentbar;荧光;FluorescentBar;\nct;24433;ctpp;CT++;;\n;24434;FZAddons;Factorization Addons;;FZAddons\n;24435;FactumOpus;Factum Opus;;\n;24436;rarityglow;RarityGlow;;\n;24437;mekanismdelight;谁说锯木机只能锯木头的？都能切西瓜了切菜也没问题！;MekanismXDelightXKaleidoscopeXCompat;MekD\nrezero-return-by-death;24438;iseng;Return by Death;;\nic2xuumatter;24439;ic2xuumatter;UU物质查看器;IC2X UU-Matter;\nbeta-plus;24440;betaplus,beta_plus;Beta+;;\n;24441;gl_ass;GLass;;\n;24442;huazhou__chinese_armor;华胄;HuaZhou : Chinese Armor;HZ\n;24443;betterarrow;更好的箭矢;BetterArrow;\nillusionist-grimoire;24444;illusionist_grimoire;Illusionist Grimoire;;\nmygo-mine-fargo;24445;mine_fargo;我的魂石;Mine Fargo;MyGo\n;24446;mr_ban_bat;禁用蝙蝠;Ban Bat;\nore-spawn-confusion;24447;ore-spawn-confusion;矿异位;Ore Spawn Confusion;\n;24448;scombridization;局在之鲭;Scombridization;SCBI\nlognore;24449;lognore;Lognore;;\ncreate-harmonics;24450;createharmonics;机械动力：和声学;Create: Harmonics;\n;24451;unfriendly-creature-mod;不友好的生物;Unfriendly Creature;\n;24452;gd656peek;GD656战术侧头;GD656Peek;PE\n;24453;OneBlockMan;One Block Man;;\nneo-bee-fix;24454;neobeefix;Neo Bee Fix;;\nitemlod;24455;itemlod;ItemLOD;;\nonysd-guns;24456;;Onysd Guns;;\ntinkers-arsenal-reloaded;24457;tinkersarsenal;Tinkers' Arsenal Reloaded;;\nfluxzoom;24458;fluxzoom;FluxZoom;;\nzoomx;24459;zoomx;ZoomX;;\ngrim-kingdoms-lost-structures-ruins;24460;mr_grim_kingdomsloststructuresruins;Grim Kingdoms: Lost Structures & Ruins;;\nvanillaconfig;24461;vanillaconfig;VanillaConfig;;\n;24462;spatialaudio;SpatialAudio;;SA\nawaken-gateway-entity;24463;awaken_gateway_entity;轻轻敲响沉睡的心灵;Awaken Gateway Entity;AGE\n;24464;koishicompat;Koishi 的通用补丁;Koishi's Universal Compat;\n;24465;mccourse;社交相册;;\n;24466;lanlans_brock_moon;坠月;Falling Moons;\n;24467;qiuymmod;Qinym Mod;;QM\n;24468;orelocator;矿石定位器;OreLocator Compass;\n;24469;logfilter;日志过滤器;Log Filter;\nicustommenu;24470;icustommenu;ICustomMenu;;icm\n;24471;;高亮信息拓展 GTNH 版;What am I looking at - GTNH;Waila-GTNH\n;24472;;Wawla 高亮显示 GTNH 版;What Are We Looking At - GTNH;Wawla-GTNH\n;24473;;Waila Harvestability - GTNH;;\n;24474;;Waila 插件 GTNH 版;Waila Plugins - GTNH;\n;24475;;开放式方块 GTNH 版;OpenBlocks - GTNH;\n;24476;;开放式模组库 GTNH 版;OpenModsLib - GTNH;\n;24477;;更好的建筑之杖 GTNH 版;Better Builder's Wands - GTNH;\n;24478;;NEI 扩充 GTNH 版;NEI Addons - GTNH;\n;24479;;NEI Integration - GTNH;;\n;24480;;林业 GTNH 版;Forestry - GTNH;\n;24481;configurationsbackport;ConfigurationsBackport;;\n;24482;;育蜂指南 GTNH 版;BeeBetterAtBees - GTNH;\nday-counter-plus;24483;daycounterplus;Day Counter Plus;;\nspore-underground-bunker-spawner-compat;24484;mr_sporeunderground_bunkerspawnercompat;Spore&Underground Bunker Spawner Compat;;\n;24485;jta;British Transport Addon;;BTA\nsneak-push;24486;sneakpush;Sneak Push;;\nbalanced-unbreakable;24487;balancedunbreakable;Balanced Unbreakable;;\nsuper-mob-tracker;24488;supermobtracker;超级生物追踪器;Super Mob Tracker;\neverything-nunchaku;24489;everythingnunchaku;Everything Nunchaku;;\ncell-terminal;24490;cellterminal;元件终端;Cell Terminal;\nic2-reinforced-stone;24491;rstone_mod;工业防爆石;IC2 Reinforced Stone;\nmilkcarton;24492;milkcarton;牛奶盒;Milkcarton;\n;24493;tpi;Tpi-Mod;;TPI\nic2cuumatter;24494;ic2cuumatte;UU物质查找器;IC2C UU-Matter;\nlimitless-vehicle;24495;ywzj_vehicle;永无止境: 载具;Limitless Vehicle;YWZJ\nbeautify-refoxed;24496;beautify;美化！(NeoForge);Beautify: ReFoxed;\naecapfix;24497;aecapfix;AECapFix;;\nlcompat;24498;lcompat;LCompat;;\nraritycore;24499;raritycore;稀有度核心;RarityCore;\nsimply-cozy;24500;simplycozymod;Simply Cozy;;\nsouls-like-universe-weapons-armors;24501;slu;souls like universe: weapon & armor & boss;;SLU\ndo-me-a-favor;24502;do_me_a_favor;Do Me a Favor;;DMAF\ncave-dweller-evolved;24503;;Cave Dweller Evolved;;\nobfuscated-kamussy;24504;horror_responses_forge_;Obfuscated - Horror Chat Responder;;\npoison-madness;24505;poison_madness;Poison Madness;;\n;24506;emotechtree;EMO科技树;EMCTechCore;\ncombatlog;24507;combatlog;Combatlog;;\nmarvelous-species;24508;marvelousspecies;Marvelous Species;;\n;24509;blink;Blinkeye;;\nsporeadds;24510;sporeadds;Sporeadds;;\nlord-of-the-mysteries-craft;24511;lotm;Lord of the Mysteries Craft;;LOTMC\nclassic-nether;24512;;Classic Nether;;\nweather2-remaster-addon-weather-computers-and;24513;weather_broadcasting;[OC and Weather 2 Remastered addon] Weather, Computers and Broadcast;;\n;24514;;ParticleCulling 1.8.9 port;;\n;24515;;RenderLib 1.8.9 port;;\n;24516;;Nothirium 1.8.9 port;;\n;24517;;EntityCulling 1.8.9 port;;\n;24518;;末影动物园 GTNH 版;EnderZoo - GTNH;\noxygen-mod;24519;oxygen;Oxygen Mod;;\n;24520;dispenspear;矛发射器;Dispenspear;\nqianmospeedmod;24521;qianmospeed;阡陌疾旅;Qianmo Speed;QS\nfunky-fluids;24522;funkyfluids;Funky Fluids;;\n;24523;kerencysystem;Kerency Addon;;\nbrainier-bees;24524;brainierbees;Brainier Bees;;\nrrv;24525;rrv;Reliable Recipe Viewer;;RRV\nhopper-golden-edition;24526;;Hopper Golden Edition;;\nae2-powertools;24527;ae2powertools;AE2 PowerTools;;\nsimple-sleep-vote;24528;simplesleepvote;Simple Sleep Vote;;\nscape-and-run-parasites-aether;24529;srpaether;Scape and Run: Parasites Aether;;\nsanta-logistics;24530;santa_logistics;Create: Santa Logistics;;\n;24531;more-things;More Things;;MS\n;24532;ender_online;末影联机;Ender Online;EO\neasy-survival-plus;24533;easysurvivalmod;Easy Survival Mod;;ESM\n;24534;rs_disk_move;RS Disk Move;;RDM\n;24535;lwwwwmod;霓闪感染：被放逐的神迹;Neon Glitch: Exiled Divinity;\n;24536;soup_pot_texture_expansion;森罗厨房：乐此不疲;Kaleidoscope Texture Expansion;\nlegendary-additions;24537;legendary_additions;Legendary Additions;;\ntacz-arcana-timeless-and-classics-guns;24538;taczexpands;TaCZ: Arcana;;\n;24539;craftle;CRAFTLE!;;\nmebahels-creatures-draugr;24540;mebahel_creatures_draugr;Mebahel 的生物 - 尸鬼;Mebahel's Creatures - Draugr Invasion;\n;24541;infslime;无限增值史莱姆;InfSlime;\n;24542;chillrainrandomideas;ChillRain随想;Chill_Rain Random Ideas;\ndgmodules;24543;dgmodules;DG 模块;DG Modules;\n;24544;customvirtualvein;虚拟矿脉;Custom Virtual Vein;CVN\ncurvebuilding;24545;curvebuilding;CurveBuilding;;\nminemenu-no-shift-conflict-patch;24546;;我的菜单 - 非官方按键冲突优化补丁;MineMenu: No-Shift-Conflict Patch;\n;24547;;大别墅 - 批量刷怪笼编辑器;Dialog Batch Spawner Utils;DaBsu\n;24548;chestlooteditor;自定义箱子战利品;ChestLootEditor;\n;24549;repetphrase;口癖重制版;Re: PetPhrase;RPP\ntinkers-ancient-tools-rebuilding;24550;tinkers_atr;工匠：古物重建;Tinkers: Ancient Tools Rebuilding;TATR\nmace-enchantments;24551;mace_enchantments_plus;Mace Enchantments+;;\nmasterful-machinery-fork;24552;;Masterful Machinery Fork;;\nenchantment-limiter-fork;24553;;Enchantment Limiter Fork;;\ncobblemonraiddens;24554;cobblemonraiddens;Cobblemon Raid Dens;;CRD\n;24555;terrain-diffusion-mc;Terrain Diffusion Mod;;\nheartmod;24556;boihm;Binding of Isaac Heart Mod;;\npokecube-legacy;24557;;Pokecube Legacy;;\nseasons-mc;24558;seasonalmod;Seasons MC;;\nimmersive-cinematics;24559;immersive_cinematics;Immersive Cinematics;;\nakashic-goggles;24560;akashic_goggles;Akashic Goggles;;\n;24561;flammpfeil.slashblade.anvilenchant;铁砧附魔;AnvilEnchant;\n;24562;voxelmenu;体素菜单;VoxelMenu;\nthe-joke-books;24563;thejokebook;The Joke Books;;TJB\n;24564;endermanneutral;苦力怕中立;Creeper Neutral;\nchainsaw-man-devilcraft;24565;csmod;Chainsaw Man: DevilCraft;;\n;24566;blockgiveeffects;方块附加药水效果;BlockGiveEffects;BGE\n;24567;illageart;灾厄艺术;Illage Art;IA\ncrd-broadcasts;24568;crd_broadcasts;CRD Broadcasts;;\ncrd-broadcasts;24569;crd_converters;CRD Converters;;\n;24570;createblockchain;Create: Blockchain;;\nmiscellaneous-metals;24571;mcore;Miscellaneous Metals / Marbled's Core;;MCORE\nowen233666s-windows;24572;owenswindows;owen233666's windows;;owenswindows\ninfinity-pattern-provider;24573;infinitypatternprovider;无限样板供应器;Infinity Pattern Provider;\n;24574;carpet-sdk-addition;Carpet SDK Additon;;CSDK\n;24575;gbattlepass;GBattlePass;;\nfloralis;24576;floralis;Floralis;;\nspartan-weaponry-unofficial;24577;spartanweaponryunofficial;斯巴达的武器：非官方版;Spartan Weaponry Unofficial;\niron-furnaces-enhanced;24578;;Iron Furnaces Enhanced;;\nspectral-utilities;24579;specutils;Spectral Utilities;;\npvp-flagging;24580;pvp_flagging;PVP Flagging;;\n;24581;automodsync;自动模组同步;Auto Mod Sync;AMS\n;24582;;龙骨匠魂化;;\n;24583;fractallightning;分形闪电;Fractal Lightning;\nelectroblobs-wizardry-redux;24584;;Electroblob's Wizardry Redux;;\nepic-fight-super-golem-x-modular-golems;24585;super_golem_mg;傀儡装配X超级铁傀儡;Epic Fight - Super Golem x Modular Golems;\n;24586;mcpml,pml;Partial Modification Loader;;PML\n;24587;f3screenshot;F3 Screenshot;;\nssc-xu-addon;24588;ssc_xu_addon;SSC Xu Addon;;\nunitytranslate;24589;unitytranslate;UnityTranslate;;\nceleritasleafculling;24590;celeritasleafculling;Celeritas Leaf Culling;;\n;24591;re_potioncore;Re: PotionCore;;\nredstone-chemical-elements;24592;redstone_chemical_elements;红石酱的化学元素;Redstone-chan Chemical Elements;\ntbc;24593;tbc;Think Big Core;;TBC\nocean-lily-pad-village;24594;ocean_lily_pad_village;Ocean Lily Pad Village;;\n;24595;player_doll;Player Doll;;\n;24596;crazyspidran;Crazy Hunters: Manhunt AI;;\n;24597;dont_hit_the_bees;Don't hit the bees;;\nskeleton-uses-custom-bow;24598;skeletonusescustombow;Skeleton Uses Custom Bow;;\n;24599;zerocontact;ZeroContact;;ZC\njubitus-birds;24600;jubitusbirds;Jubitus Birds;;\nstructureoverlapless;24601;overlapless;结构不重叠;StructureOverlapless;\n;24602;plutonium;钚;Plutonium;\nchat-calculator-boost;24603;chat_calculator_boost;聊天计算器增强;Chat Calculator Boost;CCB\n;24604;no_mobs_destroy_block,nomobsdestroyblock;禁止生物破坏方块;No Mobs Destroy Block;\ninstantworldmirror;24605;instantworldmirror;瞬时世界之镜;Instant World Mirror;\n;24606;floatingexcavation;浮空挖掘;FloatingExcavation;\nhostile-mobs-and-girls-recall;24607;hmag_fox;敌对生物与魔物娘：追忆;Hostile Mobs and Girls: Recall;HMaG:R\n;24608;holoinventoryreloaded;物品显示/物品清单：重载版;HoloInventory-Reloaded;HIR\n;24609;everlaartifacts;七叶镜绫的随想/言引定制;EverlaArtifacts;EA\n;24610;bright_sunflower;曜日金葵;Bright Sunflower;\natm-azure-tetra-materials;24611;azure_tetra_materials;蓝钢锻艺;Azure Tetra Materials;ATM\n;24612;minecolonies_skill_change;模拟殖民地知识卷轴;MineColonies Skill Change;mine_sk\nrevelation-goety-fix;24613;revelationgoetyfix;诡启兼容;RevelationGoetyFix;GRR\nresource-farm;24614;resource_farm;资源农场;Resource Farm;\n;24615;invlimiter;栏限;Inventory Limiter;\n;24616;precisepickup;精准拾取;Precise Pickup;\n;24617;itemthrow;精准投掷;Item Throw;\n;24618;singleslotitem;单格单存物;Single Slot Single Item;\ncreate-infinite-water;24619;infinitewater;机械动力：无限水;Create: Infinite Water;\n;24620;create_optical;机械动力：光学 (Fabric);Create Optical Fabric;\n;24621;blockrenderer6343;BlockRenderer6343;;\n;24622;auxiliary;Auxiliary;;\n;24623;honormod;HonorMod;;\nbotany-pot-configuration-card;24624;botanypotconfigurationcard;Botany Pot: Configuration Card;;\nhellish-trials;24625;hellish_trials;Hellish Trials / Nether Trials & Chambers;;\n;24626;ultraman;Ultraman Mod;;\n;24627;voxyworldgenv2;Voxy WorldGen;;\nfullbright-optimized;24628;optimized-fullbright;Optimized Fullbright;;\nmerlins-epic-boss-cataclysm;24629;meb_cataclysm;Merlin's Epic Boss - Cataclysm;;MEB\nenter-sandworm;24630;entersandworm;Enter Sandworm;;\nplayerengine;24631;playerengine;PlayerEngine;;\nterralith-no-structures;24632;terralith_restoned;Terralith: No Structures;;\npistonproplus;24633;pistonproplus;活塞ProPlus;PistonProPlus;PPP\nbiology-dictionary;24634;biologydictionary;生物辞典;Biology Dictionary;\n;24635;rsring;吸收戒指和经验泵;RS Rings and Tanks;rsring\nmore-tier-upgrade;24636;more_tier_upgrade;更多精妙堆叠;More Tier Upgrade;\nmore-functional-storage-upgrade;24637;more_functionalstorage_upgrade;更多功能性存储升级;More Functional Storage Upgrade;\n;24638;infinityitems;无限物品 Fabric 版;InfinityItemsFabric;IISF\ncarpet-lms-addition;24639;carpet-lms-addition;Carpet LMS Addition;;\nmaid-restaurant;24640;maid_restaurant;女仆餐厅;Maid Restaurant;\nseven-elements;24641;seven-elements;Seven Elements;;\nneo-eco-ae-extension;24642;neoecoae;Neo ECO AE Extension;;\ngolem-snowball-damage;24643;golemsnowballdamage;Golem Snowball Damage;;\nin-the-corners;24644;in_the_corners;In The Corners;;\nwaystones-x;24645;;Waystones-X (Unofficial Waystones Fork);;\nwaypoint-bridge;24646;waypointbridge;Waypoint Bridge;;\nolderpingdisplay;24647;olderpingdisplay;Older Ping Display;;\nexpanded-unuverse-pack;24648;unuverse;Expanded UNUverse Pack;;\nunu-military-pack-for-mts;24649;unumilitary;UNU Military Vehicles;;\nraisy;24650;raisy;Raisy;;\n;24651;disable_debug;Disable OpenGL Debug Output;;DODO\n;24652;kaleidoscope_chinesefood;森罗厨房：国味;Kaleidoscope Chinese Food;\nmythicalaccessories;24653;mythicalaccessories;神话饰品;Mythical Accessories;\n;24654;xiuxianzhilu;山海仙途2·修仙之路;XiuXianZhiLu;\ncombat-circle;24655;combatcircle;Combat Circle;;\nplayer2npc;24656;player2npc;Player2 AI NPC;;\n;24657;;骷髅炮台;SkeletonTurret;ST\ntacz-fire-control-extension;24658;tacz_fire_control_extension;TaCZ: Fire Control Extension;;\nceleritas-extra;24659;celeritasextra;Celeritas Extra;;\ncaves-revamp;24660;cavesrevamp;Caves Revamp: Underground Biomes;;\npneumatic-popcorn;24661;pneumaticcooking;Pneumatic Cooking;;\nrubber-tree-for-dynamic-trees;24662;rubberdt;GregTech Rubber Tree for Dynamic Trees;;\nbladecraft;24663;bladecraft;更多剑;BladeCraft;\nterrafirmagreg-core;24664;tfg;TerraFirmaGreg-Core;;\n;24665;;Etched: 1.20.1 Fabric Port;;\nepicfight-physic-rag-doll;24666;epicfight_physic_rag_doll;EpicFight : Physic Rag Doll;;\nspice-of-life-valheim-updated;24667;sol_valheim;Spice of Life: Valheim Updated;;\nae2-improved-search;24668;improvedae2search;AE2 Improved Search;;\nae2-fluid-terminals-rework;24669;ae2fluidterminalsrework;AE2 Fluid Terminals Rework;;\nshoulderturrets;24670;shoulderturrets;ShoulderTurrets;;\n;24671;infinitelava;无限流体方块;Infinite Fluid Blocks;IFB\nspartan-shields-unofficial;24672;spartanshieldsunofficial;斯巴达之盾：非官方版;Spartan Shields Unofficial;\n;24673;fakeping;Fake Ping;;\nchristmascolonies;24674;christmascolonies;圣诞殖民地;ChristmasColonies;\nmnaoverpowered;24675;mnaoverpowered;MNAOverpowered;;MNAOP\nstack-manager;24676;stackmanager;Stack Manager;;\nae2-mod-id-view-cell;24677;ae2modidviewcell;AE2 Mod ID View Cell;;\n;24678;trinket_media_holder;Curios Media Holder / Trinkets Media Holder;;\ncustommeteorjs;24679;custommeteor;CustomMeteorJS;;CMJS\nkaleidoscope-end;24680;kaleidoscope_end;森罗物语：末地;Kaleidoscope End;\nmacaws-regions-unexplored;24681;mcwregionsunexplored;Macaw's Regions Unexplored;;\n;24682;;游戏信息显示 GTNH 版;InGame Info XML - GTNH;\nbc-extra-pipes;24683;bcextrapipes;BC eXtra Pipes;;\n;24684;debugsurvivability;Debug Survivability;;\nae2-toggleable-view-cells;24685;ae2toggleableviewcells;AE2 Toggleable View Cells;;\nbetter-ic2-seed-bags;24686;betterseedbags;Better IC2 Seed Bags;;\nenergized-power-farmers-delight;24687;energizedpowerfd;Energized Power - Farmer's Delight;;\nenergized-power-the-aether;24688;energizedpowerta;Energized Power - The Aether;;\nenergized-power-biomes-o-plenty;24689;energizedpowerbop;Energized Power - Biomes O' Plenty;;\nweapon-class;24690;weapon_class;Crow's Weapon Classes;;\nboss-spawner-block;24691;bsb;自定义生物刷怪笼;;BSB\ngoety-integration;24692;goety_integration;巫法集成;Goety Integration;GINT\nblockz;24693;blockz;方块末日;BlockZ;BZ\n;24694;qauth;QAuth;;\nchest-locker-containers-locker-secure-storage;24695;;Chest Locker addon \"Container's Locker Secure Storage\";;\nsimplycataclysm;24696;simplycataclysm;Simply Swords: Cataclysm;;\nattributefix-sync;24697;attributefixsync;AttributeFix Sync;;AFS\nsophisticated-item-actions;24698;sophisticateditemactions;精妙物品操作;Sophisticated Item Actions;\nonly-hammers-and-excavators;24699;onlyhammersandexcavators;Only Hammers And Excavators;;\nfoolish;24700;foolish;Foolish;;\n;24701;lavachicken;熔岩烤鸡;Lava Chicken;LC\nentitychinesename;24702;entitychinesename;实体中文名;Entity Chinese Name;\nfirma-aircraft;24703;firma_aircraft;群峦航线;Firma Aircraft;FA\nexura;24704;exura;更多实用设备：再重生;Extra Utilities Reborn Again;ExURA\n;24705;;玩家数据拓展;PlayerDataExpansion;PDE\nshaolib;24706;shaolib;Shaolib;;\nmanage-your-ship;24707;valkyrienmanager;Manage Your Ship;;VSM\npeak-craft;24708;peak;Peak Craft;;\npink-precious-flowers;24709;shimmering_golden_flowers;闪耀的长庭;Shimmering Golden Flowers;SGF\n;24710;sampleintegration;AE2UEL 智能样板系统;Smart Pattern System;AE2UEL-SPS\n;24711;newbigtrees;New Big Trees;;NBT\nbananaplant;24712;bananamod;BananaPlant;;\n;24713;v_life;Village Life;;\ncaps-connected-area-prompt-shear;24714;caps;Connected Area Prompt Shear;;CAPS\n;24715;klbq;仿卡拉彼丘机制;Imitating the Strinova Mechanism;xpt\nexplainium;24716;explainium;Explainium;;\nsourceflow-infinite;24717;yuanliuwujin;源流无尽;Sourceflow Infinite;SI\nhbms-ntm-ce-space;24718;hbmspace;HBM's NTM CE: Space;;\n;24719;mr_dyable_faces;Dyable Faces;;\nultimatespawn;24720;ultimatespawn;UltimateSpawn;;US\nsimple2048;24721;simple2048;简单的2048;Simple 2048;\n;24722;netmusicadvancedplayer;网络音乐机：高级唱片机;Net Music: Advanced Player;\n;24723;extendednoteblock;扩展音符盒;Extended Note Block;ENB\nrealistic-explosives-ic2-exp-addon;24724;realistic_explosives;Realistic Explosives;;RE\nnomoref3-f4;24725;nomoref3f4;旁观时间管理大师;NoMoreF3+F4;\n;24726;ccccc;CCCCC;;\n;24727;appradi;应用放射学;Applied Radiology;\n;24728;appchg;应用充能;Applied Charger;\n;24729;forcebeaconloadforforge;信标强制加载Forge版;ForceBeaconLoadForForge;\nfolks-favors;24730;folksfavors;Folks & Favors;;\neasy-adventure;24731;easyadventure;便携基地;Easy Adventure;\nstar-optimized;24732;star_optimized;Star Optimized;;\nnetworkblocker;24733;networkblocker;网络拦截器;Network Blocker;\nsimple-world-downloader;24734;swd;Simple World Downloader;;SWD\nclass-armor-beast-master;24735;beastmaster;Class Armor : Beast Master;;\nclass-armor-necromancer-and-paladin;24736;umbralux;Class Armor : Necromancer and Paladin;;\nclass-armor-berserker-mage-and-archer;24737;bma_addon;Class Armor : Berserker, Mage and Archer;;BMA\nindustrial-platform;24738;industrial_platform;工业平台;Industrial Platform;IP\nsunlight-zombie-mod;24740;sunlight_zombie;Adventurers Lost in Time: Sunlight Zombie;;\nlycheejs;24741;lycheejs;LycheeJS;;\ncolorwand;24742;colorwand;调色棒;Colorwand;CW\n;24743;item_router;Item Router;;\n;24744;commandbot;CommandBot;;CB\n;24745;villagediplomacy;Village Diplomacy;;\n;24746;i-wanna-show;I Wanna Show;;\nmusket-mod-reloaded;24747;musketmod;Musket Mod Reloaded;;\n;24748;chainveinfabric;ChainVeinFabric;;\nvcfix;24749;vcfix;VCFix;;\n;24750;mixinblacklist;Mixin 黑名单;Mixin Blacklist;\nchisel-3-the-cricket-strikes-back;24751;;凿子3;Chisel 3;\nmankini;24752;mankini;曼基尼;Mankini;\nteamprojecte-reborn;24753;teamprojecte_reborn;等价团队：重生;Team ProjectE Reborn;\n;24754;mcapibridge;MCAPIBridge;;\nbehemoths;24755;behemoths;Behemoths;;\n;24756;backupalwaysright;备份永远是对的;Backup Always Right;BAR\n;24757;mf.speedslider;速度调整器;Speed Slider;\nquesttapestry;24758;questtapestry;Quest Tapestry;;\noccultism-ritual-satchel;24759;occultism_rs;神秘学 仪式挎包;Occultism Ritual Satchel;\nmineclawd;24760;mineclawd;MineClawd;;\n;24761;core_plants;矿石种子;Core Plants;CPL\nre-endergy;24762;re_endergy;末影接口：管道拓展：重生;Re: Endergy;\npocket-legion;24763;pocketlegion;口袋军团;Pocket Legion;\n;24764;randomblocks;随机方块空岛生存;;\ntennory;24765;tennory;战甲军械;Tennory;\nankinbt;24766;ankinbt;安之编辑;AnkiNBT;ANBT\ningame-info-xml-canoeedition;24767;ingameinfoxml;游戏信息显示 独木舟版;InGame Info XML CanoeEdition;IGIC\nlunatriuscorecanoeedition;24768;lunatriuscore;LunatriusCoreCanoeEdition;;\n;24769;lootplusplus;Re: Loot++;;\ntacz-charge-into-battle-reboot-pack;24770;;冲锋陷阵：重启;Charge Into Battle: Reboot Pack;CIB:R\nthaumic-integration;24771;ThaumicIntegration,ThaumicIntegration-Preloader;Thaumic Integration;;TI\n;24772;bedrockultimatetool;基岩究极工具;BedrockUltimateTool;\n;24773;windowplayerhead;WindowPlayerHead;;\nmorecars;24774;morecars;更多车;morecars;MC\n;24775;qqbot;QQBot: Rebuild;;\ntelepad;24776;telepads;传送台;Telepad;\nprimitive-mobs-wondersapped;24777;;Primitive Mobs Wondersapped;;\n;24778;farmerscreate;FarmersCreate;;\nmaestro;24779;maestro;Maestro;;\naccents;24780;accents;Accents;;\nfancy-entity-renderer;24781;fancy_entity_renderer;Fancy Entity Renderer;;FER\npantheonapi;24782;pantheonapi;PantheonAPI;;\neye-of-dreams-reforged;24783;eyeofdreams_reforged;Eye of Dreams Reforged;;\nsupplementaries-compat;24784;;锦致装饰兼容;Supplementaries Compat;\nmobs-equipment;24785;mr_mobs_equipment;Mobs Equipment;;\n;24786;civil;Civillis;;\ndesert-villager-trader;24787;desert_villager_trader;Desert Villager Trader;;\n;24788;mmdskin;MmdSkin;;\n;24789;netmusiccanneedqq;网络音乐机：看你的QQ;NetMusicCanNeedQQ;\npanoramic-screenshot;24790;panoramic_screenshot;全景截图;Panoramic Screenshot;\ncustom-totem-particles;24792;custom-totem-particles;自定义图腾粒子;Custom Totem Particles;\n;24793;jsblock;Yomi的常磐装饰;Yomi's Joban Client Mod;YJCM\n;24794;;MTR3 Unofficial;;\nirons-spells-n-spellbooks-restrictions;24795;irons_restrictions;Irons Spells 'n Spellbooks Restrictions;;\n;24796;mtrsteamloco;Aphrodite's Nemo's Transit Expansion FIX;;\n;24797;state-machine;状态机模组;State Machine;\nempty-shulker-nesting;24798;empty-shulker-nesting;Empty Shulker Nesting;;\n;24799;packaged_faa;封包禁忌与奥秘;PackagedFAA;PFAA\nad-inferos;24800;adinferos,dextersnether;Ad Inferos;;\n;24801;zdyzhlfk;自定义重力方块;Zdyzhlfk;\ncc-directgpu;24802;directgpu;CC:DirectGPU;;\neasy-exchange;24803;easyexchange;简单替换;Easy Exchange;EE\n;24804;simple-image-renderer;Simple Image Renderer;;\nait-extras;24805;ait-extras;AIT Extras;;\nrats-ratn-edition;24806;;Rats: RatN Edition;;\n;24807;eye_of_dreams;Eye of Dreams;;\nfoolish-unofficial-patch;24808;foolishfixmod;Foolish Unofficial Patch;;\n;24809;sj;升级！变强！;SJ Mod;\n;24810;damage-engine;伤害引擎;Damage Engine;DE\ntravelers-lib;24811;travelerslib;Travelers Lib;;\n;24812;hmc;heef的模组的核心;Heef's Mod Core;HMC\n;24813;hmbmm;heef的模组行为监视模组;Heef's Module Behavior Monitoring Module;HMBMM\n;24814;sickle_core;镰刀核心;Sickle Core;SCore\nstation-builder;24815;stationbuilder;车站建设器;Station Builder;\nheavy-duty-plate-blocks;24816;heavydutyplateblocks;重装防护板块;Heavy-Duty Plate Blocks;\n;24817;brx_nextbot;Brx的自定义Nextbot;Brx's Customizable Nextbot;\nsakura-reforged;24818;sakura;樱·重制;Sakura Reforged;\n;24819;worst-luck-possible;Worst Luck Possible;;\nihfr-fo;24820;ihfr_fo;IHFR FLESHED OUT;;IHFR:FO\n;24821;;匠魂弓箭平衡 (强化);;\n;24822;itemdespawntowhat;掉落物消失后变成啥;Item Despawn To What;IDTW\njurassic-saga;24823;jurassicsaga;Jurassic Saga;;\nae2-tech-add-on;24824;ae2tech;AE2 科技扩展;AE2 Tech Add-On;\n;24825;create_nerfad;Create: Nerfad;;\nquestextra;24826;questextra;QuestExtra;;QE\nflans-mod-npc-vehicles;24827;wolffsmod;Flan's Mod NPC Vehicles;;\n;24828;easylogin;简易登录;EasyLogin;\ninventory-sort-nf;24829;;背包整理 (NeoForge 版);Inventory Sort (NeoForge Edition);IS-NF\nsomeordinarytweaks-forge;24830;ordinarytweaks;SomeOrdinaryTweaks;;\n;24831;asen;附带英语;Always Show English;ASEN\nqliphoth-awakening-dimensions;24832;qliphoth-awakening-dimensions;Qliphoth Awakening Dimensions;;\n;24833;mca-expanded;MCA Expanded;;\nex-meteor-shower;24834;meteor_shower;流星雨;Meteor Shower;\nchaos-persists;24835;chaospersists;Chaos Persists;;\nadventurers-amulets;24836;adventurersamulets;冒险家的护身符;Adventurer's Amulets;\n;24837;ashinmycoffee;Respite: There's Ash in My Coffee!!;;\n;24838;mortem;Mortem;;\ncreate-frequency-filter;24839;frequencyfilterid;Create: Frequency Filter;;\n;24840;accurate-day-night-cycle;Accurate Skies;;\n;24842;antideath-carpet-addition;AD的Carpet拓展;Antideath Carpet Addition;ACA\n;24843;xy_tpa;Xy的TPA模组;;XyTpa\n;24844;hlgid;heef的轻量级游戏信息显示;Heef's Lightweight Game Information Display;HLGID\nsteves-factory-manager-reborn;24845;;史蒂夫工厂-重置;Steve's Factory Manager Reborn;\nsteves-addons;24846;StevesAddons;史蒂夫工厂管理附加;Steve's Addons;SA\nffs-fancy-fluid-storage;24847;ffs;液体存储;Fancy Fluid Storage;FFS\nreverie-foundry;24848;reveriefoundry;遐想旅途;Journey of Reverie;JR\nflowercraft;24849;flowercraftmod;花朵世界;Flowercraft;\n;24850;hdr_mod;Minecraft HDR Mod;;\nlycheejs-neoforge;24851;lycheejs;LycheeJS NeoForge;;\n;24852;throwablebricks;投掷砖块;Throwable Bricks;\nepic-fight-the-arachne;24853;epicfight_arachne;Epic Fight - The Arachne;;\ninsane-tnt;24854;insanetnt;Insane TNT;;\nextra-tnt;24855;extra_tnt;Extra TNT;;\nmob-loot-bags;24856;mob_loot_bags;Mob Loot Bags;;\n;24857;locatel;Locate Legacy;;LL\nsteve-chair;24858;steve_chair;Steve Chair;;\nstoic-dummy;24859;stoicdummy;Stoic Dummy;;\nwildex-bestiary;24860;wildex;Wildex Bestiary;;\nfubricapi;24861;fobricapi;Fubric API;;\n;24862;auto_sail;自动航行;Auto Sail;AS\n;24863;faleui;faleUI;;\n;24864;heefsgadget;heef的小玩意;Heef's Gadget;HG\nadaptability;24865;adaptability;适应性;Adaptability;\nredstone-chans-void-dimension;24866;void_dimension;虚空维度;Void Dimension;\n;24867;xmib;Xirao的更多物品与方块;Xirao's More Items and Blocks;XMIB\n;24868;tchy_difficulty_adjustment;太初寰宇难度调整;TCHY Difficulty Adjustment;TDA\n;24869;confirmed;我已确认并知晓执行此命令的风险 ☑️;I'm confirmed!;\n;24870;reforged;ReForged;;RF\n;24871;honeywave;金波;honeywave;\n;24872;noisiumed;Noisiumed;;\ntradable-wind-burst;24873;tradable-wind-burst;可交易风爆;Tradable Wind Burst;\n;24874;enchanttweaker;Enchant Tweaker;;\nmbd2thread;24875;mbd2thread;MBD2 线程拓展;Mbd2Thread;MBD2T\n;24876;ignatiushcsmoreore;IgnatiusHC 的更多矿石;IgnatiusHC's More Ore;IHCO\nultimate-village;24877;ultimate_village;终极村庄;Ultimate Village;\n;24878;fangsu;方速MTR扩展;FangSu MTR Addon;FS\n;24879;tearsofthekingdom;我的世界：王国之泪;Minecraft: Tears of the Kingdom;MiTotK\nmore-candles;24880;morecandles;更多蜡烛;More Candles;\n;24881;locksofbounding;绑定之锁;Locks Of Bounding;LOB\ntacz-automatic-first-person;24882;tacz_firstperson;TaCZ FirstPerson;;TF\n;24883;kgt6neiaddon;kGT6NEIAddon;;\nabyssalcraft-remnant-tweaker;24884;abyssalcraft_remnant_tweaker;ACRemnantTweaker;;ACRT\nherobrine-companion;24885;herobrine_companion;Herobrine 的陪伴;Herobrine Companion;HC\nindustrial-forestry;24886;industrialforestry;Industrial Forestry;;\navaritia-lite-extended;24887;avaritialiteextended;Avaritia Extended;;\ngoety-hostility;24888;goetyhostility;Goety Hostility;;\nfish-traps-remastered;24889;fishtraps;鱼栅非官方重制版;Fish Traps Remastered;\nthe-ponderer;24890;ponderer;思索者;Ponderer;\nfallen-lib;24891;fallen_lib;Fallen Lib;;\nlazyyyyy;24892;lazyyyyy;lazyyyyy;;\n;24893;moskeletons;Mo' Skeletons;;\nzunpet-vintage;24894;zunpet;ZUNPet Vintage;;ZPTV\nfluxed-crystals-2;24895;fluxedcrystals;变换水晶2;Fluxed-Crystals 2;\ngregtech-ceu-gaseous-blast-handler;24896;gtceugbh;GregTech CEu: Gaseous Blast Handler;;\ngtmoretools;24897;gtmt;GTMoreTools;;\n;24899;nebula;Nebula;;NB\nviolas-wardrobe;24900;violas_wardrobe;Viola 的衣柜;Viola's Wardrobe;VW\n;24901;disablechat;Simple Chat Disabler;;\n;24902;cbc_phantom_shells;CBC Phantom Shells;;\n;24903;createeasiergoo;Create: Easier Goo;;\n;24904;mcreator_create_mod;Create: Jade quartz;;\ncreate-idlx;24905;createidlx;Create: Improved Display Link Experience;;C:IDLX\n;24906;physics-drop;Physics Drop;;\nsketchrender;24907;sketch_render;SketchRender;;\nbrainrot-protocol;24908;brainrot-protocol;Brainrot Protocol;;\nmy-shadow;24909;my_shadow;My Shadow;;\npipecraft;24910;logisticpipes;PipeCraft;;\nbetter-forests;24911;betterforests;更好的森林;Better Forests;\noverworld-ancient-debris;24912;overworld_ancient_debis;Overworld Ancient Debris;;\nsimply-potion-rings;24913;potionrings2;Simply Potion Rings;;\nbettervanilla;24914;bettervanilla;更好的原版;Better Vanilla;\nmore-respawn-anchors-remastered;24915;morerespawnanchors;更多重生锚重制版;More Respawn Anchors Remastered;\n;24916;goety_ladder;诡厄巫法：阶梯;Goety: Ladder;GL\nouter-rim;24917;outerrim;Sown 的星球大战;Sown's Star Wars Mod;\nevoc;24918;Evoc;优化效能;Extreme Vanilla Optimizations Collection;EVOC\nmobiuscore;24919;MobiusCore;MobiusCore;;\nwormhole-potion;24921;wormholepotion;虫洞药水;Wormhole Potion;\nfinndusfillies;24922;FinndusFillies;Finndus Fillies;;\nmoogs-glow-up;24923;glow_up;Moog's Glow Up;;\nstarlight-with-create-fix;24924;;Starlight with Create Fix;;\n;24925;zombieseverywhere;Zombies everywhere;;\nundyings-bosses;24926;undyings_bosses;Undying's Bosses;;\nnice-to-have;24927;nicetohave;Nice to Have;;\n;24928;fminecraftmod;F Minecraft Mod;;\nmore-enchantment-info;24929;more_enchantment_info;更多附魔信息;More Enchantment Info;\n;24930;profitabletrade;Profitable Trade;;\ntacz-apocalypse-gun-pack;24931;;启示录武器包;[TaCZ] Apocalypse Gun Pack;\ntacz-create-armorer-koei;24932;create_armorer;机动军械师武器包;[TaCZ] Create Armorer;\nharvestcraft-waila-fixes;24933;HarvestCraftWaila;HarvestCraft Waila Fixes;;\nore-stages-restaged;24934;;Ore Stages: ReStaged;;\nthe-mini-bosses;24935;MiniBosses;Mini-Bosses;;\nnether-descent;24936;netherdescent;Nether Descent;;ND\nwtp-whats-this-pack;24937;wtp;What's This Pack;;WTP\nim-looking-at-blood;24938;ImLookingAtBlood;I'm Looking At Blood;;\nbefixment;24939;bewitchment-rei;Bewitchment REI;;\niriss-plushie-mod;24940;fickle;Iris's Plushies;;\nscreencopy;24941;screencopy;Screencopy;;\nlegendgear-returns;24942;legendgear;LegendGear Returns;;\n;24943;legendgear;LegendGear 2;;\nae2helpers;24944;ae2helpers;ae2helpers;;\nae2-pattern-workstation;24945;ae2pw;AE2样板工作站;AE2 Pattern Workstation;ae2pw\nchiseltones;24946;chiseltones;ChiselTones;;\nchisel-facades;24947;ChiselFacades;錾面;Chisel Facades;\ntinkers-extra-integrations;24948;tinkersextraintegration;Tinker's Extra Integrations;;\ntalismans-2;24949;Talismans2;符咒2;Talismans 2;\ndalek-mod-all-dimensions-plus;24950;dm_all_dims_plus;Dalek Mod: All Dimensions Plus;;\npepoyo;24951;pepoyo,yande_plan;PEPOYO Mod;;PPYM\nctnh-changelog;24952;ctnhchangelog;CTNH更新日志;CTNH-Changelog;\n;24953;i_want_cute_plushies;I want cute plushies!;;\ntinkers-ponder;24954;tponder;工匠思索;Tinkers' Ponder;TiP\n;24955;aicompanion;nekostulAI Companion;;\n;24956;chatpurity;聊天过滤;ChatPurity;\n;24957;antanicc;AntaniCC;;\nqwertech;24958;qwertech;QwerTech;;\nsilent-gear-jei;24959;silentgearjei;寂静装备JEI;Silent Gear JEI;SGJ\ncobbledex-rei-emi-jei;24960;cobbledex_rei_emi_jei;Cobbledex for REI / EMI / JEI;;\n;24961;w_loader;W Mod Loader;;WML\ntcondiadema-plus;24962;tcondiadema_plus;匠魂领域附加;TconDiadema Plus;TconD+\n;24963;nyr;新年红;New Year Red;NYR\n;24964;rpfix;Fix MC-249973 in 1.19;;\ni-wanna-music;24965;iwm;给我音乐！;I Wanna Music;IWM\n;24966;pre112inv;1.8 Style Inventory;;\n;24967;numericids;Numeric ID In Advanced Item Tooltip;;\n;24968;Multiplayer164;Multiplayer164;;\nmorechisels;24969;morechisels;更多凿子;More Chisels;\n;24970;chains;Chains;;\n;24971;cslib;Clashsoft's Minecraft Library;;CSLib\n;24972;brewingapi;Clashsoft's Brewing-API;;BAPI\n;24973;morepotions;更多药水;More Potions Mod;MPM\n;24974;naturot;Naturot;;\n;24975;infinity_stack;Infinity Stack;;\n;24976;zstdmc;ZstdMC;;\nvulkanbobby;24977;vulkanbobby;VulkanBobby;;\napplied-replicatics;24978;apprep;应用复制学;Applied Replicatics;\n;24979;undead_fire_weakness;亡灵弱火;Undead Fire Weakness;\nruthless;24980;ruthless;Ruthless;;\nbetterlooting;24981;better_looting;更好的拾取;;BL\n;24982;arknights_furs;明日方舟种族外观;Arknights Origin Furs;\nminesia;24983;minesia;创世;Minesia;MSA\n;24984;mr_double_jumpbybatspaladin4422;二段跳;Double Jump;\n;24985;mr_enhancedd;Enhanced;;\n;24986;ishouldeatmore;吃不能停;I Should Eat More;\nmekanism-ponders;24987;mekanism_ponders;Mekanism: Ponders;;\n;24988;cbc_compact_mount;CBC: Compact Mount;;\ncreate-auto-track;24989;create_auto_track;机械动力：径工地点;Create: Auto Track;CAT\npulsetech;24990;pulsetech;Pulsetech;;\n;24991;better_entity_render;Better Entity Render;;BER\n;24992;mymod;Magical Chocolate Mod;;\n;24993;night-lights-patch;Polymer Patch for Night Lights;;\nsnowscaress;24994;snowscaressmod;Snows Caress;;\n;24995;simukraft;新：模拟大都市;New: Sim-U-Kraft;NSUK\nfancy-outlines;24996;fancyoutlines;Fancy Outlines;;\nae2-pattern-encoding-access-terminal;24997;ae2peat;AE2 Pattern Encoding Access Terminal;;\nefficient-entities;24998;efficiententities;Efficient Entities;;\ndistant-banners;24999;distantbanners;Distant Banners;;\nchroma-carvings;25000;chromacarvings;Chroma Carvings;;\ncoffeechat;25001;coffeechat;无忧聊;CoffeeChat;CFC\nchest-tracker-port;25002;chesttracker;箱子追踪非官方移植版;Chest Tracker (Unofficial port);\nbetter-crash-reports;25003;better-crash-reports;更好的崩溃日志;Better Crash Report;BCR\n;25004;;Trinkets (Updated);;\nnot-enough-glyphs;25005;not_enough_glyphs;Not Enough Glyphs;;\n;25006;not-enough-vulkan;Not Enough Vulkan;;\ngoslingcord;25007;;Goslingcord;;\nemojicord;25008;emojicord;Emojicord;;\n;25009;logarithmic-volume-control;Logarithmic Volume Control;;\nscarcity;25010;scarcity;Scarcity;;\nmo-bends-unofficial-modern-port;25011;mobends;更多弯曲 [现代化移植];Mo' Bends [MODERN PORT];\n;25012;supersteve;超级史蒂夫;Super Steve;SS\n;25013;findaway;寻找一条路;FindAway;\nlongroad;25014;longroad;前路已明;Road Is Ahead;RIA\nrough-render-excessive-eating;25015;hqbdh_user_defined;粗渲滥食;Rough Render Excessive Eating;RREE\n;25016;ember;余烬;Ember;\nsimple-minesweeper;25017;simple_minesweeper;简单的扫雷;Simple Minesweeper;\nmets-blade;25018;mets_blade;拔刀剑：更多电力工具;MoreElectricTools Blade;METS_Blade\nslashblade-ununextremeaddon;25019;sbuue;拔刀剑重锋—一点也不极端的拓展;SlashBladeUnUnExtremeAddon;SBUUE\ndragit;25020;dragit;图片拖拽;DragIt;\nixapi;25021;ix_api;IXApi;;9Api\n;25022;;FIREWORK!;;\n;25023;productivelib;Productive Lib;;\nanything-but-fish;25024;anythingbutfish;就是不钓鱼;Anything But Fish;ABF\norder-to-cook;25025;ordertocook;下单了;Order To Cook;OTC\nbutchery-lite;25026;butchery_lite;Butchery LITE;;\napplied-construction-sticks;25027;appliedsticks;应用建筑棒;Applied Construction Sticks;\nfarscape-addon-for-gc3;25028;GCFarScape;FarScape;;FS\n;25029;galaxyspacetwilightforest,GalaxySpaceTwilightForest;Galaxy Space - Twilight Forest;GalaсticraftTF;\nserene-seasons-better-winter;25030;serene_better_winter;Serene Seasons Better Winter;;\n;25031;mc122477fix;Mc122477Fix re updated;;\nrefined-construction-sticks;25033;refinedsticks;Refined Construction Sticks;;\nyeti-jar-dweller;25034;yeti;The Yeti Dweller;;\ncreate-automotives;25035;automotives;Create: Automotives;;\nexposure-expanded;25036;exposure_expanded;Exposure: Expanded;;\nsmart-spawn;25037;smartspawn;Smart Spawn;;\nrandom-mob-textures;25038;randommobtextures;Random Mob Textures;;\nuifadefx;25039;uifadefx;UIFadeFX;;\ndaily-boss;25040;pladailyboss;Daily Boss;;\nit-seems-loosely-connected-to-the-ceiling;25041;islcttc;与洞顶的连接似乎并不牢固;It seems loosely connected to the ceiling;ISLCTTC\nagricraft-refitted;25042;;Refitbench Maintains - AgriCraft;;\n;25043;MPGuard;MPGuard For MCEconomy2;;\n;25044;MoreAMTRecipes;MoreAMTRecipes;;\n;25045;transfer_voucher;传送券;Transfer Voucher;TV\n;25046;overloaded_armor;护甲无上限;Armor Uncapped;AU\n;25047;unmannedonlinetickstasis;服务器无人时间静止;Server Tick Stasis;\nturbulence-loading-animation;25048;turbulenceloadinganimation;乱流加载动画;Turbulence Loading Animation;TLA\nslashblade-model-warmup;25049;sbmwu;拔刀剑：模型预热;SlashBlade Model WarmUp;\nraids-enhanced;25050;raids_enhanced;Raids: Enhanced;;\n;25051;goodmod;自动瞄准;;\ncaged-mobs-kots;25052;;笼中生物 - 耕云钓月版;Caged Mobs - KOTS;\n;25053;bulletreply;TaCZ 枪械弹药恢复;Bullet Reply;\n;25054;bsc;常用服务器命令;Basic Server Commands;BSC\nraritycore-for-fabric;25055;raritycore;稀有度核心Fabric版;RarityCore For Fabric;\n;25056;animcore;动画核心;AnimCore;AC\ntoms-storage-star-optimized;25057;toms_storage_star_optimized;汤姆存储 - 星的奇妙优化;Toms Storage Star Optimized;\nsbw-wrecked;25058;wrecked;[SBW]残骸系统;[SBW] Wrecked!;\nshipping-box;25059;shipping_box;售货箱;Shipping Box;\n;25060;cointoshop;消费了！;Coin To Shop;CTS\n;25061;liteloaderloader;LiteloaderLoader;;\ni-need-dragon-egg;25062;i_need_dragon_egg;我需要龙蛋;I Need Dragon Egg;\nnickname;25063;nickname;昵称;Nickname;NN\nredstone-chan-cobblestone-generator;25064;cobblestone_generator;圆石生成器;Cobblestone Generator;\nelytra-contrails;25065;elytratrails;鞘翅尾迹;Elytra Contrails Mod;\nguard-horn;25066;guardhorn;警卫号角;Guard Horn;\njei-trim-hider;25067;jei_trim_hider;JEI 纹饰模板隐藏;JEI Trim Hider;\n;25068;sekirodiedscreen;只狼死亡界面：重生;SekiroDiedScreen;\nendfieldpanorama;25069;endfield_panorama;终末地游戏加载页面;EndfieldPanorama;\nbmts-imagination;25070;let_me_tweak_it;让我改改;Let Me Tweak It;\n;25071;;FluxLoading 1.7.10;;\n;25072;bettercat;更好的猫;BetterCat;BC\n;25073;inf;inf的原版保护魔咒突破;Inf's Enchantment Surpass;IES\npuyu;25074;;璞玉;Puyu;\nneo-titans-variant;25075;thetitans_variant;新泰坦生物-变种;NeoTitansVariantMod;NTVM\nassembly-line;25076;assemblyline;装配线;Assembly Line;\n;25077;mcet;原版扩展;Minecraft Extra;MCET\n;25078;better_coordinate_records;更好的坐标记录 / 更好的座標紀錄;Better coordinate records;\nefn-enhance;25079;efn_enhance;夜幕强化;EFN Enhance;EFNE\npets-locator;25080;petslocator;Pets Locator;;\nguide-book;25081;guidebook;合成指南书;Guide Book;\nelders-wild;25082;elderswild;Elder's Wild;;\nvoidaic-arcania;25083;voidaicarcania;Voidaic Arcania;;\n;25084;swords;More Swords+;;\nscp-662-butlers-hand-bell;25085;butlers_hand_bell;SCP-662, Butler's Hand Bell;;\n;25086;perihelion;Perihelion;;\nauroras-arsenal;25087;aurorasarsenal;Aurora's Arsenal;;\ninfernal-expansion-redux;25088;infernalexp;Infernal Expansion Redux;;\nhbms-nuclear-tech-mod-waldemar-edition;25089;;HBM's Nuclear Tech Mod - Waldemar Edition;;\ncloud-boots;25090;cloud_boots;Cloud Boots;;\n;25091;hws;HTcpWebServer;;\n;25092;nsscm;NoServerSideClientMods;;NSSCM\n;25093;mp;MinecraftPatcher;;MP\n;25094;seir;StaticEnchantedItemRenderer;;SEIR\n;25095;timeup;TimeUp;;\n;25096;paragraphs_reborn;ParagraphsReborn;;\n;25097;ftbcprotect;Ftb区块加强保护;FtbCProtect;\nae2-overclocked;25098;ae2_overclocked;AE2超频;AE2 Overclocked;\n;25099;breadcool;Bread is Cool;;\nintegrated-mekanism;25100;integratedmekanism;Integrated Mekanism;;\nrainjava;25101;rainjava;RainJava;;RJ\n;25102;fabric-gui-imgui;Fabric GUI ImGui;;\n;25103;wildcard_pattern;通配符样板;Wildcard Pattern;\nme-soul-card;25104;mesoulcard;ME Soul Card;;\n;25105;randomblock;随机发生器;RandomBlock;RB\nmcqoy;25106;mcqoy;McQoy;;\nqomc;25107;qomc;QoMC;;\nexplosive-enhancement-legacy;25108;;Explosive Enhancement: Legacy;;\n;25109;sharedinv;共享物品栏;Shared Inventory;\n;25110;mod_FairyMod;Fairy Factions Colonization;;\nlanding-mud;25111;landingmud;落地泥！;Landing Mud;\n;25113;tat;列车与物品;MTR Trains and Textures;TAT\nfriendtp;25114;friendtp;朋友TP;FrinedTP;FP\n;25115;randomcmd;随机命令;Random Command;\n;25116;display_tool;生命和伤害显示;Display Tool;\n;25117;digging_leaderboard;Minecraft 挖掘榜;Minecraft Digging Leaderboard;\n;25118;Ly0a6DNB;第一人称沉浸式音频;First-person immersive audio;\ntechnological-assistance;25119;technological_assistance;科技辅助;technological assistance;\n;25120;neb;网络包优化;Not Enough Bandwidth;NEB\napprentices-codex;25121;apprenticecodex;Apprentice's Codex;;\nasterism-arcanum-addon-for-irons-spells-and;25122;asterismarcanum;Asterism Arcanum;;\nwizards-help-irons-spells-n-spellbooks-addon;25123;wizardshelp;Wizards Help!;;\nfw-addon;25124;swa;FW Addon;;\nnew-years-delight-a-farmers-delight-add-on;25125;nydelight;New Year's Delight;;\n;25126;more_tool;More Tools;;\n;25127;tomssimplestorageintfc;Toms Simple Storage in TFC;;\n;25128;locatelegacy;Locate Legacy;;\n;25129;oledsaver;OLED Saver;;\n;25130;typemoonworld;型月世界;Type Moon World;\nexistencealone;25131;existence;存在;Existence;\n;25132;chromeballplus;土球：扩展;ChromeBall++;\n;25133;the_nuke_mod;The Nuke Mod;;\narmor-3d;25134;armor_3d;3D Armor;;\nhot-iron;25135;hot_iron;Hot Iron;;\ndangerous;25136;dangerous;Dangerous - Just A Difficulty Mod;;\nbalaclava-mod;25137;balaclavamod;Balaclava;;\n;25138;goodblock;Changed: GoodBlock;;CGB\ntacz-sound-attracts-zombies;25139;tsaz;枪鸣尸至;TaCZ: Sound Attracts Zombies;TSAZ\nntrials;25140;ntrials;NewTrials;;\nalexs-mobs-fabric;25141;;Alex's Mobs (Fabric);;\ncitadel-fabric;25142;;Citadel (Fabric);;\nbibliocraft-legacy-fabric;25143;;BiblioCraft Legacy (Expanded);;\nuntamed-wilds-unofficial-port;25144;;Untamed Wilds (Unofficial Port);;\ncit-recrafted;25145;cit_recrafted;CIT Recrafted;;\ngreek-fantasy-unofficial-port;25146;;Greek Fantasy (Unofficial Port);;\nuniversutil;25147;universutil;Universutil;;\nindev-hell-mod;25148;indev_hell_mod;Indev Hell Mod;;\napocalypse-dimension;25149;apocalypsedimension;Apocalypse Dimension;;\ndranergize-dragon-origin;25150;dranergize-race;Dranergize Dragon Origin;;\n;25151;;Potatospherical;;\ndamage-indicators-classic;25152;retrodamageindicators;Damage Indicators (Classic);;\ndeadly-weather-storms;25153;more_thunder_fabric,more_thunder_forge;Deadly Weather: Lightning/Thunder Storms;;\nvapourware;25154;vapourware;VapourWare;;\ninlinelatex;25155;inlinelatex;Inline: LaTeX;;\nfear-of-sound;25156;fear_of_sound;Fear of Sound;;\nhorror-pacman;25157;PacmanMod;Killer Pacman;;\n;25158;lock-doors-from-villagers;Lock Doors From Villagers;;\ndepths-update;25159;cavesapi,depthsupdate;Depths Update;;\nfurnace-mk2;25160;furnacemk2;Furnace Mk2;;\nauto-clicker-fabric;25161;autoclicker-fabric;Auto Clicker;;\n;25162;no_moon_network_patch;No Moon Network Fix;;\nmulti-key-bindings;25163;multi-key-bindings;Multi Key Bindings;;\ndog-dog;25164;dog_dog;Dog Dog;;\n;25165;new_mc;旧时光的访客;Visitors from the Old World;\ndeus-ex-machina-reborn;25166;deus_ex_machina;Deus Ex Machina: Reborn;;\nnanbin-create-mod;25167;nanbin;南滨创意;Nanbin Create Mod;\n;25168;mru;竞速实用工具;;MRU\nsimple-absorption;25169;simple_absorption;Simple Absorption;;\ncobblemon-trials-edition;25170;cobblemontrialsedition;Cobblemon Trials Edition;;\npotion-core-remastered;25171;potioncoreremastered;药水核心重制版;Potion Core Remastered;\nfantasy-iron-fix;25172;fantasyironfix;铁梦兼容;Fantasy Iron Fix;FIX\ngoety-awaken-fix;25173;goetyawakenfix;厄醒修复;Goety Awaken Fix;GAF\ngoety-twilight-fix;25174;goetytwilightfix;诡暮兼容;Goety Twilight Fix;GTF\n;25175;fuck_particle;禁用粒子;Fuck Particle;\n;25176;emeraldcraft;绿宝石工艺;EmeraldCraft;\nyou-are-the-zombie-2-0;25177;as_the_zombie;我是僵尸;As The Zombie;ATZ\nmobcivics;25178;mobcivics;MobCivics;;\n;25179;gdhcpf_fabric,gdhcpf;更多原版合成配方;More Synthetic Recipes;MSR\ntarotcards-remastered;25180;tarotcards;塔罗牌：重制;TarotCards: Remastered;TCR\n;25181;copyblock;复制方块;Copy Block;\n;25182;apocalypse_ends;天启终焉;Apocalypse End;APE\n;25183;tickcommandmod;添加Tick指令;TickCommandMod;\n;25184;bypass-fabric-check;Bypass Fabric Check;;\n;25185;mmceguiext;Modular Machinery: Community Edition Gui Edit;;MMCEGE\ntensionadd;25186;tensionadd;剑拔弩张之时附加;TensionAdd;TA\n;25187;trimupgrade;纹饰增强 : 核心;Trim Upgrade : Core;TUC\n;25188;openlink_chmlfrp_extension;开放式联机：Chmlfrp扩展;OpenLink Chmlfrp Extension;OCE\n;25189;haw;Home and Warp;;HAW\n;25190;momoi_game_console;小桃的游戏机;Momoi Game Console;\nenchantato-enchantment-table;25191;enchantato_enchantment_table;Enchantato - Enchantment Table;;\n;25192;ore_features;矿异位：NeoForge;OreFeatures;\n;25193;redenvelope;红包;Red Envelope;RE\nadvanced-backups-patch;25194;advancedbackupspatch;高级备份补丁;Advanced Backups Patch;\n;25195;networkproxy;Network Proxy;;\n;25196;asmcraft;Asm Craft;;\n;25197;not-enough-upgrade;升级不足;Not Enough Upgrade;NEU\napotheosis-things;25198;apotheosis_things;神化小玩意;Apotheosis Things;\nweaken-satiated-shield;25199;weaken_satiated_shield;更平衡的饱腹代偿;Weaken Satiated Shield;\nkaleidoscope-tavern;25200;kaleidoscope_tavern;森罗物语：酒馆;Kaleidoscope Tavern;\nthe-farlands-are-back-tfab;25201;FarLandsCore;The Farlands are Back!;;TFAB\nbeta-graphics;25202;betagraphics;Beta Graphics;;\nworld-corruption-fixer;25203;world_corruption_fixer;World Corruption Fixer;;\n;25204;kingandqueen;The King and The Queen - Echoes of Utopia;;\nstructural-tools;25205;structural_tools;结构工具;Structural Tools;\n;25206;originclasses;OriginClasses;;\nslime-chunk-finder;25207;isslime;Slime Chunk Finder;;\ndragon-movement-fix;25208;dragon-movement-fix,mr_dragon_movementfix;末影龙移动修复;Dragon Movement Fix;\noptinotfine;25209;optinotfine;OptiNotFine;;\nenderdragon-loot;25210;dragonloot;Enderdragon Loot;;\n;25211;carpet_cuo_addition;Carpet_CuO_Addition;;\ncloud-boots;25212;;云靴;Cloud Boots;\n;25213;iaf_atm_compat;IceAndFire ATM Compatibility;;\n;25214;modern_glass_doors_mod;时尚玻璃门 (Neo)Forge 版;Modern Glass Doors(Forge);\nae2-better-magnet-card;25215;ae2bettermagnetcard;AE2 Better Magnet Card;;\nrangedjs-kubejs-addon;25216;rangedjs;RangedJS: KubeJS Addon;;\n;25217;origin_skiper;起源：轮椅族;Origins: Wheelchair;\nkaleidoscope-tfc-cookery;25218;ktfcc;森罗物语：群峦厨房;Kaleidoscope TFC Cookery;KTFCC\nreliable-recipes;25219;reliable_recipes;Reliable Recipes;;\ninline-tooltips;25220;inline_tooltips;Inline Tooltips;;\nmtr-east-japan-railway-addon;25221;mtr-east-japan-railway-addon;MTR: East Japan Railway Addon;;EJRA\nraidspawn;25222;raidspawn;RaidSpawn;;\ngtoex;25223;gtoex;GTOEX;;\n;25224;zzclear;Zzclear;;Zzc\n;25225;veil;Veil;;\ngregtech-molecule-drawings;25226;moldraw;GregTech Molecule Drawings;;\nthe-broken-script-patches-herdyn-edition;25227;tbspatch;The Broken Script Patches;;\nwifi-mod;25228;lasers-addon;Home Security;;\nmore-tide-fish;25229;moretidefish;More Tide Fish;;MTF\nez-actions;25230;ezactions;EZ Actions;;\ncreate-train-tracker;25231;traintracker;Create Train Tracker;;\n;25232;create_dynamic_blocking;机械动力：动态闭塞;;\ncreate-backtank-is-jetpack;25234;backtankisjetpack;Create: Backtank is Jetpack;;\ncreate-real-stack-size;25235;create_actual_stack_size;Create: Real Stack Size;;\ncreate-fluidlogistic;25236;fluidlogistics;机械动力：流体包裹;Create: Fluid Logistic;\n;25237;fluid_shell_extensions;流体炮弹扩展;Fluid Shell Extensions;\nkubejs-create-automation;25238;kjscauto;KubeJS Create Automation;;\nnether-star-items-forge;25239;spaider;Nether Star Items;;\npiglin-hunt-fix;25240;piglinhuntfix;Piglin Hunt Fix;;\nok-core;25241;okcore;OK Core;;\n;25242;ccexpand_peripherals;CC拓展外设;CCExpand Peripherals;CCE\n;25243;eto;归寄之苦难;E-PCA to Ordeal;ETO\ncolored-end-crystals;25244;coloredendcrystals;Colored End Crystals;;\nwenzhou-specialties;25245;wenzhou_specialties;温州美食;Wenzhou Specialties;\n;25246;peyro'sscythefix;铁镰兼容;Peyro's Scythe Fix;PSF\nsnowify-eternal-snow;25247;snowify_eternal_snow;❄️ Snowify: Eternal Snow ❄️;;\n;25248;;Modern UI mVUS / ModernUI MC mVUS;;\nhorror-messages-kamussy;25249;horrormessages;HORROR - Psychological Horror Messages;;\nvelthoric;25250;velthoric;Velthoric;;\nzcveinminer;25251;;Ultimate Veinminer;;\nre-constructed-wands;25252;reconstructedwands;RE: Constructed Wands;;\nenchantment-library;25253;enchlib;附魔图书馆;Enchantment Library;\nload-stone;25254;loadstone;Load Stone - Chunk Loader;;\ncreatures-of-sonaria-in-minecraft;25255;cos_mc;Creatures of SonariaCraft;;\n;25256;nmoc;NMOC建设辅助;;NMOC\ncommand-keybindings;25257;commandkeybindings;Command Keybindings;;\n;25258;rdpfix;RDP Fix;;\ntrue-end-cf;25259;true_end;True End;;\nbarnyard-buddies;25260;barnyardbuddies;Barnyard Buddies;;\nblack-souls-options;25261;black_souls_options;黑色之魂：选项;Black Souls: Options;BSO\n;25262;crosstie;CrossTie;;\njcplugin;25263;jcplugin,JCCorePlugin;JCPlugin;;JCP\npursuit-of-immortality;25264;pursuit_of_immortality;无光仙途;Nonsun's  Pursuit of Immortality;POI\ncraftingnetherstar;25265;cns;CraftingNetherStar;;CNS\nemeddium-au-naturel-edit;25266;;Embeddium - Au Naturel Edit;;\nviewmodel-tuner;25267;viewmodel_tuner;ViewModel Tuner;;\naethersteel;25268;aethersteel;Aethersteel;;\ncustom-better-beacons;25269;betterbeacons;Custom Better Beacon;;\nimmersive-cookfarm;25270;immersivecooking;Immersive Cooking & Farming;;\ngoety-heart-of-creation;25271;ghoc;Goety Heart of Creation;;GHOC\n;25272;leftclickblocker;LeftClickBlocker;;LCB\ndragns-livestock-overhaul-pets-addon;25273;dragnpets;DragN's Livestock Overhaul: Pets Addon!;;\nunnatural-absorption;25274;natural_absorption;Unnatural Absorption;;\nripples-of-the-past-recording;25275;rotp_ata;Ripples of the Past: Recording;;Rotp:Recording\nserene-seasons-api-stub-ecliptic-seasons-bridge;25276;sereneseasons;静谧四季兼容桥;Serene Seasons API Stub / Ecliptic Seasons Bridge;\necliptic-seasons-bundles;25277;eclipticseasons_bundles;节气：捆绑包;Ecliptic Seasons : Bundles;\n;25278;maceeffect;重锤效果;MaceEffect;\nspiders-shoot-webs;25279;spidersshootwebs;Spiders Shoot Webs;;\n;25280;;Attribute PVP Helper;;\n;25281;;蘑菇：古物重建;Mushroom: Ancient Tools Rebuilding;MATR\nenigmatic-addons-legacy;25282;eaddons;神秘遗物拓展经典版;;\nbeak-pouchs-plight;25283;beak_pouchs_plight;喙囊悲事;Beak&Pouch's Plight;BP2\nhammers-galore;25284;hammers-galore;锤子多多;Hammers Galore;\nlanguage-amend;25285;lang_amend;渊言订正;Language Amend;LA\n;25286;mod_Minegicka;魔能;Minegicka;\nmechanical-millstone;25287;mechanicalmillstone;Mechanical Millstone;;\nzoology-of-the-world-of-withernauts;25288;turtdance;Zoology of the World of Withernauts;;\nstackable-flowers;25289;stackableflowers;Stackable Flowers;;\ndragns-deadly-dinos-reconstructed;25290;deadlydinos;DragN's Deadly Dinos: Reconstructed;;DDD:R\nteleporttagjs;25291;teleporttagjs;TeleportTagJS;;\nteleport-tag;25292;teleporttag;传送标签;Teleport Tag;TPTag\ninsconfigjs;25293;insconfigjs;InsConfigJS;;\ninsconfig;25294;insconfig;即时重配;InsConfig;\nentityreplacer;25295;entityreplacer;生物替换;EntityReplacer;\nnohurtred;25296;NoHurtRed;没有受击变红;NoHurtRed;NHR\nbilibili-media-kots;25297;bilibili_media;哔哩哔哩WaterMedia兼容模组  - 耕云钓月版;Bilibili Media - KOTS;BMK\nsatiated-shield-fix;25298;satiated_shield_fix;饱腹代偿修复;Satiated Shield Fix;\ntacz-armor-scaling;25299;armorscaling;TaCZ护甲缩放;TaCZ Armor Scaling;\ncrazy-chocobos;25300;chocobos;Crazy Chocobos!;;\nburnt-basic;25301;;Burnt Basic;;\n;25302;regsyncfix;RegSyncFix;;\ngiddy-guinea-pigs;25303;giddypigs;Giddy Guinea Pigs!;;\ndragns-crop-overhaul;25304;dragncrops;DragN's Crop Overhaul!;;\ndragns-livestock-overhaul-permafrost;25305;permafrost;DragN's Livestock Overhaul: Permafrost;;\nradical-rats;25306;radrats;Radical Rats!;;\nlittle-tractor;25307;liltractor;Little Tractor;;\nfancy-ferrets;25308;fferrets;Fancy Ferrets!;;\n;25309;dunestriders;Dune Striders;;\nrummage;25310;rummage;翻箱倒柜;Rummage;\ntacz-creative-supplement;25311;taczcreativesupplement;TaCZ Creative Supplement;;TCS\n;25312;iems;综合能源管理系统;Integrated Energy Management System;IEMS\nanvilcraft-qualia-addon;25313;anvilcraft_qualia;铁砧工艺：Qualia;AnvilCraft: Qualia Addon;AQA\n;25314;NoDurabilityLoss;不要掉耐久!;NoDurabilityLoss;NDL\ncreate-more-machines;25315;createmoremachines;机械动力：更多机械;Create More Machines;CMM\nturret-mod-rebirth;25316;sapturretmod;Turret Mod Rebirth;;\nkind-seagull;25317;kind_seagull;善良海鸥;Kind Seagull;KS\npaper-kite-manor;25318;papercraft_magic_decoration;纸鸢庄园;Paper Kite Manor;\nepicfight-clash-blade;25319;efclash_blade;史诗战斗：冲击之刃;EpicFight : Clash Blade;\nattribute-keeper;25320;attributekeeper;Attribute Keeper;;\n;25321;him;冲撞;Collide;\n;25322;GulliverForged;格列佛游记;Gulliver the Resizing Mod;\nfrostfire-dragon;25323;frostfire_dragon;冰焰龙;Frostfire Dragon;\nadvancements-rewards;25324;advancements_and_rewards;成就&奖励;Advancements&Rewards;AR\n;25325;transmutation_lunch_bag_callback;森罗厨房：嬗变饭袋回调;Kaleidoscope Cooker: Transmutation Callback;\n;25326;constructionwandlgeacy;建筑手杖怀古;ConstructionWandLgeacy;\n;25327;improvingsurvival;改善的生存;Improving Survival;ISUR\n;25328;minewatch;MineWatch;;\n;25329;weight;重量系统;Weight System;\ninterlace-spellweaves-irons-spells-and-spellbooks;25330;iss_csw;交错织法;Interlace SpellWeaves;\nmobs-use-shield;25331;mobs_use_shield;Mobs Use Shield;;\nskilltreemkp;25332;skilltreemkp;Passive Skill Tree Mob Kill Points;;\ncells;25333;cells;Compacting/Extra Large Lattice Storage;;C.E.L.L.S.\nwanderer-pets;25334;wanderer;Wanderer: Pets;;\nmedieval-embroidery;25335;medievalembroidery;Medieval Embroidery;;\ndragns-wagons;25336;dragnwagons;DragN's Wagons!;;\ndragns-deadly-dinos;25337;deadlydinos;DragN’s Deadly Dinos!;;DDD\ndragns-bettas-aquatics;25338;bettas;DragN’s Bettas & Aquatics!;;\nreliable-remover;25339;reliable_remover;Reliable Remover;;\nreliable-replacer;25340;reliable_replacer;Reliable Replacer;;\nmob-dismemberment-unofficial-modern-port;25341;mobdismemberment;Mob Dismemberment [UNOFFICIAL MODERN PORT];;\n;25342;mobconversion;Guard Villagers - Autonomous Villager Defense;;\nrepair-station;25343;repair-station;Repair Station;;\ngtm-advanced-hatch;25345;gtmadvancedhatch;GTM Advanced Hatch;;\nkubejs-jei;25346;kubejei;KubeJS JEI;;\ntraveler-tool-belt;25347;travelertoolbelt;Traveler Tool Belt;;\nfloor-beds;25348;floorbeds;Floor Beds;;\nshadow-actionbar;25349;shadowactionbar;Shadow Actionbar;;\nbaby-steps;25350;babysteps;Baby Steps;;\n;25351;;VoxelGet;;\nvintage-damage-indicators;25352;vintagedamageindicators;复古伤害显示;Vintage Damage Indicators;\n;25353;lol;LOL装备栏;LOL;\n;25354;lol_equipment;LOL装备;LOL Equipment;\napricityui;25355;apricityui;晴雪UI;ApricityUI;AUI\n;25356;stay_on_my_target;别再射歪了;StayOnMyTarget;SOMT\nfunctionalstoragelegacy;25357;functionalstoragelegacy;怀旧功能性存储;FunctionalStorageLegacy;FSL\n;25358;betterwater;更好的原版水逻辑;Better Water Logic;BWL\necho-chest-experience-obliterator;25359;echo_no_tooltip;Echo Chest Experience Obliterator;;\n;25360;salt_refined;盐：精炼;Salt Refined;\ncustom-stars-forge;25361;custom_stars;Custom Stars [Forge];;\nmobs-use-shields;25363;mobsuseshields;Mobs Use Shields;;\nfairy-factions;25364;FairyFactions;Fairy Factions;;\nnavs-pipes;25365;;Nav's Pipes;;\nbig-lost-city;25366;big_lost_city;Big Lost City;;\n;25367;taczaddonsfix;TaCZAddonsFix;;\nrollthesky;25368;rollthesky;RollTheSky;;\n;25369;tamablefairy;车万女仆：收容异变;Tamable Fairy;TLMTF\nmaidsconstruct;25370;maidsconstruct;女仆匠魂;Maid's Construct;\nbugrock;25371;bugrock;Bugrock;;\nenchantato-loot;25372;enchantato_loot;Enchantato - Loot;;\njust-enough-crafting-tree-ject;25373;just_enough_crafting_tree;Just Enough Crafting Tree;;JECT\n;25374;locatelm;Locate Legacy: Minecraft Edit Edition;;LM\nadvanced-hud;25375;advanced_hud;Advanced HUD;;\nycqin;25376;ycqin;ycqin;;\ndeep-blue-a-bettas-aquatics-addon;25377;deepblue;Deep Blue: A Bettas & Aquatics Addon!;;\napplied-create;25378;appliedcreate;应用机动;Applied Create;AC\ntradeworks;25379;tradeworks;Create: Tradeworks;;\n;25380;createorigins;Create: Origins;;\ntorque-link-create-to-tfc;25381;c2tfc;旋力转接：机械动力×群峦;Torque Link Create To TFC;\n;25382;fu_zhuan;符箓;FuLu;FL\ni-hate-ghost-blocks;25383;ihateghostblocks;I Hate Ghost Blocks;;\ncustom-chest-name;25384;custom_chest_name;自定义箱子名称;Custom Chest Name;CCN\nmodern-chickens;25385;chickens;Modern Chickens;;\n;25386;;Anaku 的状态栏 - 重制;Anaku's Status Bars Port;\nrpg-backpacks;25387;rpgbackpacks;RPG背包;RPG Backpacks;\nresource-replicator;25388;resource_replicator;资源复制机;Resource Replicator;\n;25389;fossils;The Fossils Mod;;\nlittle-johns-galvanized-square-steel;25390;little_johns;Little John's: Galvanized Square Steel;;\nnavaroneecorelib;25391;navaroneecoremod;NavaroneeCoreLib;;\n;25392;;Your Options Shall Be Neoforged;;YOSBN\n;25393;osten_botanics;东洋花卉;Osten Botanics;OBS\ntree-vein-miner;25394;mr_tree_veinminer;Tree Vein Miner;;\nziplines-rezipped;25395;zipline;Ziplines: Rezipped!;;\nreconnectible-chains;25396;connectiblechains;Reconnectible Chains;;\nfoxeternity;25397;foxeternity;FoxEternity;;\nserverofflineauth;25398;server_offline_auth;服务器离线认证;ServerOfflineAuth;\nlegendary-spellbooks;25399;legendary_spellbooks;Legendary Spellbooks;;\nlootselect;25400;lootselect;LootSelect;;\n;25401;;咒术时代;Sorcery Age;\n;25402;no_smithing_template;无锻造模板(No Smithing Template);;NST\n;25403;mm;Modern Mayhem;MM;\nepicfight-extra;25404;EpicFight-Extra;EpicFight-Extra;;\nthe-space;25405;space;Space;;\nroguelike-world;25406;roguelikeworld;地牢世界;Roguelike World;RW\nmusic-trigger;25407;music_trigger;Music Trigger;;MT\ncheating-free-locate-teleporting;25409;cflt;无作弊Locate传送;;CFLT\nwallpapers;25410;wallpapers;Wallpapers;;\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/modpack_data.txt",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# mcmod.cn\n# Copyright (C) 2025. All Rights Reserved.\n#\ngt-new-horizons;1;;格雷科技：新视野;GT: New Horizons;GTNH\nenigtech-2;2;;玄理2;EnigTech 2;ET2\n;3;;飞翔之路3;Flying Road 3;\ndungeons-dragons-and-space-shuttles;4;;龙与地下城和太空飞船;Dungeons, Dragons and Space Shuttles;DDSS\nskyfactory-4;5;;天空工厂4;SkyFactory 4;SF4\nlife-in-the-village;6;;Life in the village;;LitV\nexpedition-in-the-sky;7;;Expedition In The Sky/Journey Across The Void;;JATV\nlevitated;8;;失重;Levitated;LTT\nall-the-mods-3;9;;All The Mods 3;;ATM3\ncreate101;10;;Create 101;;C101\nftb-interactions;11;;FTB Interactions;;FTBI\nmaterial-energy-5-entity;12;;Material Energy^5: Entity;;ME5\nminecraft-eternal;13;;永恒的MC;MC Eternal;MCE\nmc-eternal-lite;14;;永恒的MC精简版;MC Eternal Lite;\nmc-eternal-hardcore;15;;永恒的MC 硬核版;MC Eternal Hardcore;\nrlcraft;16;;超现实 x 虚拟生存;RLCraft;RLC\nthe-trial-of-god;17;;神之试炼;The Trial of God;TIMW4\nproject-ozone-3-a-new-way-forward;18;;臭氧计划3;Project Ozone 3 A New Way Forward;PO3\nequivalent-skies;19;;等价空岛;Equivalent Skies;\nftb-continuum;20;;FTB Continuum;;\nenigmatica;21;;Enigmatica;;E\nenigmatica2;22;;Enigmatica 2;;E2\nenigmatica2expert;23;;Enigmatica 2: Expert;;E2E\nenigmatica2light;24;;Enigmatica 2: Light;;E2L\nenigmatica4;25;;Enigmatica 4;;E4\nenigmatica5;26;;Enigmatica 5;;E5\nenigmatica6;27;;Enigmatica 6;;E6\nroguelike-adventures-and-dungeons;28;;Roguelike冒险与地牢;Roguelike Adventures and Dungeons;R.A.D.\nfarming-valley;29;;农场物语;Farming Valley;\ncompact-claustrophobia;30;;幽恐噩梦;Compact Claustrophobia;\nmodern-skyblock-3-departed;31;;现代空岛3;Modern Skyblock 3: Departed;\ngregblock;32;;格雷空岛;Gregblock;\nsevtech-ages;33;;赛文科技;SevTech: Ages;STA\n;34;;伊卡洛斯号迫降;Icarus Landing;IL\ngreedycraft;35;;贪婪整合包;GreedyCraft;GC\nstoneblock;36;;StoneBlock;;\nftb-presents-skyfactory-3;37;;天空工厂3;FTB Presents SkyFactory 3;SF3\nspace-astronomy;38;;Space Astronomy;;\nworld-of-dragons;39;;World of Dragons;;\nmodern-skyblock-2;40;;现代空岛2;Modern Skyblock 2;\nall-the-mods-2;41;;All the Mods 2;;ATM2\nautomaton;42;;Automaton;;\nbotania-skyblock-the-modpack-the-mod-the-modpack;43;;Botania Skyblock;;\nfarming-valley-lite;44;;Farming Valley - Lite;;\natm-all-the-magic;45;;All the Mods: All the Magic;;\nhr-new-beginnings;46;;HR: New Beginnings;;\nvalhelsia-2;47;;Valhelsia 2;;\nftb-presents-stoneblock-2;48;;FTB Presents Stoneblock 2;;\nftb-sky-odyssey;49;;天空奥德赛;FTB Sky Odyssey;\nall-of-fabric-3;50;;All of Fabric 3;;AOF3\nterrafirma-rescue;51;;群峦：救援;Terrafirma:Rescue;TFR\nantimatter-chemistry;52;;Antimatter Chemistry;;\nre-build;53;;RE-BUILD;;\nintegration-by-parts;54;;Integration By Parts;;IBP\ncrafttoexiledissonance;55;;放逐之路;Craft to Exile [Dissonance];CTE\ncrafttoexileharmony;56;;放逐之路;Craft to Exile [Harmony];CTE\nexoria;57;;艾柯索瑞亚;Exoria;\nage-of-engineering;58;;时代工业;Age of Engineering;AOE\namnesia;60;;Amnesia;;\nthe-vanilla-experience;61;;The Vanilla Experience;;TVE\ngregicality-skyblock-edition;62;;Gregicality Skyblock Edition;;GSE\nTownCraft;63;;TownCraft;;\nftb-revelation;64;;FTB Revelation;;\nftb-infinity-evolved;65;;FTB Infinity Evolved 1.7;;\nftb-beyond;66;;FTB Beyond;;\nftb-infinity-evolved-skyblock;67;;FTB Infinity Evolved Skyblock;;\nftb-ultimate-reloaded;68;;FTB Ultimate Reloaded;;\nftb-ultimate;69;;FTB Ultimate;;\nftb-academy;70;;FTB Academy 1.12;;\n;71;;FTB University 1.12;;\nftb-presents-direwolf20;72;;FTB Presents Direwolf20;;\nftb-presents-direwolf20-1-10;73;;FTB Presents Direwolf20 1.10;;\nftb-presents-direwolf20-1-12;74;;FTB Presents Direwolf20 1.12;;\ndirewolf20-1-4-7;75;;Direwolf20 1.4.7;;\ndirewolf20-1-5-2;76;;Direwolf20 - 1.5.2;;\ndirewolf20-1-6-4;77;;Direwolf20 - 1.6.4;;\nftb-unstable-1-12;78;;FTB Unstable 1.12;;\nftb-unstable-1-14;79;;FTB Unstable 1.14;;\nftb-unstable-1-10;80;;FTB Unstable 1.10;;\n;81;;FTB Unstable 1.16;;\nftb-presents-skyfactory-2-5;82;;天空工厂2.5;FTB Presents SkyFactory 2.5;SF2.5\nftb-skyfactory-challenges;83;;FTB Skyfactory Challenges;;\nftb-sky-adventures;84;;FTB Sky Adventures;;\nvolcano-block;85;;Volcano Block;;VB\nchemical-exchange;86;;Chemical Exchange;;\nftb-builders-paradise;87;;FTB Builders Paradise;;\nftb-pyramid-reborn;88;;FTB Pyramid Reborn 3.0;;\nfeed-the-beast-egg-hunt;89;;FTB Egg Hunt;;\nftb-presents-cloud-9;90;;FTB Presents Cloud 9;;\nftb-retro-smp;91;;FTB Retro SMP;;\nftb-horizons-iii;92;;FTB Horizons III;;\nftb-infinity-lite-1-10;93;;FTB Infinity Lite 1.10;;\nftb-presents-hermitpack;94;;FTB Presents HermitPack;;\nyogcraft-modpack;95;;YogCraft Modpack;;\ntech-world;96;;Tech World;;\nmindcrack;97;;MindCrack Pack;;\nmagic-world-1-4-7;98;;Magic World - 1.4.7;;\nftb-lite;99;;FTB Lite;;\nftb-inventions;100;;FTB Inventions;;\nftb-presents-crackpack;101;;FTB Presents Crackpack;;\nftb-vanilla;102;;FTB Vanilla +;;\nftb-lite-3;103;;FTB Lite 3;;\nftb-departed;104;;FTB Departed;;\nftb-trident;105;;FTB Trident;;\nftb-mage-quest;106;;FTB Mage Quest;;\nftb-retro-ssp;107;;FTB Retro SSP;;\nftb-resurrection;108;;FTB Resurrection;;\nftb-monster;109;;FTB Monster;;\nftb-horizons;110;;FTB Horizons;;\ntech-world-2;111;;FTB Tech World 2;;\nmagic-world-2;112;;FTB Magic World 2;;\nftb-unleashed;113;;FTB Unleashed;;\nftb-lite-2;114;;FTB Lite 2;;\nunstable-1-7-x-public-beta-test-pack;115;;Unstable 1.7.x;;\npax-prime-2014-challenge;116;;PAX Prime 2014 Challenge;;\nftb-unhinged;117;;FTB Unhinged;;\nslows-stream-pack;118;;Slow's Stream Pack;;\nfeed-the-beast-beta-pack;119;;Feed The Beast Beta Pack;;\npax-prime-2015-challenge;120;;PAX Prime 2015 Challenge;;\npax-south-2015-challenge;121;;PAX South 2015 Challenge;;\npax-east-2014-challenge;122;;PAX East 2014 Challenge;;\npax-east-2013-challenge;123;;FTB Pax Challenge Pack 2013;;\nftb-terra;124;;FTB Terra;;\nftb-horizons-daybreaker;125;;FTB Horizons: Daybreaker;;\nomnifactory;126;;全能工厂;Omnifactory;\nimpact-gt-edition;127;;IMPACT;;\nkinda-crazy-craft;128;;Kinda Crazy Craft;;KCC\nkinda-crazy-craft-2-0;129;;Kinda Crazy Craft 2.0;;KCC2\nsevtech-ages-of-the-sky;130;;SevTech: Ages of the Sky;;\none-hundred-years-after-the-apocalypse;131;;One Hundred Years After the Apocalypse;;\nstaged-learning;132;;Staged Learning;;\ntechnocratica;133;;Technocratica;;\njourney-beyond-the-abyss;134;;超越深渊之旅;Journey Beyond the Abyss;JBtA\nawakening-sky-of-diamonds;135;;Awakening - Sky of Diamonds;;\nragnamod-v;136;;Ragnamod V;;\nall-the-mods-3-expert;137;;All the Mods 3 专家版;All the Mods 3 Expert;ATM3E\nseablock-rustic-waters;138;;乡村水域;Seablock: Rustic Waters;\nengineers-life;139;;Engineer's Life;;EL\nstar-factory;140;;Star Factory;;\nlunar-laboratory;141;;Lunar Laboratory;;\nglacial-awakening;142;;Glacial Awakening;;\nproject-equivalence;143;;Project Equivalence;;\nbreakout;144;;Break Out;;\nscientists-dream;145;;Scientific Dreams;;\nabandend;146;;AbandEnd;;\ntech-expansion;147;;Tech Expansion;;\nminefanstasyworld;148;;我的幻想世界;MineFantasyWorld;\nslightly-vanilla-flavoured;149;;Slightly Vanilla Flavoured;;\nall-the-mods;150;;All the Mods;;ATM1\ncrackpack-3;151;;Crackpack 3;;\nvalhelsia-3;152;;Valhelsia 3;;\ncortex-3;153;;Cortex 3;;\ntechblock-caveblock;154;;TechBlock: CaveBlock;;\n;155;;星空;Galaxy Space Integration;GSI\nvalhelsia;156;;Valhelsia 1;;\nvalhelsia-origins;157;;Valhelsia: Origins;;\ngalactic-science;159;;Galactic Science;;\nspace-astronomy-2;160;;Space Astronomy 2;;\n;161;;匠魂的生存;Tinker's Survival Modpack;TSMp\nlife-in-the-village-2;162;;Life in the village 2;;LitV2\n;163;;魔法与工业;Magic And Creat;MAC\nall-the-mods-6;164;;All the Mods 6;;ATM6\ngarden-of-glass-questbook-edition;165;;Garden of Glass (Questbook Edition);;\n;166;;圣剑传奇;Sword Legendary;\n;167;;FTB Presents Direwolf20 1.16;;\nteam-rustic;168;;Rusty Sink 2;;\nenigmatica2expertskyblock;169;;Enigmatica 2: Expert Skyblock;;E2ES\n;170;;休闲-生存-2021版;Leisure Survival-2021;ls21\nhexxit-updated;171;;Hexxit Updated;;\ncreate-101-season-2;172;;Create 101 第二季;Create 101 Season 2;C101 S2\nforever-stranded;173;;荒漠求生;Forever Stranded;\nminecraft-ultimato;174;;MC Ultimato;;\nlapitos-galacticraft;175;;Lapito's Galacticraft;;\nmaterial-energy-4;176;;Material Energy^4;;\nmaterial-energy-classic;177;;Material Energy^3;;\nslashaoa3;178;;SlashAoA3;;\npopularmmos-epic-proportions-season-10;179;;PopularMMOS Epic Proportions Season 10;;\nmcmod-testpack;180;;MC百科-测试包;MCMOD-TestPack;\n;181;;FTB Endeavour;;\n;182;;铅笔空岛;How are you block;HB\n;183;;花鸟旅途;Hananichi On The Road;HOTR\nsky-bees;184;;蜜蜂空岛;Sky Bees;\nrebirth-of-the-night;185;;重生之夜;Rebirth of the Night;RotN\nzombie-apocalypse-slow-zombies-by-forge-labs;186;;僵尸启示录;Zombie Apocalypse / Mustard Virus;\nstar-wars-conquest;187;;Star Wars Conquest;;\ntekkit-classic-reloaded;188;;Tekkit Classic Reloaded;;TCR\nmineshafts-monsters;189;;矿井与怪物;Mineshafts & Monsters;\ncrazy-craft-remastered;190;;Crazy Craft Remastered;;\nengineers-life-2;191;;Engineer's Life 2;;EL2\nhr-new-beginnings-2;192;;HR: New Beginnings 2;;\nminecolonies-official;193;;模拟殖民地;MineColonies Official;\ntnp-limitless;194;;TNP Limitless;;\ntnp-limitless-2;195;;TNP Limitless 2;;\ntnp-limitless-3;196;;TNP Limitless 3;;\nall-in-one-modded-one-block;197;;All in One;;\nall-the-mods-6-to-the-sky-atm6s;198;;All the Mods 6 - To the Sky;;ATM6S\nall-the-mods-4;199;;All the Mods 4;;ATM4\nall-the-mods-5;200;;All the Mods 5;;ATM5\natm-3-lite;201;;All the Mods 3: Lite;;ATM3L\n;202;;伪装者;The Disguiser;\ncreatech-1165;203;;科瑞亚科技;CreaTech;CT\nreboot;204;;重启;Reboot;\nbetter-mc-forge-bmc1;205;;更好的我的世界;Better MC;BMC\ncrucial-2;206;;Crucial 2;;\nbetter-minecraft-plus;207;;Better Minecraft [PLUS];;\nparasites-by-forge-labs;208;;Parasites by Forge Labs;;\ncreate-live;209;;Create Live;;\nairaka-tech;211;;艾瑞卡科技;Airaka Tech;\n;212;;TeaCon;;\noriginssmp;213;;起源SMP;OriginsSMP;\ncreate-live-2;214;;Create Live 2;;\norigin-smp-copy;215;;Origin - SMP;;\n;216;;逆迫降;INVERSE FORCED LANDING;\navatar-the-four-elements;217;;Avatar: The Four Elements;;\nseaopolis;218;;Seaopolis;;\nzombie-apocalypse-remastered;219;;僵尸启示录：重制;Zombie Apocalypse Remastered;ZAR\nstablejava;220;;StableJava;;SJ\ntechnodefirmacraft;221;;TechNodeFirmaCraft;;TNFC\nagrarian-skies;222;;Agrarian Skies;;\nagrarian-skies-2;223;;Agrarian Skies 2;;\ninteractions;224;;交汇;Interactions;IA\nskyexchange;225;;Skyexchange;;\nproject-ozone;226;;臭氧计划;Project Ozone;PO\nproject-ozone-2-reloaded;227;;臭氧计划2;Project Ozone 2: Reloaded;PO2\nocean-outlast;228;;Ocean Outlast;;\nfabric-adventure-pack;229;;Adventure Pack;;AP\ncrash-landing;230;;Crash Landing;;\ninvasion;231;;Invasion;;\n;232;;FTB Cotton;;\nchroma-sky;233;;色度空岛;Chroma Sky;\nastroblock;234;;AstroBlock;;\n;235;;群峦：核工程师;Terra Firma Atomic Engineer;TFAE\n;236;;北极星;Borealis;\nmultiblock-madness;237;;Multiblock Madness;;\nhappy-bat;238;;Happy Bat;;HB\n;239;;氮元素;Nitrogen;N\nfictioncraft-atomic-space;240;;合虚为实：原子空间;FictionCraft: Atomic Space;\nstacia-expert;241;;Stacia Expert;;\nmind;242;;思维;Mind;\nall-the-tinkers-construct;243;;最全的匠魂;All The Tinker's Construct;ATTC\ncuriosities-the-sequel;246;;Curiosities: The Sequel;;\nall-of-fabric-4;247;;All of Fabric 4;;AOF4\nfabulously-optimized;248;;难以置信的优化;Fabulously Optimized;FO\nbetter-minecraft-otv-edition;249;;Better Minecraft (OfflineTV Edition);;\nevolution-reset;250;;进化：重启;Evolution: Reset;ER\nthe-nuclear-wasteland;251;;核荒原;The Nuclear Wasteland;\nblessed-or-cursed-expedition-to-infinite-force;252;;祝福或诅咒：无限力量之征;Blessed or Cursed: Expedition to Infinite Force;BoC\n;253;;边界生存;Border Survival;BSV\nmedieval-mc-forge-mmc1;255;;Medieval Minecraft;;MMC\nheavens-of-sorcery;256;;巫术天堂;Heavens of Sorcery;\nherodotus;257;;希罗多德;Herodotus-Authentic Edition:Official;HDS\n;258;;重生世界;Rebirth world;RW\npermafrost-eots;259;;永久冻土：风暴之眼;Permafrost - Eyes of the Storm;\nultimate-alchemy;260;;终极炼金;Ultimate Alchemy;\nsurviva-originl-and-future;261;;虚拟生存 起源与未来;Survival - Origin & Future;SOF\n;262;;超级英雄无限;Superheroes Unlimited;SU\n;263;;植物大战僵尸：科学默语;Science in PVZ;SIP\nsky-high;264;;Sky High;;\ncreatetogether;265;;CreateTogether;;\n;266;;FTB Academy 1.16;;\nthe-kingdom-of-daldar-forge-labs;267;;The Kingdom of Daldar - Forge Labs;;\ndemon-slayers-unleashed;268;;Demon Slayers Unleashed;;\nvalhelsia-enhanced-vanilla;269;;Valhelsia: Enhanced Vanilla;;\n;270;;FTB University 1.16;;\nthaumic-renaissance-kedition;271;;神秘复兴;Thaumic Renaissance / Thaumic Renaissance - Kedition;\nthe-winter-rescue;273;;冬季救援;The Winter Rescue;TWR\n;274;;飞翔之路;Flying Road;\nforever-stranded-lost-souls;275;;Forever Stranded Lost Souls;;\nregrowth-an-hqm-pack;276;;Regrowth;;\nroguelike-adventures-and-dungeons-tic-edition;277;;RAD匠魂版;Roguelike Adventures and Dungeons TiC Edition;\nvanilla-forge-essentials;279;;Vanilla Forge Essentials;;VFE\nrad-pack-lite;280;;RAD pack(LITE);;\nmanufactio;281;;Manufactio;;\nworld-of-dragons-ii;282;;World of Dragons II;;\ntensura-unleashed;283;;Tensura Unleashed;;\nskyopolis-4;284;;Skyopolis 4;;\nftb-oceanblock;286;;FTB OceanBlock;;\ntolkiencraft-iii-return-to-middle-earth;287;;托尔金工艺3 - 重返中洲;TolkienCraft III - Return to Middle-earth;TC3\ndelivery-inc;288;;Delivery Inc.;;\npoetica;289;;Poetica;;\ndivine-journey-2;290;;神圣之旅2;Divine Journey 2;DJ2\nall-the-mods-7;291;;All the Mods 7;;ATM7\nskyfactory-one;292;;SkyFactory One;;\nfeed-the-singularity;293;;喂养奇点生存;Feed the Singularity;\ncrucial;294;;Crucial;;C\n;295;;废土文明;Wasteland civilization;\n;296;;SkyFactory 2;;\na-bit-of-everything-official;297;;A Bit Of Everything;;ABOE\nresonantrise-5;298;;Resonant Rise 5;;\nfabric-exploration;299;;Fabric Exploration;;\ninto-the-betweenlands;300;;Into The Betweenlands;;ITB\nannals-mechanizac;301;;尘烬:消失世界;Annals:Mechanizac;ANS\ntechopolis;302;;Techopolis;;\nanother-quality-modpack-2;303;;Another Quality Modpack 2;;AQM2\ngo-create;304;;Go Create;;\nrlcraft-remastered;305;;RLCraft Recrafted;;RLCR\ncave-factory;306;;洞穴工厂;Cave Factory;\nnaruto-modpack-reforged;307;;Naruto Modpack Reforged;;\neuphoriccuriosity;308;;Euphoric Curiosity;;\nbeyond-the-void-2;309;;Beyond The Void 2;;\njourney-in-the-sky;310;;Journey In The Sky;;\nwinter-regrowth;311;;Winter Regrowth;;\ncreate-above-and-beyond;312;;机械动力：永无止境;Create: Above and Beyond;CAB\nmonumental-experience;313;;Monumental Experience;;\nchroma-technology-2;314;;色度科技2;Chroma Technology 2;\nhuntingmonstersmodpack;315;;Hunting Monsters The Witcher Modpack;;\ncuboid-outpost;316;;Cuboid Outpost;;\nragnamod-vi;317;;Ragnamod VI;;\ncrundee-craft;318;;Crundee Craft;;\n;319;;Yogscast Complete Pack;;\ninsanecraft-modpack;320;;InsaneCraft;;\nall-the-magic-spellbound;321;;All the Magic Spellbound;;ATMS\nodyssey-space-a-new-beginning;322;;Odyssey: Space - A New Beginning;;\nnuts-and-bolts;323;;Nuts and Bolts;;\nvault-hunters-official-modpack;324;;Vault Hunters;;\n;325;;FTB Ultimate: Anniversary Edition;;\nenigmatica8;326;;Enigmatica 8;;E8\nall-of-fabric-5;327;;All of Fabric 5;;AOF5\nlast-days-of-humanity;328;;人类末日;Last Days of Humanity;LDH\nunderdog;329;;弱者;Underdog;\nmc-dripcraft;330;;DripCraft;;\natr1;331;;All the RPMTW 1;;ATR1\ntechno-ages;332;;技术时代;Techno Ages;\nexpert-creation;333;;Expert Creation;;\nspookyjam-2021-forge-edition;334;;SpookyJam 2021 - Forge Edition;;\ndigsite;335;;DigSite;;\ndark-souls-simulator;336;;Dark Souls Simulator;;\nadvent-of-ascension-plus;337;;Advent of Ascension Plus;;\nbounds;338;;Bounds;;\nreimagined-rlcraft;339;;RLCraft - Reimagined (not official);;\n;340;;大轩整合包;Biggest Xuan Pack;BxP\nplunger;341;;Plunger;;\nfoolcraft-3;342;;FoolCraft 3;;\n;343;;命定之旅;Deep To Destiny;DTD\none-thousand-years-after-the-apocalypse;344;;One Thousand Years After the Apocalypse;;\nall-the-mods-3-remix;345;;All the Mods 3 - Remix;;ATM3R\nultimate-progression;346;;Ultimate Progression;;\nbenchmark-ii;347;;Benchmark II;;\nragnamod-ii-reborn;348;;Ragnamod II Reborn;;\nragnamod-iii;349;;Ragnamod III;;\nragnamod-iv;350;;Ragnamod IV;;\npokehaan-craft;351;;Pokehaan Craft;;\nall-the-sky;352;;All The Sky;;\nall-the-mods-expert;353;;All the Mods Expert;;ATM1E\nall-the-mods-lite;354;;All the Mods Lite;;ATM1L\nall-the-mods-expert-remastered;355;;All The Mods Expert: Remastered;;\nall-the-mods-0;356;;All the Mods 0;;ATM0\nall-the-mods-fabric;357;;All the Mods Fabric;;ATMF\nthe-pixelmon-modpack;358;;The Pixelmon Modpack;;\nbetter-mc-skyblock;359;;更好的我的世界空岛;Better Minecraft Skyblock;BMCS\nage-of-fate;360;;命运时代;Age of Fate;AOF\nminecraft-kingdoms;361;;MC Kingdoms;;\n;362;;幻想旅程;Imaginary Journey;\nmoonlitmyths;363;;月耀神话;Moonlit Myths;\n;364;;晴天整合;Sunny Modpack;\nvalhelsia-4;365;;Valhelsia 4;;\nanother-quality-modpack-3;366;;Another Quality Modpack 3;;\nnomifactory;367;;Nomifactory;;\nallusiveness;368;;Allusiveness;;\nthe-way-of-reincarnation;370;;转生之路;The Way of Reincarnation;\nsky-friends;371;;Sky Friends;;\nmagic-pouch;372;;魔法包裹;Magic Pouch;\nmagica-infinitum-iii;373;;Magica Infinitum III;;\nardentcraft-the-beginning;374;;ArdentCraft: The Beginning;;\n;375;;神秘启示录2：梦魇般的回忆;Nightmare Memories Pack;\njurassic-world-reborn;376;;Jurassic World Reborn;;\n;377;;FTB Omnia;;\nfantazy-tech-expert-mode;378;;幻想科技;Fantazy Tech;\ncrainer-craft-2;379;;Crainer Craft 2;;\nall-the-forge-8;380;;All The Forge 8;;\ncolony-space-modpack;381;;KN: Colony New Worlds;;\nskyopolis-3;382;;Skyopolis 3;;\naboe-3;383;;A Bit of Everything 3;;ABOE3\nsky-alchemist;384;;天空炼金术士;Sky Alchemist;\n;386;;字字落实;;\nnuclear-earth-reirradiated;387;;核地球：再辐照;Nuclear Earth: Reirradiated;\ngod-block;388;;God Block;;\nfaruk-v;389;;Faruk V;;\nkreezcraft-presents-cobbleblock;390;;Cobbleblock;;\n;391;;寄生虫：血染黎明;Parasites: Blood Dawn;PBD\nminecraft-trillionaire;392;;亿万富翁;Minecraft Trillionaire;\ngregtech-community-pack;393;;GregTech Community Pack;;GCP\n;394;;诸神匠心;Tinkers Heartwork and Deities;THD\ncreate-yourself;395;;Create Yourself;;\nstarrynight-pray-stp;397;;星夜祈临;StarryNight Pray;StP\nrustic-waters-ii;398;;乡村水域2;Rustic Waters II;\nenigmatica6expert;399;;Enigmatica 6: Expert;;E6E\ncelestial-journey;400;;星空之旅;Celestial Journey;\n;401;;天空舞台;Skyblock Create Show;\nproject-architect;402;;Project Architect;;\ngreed-the-resource-3-factory;403;;Greed the Resource 3: Factory;;\nf-o-g-of-magic;404;;F.O.G. of Magic;;\n;405;;僵尸生存;Mike's Zombie Survival;MZS\ndarkrpg;406;;DarkRPG;;\n;407;;征途;Conquest journey;CJ\n;408;;创生银莲;Create Anemone;\nkimetsu-no-yaiba-demon-slayer-mod-pack;409;;鬼灭之刃;Kimetsu no Yaiba;\n;410;;七曜之际;FOR A WEEK;\nnot-too-complicated;411;;Not Too Complicated;;NTC\nfiefdom;412;;列土封疆;Fiefdom;\nreinforce-reality;413;;死亡工艺&重生;Deathcraft & Rebirth / Reinforce Reality;\nafterlife;414;;The Afterlife;;\ncreate-skyblock;415;;CREATE Skyblock;;\nlightcraftprojectlite;416;;Light Craft;;\nepoch-runner-formerly-nuclear-beyond-modpack;417;;时代领跑人;Epoch Runner;\n;418;;星海征途;The Journey of Star Sea;TJoSS\nroguelike-adventures-and-dungeons-2;419;;冒险与地牢2;Roguelike Adventures and Dungeons 2;RAD2\nmultiskyblock;420;;模块化空岛;MultiSkyBlock;MSB\ncreate-live-3;422;;Create Live 3;;\nmagnificia;423;;Magnificia;;\n;424;;魔法金属-新手向导;ManaMetalMod - WithNewbHelper;\n;425;;奇美拉;Chimera;CMA\nchroma-sky-2;426;;色度空岛2;Chroma Sky 2;\ninfinity-fabric;427;;无限;Infinity;IF\nenigmatica-2-expert-extended;428;;Enigmatica 2: Expert - Extended;;E2E-E\nmercury-landing;429;;水星迫降重制版;Mercury landing;\nmagic-sky;430;;Magic Sky;;\n;431;;科技飞升;Rise of Tech;RoT\nnot-too-complicated-2;432;;Not Too Complicated 2;;\nlow-res-trailer-recreation;433;;Low Res Trailer Recreation;;\nshattered-ring;434;;Shattered Ring;;\n;435;;FTB Chocolate;;\n;436;;Intergalactical Modpack;;\ndude-wheres-my-blocks;437;;谁动了我的合成表;Dude where's my blocks;\nproject-osud-survive-the-wasteland;439;;Project Osud - Survive the apocalyptic Wasteland;;\nproject-isothermal-expert;440;;Project Isothermal Expert;;\nminecraft-battle;441;;Battle with friends;;\n;442;;奇点工厂;Factory of Singularity;FoS\n;443;;异界旅者;Traveller of the World;TOW\nproletaria-expert;444;;无产阶级：专家模式;Proletaria Expert;\nskyopolis-5;445;;Skyopolis 5;;\n;446;;Innovation Timespace 5;;\n;447;;氘;Deuterium;D\nbliss;449;;Bliss;;\ndecursio-project;450;;The Decursio Project: Expert;;\n;451;;FTB Presents Direwolf20 1.18;;\nrestrict-skyblock;452;;Restrict Skyblock;;\nlcw;453;;失落文明：野原;Lost_Civilization:Wilds;LCW\npromo-cinematic;454;;Minecraft trailer recreation | Promo-Cinematic Modpack;;\nthe-last-one;455;;最后一人;The Last One;TLO\n;456;;帕斯特之梦;PasterDream;PD\nlost-midgard;457;;失落的米德加尔特;Lost Midgard;LM\n;458;;真实空岛;Real Island;RI\nabyss-hunter;459;;深渊猎人;Abyss Hunter;AH\nblackstone-block;460;;Blackstone Block;;\nstone-technology;461;;Stone Technology;;\njurassic-world-reborn-2;462;;Jurassic World Reborn 2;;\nmcdoom-modpack;463;;MCDoom Modpack;;\npoko;464;;Poko;;\n;465;;永无止境：重缝;Cabricality;CABF\naperomods;466;;Aperomods;;APM\n;467;;XPlus 2.0 基础整合;XPlus 2.0 Modpack;X+\ncrazy-crave-5;468;;Crazy Crave 5;;\nsky-research;469;;Sky Research;;\n;470;;魔力做功;Work Done by Mana;WDM\n;471;;Beyond Reality;;\nslcp4;472;;微笑草坪4;SmileLawn 4;SLCP4\ncrazy-craft-updated;473;;Crazy Craft Updated;;\nplanetary;474;;行星;Planetary;\nslcp5;475;;微笑草坪5;SmileLawn 5;SLCP5\n;476;;寂静传说;tales of silent;TOS\nWightcraft;477;;Wightcraft;;\nandromeda-automation;478;;Andromeda Automation;;\n;479;;MITE：绝境求生;MITE Desperate Survival;\nprotect-the-monolith;480;;Protect The Monolith;;PTM\ncorsair-the-seven-treasures;481;;Corsair: The Seven Treasures;;\njetpack-cat;482;;喷气背包猫;Jetpack Cat;\n;483;;随心石艺;Random Stone;RS\n;484;;贪婪整合包II：重生;GreedyCraft II:Reborn;GC2\n;485;;机械空岛;Create to Sky;CtS\n;486;;兔头：超级惊变;jokbit super apocalypse;JSA\nmineshafts-monsters-lite;487;;Mineshafts & Monsters - LITE;;\n;488;;AELife;;AEL\nmineshafts-monsters-adventure;489;;Mineshafts & Monsters - Adventure;;\ndimensionhopper;490;;Dimension Hopper：The Fall;;\ntechnology-empty-island;491;;科技空岛;RxanGrben;\ngreed-the-resource-4-infinity;492;;Greed the Resource 4 - Infinity;;\ncaveopolis;493;;Caveopolis;;\n;494;;坚毅征途;HardCraft;HC\n;495;;怀末韵云;Institutions mz;MZ1\nfeed-the-factory;496;;Feed the Factory;;FTF\nfantasy-realm;497;;幻想领域;Fantasy Realm;\nhorror-craft;498;;Horror Craft;;\n;499;;道阻且长;Copy A JB;CAJB\n;500;;重构计划;;\nultimate-origins-modpack;501;;Ultimate Origins Modpack;;\nart-of-enigma;502;;机巧花苑;Art of Enigma;AoE\nencrypted_;503;;Encrypted_;;\nother-body;504;;Other Body;;\n;505;;兔头：地狱前线;jokbit nether frontline;JNF\n;506;;莱姆星云;Lime Nebula;LN\nvoid-genesis;507;;Void Genesis;;\ncreate-flavored;508;;Create Flavored;;\nterrafirmagreg-vintage;509;;TerraFirmaGreg;;TFG\ncreate-astral;510;;机械动力：星辰;Create: Astral;\n;511;;起源创造;CreateOrigin;CO\nrandom-item-skyblock-recondite-i;512;;Random Item Island - Recondite I;;\ninfinity-foundation;513;;Infinity Foundation;;\nsky-to-void;514;;Sky to Void;;\n;515;;群星构析;Analyses Of Galaxy;ANOG\nmechanical-mastery;516;;Mechanical Mastery;;MM\nescape-from-2020;517;;逃离2020;Escape From 2020;\nlunaria-expert;518;;Lunaria Expert;;\nnomi-ceu;519;;Nomifactory CEu;;Nomi-CEu\n;520;;乐事 · 起源;Delight ·  Origins;DO\ntankoptimization;521;;坦坦优化;Tankoptimization;\n;522;;InfinityCraft;;IC\npick-the-stars;523;;摘星;Pick The Stars;PTS\nearth-technology;524;;Earth Technology;;\n;525;;核乐不为;;ZMC\nftb-stoneblock-3;526;;FTB StoneBlock 3;;\ncreate-mod-plus;527;;Create +;;\nmultiplayer-optimization;528;;RawDiamondMC的优化包;RawDiamondMC's optimization pack;MPO\nchroma-endless;529;;Chroma Endless;;\nragnamod-vi-in-the-sky;530;;Ragnamod VI In The Sky;;\n;531;;合成蓝图;;\nall-the-mods-8;532;;All the Mods 8;;ATM8\n;533;;斯洛文尼亚发生了什么;What Happened in Slovenia;WHiS\nfos-community-edition;534;;奇点工厂1：社区版;Factory of Singularity:Community Edition;FOSC\n;535;;洋之叹息;Ocean Sigh;OcS\n;536;;提尔3：九天往事;Tover 3:the Dream;T3\n;537;;沉没的亚伯拉罕;Sunken Abraham Craft;SAC\n;538;;跃迁计划;CT TransitionPlan;CTTP\nastropolis;539;;Astropolis;;\n;540;;筑梦者;Builder Dream;\n;541;;尘埃传说;;\nmagiculture-2;542;;Magiculture 2;;\n;543;;交错维度2;Among Dimensions 2;AD\nhexteria-skies;544;;Hexteria Skies;;\n;545;;Omniworld;;\nal-legacy;546;;冒险生活-L;Adventure Life - Legacy;ALL\nskymachina;547;;SkyMachina;;\nall-of-fabric-6;548;;All of Fabric 6;;AOF6\n;549;;机械动力：锄与锤;;CHH\nsky-bees-2;550;;蜜蜂空岛2;Sky Bees 2;\ntechnologicaljourney;552;;Technological Journey;;TJ\n;553;;末世工匠;End-Time Craftsmen;EdTC\n;554;;光芒消逝之日;;VL\ngregic-skies;555;;Gregic Skies;;\n;556;;勇者之章;Chapter of Yuusha;CY\n;557;;齿轮颂歌：暮光;Gear Carol;\npluma;558;;Pluma, a Journey to the Future!;;Pluma\nall-the-mods-7-to-the-sky;559;;All the Mods 7 - To the Sky;;ATM7S\n;560;;月见苍穹传说;TskimiCanopyCraft;TSCC\n;561;;最后的战役：劫后余生;The Last Stand:Aftermath;LSA/TLS\n;562;;小猪配平;Peppa Pig's Burning Broom;2P2B\nzerblands-remastered;563;;Zerblands-Remastered;;\nmanufactio-2-nuclear-edition;564;;Manufactio 2 - Nuclear Edition;;\n;565;;Rewind Upsilon;;\n;566;;FTB Legend of the Eyes;;\n;567;;失落钶罗;Chrost;CRT\n;568;;FTB Inferno;;\nprovefrom;569;;忘却之刻;Provefrom;PvF\n;570;;我不干了;;\n;571;;匠心魔艺;Wizardry Tinkers;WT\ndarkrpg-forge;572;;DarkRPG Forge;;\nexpert-of-magic-art-world;573;;魔艺世界;Expert of Magic Art World;EOMAW\n;574;;龙境;Dragoncraft;DGC\nnuclear-radiation-2;576;;核污染二;nuclear radiation 2;NR2\n;577;;白狐铃的奇思妙想;Arctic Fox Ling Fantasy;AFLF\n;578;;MineSpace;;MS\n;580;;月见清兰的个人优化;TskimiSeiran's Idea;TkSrI\n;581;;兔头：真视世界;;\n;582;;灾厄战争;;\nmultiblock-madness-2;583;;MultiBlock Madness 2;;MBM2\n;584;;OptiFab;;\n;585;;模块化科技：探索;ModularTech Odyssey;MTO\n;586;;简单优化;Simply Optimized;SO\ne2eu;587;;Enigmatica 2 Expert Unofficial;;E2Eu\ne2eus;588;;Enigmatica 2 Expert Unofficial Skybound;;E2EuS\n;589;;龙之冒险;;\ngregtech-quantum-transition;590;;格雷：量子跃迁;GregTech:QuantumTransition;GTQT\nthe-winter-frontier;591;;冬境边域;The Winter Frontier;TWF\n;592;;林深不知处;NO, is killed by... Twlight Forest?;NON\n;593;;太阳机械厂;Create Sun;CS\ndawn-craft;594;;破晓之界;DawnCraft: Echoes of Legends;DC\naof-presents-skylore;595;;AOF: Skylore;;\nkeletus-gregpack;596;;可乐兔的格雷整合包;Keletu's GregPack;GtMagic\nftb-skies;597;;FTB Skies;;\ntnp-limitless-5;598;;TNP Limitless 5;;\ncreate-back-on-track;599;;Create: Back on Track;;\n;600;;重构：炼金术;Refactoring: Alchemy;RA\n;601;;地心;Geocentric;GOC\n;602;;巫师·古老恶意;Wizard against wicked;WaW\n;603;;进化太阳生存;Evolutionary Sun Survival;ESS\nchangfeng;604;;长峰漫路;Long peak Long road;LPR\ntechopolis-2;605;;科技城2;Techopolis 2;\n;606;;Arcomua 原版整合;Arcomua Modpack;ARC\n;607;;UnityCraft;;UC\n;608;;灵犀;;\n;609;;乐土韶光;Delighted Time on Another Land;DoA\ncryptopolis;610;;Cryptopolis;;\nreaching-darkness;611;;冥行之至;Reaching Darkness;RD\ncreate-beyond-earth;612;;Create: Beyond Earth;;\n;613;;原环之理;Science of Cycles;\n;614;;Additive;;\n;615;;无人荒漠-新世界;;\nnew-game-modpack-release;616;;New Game - An Improved Vanilla Experience;;\nprodigium-reforged;617;;Prodigium Reforged (Terraria Pack);;\n;618;;FTB Presents Integration by Parts DX;;\nstriving-standoff;619;;抗争之际;Striving Standoff;SS\ngun-fire-survival;620;;枪火生存;Gun fire survival;\nanother-rpg-pack-forge;621;;AnotherRPG;;\neternal-night-modpacks;622;;永夜;Eternal Night;ETN\nall-the-mods-9;623;;All the Mods 9;;ATM9\nspoornpack;624;;Spoornpack;;\n;625;;起源RPG;;ORPG\naruori;626;;织彩钓月;Aruori;\n;627;;冰河物语;;\nhommage;628;;Hommage;;\ndays-to-craft;629;;Seven Days To Craft;;\nemcworld;630;;质能世界;EMCWorld;\npyramid-scheme;631;;Pyramid Scheme;;\ncrop-block;632;;Crop Block;;\n;633;;月亮工厂;Moon Create factory;MCF\n;634;;Technical Electrical;;\ncobblemon-forge;635;;Cobblemon Official Modpack;;\nsbeevs-industrial-revolution;636;;Sbeev's Industrial Revolution;;\n;637;;Gensokyo Reimagined QOL;;GR\nstatech-industry;638;;StaTech Industry;;\nvault-hunters-1-18-2;639;;宝藏猎人3;Vault Hunters 3rd Edition;\nsurvival-hard-rock;640;;生存硬摇滚;Survival Hard Rock;\naged;641;;Aged;;\nthe-lost-era-modpack;642;;The Lost Era;;\nvault-hunters-official-modpack;643;;Vault Hunters 2nd Edition;;\n;644;;Sodium Plus;;\n;645;;霜叶表达法;Wither Expression;WE\ncreate-live_4;646;;Create Live 4;;\n;647;;The Farm;;\nvalhelsia-5;648;;Valhelsia 5;;\ncaves-cliffs-plus;649;;洞穴与山崖：增强版;Caves & Cliffs: Plus;CC+\nenigmatica9expert;650;;Enigmatica 9: Expert;;E9E\nthe-lord-of-ascension;651;;虚无之主;The Lord of Ascension;TLoA\n;652;;寄生科技;;PTY\nrise-of-tech-2;653;;科技飞升2;Rise of Tech 2;RoT2\n;654;;虚空居者;Void Dweller;\n;655;;格雷科技精简版;GregTech Lite;GTLite\n;656;;持之永恒;Stay With Eternity;SWE\neternal-delight2-0;657;;永恒乐事2.0;Eternal Delight2.0;ED\ngregtech-beyond;658;;GregTech：Beyond;;\nuniversio;659;;UniversIO;;\nvalhelsia-6;660;;Valhelsia 6;;\nall-the-mods-volcanoblock;661;;All the Mods: Volcano Block;;ATMVB\ngregtronix-revoluton;662;;Gregtronix Revolution;;GR\n;663;;空岛物语;Empty Island RPG;EIRPG\nprominence-1-rpg;664;;卓越 I;Prominence I;\nmultiskyblock-2;665;;模块化空岛2;MultiSkyBlock 2;MSB2\n;666;;The Minehattan Project;;\n;667;;万象天工;Millions of Heavens industries;MOHI\ncreative-drawers-producer;668;;Creative Drawers Producer;;CDP\n;669;;魔幻大陆;Enchanted Lands;\ncursed-walking-a-modern-zombie-apocalypse;670;;Cursed Walking;;\nsteam-punk;671;;SteamPunk;;\nsupersymmetry;672;;Supersymmetry;;SUSY\nopskies;673;;OPSkies;;\nbetter-overworld-bow;674;;Better Overworld;;BOW\nopskies-2;675;;OPSkies 2;;\njourney-watch-ytz;676;;征途 守望;Journey Watch;JW\n;677;;Arcomua Lite 原版整合;Arcomua Lite;AL\nincremental-industries;678;;Incremental Industries;;\n;679;;海王星计划：重构;NeptunePlan:Reboot;NPR\n;680;;求生日记：荒野;Survival Diary: Wilderness;SWD\na-new-technological-era;681;;A New Technological Era;;ANTE\n;682;;超能力之旅;Meet Your Superpower;MYS\nmusketeer;683;;Musketeer - One for All;;\n;684;;匠造之传;The Legend of Tinker;TLT\n;685;;我的骑士：怪物大乱斗;;\n;686;;塔可努的原生优化整合包;TechNoob's Vanilla Boost Pack;TNVBP\n;687;;灵茗屿空岛;LMY SKY;\n;688;;红石生电优化;Redstone Survival Optimization;RSO\n;689;;恐惧深渊;Abyss of Fear;AoF\n;690;;德雷戈拉旅人日记;Diary of Dregora's Traveller;DoDT\nnwtcraft;691;;近西之旅;NwtCraft;Nwtc\n;692;;Apocalypse Pack;;\naeropolis;693;;Aeropolis;;\nproject-sacrifice;694;;Project Sacrifice;;\nchosens-modded-adventure;695;;Chosen's Modded Adventure;;\nciscos-fantasy-medieval-adventure-rpg;696;;Cisco's Fantasy Medieval RPG [Lite];;\n;697;;命运之轮;Wheel of Fate;\nall-the-mods-gravitas2;698;;All The Mods - Gravitas²;;ATMG2\nenigmatica9;699;;Enigmatica 9;;E9\n;700;;死亡突围;Deadout;DT\ncreate-arcane-engineering;701;;机械动力：奥术工程;Create: Arcane Engineering;CAE\nchroma-endless-2;702;;Chroma Endless 2;;\ntechrevolution;703;;TechRevolution;;\ncabin;704;;Create Above & Beyond In Newer;;CABIN\naoc;705;;All of Create;;\n;706;;交错维度3;Among Dimensions 3;AD\ngregfactory-sky;707;;GregFactory Sky;;GFS\n;708;;飞翔之路4;Flying Road 4;\n;709;;星月传说;Celestial Legend;CL\nctc-create-the-creation;710;;Create：the Creation;;CTC\ncreate-perfect-world;711;;Create: Perfect World;;\nnew-simple-mods;712;;New Simple Mods - Easy to Understand;;\n;713;;灾变录;Catastrophe Record;CR\nsky-greg;714;;Sky Greg;;\ndeceasedcraft;715;;亡者世界;DeceasedCraft - Urban Zombie Apocalypse;\n;716;;生欲:求生;Vivere The Zombie Apocalypse;VZA\ncreate-planetary;717;;Create Planetary;;\nmaster-mabaoguo;718;;Master mabaoguo;;\ngregorian-nightmares;719;;Gregorian Nightmares Industrially Fabricated;;\n;720;;史诗的地下城;Dungeons Of Fantasy;\n;721;;Emotional Damaged Minecraft;;EmDM\n;722;;脆骨症;No Flesh Within Chest;NFWC\nall-in-one-create;723;;All in One: Create;;AIOC\nthe-backrooms-survival;724;;The Backrooms Survival;;\n;725;;13年前的梦;Dreams 13 years ago;\ncookiecrafter;726;;CookieCrafter;;\n;727;;理想国：科克肖特;Utopia:Cockshott;UCST\nbcg;728;;BigChadGuys Plus;;BCG+\n;729;;异世界探险;ISEKAIBOUKEN;YSB\ncreate-explore-c-e;730;;Create & Explore;;C&E\ntwistercraft;731;;TwisterCraft;;\nall-the-horror;732;;恐怖生存;All The Horror;\nthe-backrooms-liminal-spaces;733;;The Backrooms;;\n;734;;伊甸空天;Eden Aerospace;EAS\natlanabyss;735;;亚特兰深渊;AtlanAbyss;\n;736;;龙境II;Dragon Odyssey;DO\n;737;;龙之冒险：新征程;;\nall-the-mods-9-no-frills;738;;All the Mods 9 - No Frills;;ATM9NF\ntoms-cobblemon;739;;Tom's Cobblemon;;\nlife-in-the-village-3;740;;Life in the village 3;;LitV3\n;741;;联缘：NEXUS;;\n;742;;Create: Cinematic;;\n;743;;神秘纪元;Thaumic Epoch;ThE\n;745;;FTB Genesis;;\ncook-in-skyblock;746;;天空厨房;Cook in skyland;CiS\n;747;;被恐惧支配 1;Dominated by Fear 1;DoF\nnew-vanilla-newhome;748;;新的原版;New Vanilla;NVA\n;749;;峦屿梦星群;Terrafirma Rescue Unofficial;TFRU\nstoneopolis;750;;Stoneopolis;;\nftb-skies-expert;751;;FTB Skies Expert;;\nwmshd-pack-what-mojang-should-have-done;752;;What Mojang Should Have Done;;WMSHD\n;753;;无限:重生;Infinity-Reborn;IF2\n;754;;随心之旅;spontaneous journey;\noxmodpack;755;;OxMODPACK 8 +Online;;\ngregtech-community-pack-modern;756;;GregTech Community Pack Modern;;GCPM\n;757;;锻造大师4;Master Blacksmith4;MB4\n;758;;科技用于探索世界;Technology Exploration World;\n;759;;幻梦闲笔;Aetherial Voyage;\nfear-the-mist;760;;雾中的恐惧;Fear The Mist;\ntekkit-resurrection;761;;Tekkit The Resurrection;;\n;762;;Cup Pineapple UBLU;;CPU\nftb-arcanum-institute;763;;FTB Arcanum Institute;;\ncreate-haven-adventures;764;;Create: Haven Adventures;;\nmodecube;765;;ModéCube;;\nelectrictechnocraft;766;;ElectricTechnoCraft;;\ncreate-2-mekanism;767;;Create 2 Mekanism;;C2M\nrandblock;768;;随机空岛;RandBlock;RD\n;769;;格雷科技休闲版;GregTech  Leisure;GTL\nfrozenopolis;770;;Frozenopolis;;\ninfernal-rpg;771;;Infernal Origins;;\n;772;;天狼星;Sirius;\n;773;;悠沙海;Ethereal Sand Sea;ESS\n;774;;AIR for JAVA;;AFJ\n;775;;Create Extra;;\nfrs-optimisation-for-survival;776;;Fr的生存优化;Fr's Optimisation for Survival;FROS\nexile-magic-constructor;777;;Exile Magic Constructor;;EMC\n;778;;Landscapes Reimagined Genesis;;\nal-nl;779;;冒险生活 - 新生活;Adventure Life - New Life;AL-NL\nharvista;780;;拾穗;Harvista;\n;781;;时间之书 : 星之旅行;BookofTime StarTravel;BKSL\ncreate-applied-energevation;783;;机械动力：应用能源时代;Create: Applied Energevation;CAEV\n;784;;新星工程：世界;Nova Engineering - World;NEW\ncreate-chronicles-bosses-and-beyond;785;;Create Chronicles: Bosses and Beyond;;\n;786;;Adrenaline;;\nforget-me-not;787;;勿忘我;Forget Me Not;FMN\nmodpack-essentials;788;;整合优选;Modpack Essentials;ME\ndesertopolis;789;;Desertopolis;;\nomniopolis;790;;Omniopolis;;\n;791;;战斗的法则;The Law of Battle;\nfactory-of-singularity-ii-colorfulness;792;;奇点工厂II：绚彩;Factory of Singularity II: Colorfulness;FoS2\nprey-beta;793;;Prey;;\n;794;;魔法之旅;Magic journey;MJ\n;795;;轻松冒险;Easy adventure;EAT\n;796;;魔幻大陆2;Enchanted Lands 2;\n;797;;艾利克斯的世界;Alexcraft;AC\nterrafirmacraft-wiph-walden-lagu;798;;瓦尔登湖畔;Wiph Walden Lagu;WWL\naocfabric;799;;All of Create Fabric;;\ncraftingcraft;800;;合成工艺;CraftingCraft;\n;801;;感染者手册;Handbook for Infected People;HIP\n;802;;居无定所—中世旷野;;\n;803;;宾果难题;Bingo Madness;\nchemillas-reborn;804;;化学：重生;Chemilla's Reborn;CR\n;805;;群峦传说·异世界行纪;Terra Firma Craft:Travel in Another World;TFC:TAW\nmilkyway;806;;Create: Milkyway;;\nfarming-crossing-4;807;;农业森友会 4;Farming Crossing 4;\nhardrock-terrafirmacraft-4-realistic-extreme;808;;HardRock TerraFirmaCraft;;TFCH/HTFC\nterrafirma-rebirth;809;;群峦：重生;TerraFirma: Rebirth;TFCR\ncreate-live-5-skyblock;810;;Create Live 5;;\n;811;;万象征程;Cosmos of Endless Allegory;CEA\n;812;;远古之遗;;\n;813;;奈の奇妙冒险;Kotona's Mystery Adventure;KSMA\n;814;;战争魔匠;war craftman;WC\n;815;;命定IV;unique destiny IV;\n;816;;Wither Storm Enhanced;;\n;817;;朝花夕拾;Dawn Blossoms Plucked at Dusk;\nla;818;;胶兽大冒险;Latex Advanture;LA\nmystics-monstrosity;819;;Mystic's Monstrosity;;\n;820;;更完美的MC;More Perfect Minecraft;MPMC\n;821;;动画世界：烂柯人;AnimateCraft: Lankeren;\n;822;;New Create;;NC\nland-of-memories;823;;回忆之地;;LOM\n;824;;异世界冒险2;;\nragnamod-vii;825;;Ragnamod VII;;\npath-of-truth;826;;真理之路;Path of Truth;PoT\n;827;;TFC生活质量更新;;\nall-the-mods-9-to-the-sky;828;;All The Mod 9 - To The Sky;;ATM9S\n;829;;魔金：探秘;ManaMetalMod Exploration;M3E\nfallenpetals;830;;落英;FallenPetals;FP\ndungeon-heroes;831;;Dungeon Heroes (RPG Series);;\nplay-as-dragon-gothic-edition;832;;Play as Dragon: Gothic Edition;;\nskyblock-burgeria;833;;空岛汉堡店;Skyblock Burgeria;\ncrafting-craft-2;834;;合成工艺2;Crafting Craft 2;\n;835;;僵尸入侵100天;Zombie Invade 100 Days;ZI100D\ncompact-world-expert;836;;Compact World Expert;;CWE\n;837;;宁然一隅;A Fragment of Peace;AFoP\neasy-create-modpack;838;;Easy Create;;\n;839;;流线鱼概念;Fish Spi Rally;FSRMP\nmeatballcraft;840;;肉丸工艺，次元飞升;MeatballCraft, Dimensional Ascension;MBC\nseaopolis-submerged;841;;海之城: 潜没水中;Seaopolis: Submerged;\n;842;;空中厕所;PoopSky;\ncompact-sky;843;;收缩空岛;Compact Sky;\nback-to-back;844;;背靠背;Back to Back;BTB\nane2;845;;冒险与探索;Adventure & Exploration;AnE\n;846;;复兴;Renaissance;\nlive-like-a-castaway;847;;木筏求生2;Raftcraft2;RC2\nmermaid-legend;848;;人鱼传说;Mermaid legend;\n;849;;文明时代;;Aoc\ncreate-lost-and-renaissance;850;;机械动力：失落与复兴;Create Lost and Renaissance;CLR\nall-the-mods-10;851;;All the Mods 10;;ATM10\n;852;;格雷伊甸：建造;GregEden:Building;GEB\n;853;;迷失时代;LOST ERA;LE\ncreate-alchemy-plan;854;;机械动力：炼金计划;Create：Alchemy Plan;CAP\ndeus-ex-machina;855;;Deus Ex Machina;;\ncreate-prepare-to-dye;856;;Create Prepare to Dye;;\n;857;;All The Fabric 5;;\ncreate-leisurely-delight;858;;机械动力：悠然乐事;Create Delight;CD\n;859;;蔚蓝悠悠;Azure Leisurely;AL\ninfinityevolved-reloaded;860;;Infinity Evolved: Reloaded;;\n;862;;佛罗伦萨迷思;Fantasy in Florence;FIF\ncraftoria;863;;Craftoria;;\nmechanic-craft;864;;MechanicCraft;;\n;865;;无用之人-群星;Good For Nothing:Stellaris;GFN\n;866;;打金无限;;\nciscos-adventure-rpg-ultimate;867;;Cisco's Fantasy Medieval RPG [Ultimate];;\njour-avancee-daylight;868;;Jour avancée/DayLight;;DL\n;869;;勇者之章 Ⅱ;Chapter of Yuusha II;CY2\n;870;;Beta 1.7.4;;B1.7.4\n;871;;神不在的星期天;God Without Create;GWC\ncreatea-colony;872;;Create'a Colony;;\n;873;;黑暗深处;Deep Dark;\n;874;;自在极境;Free Extreme Realm;\nall-of-fabric-7;875;;All of Fabric 7;;AOF7\nftb-neotech;876;;FTB NeoTech;;\n;877;;Minecraft+;Minecraft plus;MCP\n;878;;龙珠Super;Dragon Ball Super;\ncreate-vanilla-exploration;879;;Vanilla Create Exploration;;\nprogress;880;;Progress: Create;;\naeon-automata;881;;Aeon Automata;;AA\n;882;;被恐惧支配2：收容失效;Dominated by Fear 2 Containment Breach;DoF2\ntensioncraft;883;;剑拔弩张之时;;TC\nproject-architect-2;884;;Project Architect 2;;\n;885;;接入铁路网络：我的世界铁路;MTR Railway Network Access;RNA-MTR\ncreate-mekanized;886;;Create: Mekanized;;\ncreate-perfect-world-2;887;;Create: Perfect World 2;;\ncreate-stellar;888;;Create Stellar;;\nmonifactory;889;;Monifactory;;\ncreate-factory-builder;890;;Create: Factory Builder;;\n;891;;群峦传说：宝宝快乐版;TerraFirma: Baby Happy Edition;TFBH\n;892;;阶段: 残骸;Phase: the Debris;PTD\n;893;;AE管道;AE Pipes;APE\nskyt-2;894;;SkyT 2;;\nstagecraft;895;;StageCraft;;\n;896;;我的世界从来不简单;Minecraft Is Never Easy;MINE\nctnh;897;;机械动力：新视野;Create: New Horizon;CTNH\nagora-interregnum;898;;Agora Interregnum;;\nevolving-technology;899;;Evolving Technology;;\nenlightened-6-expert;900;;Enlightened 6 Expert;;En6E\n;901;;历久尤新;Old But New;OBN\n;902;;继承;Inherit;IN\nlucky-world-invasion;903;;幸运世界入侵;Lucky World Invasion;LWI\nftb-interactions-remastered;904;;FTB Interactions Remastered;;FTBIR\n;905;;崩坏-新生;Broken-New Life;BNL\n;906;;持之永恒重生;Stay With Eternity Rebirth;SWER\nshadow-awakening;907;;暗影觉醒;Shadow Awakening;SAG\n;908;;理想箱庭物语;Second Eden;SE\nfinality-genesis-1-20-1;909;;Finality Genesis;;\n;910;;死亡突围 · 重临死境;DeadoutRe;DTR\nsoa3;911;;Slow Start AoA3;;SoA3\nnew-vanilla-2;912;;新的原版 2;New Vanilla 2;NVA2\nprominence-2-rpg;913;;卓越 II RPG;Prominence II RPG;\n;914;;伟大冒险者：世界之眼;Great Adventurer:the Eye of the World;GAEW\n;915;;破碎;Fractures Rainimator;Rain\n;916;;更好的原版;Better Vanilla;BV\nstrictly-medieval-3-forge;917;;Strictly Medival 3;;\n;918;;潘神的迷宫;Pan's Labyrinth;PL\n;919;;持之永恒-天空挑战者;Stay With Eternity Sky Challenger;SWESC\neasy-to-ascension;920;;易如飞升;Easy to Ascension;\n;921;;幽匿末日;The Last Day of Sculk;TLDS\n;922;;交错空岛;Crossisland;CrI\n;923;;机动工程：刀剑与魔法;Mechanical Engineering: SlashBlade and Magic;CRMD\nminetech-evolution;924;;科技进化;Minetech Evolution;MTE\ngregified-sky-edition;925;;Gregified: Sky Edition;;\n;926;;原版扩展;Additonal Vanilla;AV\ncreate-on-a-potato;927;;Create on a Potato PC: Ultimate;;CPU\n;928;;现实主义;realism;RLM\n;929;;晋升之路;path of advance;POA\n;930;;永无止境：超越常新;Above and Beyond : Refresh;CABR\ncosmic-frontiers;931;;星河边疆;Cosmic Frontiers;CF\nintegrated-minecraft;932;;Integrated MC;;\n;933;;Rising Legends;;\n;934;;城乡之旅;Harvest's Journey;HJ\n;935;;龙鸣牢大;FarmerKobeBryant;FKB\nsetgamedifficulty-reika;936;;SetGameDifficulty Reika;;\nepic-journey-gtceu;937;;Epic Journey GTCEu;;\nterrafirmacraft-leap-the-top;938;;飞渡群峦;TerraFirmaCraft:Leap The Top;TFC:LTT\ngregtech-skybound;939;;Gregtech: Skybound;;\nsimple-tfc;940;;简单群峦;Simple TFC;\nftb-presents-direwolf20-s14;941;;FTB Presents Direwolf20 1.21;;\nfinal-judgment-save-the-world;942;;Final Judgment:Save the world;;\n;943;;极端世界：重置;Extreme World Reset;EWR\nexplore-everything;944;;空岛：探索;Explore Everything;SEE\nepic-journey-gtceu-conquerors-edition;945;;Epic Journey GTCEu - Conqueror's Edition;;\nthe-undead-blooms;946;;亡灵绽放 | 真菌孢子启示录 |;The Undead Blooms | Fungal Spore Apocalypse |;\n;947;;星地征程;Starland Journey;SJ\n;948;;希望与绝望-惊变100天;Hope and Despair;had\n;949;;梦篡光阴;Dreams Distort Overture;DDO\n;950;;最后的光芒;The Last Light;TLL\nendil-expert-edition;951;;Endil's Expert Edition;;EEE\npeace-of-mind;952;;Peace of Mind;;\ndistantly-optimized;953;;Distantly Optimized;;DO\nfarming-experience;954;;Farming Experience;;\nhungry-for-tech-2;955;;Hungry For Tech 2;;\nremain;957;;Remain;;\n;958;;末日狂徒;;\ngregtania6-skyblock;959;;Gregtania6 Skyblock;;\nharmonious-engineering;960;;Harmonious Engineering;;\nvalley-of-farming-and-ranching;961;;农牧物语;Valley of Farming and Ranching;\n;962;;机械动力：火车;Create Train;\nthe-old-era-of-the-republic-fot-yifei;963;;理想国冒险之旅;;AOIC\n;964;;生欲:求生2;Vivere The Zombie Apocalypse-II;VZA2\n;965;;起源-世界;OriginCraft Pulses;\nlegacies-mc;966;;Legacies;;\nthe-legend-of-2900;967;;海上机械师;The Legend Of 2900;TLOU\nmirasites-100-days;968;;畸变：蛊之奇境;Mirasites-BrokenMemories;MBM\n;969;;末日余晖：独自一人;;MRYH\n;970;;幸存者与僵尸;Survivors And Zombies;SAZ\n;971;;海岛寿司店;Sushi Island;\ninfinite-alchemy;972;;Infinite Alchemy;;IA\n;973;;探索的时光;Adventuring Time;AT\n;974;;饕餮飨食;;\n;975;;丰富的MC;RichMC;RM\n;976;;刀剑异闻录;Sword Strange Tales;SST\netbw;977;;探索光明的世界;Explore the Bright World;EtBW\nhardship-era;978;;苦难年代;Hardship Era;HE\nrlcraft-dregora;979;;RLCraft Dregora;;\n;980;;Farm The Ingots;;FTI\nvenoms-craft;981;;Venom's Craft;;\n;982;;慢生活冒险;;\n;983;;多元工艺：绘镇曲;Painted Town Melody;PTM\n;984;;遗忘之海;Leisurely Life;\n;985;;幻想国度;Fancy_Land;\n;986;;群峦传说：重制;TerraFirmaCraft Remake;TFCRe\n;987;;未来;;\n;988;;起源生物—崛起;;\n;989;;工匠：技艺革新;Construct：Technological Innovation;CTI\nerstwhile-journey;990;;往昔之旅;Erstwhile Journey;EJ\n;991;;Exploria;;\nftb-evolution;992;;FTB Evolution;;\n;994;;云程发轫;cloud journey begins;CJB\nforever-factory;995;;永恒工厂;Forever Factory;\n;996;;FTB University 1.19;;\nsymbolica;997;;Symbolica;;\naelstar;998;;Aelstar;;\nsrp-qz;999;;SRP：隔离区;SRP: Quarantine Zone;\nindustrial-machines;1000;;Industrial Machines;;\nauto-terrafirmacraft-tfc-create;1001;;Auto-TerraFirmaCraft (TFC + Create);;ATFC\n;1002;;时间之书：星的日记;The Book of Time: A Star's Journal;TASL\n;1003;;特摄世界;Tokusatsu World;TW\njourney-of-reincarnation;1004;;转世之旅;Tensura: Journey of Reincarnation;\n;1005;;冒险与休闲;Adventure and Leisure;AL\nenigmatica10;1006;;Enigmatica 10;;E10\nmc-chocolate-edition;1007;;Chocolate Edition Minecraft;;\ncivilization-l;1008;;Civilization-L;;\nterrafirma-advent;1009;;群峦：降临;TerraFirma：Advent;TFA\nvalhelsia-7;1010;;Valhelsia 7;;\nmechanical-mastery-plus;1011;;Mechanical Mastery Plus;;\nashes-to-ashes;1012;;尘归尘;Ashes To Ashes;ATA\n;1013;;格雷乐事;GT So Easy;GTse\ncreate-silence-and-prosperity;1014;;机械动力：寂静与繁荣;Create Silence and Prosperity;CSP\nmif;1015;;MI:Foundation;;MIF\nc2mse2;1016;;Create 2 Mekanism: Sky Edition 2;;C2MSE2\nkumo-o-tagayasu;1017;;耕云钓月;Kumo O Tagayasu;KOTS\n;1018;;无限战争4;Infinity War4;INF4\n;1019;;群峦传说：工业长路;;\n;1020;;自然冒险     新生;Nature Adventure Newborn;NAB\nhmbo;1021;;羽舲的基础优化;Hanekmio's Basis Optimization;\ncreate-star-valley;1022;;Create: Gears of History;;\n;1023;;阿尔法计划;AlphaPlan;AP\nspell-dimension;1024;;咒次元;Spell Dimension;SD\nskyfactory-5;1025;;天空工厂 5;SkyFactory 5;SF5\n;1026;;破劫之刃;Break Disaster;\nreminiscent-create;1027;;Reminiscent Create;;\ntechsert;1028;;匿踪之城;Techsert;TH\nprehistoric-world-dinosaurs-adventure;1029;;Prehistoric World - Dinosaurs & Adventure;;\nterminal;1030;;终末;Terminal;\nisolated-crystal-3;1031;;异次元水晶 3;Isolated Crystal 3;\n;1032;;机械动力：无限总是小于零;Create:Infinity Always Smaller Than Zero;CIASZ\n;1033;;踪灭I-起源;;\n;1034;;踪灭II;;\nrabbits-dawn-craft;1035;;某兔子的破晓之界;A Certain Rabbit's DawnCraft - Dawn of the Deep Soul;RDC\n;1036;;铁砧天空;;ASKY\ngrand-architect-journey;1037;;伟大工程巡礼;Grand Architecture Journey;GAJ\nthe-adventure-of-valkyriens;1038;;瓦尔基里大冒险;The Adventure of Valkyriens;TAOV\n;1039;;方块宝可梦:奥瑞星光;Cobblemon Ori Starlight;COS\n;1040;;群峦传说：天才版;;\ncompression;1041;;Compression;;\ncompact-sky-easy;1042;;Compact Sky: Easy;;CSE\n;1043;;丰收与探索;Harvest and Exploration;\n;1044;;脆骨症：黯光;No Flesh Within Chest-DIM;NFWC-DIM\n;1045;;光陆冒险;;\ncobblemon-eternal-edition;1046;;Cobblemon Eternal;;\n;1047;;Tinkerer's Quilt;;\ngregtech-expert;1048;;GregTech Expert;;\n;1049;;Beta Horizons;;\nminecraft-legendary-edition;1050;;Legendary Edition;;\n;1051;;Origenceal LPackage;;OLP\n;1052;;我的世界：未闻的故事;;mtus\n;1053;;Versatile;;\n;1054;;勇者之章 Ⅲ;Chapter of Yuusha Ⅲ;CY3\nforge-frontier;1055;;Create - Forge Frontier;;\ncreate-workshop-uwu;1056;;Create: Workshop UwU Edition;;\n;1057;;古钠重铸;Oldium Optimized;OO\n;1058;;越寰;Leaping across the world;LTW\nftb-oceanblock-2;1059;;FTB OceanBlock 2;;\n;1060;;伊甸空天2;Eden Aerospace 2;EAS2\ncreate-infinity-mechanism;1061;;机械动力：无限构件;Create:Infinity Mechanism;CIM\nbeyond-depth;1062;;Beyond Depth;;\n;1063;;枪械交界地-旧地狱篇;;\n;1064;;真幻世界;Realfantacy World;R.F.\nminenufactory;1065;;创世工厂;Minenufactory;MF\ncraft-to-exile-2;1066;;放逐之路2;Craft to Exile 2 (VR Support);CTE2\n;1067;;龙境III：菌土重来;Dragon Odyssey: Fungreda;DO3\nthe-culinary-journey;1068;;美食的旅途;The Culinary Journey;TCJ\n;1069;;缥缈方舟;;PMFZ\n;1070;;东方匠魂传;Touhou Tinker Fantasy;TTF\n;1071;;亵渎;;MYBL\ntechopolis-3;1072;;Techopolis 3;;\nsky1234;1074;;sky1234;;\nstar-technology;1075;;Star Technology;;\n;1076;;Hyper World;;HW\n;1077;;妖月的魔法金属整合包;yaoyaoDream's ManaMetalMod Modpack;YMM\nforgottenwinter;1078;;遗忘严冬;ForgottenWinter;FTW\nrandblock2-winterize;1079;;随机空岛 2：银装素裹;RandBlock2：Winterize;\ncreate-the-brass-concerto;1080;;机械动力：黄铜协奏曲;Create: The Brass Concerto;CBC\nexplorers-eve;1081;;Explorer's Eve;;\nall-the-magic-arcana;1082;;All the Magic :Arcana;;ATMAr\n;1083;;创想新世界;Inspired Creations: New World;ICNW\nfarming-crossing-5-made-to-order;1084;;农业森友会 5;Farming Crossing 5: Made to Order!;\n;1085;;Ben10 Craft;;BEN10\n;1086;;渺小的世界;Small world;\nmierno;1087;;迷厄路;Mierno;\n;1088;;最后那些天;Those last day;\n;1089;;泠月狐基础整合;LunarFox;\nroguelike-adventures-and-dungeons-3;1090;;冒险与地牢3;Roguelike Adventures and Dungeons 3;RAD3\n;1092;;玄理2：洗剪版;EnigTech 2 - Dishwasher's Cut;ET2DC\nwheelchair-gregtech;1093;;Wheelchair Gregtech;;\negged-up;1094;;Egged Up;;\n;1095;;香草纪元：食旅纪行;VanillaEra: FaresChron;VEFC\n;1096;;愚者;The Fool;TF\n;1097;;时间之书2：星梦指南;BookofTime II Stardream Compass;BKSC\n;1098;;群峦之上：无垠伊土;EndlessFirma;EnF\n;1099;;血月低语：暗夜惊魂;Bloodmoon's Whisper;BW\n;1100;;MC饭;Delicious in Minecraft;DIM\n;1101;;美味的甜点总是有毒;;\ndelight-desert-chef;1102;;乐事：沙漠厨师;Delight: Desert Chef;DDC\n;1103;;异界随行诗;Otherworld Traveling Poems;\n;1104;;无边荒漠;;\n;1105;;空中厕所2;PoopSky2;\nfear-nightfall;1106;;Fear Nightfall;;\nhomestead-cozy;1107;;Homestead - A Cozy Survival Experience;;HD\nmadness-of-the-skies;1108;;Madness of the skies;;\n;1109;;魔之逆鳞;Nemesis of Demons;NOD\nemc-and-fe-plus2;1110;;EMC & FE ++;;E&F\n;1111;;云之谷;CloudValley;\nbm-exosphere;1112;;BM: Exosphere;;BME\n;1113;;锻造大师：侠之器;Master Blacksmith：Chivalrous blade;MB:CB\n;1114;;自然;Nature;NA\n;1115;;农场物语;FarmingTales;FT\noptimaxcraft;1116;;OptiMaxCraft;;OMC\n;1117;;决战黎明;Decisive Battle at Dawn;DBAD\nfloaty-creatoria;1118;;天工浮生;Floaty Creatoria;\n;1119;;简约生电优化;Simplicity Survival Optimization;SSO\n;1120;;星辰之下;Under the Stars;UtS\nmadness-of-the-skies-2;1121;;Madness of the Skies 2;;\nraspberry-flavoured;1122;;Raspberry Flavoured;;RF\nskillet-man;1123;;平底锅侠;Skillet Man;\n;1124;;格雷科技：新征程;GregTech Odyssey;GTO\n;1125;;无中生无尽;Ex Nihilo Ad Avaritia;ENAA\n;1126;;Undertale;;\ncreate-creative-and-beyond;1127;;机械动力：创造与超越;Create Creative and Beyond;CCB\ncreate-delight-remake;1128;;机械动力：齿轮盛宴;Create:Delight Remake;CDR\nftb-skies-2;1129;;FTB Skies 2;;\n;1130;;星熵：灵械;Stellar Era: Technomagic;SETM\naircrashed-engineer;1131;;坠机工程师;Aircrashed Engineer;ACE\nproject-tfcreate;1132;;Project: TFCreate;;\n;1133;;落幕曲;Closing Song;\n;1134;;超大陆：勇气;Supercontinent：Pluck;SUPL\ncreate-chronicles-the-endventure;1135;;Create Chronicles: The Endventure;;\nlife-in-the-village-4;1136;;Life in the village 4;;LITV4\nreal-island-2-modular-craft;1137;;真实空岛2：模块化之旅;Real Island 2 - Modular Craft;RI2\n;1138;;奥术咖啡馆;Arcane Cofferia;\nunyielding;1139;;Unyielding;;\n;1140;;剑与王国;;SNK\nthe-fungal-infection-coop;1141;;The Fungal Infection | Co-Op Base Defense;;\nmc-eternal-2;1142;;永恒的MC 2;MC Eternal 2;MCE2\nsolo-leveling-reawakening;1143;;Solo Leveling - Reawakening;;\ncreate-ultimate-selection;1144;;Create: Ultimate Selection - QUESTS NOW!;;\n;1145;;元素觉醒;Elemental Awakening;\nendless-horror-the-ultimate-horror-modpack;1146;;Endless Horror - The ULTIMATE Horror Experience;;\n;1147;;锻造之旅;;\npicky;1148;;Picky;;\nresource-factory;1149;;资源工厂;Resource Factory;\nstacia-2-expert;1150;;Stacia 2 Expert;;S2E\n;1151;;田园乐事;Conutryside_Delight;\n;1152;;世界之旅;Journey of the world;JOTW\n;1153;;清新优化;Fresh optimization;FRO\n;1154;;遗世之匠;Touhou：The Forgotten Swordsmith;\n;1155;;千禧：蝉鸣之时;;\nskyrealm-descent;1157;;Skyrealm: Descent;;\n;1158;;远征：以太利亚;Expedition: Ætheria;\n;1159;;快速优化;Rapid Optimization;RO\nliminal-industries;1160;;阈限工业;Liminal Industries;\n;1161;;史诗冒险;Epic Adventure;EA\n;1162;;根自日常;ROOTINE;\nblue-archive-x-mineraft;1163;;我的世界x蔚蓝档案;BlueArchive x Minecraft;BACraft\n;1164;;Fresh & Smooth;;\n;1165;;机动重启工程：风之旅;The Caravan of Winds;TCW\n;1166;;Furry Fandom Club Boss Rush Official Offline;;FFCBR\nimmersive-ultra;1167;;极致沉浸;Immersive Ultra;\n;1168;;Polymania!;;\nsurvivearchive;1169;;SurviveArchive - A Blue Archive modpack;;\nbeloong;1170;;化龍;BeLoong;\n;1171;;畅游自然;;SiF\n;1172;;退休工艺;Retirement Craft;RmC\n;1173;;溯光暗潮：云影;NS's Random Gunfight;NRG\n;1174;;雪狐的基础整合;SF Integrated Modpack;SFI\n;1175;;硬核惊变;Hardcore Surprise;HS\nsociety-sunlit-valley;1176;;Society: Sunlit Valley;;\nciscos-fantasy-medieval-rpg-dragonfyre;1177;;Cisco's Fantasy Medieval RPG [Dragonfyre];;\nall-the-mods-10-sky;1178;;All the Mods 10: To the Sky;;ATM10S\n;1179;;谐伴匠途;;\n;1181;;转变思路优化;‌New-Path Optimization;NO\nuls-zsurvival-craft;1182;;Z-Liminex;;\n;1183;;MITE 重生 3;MITE Reborn 3;MR\nreclamation-reclaim-the-world;1184;;Reclamation - Reclaim the World!;;\n;1185;;寰宇地球;Earthworld;EW\npokehaan-craft-2;1186;;Pokehaan Craft 2;;\nproject-nope-zone;1187;;Project Nope Zone;;PNZ\nall-the-simple-zero;1188;;一切简易;All the Simple;ATS\n;1189;;匠战妖域;Tinkers' Warfare Demon Realm;twdr\n;1190;;空想生存;;\n;1191;;东方起源鉴;Touhou Origins Annals;TOA\nrealm-of-destiny;1192;;生命之境;Realm of Destiny;ROD\n;1193;;奇妙的世界;Special World;\n;1194;;毁灭;DESTRUCTION;DES\npvzw;1195;;植物大战僵尸：世界;Plants VS Zombies : World;PVZW\n;1196;;农场物语Forge版;FarmingTales Forge;FT-WT\nnew-century-adventure;1197;;新世纪之险;New Century Adventure;NCA\nrepalmon;1198;;Re帕鲁梦;RePalmon;RPLM\nstructural-ingenuity;1199;;结构型奇思妙想;Structural Ingenuity;\narcaneaxis-ars;1200;;秘法象限;ArcaneAxis;ARS\ngregtech-easy;1201;;格雷科技简单版;GregTech Easy;GTE\n;1202;;机境交响;Creation Exploring the Road of;CETR\nmultiblockful-skies;1203;;Multiblockful Skies;;\n;1204;;刀剑物语;SwordStory;SS\n;1205;;无垠征途;The Unbounded Expedition;TUE\n;1206;;云栖谧土;Nephosia;\ncustom-battleroyale-complete;1207;;完整自定义大逃杀;Custome BattleRoyale Complete;CBRC\nparadise-of-lucky-blocks;1208;;Paradise of Lucky Blocks;;\n;1209;;极致优化;Xavier Optimized;XO\n;1210;;神秘时代4花园;Thaumcraft IV Sky Islands;\n;1211;;弥散往生;Diffusion: Soulfare;DSF\nmana-metal-new-horizons;1212;;魔法金屬：新視野;Mana Metal: New Horizons;M3NH\nfarmy-skyblock;1213;;Farmy Skyblock;;\nbloody-skyblock-modpack;1214;;Bloody Skyblock;;\nstarages;1215;;Star Space Ages;;\ncreate-spectral-cuisine;1216;;Create: Spectral Cuisine;;\nproject-infinity-0-1;1217;;Project Infinity 0.1;;\ncreate-live-6;1218;;Create Live 6;;\n;1219;;全面包围-硬核惊变;Full encirclement;FLET\n;1220;;科波菲尔;Copperfield;CPF\n;1221;;试用核心/I.O.P的水槽基础整合;Trial Essential;\ntnp-limitless-8;1222;;TNP Limitless 8;;\n;1223;;重度机械症;Mechanomania;\n;1224;;寻此苦旅3;Per Aspera3;PA3\ncalamity-rpg-iii-wounds-of-the-world;1225;;Calamity RPG III : Wounds of the World;;\n;1226;;烛枫公会优化整合包;;ZFGH\nlegends-reborn-medieval;1227;;Legends Reborn: Medieval;;LRM\n;1228;;双衰变;;DEGENE\nnd-new-dawn;1229;;New Dawn;;ND\n;1230;;星遥优化;StarCarefree Optimized;SO\n;1231;;寄生仙途;;JSXT\n;1232;;神秘启旅;Thaum Journey;\nnightfallcraft-the-casket-of-reveries;1233;;远梦之棺;NightfallCraft - The Casket of Reveries;\nopenworld-battleroyale;1234;;开放世界大逃杀;Openworld BattleRoyale;OBR\n;1235;;未尽之路;Unfinished Path;UP\n;1236;;命运齿轮;Fate's Machinery;FOM\nlinggango;1237;;Linggango;;\ncreate-industrialized-tech;1238;;Create: Industrialized Tech;;\n;1239;;灵璟铁路都市;LingJing Train City;LJTC\nerstwhile-journey-2;1240;;往昔之旅2;Erstwhile Journey 2;EJ2\n;1241;;诡厄：使徒;;GRAE\n;1242;;时间之书 : 星之旅行-重制版;BookofTime StarTravel;BKSL\ncook-in-skyland-elegant-feast;1243;;天空厨房：典雅盛宴;Cook in Skyland Elegant Feast;CiS2\nftb-stoneblock-4;1244;;FTB StoneBlock 4;;SB4\ntechevdiscovery;1245;;TechEv || Discovery;;\ng3-project;1246;;G3 project;;\n;1247;;新州;Xin Zhou;XZ\nemc-and-fe-plus3;1248;;EMC & FE +++;;E&F2\nterracraft-confluence;1249;;泰拉工艺：汇流;TerraCraft:Confluence;TCC\n;1250;;探索领域;Explore Realm;ER\nbeyond-cosmo;1251;;Beyond Cosmo;;\n;1252;;闲居物语;Travel Architecture Dreams;TAD\nhigh-altitude-alchemy;1253;;High-Altitude Alchemy;;\n;1254;;机械动力: 界位工程师;Create Boundary Engineer;CBE\nskyopolis-evolved;1255;;Skyopolis: Evolved;;\n;1256;;IFR的1.12.2优化整合包;IFR's Forgified Optimization;IFO\n;1257;;无穷匠艺;;\n;1258;;Mersive.;;MS\n;1259;;GD656极致生电整合包;GD656 Ultimate Redstone;SD\n;1260;;登神者：天阶咏叹;The God Ascender;TGA\n;1261;;虚饰作品;Fictional;\n;1262;;无尽工坊：奇境传说;Infinity Workshop: Legends of Wonderland;IWLoW\n;1263;;大群方舟;Arknights: The Great Tide;AGT\narcadia-rpg;1264;;Arcadia[RPG];;\nozone-skyblock-reborn-2;1265;;Ozone Skyblock Reborn 2;;\nwitarcland;1266;;维塔克兰;Witarcland;WTL\nbig-globe-optimized-forge;1267;;Big Globe Optimized;;\n;1268;;GD656轻量优化打底整合包;GD656 Ultimate Optimized;GDO\nterra-firma-far-horizons;1269;;群峦传说：野望;Terra Firma Far Horizons;TFFH\n;1270;;烬蓝物语 2;BlueAsh Things II;BAT2\nanvilcraft-turbo;1271;;铁砧工艺：极速版;AnvilCraft-Turbo;ANCT\njrs-tree-of-life;1272;;生命之树;Tree of life;\nreimagined-forge;1273;;Reimagined;;\n;1274;;MTR城市建设;MTR Urban Construction;MUC\n;1275;;Zecoar 整合包;Zecoar Modpack;ZM\ncreate-stranded-at-sea;1276;;Create: Stranded at Sea;;\n;1277;;胶兽异界之旅;;\nspellbound-skies;1278;;Spellbound Skies;;\ncozy-meadows;1279;;Cozy Meadows;;\n;1280;;无际旅人;EpochRealms;\nmythcrafts;1281;;MYTHCRAFT 5;;MC\n;1282;;牧场物语;Harvest Moon;\n;1283;;逃逸：寄生末日;SRP: Escape Parasite Doom;SRP:epd\n;1284;;Ice and M;;IAM\n;1285;;秋水;autumn floods;AF\n;1286;;V2O5;;\n;1287;;地牢冒险者;Dungeon Adventurer;DA\nultramodern-optimization;1289;;超现代优化;Ultramodern optimization;UO\nreverie-foundry;1290;;遐想铸造厂;Reverie Foundry;RF\n;1291;;神秘时代4·全家桶;All the thaumcraft's mod;AllTC\nover-stars;1292;;Over Stars;;\npandemonium-nexus-anarchic-aeon;1293;;万象枢纽：无序纪元;Pandemonium Nexus: Anarchic Aeon;PNAA\n;1294;;菌染亵渎;Fungal Infection Blasphemy;FIB\n;1295;;Ice and M:Sky;;IAMS\n;1296;;TUFF PERFORMANCE;;\n;1297;;灾厄巫咒;;\n;1298;;子弹呼啸 : 重制;Bullet Whizz : Remake;BWR\nmybiomesobeautiful;1299;;My Biomes Is So Beautiful;;\ncreate-capitalist;1300;;Create Capitalist;;\ndiving-beyond;1301;;Diving Beyond;;\n;1302;;奇星·奥术;Mystic Star_Arcane;\nguild-adventures;1303;;Guild Adventures;;\n;1304;;艾斯爱慕：新视野;Spore and M:New Horizons;SMNH\notherworld-dnd;1305;;Otherworld [Dungeons & Dragons];;\n;1306;;奇幻起源;Fantasy Origins;\nprojects;1307;;ProjectS;;\natheism;1308;;无神论;Atheism;\n;1309;;神之征伐;Apotheosis Conquest;\nthe-adventure-of-valkyriens-2;1310;;瓦尔基里大冒险2;The Adventure of Valkyriens 2;TAOV2\n;1311;;超然多味;Various flavors of supernatural;vfos\nall-the-mons;1312;;All the Mons;;ATMons\nstarskyblock;1313;;Star Sky Factory;;\n;1314;;拔刀之旅;Battou No Tabi;\n;1315;;物语;Stories;\n;1316;;圣末纪元;Holy End Era;HEE\narcane-ascension;1317;;Arcane Ascension;;\nthe-cordyceps-fungus;1318;;The Cordyceps Fungus;;\nbth-beyond-the-horizon;1319;;Beyond The Horizon;;BTH\nchosens-create-integrated;1320;;Chosen's Create: Integrated;;\n;1321;;织路;Travel Extensively;TE\n;1322;;MineZ v.无尽苦寒之地;MineZ v.I.C.E;\n;1323;;森语田园;;\n;1324;;子弹呼啸;Bullet Whizz;BW\nforgemechtrek;1325;;锻械游行;ForgeMechTrek;FMT\n;1326;;生电与优化整合包;Electricity Generation Integration Package;EGIP\ncreate-perfect-world-post-production;1327;;Create - Perfect World Post Production;;\nterrafirmagreg-modern;1328;;群峦格雷：现代版;TerraFirmaGreg: Modern;TFG\nnanomagic;1329;;NanoMagic;;\n;1330;;接入铁路网络：沉浸铁路;IR Railway Network Access (Immersive Railroading);RNA-IR\nbiohazard-project-genesis;1331;;Biohazard: Project Genesis - The Ultimate Apocalypse;;\n;1332;;齐柏林的启示录;Apocalypse of Zeppelin;AoZ\n;1333;;遗尘往事;Story of Forgotten Land;SOFL\nfarming-crossing-6-dinner-party;1334;;Farming Crossing 6: Dinner Party!;;\n;1335;;血族机械师;vampire mechanic;VM\n;1336;;维度行者：天启旅途;;\n;1337;;乌托邦探险之旅;Utopian Journey;UJ\n;1338;;统合防御·钢铁晨曦;United Defense: Steel Dawn;UDSD\n;1339;;机械动力：枪火不断;Create: Gunfire Never Stops;CGNS\n;1340;;逆转未来;Battle For The Future;BFF\nday-of-mind;1341;;DayOfMind;;DOM\n;1342;;机械动力：探险生存 2;Create Adventure Survival 2;\n;1343;;云游四海;WAYFARING;WF\n;1344;;阿卡迪亚的天启;The Apocalypse of Arcadia;TAOA\n;1345;;纯净原版・焕新;Pure Vanilla Renewal;PVR\n;1346;;命轮无章;Infinite Random;IR\n;1347;;星辉科技;StellarLightTech;STL\ndragns-o-suite-pack;1348;;DragN's O-Suite Pack;;\n;1349;;机械动力：工业与牧歌;Create: Industry and Pastoral;CIAP\n;1350;;灾厄纪行;;\nbeebeeblock-the-skyhive;1351;;蜂蜂空坊;Beebeeblock: The Skyhive;BTS\n"
  },
  {
    "path": "HMCL/src/main/resources/assets/natives.json",
    "content": "{\n  \"linux-arm64\": {\n    \"org.lwjgl:lwjgl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"sha1\": \"4421d94af68e35dcaa31737a6fc59136a1e61b94\",\n          \"size\": 786196\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"8bd89332c90a90e6bc4aa997a25c05b7db02c90a\",\n          \"size\": 90795\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"sha1\": \"877e17e39ebcd58a9c956dc3b5b777813de0873a\",\n          \"size\": 43233\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"5249f18a9ae20ea86c5816bc3107a888ce7a17d2\",\n          \"size\": 206402\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"sha1\": \"ae5357ed6d934546d3533993ea84c0cfb75eed95\",\n          \"size\": 108230\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"22408980cc579709feaf9acb807992d3ebcf693f\",\n          \"size\": 590865\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"sha1\": \"ee8e95be0b438602038bc1f02dc5e3d011b1b216\",\n          \"size\": 928871\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bb9eb56da6d1d549d6a767218e675e36bc568eb9\",\n          \"size\": 58627\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"sha1\": \"757920418805fb90bfebb3d46b1d9e7669fca2eb\",\n          \"size\": 135828\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bc49e64bae0f7ff103a312ee8074a34c4eb034c7\",\n          \"size\": 120168\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"sha1\": \"a2550795014d622b686e9caac50b14baa87d2c70\",\n          \"size\": 118874\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"11a380c37b0f03cb46db235e064528f84d736ff7\",\n          \"size\": 207419\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"sha1\": \"9f65c248dd77934105274fcf8351abb75b34327c\",\n          \"size\": 13404\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"93f8c5bc1984963cd79109891fb5a9d1e580373e\",\n          \"size\": 43381\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"sha1\": \"4421d94af68e35dcaa31737a6fc59136a1e61b94\",\n          \"size\": 786196\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"8bd89332c90a90e6bc4aa997a25c05b7db02c90a\",\n          \"size\": 90795\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"sha1\": \"877e17e39ebcd58a9c956dc3b5b777813de0873a\",\n          \"size\": 43233\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"5249f18a9ae20ea86c5816bc3107a888ce7a17d2\",\n          \"size\": 206402\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"sha1\": \"ae5357ed6d934546d3533993ea84c0cfb75eed95\",\n          \"size\": 108230\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"22408980cc579709feaf9acb807992d3ebcf693f\",\n          \"size\": 590865\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"sha1\": \"ee8e95be0b438602038bc1f02dc5e3d011b1b216\",\n          \"size\": 928871\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bb9eb56da6d1d549d6a767218e675e36bc568eb9\",\n          \"size\": 58627\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"sha1\": \"757920418805fb90bfebb3d46b1d9e7669fca2eb\",\n          \"size\": 135828\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bc49e64bae0f7ff103a312ee8074a34c4eb034c7\",\n          \"size\": 120168\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"sha1\": \"a2550795014d622b686e9caac50b14baa87d2c70\",\n          \"size\": 118874\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"11a380c37b0f03cb46db235e064528f84d736ff7\",\n          \"size\": 207419\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"sha1\": \"9f65c248dd77934105274fcf8351abb75b34327c\",\n          \"size\": 13404\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"93f8c5bc1984963cd79109891fb5a9d1e580373e\",\n          \"size\": 43381\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"sha1\": \"4421d94af68e35dcaa31737a6fc59136a1e61b94\",\n          \"size\": 786196\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"8bd89332c90a90e6bc4aa997a25c05b7db02c90a\",\n          \"size\": 90795\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"sha1\": \"877e17e39ebcd58a9c956dc3b5b777813de0873a\",\n          \"size\": 43233\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"5249f18a9ae20ea86c5816bc3107a888ce7a17d2\",\n          \"size\": 206402\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"sha1\": \"ae5357ed6d934546d3533993ea84c0cfb75eed95\",\n          \"size\": 108230\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"22408980cc579709feaf9acb807992d3ebcf693f\",\n          \"size\": 590865\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"sha1\": \"ee8e95be0b438602038bc1f02dc5e3d011b1b216\",\n          \"size\": 928871\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bb9eb56da6d1d549d6a767218e675e36bc568eb9\",\n          \"size\": 58627\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"sha1\": \"757920418805fb90bfebb3d46b1d9e7669fca2eb\",\n          \"size\": 135828\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bc49e64bae0f7ff103a312ee8074a34c4eb034c7\",\n          \"size\": 120168\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"sha1\": \"a2550795014d622b686e9caac50b14baa87d2c70\",\n          \"size\": 118874\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"11a380c37b0f03cb46db235e064528f84d736ff7\",\n          \"size\": 207419\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"sha1\": \"9f65c248dd77934105274fcf8351abb75b34327c\",\n          \"size\": 13404\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"93f8c5bc1984963cd79109891fb5a9d1e580373e\",\n          \"size\": 43381\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"sha1\": \"4421d94af68e35dcaa31737a6fc59136a1e61b94\",\n          \"size\": 786196\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"8bd89332c90a90e6bc4aa997a25c05b7db02c90a\",\n          \"size\": 90795\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"sha1\": \"877e17e39ebcd58a9c956dc3b5b777813de0873a\",\n          \"size\": 43233\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"5249f18a9ae20ea86c5816bc3107a888ce7a17d2\",\n          \"size\": 206402\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"sha1\": \"ae5357ed6d934546d3533993ea84c0cfb75eed95\",\n          \"size\": 108230\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"22408980cc579709feaf9acb807992d3ebcf693f\",\n          \"size\": 590865\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"sha1\": \"ee8e95be0b438602038bc1f02dc5e3d011b1b216\",\n          \"size\": 928871\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bb9eb56da6d1d549d6a767218e675e36bc568eb9\",\n          \"size\": 58627\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"sha1\": \"757920418805fb90bfebb3d46b1d9e7669fca2eb\",\n          \"size\": 135828\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bc49e64bae0f7ff103a312ee8074a34c4eb034c7\",\n          \"size\": 120168\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"sha1\": \"a2550795014d622b686e9caac50b14baa87d2c70\",\n          \"size\": 118874\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"11a380c37b0f03cb46db235e064528f84d736ff7\",\n          \"size\": 207419\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"sha1\": \"9f65c248dd77934105274fcf8351abb75b34327c\",\n          \"size\": 13404\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"93f8c5bc1984963cd79109891fb5a9d1e580373e\",\n          \"size\": 43381\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"8bd89332c90a90e6bc4aa997a25c05b7db02c90a\",\n          \"size\": 90795\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"5249f18a9ae20ea86c5816bc3107a888ce7a17d2\",\n          \"size\": 206402\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"22408980cc579709feaf9acb807992d3ebcf693f\",\n          \"size\": 590865\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bb9eb56da6d1d549d6a767218e675e36bc568eb9\",\n          \"size\": 58627\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"bc49e64bae0f7ff103a312ee8074a34c4eb034c7\",\n          \"size\": 120168\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"11a380c37b0f03cb46db235e064528f84d736ff7\",\n          \"size\": 207419\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar\",\n          \"sha1\": \"93f8c5bc1984963cd79109891fb5a9d1e580373e\",\n          \"size\": 43381\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.3:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.3/lwjgl-3.3.3-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.3/lwjgl-3.3.3-natives-linux-arm64.jar\",\n          \"sha1\": \"f35d8b6ffe1ac1e3a5eb1d4e33de80f044ad5fd8\",\n          \"size\": 91294\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.3/lwjgl-jemalloc-3.3.3-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.3/lwjgl-jemalloc-3.3.3-natives-linux-arm64.jar\",\n          \"sha1\": \"eff8b86798191192fe2cba2dc2776109f30c239d\",\n          \"size\": 209315\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.3:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.3/lwjgl-openal-3.3.3-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.3/lwjgl-openal-3.3.3-natives-linux-arm64.jar\",\n          \"sha1\": \"ad8f302118a65bb8d615f8a2a680db58fb8f835e\",\n          \"size\": 592963\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.3:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.3/lwjgl-opengl-3.3.3-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.3/lwjgl-opengl-3.3.3-natives-linux-arm64.jar\",\n          \"sha1\": \"2096f6b94b2d68745d858fbfe53aacf5f0c8074c\",\n          \"size\": 58625\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.3:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.3/lwjgl-glfw-3.3.3-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.3/lwjgl-glfw-3.3.3-natives-linux-arm64.jar\",\n          \"sha1\": \"492a0f11f85b85899a6568f07511160c1b87cd38\",\n          \"size\": 122159\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.3:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.3/lwjgl-stb-3.3.3-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.3/lwjgl-stb-3.3.3-natives-linux-arm64.jar\",\n          \"sha1\": \"ddc177afc2be1ee8d93684b11363b80589a13fe1\",\n          \"size\": 207418\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.3/lwjgl-tinyfd-3.3.3-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.3/lwjgl-tinyfd-3.3.3-natives-linux-arm64.jar\",\n          \"sha1\": \"2823a8c955c758d0954d282888075019ef99cec7\",\n          \"size\": 43864\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.3.3/lwjgl-freetype-3.3.3-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.3/lwjgl-freetype-3.3.3-natives-linux-arm64.jar\",\n          \"sha1\": \"498965aac06c4a0d42df1fbef6bacd05bde7f974\",\n          \"size\": 1093516\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar\",\n          \"sha1\": \"6105690714874b995aa7812e4f7732a21109276c\",\n          \"size\": 1157957\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.4.1:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1-natives-linux-arm64.jar\",\n          \"sha1\": \"46883f3b622d8b4d7f27b627ca3360cda3db0e0e\",\n          \"size\": 120615\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar\",\n          \"sha1\": \"16b663092854fc6adfe0a941e61cae2bb2e437b6\",\n          \"size\": 47872\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.4.1:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1-natives-linux-arm64.jar\",\n          \"sha1\": \"7891964dfb723209c6d02b0401432348fb707cc0\",\n          \"size\": 238730\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar\",\n          \"sha1\": \"8508bd60589de0539aa465b78fcf838efd07ae9a\",\n          \"size\": 153919\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.4.1:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1-natives-linux-arm64.jar\",\n          \"sha1\": \"3729b70cdd42df5571b075e051fa2fc8586dc538\",\n          \"size\": 839895\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar\",\n          \"sha1\": \"830412cab823c029cda6b9729f9d2ed36cf79959\",\n          \"size\": 937660\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.4.1:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1-natives-linux-arm64.jar\",\n          \"sha1\": \"61a4103e56bbaeb74ad3f19ec14299fd6891c4b0\",\n          \"size\": 79752\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar\",\n          \"sha1\": \"a782b1ddd175c9107bf300fd92920579ea65e2a4\",\n          \"size\": 151893\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.4.1:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1-natives-linux-arm64.jar\",\n          \"sha1\": \"e5e87034c47118960746077dba46280e8de864b3\",\n          \"size\": 141530\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar\",\n          \"sha1\": \"f077c3dafc31924fbe8acb0b913f08977ba27f8e\",\n          \"size\": 142927\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.4.1:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1-natives-linux-arm64.jar\",\n          \"sha1\": \"3bc107f901f931fea07cb0d80b1d74a34b806a2b\",\n          \"size\": 259116\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar\",\n          \"sha1\": \"562697d40dbd2ed0424bf24dcf65d51170f81adf\",\n          \"size\": 15931\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1-natives-linux-arm64.jar\",\n          \"sha1\": \"20771d2b4e01f5295156912ab62e170508aef618\",\n          \"size\": 45433\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar\",\n          \"sha1\": \"caa75a37e857efc9b0686e434d708d6420c5b6ea\",\n          \"size\": 464342\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.4.1:natives-linux-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1-natives-linux-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1-natives-linux-arm64.jar\",\n          \"sha1\": \"8f37d0da3386ff602ec54cd06626881895711041\",\n          \"size\": 1309568\n        }\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-arm64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3/lwjgl2-natives-2.9.3-linux-arm64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-linux-arm64/lwjgl2-natives-2.9.3-linux-arm64.jar\",\n            \"sha1\": \"c47df34b6a0414b2d9972f602d0c85191129d69c\",\n            \"size\": 7346768\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-arm64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-arm64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3/lwjgl2-natives-2.9.3-linux-arm64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-linux-arm64/lwjgl2-natives-2.9.3-linux-arm64.jar\",\n            \"sha1\": \"c47df34b6a0414b2d9972f602d0c85191129d69c\",\n            \"size\": 7346768\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-arm64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-arm64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3/lwjgl2-natives-2.9.3-linux-arm64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-linux-arm64/lwjgl2-natives-2.9.3-linux-arm64.jar\",\n            \"sha1\": \"c47df34b6a0414b2d9972f602d0c85191129d69c\",\n            \"size\": 7346768\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-arm64\"\n      }\n    },\n    \"net.java.jinput:jinput-platform:2.0.5:natives\": null,\n    \"com.mojang:text2speech:1.10.3:natives\": null,\n    \"com.mojang:text2speech:1.11.3:natives\": null,\n    \"com.mojang:text2speech:1.12.4:natives\": null,\n    \"com.mojang:text2speech:1.13.9:natives-linux\": null\n  },\n  \"linux-arm32\": {\n    \"org.lwjgl:lwjgl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3.jar\",\n          \"sha1\": \"17a59ba0fe8d474ec9dbe0d5db40d2cfe59c4c08\",\n          \"size\": 552997\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"3180d363040744dfe0c6a0dd5d018cedae476e9a\",\n          \"size\": 53035\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3.jar\",\n          \"sha1\": \"b6fd0932171ba3f2eaa4547beddca3a3e645342d\",\n          \"size\": 34130\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"d7e5cecbf045b7b7863343273ffea94e0e2f6994\",\n          \"size\": 137847\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3.jar\",\n          \"sha1\": \"106742e805803ab9eab8e343f0fb31a3d263903c\",\n          \"size\": 79432\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"5c30ef08c829252e542f9fbc04772d51013326c5\",\n          \"size\": 552314\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3.jar\",\n          \"sha1\": \"bdd534a323d0c8f54969b95e424b6ac8984f7d6e\",\n          \"size\": 936589\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"4925362a5f2412cb6467e6d6c6de26b9e1ccfc71\",\n          \"size\": 58594\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3.jar\",\n          \"sha1\": \"5e520d5c290c8b012545a8d34fa5db5ab051ea53\",\n          \"size\": 107999\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"aab1a5a1e21eca87f4acd5ba055f6bfd5d90951c\",\n          \"size\": 138698\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3.jar\",\n          \"sha1\": \"40eccaa4fa86fc815f2e17946a392fb5fdcc286a\",\n          \"size\": 104049\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"f28dc1e73025cf699a2cdd4f6db7964ed357ce50\",\n          \"size\": 146890\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3.jar\",\n          \"sha1\": \"d5edf89c7b6ca1ea20865a6ba0a09bfc5efb29c1\",\n          \"size\": 6392\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"b3ad16cb0e4c1307bf3d1ecb29559e18a4f8633c\",\n          \"size\": 38752\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3.jar\",\n          \"sha1\": \"17a59ba0fe8d474ec9dbe0d5db40d2cfe59c4c08\",\n          \"size\": 552997\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"3180d363040744dfe0c6a0dd5d018cedae476e9a\",\n          \"size\": 53035\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3.jar\",\n          \"sha1\": \"b6fd0932171ba3f2eaa4547beddca3a3e645342d\",\n          \"size\": 34130\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"d7e5cecbf045b7b7863343273ffea94e0e2f6994\",\n          \"size\": 137847\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3.jar\",\n          \"sha1\": \"106742e805803ab9eab8e343f0fb31a3d263903c\",\n          \"size\": 79432\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"5c30ef08c829252e542f9fbc04772d51013326c5\",\n          \"size\": 552314\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3.jar\",\n          \"sha1\": \"bdd534a323d0c8f54969b95e424b6ac8984f7d6e\",\n          \"size\": 936589\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"4925362a5f2412cb6467e6d6c6de26b9e1ccfc71\",\n          \"size\": 58594\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3.jar\",\n          \"sha1\": \"5e520d5c290c8b012545a8d34fa5db5ab051ea53\",\n          \"size\": 107999\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"aab1a5a1e21eca87f4acd5ba055f6bfd5d90951c\",\n          \"size\": 138698\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3.jar\",\n          \"sha1\": \"40eccaa4fa86fc815f2e17946a392fb5fdcc286a\",\n          \"size\": 104049\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"f28dc1e73025cf699a2cdd4f6db7964ed357ce50\",\n          \"size\": 146890\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3.jar\",\n          \"sha1\": \"d5edf89c7b6ca1ea20865a6ba0a09bfc5efb29c1\",\n          \"size\": 6392\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"b3ad16cb0e4c1307bf3d1ecb29559e18a4f8633c\",\n          \"size\": 38752\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3.jar\",\n          \"sha1\": \"17a59ba0fe8d474ec9dbe0d5db40d2cfe59c4c08\",\n          \"size\": 552997\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"3180d363040744dfe0c6a0dd5d018cedae476e9a\",\n          \"size\": 53035\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3.jar\",\n          \"sha1\": \"b6fd0932171ba3f2eaa4547beddca3a3e645342d\",\n          \"size\": 34130\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"d7e5cecbf045b7b7863343273ffea94e0e2f6994\",\n          \"size\": 137847\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3.jar\",\n          \"sha1\": \"106742e805803ab9eab8e343f0fb31a3d263903c\",\n          \"size\": 79432\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"5c30ef08c829252e542f9fbc04772d51013326c5\",\n          \"size\": 552314\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3.jar\",\n          \"sha1\": \"bdd534a323d0c8f54969b95e424b6ac8984f7d6e\",\n          \"size\": 936589\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"4925362a5f2412cb6467e6d6c6de26b9e1ccfc71\",\n          \"size\": 58594\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3.jar\",\n          \"sha1\": \"5e520d5c290c8b012545a8d34fa5db5ab051ea53\",\n          \"size\": 107999\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"aab1a5a1e21eca87f4acd5ba055f6bfd5d90951c\",\n          \"size\": 138698\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3.jar\",\n          \"sha1\": \"40eccaa4fa86fc815f2e17946a392fb5fdcc286a\",\n          \"size\": 104049\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"f28dc1e73025cf699a2cdd4f6db7964ed357ce50\",\n          \"size\": 146890\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3.jar\",\n          \"sha1\": \"d5edf89c7b6ca1ea20865a6ba0a09bfc5efb29c1\",\n          \"size\": 6392\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.3:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3-natives-linux-arm32.jar\",\n          \"sha1\": \"b3ad16cb0e4c1307bf3d1ecb29559e18a4f8633c\",\n          \"size\": 38752\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux-arm32.jar\",\n          \"sha1\": \"41a3c1dd15d6b964eb8196dde69720a3e3e5e969\",\n          \"size\": 82374\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-linux-arm32.jar\",\n          \"sha1\": \"a96a6d6cb3876d7813fcee53c3c24f246aeba3b3\",\n          \"size\": 136157\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux-arm32.jar\",\n          \"sha1\": \"ffbe35d7fa5ec9b7eca136a7c71f24d4025a510b\",\n          \"size\": 400129\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux-arm32.jar\",\n          \"sha1\": \"e3550fa91097fd56e361b4370fa822220fef3595\",\n          \"size\": 58474\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux-arm32.jar\",\n          \"sha1\": \"816d935933f2dd743074c4e717cc25b55720f294\",\n          \"size\": 104027\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux-arm32.jar\",\n          \"sha1\": \"b08226bab162c06ae69337d8a1b0ee0a3fdf0b90\",\n          \"size\": 153889\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux-arm32\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-natives-linux-arm32.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-natives-linux-arm32.jar\",\n          \"sha1\": \"d53d331e859217a61298fcbcf8d79137f3df345c\",\n          \"size\": 48061\n        }\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-arm32\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3/lwjgl2-natives-2.9.3-linux-arm32.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-linux-arm32/lwjgl2-natives-2.9.3-linux-arm32.jar\",\n            \"sha1\": \"b3017961cf4ff2ce189b64903e52025a88ed9229\",\n            \"size\": 580670\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-arm32\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-arm32\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3/lwjgl2-natives-2.9.3-linux-arm32.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-linux-arm32/lwjgl2-natives-2.9.3-linux-arm32.jar\",\n            \"sha1\": \"b3017961cf4ff2ce189b64903e52025a88ed9229\",\n            \"size\": 580670\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-arm32\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-arm32\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3/lwjgl2-natives-2.9.3-linux-arm32.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-linux-arm32/lwjgl2-natives-2.9.3-linux-arm32.jar\",\n            \"sha1\": \"b3017961cf4ff2ce189b64903e52025a88ed9229\",\n            \"size\": 580670\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-arm32\"\n      }\n    },\n    \"net.java.jinput:jinput-platform:2.0.5:natives\": null,\n    \"com.mojang:text2speech:1.10.3:natives\": null,\n    \"com.mojang:text2speech:1.11.3:natives\": null,\n    \"com.mojang:text2speech:1.12.4:natives\": null,\n    \"com.mojang:text2speech:1.13.9:natives-linux\": null\n  },\n  \"linux-mips64el\": {\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-mips64el\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2/lwjgl2-natives-2.9.3-rc2-linux-mips64el.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2-linux-mips64el/lwjgl2-natives-2.9.3-rc2-linux-mips64el.jar\",\n            \"sha1\": \"8e96ae0b3ca2b566d4aa1ef737f1a11fde34636c\",\n            \"size\": 947858\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-mips64el\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-mips64el\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2/lwjgl2-natives-2.9.3-rc2-linux-mips64el.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2-linux-mips64el/lwjgl2-natives-2.9.3-rc2-linux-mips64el.jar\",\n            \"sha1\": \"8e96ae0b3ca2b566d4aa1ef737f1a11fde34636c\",\n            \"size\": 947858\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-mips64el\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-mips64el\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2/lwjgl2-natives-2.9.3-rc2-linux-mips64el.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2-linux-mips64el/lwjgl2-natives-2.9.3-rc2-linux-mips64el.jar\",\n            \"sha1\": \"8e96ae0b3ca2b566d4aa1ef737f1a11fde34636c\",\n            \"size\": 947858\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-mips64el\"\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-mips64el\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2-linux-mips64el/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar\",\n            \"sha1\": \"babec61846d8feb7a60cce1c9909281b1a3e0640\",\n            \"size\": 2464146\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-mips64el\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-openal:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-opengl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-glfw:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"sha1\": \"cbac1b8d30cb4795149c1ef540f912671a8616d0\",\n          \"size\": 128801\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-stb:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-mips64el\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2-linux-mips64el/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar\",\n            \"sha1\": \"babec61846d8feb7a60cce1c9909281b1a3e0640\",\n            \"size\": 2464146\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-mips64el\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"sha1\": \"cbac1b8d30cb4795149c1ef540f912671a8616d0\",\n          \"size\": 128801\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-mips64el\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2-linux-mips64el/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar\",\n            \"sha1\": \"babec61846d8feb7a60cce1c9909281b1a3e0640\",\n            \"size\": 2464146\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-mips64el\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-openal:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-opengl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-glfw:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"sha1\": \"cbac1b8d30cb4795149c1ef540f912671a8616d0\",\n          \"size\": 128801\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-stb:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl:3.3.1:natives-linux\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-mips64el\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2-linux-mips64el/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar\",\n            \"sha1\": \"babec61846d8feb7a60cce1c9909281b1a3e0640\",\n            \"size\": 2464146\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-mips64el\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-openal:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-stb:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux\": null,\n    \"net.java.jinput:jinput-platform:2.0.5:natives\": null,\n    \"com.mojang:text2speech:1.10.3:natives\": null,\n    \"com.mojang:text2speech:1.11.3:natives\": null,\n    \"com.mojang:text2speech:1.12.4:natives\": null,\n    \"com.mojang:text2speech:1.13.9:natives-linux\": null\n  },\n  \"linux-loongarch64\": {\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2/lwjgl2-natives-2.9.3-rc2-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2-linux-loongarch64/lwjgl2-natives-2.9.3-rc2-linux-loongarch64.jar\",\n            \"sha1\": \"f4d42d89b31d8c6c60e847042e350883c2cdaa25\",\n            \"size\": 623682\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2/lwjgl2-natives-2.9.3-rc2-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2-linux-loongarch64/lwjgl2-natives-2.9.3-rc2-linux-loongarch64.jar\",\n            \"sha1\": \"f4d42d89b31d8c6c60e847042e350883c2cdaa25\",\n            \"size\": 623682\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2/lwjgl2-natives-2.9.3-rc2-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc2-linux-loongarch64/lwjgl2-natives-2.9.3-rc2-linux-loongarch64.jar\",\n            \"sha1\": \"f4d42d89b31d8c6c60e847042e350883c2cdaa25\",\n            \"size\": 623682\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar\",\n            \"sha1\": \"2375ec8e8094a765ef61f3c4f2f832b1b8dfed4b\",\n            \"size\": 2651163\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-openal:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-opengl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-glfw:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"sha1\": \"cbac1b8d30cb4795149c1ef540f912671a8616d0\",\n          \"size\": 128801\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-stb:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar\",\n            \"sha1\": \"2375ec8e8094a765ef61f3c4f2f832b1b8dfed4b\",\n            \"size\": 2651163\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-openal:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-opengl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-glfw:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"sha1\": \"cbac1b8d30cb4795149c1ef540f912671a8616d0\",\n          \"size\": 128801\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-stb:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar\",\n            \"sha1\": \"2375ec8e8094a765ef61f3c4f2f832b1b8dfed4b\",\n            \"size\": 2651163\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-openal:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-opengl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-glfw:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"sha1\": \"cbac1b8d30cb4795149c1ef540f912671a8616d0\",\n          \"size\": 128801\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-stb:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl:3.3.1:natives-linux\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar\",\n            \"sha1\": \"2375ec8e8094a765ef61f3c4f2f832b1b8dfed4b\",\n            \"size\": 2651163\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-openal:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-stb:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.2:natives-linux\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.4-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2-linux-loongarch64/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar\",\n            \"sha1\": \"34a7f913c6750f2bede863f59c074cc4d540fb64\",\n            \"size\": 12234234\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux\": null,\n    \"org.lwjgl:lwjgl-openal:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.2:natives-linux\": null,\n    \"org.lwjgl:lwjgl-opengl:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux\": null,\n    \"org.lwjgl:lwjgl-glfw:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux\": null,\n    \"org.lwjgl:lwjgl-stb:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.2:natives-linux\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux\": null,\n    \"org.lwjgl:lwjgl:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.3:natives-linux\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.4-rc2\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2-linux-loongarch64/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar\",\n            \"sha1\": \"34a7f913c6750f2bede863f59c074cc4d540fb64\",\n            \"size\": 12234234\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux\": null,\n    \"org.lwjgl:lwjgl-openal:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.3:natives-linux\": null,\n    \"org.lwjgl:lwjgl-opengl:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.3:natives-linux\": null,\n    \"org.lwjgl:lwjgl-glfw:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.3:natives-linux\": null,\n    \"org.lwjgl:lwjgl-stb:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.3:natives-linux\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux\": null,\n    \"org.lwjgl:lwjgl-freetype:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar\",\n          \"sha1\": \"23f7bf165068ef2ca80ae1b79fd905af20498600\",\n          \"size\": 453489\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux\": null,\n    \"org.lwjgl:lwjgl:3.4.1:natives-linux\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.4.1-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1-linux-loongarch64/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar\",\n            \"sha1\": \"3fed872fc2f9929d2be25389dd312dfb48e9f89e\",\n            \"size\": 22854580\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.4.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-openal:3.4.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-opengl:3.4.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-glfw:3.4.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-stb:3.4.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-freetype:3.4.1:natives-linux\": null,\n    \"net.java.dev.jna:jna:5.8.0\": {\n      \"name\": \"net.java.dev.jna:jna:5.13.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar\",\n          \"sha1\": \"1200e7ebeedbe0d10062093f32925a912020e747\",\n          \"size\": 1879325\n        }\n      }\n    },\n    \"net.java.dev.jna:jna-platform:5.8.0\": {\n      \"name\": \"net.java.dev.jna:jna-platform:5.13.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"sha1\": \"88e9a306715e9379f3122415ef4ae759a352640d\",\n          \"size\": 1363209\n        }\n      }\n    },\n    \"net.java.dev.jna:jna:5.10.0\": {\n      \"name\": \"net.java.dev.jna:jna:5.13.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar\",\n          \"sha1\": \"1200e7ebeedbe0d10062093f32925a912020e747\",\n          \"size\": 1879325\n        }\n      }\n    },\n    \"net.java.dev.jna:jna-platform:5.10.0\": {\n      \"name\": \"net.java.dev.jna:jna-platform:5.13.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"sha1\": \"88e9a306715e9379f3122415ef4ae759a352640d\",\n          \"size\": 1363209\n        }\n      }\n    },\n    \"net.java.dev.jna:jna:5.12.1\": {\n      \"name\": \"net.java.dev.jna:jna:5.13.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar\",\n          \"sha1\": \"1200e7ebeedbe0d10062093f32925a912020e747\",\n          \"size\": 1879325\n        }\n      }\n    },\n    \"net.java.dev.jna:jna-platform:5.12.1\": {\n      \"name\": \"net.java.dev.jna:jna-platform:5.13.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"sha1\": \"88e9a306715e9379f3122415ef4ae759a352640d\",\n          \"size\": 1363209\n        }\n      }\n    },\n    \"net.java.jinput:jinput-platform:2.0.5:natives\": null,\n    \"com.mojang:text2speech:1.10.3:natives\": null,\n    \"com.mojang:text2speech:1.11.3:natives\": null,\n    \"com.mojang:text2speech:1.12.4:natives\": null,\n    \"com.mojang:text2speech:1.13.9:natives-linux\": null\n  },\n  \"linux-loongarch64_ow\": {\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1/lwjgl2-natives-2.9.3-rc1-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1-linux-loongarch64/lwjgl2-natives-2.9.3-rc1-linux-loongarch64.jar\",\n            \"sha1\": \"135e7e557431be70fa590eb6feffebb078d34728\",\n            \"size\": 594335\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1/lwjgl2-natives-2.9.3-rc1-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1-linux-loongarch64/lwjgl2-natives-2.9.3-rc1-linux-loongarch64.jar\",\n            \"sha1\": \"135e7e557431be70fa590eb6feffebb078d34728\",\n            \"size\": 594335\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1/lwjgl2-natives-2.9.3-rc1-linux-loongarch64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1-linux-loongarch64/lwjgl2-natives-2.9.3-rc1-linux-loongarch64.jar\",\n            \"sha1\": \"135e7e557431be70fa590eb6feffebb078d34728\",\n            \"size\": 594335\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64\"\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64_ow\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64_ow/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar\",\n            \"sha1\": \"4c7d6978dae411e5041f478d78cc329c4c75fc73\",\n            \"size\": 2311861\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64_ow\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-openal:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-opengl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-glfw:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"sha1\": \"cbac1b8d30cb4795149c1ef540f912671a8616d0\",\n          \"size\": 128801\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-stb:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6:natives\": null,\n    \"org.lwjgl:lwjgl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64_ow\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64_ow/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar\",\n            \"sha1\": \"4c7d6978dae411e5041f478d78cc329c4c75fc73\",\n            \"size\": 2311861\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64_ow\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-openal:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-opengl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-glfw:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"sha1\": \"cbac1b8d30cb4795149c1ef540f912671a8616d0\",\n          \"size\": 128801\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-stb:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1:natives\": null,\n    \"org.lwjgl:lwjgl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64_ow\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64_ow/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar\",\n            \"sha1\": \"4c7d6978dae411e5041f478d78cc329c4c75fc73\",\n            \"size\": 2311861\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64_ow\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-openal:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-opengl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-glfw:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar\",\n          \"sha1\": \"cbac1b8d30cb4795149c1ef540f912671a8616d0\",\n          \"size\": 128801\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-stb:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2:natives\": null,\n    \"org.lwjgl:lwjgl:3.3.1:natives-linux\": {\n      \"name\": \"org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"linux-loongarch64_ow\": {\n            \"path\": \"org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64_ow/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar\",\n            \"sha1\": \"4c7d6978dae411e5041f478d78cc329c4c75fc73\",\n            \"size\": 2311861\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"linux\": \"linux-loongarch64_ow\"\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-openal:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-stb:3.3.1:natives-linux\": null,\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux\": null,\n    \"net.java.dev.jna:jna:5.8.0\": {\n      \"name\": \"org.glavo.hmcl:jna:5.13.0-rc1-linux-loongarch64_ow\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/hmcl/jna/5.13.0-rc1-linux-loongarch64_ow/jna-5.13.0-rc1-linux-loongarch64_ow.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/jna/5.13.0-rc1-linux-loongarch64_ow/jna-5.13.0-rc1-linux-loongarch64_ow.jar\",\n          \"sha1\": \"42894c00949063f26752264288f2f2ab5179e21d\",\n          \"size\": 1886827\n        }\n      }\n    },\n    \"net.java.dev.jna:jna-platform:5.8.0\": {\n      \"name\": \"net.java.dev.jna:jna-platform:5.13.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"sha1\": \"88e9a306715e9379f3122415ef4ae759a352640d\",\n          \"size\": 1363209\n        }\n      }\n    },\n    \"net.java.dev.jna:jna:5.10.0\": {\n      \"name\": \"org.glavo.hmcl:jna:5.13.0-rc1-linux-loongarch64_ow\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/hmcl/jna/5.13.0-rc1-linux-loongarch64_ow/jna-5.13.0-rc1-linux-loongarch64_ow.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/jna/5.13.0-rc1-linux-loongarch64_ow/jna-5.13.0-rc1-linux-loongarch64_ow.jar\",\n          \"sha1\": \"42894c00949063f26752264288f2f2ab5179e21d\",\n          \"size\": 1886827\n        }\n      }\n    },\n    \"net.java.dev.jna:jna-platform:5.10.0\": {\n      \"name\": \"net.java.dev.jna:jna-platform:5.13.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"sha1\": \"88e9a306715e9379f3122415ef4ae759a352640d\",\n          \"size\": 1363209\n        }\n      }\n    },\n    \"net.java.dev.jna:jna:5.12.1\": {\n      \"name\": \"org.glavo.hmcl:jna:5.13.0-rc1-linux-loongarch64_ow\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/hmcl/jna/5.13.0-rc1-linux-loongarch64_ow/jna-5.13.0-rc1-linux-loongarch64_ow.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/jna/5.13.0-rc1-linux-loongarch64_ow/jna-5.13.0-rc1-linux-loongarch64_ow.jar\",\n          \"sha1\": \"42894c00949063f26752264288f2f2ab5179e21d\",\n          \"size\": 1886827\n        }\n      }\n    },\n    \"net.java.dev.jna:jna-platform:5.12.1\": {\n      \"name\": \"net.java.dev.jna:jna-platform:5.13.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar\",\n          \"sha1\": \"88e9a306715e9379f3122415ef4ae759a352640d\",\n          \"size\": 1363209\n        }\n      }\n    },\n    \"net.java.jinput:jinput-platform:2.0.5:natives\": null,\n    \"com.mojang:text2speech:1.10.3:natives\": null,\n    \"com.mojang:text2speech:1.11.3:natives\": null,\n    \"com.mojang:text2speech:1.12.4:natives\": null,\n    \"com.mojang:text2speech:1.13.9:natives-linux\": null\n  },\n  \"linux-riscv64\": {\n    \"org.lwjgl:lwjgl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"3ce36a5c5e6feb61bad2ea1426352482e04c6db1\",\n          \"size\": 80164\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6c5d38aad1dbd1c4945bcbe86640e616ceb9588f\",\n          \"size\": 197209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"74c5ddf47ad6bee41348bbb0735008694b5315af\",\n          \"size\": 516661\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e\",\n          \"size\": 57864\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"9582481036b6fb762c3f791e2ed26128152a1cec\",\n          \"size\": 117084\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"502804ee61eb7adb30bbb5e4459899f9a0cb23cf\",\n          \"size\": 215885\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8\",\n          \"size\": 46550\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"3ce36a5c5e6feb61bad2ea1426352482e04c6db1\",\n          \"size\": 80164\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6c5d38aad1dbd1c4945bcbe86640e616ceb9588f\",\n          \"size\": 197209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"74c5ddf47ad6bee41348bbb0735008694b5315af\",\n          \"size\": 516661\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e\",\n          \"size\": 57864\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"9582481036b6fb762c3f791e2ed26128152a1cec\",\n          \"size\": 117084\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"502804ee61eb7adb30bbb5e4459899f9a0cb23cf\",\n          \"size\": 215885\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8\",\n          \"size\": 46550\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"3ce36a5c5e6feb61bad2ea1426352482e04c6db1\",\n          \"size\": 80164\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6c5d38aad1dbd1c4945bcbe86640e616ceb9588f\",\n          \"size\": 197209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"74c5ddf47ad6bee41348bbb0735008694b5315af\",\n          \"size\": 516661\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e\",\n          \"size\": 57864\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"9582481036b6fb762c3f791e2ed26128152a1cec\",\n          \"size\": 117084\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"502804ee61eb7adb30bbb5e4459899f9a0cb23cf\",\n          \"size\": 215885\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8\",\n          \"size\": 46550\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"3ce36a5c5e6feb61bad2ea1426352482e04c6db1\",\n          \"size\": 80164\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6c5d38aad1dbd1c4945bcbe86640e616ceb9588f\",\n          \"size\": 197209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"74c5ddf47ad6bee41348bbb0735008694b5315af\",\n          \"size\": 516661\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e\",\n          \"size\": 57864\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"9582481036b6fb762c3f791e2ed26128152a1cec\",\n          \"size\": 117084\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"502804ee61eb7adb30bbb5e4459899f9a0cb23cf\",\n          \"size\": 215885\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8\",\n          \"size\": 46550\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"3ce36a5c5e6feb61bad2ea1426352482e04c6db1\",\n          \"size\": 80164\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6c5d38aad1dbd1c4945bcbe86640e616ceb9588f\",\n          \"size\": 197209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"74c5ddf47ad6bee41348bbb0735008694b5315af\",\n          \"size\": 516661\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e\",\n          \"size\": 57864\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"9582481036b6fb762c3f791e2ed26128152a1cec\",\n          \"size\": 117084\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"502804ee61eb7adb30bbb5e4459899f9a0cb23cf\",\n          \"size\": 215885\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8\",\n          \"size\": 46550\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"3ce36a5c5e6feb61bad2ea1426352482e04c6db1\",\n          \"size\": 80164\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6c5d38aad1dbd1c4945bcbe86640e616ceb9588f\",\n          \"size\": 197209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"74c5ddf47ad6bee41348bbb0735008694b5315af\",\n          \"size\": 516661\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e\",\n          \"size\": 57864\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"9582481036b6fb762c3f791e2ed26128152a1cec\",\n          \"size\": 117084\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"502804ee61eb7adb30bbb5e4459899f9a0cb23cf\",\n          \"size\": 215885\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8\",\n          \"size\": 46550\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar\",\n          \"sha1\": \"23f7bf165068ef2ca80ae1b79fd905af20498600\",\n          \"size\": 453489\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.3.4:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-linux-riscv64.jar\",\n          \"sha1\": \"2cb82939dc283686f9febf5349b66e773f515c59\",\n          \"size\": 1194584\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar\",\n          \"sha1\": \"6105690714874b995aa7812e4f7732a21109276c\",\n          \"size\": 1157957\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.4.1:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1-natives-linux-riscv64.jar\",\n          \"sha1\": \"a43b5208883562d31b5a2de1810aa82e2f2bf0ba\",\n          \"size\": 88670\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar\",\n          \"sha1\": \"16b663092854fc6adfe0a941e61cae2bb2e437b6\",\n          \"size\": 47872\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.4.1:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1-natives-linux-riscv64.jar\",\n          \"sha1\": \"ac7be1f86e87df030e668c02df2dec27c44da32b\",\n          \"size\": 216564\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar\",\n          \"sha1\": \"8508bd60589de0539aa465b78fcf838efd07ae9a\",\n          \"size\": 153919\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.4.1:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1-natives-linux-riscv64.jar\",\n          \"sha1\": \"652b9424f529dccc79694f2124709083d2712104\",\n          \"size\": 829997\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar\",\n          \"sha1\": \"830412cab823c029cda6b9729f9d2ed36cf79959\",\n          \"size\": 937660\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.4.1:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1-natives-linux-riscv64.jar\",\n          \"sha1\": \"8a6f47236738e3c682e98e15a13cf0358c1da85c\",\n          \"size\": 58079\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar\",\n          \"sha1\": \"a782b1ddd175c9107bf300fd92920579ea65e2a4\",\n          \"size\": 151893\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.4.1:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1-natives-linux-riscv64.jar\",\n          \"sha1\": \"d76ed6846bff9804cfd1adb84b068728645a097b\",\n          \"size\": 120912\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar\",\n          \"sha1\": \"f077c3dafc31924fbe8acb0b913f08977ba27f8e\",\n          \"size\": 142927\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.4.1:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1-natives-linux-riscv64.jar\",\n          \"sha1\": \"f1d8fe25d44e26c40f4dd4c48189323675467578\",\n          \"size\": 224156\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar\",\n          \"sha1\": \"562697d40dbd2ed0424bf24dcf65d51170f81adf\",\n          \"size\": 15931\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1-natives-linux-riscv64.jar\",\n          \"sha1\": \"6aa204e5047e9f272c7271119105833cc0ddfd27\",\n          \"size\": 46650\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar\",\n          \"sha1\": \"caa75a37e857efc9b0686e434d708d6420c5b6ea\",\n          \"size\": 464342\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.4.1:natives-linux-riscv64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1-natives-linux-riscv64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1-natives-linux-riscv64.jar\",\n          \"sha1\": \"737708c27a49ba5884ef347565fcb9964e8d85a9\",\n          \"size\": 1249074\n        }\n      }\n    },\n    \"com.github.oshi:oshi-core:6.6.5\": {\n      \"name\": \"com.github.oshi:oshi-core:6.8.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"com/github/oshi/oshi-core/6.8.0/oshi-core-6.8.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/com/github/oshi/oshi-core/6.8.0/oshi-core-6.8.0.jar\",\n          \"sha1\": \"004d12fac84286063e6842688610b8b2d43924a4\",\n          \"size\": 1011309\n        }\n      }\n    },\n    \"net.java.jinput:jinput-platform:2.0.5:natives\": null,\n    \"com.mojang:text2speech:1.10.3:natives\": null,\n    \"com.mojang:text2speech:1.11.3:natives\": null,\n    \"com.mojang:text2speech:1.12.4:natives\": null,\n    \"com.mojang:text2speech:1.13.9:natives-linux\": null\n  },\n  \"windows-x86_64\": {\n    \"mesa-loader\": {\n      \"name\": \"org.glavo:mesa-loader-windows:25.0.3:x64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x64.jar\",\n          \"sha1\": \"b2552fcc8c98e95b4559c39f2ff87cdb3aaaa513\",\n          \"size\": 27971214\n        }\n      }\n    },\n    \"software-renderer-loader\": {\n      \"name\": \"org.glavo:llvmpipe-loader:1.0\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar\",\n          \"sha1\": \"ff255415e5c4b2a18970da0a8e552b557ca013ae\",\n          \"size\": 12964773\n        }\n      }\n    }\n  },\n  \"windows-x86\": {\n    \"mesa-loader\": {\n      \"name\": \"org.glavo:mesa-loader-windows:25.0.3:x86\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x86.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x86.jar\",\n          \"sha1\": \"6fd0fd7da88ca6636f735e1e0f6feb7f62f59715\",\n          \"size\": 23311299\n        }\n      }\n    }\n  },\n  \"windows-arm64\": {\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"windows-arm64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1/lwjgl2-natives-2.9.3-rc1-windows-arm64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1-windows-arm64/lwjgl2-natives-2.9.3-rc1-windows-arm64.jar\",\n            \"sha1\": \"e0948a3692856d47854f3cd5a698d5c0233606d7\",\n            \"size\": 617106\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"windows\": \"windows-arm64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"windows-arm64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1/lwjgl2-natives-2.9.3-rc1-windows-arm64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1-windows-arm64/lwjgl2-natives-2.9.3-rc1-windows-arm64.jar\",\n            \"sha1\": \"e0948a3692856d47854f3cd5a698d5c0233606d7\",\n            \"size\": 617106\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"windows\": \"windows-arm64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"windows-arm64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1/lwjgl2-natives-2.9.3-rc1-windows-arm64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1-windows-arm64/lwjgl2-natives-2.9.3-rc1-windows-arm64.jar\",\n            \"sha1\": \"e0948a3692856d47854f3cd5a698d5c0233606d7\",\n            \"size\": 617106\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"windows\": \"windows-arm64\"\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"sha1\": \"4421d94af68e35dcaa31737a6fc59136a1e61b94\",\n          \"size\": 786196\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"d900e4678449ba97ff46fa64b22e0376bf8cd00e\",\n          \"size\": 133200\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"sha1\": \"877e17e39ebcd58a9c956dc3b5b777813de0873a\",\n          \"size\": 43233\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"598790de603c286dbc4068b27829eacc37592786\",\n          \"size\": 152780\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"sha1\": \"ae5357ed6d934546d3533993ea84c0cfb75eed95\",\n          \"size\": 108230\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"545ddec7959007a78b6662d616e00dacf00e1c29\",\n          \"size\": 627059\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"sha1\": \"ee8e95be0b438602038bc1f02dc5e3d011b1b216\",\n          \"size\": 928871\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"21df035bf03dbf5001f92291b24dc951da513481\",\n          \"size\": 83132\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"sha1\": \"757920418805fb90bfebb3d46b1d9e7669fca2eb\",\n          \"size\": 135828\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"e79c4857a887bd79ba78bdf2d422a7d333028a2d\",\n          \"size\": 141892\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"sha1\": \"a2550795014d622b686e9caac50b14baa87d2c70\",\n          \"size\": 118874\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"c29df97c3cca97dc00d34e171936153764c9f78b\",\n          \"size\": 218460\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"sha1\": \"9f65c248dd77934105274fcf8351abb75b34327c\",\n          \"size\": 13404\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"500f5daa3b731ca282d4b90aeafda94c528d3e27\",\n          \"size\": 110758\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar\",\n          \"sha1\": \"4421d94af68e35dcaa31737a6fc59136a1e61b94\",\n          \"size\": 786196\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"d900e4678449ba97ff46fa64b22e0376bf8cd00e\",\n          \"size\": 133200\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar\",\n          \"sha1\": \"877e17e39ebcd58a9c956dc3b5b777813de0873a\",\n          \"size\": 43233\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"598790de603c286dbc4068b27829eacc37592786\",\n          \"size\": 152780\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar\",\n          \"sha1\": \"ae5357ed6d934546d3533993ea84c0cfb75eed95\",\n          \"size\": 108230\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"545ddec7959007a78b6662d616e00dacf00e1c29\",\n          \"size\": 627059\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar\",\n          \"sha1\": \"ee8e95be0b438602038bc1f02dc5e3d011b1b216\",\n          \"size\": 928871\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"21df035bf03dbf5001f92291b24dc951da513481\",\n          \"size\": 83132\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar\",\n          \"sha1\": \"757920418805fb90bfebb3d46b1d9e7669fca2eb\",\n          \"size\": 135828\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"e79c4857a887bd79ba78bdf2d422a7d333028a2d\",\n          \"size\": 141892\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar\",\n          \"sha1\": \"a2550795014d622b686e9caac50b14baa87d2c70\",\n          \"size\": 118874\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"c29df97c3cca97dc00d34e171936153764c9f78b\",\n          \"size\": 218460\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar\",\n          \"sha1\": \"9f65c248dd77934105274fcf8351abb75b34327c\",\n          \"size\": 13404\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-windows-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-windows-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-windows-arm64.jar\",\n          \"sha1\": \"500f5daa3b731ca282d4b90aeafda94c528d3e27\",\n          \"size\": 110758\n        }\n      }\n    },\n    \"net.java.jinput:jinput-platform:2.0.5:natives\": null,\n    \"com.mojang:text2speech:1.10.3:natives\": null,\n    \"com.mojang:text2speech:1.11.3:natives\": null,\n    \"com.mojang:text2speech:1.12.4:natives\": null,\n    \"com.mojang:text2speech:1.13.9:natives-windows\": null,\n    \"mesa-loader\": {\n      \"name\": \"org.glavo:mesa-loader-windows:25.0.3:arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-arm64.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-arm64.jar\",\n          \"sha1\": \"40ca5de3351bf48b7b9efa5d2598d5b6b4d1ab8e\",\n          \"size\": 24905270\n        }\n      }\n    }\n  },\n  \"macos-arm64\": {\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.1-nightly-20130708-debug3:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"osx-arm64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1/lwjgl2-natives-2.9.3-rc1-osx-arm64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1-osx-arm64/lwjgl2-natives-2.9.3-rc1-osx-arm64.jar\",\n            \"sha1\": \"042ef2024a9a4054952c496c9ebcdb83345dabe0\",\n            \"size\": 500107\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"osx\": \"osx-arm64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.1:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"osx-arm64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1/lwjgl2-natives-2.9.3-rc1-osx-arm64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1-osx-arm64/lwjgl2-natives-2.9.3-rc1-osx-arm64.jar\",\n            \"sha1\": \"042ef2024a9a4054952c496c9ebcdb83345dabe0\",\n            \"size\": 500107\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"osx\": \"osx-arm64\"\n      }\n    },\n    \"org.lwjgl.lwjgl:lwjgl-platform:2.9.2-nightly-20140822:natives\": {\n      \"name\": \"org.glavo.hmcl:lwjgl2-natives:2.9.3-rc1\",\n      \"downloads\": {\n        \"classifiers\": {\n          \"osx-arm64\": {\n            \"path\": \"org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1/lwjgl2-natives-2.9.3-rc1-osx-arm64.jar\",\n            \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl2-natives/2.9.3-rc1-osx-arm64/lwjgl2-natives-2.9.3-rc1-osx-arm64.jar\",\n            \"sha1\": \"042ef2024a9a4054952c496c9ebcdb83345dabe0\",\n            \"size\": 500107\n          }\n        }\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"natives\": {\n        \"osx\": \"osx-arm64\"\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"71d0d5e469c9c95351eb949064497e3391616ac9\",\n          \"size\": 42693\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"e577b87d8ad2ade361aaea2fcf226c660b15dee8\",\n          \"size\": 103475\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"23d55e7490b57495320f6c9e1936d78fd72c4ef8\",\n          \"size\": 346125\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"eafe34b871d966292e8db0f1f3d6b8b110d4e91d\",\n          \"size\": 41665\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6\": {\n      \"name\": \"org.glavo.hmcl.mmachina:lwjgl-glfw:3.3.1-mmachina.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/hmcl/mmachina/lwjgl-glfw/3.3.1-mmachina.1/lwjgl-glfw-3.3.1-mmachina.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/mmachina/lwjgl-glfw/3.3.1-mmachina.1/lwjgl-glfw-3.3.1-mmachina.1.jar\",\n          \"sha1\": \"e9a101bca4fa30d26b21b526ff28e7c2d8927f1b\",\n          \"size\": 130128\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"cac0d3f712a3da7641fa174735a5f315de7ffe0a\",\n          \"size\": 129077\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"fcf073ed911752abdca5f0b00a53cfdf17ff8e8b\",\n          \"size\": 178408\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"972ecc17bad3571e81162153077b4d47b7b9eaa9\",\n          \"size\": 41380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar\",\n          \"sha1\": \"ae58664f88e18a9bb2c77b063833ca7aaec484cb\",\n          \"size\": 724243\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"71d0d5e469c9c95351eb949064497e3391616ac9\",\n          \"size\": 42693\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar\",\n          \"sha1\": \"a817bcf213db49f710603677457567c37d53e103\",\n          \"size\": 36601\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"e577b87d8ad2ade361aaea2fcf226c660b15dee8\",\n          \"size\": 103475\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar\",\n          \"sha1\": \"2623a6b8ae1dfcd880738656a9f0243d2e6840bd\",\n          \"size\": 88237\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"23d55e7490b57495320f6c9e1936d78fd72c4ef8\",\n          \"size\": 346125\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar\",\n          \"sha1\": \"831a5533a21a5f4f81bbc51bb13e9899319b5411\",\n          \"size\": 921563\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"eafe34b871d966292e8db0f1f3d6b8b110d4e91d\",\n          \"size\": 41665\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1\": {\n      \"name\": \"org.glavo.hmcl.mmachina:lwjgl-glfw:3.3.1-mmachina.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/hmcl/mmachina/lwjgl-glfw/3.3.1-mmachina.1/lwjgl-glfw-3.3.1-mmachina.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/mmachina/lwjgl-glfw/3.3.1-mmachina.1/lwjgl-glfw-3.3.1-mmachina.1.jar\",\n          \"sha1\": \"e9a101bca4fa30d26b21b526ff28e7c2d8927f1b\",\n          \"size\": 130128\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"cac0d3f712a3da7641fa174735a5f315de7ffe0a\",\n          \"size\": 129077\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar\",\n          \"sha1\": \"b119297cf8ed01f247abe8685857f8e7fcf5980f\",\n          \"size\": 112380\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"fcf073ed911752abdca5f0b00a53cfdf17ff8e8b\",\n          \"size\": 178408\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar\",\n          \"sha1\": \"0ff1914111ef2e3e0110ef2dabc8d8cdaad82347\",\n          \"size\": 6767\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-macos-arm64\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-natives-macos-arm64.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-natives-macos-arm64.jar\",\n          \"sha1\": \"972ecc17bad3571e81162153077b4d47b7b9eaa9\",\n          \"size\": 41380\n        }\n      }\n    },\n    \"ca.weblite:java-objc-bridge:1.0.0\": {\n      \"name\": \"org.glavo.hmcl.mmachina:java-objc-bridge:1.1.0-mmachina.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/glavo/hmcl/mmachina/java-objc-bridge/1.1.0-mmachina.1/java-objc-bridge-1.1.0-mmachina.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/glavo/hmcl/mmachina/java-objc-bridge/1.1.0-mmachina.1/java-objc-bridge-1.1.0-mmachina.1.jar\",\n          \"sha1\": \"369a83621e3c65496348491e533cb97fe5f2f37d\",\n          \"size\": 91947\n        }\n      }\n    },\n    \"ca.weblite:java-objc-bridge:1.0.0:natives\": null,\n    \"com.mojang:text2speech:1.10.3\": {\n      \"name\": \"com.mojang:text2speech:1.11.3\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"com/mojang/text2speech/1.11.3/text2speech-1.11.3.jar\",\n          \"url\": \"https://libraries.minecraft.net/com/mojang/text2speech/1.11.3/text2speech-1.11.3.jar\",\n          \"sha1\": \"f378f889797edd7df8d32272c06ca80a1b6b0f58\",\n          \"size\": 13164\n        }\n      }\n    },\n    \"net.java.jinput:jinput-platform:2.0.5:natives\": null,\n    \"com.mojang:text2speech:1.10.3:natives\": null,\n    \"com.mojang:text2speech:1.11.3:natives\": null,\n    \"com.mojang:text2speech:1.12.4:natives\": null\n  },\n  \"freebsd-x86_64\": {\n    \"org.lwjgl:lwjgl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"610d14530e637564d97d74af7cb98a737e70b77b\",\n          \"size\": 96209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"5ee27f3bad4715067cef0630682da4bb5a1b88ac\",\n          \"size\": 157297\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"3863f8268f5515c27f1364257f8a018f0c6afa79\",\n          \"size\": 597486\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"579071d2a3714f5662522f7d3edf58e941580587\",\n          \"size\": 81028\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f67b9b6c29451d8fea66db17aaba2f65e908c7e9\",\n          \"size\": 104415\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f5551338a1e2035ff747053f0e985dc93db1235c\",\n          \"size\": 226093\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.1.6:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"acd5e1b9b9b99ce4d21867058ee468ee45a859e5\",\n          \"size\": 40104\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"610d14530e637564d97d74af7cb98a737e70b77b\",\n          \"size\": 96209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"5ee27f3bad4715067cef0630682da4bb5a1b88ac\",\n          \"size\": 157297\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"3863f8268f5515c27f1364257f8a018f0c6afa79\",\n          \"size\": 597486\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"579071d2a3714f5662522f7d3edf58e941580587\",\n          \"size\": 81028\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f67b9b6c29451d8fea66db17aaba2f65e908c7e9\",\n          \"size\": 104415\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f5551338a1e2035ff747053f0e985dc93db1235c\",\n          \"size\": 226093\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.1:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"acd5e1b9b9b99ce4d21867058ee468ee45a859e5\",\n          \"size\": 40104\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"610d14530e637564d97d74af7cb98a737e70b77b\",\n          \"size\": 96209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"5ee27f3bad4715067cef0630682da4bb5a1b88ac\",\n          \"size\": 157297\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"3863f8268f5515c27f1364257f8a018f0c6afa79\",\n          \"size\": 597486\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"579071d2a3714f5662522f7d3edf58e941580587\",\n          \"size\": 81028\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f67b9b6c29451d8fea66db17aaba2f65e908c7e9\",\n          \"size\": 104415\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f5551338a1e2035ff747053f0e985dc93db1235c\",\n          \"size\": 226093\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.2.2:natives\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"acd5e1b9b9b99ce4d21867058ee468ee45a859e5\",\n          \"size\": 40104\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"610d14530e637564d97d74af7cb98a737e70b77b\",\n          \"size\": 96209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"5ee27f3bad4715067cef0630682da4bb5a1b88ac\",\n          \"size\": 157297\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"3863f8268f5515c27f1364257f8a018f0c6afa79\",\n          \"size\": 597486\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"579071d2a3714f5662522f7d3edf58e941580587\",\n          \"size\": 81028\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f67b9b6c29451d8fea66db17aaba2f65e908c7e9\",\n          \"size\": 104415\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f5551338a1e2035ff747053f0e985dc93db1235c\",\n          \"size\": 226093\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"acd5e1b9b9b99ce4d21867058ee468ee45a859e5\",\n          \"size\": 40104\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"610d14530e637564d97d74af7cb98a737e70b77b\",\n          \"size\": 96209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"5ee27f3bad4715067cef0630682da4bb5a1b88ac\",\n          \"size\": 157297\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"3863f8268f5515c27f1364257f8a018f0c6afa79\",\n          \"size\": 597486\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"579071d2a3714f5662522f7d3edf58e941580587\",\n          \"size\": 81028\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f67b9b6c29451d8fea66db17aaba2f65e908c7e9\",\n          \"size\": 104415\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f5551338a1e2035ff747053f0e985dc93db1235c\",\n          \"size\": 226093\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.2\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"acd5e1b9b9b99ce4d21867058ee468ee45a859e5\",\n          \"size\": 40104\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar\",\n          \"sha1\": \"b86c3e4832426e8a6b466013b7cb34b40e9ce956\",\n          \"size\": 800127\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"610d14530e637564d97d74af7cb98a737e70b77b\",\n          \"size\": 96209\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar\",\n          \"sha1\": \"e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4\",\n          \"size\": 46430\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"5ee27f3bad4715067cef0630682da4bb5a1b88ac\",\n          \"size\": 157297\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar\",\n          \"sha1\": \"9b74d3ea380c83353d42af43ad9659e04dabe84a\",\n          \"size\": 113103\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"3863f8268f5515c27f1364257f8a018f0c6afa79\",\n          \"size\": 597486\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar\",\n          \"sha1\": \"2852ac7d9f6fc71349f1ce28e2708ff1977f18af\",\n          \"size\": 931960\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"579071d2a3714f5662522f7d3edf58e941580587\",\n          \"size\": 81028\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar\",\n          \"sha1\": \"7e46ecdec85db8738053cfde1414352cd62dab74\",\n          \"size\": 147044\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f67b9b6c29451d8fea66db17aaba2f65e908c7e9\",\n          \"size\": 104415\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar\",\n          \"sha1\": \"5821735d5ef23f6da8542887344e57eb181b7cac\",\n          \"size\": 143112\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"f5551338a1e2035ff747053f0e985dc93db1235c\",\n          \"size\": 226093\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar\",\n          \"sha1\": \"2d73789ffd8962b38d9d599cc38b2383ce818c7a\",\n          \"size\": 15928\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"acd5e1b9b9b99ce4d21867058ee468ee45a859e5\",\n          \"size\": 40104\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.3.3\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.3.4\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar\",\n          \"sha1\": \"23f7bf165068ef2ca80ae1b79fd905af20498600\",\n          \"size\": 453489\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.3.4:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar\",\n          \"sha1\": \"67d6775292771087cb3d5ba1239bf9bf42fb3bd7\",\n          \"size\": 1176759\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar\",\n          \"sha1\": \"6105690714874b995aa7812e4f7732a21109276c\",\n          \"size\": 1157957\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl:3.4.1:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1-natives-freebsd.jar\",\n          \"sha1\": \"0b1c18e52be76ce4e0a05e323e01060406887351\",\n          \"size\": 102712\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar\",\n          \"sha1\": \"16b663092854fc6adfe0a941e61cae2bb2e437b6\",\n          \"size\": 47872\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-jemalloc:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.4.1:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1-natives-freebsd.jar\",\n          \"sha1\": \"a9565df163061a20bd3062dd7eb240d019e748b2\",\n          \"size\": 159419\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar\",\n          \"sha1\": \"8508bd60589de0539aa465b78fcf838efd07ae9a\",\n          \"size\": 153919\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-openal:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.4.1:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1-natives-freebsd.jar\",\n          \"sha1\": \"c28d721b930590df368c9503fd84f06e4a3211f5\",\n          \"size\": 830092\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar\",\n          \"sha1\": \"830412cab823c029cda6b9729f9d2ed36cf79959\",\n          \"size\": 937660\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-opengl:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.4.1:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1-natives-freebsd.jar\",\n          \"sha1\": \"fdde3b33c1fb4c9329da9e741fa894266baef71f\",\n          \"size\": 81739\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar\",\n          \"sha1\": \"a782b1ddd175c9107bf300fd92920579ea65e2a4\",\n          \"size\": 151893\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-glfw:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.4.1:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1-natives-freebsd.jar\",\n          \"sha1\": \"c367840595bdaa6f84830138c8b5cf59618b60fa\",\n          \"size\": 106220\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar\",\n          \"sha1\": \"f077c3dafc31924fbe8acb0b913f08977ba27f8e\",\n          \"size\": 142927\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-stb:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.4.1:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1-natives-freebsd.jar\",\n          \"sha1\": \"299c873b02718f5a11bc31279189d434a7279c15\",\n          \"size\": 224821\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar\",\n          \"sha1\": \"562697d40dbd2ed0424bf24dcf65d51170f81adf\",\n          \"size\": 15931\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1-natives-freebsd.jar\",\n          \"sha1\": \"c7cabb0cf65ab1025667335bf4e4df3af9ef4e01\",\n          \"size\": 40471\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.4.1\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.4.1\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar\",\n          \"sha1\": \"caa75a37e857efc9b0686e434d708d6420c5b6ea\",\n          \"size\": 464342\n        }\n      }\n    },\n    \"org.lwjgl:lwjgl-freetype:3.4.1:natives-linux\": {\n      \"name\": \"org.lwjgl:lwjgl-freetype:3.4.1:natives-freebsd\",\n      \"downloads\": {\n        \"artifact\": {\n          \"path\": \"org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1-natives-freebsd.jar\",\n          \"url\": \"https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1-natives-freebsd.jar\",\n          \"sha1\": \"d7e622ab389bb8549617b0b20db5824b7b2e262b\",\n          \"size\": 1217256\n        }\n      }\n    },\n    \"net.java.jinput:jinput-platform:2.0.5:natives\": null,\n    \"com.mojang:text2speech:1.10.3:natives\": null,\n    \"com.mojang:text2speech:1.11.3:natives\": null,\n    \"com.mojang:text2speech:1.12.4:natives\": null,\n    \"com.mojang:text2speech:1.13.9:natives-linux\": null\n  }\n}"
  },
  {
    "path": "HMCL/src/main/resources/assets/openjfx-dependencies.json",
    "content": "{\n  \"windows-x86\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"19.0.2.1\",\n        \"classifier\": \"win-x86\",\n        \"sha1\": \"6c9ebafc7f9c4544d72fa5e306f4111e56b5db58\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"19.0.2.1\",\n        \"classifier\": \"win-x86\",\n        \"sha1\": \"a14a1fbe3a0dca81d99c53fd7be8e7c784a68afe\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"19.0.2.1\",\n        \"classifier\": \"win-x86\",\n        \"sha1\": \"7df1501701f9e9fbadab8ce55ef1dde128e2e88a\"\n      }\n    ]\n  },\n  \"windows-x86_64\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"win\",\n        \"sha1\": \"c000208028c983b9ccf85b57c74e0e74f85849f0\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"win\",\n        \"sha1\": \"f3aba10d2c9f767e41831aeed6e1cd5418299c03\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"win\",\n        \"sha1\": \"ed4a6cb56461d8536e339abf8d7f771e4acf9054\"\n      }\n    ],\n    \"modern\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"25\",\n        \"classifier\": \"win\",\n        \"sha1\": \"1119ad6aa5bdc83cdbfacdcfe657f1ba26c0bacc\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"25\",\n        \"classifier\": \"win\",\n        \"sha1\": \"773a7affb4f0422935233cfdcb16986a6b4982d5\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"25\",\n        \"classifier\": \"win\",\n        \"sha1\": \"d68b7b7ab8b01c94b79205423337f03995ee55ef\"\n      }\n    ]\n  },\n  \"windows-arm64\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"18.0.2+1-arm64\",\n        \"classifier\": \"win\",\n        \"sha1\": \"4518a696b9d509dc09a7fe283452fce84a1686a8\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"18.0.2+1-arm64\",\n        \"classifier\": \"win\",\n        \"sha1\": \"e19ba9aefc4bba8ff86dcdf416620b93b7bdea39\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"18.0.2+1-arm64\",\n        \"classifier\": \"win\",\n        \"sha1\": \"0bf7380823bb8c420dd41837d2c71087b8953ec1\"\n      }\n    ]\n  },\n  \"macos-x86_64\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"mac\",\n        \"sha1\": \"ce5cc0a19e2351281e1bf8d1cf798a9743739cfe\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"mac\",\n        \"sha1\": \"dd15793df474565ec39def7c645ea88a06073848\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"mac\",\n        \"sha1\": \"8dd8254e99be91c75c36756bba5c157275e44fa8\"\n      }\n    ],\n    \"modern\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"25\",\n        \"classifier\": \"mac\",\n        \"sha1\": \"336b81e175591aab779d0a776fd59cd6f91822c5\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"25\",\n        \"classifier\": \"mac\",\n        \"sha1\": \"6fecc3a53eb4eba86ad434952b0996491a210727\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"25\",\n        \"classifier\": \"mac\",\n        \"sha1\": \"60b2883c52983d8fdfb11210ddc27951d1e9ce67\"\n      }\n    ]\n  },\n  \"macos-arm64\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"mac-aarch64\",\n        \"sha1\": \"dd76c9028469d84f4475634b428da56fac99cad7\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"mac-aarch64\",\n        \"sha1\": \"055ae5d08c5cf0a71da00f80d31a4bdfb3068aeb\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"mac-aarch64\",\n        \"sha1\": \"8a2622c5e6df1334cb3a62d6829c60326261e47d\"\n      }\n    ],\n    \"modern\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"25\",\n        \"classifier\": \"mac-aarch64\",\n        \"sha1\": \"ce7b7a51e705639cc62a32e464114d6c06e1df0e\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"25\",\n        \"classifier\": \"mac-aarch64\",\n        \"sha1\": \"01914f76d206a4cdb19311445e5aff18ea9c091a\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"25\",\n        \"classifier\": \"mac-aarch64\",\n        \"sha1\": \"a5528f381b061cb83dda75e019da3c8c4d4dc5a1\"\n      }\n    ]\n  },\n  \"linux-x86_64\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"4a9707853e6cfe0520ebe24ad3c58411ffedf5c0\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"efc080757b6b0cfe1ecbfddef27a44496549afa4\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"21.0.8\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"3e48e35bfebdb3de88092909ef69de25db22eaf8\"\n      }\n    ],\n    \"modern\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"25\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"1f52994bfed549d07ef52a5fa4603db17ea44fbe\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"25\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"dd1b0cfb56cec5fb3434cbdc030c02b1c8e773b2\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"25\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"d2a1bb11a5fb507ed5da975eee949d8f78a75d9a\"\n      }\n    ]\n  },\n  \"linux-arm32\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"19.0.2.1\",\n        \"classifier\": \"linux-arm32-monocle\",\n        \"sha1\": \"452f455d6948788c1e5350a41259eb8101d3f82a\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"19.0.2.1\",\n        \"classifier\": \"linux-arm32-monocle\",\n        \"sha1\": \"b45b33252e88263fe80a462a45828b4562c3c709\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"19.0.2.1\",\n        \"classifier\": \"linux-arm32-monocle\",\n        \"sha1\": \"e606c619fc493ecd18d281b0ded3aa38ba15a9e5\"\n      }\n    ]\n  },\n  \"linux-arm64\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"21.0.1\",\n        \"classifier\": \"linux-aarch64\",\n        \"sha1\": \"e8d960421a991229e373a417a1202da19b1782d8\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"21.0.1\",\n        \"classifier\": \"linux-aarch64\",\n        \"sha1\": \"4e2e682b65edf70691f4dabef37bdf97653bbc4c\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"21.0.1\",\n        \"classifier\": \"linux-aarch64\",\n        \"sha1\": \"ad7b31977534f66e80ebb939a88651cba2c34122\"\n      }\n    ],\n    \"modern\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"25\",\n        \"classifier\": \"linux-aarch64\",\n        \"sha1\": \"e63ea8dde9078578ed470c2c057a8767d23e6eed\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"25\",\n        \"classifier\": \"linux-aarch64\",\n        \"sha1\": \"ea0e286fd1270afd04b4c19823469a9df64e7682\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"25\",\n        \"classifier\": \"linux-aarch64\",\n        \"sha1\": \"04f845ca609a8cd8339b9b466ed12b527a7ffa04\"\n      }\n    ]\n  },\n  \"linux-loongarch64\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"17.0.8-loongarch64\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"9d692dfc79eb334a7b4bae922aa57cb3a437345e\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"17.0.8-loongarch64\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"7f241dc6adc8beddb776f453a3c63a5848d7b90b\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"17.0.8-loongarch64\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"1c8b0141bec93ed21d7c0e9a2f69a1c7e3c734d2\"\n      }\n    ]\n  },\n  \"linux-loongarch64_ow\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"19-ea+10-loongson64\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"f90663ba93aef9f818236ce19ecfa33dca0ffe10\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"19-ea+10-loongson64\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"6ff76304d08bba093abfe7f4d50ce6a49279c87f\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"19-ea+10-loongson64\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"8a16096d42de70f2548f18840cdcd49a07fc1654\"\n      }\n    ]\n  },\n  \"linux-riscv64\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"19.0.2.1-riscv64\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"e33c7e0bac2931a8f7a752bb74e4e4e535eec4ad\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"19.0.2.1-riscv64\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"ac4e5edd55d84da80f8fc2d81a4c9994296a6c09\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"19.0.2.1-riscv64\",\n        \"classifier\": \"linux\",\n        \"sha1\": \"efe6a87ea24972d45f1931528f87e64a53bd2232\"\n      }\n    ]\n  },\n  \"freebsd-x86_64\": {\n    \"classic\": [\n      {\n        \"module\": \"javafx.base\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-base\",\n        \"version\": \"14.0.2.1-freebsd\",\n        \"classifier\": \"freebsd\",\n        \"sha1\": \"7bac900f0ab0d4d6dcf178252cf37ee1e0470d40\"\n      },\n      {\n        \"module\": \"javafx.graphics\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-graphics\",\n        \"version\": \"14.0.2.1-freebsd\",\n        \"classifier\": \"freebsd\",\n        \"sha1\": \"7773ce02d1dc29160801e4e077ed2a26e93bed13\"\n      },\n      {\n        \"module\": \"javafx.controls\",\n        \"groupId\": \"org.glavo.hmcl.openjfx\",\n        \"artifactId\": \"javafx-controls\",\n        \"version\": \"14.0.2.1-freebsd\",\n        \"classifier\": \"freebsd\",\n        \"sha1\": \"34a3dd3ccbc898bacfdcb3256cfb87ddc50621d3\"\n      }\n    ]\n  }\n}"
  },
  {
    "path": "HMCL/src/main/resources/assets/terracotta.json",
    "content": "{\n  \"__comment__\": \"THIS FILE IS MACHINE GENERATED! DO NOT EDIT!\",\n  \"version_latest\": \"0.4.2\",\n  \"packages\": {\n    \"windows-x86_64\": {\n      \"hash\": \"6a98f524d4f00373696517306af8aa50d01d55ce4eadb27e9e4bc2f882707a0b5f20d5d4c33371d1459dcf5bf144ffed9beb414202d9ccf32b11dbbfcf19d650\",\n      \"files\": {\n        \"VCRUNTIME140.DLL\": \"3d4b24061f72c0e957c7b04a0c4098c94c8f1afb4a7e159850b9939c7210d73398be6f27b5ab85073b4e8c999816e7804fef0f6115c39cd061f4aaeb4dcda8cf\",\n        \"terracotta-0.4.2-windows-x86_64.exe\": \"6e98d1f2380ed22fb5a2dd4aafce6c773e9cf69100c8bb8e49e7d6983756bdb9a31f80e06bcfbe5a2742144fe806d3d687dec54d8f09d87c659341f99dd9fd80\"\n      }\n    },\n    \"windows-arm64\": {\n      \"hash\": \"fc1077247014ac0c712469498bde2ef7f6d881d5fcb7bdd5e11ebe20218fed365be19afdb8d453a79d77b729f866058522b910741767f4df947faa891434b463\",\n      \"files\": {\n        \"VCRUNTIME140.DLL\": \"5cb5ce114614101d260f4754c09e8a0dd57e4da885ebb96b91e274326f3e1dd95ed0ade9f542f1922fad0ed025e88a1f368e791e1d01fae69718f0ec3c7b98c8\",\n        \"terracotta-0.4.2-windows-arm64.exe\": \"30a15c5c53e5817c5a3634532172559327474741d3b2c7ef4e8a30acc6f59cdcf3570bf5f583e3cbe9e2abc8253e977c1abda1e9f36c88c4e99240da257347d0\"\n      }\n    },\n    \"macos-x86_64\": {\n      \"hash\": \"a762e4b2d6f84e899292b9e3856d009411a516d3c47f54575f843ce082f63dff2baa68ba0faa844b8b64fb12e91017386f15f5e7f975f8ee605bf8d4217cb091\",\n      \"files\": {\n        \"terracotta-0.4.2-macos-x86_64\": \"24efb85390eff88a538ed7e503fb1488e5e622730ca30c741a0e8b4c8f4e8d4868a2f9f38da8de540aeb535af2fd1e41c7081dae9c700e8a1a03b6c540218164\",\n        \"terracotta-0.4.2-macos-x86_64.pkg\": \"0f80437061231018ec0f860f875aba02cae9e0b36d21f2db8e99d25948806e1119f7844a4d4acca01d6124d7079718762cdebe3756b005a55632de7029fe0d24\"\n      }\n    },\n    \"macos-arm64\": {\n      \"hash\": \"09e444fea2d9fd19f3e5cb62e29055228345be163924cbd408d947646fafed1012cf48508ee6a155ede3d571e2ffaa72d09ceeb1493c8a60feb05e0699f19ba3\",\n      \"files\": {\n        \"terracotta-0.4.2-macos-arm64\": \"8e59a9d78acd57702dc044d6f2799c6af586b075a262f7d4dbbf0876e1af8d8271e04783c24ff820b801e3b14cd0190ab8403d097f3f2d98b6d911f95ed1e972\",\n        \"terracotta-0.4.2-macos-arm64.pkg\": \"b0b72f8883767c359f0700e958bd859dd5932c4674fbdf2a32f7de189fb9f54822d4825a754dc856416c8760338eb8ffca80b40c0a57c608fbe6ae9928888993\"\n      }\n    },\n    \"linux-x86_64\": {\n      \"hash\": \"d326ad95815d04568d485b5038e40ffc47ca54292fa0925eee6f5cea014024f901d661708aac2a743037b990882ad82b4d0b7bb03dc3b2fe720dbf0f3efe1c98\",\n      \"files\": {\n        \"terracotta-0.4.2-linux-x86_64\": \"fac328ba8957a711b03557bb913940f22d61b76608cd203fdf51024b6f94b19f5bc91c9b8a9fa80baf6968e1e6873c1880fd4cf54a2f8e3c6cf1e6ac161f8d0c\"\n      }\n    },\n    \"linux-arm64\": {\n      \"hash\": \"57c08f48d9535e93ad547d2dfc852d267992cc164a7208b42a2da0a6cbc2f21862f610e02a746b4b67150f4dec26b86a4f96eb9bd2f58d124d5b40ba50c6d55e\",\n      \"files\": {\n        \"terracotta-0.4.2-linux-arm64\": \"d807744c2041c98686e4b505324713badea7a0f31e8810be49ae053a63fb6dfc474ac58d678fb93eea0dd5cccff7372d9ec6135a1046f4b306cad35cd90ecacd\"\n      }\n    },\n    \"linux-loongarch64\": {\n      \"hash\": \"a19f4ad4fa1fd2f3c1c867f8743178ab0524d1634d63e122ffc080623e4f14207ecab58830fa0d888696ef28ce4cfa189e6ffbe92c6e1aa1836652617a96a801\",\n      \"files\": {\n        \"terracotta-0.4.2-linux-loongarch64\": \"7d4896b275207def0861e544cf2afc9501c1946f1bc8bb2bad9ef4510dd915eabb63eeedff725e69565cdf5c27c146b52dd75d24115d5141909c0865f89cd7b9\"\n      }\n    },\n    \"linux-riscv64\": {\n      \"hash\": \"ef3dfcf3c64dfcaf94adc518f0184d017a32a924f67ef33ffb8128f20669cf177cd345df3be8c00225a275bd44f70657b94fb43c60afeb21238000ac7a7f1721\",\n      \"files\": {\n        \"terracotta-0.4.2-linux-riscv64\": \"afddc55f16f5cf3103548d13f7a3238964c8899dba8eb0e1fdc9e354da65e91fa4486de4b8680a7a3d9326eacf18c1ddb6b5b1f9459908f35f77b36f769172ea\"\n      }\n    },\n    \"freebsd-x86_64\": {\n      \"hash\": \"a4d02ebbb1419f4e9265f5b24060432799151a27e0d1871690d2f3a51f40bcfd6d92c176001a15a62caa9bf31ea1b67a7b2da10e3351bfcdd36bfac1377d08d9\",\n      \"files\": {\n        \"terracotta-0.4.2-freebsd-x86_64\": \"a50da02752cef73d21804b26eb02261568ed3053c8fef1074014d50226843810f71e6eda39a53af3bad8e173cec40826631a4c8877af460d14a307f1b0a62519\"\n      }\n    }\n  },\n  \"downloads\": [\n    \"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz\"\n  ],\n  \"downloads_CN\": [\n    \"https://gitee.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz\",\n    \"https://cnb.cool/HMCL-Terracotta/Terracotta/-/releases/download/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz\",\n    \"https://alist.8mi.tech/d/mirror/HMCL-Terracotta/Auto/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz\"\n  ],\n  \"links\": [\n    {\n      \"desc\": {\n        \"default\": \"GitHub Release\",\n        \"zh\": \"GitHub 发布页\",\n        \"zh-Hant\": \"GitHub 發布頁\"\n      },\n      \"link\": \"https://github.com/burningtnt/Terracotta/releases/tag/v${version}\"\n    },\n    {\n      \"desc\": {\n        \"default\": \"Tencent QQ Group\",\n        \"zh\": \"QQ 群\",\n        \"zh-Hant\": \"QQ 群\"\n      },\n      \"link\": \"https://qm.qq.com/cgi-bin/qm/qr?k\\u003dnIf5u5xQ3LXEP4ZEmLQtfjtpppjgHfI5\\u0026jump_from\\u003dwebapi\\u0026authKey\\u003dsXStlPuGzhD1JyAhyExd2OwjzZkRf3x7bAEb/j1xNX1wrQcDdg71qPrhumIm6pyf\"\n    }\n  ]\n}"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/JavaFXLauncher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\npublic final class JavaFXLauncher {\n\n    private JavaFXLauncher() {\n    }\n\n    private static boolean started = false;\n\n    static {\n        // init JavaFX Toolkit\n        try {\n            javafx.application.Platform.startup(() -> {\n            });\n            started = true;\n        } catch (IllegalStateException e) {\n            started = true;\n        } catch (Throwable e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static void start() {\n    }\n\n    public static boolean isStarted() {\n        return started;\n    }\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/setting/ThemeColorTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.setting;\n\nimport javafx.scene.paint.Color;\nimport org.jackhuang.hmcl.theme.ThemeColor;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\n/// @author Glavo\npublic final class ThemeColorTest {\n\n    @Test\n    public void testOf() {\n        assertEquals(new ThemeColor(\"#AABBCC\", Color.web(\"#AABBCC\")), ThemeColor.of(\"#AABBCC\"));\n        assertEquals(new ThemeColor(\"blue\", Color.web(\"#5C6BC0\")), ThemeColor.of(\"blue\"));\n        assertEquals(new ThemeColor(\"darker_blue\", Color.web(\"#283593\")), ThemeColor.of(\"darker_blue\"));\n        assertEquals(new ThemeColor(\"green\", Color.web(\"#43A047\")), ThemeColor.of(\"green\"));\n        assertEquals(new ThemeColor(\"orange\", Color.web(\"#E67E22\")), ThemeColor.of(\"orange\"));\n        assertEquals(new ThemeColor(\"purple\", Color.web(\"#9C27B0\")), ThemeColor.of(\"purple\"));\n        assertEquals(new ThemeColor(\"red\", Color.web(\"#B71C1C\")), ThemeColor.of(\"red\"));\n\n        assertNull(ThemeColor.of((String) null));\n        assertNull(ThemeColor.of(\"\"));\n        assertNull(ThemeColor.of(\"unknown\"));\n    }\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/ui/GameCrashWindowTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport org.jackhuang.hmcl.JavaFXLauncher;\nimport org.jackhuang.hmcl.game.ClassicVersion;\nimport org.jackhuang.hmcl.game.LaunchOptions;\nimport org.jackhuang.hmcl.java.JavaInfo;\nimport org.jackhuang.hmcl.game.Log;\nimport org.jackhuang.hmcl.launch.ProcessListener;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.stream.Collectors;\n\npublic class GameCrashWindowTest {\n\n    @Test\n    @Disabled\n    public void test() throws Exception {\n        JavaFXLauncher.start();\n\n        ManagedProcess process = new ManagedProcess(null, Arrays.asList(\"commands\", \"2\"));\n\n        String logs = Files.readString(new File(\"../HMCLCore/src/test/resources/logs/too_old_java.txt\").toPath());\n\n        CountDownLatch latch = new CountDownLatch(1);\n        FXUtils.runInFX(() -> {\n            Path workingPath = Path.of(System.getProperty(\"user.dir\"));\n\n            GameCrashWindow window = new GameCrashWindow(process, ProcessListener.ExitType.APPLICATION_ERROR, null,\n                    new ClassicVersion(),\n                    new LaunchOptions.Builder()\n                            .setJava(new JavaRuntime(workingPath, new JavaInfo(Platform.SYSTEM_PLATFORM, \"16\", null), false, false))\n                            .setGameDir(workingPath)\n                            .create(),\n                    Arrays.stream(logs.split(\"\\\\n\"))\n                            .map(Log::new)\n                            .collect(Collectors.toList()));\n\n            window.showAndWait();\n\n            latch.countDown();\n        });\n        latch.await();\n    }\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/ui/SVGTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui;\n\nimport javafx.geometry.Bounds;\nimport javafx.scene.shape.SVGPath;\nimport org.junit.jupiter.api.condition.EnabledIf;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/// @author Glavo\npublic final class SVGTest {\n\n    @ParameterizedTest\n    @EnumSource(SVG.class)\n    @EnabledIf(\"org.jackhuang.hmcl.JavaFXLauncher#isStarted\")\n    public void testViewWindow(SVG svg) {\n        SVGPath path = new SVGPath();\n        path.setContent(svg.getPath());\n\n        Bounds layoutBounds = path.getLayoutBounds();\n        assertEquals(\n                new ViewBox(0, 0, 24, 24),\n                new ViewBox(layoutBounds.getMinX(), layoutBounds.getMinY(), layoutBounds.getWidth(), layoutBounds.getHeight())\n        );\n    }\n\n    private record ViewBox(double minX, double minY, double width, double height) {\n    }\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/ui/image/ImageUtilsTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.image;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic final class ImageUtilsTest {\n\n    private static byte[] readHeaderBuffer(String fileName) {\n        try (var input = Files.newInputStream(Path.of(\"src/test/resources/image/\" + fileName))) {\n            return input.readNBytes(ImageUtils.HEADER_BUFFER_SIZE);\n\n        } catch (IOException e) {\n            throw new AssertionError(e);\n        }\n    }\n\n    @Test\n    public void testIsApng() {\n        assertTrue(ImageUtils.isApng(readHeaderBuffer(\"16x16.apng\")));\n        assertFalse(ImageUtils.isApng(readHeaderBuffer(\"16x16.png\")));\n        assertFalse(ImageUtils.isApng(readHeaderBuffer(\"16x16-lossless.webp\")));\n        assertFalse(ImageUtils.isApng(readHeaderBuffer(\"16x16-lossy.webp\")));\n        assertFalse(ImageUtils.isApng(readHeaderBuffer(\"16x16-animation-lossy.webp\")));\n        assertFalse(ImageUtils.isApng(readHeaderBuffer(\"16x16-animation-lossy.webp\")));\n    }\n\n    @Test\n    public void testIsWebP() {\n        assertFalse(ImageUtils.isWebP(readHeaderBuffer(\"16x16.apng\")));\n        assertFalse(ImageUtils.isWebP(readHeaderBuffer(\"16x16.png\")));\n        assertTrue(ImageUtils.isWebP(readHeaderBuffer(\"16x16-lossless.webp\")));\n        assertTrue(ImageUtils.isWebP(readHeaderBuffer(\"16x16-lossy.webp\")));\n        assertTrue(ImageUtils.isWebP(readHeaderBuffer(\"16x16-animation-lossy.webp\")));\n        assertTrue(ImageUtils.isWebP(readHeaderBuffer(\"16x16-animation-lossy.webp\")));\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/ui/image/ImageViewTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.ui.image;\n\nimport javafx.application.Application;\nimport javafx.stage.Stage;\n\npublic class ImageViewTest extends Application {\n    @Override\n    public void start(Stage primaryStage) throws Exception {\n\n    }\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/ui/skin/SkinCanvasSupport.java",
    "content": "package org.jackhuang.hmcl.ui.skin;\n\nimport javafx.application.Platform;\nimport javafx.scene.Scene;\nimport javafx.scene.image.Image;\nimport javafx.scene.input.DragEvent;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.input.ScrollEvent;\nimport javafx.scene.input.TransferMode;\nimport javafx.stage.Stage;\nimport org.jackhuang.hmcl.ui.skin.test.Test;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.util.function.Consumer;\n\npublic abstract class SkinCanvasSupport implements Consumer<SkinCanvas> {\n\n    public static final class Mouse extends SkinCanvasSupport {\n\n        private double lastX, lastY, sensitivity;\n\n        public void setSensitivity(double sensitivity) {\n            this.sensitivity = sensitivity;\n        }\n\n        public double getSensitivity() {\n            return sensitivity;\n        }\n\n        public Mouse(double sensitivity) {\n            this.sensitivity = sensitivity;\n        }\n\n        @Override\n        public void accept(SkinCanvas t) {\n            t.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> {\n                lastX = -1;\n                lastY = -1;\n            });\n            t.addEventHandler(MouseEvent.MOUSE_DRAGGED, e -> {\n                if (!(lastX == -1 || lastY == -1)) {\n                    if (e.isAltDown() || e.isControlDown() || e.isShiftDown()) {\n                        if (e.isShiftDown())\n                            t.zRotate.setAngle(t.zRotate.getAngle() - (e.getSceneY() - lastY) * sensitivity);\n                        if (e.isAltDown())\n                            t.yRotate.setAngle(t.yRotate.getAngle() + (e.getSceneX() - lastX) * sensitivity);\n                        if (e.isControlDown())\n                            t.xRotate.setAngle(t.xRotate.getAngle() + (e.getSceneY() - lastY) * sensitivity);\n                    } else {\n                        double yaw = t.yRotate.getAngle() + (e.getSceneX() - lastX) * sensitivity;\n                        yaw %= 360;\n                        if (yaw < 0)\n                            yaw += 360;\n\n                        int flagX = yaw < 90 || yaw > 270 ? 1 : -1;\n                        int flagZ = yaw < 180 ? -1 : 1;\n                        double kx = Math.abs(90 - yaw % 180) / 90 * flagX, kz = Math.abs(90 - (yaw + 90) % 180) / 90 * flagZ;\n\n                        t.xRotate.setAngle(t.xRotate.getAngle() + (e.getSceneY() - lastY) * sensitivity * kx);\n                        t.yRotate.setAngle(yaw);\n                        t.zRotate.setAngle(t.zRotate.getAngle() + (e.getSceneY() - lastY) * sensitivity * kz);\n                    }\n                }\n                lastX = e.getSceneX();\n                lastY = e.getSceneY();\n            });\n            t.addEventHandler(ScrollEvent.SCROLL, e -> {\n                double delta = (e.getDeltaY() > 0 ? 1 : e.getDeltaY() == 0 ? 0 : -1) / 10D * sensitivity;\n                t.scale.setX(Math.min(Math.max(t.scale.getX() - delta, 0.1), 10));\n                t.scale.setY(Math.min(Math.max(t.scale.getY() - delta, 0.1), 10));\n            });\n        }\n\n    }\n\n    public static class Drag extends SkinCanvasSupport {\n\n        private String title;\n\n        public Drag(String title) {\n            this.title = title;\n        }\n\n        @Override\n        public void accept(SkinCanvas t) {\n            t.addEventHandler(DragEvent.DRAG_OVER, e -> {\n                if (e.getDragboard().hasFiles()) {\n                    File file = e.getDragboard().getFiles().get(0);\n                    if (file.getAbsolutePath().endsWith(\".png\"))\n                        e.acceptTransferModes(TransferMode.COPY);\n                }\n            });\n            t.addEventHandler(DragEvent.DRAG_DROPPED, e -> {\n                if (e.isAccepted()) {\n                    File skin = e.getDragboard().getFiles().get(0);\n                    Platform.runLater(() -> {\n                        try {\n                            FileInputStream input = new FileInputStream(skin);\n                            Stage stage = new Stage();\n                            stage.setTitle(title);\n                            SkinCanvas canvas = Test.createSkinCanvas();\n                            canvas.updateSkin(new Image(input), skin.getName().contains(\"[alex]\"), null);\n                            Scene scene = new Scene(canvas);\n                            stage.setScene(scene);\n                            stage.show();\n                        } catch (FileNotFoundException ex) {\n                            ex.printStackTrace();\n                        }\n                    });\n                }\n            });\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/ui/skin/test/Test.java",
    "content": "package org.jackhuang.hmcl.ui.skin.test;\n\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.stage.Stage;\nimport org.jackhuang.hmcl.ui.skin.FunctionHelper;\nimport org.jackhuang.hmcl.ui.skin.SkinCanvas;\nimport org.jackhuang.hmcl.ui.skin.SkinCanvasSupport;\nimport org.jackhuang.hmcl.ui.skin.animation.SkinAniRunning;\nimport org.jackhuang.hmcl.ui.skin.animation.SkinAniWavingArms;\nimport org.jackhuang.hmcl.game.TexturesLoader;\n\nimport java.util.function.Consumer;\n\npublic class Test extends Application {\n\n    public static final String TITLE = \"FX - Minecraft skin preview\";\n\n    public static SkinCanvas createSkinCanvas() {\n        SkinCanvas canvas = new SkinCanvas(TexturesLoader.getDefaultSkinImage(), 400, 400, true);\n        canvas.getAnimationPlayer().addSkinAnimation(new SkinAniWavingArms(100, 2000, 7.5, canvas), new SkinAniRunning(100, 100, 30, canvas));\n        FunctionHelper.alwaysB(Consumer<SkinCanvas>::accept, canvas, new SkinCanvasSupport.Mouse(.5), new SkinCanvasSupport.Drag(TITLE));\n        return canvas;\n    }\n\n    @Override\n    public void start(Stage stage) throws Exception {\n        stage.setTitle(TITLE);\n        Scene scene = new Scene(createSkinCanvas());\n        stage.setScene(scene);\n        stage.show();\n\n    }\n\n    public static void main(String... args) {\n        launch(args);\n    }\n\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/util/AggregatedObservableListTest.java",
    "content": "package org.jackhuang.hmcl.util;\n\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Testing the AggregatedObservableList\n */\npublic class AggregatedObservableListTest {\n\n    @Test\n    public void testInteger() {\n        final AggregatedObservableList<Integer> aggregatedWrapper = new AggregatedObservableList<>();\n        final ObservableList<Integer> aggregatedList = aggregatedWrapper.getAggregatedList();\n\n        final ObservableList<Integer> list1 = FXCollections.observableArrayList();\n        final ObservableList<Integer> list2 = FXCollections.observableArrayList();\n        final ObservableList<Integer> list3 = FXCollections.observableArrayList();\n\n        list1.addAll(1, 2, 3, 4, 5);\n        list2.addAll(10, 11, 12, 13, 14, 15);\n        list3.addAll(100, 110, 120, 130, 140, 150);\n\n        // adding list 1 to aggregate\n        aggregatedWrapper.appendList(list1);\n        assertEquals(\"[1,2,3,4,5]\", aggregatedWrapper.dump());\n\n        // removing elems from list1\n        list1.remove(2, 4);\n        assertEquals(\"[1,2,5]\", aggregatedWrapper.dump());\n\n        // adding second List\n        aggregatedWrapper.appendList(list2);\n        assertEquals(\"[1,2,5,10,11,12,13,14,15]\", aggregatedWrapper.dump());\n\n        // removing elems from second List\n        list2.remove(1, 3);\n        assertEquals(\"[1,2,5,10,13,14,15]\", aggregatedWrapper.dump());\n\n        // replacing element in first list\n        list1.set(1, 3);\n        assertEquals(\"[1,3,5,10,13,14,15]\", aggregatedWrapper.dump());\n\n        // adding third List\n        aggregatedWrapper.appendList(list3);\n        assertEquals(\"[1,3,5,10,13,14,15,100,110,120,130,140,150]\", aggregatedWrapper.dump());\n\n        // emptying second list\n        list2.clear();\n        assertEquals(\"[1,3,5,100,110,120,130,140,150]\", aggregatedWrapper.dump());\n\n        // adding new elements to second list\n        list2.addAll(203, 202, 201);\n        assertEquals(\"[1,3,5,203,202,201,100,110,120,130,140,150]\", aggregatedWrapper.dump());\n\n        // sorting list2. this results in permutation\n        list2.sort(Integer::compareTo);\n        assertEquals(\"[1,3,5,201,202,203,100,110,120,130,140,150]\", aggregatedWrapper.dump());\n\n        // removing list2 completely\n        aggregatedWrapper.removeList(list2);\n        assertEquals(\"[1,3,5,100,110,120,130,140,150]\", aggregatedWrapper.dump());\n\n        // updating one integer value in list 3\n        list3.set(0, 1);\n        assertEquals(\"[1,3,5,1,110,120,130,140,150]\", aggregatedWrapper.dump());\n\n        // prepending list 2 again\n        aggregatedWrapper.prependList(list2);\n        assertEquals(\"[201,202,203,1,3,5,1,110,120,130,140,150]\", aggregatedWrapper.dump());\n\n    }\n\n    @Test\n    public void testSetAll() {\n        final AggregatedObservableList<Integer> aggregatedWrapper = new AggregatedObservableList<>();\n        final ObservableList<Integer> aggregatedList = aggregatedWrapper.getAggregatedList();\n\n        final ObservableList<Integer> a = FXCollections.singletonObservableList(1);\n        final ObservableList<Integer> b = FXCollections.observableArrayList(2, 3, 4);\n\n        aggregatedWrapper.appendList(a);\n        aggregatedWrapper.appendList(b);\n\n        assertEquals(\"[1,2,3,4]\", aggregatedWrapper.dump());\n\n        b.setAll(7, 8, 9);\n        assertEquals(\"[1,7,8,9]\", aggregatedWrapper.dump());\n    }\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/util/i18n/translator/TranslatorTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n.translator;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * @author Glavo\n */\npublic final class TranslatorTest {\n\n    //region lzh\n\n    private static void assertYearToLZH(String value, int year) {\n        StringBuilder builder = new StringBuilder(2);\n        Translator_lzh.appendYear(builder, year);\n        assertEquals(value, builder.toString());\n    }\n\n    @Test\n    public void testYearToLZH() {\n        assertYearToLZH(\"甲子\", 1984);\n        assertYearToLZH(\"乙巳\", 2025);\n        assertYearToLZH(\"甲子\", -2996);\n        assertYearToLZH(\"庚子\", 1000);\n    }\n\n    @Test\n    public void testHourToLZH() {\n        List<String> list = List.of(\n                \"子正\", \"丑初\", \"丑正\", \"寅初\", \"寅正\", \"卯初\", \"卯正\", \"辰初\", \"辰正\", \"巳初\", \"巳正\", \"午初\",\n                \"午正\", \"未初\", \"未正\", \"申初\", \"申正\", \"酉初\", \"酉正\", \"戌初\", \"戌正\", \"亥初\", \"亥正\", \"子初\"\n        );\n        for (int hour = 0; hour < list.size(); hour++) {\n            StringBuilder builder = new StringBuilder(2);\n            Translator_lzh.appendHour(builder, hour);\n            assertEquals(list.get(hour), builder.toString());\n        }\n    }\n\n    //endregion\n}\n"
  },
  {
    "path": "HMCL/src/test/java/org/jackhuang/hmcl/util/url/data/DataUriTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.url.data;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.net.URI;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic final class DataUriTest {\n\n    private static String readString(String uri) throws IOException {\n        return new DataUri(URI.create(uri)).readString();\n    }\n\n    private static byte[] readBytes(String uri) throws IOException {\n        return new DataUri(URI.create(uri)).readBytes();\n    }\n\n    @Test\n    public void testReadString() throws IOException {\n        assertEquals(\"Hello, World!\", readString(\"data:,Hello%2C%20World%21\"));\n        assertEquals(\"Hello, World!\", readString(\"data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==\"));\n        assertEquals(\"<h1>Hello, World!</h1>\", readString(\"data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E\"));\n        assertEquals(\"<script>alert('hi');</script>\", readString(\"data:text/html,%3Cscript%3Ealert%28%27hi%27%29%3B%3C%2Fscript%3E\"));\n    }\n}\n"
  },
  {
    "path": "HMCL/terracotta-template.json",
    "content": "{\n  \"__comment__\": \"Run upgradeTerracottaConfig task with Gradle to resolve this template.\",\n\n  \"version_latest\": \"@script_generated\",\n\n  \"packages\": \"@script_generated\",\n\n  \"downloads\": [\n    \"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz\"\n  ],\n  \"downloads_CN\": [\n    \"https://gitee.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz\",\n    \"https://cnb.cool/HMCL-Terracotta/Terracotta/-/releases/download/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz\",\n    \"https://alist.8mi.tech/d/mirror/HMCL-Terracotta/Auto/v${version}/terracotta-${version}-${classifier}-pkg.tar.gz\"\n  ],\n\n  \"links\": [\n    {\n      \"desc\": {\n        \"default\": \"GitHub Release\",\n        \"zh\": \"GitHub 发布页\",\n        \"zh-Hant\": \"GitHub 發布頁\"\n      },\n      \"link\": \"https://github.com/burningtnt/Terracotta/releases/tag/v${version}\"\n    },\n    {\n      \"desc\": {\n        \"default\": \"Tencent QQ Group\",\n        \"zh\": \"QQ 群\",\n        \"zh-Hant\": \"QQ 群\"\n      },\n      \"link\": \"https://qm.qq.com/cgi-bin/qm/qr?k=nIf5u5xQ3LXEP4ZEmLQtfjtpppjgHfI5&jump_from=webapi&authKey=sXStlPuGzhD1JyAhyExd2OwjzZkRf3x7bAEb/j1xNX1wrQcDdg71qPrhumIm6pyf\"\n    }\n  ]\n}"
  },
  {
    "path": "HMCLBoot/build.gradle.kts",
    "content": "plugins {\n    id(\"java-library\")\n}\n\ntasks.compileJava {\n    options.release.set(8)\n}\n\ntasks.compileTestJava {\n    options.release.set(17)\n}\n"
  },
  {
    "path": "HMCLBoot/src/main/java/org/jackhuang/hmcl/BootProperties.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\nimport org.jackhuang.hmcl.util.UTF8Control;\n\nimport java.util.ResourceBundle;\n\nfinal class BootProperties {\n\n    private static ResourceBundle resourceBundle;\n\n    static ResourceBundle getResourceBundle() {\n        if (resourceBundle == null) {\n            resourceBundle = ResourceBundle.getBundle(\"assets.lang.boot\", new UTF8Control());\n        }\n\n        return resourceBundle;\n    }\n\n    private BootProperties() {\n    }\n}\n"
  },
  {
    "path": "HMCLBoot/src/main/java/org/jackhuang/hmcl/DesktopUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Locale;\n\n/// @author Glavo\npublic final class DesktopUtils {\n    private static final String[] linuxBrowsers = {\n            \"xdg-open\",\n            \"google-chrome\",\n            \"firefox\",\n            \"microsoft-edge\",\n            \"opera\",\n            \"konqueror\",\n            \"mozilla\"\n    };\n\n    public static Path which(String command) {\n        String path = System.getenv(\"PATH\");\n        if (path == null)\n            return null;\n\n        try {\n            for (String item : path.split(File.pathSeparator)) {\n                try {\n                    Path program = Paths.get(item, command);\n                    if (Files.isExecutable(program))\n                        return program.toRealPath();\n                } catch (Throwable ignored) {\n                }\n            }\n        } catch (Throwable ignored) {\n        }\n\n        return null;\n    }\n\n    public static void openLink(String link) {\n        String osName = System.getProperty(\"os.name\").toLowerCase(Locale.ROOT);\n\n        Throwable e1 = null;\n        try {\n            if (osName.startsWith(\"windows\")) {\n                Runtime.getRuntime().exec(new String[]{\"rundll32.exe\", \"url.dll,FileProtocolHandler\", link});\n                return;\n            } else if (osName.startsWith(\"mac\") || osName.startsWith(\"darwin\")) {\n                Runtime.getRuntime().exec(new String[]{\"open\", link});\n                return;\n            } else {\n                for (String browser : linuxBrowsers) {\n                    Path path = which(browser);\n                    if (path != null) {\n                        try {\n                            Runtime.getRuntime().exec(new String[]{path.toString(), link});\n                            return;\n                        } catch (Throwable ignored) {\n                        }\n                    }\n                }\n                System.err.println(\"No known browser found\");\n            }\n        } catch (Throwable e) {\n            e1 = e;\n        }\n\n        try {\n            java.awt.Desktop.getDesktop().browse(new URI(link));\n        } catch (Throwable e2) {\n            if (e1 != null)\n                e2.addSuppressed(e1);\n\n            e2.printStackTrace(System.err);\n        }\n    }\n\n    private DesktopUtils() {\n    }\n}\n"
  },
  {
    "path": "HMCLBoot/src/main/java/org/jackhuang/hmcl/EntryPoint.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\n/**\n * This is a dummy class and will be overwritten in the shadow jar.\n */\npublic final class EntryPoint {\n    private EntryPoint() {\n    }\n\n    public static void main(String[] args) {\n        throw new AssertionError(\"This method should not be called, please verify that the HMCL is complete.\");\n    }\n}\n"
  },
  {
    "path": "HMCLBoot/src/main/java/org/jackhuang/hmcl/Main.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\nimport org.jackhuang.hmcl.util.SwingUtils;\n\nimport javax.swing.*;\nimport java.net.URISyntaxException;\nimport java.nio.file.FileSystemNotFoundException;\nimport java.nio.file.Paths;\nimport java.security.CodeSource;\nimport java.security.ProtectionDomain;\nimport java.util.ResourceBundle;\n\n/**\n * @author Glavo\n */\npublic final class Main {\n    private static final int MINIMUM_JAVA_VERSION = 17;\n    private static final String DOWNLOAD_PAGE = \"https://hmcl.huangyuhui.net/download/\";\n\n    private Main() {\n    }\n\n    static int findFirstNotNumber(String str, int start) {\n        if (start >= str.length())\n            return -1;\n\n        char ch = str.charAt(start);\n        if (ch < '0' || ch > '9')\n            return -1;\n\n        for (int i = start + 1; i < str.length(); i++) {\n            ch = str.charAt(i);\n            if (ch < '0' || ch > '9')\n                return i;\n        }\n        return str.length();\n    }\n\n    static int getJavaFeatureVersion(String javaVersion) {\n        if (javaVersion == null)\n            return -1;\n\n        try {\n            int end = findFirstNotNumber(javaVersion, 0);\n            if (end < 0)\n                return -1; // No valid version number found\n\n            int major = Integer.parseInt(javaVersion.substring(0, end));\n            if (major > 1)\n                return major;\n\n            if (major < 1)\n                return -1; // Invalid major version\n\n            // Java 1.x versions\n            int start = end + 1;\n            end = findFirstNotNumber(javaVersion, start);\n\n            if (end < 0)\n                return -1; // No valid minor version found\n\n            return Integer.parseInt(javaVersion.substring(start, end));\n        } catch (NumberFormatException e) {\n            return -1; // version number is too long\n        }\n    }\n\n    static void showErrorAndExit(String[] args) {\n        SwingUtils.initLookAndFeel();\n\n        ResourceBundle resourceBundle = BootProperties.getResourceBundle();\n        String errorTitle = resourceBundle.getString(\"boot.message.error\");\n\n        if (args.length > 0 && args[0].equals(\"--apply-to\")) {\n            String errorMessage = resourceBundle.getString(\"boot.manual_update\");\n            System.err.println(errorMessage);\n            int result = JOptionPane.showOptionDialog(null, errorMessage, errorTitle, JOptionPane.YES_NO_OPTION,\n                    JOptionPane.ERROR_MESSAGE, null, null, null);\n\n            if (result == JOptionPane.YES_OPTION) {\n                System.out.println(\"Open \" + DOWNLOAD_PAGE);\n                DesktopUtils.openLink(DOWNLOAD_PAGE);\n            }\n        } else {\n            String errorMessage = resourceBundle.getString(\"boot.unsupported_java_version\");\n            System.err.println(errorMessage);\n            SwingUtils.showErrorDialog(errorMessage, errorTitle);\n        }\n\n        System.exit(1);\n    }\n\n    private static void checkDirectoryPath() {\n        String currentDir = System.getProperty(\"user.dir\", \"\");\n        String jarPath = getThisJarPath();\n        if (currentDir.contains(\"!\")) {\n            SwingUtils.initLookAndFeel();\n            System.err.println(\"The current working path contains an exclamation mark: \" + currentDir);\n            // No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.\n            SwingUtils.showErrorDialog(\"Exclamation mark(!) is not allowed in the working path.\\n\" + \"The path is \" + currentDir);\n            System.exit(1);\n        } else if (jarPath != null && jarPath.contains(\"!\")) {\n            SwingUtils.initLookAndFeel();\n            System.err.println(\"The jar path contains an exclamation mark: \" + jarPath);\n            // No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.\n            SwingUtils.showErrorDialog(\"Exclamation mark(!) is not allowed in the path where HMCL is in.\\n\" + \"The path is \" + jarPath);\n            System.exit(1);\n        }\n    }\n\n    private static String getThisJarPath() {\n        ProtectionDomain protectionDomain = Main.class.getProtectionDomain();\n        if (protectionDomain == null)\n            return null;\n\n        CodeSource codeSource = protectionDomain.getCodeSource();\n        if (codeSource == null || codeSource.getLocation() == null)\n            return null;\n\n        try {\n            return Paths.get(codeSource.getLocation().toURI()).toAbsolutePath().normalize().toString();\n        } catch (FileSystemNotFoundException | IllegalArgumentException | URISyntaxException e) {\n            return null;\n        }\n    }\n\n    public static void main(String[] args) throws Throwable {\n        checkDirectoryPath();\n        if (getJavaFeatureVersion(System.getProperty(\"java.version\")) >= MINIMUM_JAVA_VERSION) {\n            EntryPoint.main(args);\n        } else {\n            showErrorAndExit(args);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLBoot/src/main/java/org/jackhuang/hmcl/util/SwingUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport javax.swing.*;\n\n/**\n * @author Glavo\n */\npublic final class SwingUtils {\n    static {\n        if (System.getProperty(\"swing.defaultlaf\") == null) {\n            try {\n                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\n            } catch (Throwable ignored) {\n            }\n        }\n    }\n\n    private SwingUtils() {\n    }\n\n    public static void initLookAndFeel() {\n        // Make sure the static constructor is called\n    }\n\n    public static void showInfoDialog(Object message) {\n        showInfoDialog(message, \"Info\");\n    }\n\n    public static void showInfoDialog(Object message, String title) {\n        JOptionPane.showMessageDialog(null, message, title, JOptionPane.INFORMATION_MESSAGE);\n    }\n\n    public static void showWarningDialog(Object message) {\n        showWarningDialog(message, \"Warning\");\n    }\n\n    public static void showWarningDialog(Object message, String title) {\n        JOptionPane.showMessageDialog(null, message, title, JOptionPane.WARNING_MESSAGE);\n    }\n\n    public static void showErrorDialog(Object message) {\n        showErrorDialog(message, \"Error\");\n    }\n\n    public static void showErrorDialog(Object message, String title) {\n        JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);\n    }\n}\n"
  },
  {
    "path": "HMCLBoot/src/main/java/org/jackhuang/hmcl/util/UTF8Control.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.util.Locale;\nimport java.util.PropertyResourceBundle;\nimport java.util.ResourceBundle;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\npublic final class UTF8Control extends ResourceBundle.Control {\n\n    public UTF8Control() {}\n\n    @Override\n    public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IOException {\n        // The below is a copy of the default implementation.\n        String bundleName = toBundleName(baseName, locale);\n        String resourceName = toResourceName(bundleName, \"properties\");\n        ResourceBundle bundle = null;\n        InputStream stream = null;\n        if (reload) {\n            URL url = loader.getResource(resourceName);\n            if (url != null) {\n                URLConnection connection = url.openConnection();\n                if (connection != null) {\n                    connection.setUseCaches(false);\n                    stream = connection.getInputStream();\n                }\n            }\n        } else {\n            stream = loader.getResourceAsStream(resourceName);\n        }\n        if (stream != null) {\n            try {\n                // Only this line is changed to make it to read properties files as UTF-8.\n                bundle = new PropertyResourceBundle(new InputStreamReader(stream, UTF_8));\n            } finally {\n                stream.close();\n            }\n        }\n        return bundle;\n    }\n}\n"
  },
  {
    "path": "HMCLBoot/src/main/resources/assets/lang/boot.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\nboot.unsupported_java_version=HMCL requires Java 17 or later to run, but still supports launching games with Java 6~16. Please install the latest version of Java and try opening HMCL again.\\nYou can keep your old version of Java. HMCL can detect and manage multiple Java installations, and will automatically select the appropriate Java version for your game.\nboot.manual_update=HMCL cannot complete automatic updates in the current environment. Please download the latest version of HMCL manually.\\nWould you like to go to the download page?\n\nboot.message.error=Error"
  },
  {
    "path": "HMCLBoot/src/main/resources/assets/lang/boot_es.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\nboot.unsupported_java_version=HMCL requiere Java 17 o posterior para ejecutarse, pero sigue admitiendo el inicio de juegos con Java 6~16.\\nPor favor, instala la última versión de Java e intenta ejecutar HMCL de nuevo.\\nPuedes conservar tu versión antigua de Java. HMCL puede detectar y gestionar múltiples instalaciones de Java,\\ny seleccionará automáticamente la versión de Java adecuada para tu juego.\n"
  },
  {
    "path": "HMCLBoot/src/main/resources/assets/lang/boot_zh.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\nboot.unsupported_java_version=HMCL 需要 Java 17 或更高版本才能运行，但依然支持使用 Java 6~16 启动游戏。请安装最新版本的 Java 再尝试启动 HMCL。\\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java，并会自动根据游戏版本为你选择合适的 Java。\\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\nboot.manual_update=HMCL 在当前环境无法完成自动更新，请手动下载最新版本的 HMCL。\\n是否前往下载页面？\n\nboot.message.error=错误"
  },
  {
    "path": "HMCLBoot/src/main/resources/assets/lang/boot_zh_Hant.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\nboot.unsupported_java_version=HMCL 需要 Java 17 或更高版本才能執行，但依然支援使用 Java 6~16 啟動遊戲。請安裝最新版本的 Java 再嘗試開啟 HMCL。\\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java，並會自動根據遊戲版本為你選取合適的 Java。\nboot.manual_update=HMCL 在當前環境無法完成自動更新，請手動下載最新版本的 HMCL。\\n是否前往下載頁面？\n\nboot.message.error=錯誤\n"
  },
  {
    "path": "HMCLBoot/src/test/java/org/jackhuang/hmcl/MainTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic final class MainTest {\n    @Test\n    public void testGetJavaFeatureVersion() {\n        assertEquals(6, Main.getJavaFeatureVersion(\"1.6.0\"));\n        assertEquals(6, Main.getJavaFeatureVersion(\"1.6.0_45\"));\n        assertEquals(7, Main.getJavaFeatureVersion(\"1.7.0\"));\n        assertEquals(7, Main.getJavaFeatureVersion(\"1.7.0_80\"));\n        assertEquals(8, Main.getJavaFeatureVersion(\"1.8\"));\n        assertEquals(8, Main.getJavaFeatureVersion(\"1.8u321\"));\n        assertEquals(8, Main.getJavaFeatureVersion(\"1.8.0_321\"));\n        assertEquals(11, Main.getJavaFeatureVersion(\"11\"));\n        assertEquals(11, Main.getJavaFeatureVersion(\"11.0.26\"));\n        assertEquals(21, Main.getJavaFeatureVersion(\"21\"));\n        assertEquals(26, Main.getJavaFeatureVersion(\"26-ea\"));\n\n        assertEquals(-1, Main.getJavaFeatureVersion(null));\n        assertEquals(-1, Main.getJavaFeatureVersion(\"\"));\n        assertEquals(-1, Main.getJavaFeatureVersion(\"0\"));\n        assertEquals(-1, Main.getJavaFeatureVersion(\"0.8\"));\n        assertEquals(-1, Main.getJavaFeatureVersion(\"abc\"));\n        assertEquals(-1, Main.getJavaFeatureVersion(\"1.abc\"));\n        assertEquals(-1, Main.getJavaFeatureVersion(\".1\"));\n        assertEquals(-1, Main.getJavaFeatureVersion(\"1111111111111111111111\"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/build.gradle.kts",
    "content": "plugins {\n    `java-library`\n}\n\ntasks.withType<JavaCompile> {\n    sourceCompatibility = \"17\"\n    targetCompatibility = \"17\"\n}\n\ntasks.compileJava {\n    options.compilerArgs.add(\"--add-exports=jdk.attach/sun.tools.attach=ALL-UNNAMED\")\n}\n\ndependencies {\n    api(libs.kala.compress.zip)\n    api(libs.kala.compress.tar)\n    api(libs.simple.png.javafx)\n    api(libs.gson)\n    api(libs.toml)\n    api(libs.xz)\n    api(libs.fx.gson)\n    api(libs.constant.pool.scanner)\n    api(libs.opennbt)\n    api(libs.nanohttpd)\n    api(libs.jsoup)\n    api(libs.chardet)\n    api(libs.jna)\n    api(libs.pci.ids)\n\n    compileOnlyApi(libs.jetbrains.annotations)\n\n    testImplementation(libs.jna.platform)\n    testImplementation(libs.jimfs)\n}\n\ntasks.processResources {\n    listOf(\n        \"HMCLTransformerDiscoveryService\",\n        \"HMCLMultiMCBootstrap\"\n    ).map { project(\":$it\").tasks[\"jar\"] as Jar }.forEach { task ->\n        dependsOn(task)\n\n        into(\"assets/game\") {\n            from(task.outputs.files)\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.ObjectBinding;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport org.jackhuang.hmcl.auth.yggdrasil.Texture;\nimport org.jackhuang.hmcl.auth.yggdrasil.TextureType;\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jackhuang.hmcl.util.javafx.ObservableHelper;\n\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.UUID;\n\n/**\n *\n * @author huangyuhui\n */\npublic abstract class Account implements Observable {\n\n    /**\n     * @return the name of the account who owns the character\n     */\n    public abstract String getUsername();\n\n    /**\n     * @return the character name\n     */\n    public abstract String getCharacter();\n\n    /**\n     * @return the character UUID\n     */\n    public abstract UUID getUUID();\n\n    /**\n     * Login with stored credentials.\n     *\n     * @throws CredentialExpiredException when the stored credentials has expired, in which case a password login will be performed\n     */\n    public abstract AuthInfo logIn() throws AuthenticationException;\n\n    /**\n     * Play offline.\n     * @return the specific offline player's info.\n     */\n    public abstract AuthInfo playOffline() throws AuthenticationException;\n\n    public boolean canUploadSkin() {\n        return false;\n    }\n\n    public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {\n        throw new UnsupportedOperationException(\"Unsupported Operation\");\n    }\n\n    public abstract Map<Object, Object> toStorage();\n\n    public void clearCache() {\n    }\n\n    private final BooleanProperty portable = new SimpleBooleanProperty(false);\n\n    public BooleanProperty portableProperty() {\n        return portable;\n    }\n\n    public boolean isPortable() {\n        return portable.get();\n    }\n\n    public void setPortable(boolean value) {\n        this.portable.set(value);\n    }\n\n    public abstract String getIdentifier();\n\n    private final ObservableHelper helper = new ObservableHelper(this);\n\n    @Override\n    public void addListener(InvalidationListener listener) {\n        helper.addListener(listener);\n    }\n\n    @Override\n    public void removeListener(InvalidationListener listener) {\n        helper.removeListener(listener);\n    }\n\n    /**\n     * Called when the account has changed.\n     * This method can be called from any thread.\n     */\n    protected void invalidate() {\n        Platform.runLater(helper::invalidate);\n    }\n\n    public ObjectBinding<Optional<Map<TextureType, Texture>>> getTextures() {\n        return Bindings.createObjectBinding(Optional::empty);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(portable);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj)\n            return true;\n        if (!(obj instanceof Account))\n            return false;\n\n        Account another = (Account) obj;\n        return isPortable() == another.isPortable();\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"username\", getUsername())\n                .append(\"character\", getCharacter())\n                .append(\"uuid\", getUUID())\n                .append(\"portable\", isPortable())\n                .toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\npublic abstract class AccountFactory<T extends Account> {\n\n    public enum AccountLoginType {\n        /**\n         * Either username or password should not be provided.\n         * AccountFactory will take its own way to check credentials.\n         */\n        NONE(false, false),\n\n        /**\n         * AccountFactory only needs username.\n         */\n        USERNAME(true, false),\n\n        /**\n         * AccountFactory needs both username and password for credential verification.\n         */\n        USERNAME_PASSWORD(true, true);\n\n        public final boolean requiresUsername, requiresPassword;\n\n        AccountLoginType(boolean requiresUsername, boolean requiresPassword) {\n            this.requiresUsername = requiresUsername;\n            this.requiresPassword = requiresPassword;\n        }\n    }\n\n    public interface ProgressCallback {\n        void onProgressChanged(String stageName);\n    }\n\n    /**\n     * Informs how this account factory verifies user's credential.\n     *\n     * @see AccountLoginType\n     */\n    public abstract AccountLoginType getLoginType();\n\n    /**\n     * Create a new(to be verified via network) account, and log in.\n     *\n     * @param selector         for character selection if multiple characters belong to single account. Pick out which character to act as.\n     * @param username         username of the account if needed.\n     * @param password         password of the account if needed.\n     * @param progressCallback notify login progress.\n     * @param additionalData   extra data for specific account factory.\n     * @return logged-in account.\n     * @throws AuthenticationException if an error occurs when logging in.\n     */\n    public abstract T create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException;\n\n    /**\n     * Create a existing(stored in local files) account.\n     *\n     * @param storage serialized account data.\n     * @return account stored in local storage. Credentials may expired, and you should refresh account state later.\n     */\n    public abstract T fromStorage(Map<Object, Object> storage);\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\nimport org.jackhuang.hmcl.game.Arguments;\nimport org.jackhuang.hmcl.game.LaunchOptions;\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.io.IOException;\nimport java.util.UUID;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic class AuthInfo implements AutoCloseable {\n    public static final String USER_TYPE_MSA = \"msa\";\n    public static final String USER_TYPE_MOJANG = \"mojang\";\n    public static final String USER_TYPE_LEGACY = \"legacy\";\n\n\n    private final String username;\n    private final UUID uuid;\n    private final String accessToken;\n    private final String userType;\n    private final String userProperties;\n\n    public AuthInfo(String username, UUID uuid, String accessToken, String userType, String userProperties) {\n        this.username = username;\n        this.uuid = uuid;\n        this.accessToken = accessToken;\n        this.userType = userType;\n        this.userProperties = userProperties;\n    }\n\n    public String getUsername() {\n        return username;\n    }\n\n    public UUID getUUID() {\n        return uuid;\n    }\n\n    public String getAccessToken() {\n        return accessToken;\n    }\n\n    public String getUserType() {\n        return userType;\n    }\n\n    /**\n     * Properties of this user.\n     * Don't know the difference between user properties and user property map.\n     *\n     * @return the user property map in JSON.\n     */\n    public String getUserProperties() {\n        return userProperties;\n    }\n\n    /**\n     * Called when launching game.\n     * @return null if no argument is specified\n     */\n    public Arguments getLaunchArguments(LaunchOptions options) throws IOException {\n        return null;\n    }\n\n    @Override\n    public void close() throws Exception {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthenticationException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\n/**\n *\n * @author huangyuhui\n */\npublic class AuthenticationException extends Exception {\n    public AuthenticationException() {\n    }\n\n    public AuthenticationException(String message) {\n        super(message);\n    }\n\n    public AuthenticationException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public AuthenticationException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/CharacterDeletedException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\n/**\n * Thrown when a previously existing character cannot be found.\n */\npublic final class CharacterDeletedException extends AuthenticationException {\n    public CharacterDeletedException() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/CharacterSelector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\nimport org.jackhuang.hmcl.auth.yggdrasil.GameProfile;\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;\n\nimport java.util.List;\n\n/**\n * This interface is for your application to open a GUI for user to choose the character\n * when a having-multi-character yggdrasil account is being logging in.\n */\npublic interface CharacterSelector {\n\n    /**\n     * Select one of {@code names} GameProfiles to login.\n     * @param names available game profiles.\n     * @throws NoSelectedCharacterException if cannot select any character may because user close the selection window or cancel the selection.\n     * @return your choice of game profile.\n     */\n    GameProfile select(YggdrasilService yggdrasilService, List<GameProfile> names) throws NoSelectedCharacterException;\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/ClassicAccount.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\npublic abstract class ClassicAccount extends Account {\n\n    /**\n     * Login with specified password.\n     *\n     * When credentials expired, the auth server will ask you to login with password to refresh\n     * credentials.\n     */\n    public abstract AuthInfo logInWithPassword(String password) throws AuthenticationException;\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/CredentialExpiredException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\n/**\n * Thrown when the stored credentials has expired.\n * This exception indicates that a password login should be performed.\n *\n * @author yushijinhun\n * @see Account#logIn()\n */\npublic class CredentialExpiredException extends AuthenticationException {\n\n    public CredentialExpiredException() {}\n\n    public CredentialExpiredException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public CredentialExpiredException(String message) {\n        super(message);\n    }\n\n    public CredentialExpiredException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/NoCharacterException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\n/**\n * This exception gets threw when authenticating a yggdrasil account and there is no valid character.\n * (A account may hold more than one characters.)\n */\npublic final class NoCharacterException extends AuthenticationException {\n    public NoCharacterException() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/NoSelectedCharacterException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\n/**\n * This exception gets threw when a monitor of {@link CharacterSelector} cannot select a\n * valid character.\n *\n * @see CharacterSelector\n * @author huangyuhui\n */\npublic final class NoSelectedCharacterException extends AuthenticationException {\n    public NoSelectedCharacterException() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/NotLoggedInException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\npublic class NotLoggedInException extends AuthenticationException {\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;\nimport org.jackhuang.hmcl.util.io.HttpRequest;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.util.Base64;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class OAuth {\n    public static final OAuth MICROSOFT = new OAuth(\n            \"https://login.live.com/oauth20_authorize.srf\",\n            \"https://login.live.com/oauth20_token.srf\",\n            \"https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode\",\n            \"https://login.microsoftonline.com/consumers/oauth2/v2.0/token\");\n\n    private final String authorizationURL;\n    private final String accessTokenURL;\n    private final String deviceCodeURL;\n    private final String tokenURL;\n\n    public OAuth(String authorizationURL, String accessTokenURL, String deviceCodeURL, String tokenURL) {\n        this.authorizationURL = authorizationURL;\n        this.accessTokenURL = accessTokenURL;\n        this.deviceCodeURL = deviceCodeURL;\n        this.tokenURL = tokenURL;\n    }\n\n    public Result authenticate(GrantFlow grantFlow, Options options) throws AuthenticationException {\n        try {\n            switch (grantFlow) {\n                case AUTHORIZATION_CODE:\n                    return authenticateAuthorizationCode(options);\n                case DEVICE:\n                    return authenticateDevice(options);\n                default:\n                    throw new UnsupportedOperationException(\"grant flow \" + grantFlow);\n            }\n        } catch (IOException e) {\n            throw new ServerDisconnectException(e);\n        } catch (InterruptedException e) {\n            throw new NoSelectedCharacterException();\n        } catch (ExecutionException e) {\n            if (e.getCause() instanceof InterruptedException) {\n                throw new NoSelectedCharacterException();\n            } else {\n                throw new ServerDisconnectException(e);\n            }\n        } catch (JsonParseException e) {\n            throw new ServerResponseMalformedException(e);\n        }\n    }\n\n    private Result authenticateAuthorizationCode(Options options) throws IOException, InterruptedException, JsonParseException, ExecutionException, AuthenticationException {\n        Session session = options.callback.startServer();\n\n        String codeVerifier = session.getCodeVerifier();\n        String state = session.getState();\n        String codeChallenge = generateCodeChallenge(codeVerifier);\n\n        options.callback.openBrowser(GrantFlow.AUTHORIZATION_CODE, NetworkUtils.withQuery(authorizationURL,\n                mapOf(pair(\"client_id\", options.callback.getClientId()),\n                        pair(\"response_type\", \"code\"),\n                        pair(\"redirect_uri\", session.getRedirectURI()),\n                        pair(\"scope\", options.scope),\n                        pair(\"prompt\", \"select_account\"),\n                        pair(\"code_challenge\", codeChallenge),\n                        pair(\"state\", state),\n                        pair(\"code_challenge_method\", \"S256\")\n                )));\n        String code = session.waitFor();\n\n        // Authorization Code -> Token\n        AuthorizationResponse response = HttpRequest.POST(accessTokenURL)\n                .form(pair(\"client_id\", options.callback.getClientId()),\n                        pair(\"code\", code),\n                        pair(\"grant_type\", \"authorization_code\"),\n                        pair(\"code_verifier\", codeVerifier),\n                        pair(\"redirect_uri\", session.getRedirectURI()),\n                        pair(\"scope\", options.scope))\n                .ignoreHttpCode()\n                .retry(5)\n                .getJson(AuthorizationResponse.class);\n        handleErrorResponse(response);\n        return new Result(response.accessToken, response.refreshToken);\n    }\n\n    private Result authenticateDevice(Options options) throws IOException, InterruptedException, JsonParseException, AuthenticationException {\n        DeviceTokenResponse deviceTokenResponse = HttpRequest.POST(deviceCodeURL)\n                .form(pair(\"client_id\", options.callback.getClientId()), pair(\"scope\", options.scope))\n                .ignoreHttpCode()\n                .retry(5)\n                .getJson(DeviceTokenResponse.class);\n        handleErrorResponse(deviceTokenResponse);\n\n        options.callback.grantDeviceCode(deviceTokenResponse.userCode, deviceTokenResponse.verificationURI);\n\n        // Microsoft OAuth Flow\n        options.callback.openBrowser(GrantFlow.DEVICE, deviceTokenResponse.verificationURI);\n\n        long startTime = System.nanoTime();\n        long interval = TimeUnit.MILLISECONDS.convert(deviceTokenResponse.interval, TimeUnit.SECONDS);\n\n        while (true) {\n            Thread.sleep(Math.max(interval, 1));\n\n            // We stop waiting if user does not respond our authentication request in 15 minutes.\n            long estimatedTime = System.nanoTime() - startTime;\n            if (TimeUnit.SECONDS.convert(estimatedTime, TimeUnit.NANOSECONDS) >= Math.min(deviceTokenResponse.expiresIn, 900)) {\n                throw new NoSelectedCharacterException();\n            }\n\n            TokenResponse tokenResponse = HttpRequest.POST(tokenURL)\n                    .form(\n                            pair(\"grant_type\", \"urn:ietf:params:oauth:grant-type:device_code\"),\n                            pair(\"code\", deviceTokenResponse.deviceCode),\n                            pair(\"client_id\", options.callback.getClientId()))\n                    .ignoreHttpCode()\n                    .retry(5)\n                    .getJson(TokenResponse.class);\n\n            if (\"authorization_pending\".equals(tokenResponse.error)) {\n                continue;\n            }\n\n            if (\"expired_token\".equals(tokenResponse.error)) {\n                throw new NoSelectedCharacterException();\n            }\n\n            if (\"slow_down\".equals(tokenResponse.error)) {\n                interval += 5000;\n                continue;\n            }\n\n            return new Result(tokenResponse.accessToken, tokenResponse.refreshToken);\n        }\n    }\n\n    public Result refresh(String refreshToken, Options options) throws AuthenticationException {\n        try {\n            Map<String, String> query = mapOf(pair(\"client_id\", options.callback.getClientId()),\n                    pair(\"refresh_token\", refreshToken),\n                    pair(\"grant_type\", \"refresh_token\")\n            );\n\n            RefreshResponse response = HttpRequest.POST(tokenURL)\n                    .form(query)\n                    .accept(\"application/json\")\n                    .ignoreHttpCode()\n                    .retry(5)\n                    .getJson(RefreshResponse.class);\n\n            handleErrorResponse(response);\n\n            return new Result(response.accessToken, response.refreshToken);\n        } catch (IOException e) {\n            throw new ServerDisconnectException(e);\n        } catch (JsonParseException e) {\n            throw new ServerResponseMalformedException(e);\n        }\n    }\n\n    private static String generateCodeChallenge(String codeVerifier) {\n        // https://datatracker.ietf.org/doc/html/rfc7636#section-4.2\n        try {\n            byte[] bytes = codeVerifier.getBytes(StandardCharsets.US_ASCII);\n            MessageDigest messageDigest = MessageDigest.getInstance(\"SHA-256\");\n            messageDigest.update(bytes, 0, bytes.length);\n            byte[] digest = messageDigest.digest();\n            return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);\n        } catch (Exception e) {\n            LOG.warning(\"Failed to generate code challenge\", e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    private static void handleErrorResponse(ErrorResponse response) throws AuthenticationException {\n        if (response.error == null || response.errorDescription == null) {\n            return;\n        }\n\n        switch (response.error) {\n            case \"invalid_grant\":\n                if (response.errorDescription.contains(\"AADSTS70000\")) {\n                    throw new CredentialExpiredException();\n                }\n                break;\n        }\n\n        throw new RemoteAuthenticationException(response.error, response.errorDescription, \"\");\n    }\n\n    public static class Options {\n        private String userAgent;\n        private final String scope;\n        private final Callback callback;\n\n        public Options(String scope, Callback callback) {\n            this.scope = scope;\n            this.callback = callback;\n        }\n\n        public Options setUserAgent(String userAgent) {\n            this.userAgent = userAgent;\n            return this;\n        }\n    }\n\n    public interface Session {\n        String getState();\n\n        String getCodeVerifier();\n\n        String getRedirectURI();\n\n        /**\n         * Wait for authentication\n         *\n         * @return authentication code\n         * @throws InterruptedException if interrupted\n         * @throws ExecutionException   if an I/O error occurred.\n         */\n        String waitFor() throws InterruptedException, ExecutionException;\n\n        default String getIdToken() {\n            return null;\n        }\n    }\n\n    public interface Callback {\n        /**\n         * Start OAuth callback server at localhost.\n         *\n         * @throws IOException if an I/O error occurred.\n         */\n        Session startServer() throws IOException, AuthenticationException;\n\n        void grantDeviceCode(String userCode, String verificationURI);\n\n        /**\n         * Open browser\n         *\n         * @param grantFlow the grant flow.\n         * @param url       OAuth url.\n         */\n        void openBrowser(GrantFlow grantFlow, String url) throws IOException;\n\n        String getClientId();\n    }\n\n    public enum GrantFlow {\n        AUTHORIZATION_CODE,\n        DEVICE,\n    }\n\n    public static final class Result {\n        private final String accessToken;\n        private final String refreshToken;\n\n        public Result(String accessToken, String refreshToken) {\n            this.accessToken = accessToken;\n            this.refreshToken = refreshToken;\n        }\n\n        public String getAccessToken() {\n            return accessToken;\n        }\n\n        public String getRefreshToken() {\n            return refreshToken;\n        }\n    }\n\n    private final static class DeviceTokenResponse extends ErrorResponse {\n        @SerializedName(\"user_code\")\n        public String userCode;\n\n        @SerializedName(\"device_code\")\n        public String deviceCode;\n\n        // The URI to be visited for user.\n        @SerializedName(\"verification_uri\")\n        public String verificationURI;\n\n        // Lifetime in seconds for device_code and user_code\n        @SerializedName(\"expires_in\")\n        public int expiresIn;\n\n        // Polling interval\n        @SerializedName(\"interval\")\n        public int interval;\n    }\n\n    private final static class TokenResponse extends ErrorResponse {\n        @SerializedName(\"token_type\")\n        public String tokenType;\n\n        @SerializedName(\"expires_in\")\n        public int expiresIn;\n\n        @SerializedName(\"ext_expires_in\")\n        public int extExpiresIn;\n\n        @SerializedName(\"scope\")\n        public String scope;\n\n        @SerializedName(\"access_token\")\n        public String accessToken;\n\n        @SerializedName(\"refresh_token\")\n        public String refreshToken;\n\n    }\n\n    private static class ErrorResponse {\n        @SerializedName(\"error\")\n        public String error;\n\n        @SerializedName(\"error_description\")\n        public String errorDescription;\n\n        @SerializedName(\"correlation_id\")\n        public String correlationId;\n    }\n\n    /**\n     * Error response: {\"error\":\"invalid_grant\",\"error_description\":\"The provided\n     * value for the 'redirect_uri' is not valid. The value must exactly match the\n     * redirect URI used to obtain the authorization\n     * code.\",\"correlation_id\":\"??????\"}\n     */\n    public final static class AuthorizationResponse extends ErrorResponse {\n        @SerializedName(\"token_type\")\n        public String tokenType;\n\n        @SerializedName(\"expires_in\")\n        public int expiresIn;\n\n        @SerializedName(\"scope\")\n        public String scope;\n\n        @SerializedName(\"access_token\")\n        public String accessToken;\n\n        @SerializedName(\"refresh_token\")\n        public String refreshToken;\n\n        @SerializedName(\"user_id\")\n        public String userId;\n\n        @SerializedName(\"foci\")\n        public String foci;\n    }\n\n    private final static class RefreshResponse extends ErrorResponse {\n        @SerializedName(\"expires_in\")\n        int expiresIn;\n\n        @SerializedName(\"access_token\")\n        String accessToken;\n\n        @SerializedName(\"refresh_token\")\n        String refreshToken;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuthAccount.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\nimport java.util.UUID;\n\npublic abstract class OAuthAccount extends Account {\n\n    /**\n     * Fully login.\n     *\n     * OAuth server may ask us to do fully login because too frequent action to log in, IP changed,\n     * or some other vulnerabilities detected.\n     *\n     * Difference between logIn & logInWhenCredentialsExpired.\n     * logIn only update access token by refresh token, and will not ask user to login by opening the authorization\n     * page in web browser.\n     * logInWhenCredentialsExpired will open the authorization page in web browser, asking user to select an account\n     * (and enter password or PIN if necessary).\n     */\n    public abstract AuthInfo logInWhenCredentialsExpired() throws AuthenticationException;\n\n    public static class WrongAccountException extends AuthenticationException {\n        private final UUID expected;\n        private final UUID actual;\n\n        public WrongAccountException(UUID expected, UUID actual) {\n            super(\"Expected account \" + expected + \", but found \" + actual);\n            this.expected = expected;\n            this.actual = actual;\n        }\n\n        public UUID getExpected() {\n            return expected;\n        }\n\n        public UUID getActual() {\n            return actual;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/ServerDisconnectException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\npublic class ServerDisconnectException extends AuthenticationException {\n    public ServerDisconnectException() {\n    }\n\n    public ServerDisconnectException(String message) {\n        super(message);\n    }\n\n    public ServerDisconnectException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public ServerDisconnectException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/ServerResponseMalformedException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth;\n\npublic class ServerResponseMalformedException extends AuthenticationException {\n    public ServerResponseMalformedException() {\n    }\n\n    public ServerResponseMalformedException(String message) {\n        super(message);\n    }\n\n    public ServerResponseMalformedException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public ServerResponseMalformedException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccount.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport org.jackhuang.hmcl.auth.*;\nimport org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;\nimport org.jackhuang.hmcl.auth.yggdrasil.TextureType;\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;\nimport org.jackhuang.hmcl.game.Arguments;\nimport org.jackhuang.hmcl.game.LaunchOptions;\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jackhuang.hmcl.util.function.ExceptionalSupplier;\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.ExecutionException;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static java.util.Collections.emptySet;\nimport static java.util.Collections.unmodifiableSet;\n\npublic class AuthlibInjectorAccount extends YggdrasilAccount {\n    private final AuthlibInjectorServer server;\n    private AuthlibInjectorArtifactProvider downloader;\n\n    public AuthlibInjectorAccount(AuthlibInjectorServer server, AuthlibInjectorArtifactProvider downloader, String username, String password, CharacterSelector selector) throws AuthenticationException {\n        super(server.getYggdrasilService(), username, password, selector);\n        this.server = server;\n        this.downloader = downloader;\n    }\n\n    public AuthlibInjectorAccount(AuthlibInjectorServer server, AuthlibInjectorArtifactProvider downloader, String username, YggdrasilSession session) {\n        super(server.getYggdrasilService(), username, session);\n        this.server = server;\n        this.downloader = downloader;\n    }\n\n    @Override\n    public synchronized AuthInfo logIn() throws AuthenticationException {\n        return inject(super::logIn);\n    }\n\n    @Override\n    public synchronized AuthInfo logInWithPassword(String password) throws AuthenticationException {\n        return inject(() -> super.logInWithPassword(password));\n    }\n\n    @Override\n    public AuthInfo playOffline() throws AuthenticationException {\n        AuthInfo auth = super.playOffline();\n        Optional<AuthlibInjectorArtifactInfo> artifact = downloader.getArtifactInfoImmediately();\n        Optional<String> prefetchedMeta = server.getMetadataResponse();\n\n        if (artifact.isPresent() && prefetchedMeta.isPresent()) {\n            return new AuthlibInjectorAuthInfo(auth, artifact.get(), server, prefetchedMeta.get());\n        } else {\n            throw new NotLoggedInException();\n        }\n    }\n\n    private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> loginAction) throws AuthenticationException {\n        CompletableFuture<String> prefetchedMetaTask = CompletableFuture.supplyAsync(() -> {\n            try {\n                return server.fetchMetadataResponse();\n            } catch (IOException e) {\n                throw new CompletionException(new ServerDisconnectException(e));\n            }\n        });\n\n        CompletableFuture<AuthlibInjectorArtifactInfo> artifactTask = CompletableFuture.supplyAsync(() -> {\n            try {\n                return downloader.getArtifactInfo();\n            } catch (IOException e) {\n                throw new CompletionException(new AuthlibInjectorDownloadException(e));\n            }\n        });\n\n        AuthInfo auth = loginAction.get();\n        String prefetchedMeta;\n        AuthlibInjectorArtifactInfo artifact;\n\n        try {\n            prefetchedMeta = prefetchedMetaTask.get();\n            artifact = artifactTask.get();\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new AuthenticationException(e);\n        } catch (ExecutionException e) {\n            if (e.getCause() instanceof AuthenticationException) {\n                throw (AuthenticationException) e.getCause();\n            } else {\n                throw new AuthenticationException(e.getCause());\n            }\n        }\n\n        return new AuthlibInjectorAuthInfo(auth, artifact, server, prefetchedMeta);\n    }\n\n    private static class AuthlibInjectorAuthInfo extends AuthInfo {\n\n        private final AuthlibInjectorArtifactInfo artifact;\n        private final AuthlibInjectorServer server;\n        private final String prefetchedMeta;\n\n        public AuthlibInjectorAuthInfo(AuthInfo authInfo, AuthlibInjectorArtifactInfo artifact, AuthlibInjectorServer server, String prefetchedMeta) {\n            super(authInfo.getUsername(), authInfo.getUUID(), authInfo.getAccessToken(), authInfo.getUserType(), authInfo.getUserProperties());\n\n            this.artifact = artifact;\n            this.server = server;\n            this.prefetchedMeta = prefetchedMeta;\n        }\n\n        @Override\n        public Arguments getLaunchArguments(LaunchOptions options) {\n            return new Arguments().addJVMArguments(\n                    \"-javaagent:\" + artifact.getLocation().toString() + \"=\" + server.getUrl(),\n                    \"-Dauthlibinjector.side=client\",\n                    \"-Dauthlibinjector.yggdrasil.prefetched=\" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8)));\n        }\n    }\n\n    @Override\n    public Map<Object, Object> toStorage() {\n        Map<Object, Object> map = super.toStorage();\n        map.put(\"serverBaseURL\", server.getUrl());\n        return map;\n    }\n\n    @Override\n    public void clearCache() {\n        super.clearCache();\n        server.invalidateMetadataCache();\n    }\n\n    public AuthlibInjectorServer getServer() {\n        return server;\n    }\n\n    @Override\n    public String getIdentifier() {\n        return server.getUrl() + \":\" + super.getIdentifier();\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(super.hashCode(), server.hashCode());\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == null || obj.getClass() != AuthlibInjectorAccount.class)\n            return false;\n        AuthlibInjectorAccount another = (AuthlibInjectorAccount) obj;\n        return isPortable() == another.isPortable()\n                && characterUUID.equals(another.characterUUID) && server.equals(another.server);\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"uuid\", characterUUID)\n                .append(\"username\", getUsername())\n                .append(\"server\", getServer().getUrl())\n                .toString();\n    }\n\n    public static Set<TextureType> getUploadableTextures(CompleteGameProfile profile) {\n        String prop = profile.getProperties().get(\"uploadableTextures\");\n        if (prop == null)\n            return emptySet();\n        Set<TextureType> result = EnumSet.noneOf(TextureType.class);\n        for (String val : prop.split(\",\")) {\n            val = val.toUpperCase(Locale.ROOT);\n            TextureType parsed;\n            try {\n                parsed = TextureType.valueOf(val);\n            } catch (IllegalArgumentException e) {\n                continue;\n            }\n            result.add(parsed);\n        }\n        return unmodifiableSet(result);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport org.jackhuang.hmcl.auth.AccountFactory;\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.CharacterSelector;\nimport org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;\nimport org.jackhuang.hmcl.auth.yggdrasil.GameProfile;\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;\nimport org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.UUID;\nimport java.util.function.Function;\n\nimport static org.jackhuang.hmcl.util.Lang.tryCast;\n\npublic class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjectorAccount> {\n    private final AuthlibInjectorArtifactProvider downloader;\n    private final Function<String, AuthlibInjectorServer> serverLookup;\n\n    /**\n     * @param serverLookup a function that looks up {@link AuthlibInjectorServer} by url\n     */\n    public AuthlibInjectorAccountFactory(AuthlibInjectorArtifactProvider downloader, Function<String, AuthlibInjectorServer> serverLookup) {\n        this.downloader = downloader;\n        this.serverLookup = serverLookup;\n    }\n\n    @Override\n    public AccountLoginType getLoginType() {\n        return AccountLoginType.USERNAME_PASSWORD;\n    }\n\n    @Override\n    public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException {\n        Objects.requireNonNull(selector);\n        Objects.requireNonNull(username);\n        Objects.requireNonNull(password);\n\n        AuthlibInjectorServer server = (AuthlibInjectorServer) additionalData;\n\n        return new AuthlibInjectorAccount(server, downloader, username, password, selector);\n    }\n\n    @Override\n    public AuthlibInjectorAccount fromStorage(Map<Object, Object> storage) {\n        Objects.requireNonNull(storage);\n\n        String apiRoot = tryCast(storage.get(\"serverBaseURL\"), String.class)\n                .orElseThrow(() -> new IllegalArgumentException(\"storage does not have API root.\"));\n        AuthlibInjectorServer server = serverLookup.apply(apiRoot);\n        return fromStorage(storage, downloader, server);\n    }\n\n    static AuthlibInjectorAccount fromStorage(Map<Object, Object> storage, AuthlibInjectorArtifactProvider downloader, AuthlibInjectorServer server) {\n        YggdrasilSession session = YggdrasilSession.fromStorage(storage);\n\n        String username = tryCast(storage.get(\"username\"), String.class)\n                .orElseThrow(() -> new IllegalArgumentException(\"storage does not have username\"));\n\n        tryCast(storage.get(\"profileProperties\"), Map.class).ifPresent(\n                it -> {\n                    @SuppressWarnings(\"unchecked\")\n                    Map<String, String> properties = it;\n                    GameProfile selected = session.getSelectedProfile();\n                    ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository = server.getYggdrasilService().getProfileRepository();\n                    profileRepository.put(selected.getId(), new CompleteGameProfile(selected, properties));\n                    profileRepository.invalidate(selected.getId());\n                });\n\n        return new AuthlibInjectorAccount(server, downloader, username, session);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorArtifactInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\n\npublic class AuthlibInjectorArtifactInfo {\n\n    public static AuthlibInjectorArtifactInfo from(Path location) throws IOException {\n        try (JarFile jarFile = new JarFile(location.toFile())) {\n            Attributes attributes = jarFile.getManifest().getMainAttributes();\n\n            String title = Optional.ofNullable(attributes.getValue(\"Implementation-Title\"))\n                    .orElseThrow(() -> new IOException(\"Missing Implementation-Title\"));\n            if (!\"authlib-injector\".equals(title)) {\n                throw new IOException(\"Bad Implementation-Title\");\n            }\n\n            String version = Optional.ofNullable(attributes.getValue(\"Implementation-Version\"))\n                    .orElseThrow(() -> new IOException(\"Missing Implementation-Version\"));\n\n            int buildNumber;\n            try {\n                buildNumber = Optional.ofNullable(attributes.getValue(\"Build-Number\"))\n                        .map(Integer::parseInt)\n                        .orElseThrow(() -> new IOException(\"Missing Build-Number\"));\n            } catch (NumberFormatException e) {\n                throw new IOException(\"Bad Build-Number\", e);\n            }\n            return new AuthlibInjectorArtifactInfo(buildNumber, version, location.toAbsolutePath());\n        }\n    }\n\n    private int buildNumber;\n    private String version;\n    private Path location;\n\n    public AuthlibInjectorArtifactInfo(int buildNumber, String version, Path location) {\n        this.buildNumber = buildNumber;\n        this.version = version;\n        this.location = location;\n    }\n\n    public int getBuildNumber() {\n        return buildNumber;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public Path getLocation() {\n        return location;\n    }\n\n    @Override\n    public String toString() {\n        return \"authlib-injector [buildNumber=\" + buildNumber + \", version=\" + version + \"]\";\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorArtifactProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport java.io.IOException;\nimport java.util.Optional;\n\npublic interface AuthlibInjectorArtifactProvider {\n\n    AuthlibInjectorArtifactInfo getArtifactInfo() throws IOException;\n\n    Optional<AuthlibInjectorArtifactInfo> getArtifactInfoImmediately();\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorDnD.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport javafx.event.EventHandler;\nimport javafx.scene.input.DragEvent;\nimport javafx.scene.input.Dragboard;\nimport javafx.scene.input.TransferMode;\n\nimport java.util.Optional;\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.util.io.NetworkUtils.decodeURL;\n\n/**\n * @author yushijinhun\n * @see <a href=\"https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#dnd-%E6%96%B9%E5%BC%8F%E6%B7%BB%E5%8A%A0-yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF\">Launcher Technical Specification for Authlib-Injector</a>\n */\npublic final class AuthlibInjectorDnD {\n\n    private static final String SCHEME = \"authlib-injector\";\n    private static final String PATH_YGGDRASIL_SERVER = \"yggdrasil-server\";\n\n    private AuthlibInjectorDnD() {}\n\n    public static Optional<String> parseUrlFromDragboard(Dragboard dragboard) {\n        String uri = dragboard.getString();\n        if (uri == null) return Optional.empty();\n\n        String[] uriElements = uri.split(\":\");\n        if (uriElements.length == 3 && SCHEME.equals(uriElements[0]) && PATH_YGGDRASIL_SERVER.equals(uriElements[1])) {\n            return Optional.of(decodeURL(uriElements[2]));\n        }\n        return Optional.empty();\n    }\n\n    public static EventHandler<DragEvent> dragOverHandler() {\n        return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {\n            event.acceptTransferModes(TransferMode.COPY);\n            event.consume();\n        });\n    }\n\n    public static EventHandler<DragEvent> dragDroppedHandler(Consumer<String> onUrlTransfered) {\n        return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {\n            event.setDropCompleted(true);\n            event.consume();\n            onUrlTransfered.accept(url);\n        });\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorDownloadException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport org.jackhuang.hmcl.auth.AuthenticationException;\n\n/**\n * @author yushijinhun\n */\npublic class AuthlibInjectorDownloadException extends AuthenticationException {\n\n    public AuthlibInjectorDownloadException() {\n    }\n\n    public AuthlibInjectorDownloadException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public AuthlibInjectorDownloadException(String message) {\n        super(message);\n    }\n\n    public AuthlibInjectorDownloadException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorDownloader.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;\nimport org.jackhuang.hmcl.util.io.HttpRequest;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Supplier;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvider {\n\n    private static final String LATEST_BUILD_URL = \"https://authlib-injector.yushi.moe/artifact/latest.json\";\n\n    private final Path artifactLocation;\n    private final Supplier<DownloadProvider> downloadProvider;\n\n    /**\n     * @param artifactLocation where to save authlib-injector artifacts\n     */\n    public AuthlibInjectorDownloader(Path artifactLocation, Supplier<DownloadProvider> downloadProvider) {\n        this.artifactLocation = artifactLocation;\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public AuthlibInjectorArtifactInfo getArtifactInfo() throws IOException {\n        Optional<AuthlibInjectorArtifactInfo> cached = getArtifactInfoImmediately();\n        if (cached.isPresent()) {\n            return cached.get();\n        }\n\n        synchronized (this) {\n            Optional<AuthlibInjectorArtifactInfo> local = getLocalArtifact();\n            if (local.isPresent()) {\n                return local.get();\n            }\n            LOG.info(\"No local authlib-injector found, downloading\");\n            updateChecked.set(true);\n            update();\n            local = getLocalArtifact();\n            return local.orElseThrow(() -> new IOException(\"The downloaded authlib-inejector cannot be recognized\"));\n        }\n    }\n\n    @Override\n    public Optional<AuthlibInjectorArtifactInfo> getArtifactInfoImmediately() {\n        return getLocalArtifact();\n    }\n\n    private final AtomicBoolean updateChecked = new AtomicBoolean(false);\n\n    public void checkUpdate() throws IOException {\n        // this method runs only once\n        if (updateChecked.compareAndSet(false, true)) {\n            synchronized (this) {\n                LOG.info(\"Checking update of authlib-injector\");\n                update();\n            }\n        }\n    }\n\n    private void update() throws IOException {\n        AuthlibInjectorVersionInfo latest = getLatestArtifactInfo();\n\n        Optional<AuthlibInjectorArtifactInfo> local = getLocalArtifact();\n        if (local.isPresent() && local.get().getBuildNumber() >= latest.buildNumber) {\n            return;\n        }\n\n        try {\n            new FileDownloadTask(downloadProvider.get().injectURLWithCandidates(latest.downloadUrl), artifactLocation,\n                    Optional.ofNullable(latest.checksums.get(\"sha256\"))\n                            .map(checksum -> new IntegrityCheck(\"SHA-256\", checksum))\n                            .orElse(null))\n                    .run();\n        } catch (Exception e) {\n            throw new IOException(\"Failed to download authlib-injector\", e);\n        }\n\n        LOG.info(\"Updated authlib-injector to \" + latest.version);\n    }\n\n    private AuthlibInjectorVersionInfo getLatestArtifactInfo() throws IOException {\n        IOException exception = null;\n        for (URI url : downloadProvider.get().injectURLWithCandidates(LATEST_BUILD_URL)) {\n            try {\n                return HttpRequest.GET(url.toString()).getJson(AuthlibInjectorVersionInfo.class);\n            } catch (IOException | JsonParseException e) {\n                if (exception == null) {\n                    exception = new IOException(\"Failed to fetch authlib-injector artifact info\");\n                }\n                exception.addSuppressed(e);\n            }\n        }\n\n        if (exception == null) {\n            exception = new IOException(\"No authlib-injector download providers available\");\n        }\n        throw exception;\n    }\n\n    private Optional<AuthlibInjectorArtifactInfo> getLocalArtifact() {\n        return parseArtifact(artifactLocation);\n    }\n\n    protected static Optional<AuthlibInjectorArtifactInfo> parseArtifact(Path path) {\n        if (!Files.isRegularFile(path)) {\n            return Optional.empty();\n        }\n        try {\n            return Optional.of(AuthlibInjectorArtifactInfo.from(path));\n        } catch (IOException e) {\n            LOG.warning(\"Bad authlib-injector artifact\", e);\n            return Optional.empty();\n        }\n    }\n\n    private static final class AuthlibInjectorVersionInfo {\n        @SerializedName(\"build_number\")\n        public int buildNumber;\n\n        @SerializedName(\"version\")\n        public String version;\n\n        @SerializedName(\"download_url\")\n        public String downloadUrl;\n\n        @SerializedName(\"checksums\")\n        public Map<String, String> checksums;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorExtractor.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class AuthlibInjectorExtractor implements AuthlibInjectorArtifactProvider {\n    private final URL source;\n    private final Path artifactLocation;\n\n    public AuthlibInjectorExtractor(URL source, Path artifactLocation) {\n        if (source == null)\n            throw new IllegalArgumentException(\"Missing authlib injector\");\n        this.source = source;\n        this.artifactLocation = artifactLocation;\n    }\n\n    @Override\n    public AuthlibInjectorArtifactInfo getArtifactInfo() throws IOException {\n        Optional<AuthlibInjectorArtifactInfo> cached = getArtifactInfoImmediately();\n        if (cached.isPresent())\n            return cached.get();\n\n        synchronized (this) {\n            cached = getArtifactInfoImmediately();\n            if (cached.isPresent())\n                return cached.get();\n\n            LOG.info(\"No local authlib-injector found, extracting\");\n            Files.createDirectories(artifactLocation.getParent());\n            try (InputStream inputStream = source.openStream()) {\n                FileUtils.saveSafely(artifactLocation, inputStream::transferTo);\n            }\n            return getArtifactInfoImmediately().orElseThrow(() ->\n                    new IOException(\"Failed to extract authlib-injector artifact\"));\n        }\n    }\n\n    @Override\n    public Optional<AuthlibInjectorArtifactInfo> getArtifactInfoImmediately() {\n        if (!Files.isRegularFile(artifactLocation))\n            return Optional.empty();\n\n        try {\n            return Optional.of(AuthlibInjectorArtifactInfo.from(artifactLocation));\n        } catch (IOException e) {\n            LOG.warning(\"Bad authlib-injector artifact\", e);\n            return Optional.empty();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilProvider;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\n\nimport java.net.URI;\nimport java.util.UUID;\n\npublic class AuthlibInjectorProvider implements YggdrasilProvider {\n\n    private final String apiRoot;\n\n    public AuthlibInjectorProvider(String apiRoot) {\n        this.apiRoot = apiRoot;\n    }\n\n    @Override\n    public URI getAuthenticationURL() throws AuthenticationException {\n        return URI.create(apiRoot + \"authserver/authenticate\");\n    }\n\n    @Override\n    public URI getRefreshmentURL() throws AuthenticationException {\n        return URI.create(apiRoot + \"authserver/refresh\");\n    }\n\n    @Override\n    public URI getValidationURL() throws AuthenticationException {\n        return URI.create(apiRoot + \"authserver/validate\");\n    }\n\n    @Override\n    public URI getInvalidationURL() throws AuthenticationException {\n        return URI.create(apiRoot + \"authserver/invalidate\");\n    }\n\n    @Override\n    public URI getSkinUploadURL(UUID uuid) throws UnsupportedOperationException {\n        return URI.create(apiRoot + \"api/user/profile/\" + UUIDTypeAdapter.fromUUID(uuid) + \"/skin\");\n    }\n\n    @Override\n    public URI getProfilePropertiesURL(UUID uuid) throws AuthenticationException {\n        return URI.create(apiRoot + \"sessionserver/session/minecraft/profile/\" + UUIDTypeAdapter.fromUUID(uuid));\n    }\n\n    @Override\n    public String toString() {\n        return apiRoot;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorServer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport static java.util.Collections.emptyMap;\nimport static org.jackhuang.hmcl.util.Lang.tryCast;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.net.HttpURLConnection;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;\nimport org.jackhuang.hmcl.util.io.HttpRequest;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.javafx.ObservableHelper;\nimport org.jetbrains.annotations.Nullable;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonDeserializationContext;\nimport com.google.gson.JsonDeserializer;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParseException;\nimport com.google.gson.JsonPrimitive;\nimport com.google.gson.annotations.JsonAdapter;\n\nimport javafx.application.Platform;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\n\n@JsonAdapter(AuthlibInjectorServer.Deserializer.class)\npublic class AuthlibInjectorServer implements Observable {\n\n    private static final Gson GSON = new GsonBuilder().create();\n\n    public static AuthlibInjectorServer locateServer(String url) throws IOException {\n        try {\n            url = NetworkUtils.addHttpsIfMissing(url);\n            HttpURLConnection conn = NetworkUtils.createHttpConnection(url);\n            conn = NetworkUtils.resolveConnection(conn);\n\n            String ali = conn.getHeaderField(\"x-authlib-injector-api-location\");\n            if (ali != null) {\n                URI absoluteAli = conn.getURL().toURI().resolve(NetworkUtils.toURI(ali));\n                if (!urlEqualsIgnoreSlash(url, absoluteAli.toString())) {\n                    conn.disconnect();\n                    url = absoluteAli.toString();\n                    conn = NetworkUtils.resolveConnection(NetworkUtils.createHttpConnection(absoluteAli));\n                }\n            }\n\n            if (!url.endsWith(\"/\"))\n                url += \"/\";\n\n            try {\n                AuthlibInjectorServer server = new AuthlibInjectorServer(url);\n                server.refreshMetadata(NetworkUtils.readFullyAsString(conn));\n                return server;\n            } finally {\n                conn.disconnect();\n            }\n        } catch (IllegalArgumentException | URISyntaxException e) {\n            throw new IOException(e);\n        }\n    }\n\n    private static boolean urlEqualsIgnoreSlash(String a, String b) {\n        if (!a.endsWith(\"/\"))\n            a += \"/\";\n        if (!b.endsWith(\"/\"))\n            b += \"/\";\n        return a.equals(b);\n    }\n\n    private final String url;\n    @Nullable\n    private String metadataResponse;\n    private long metadataTimestamp;\n\n    @Nullable\n    private transient String name;\n    private transient Map<String, String> links = emptyMap();\n    private transient boolean nonEmailLogin;\n\n    private transient boolean metadataRefreshed;\n    private final transient ObservableHelper helper = new ObservableHelper(this);\n    private final transient YggdrasilService yggdrasilService;\n\n    public AuthlibInjectorServer(String url) {\n        this.url = url;\n        this.yggdrasilService = new YggdrasilService(new AuthlibInjectorProvider(url));\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public YggdrasilService getYggdrasilService() {\n        return yggdrasilService;\n    }\n\n    public Optional<String> getMetadataResponse() {\n        return Optional.ofNullable(metadataResponse);\n    }\n\n    public long getMetadataTimestamp() {\n        return metadataTimestamp;\n    }\n\n    public String getName() {\n        return Optional.ofNullable(name)\n                .orElse(url);\n    }\n\n    public Map<String, String> getLinks() {\n        return links;\n    }\n\n    public boolean isNonEmailLogin() {\n        return nonEmailLogin;\n    }\n\n    public String fetchMetadataResponse() throws IOException {\n        if (metadataResponse == null || !metadataRefreshed) {\n            refreshMetadata();\n        }\n        return getMetadataResponse().get();\n    }\n\n    public void refreshMetadata() throws IOException {\n        refreshMetadata(HttpRequest.GET(url).getString());\n    }\n\n    private void refreshMetadata(String text) throws IOException {\n        long timestamp = System.currentTimeMillis();\n        try {\n            setMetadataResponse(text, timestamp);\n        } catch (JsonParseException e) {\n            throw new IOException(\"Malformed response\\n\" + text, e);\n        }\n\n        metadataRefreshed = true;\n        LOG.info(\"authlib-injector server metadata refreshed: \" + url);\n        Platform.runLater(helper::invalidate);\n    }\n\n    private void setMetadataResponse(String metadataResponse, long metadataTimestamp) throws JsonParseException {\n        JsonObject response = GSON.fromJson(metadataResponse, JsonObject.class);\n        if (response == null) {\n            throw new JsonParseException(\"Metadata response is empty\");\n        }\n\n        synchronized (this) {\n            this.metadataResponse = metadataResponse;\n            this.metadataTimestamp = metadataTimestamp;\n\n            Optional<JsonObject> metaObject = tryCast(response.get(\"meta\"), JsonObject.class);\n\n            this.name = metaObject.flatMap(meta -> tryCast(meta.get(\"serverName\"), JsonPrimitive.class).map(JsonPrimitive::getAsString))\n                    .orElse(null);\n            this.links = metaObject.flatMap(meta -> tryCast(meta.get(\"links\"), JsonObject.class))\n                    .map(linksObject -> {\n                        Map<String, String> converted = new LinkedHashMap<>();\n                        linksObject.entrySet().forEach(\n                                entry -> tryCast(entry.getValue(), JsonPrimitive.class).ifPresent(element -> {\n                                    converted.put(entry.getKey(), element.getAsString());\n                                }));\n                        return converted;\n                    })\n                    .orElse(emptyMap());\n            this.nonEmailLogin = metaObject.flatMap(meta -> tryCast(meta.get(\"feature.non_email_login\"), JsonPrimitive.class))\n                    .map(it -> it.getAsBoolean())\n                    .orElse(false);\n        }\n    }\n\n    public void invalidateMetadataCache() {\n        metadataRefreshed = false;\n    }\n\n    @Override\n    public int hashCode() {\n        return url.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this)\n            return true;\n        if (!(obj instanceof AuthlibInjectorServer))\n            return false;\n        AuthlibInjectorServer another = (AuthlibInjectorServer) obj;\n        return this.url.equals(another.url);\n    }\n\n    @Override\n    public String toString() {\n        return name == null ? url : url + \" (\" + name + \")\";\n    }\n\n    @Override\n    public void addListener(InvalidationListener listener) {\n        helper.addListener(listener);\n    }\n\n    @Override\n    public void removeListener(InvalidationListener listener) {\n        helper.removeListener(listener);\n    }\n\n    public static class Deserializer implements JsonDeserializer<AuthlibInjectorServer> {\n        @Override\n        public AuthlibInjectorServer deserialize(JsonElement json, Type type, JsonDeserializationContext ctx) throws JsonParseException {\n            JsonObject jsonObj = json.getAsJsonObject();\n            AuthlibInjectorServer instance = new AuthlibInjectorServer(jsonObj.get(\"url\").getAsString());\n\n            if (jsonObj.has(\"name\")) {\n                instance.name = jsonObj.get(\"name\").getAsString();\n            }\n\n            if (jsonObj.has(\"metadataResponse\")) {\n                try {\n                    instance.setMetadataResponse(jsonObj.get(\"metadataResponse\").getAsString(), jsonObj.get(\"metadataTimestamp\").getAsLong());\n                } catch (JsonParseException e) {\n                    LOG.warning(\"Ignoring malformed metadata response cache: \" + jsonObj.get(\"metadataResponse\"), e);\n                }\n            }\n            return instance;\n        }\n\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/BoundAuthlibInjectorAccountFactory.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport org.jackhuang.hmcl.auth.AccountFactory;\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.CharacterSelector;\n\nimport java.util.Map;\nimport java.util.Objects;\n\npublic class BoundAuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjectorAccount> {\n    private final AuthlibInjectorArtifactProvider downloader;\n    private final AuthlibInjectorServer server;\n\n    /**\n     * @param server Authlib-Injector Server\n     */\n    public BoundAuthlibInjectorAccountFactory(AuthlibInjectorArtifactProvider downloader, AuthlibInjectorServer server) {\n        this.downloader = downloader;\n        this.server = server;\n    }\n\n    @Override\n    public AccountLoginType getLoginType() {\n        return AccountLoginType.USERNAME_PASSWORD;\n    }\n\n    public AuthlibInjectorServer getServer() {\n        return server;\n    }\n\n    @Override\n    public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException {\n        Objects.requireNonNull(selector);\n        Objects.requireNonNull(username);\n        Objects.requireNonNull(password);\n\n        return new AuthlibInjectorAccount(server, downloader, username, password, selector);\n    }\n\n    @Override\n    public AuthlibInjectorAccount fromStorage(Map<Object, Object> storage) {\n        return AuthlibInjectorAccountFactory.fromStorage(storage, downloader, server);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/SimpleAuthlibInjectorArtifactProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.authlibinjector;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Optional;\n\npublic class SimpleAuthlibInjectorArtifactProvider implements AuthlibInjectorArtifactProvider {\n\n    private Path location;\n\n    public SimpleAuthlibInjectorArtifactProvider(Path location) {\n        this.location = location;\n    }\n\n    @Override\n    public AuthlibInjectorArtifactInfo getArtifactInfo() throws IOException {\n        return AuthlibInjectorArtifactInfo.from(location);\n    }\n\n    @Override\n    public Optional<AuthlibInjectorArtifactInfo> getArtifactInfoImmediately() {\n        try {\n            return Optional.of(getArtifactInfo());\n        } catch (IOException e) {\n            LOG.warning(\"Bad authlib-injector artifact\", e);\n            return Optional.empty();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.microsoft;\n\nimport javafx.beans.binding.ObjectBinding;\nimport org.jackhuang.hmcl.auth.*;\nimport org.jackhuang.hmcl.auth.yggdrasil.Texture;\nimport org.jackhuang.hmcl.auth.yggdrasil.TextureType;\nimport org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\n\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.UUID;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class MicrosoftAccount extends OAuthAccount {\n\n    protected final MicrosoftService service;\n    protected UUID characterUUID;\n\n    private boolean authenticated = false;\n    private MicrosoftSession session;\n\n    protected MicrosoftAccount(MicrosoftService service, MicrosoftSession session) {\n        this.service = requireNonNull(service);\n        this.session = requireNonNull(session);\n        this.characterUUID = requireNonNull(session.getProfile().getId());\n    }\n\n    protected MicrosoftAccount(MicrosoftService service, OAuth.GrantFlow flow) throws AuthenticationException {\n        this.service = requireNonNull(service);\n\n        MicrosoftSession acquiredSession = service.authenticate(flow);\n        if (acquiredSession.getProfile() == null) {\n            session = service.refresh(acquiredSession);\n        } else {\n            session = acquiredSession;\n        }\n\n        characterUUID = session.getProfile().getId();\n        authenticated = true;\n    }\n\n    @Override\n    public String getUsername() {\n        // TODO: email of Microsoft account is blocked by oauth.\n        return \"\";\n    }\n\n    @Override\n    public String getCharacter() {\n        return session.getProfile().getName();\n    }\n\n    @Override\n    public UUID getUUID() {\n        return session.getProfile().getId();\n    }\n\n    @Override\n    public String getIdentifier() {\n        return \"microsoft:\" + getUUID();\n    }\n\n    @Override\n    public AuthInfo logIn() throws AuthenticationException {\n        if (!authenticated || System.currentTimeMillis() > session.getNotAfter()) {\n            if (service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {\n                authenticated = true;\n            } else {\n                MicrosoftSession acquiredSession = service.refresh(session);\n                if (!Objects.equals(acquiredSession.getProfile().getId(), session.getProfile().getId())) {\n                    throw new ServerResponseMalformedException(\"Selected profile changed\");\n                }\n\n                session = acquiredSession;\n\n                authenticated = true;\n                invalidate();\n            }\n        }\n\n        return session.toAuthInfo();\n    }\n\n    @Override\n    public AuthInfo logInWhenCredentialsExpired() throws AuthenticationException {\n        MicrosoftSession acquiredSession = service.authenticate(OAuth.GrantFlow.DEVICE);\n        if (!Objects.equals(characterUUID, acquiredSession.getProfile().getId())) {\n            throw new WrongAccountException(characterUUID, acquiredSession.getProfile().getId());\n        }\n\n        if (acquiredSession.getProfile() == null) {\n            session = service.refresh(acquiredSession);\n        } else {\n            session = acquiredSession;\n        }\n\n        authenticated = true;\n        invalidate();\n        return session.toAuthInfo();\n    }\n\n    @Override\n    public AuthInfo playOffline() {\n        return session.toAuthInfo();\n    }\n\n    @Override\n    public boolean canUploadSkin() {\n        return true;\n    }\n\n    @Override\n    public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {\n        service.uploadSkin(session.getAccessToken(), isSlim, file);\n    }\n\n    @Override\n    public Map<Object, Object> toStorage() {\n        return session.toStorage();\n    }\n\n    public MicrosoftService getService() {\n        return service;\n    }\n\n    @Override\n    public ObjectBinding<Optional<Map<TextureType, Texture>>> getTextures() {\n        return BindingMapping.of(service.getProfileRepository().binding(getUUID()))\n                .map(profile -> profile.flatMap(it -> {\n                    try {\n                        return YggdrasilService.getTextures(it);\n                    } catch (ServerResponseMalformedException e) {\n                        LOG.warning(\"Failed to parse texture payload\", e);\n                        return Optional.empty();\n                    }\n                }));\n    }\n\n    @Override\n    public void clearCache() {\n        authenticated = false;\n        service.getProfileRepository().invalidate(characterUUID);\n    }\n\n    @Override\n    public String toString() {\n        return \"MicrosoftAccount[uuid=\" + characterUUID + \", name=\" + getCharacter() + \"]\";\n    }\n\n    @Override\n    public int hashCode() {\n        return characterUUID.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        MicrosoftAccount that = (MicrosoftAccount) o;\n        return this.isPortable() == that.isPortable() && characterUUID.equals(that.characterUUID);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccountFactory.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.microsoft;\n\nimport org.jackhuang.hmcl.auth.AccountFactory;\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.CharacterSelector;\nimport org.jackhuang.hmcl.auth.OAuth;\n\nimport java.util.Map;\nimport java.util.Objects;\n\npublic class MicrosoftAccountFactory extends AccountFactory<MicrosoftAccount> {\n\n    private final MicrosoftService service;\n\n    public MicrosoftAccountFactory(MicrosoftService service) {\n        this.service = service;\n    }\n\n    @Override\n    public AccountLoginType getLoginType() {\n        return AccountLoginType.NONE;\n    }\n\n    @Override\n    public MicrosoftAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException {\n        return new MicrosoftAccount(service, (OAuth.GrantFlow) additionalData);\n    }\n\n    @Override\n    public MicrosoftAccount fromStorage(Map<Object, Object> storage) {\n        Objects.requireNonNull(storage);\n        MicrosoftSession session = MicrosoftSession.fromStorage(storage);\n        return new MicrosoftAccount(service, session);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.microsoft;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.OAuth;\nimport org.jackhuang.hmcl.auth.ServerDisconnectException;\nimport org.jackhuang.hmcl.auth.ServerResponseMalformedException;\nimport org.jackhuang.hmcl.auth.yggdrasil.*;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.*;\nimport org.jackhuang.hmcl.util.io.*;\nimport org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport static java.net.HttpURLConnection.HTTP_NOT_FOUND;\nimport static java.util.Objects.requireNonNull;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Lang.threadPool;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class MicrosoftService {\n    private static final String SCOPE = \"XboxLive.signin offline_access\";\n    private static final ThreadPoolExecutor POOL = threadPool(\"MicrosoftProfileProperties\", true, 2, 10,\n            TimeUnit.SECONDS);\n\n    private final OAuth.Callback callback;\n\n    private final ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository;\n\n    public MicrosoftService(OAuth.Callback callback) {\n        this.callback = requireNonNull(callback);\n        this.profileRepository = new ObservableOptionalCache<>(uuid -> {\n            LOG.info(\"Fetching properties of \" + uuid);\n            return getCompleteGameProfile(uuid);\n        }, (uuid, e) -> LOG.warning(\"Failed to fetch properties of \" + uuid, e), POOL);\n    }\n\n    public ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> getProfileRepository() {\n        return profileRepository;\n    }\n\n    public MicrosoftSession authenticate(OAuth.GrantFlow flow) throws AuthenticationException {\n        try {\n            OAuth.Result result = OAuth.MICROSOFT.authenticate(flow, new OAuth.Options(SCOPE, callback));\n            return authenticateViaLiveAccessToken(result.getAccessToken(), result.getRefreshToken());\n        } catch (IOException e) {\n            throw new ServerDisconnectException(e);\n        } catch (JsonParseException e) {\n            throw new ServerResponseMalformedException(e);\n        }\n    }\n\n    public MicrosoftSession refresh(MicrosoftSession oldSession) throws AuthenticationException {\n        try {\n            OAuth.Result result = OAuth.MICROSOFT.refresh(oldSession.getRefreshToken(), new OAuth.Options(SCOPE, callback));\n            return authenticateViaLiveAccessToken(result.getAccessToken(), result.getRefreshToken());\n        } catch (IOException e) {\n            throw new ServerDisconnectException(e);\n        } catch (JsonParseException e) {\n            throw new ServerResponseMalformedException(e);\n        }\n    }\n\n    private String getUhs(XBoxLiveAuthenticationResponse response, String existingUhs) throws AuthenticationException {\n        if (response.errorCode != 0) {\n            throw new XboxAuthorizationException(response.errorCode, response.redirectUrl);\n        }\n\n        if (response.displayClaims == null || response.displayClaims.xui == null || response.displayClaims.xui.size() == 0 || !response.displayClaims.xui.get(0).containsKey(\"uhs\")) {\n            LOG.warning(\"Unrecognized xbox authorization response \" + GSON.toJson(response));\n            throw new NoXuiException();\n        }\n\n        String uhs = (String) response.displayClaims.xui.get(0).get(\"uhs\");\n        if (existingUhs != null) {\n            if (!Objects.equals(uhs, existingUhs)) {\n                throw new ServerResponseMalformedException(\"uhs mismatched\");\n            }\n        }\n        return uhs;\n    }\n\n    private MicrosoftSession authenticateViaLiveAccessToken(String liveAccessToken, String liveRefreshToken) throws IOException, JsonParseException, AuthenticationException {\n        String uhs;\n        XBoxLiveAuthenticationResponse xboxResponse, minecraftXstsResponse;\n        try {\n            // Authenticate with XBox Live\n            xboxResponse = HttpRequest\n                    .POST(\"https://user.auth.xboxlive.com/user/authenticate\")\n                    .json(mapOf(\n                            pair(\"Properties\",\n                                    mapOf(pair(\"AuthMethod\", \"RPS\"), pair(\"SiteName\", \"user.auth.xboxlive.com\"),\n                                            pair(\"RpsTicket\", \"d=\" + liveAccessToken))),\n                            pair(\"RelyingParty\", \"http://auth.xboxlive.com\"), pair(\"TokenType\", \"JWT\")))\n                    .retry(5)\n                    .accept(\"application/json\")\n                    .getJson(XBoxLiveAuthenticationResponse.class);\n\n            uhs = getUhs(xboxResponse, null);\n\n            minecraftXstsResponse = HttpRequest\n                    .POST(\"https://xsts.auth.xboxlive.com/xsts/authorize\")\n                    .json(mapOf(\n                            pair(\"Properties\",\n                                    mapOf(pair(\"SandboxId\", \"RETAIL\"),\n                                            pair(\"UserTokens\", Collections.singletonList(xboxResponse.token)))),\n                            pair(\"RelyingParty\", \"rp://api.minecraftservices.com/\"), pair(\"TokenType\", \"JWT\")))\n                    .ignoreHttpErrorCode(401)\n                    .retry(5)\n                    .getJson(XBoxLiveAuthenticationResponse.class);\n        } catch (ResponseCodeException e) {\n            if (e.getResponseCode() == 400) {\n                throw new XBox400Exception();\n            }\n\n            throw e;\n        }\n\n        getUhs(minecraftXstsResponse, uhs);\n\n        // Authenticate with Minecraft\n        MinecraftLoginWithXBoxResponse minecraftResponse = HttpRequest\n                .POST(\"https://api.minecraftservices.com/authentication/login_with_xbox\")\n                .json(mapOf(pair(\"identityToken\", \"XBL3.0 x=\" + uhs + \";\" + minecraftXstsResponse.token)))\n                .retry(5)\n                .accept(\"application/json\").getJson(MinecraftLoginWithXBoxResponse.class);\n\n        long notAfter = minecraftResponse.expiresIn * 1000L + System.currentTimeMillis();\n\n        // Check MC ownership, this is necessary, see GitHub#2979\n        HttpURLConnection request = HttpRequest.GET(\"https://api.minecraftservices.com/entitlements/mcstore\")\n                .authorization(\"Bearer \" + minecraftResponse.accessToken)\n                .retry(5)\n                .accept(\"application/json\").createConnection();\n\n        if (request.getResponseCode() != 200) {\n            throw new ResponseCodeException(\"https://api.minecraftservices.com/entitlements/mcstore\", request.getResponseCode());\n        }\n\n        // Get Minecraft Account UUID\n        MinecraftProfileResponse profileResponse = getMinecraftProfile(minecraftResponse.tokenType, minecraftResponse.accessToken);\n        handleErrorResponse(profileResponse);\n\n        return new MicrosoftSession(minecraftResponse.tokenType, minecraftResponse.accessToken, notAfter, liveRefreshToken,\n                new MicrosoftSession.User(minecraftResponse.username), new MicrosoftSession.GameProfile(profileResponse.id, profileResponse.name));\n    }\n\n    public Optional<MinecraftProfileResponse> getCompleteProfile(String authorization) throws AuthenticationException {\n        try {\n            return Optional.ofNullable(\n                    HttpRequest.GET(\"https://api.minecraftservices.com/minecraft/profile\")\n                            .authorization(authorization).getJson(MinecraftProfileResponse.class));\n        } catch (IOException e) {\n            throw new ServerDisconnectException(e);\n        } catch (JsonParseException e) {\n            throw new ServerResponseMalformedException(e);\n        }\n    }\n\n    public boolean validate(long notAfter, String tokenType, String accessToken) throws AuthenticationException {\n        requireNonNull(tokenType);\n        requireNonNull(accessToken);\n\n        if (System.currentTimeMillis() > notAfter) {\n            return false;\n        }\n\n        try {\n            getMinecraftProfile(tokenType, accessToken);\n            return true;\n        } catch (ResponseCodeException e) {\n            return false;\n        } catch (IOException e) {\n            throw new ServerDisconnectException(e);\n        }\n    }\n\n    private static void handleErrorResponse(MinecraftErrorResponse response) throws AuthenticationException {\n        if (response.error != null) {\n            throw new RemoteAuthenticationException(response.error, response.errorMessage, response.developerMessage);\n        }\n    }\n\n    public static Optional<Map<TextureType, Texture>> getTextures(MinecraftProfileResponse profile) {\n        Objects.requireNonNull(profile);\n\n        Map<TextureType, Texture> textures = new EnumMap<>(TextureType.class);\n\n        if (!profile.skins.isEmpty()) {\n            textures.put(TextureType.SKIN, new Texture(profile.skins.get(0).url, null));\n        }\n        // if (!profile.capes.isEmpty()) {\n        // textures.put(TextureType.CAPE, new Texture(profile.capes.get(0).url, null);\n        // }\n\n        return Optional.of(textures);\n    }\n\n    private static void getXBoxProfile(String uhs, String xstsToken) throws IOException {\n        HttpRequest.GET(\"https://profile.xboxlive.com/users/me/profile/settings\",\n                        pair(\"settings\", \"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,\"\n                                + \"PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix,\"\n                                + \"UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep,\"\n                                + \"PreferredColor,Location,Bio,Watermarks,\" + \"RealName,RealNameOverride,IsQuarantined\"))\n                .accept(\"application/json\")\n                .authorization(String.format(\"XBL3.0 x=%s;%s\", uhs, xstsToken))\n                .header(\"x-xbl-contract-version\", \"3\")\n                .getString();\n    }\n\n    private static MinecraftProfileResponse getMinecraftProfile(String tokenType, String accessToken)\n            throws IOException, AuthenticationException {\n        HttpURLConnection conn = HttpRequest.GET(\"https://api.minecraftservices.com/minecraft/profile\")\n                .authorization(tokenType, accessToken)\n                .createConnection();\n        int responseCode = conn.getResponseCode();\n        if (responseCode == HTTP_NOT_FOUND) {\n            throw new NoMinecraftJavaEditionProfileException();\n        } else if (responseCode != 200) {\n            throw new ResponseCodeException(\"https://api.minecraftservices.com/minecraft/profile\", responseCode);\n        }\n\n        String result = NetworkUtils.readFullyAsString(conn);\n        return JsonUtils.fromNonNullJson(result, MinecraftProfileResponse.class);\n    }\n\n    public Optional<CompleteGameProfile> getCompleteGameProfile(UUID uuid) throws AuthenticationException {\n        Objects.requireNonNull(uuid);\n\n        return Optional.ofNullable(GSON.fromJson(request(\"https://sessionserver.mojang.com/session/minecraft/profile/\" + UUIDTypeAdapter.fromUUID(uuid), null), CompleteGameProfile.class));\n    }\n\n    public void uploadSkin(String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {\n        try {\n            HttpURLConnection con = NetworkUtils.createHttpConnection(\"https://api.minecraftservices.com/minecraft/profile/skins\");\n            con.setRequestMethod(\"POST\");\n            con.setRequestProperty(\"Authorization\", \"Bearer \" + accessToken);\n            con.setDoOutput(true);\n            try (HttpMultipartRequest request = new HttpMultipartRequest(con)) {\n                request.param(\"variant\", isSlim ? \"slim\" : \"classic\");\n                try (InputStream fis = Files.newInputStream(file)) {\n                    request.file(\"file\", FileUtils.getName(file), \"image/\" + FileUtils.getExtension(file), fis);\n                }\n            }\n\n            String response = NetworkUtils.readFullyAsString(con);\n            if (StringUtils.isBlank(response)) {\n                if (con.getResponseCode() / 100 != 2)\n                    throw new ResponseCodeException(con.getURL().toURI(), con.getResponseCode());\n            } else {\n                MinecraftErrorResponse profileResponse = GSON.fromJson(response, MinecraftErrorResponse.class);\n                if (StringUtils.isNotBlank(profileResponse.errorMessage) || con.getResponseCode() / 100 != 2)\n                    throw new AuthenticationException(\"Failed to upload skin, response code: \" + con.getResponseCode() + \", response: \" + response);\n            }\n        } catch (IOException | JsonParseException | URISyntaxException e) {\n            throw new AuthenticationException(e);\n        }\n    }\n\n    private static String request(String url, Object payload) throws AuthenticationException {\n        try {\n            if (payload == null)\n                return NetworkUtils.doGet(url);\n            else\n                return NetworkUtils.doPost(NetworkUtils.toURI(url), payload instanceof String ? (String) payload : GSON.toJson(payload), \"application/json\");\n        } catch (IOException e) {\n            throw new ServerDisconnectException(e);\n        }\n    }\n\n    public static class XboxAuthorizationException extends AuthenticationException {\n        private final long errorCode;\n        private final String redirect;\n\n        public XboxAuthorizationException(long errorCode, String redirect) {\n            this.errorCode = errorCode;\n            this.redirect = redirect;\n        }\n\n        public long getErrorCode() {\n            return errorCode;\n        }\n\n        public String getRedirect() {\n            return redirect;\n        }\n\n        public static final long BANNED = 2148916227L;\n        public static final long MISSING_XBOX_ACCOUNT = 2148916233L;\n        public static final long COUNTRY_UNAVAILABLE = 2148916235L;\n        public static final long ADD_FAMILY = 2148916238L;\n    }\n\n    public final static class XBox400Exception extends AuthenticationException {\n    }\n\n    public final static class NoMinecraftJavaEditionProfileException extends AuthenticationException {\n    }\n\n    public final static class NoXuiException extends AuthenticationException {\n    }\n\n    private final static class XBoxLiveAuthenticationResponseDisplayClaims {\n        List<Map<Object, Object>> xui;\n    }\n\n    private static class MicrosoftErrorResponse {\n        @SerializedName(\"XErr\")\n        long errorCode;\n\n        @SerializedName(\"Message\")\n        String message;\n\n        @SerializedName(\"Redirect\")\n        String redirectUrl;\n    }\n\n    /**\n     * Success Response: { \"IssueInstant\":\"2020-12-07T19:52:08.4463796Z\",\n     * \"NotAfter\":\"2020-12-21T19:52:08.4463796Z\", \"Token\":\"token\", \"DisplayClaims\":{\n     * \"xui\":[ { \"uhs\":\"userhash\" } ] } }\n     * <p>\n     * Error response: { \"Identity\":\"0\", \"XErr\":2148916238, \"Message\":\"\",\n     * \"Redirect\":\"https://start.ui.xboxlive.com/AddChildToFamily\" }\n     * <p>\n     * XErr Candidates: 2148916233 = missing XBox account 2148916238 = child account\n     * not linked to a family\n     */\n    private final static class XBoxLiveAuthenticationResponse extends MicrosoftErrorResponse {\n        @SerializedName(\"IssueInstant\")\n        String issueInstant;\n\n        @SerializedName(\"NotAfter\")\n        String notAfter;\n\n        @SerializedName(\"Token\")\n        String token;\n\n        @SerializedName(\"DisplayClaims\")\n        XBoxLiveAuthenticationResponseDisplayClaims displayClaims;\n    }\n\n    private final static class MinecraftLoginWithXBoxResponse {\n        @SerializedName(\"username\")\n        String username;\n\n        @SerializedName(\"roles\")\n        List<String> roles;\n\n        @SerializedName(\"access_token\")\n        String accessToken;\n\n        @SerializedName(\"token_type\")\n        String tokenType;\n\n        @SerializedName(\"expires_in\")\n        int expiresIn;\n    }\n\n    private final static class MinecraftStoreResponseItem {\n        @SerializedName(\"name\")\n        String name;\n        @SerializedName(\"signature\")\n        String signature;\n    }\n\n    private final static class MinecraftStoreResponse extends MinecraftErrorResponse {\n        @SerializedName(\"items\")\n        List<MinecraftStoreResponseItem> items;\n\n        @SerializedName(\"signature\")\n        String signature;\n\n        @SerializedName(\"keyId\")\n        String keyId;\n    }\n\n    public final static class MinecraftProfileResponseSkin implements Validation {\n        public String id;\n        public String state;\n        public String url;\n        public String variant; // CLASSIC, SLIM\n        public String alias;\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            Validation.requireNonNull(id, \"id cannot be null\");\n            Validation.requireNonNull(state, \"state cannot be null\");\n            Validation.requireNonNull(url, \"url cannot be null\");\n            Validation.requireNonNull(variant, \"variant cannot be null\");\n        }\n    }\n\n    public static class MinecraftProfileResponseCape {\n\n    }\n\n    public static class MinecraftProfileResponse extends MinecraftErrorResponse implements Validation {\n        @SerializedName(\"id\")\n        UUID id;\n        @SerializedName(\"name\")\n        String name;\n        @SerializedName(\"skins\")\n        List<MinecraftProfileResponseSkin> skins;\n        @SerializedName(\"capes\")\n        List<MinecraftProfileResponseCape> capes;\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            Validation.requireNonNull(id, \"id cannot be null\");\n            Validation.requireNonNull(name, \"name cannot be null\");\n            Validation.requireNonNull(skins, \"skins cannot be null\");\n            Validation.requireNonNull(capes, \"capes cannot be null\");\n        }\n    }\n\n    private static class MinecraftErrorResponse {\n        public String path;\n        public String errorType;\n        public String error;\n        public String errorMessage;\n        public String developerMessage;\n    }\n\n    private static final Gson GSON = new GsonBuilder()\n            .registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)\n            .registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)\n            .create();\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftSession.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.microsoft;\n\nimport org.jackhuang.hmcl.auth.AuthInfo;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\nimport org.jackhuang.hmcl.util.logging.Logger;\n\nimport java.util.Map;\nimport java.util.UUID;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Lang.tryCast;\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\npublic class MicrosoftSession {\n    private final String tokenType;\n    private final long notAfter;\n    private final String accessToken;\n    private final String refreshToken;\n    private final User user;\n    private final GameProfile profile;\n\n    public MicrosoftSession(String tokenType, String accessToken, long notAfter, String refreshToken, User user, GameProfile profile) {\n        this.tokenType = tokenType;\n        this.accessToken = accessToken;\n        this.notAfter = notAfter;\n        this.refreshToken = refreshToken;\n        this.user = user;\n        this.profile = profile;\n\n        if (accessToken != null) Logger.registerAccessToken(accessToken);\n    }\n\n    public String getTokenType() {\n        return tokenType;\n    }\n\n    public String getAccessToken() {\n        return accessToken;\n    }\n\n    public long getNotAfter() {\n        return notAfter;\n    }\n\n    public String getRefreshToken() {\n        return refreshToken;\n    }\n\n    public String getAuthorization() {\n        return String.format(\"%s %s\", getTokenType(), getAccessToken());\n    }\n\n    public User getUser() {\n        return user;\n    }\n\n    public GameProfile getProfile() {\n        return profile;\n    }\n\n    public static MicrosoftSession fromStorage(Map<?, ?> storage) {\n        UUID uuid = tryCast(storage.get(\"uuid\"), String.class).map(UUIDTypeAdapter::fromString)\n                .orElseThrow(() -> new IllegalArgumentException(\"uuid is missing\"));\n        String name = tryCast(storage.get(\"displayName\"), String.class)\n                .orElseThrow(() -> new IllegalArgumentException(\"displayName is missing\"));\n        String tokenType = tryCast(storage.get(\"tokenType\"), String.class)\n                .orElseThrow(() -> new IllegalArgumentException(\"tokenType is missing\"));\n        String accessToken = tryCast(storage.get(\"accessToken\"), String.class)\n                .orElseThrow(() -> new IllegalArgumentException(\"accessToken is missing\"));\n        String refreshToken = tryCast(storage.get(\"refreshToken\"), String.class)\n                .orElseThrow(() -> new IllegalArgumentException(\"refreshToken is missing\"));\n        Long notAfter = tryCast(storage.get(\"notAfter\"), Number.class).map(Number::longValue).orElse(0L);\n        String userId = tryCast(storage.get(\"userid\"), String.class)\n                .orElseThrow(() -> new IllegalArgumentException(\"userid is missing\"));\n        return new MicrosoftSession(tokenType, accessToken, notAfter, refreshToken, new User(userId), new GameProfile(uuid, name));\n    }\n\n    public Map<Object, Object> toStorage() {\n        requireNonNull(profile);\n        requireNonNull(user);\n\n        return mapOf(\n                pair(\"uuid\", UUIDTypeAdapter.fromUUID(profile.getId())),\n                pair(\"displayName\", profile.getName()),\n                pair(\"tokenType\", tokenType),\n                pair(\"accessToken\", accessToken),\n                pair(\"refreshToken\", refreshToken),\n                pair(\"notAfter\", notAfter),\n                pair(\"userid\", user.id));\n    }\n\n    public AuthInfo toAuthInfo() {\n        requireNonNull(profile);\n\n        return new AuthInfo(profile.getName(), profile.getId(), accessToken, AuthInfo.USER_TYPE_MSA, \"{}\");\n    }\n\n    public static class User {\n        private final String id;\n\n        public User(String id) {\n            this.id = id;\n        }\n\n        public String getId() {\n            return id;\n        }\n    }\n\n    public static class GameProfile {\n        private final UUID id;\n        private final String name;\n\n        public GameProfile(UUID id, String name) {\n            this.id = id;\n            this.name = name;\n        }\n\n        public UUID getId() {\n            return id;\n        }\n\n        public String getName() {\n            return name;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccount.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.offline;\n\nimport javafx.beans.binding.ObjectBinding;\nimport org.jackhuang.hmcl.auth.Account;\nimport org.jackhuang.hmcl.auth.AuthInfo;\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorArtifactInfo;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorArtifactProvider;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;\nimport org.jackhuang.hmcl.auth.yggdrasil.Texture;\nimport org.jackhuang.hmcl.auth.yggdrasil.TextureType;\nimport org.jackhuang.hmcl.game.Arguments;\nimport org.jackhuang.hmcl.game.LaunchOptions;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\n\nimport java.io.IOException;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.ExecutionException;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\n/**\n *\n * @author huang\n */\npublic class OfflineAccount extends Account {\n\n    private final AuthlibInjectorArtifactProvider downloader;\n    private final String username;\n    private final UUID uuid;\n    private Skin skin;\n\n    protected OfflineAccount(AuthlibInjectorArtifactProvider downloader, String username, UUID uuid, Skin skin) {\n        this.downloader = requireNonNull(downloader);\n        this.username = requireNonNull(username);\n        this.uuid = requireNonNull(uuid);\n        this.skin = skin;\n\n        if (StringUtils.isBlank(username)) {\n            throw new IllegalArgumentException(\"Username cannot be blank\");\n        }\n    }\n\n    public AuthlibInjectorArtifactProvider getDownloader() {\n        return downloader;\n    }\n\n    @Override\n    public UUID getUUID() {\n        return uuid;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getCharacter() {\n        return username;\n    }\n\n    @Override\n    public String getIdentifier() {\n        return username + \":\" + username;\n    }\n\n    public Skin getSkin() {\n        return skin;\n    }\n\n    public void setSkin(Skin skin) {\n        this.skin = skin;\n        invalidate();\n    }\n\n    protected boolean loadAuthlibInjector(Skin skin) {\n        return skin != null && skin.getType() != Skin.Type.DEFAULT;\n    }\n\n    public AuthInfo logInWithoutSkin() throws AuthenticationException {\n        // Using \"legacy\" user type here because \"mojang\" user type may cause \"invalid session token\" or \"disconnected\" when connecting to a game server.\n        return new AuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), AuthInfo.USER_TYPE_MSA, \"{}\");\n    }\n\n    @Override\n    public AuthInfo logIn() throws AuthenticationException {\n        AuthInfo authInfo = logInWithoutSkin();\n\n        if (loadAuthlibInjector(skin)) {\n            CompletableFuture<AuthlibInjectorArtifactInfo> artifactTask = CompletableFuture.supplyAsync(() -> {\n                try {\n                    return downloader.getArtifactInfo();\n                } catch (IOException e) {\n                    throw new CompletionException(new AuthlibInjectorDownloadException(e));\n                }\n            });\n\n            AuthlibInjectorArtifactInfo artifact;\n            try {\n                artifact = artifactTask.get();\n            } catch (InterruptedException e) {\n                Thread.currentThread().interrupt();\n                throw new AuthenticationException(e);\n            } catch (ExecutionException e) {\n                if (e.getCause() instanceof AuthenticationException) {\n                    throw (AuthenticationException) e.getCause();\n                } else {\n                    throw new AuthenticationException(e.getCause());\n                }\n            }\n\n            try {\n                return new OfflineAuthInfo(authInfo, artifact);\n            } catch (Exception e) {\n                throw new AuthenticationException(e);\n            }\n        } else {\n            return authInfo;\n        }\n    }\n\n    private class OfflineAuthInfo extends AuthInfo {\n        private final AuthlibInjectorArtifactInfo artifact;\n        private YggdrasilServer server;\n\n        public OfflineAuthInfo(AuthInfo authInfo, AuthlibInjectorArtifactInfo artifact) {\n            super(authInfo.getUsername(), authInfo.getUUID(), authInfo.getAccessToken(), USER_TYPE_MSA, authInfo.getUserProperties());\n\n            this.artifact = artifact;\n        }\n\n        @Override\n        public Arguments getLaunchArguments(LaunchOptions options) throws IOException {\n            if (!options.isDaemon()) return null;\n\n            server = new YggdrasilServer(0);\n            server.start();\n\n            try {\n                server.addCharacter(new YggdrasilServer.Character(uuid, username,\n                        skin != null ? skin.load(username).run() : null));\n            } catch (IOException e) {\n                // ignore\n            } catch (Exception e) {\n                throw new IOException(e);\n            }\n\n            return new Arguments().addJVMArguments(\n                    \"-javaagent:\" + artifact.getLocation().toString() + \"=\" + \"http://localhost:\" + server.getListeningPort(),\n                    \"-Dauthlibinjector.side=client\"\n            );\n        }\n\n        @Override\n        public void close() throws Exception {\n            super.close();\n\n            if (server != null)\n                server.stop();\n        }\n    }\n\n    @Override\n    public AuthInfo playOffline() throws AuthenticationException {\n        return logIn();\n    }\n\n    @Override\n    public Map<Object, Object> toStorage() {\n        return mapOf(\n                pair(\"uuid\", UUIDTypeAdapter.fromUUID(uuid)),\n                pair(\"username\", username),\n                pair(\"skin\", skin == null ? null : skin.toStorage())\n        );\n    }\n\n    @Override\n    public ObjectBinding<Optional<Map<TextureType, Texture>>> getTextures() {\n        return super.getTextures();\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"username\", username)\n                .append(\"uuid\", uuid)\n                .toString();\n    }\n\n    @Override\n    public int hashCode() {\n        return username.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (!(obj instanceof OfflineAccount))\n            return false;\n        OfflineAccount another = (OfflineAccount) obj;\n        return isPortable() == another.isPortable() && username.equals(another.username);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccountFactory.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.offline;\n\nimport org.jackhuang.hmcl.auth.AccountFactory;\nimport org.jackhuang.hmcl.auth.CharacterSelector;\nimport org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorArtifactProvider;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\n\nimport java.util.Map;\nimport java.util.UUID;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.jackhuang.hmcl.util.Lang.tryCast;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class OfflineAccountFactory extends AccountFactory<OfflineAccount> {\n    private final AuthlibInjectorArtifactProvider downloader;\n\n    public OfflineAccountFactory(AuthlibInjectorArtifactProvider downloader) {\n        this.downloader = downloader;\n    }\n\n    @Override\n    public AccountLoginType getLoginType() {\n        return AccountLoginType.USERNAME;\n    }\n\n    public OfflineAccount create(String username, UUID uuid) {\n        return new OfflineAccount(downloader, username, uuid, null);\n    }\n\n    @Override\n    public OfflineAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) {\n        AdditionalData data;\n        UUID uuid;\n        Skin skin;\n        if (additionalData != null) {\n            data = (AdditionalData) additionalData;\n            uuid = data.uuid == null ? getUUIDFromUserName(username) : data.uuid;\n            skin = data.skin;\n        } else {\n            uuid = getUUIDFromUserName(username);\n            skin = null;\n        }\n        return new OfflineAccount(downloader, username, uuid, skin);\n    }\n\n    @Override\n    public OfflineAccount fromStorage(Map<Object, Object> storage) {\n        String username = tryCast(storage.get(\"username\"), String.class)\n                .orElseThrow(() -> new IllegalStateException(\"Offline account configuration malformed.\"));\n        UUID uuid = tryCast(storage.get(\"uuid\"), String.class)\n                .map(UUIDTypeAdapter::fromString)\n                .orElse(getUUIDFromUserName(username));\n        Skin skin = Skin.fromStorage(tryCast(storage.get(\"skin\"), Map.class).orElse(null));\n\n        return new OfflineAccount(downloader, username, uuid, skin);\n    }\n\n    public static UUID getUUIDFromUserName(String username) {\n        return UUID.nameUUIDFromBytes((\"OfflinePlayer:\" + username).getBytes(UTF_8));\n    }\n\n    public static class AdditionalData {\n        private final UUID uuid;\n        private final Skin skin;\n\n        public AdditionalData(UUID uuid, Skin skin) {\n            this.uuid = uuid;\n            this.skin = skin;\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.offline;\n\nimport com.google.gson.annotations.SerializedName;\nimport javafx.scene.image.Image;\nimport org.jackhuang.hmcl.auth.yggdrasil.TextureModel;\nimport org.jackhuang.hmcl.task.FetchTask;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.http.HttpResponse;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Lang.tryCast;\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\npublic class Skin {\n\n    public enum Type {\n        DEFAULT,\n        ALEX,\n        ARI,\n        EFE,\n        KAI,\n        MAKENA,\n        NOOR,\n        STEVE,\n        SUNNY,\n        ZURI,\n        LOCAL_FILE,\n        LITTLE_SKIN,\n        CUSTOM_SKIN_LOADER_API,\n        YGGDRASIL_API;\n\n        public static Type fromStorage(String type) {\n            switch (type) {\n                case \"default\":\n                    return DEFAULT;\n                case \"alex\":\n                    return ALEX;\n                case \"ari\":\n                    return ARI;\n                case \"efe\":\n                    return EFE;\n                case \"kai\":\n                    return KAI;\n                case \"makena\":\n                    return MAKENA;\n                case \"noor\":\n                    return NOOR;\n                case \"steve\":\n                    return STEVE;\n                case \"sunny\":\n                    return SUNNY;\n                case \"zuri\":\n                    return ZURI;\n                case \"local_file\":\n                    return LOCAL_FILE;\n                case \"little_skin\":\n                    return LITTLE_SKIN;\n                case \"custom_skin_loader_api\":\n                    return CUSTOM_SKIN_LOADER_API;\n                case \"yggdrasil_api\":\n                    return YGGDRASIL_API;\n                default:\n                    return null;\n            }\n        }\n    }\n\n    private final Type type;\n    private final String cslApi;\n    private final TextureModel textureModel;\n    private final String localSkinPath;\n    private final String localCapePath;\n\n    public Skin(Type type, String cslApi, TextureModel textureModel, String localSkinPath, String localCapePath) {\n        this.type = type;\n        this.cslApi = cslApi;\n        this.textureModel = textureModel;\n        this.localSkinPath = localSkinPath;\n        this.localCapePath = localCapePath;\n    }\n\n    public Type getType() {\n        return type;\n    }\n\n    public String getCslApi() {\n        return cslApi;\n    }\n\n    public TextureModel getTextureModel() {\n        return textureModel == null ? TextureModel.WIDE : textureModel;\n    }\n\n    public String getLocalSkinPath() {\n        return localSkinPath;\n    }\n\n    public String getLocalCapePath() {\n        return localCapePath;\n    }\n\n    public Task<LoadedSkin> load(String username) {\n        switch (type) {\n            case DEFAULT:\n                return Task.supplyAsync(() -> null);\n            case ALEX:\n            case ARI:\n            case EFE:\n            case KAI:\n            case MAKENA:\n            case NOOR:\n            case STEVE:\n            case SUNNY:\n            case ZURI:\n                TextureModel model = this.textureModel != null ? this.textureModel : type == Type.ALEX ? TextureModel.SLIM : TextureModel.WIDE;\n                String resource = (model == TextureModel.SLIM ? \"/assets/img/skin/slim/\" : \"/assets/img/skin/wide/\") + type.name().toLowerCase(Locale.ROOT) + \".png\";\n\n                return Task.supplyAsync(() -> new LoadedSkin(\n                        model,\n                        Texture.loadTexture(new Image(resource)),\n                        null\n                ));\n            case LOCAL_FILE:\n                return Task.supplyAsync(() -> {\n                    Texture skin = null, cape = null;\n                    Optional<Path> skinPath = FileUtils.tryGetPath(localSkinPath);\n                    Optional<Path> capePath = FileUtils.tryGetPath(localCapePath);\n                    if (skinPath.isPresent()) skin = Texture.loadTexture(Files.newInputStream(skinPath.get()));\n                    if (capePath.isPresent()) cape = Texture.loadTexture(Files.newInputStream(capePath.get()));\n                    return new LoadedSkin(getTextureModel(), skin, cape);\n                });\n            case LITTLE_SKIN:\n            case CUSTOM_SKIN_LOADER_API:\n                String realCslApi = type == Type.LITTLE_SKIN\n                        ? \"https://littleskin.cn/csl\"\n                        : NetworkUtils.addHttpsIfMissing(StringUtils.removeSuffix(Lang.requireNonNullElse(cslApi, \"\"), \"/\"));\n                return Task.composeAsync(() -> new GetTask(String.format(\"%s/%s.json\", realCslApi, username)))\n                        .thenComposeAsync(json -> {\n                            SkinJson result = JsonUtils.GSON.fromJson(json, SkinJson.class);\n\n                            if (!result.hasSkin()) {\n                                return Task.supplyAsync(() -> null);\n                            }\n\n                            return Task.allOf(\n                                    Task.supplyAsync(result::getModel),\n                                    result.getHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(String.format(\"%s/textures/%s\", realCslApi, result.getHash())),\n                                    result.getCapeHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(String.format(\"%s/textures/%s\", realCslApi, result.getCapeHash()))\n                            );\n                        }).thenApplyAsync(result -> {\n                            if (result == null) {\n                                return null;\n                            }\n\n                            Texture skin, cape;\n                            if (result.get(1) != null) {\n                                skin = Texture.loadTexture((InputStream) result.get(1));\n                            } else {\n                                skin = null;\n                            }\n\n                            if (result.get(2) != null) {\n                                cape = Texture.loadTexture((InputStream) result.get(2));\n                            } else {\n                                cape = null;\n                            }\n\n                            return new LoadedSkin((TextureModel) result.get(0), skin, cape);\n                        });\n            default:\n                throw new UnsupportedOperationException();\n        }\n    }\n\n    public Map<?, ?> toStorage() {\n        return mapOf(\n                pair(\"type\", type.name().toLowerCase(Locale.ROOT)),\n                pair(\"cslApi\", cslApi),\n                pair(\"textureModel\", getTextureModel().modelName),\n                pair(\"localSkinPath\", localSkinPath),\n                pair(\"localCapePath\", localCapePath)\n        );\n    }\n\n    public static Skin fromStorage(Map<?, ?> storage) {\n        if (storage == null) return null;\n\n        Type type = tryCast(storage.get(\"type\"), String.class).flatMap(t -> Optional.ofNullable(Type.fromStorage(t)))\n                .orElse(Type.DEFAULT);\n        String cslApi = tryCast(storage.get(\"cslApi\"), String.class).orElse(null);\n        String textureModel = tryCast(storage.get(\"textureModel\"), String.class).orElse(\"default\");\n        String localSkinPath = tryCast(storage.get(\"localSkinPath\"), String.class).orElse(null);\n        String localCapePath = tryCast(storage.get(\"localCapePath\"), String.class).orElse(null);\n\n        return new Skin(type, cslApi, \"slim\".equals(textureModel) ? TextureModel.SLIM : TextureModel.WIDE, localSkinPath, localCapePath);\n    }\n\n    private static class FetchBytesTask extends FetchTask<InputStream> {\n\n        public FetchBytesTask(String uri) {\n            super(List.of(NetworkUtils.toURI(uri)));\n        }\n\n        @Override\n        protected void useCachedResult(Path cachedFile) throws IOException {\n            setResult(Files.newInputStream(cachedFile));\n        }\n\n        @Override\n        protected EnumCheckETag shouldCheckETag() {\n            return EnumCheckETag.CHECK_E_TAG;\n        }\n\n        @Override\n        protected Context getContext(HttpResponse<?> response, boolean checkETag, String bmclapiHash) throws IOException {\n            return new Context() {\n                final ByteArrayOutputStream baos = new ByteArrayOutputStream();\n\n                @Override\n                public void write(byte[] buffer, int offset, int len) {\n                    baos.write(buffer, offset, len);\n                }\n\n                @Override\n                public void close() throws IOException {\n                    if (!isSuccess()) return;\n\n                    setResult(new ByteArrayInputStream(baos.toByteArray()));\n\n                    if (checkETag) {\n                        repository.cacheBytes(response, baos.toByteArray());\n                    }\n                }\n            };\n        }\n    }\n\n    public static class LoadedSkin {\n        private final TextureModel model;\n        private final Texture skin;\n        private final Texture cape;\n\n        public LoadedSkin(TextureModel model, Texture skin, Texture cape) {\n            this.model = model;\n            this.skin = skin;\n            this.cape = cape;\n        }\n\n        public TextureModel getModel() {\n            return model;\n        }\n\n        public Texture getSkin() {\n            return skin;\n        }\n\n        public Texture getCape() {\n            return cape;\n        }\n    }\n\n    private static class SkinJson {\n        private final String username;\n        private final String skin;\n        private final String cape;\n        private final String elytra;\n\n        @SerializedName(value = \"textures\", alternate = { \"skins\" })\n        private final TextureJson textures;\n\n        public SkinJson(String username, String skin, String cape, String elytra, TextureJson textures) {\n            this.username = username;\n            this.skin = skin;\n            this.cape = cape;\n            this.elytra = elytra;\n            this.textures = textures;\n        }\n\n        public boolean hasSkin() {\n            return StringUtils.isNotBlank(username);\n        }\n\n        @Nullable\n        public TextureModel getModel() {\n            if (textures != null && textures.slim != null) {\n                return TextureModel.SLIM;\n            } else if (textures != null && textures.defaultSkin != null) {\n                return TextureModel.WIDE;\n            } else {\n                return null;\n            }\n        }\n\n        public String getAlexModelHash() {\n            if (textures != null && textures.slim != null) {\n                return textures.slim;\n            } else {\n                return null;\n            }\n        }\n\n        public String getSteveModelHash() {\n            if (textures != null && textures.defaultSkin != null) {\n                return textures.defaultSkin;\n            } else return skin;\n        }\n\n        public String getHash() {\n            TextureModel model = getModel();\n            if (model == TextureModel.SLIM)\n                return getAlexModelHash();\n            else if (model == TextureModel.WIDE)\n                return getSteveModelHash();\n            else\n                return null;\n        }\n\n        public String getCapeHash() {\n            if (textures != null && textures.cape != null) {\n                return textures.cape;\n            } else return cape;\n        }\n\n        public static class TextureJson {\n            @SerializedName(\"default\")\n            private final String defaultSkin;\n\n            private final String slim;\n            private final String cape;\n            private final String elytra;\n\n            public TextureJson(String defaultSkin, String slim, String cape, String elytra) {\n                this.defaultSkin = defaultSkin;\n                this.slim = slim;\n                this.cape = cape;\n                this.elytra = elytra;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Texture.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.offline;\n\nimport javafx.scene.image.Image;\nimport javafx.scene.image.PixelReader;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.HashMap;\nimport java.util.HexFormat;\nimport java.util.Map;\n\nimport static java.util.Objects.requireNonNull;\n\npublic final class Texture {\n    private final String hash;\n    private final Image image;\n\n    public Texture(String hash, Image image) {\n        this.hash = requireNonNull(hash);\n        this.image = requireNonNull(image);\n    }\n\n    public String getHash() {\n        return hash;\n    }\n\n    public Image getImage() {\n        return image;\n    }\n\n    private static final Map<String, Texture> textures = new HashMap<>();\n\n    public static boolean hasTexture(String hash) {\n        return textures.containsKey(hash);\n    }\n\n    public static Texture getTexture(String hash) {\n        return textures.get(hash);\n    }\n\n    private static String computeTextureHash(Image img) {\n        MessageDigest digest;\n        try {\n            digest = MessageDigest.getInstance(\"SHA-256\");\n        } catch (NoSuchAlgorithmException e) {\n            throw new RuntimeException(e);\n        }\n\n        PixelReader reader = img.getPixelReader();\n        int width = (int) img.getWidth();\n        int height = (int) img.getHeight();\n        byte[] buf = new byte[4096];\n\n        putInt(buf, 0, width);\n        putInt(buf, 4, height);\n        int pos = 8;\n        for (int x = 0; x < width; x++) {\n            for (int y = 0; y < height; y++) {\n                putInt(buf, pos, reader.getArgb(x, y));\n                if (buf[pos + 0] == 0) {\n                    buf[pos + 1] = buf[pos + 2] = buf[pos + 3] = 0;\n                }\n                pos += 4;\n                if (pos == buf.length) {\n                    pos = 0;\n                    digest.update(buf, 0, buf.length);\n                }\n            }\n        }\n        if (pos > 0) {\n            digest.update(buf, 0, pos);\n        }\n\n        return HexFormat.of().formatHex(digest.digest());\n    }\n\n    private static void putInt(byte[] array, int offset, int x) {\n        array[offset + 0] = (byte) (x >> 24 & 0xff);\n        array[offset + 1] = (byte) (x >> 16 & 0xff);\n        array[offset + 2] = (byte) (x >> 8 & 0xff);\n        array[offset + 3] = (byte) (x >> 0 & 0xff);\n    }\n\n    public static Texture loadTexture(InputStream in) throws IOException {\n        if (in == null) return null;\n        Image img;\n        try (InputStream is = in) {\n            img = new Image(is);\n        }\n\n        if (img.isError()) {\n            throw new IOException(\"No image found\", img.getException());\n        }\n        return loadTexture(img);\n    }\n\n    public static Texture loadTexture(Image image) {\n        if (image == null) return null;\n\n        String hash = computeTextureHash(image);\n\n        Texture existent = textures.get(hash);\n        if (existent != null) {\n            return existent;\n        }\n\n        Texture texture = new Texture(hash, image);\n        existent = textures.putIfAbsent(hash, texture);\n\n        if (existent != null) {\n            return existent;\n        }\n        return texture;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.offline;\n\nimport org.glavo.png.javafx.PNGJavaFXUtils;\nimport org.jackhuang.hmcl.auth.yggdrasil.GameProfile;\nimport org.jackhuang.hmcl.auth.yggdrasil.TextureModel;\nimport org.jackhuang.hmcl.util.KeyUtils;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\nimport org.jackhuang.hmcl.util.io.HttpServer;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.security.*;\nimport java.util.*;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\n\npublic class YggdrasilServer extends HttpServer {\n\n    private final Map<UUID, Character> charactersByUuid = new HashMap<>();\n    private final Map<String, Character> charactersByName = new HashMap<>();\n\n    public YggdrasilServer(int port) {\n        super(port);\n\n        addRoute(Method.GET, Pattern.compile(\"^/$\"), this::root);\n        addRoute(Method.GET, Pattern.compile(\"/status\"), this::status);\n        addRoute(Method.POST, Pattern.compile(\"/api/profiles/minecraft\"), this::profiles);\n        addRoute(Method.GET, Pattern.compile(\"/sessionserver/session/minecraft/hasJoined\"), this::hasJoined);\n        addRoute(Method.POST, Pattern.compile(\"/sessionserver/session/minecraft/join\"), this::joinServer);\n        addRoute(Method.GET, Pattern.compile(\"/sessionserver/session/minecraft/profile/(?<uuid>[a-f0-9]{32})\"), this::profile);\n        addRoute(Method.GET, Pattern.compile(\"/textures/(?<hash>[a-f0-9]{64})\"), this::texture);\n    }\n\n    private Response root(Request request) {\n        return ok(mapOf(\n                pair(\"signaturePublickey\", KeyUtils.toPEMPublicKey(getSignaturePublicKey())),\n                pair(\"skinDomains\", Arrays.asList(\n                        \"127.0.0.1\",\n                        \"localhost\"\n                )),\n                pair(\"meta\", mapOf(\n                        pair(\"serverName\", \"HMCL\"),\n                        pair(\"implementationName\", \"HMCL\"),\n                        pair(\"implementationVersion\", \"1.0\"),\n                        pair(\"feature.non_email_login\", true)\n                ))\n        ));\n    }\n\n    private Response status(Request request) {\n        return ok(mapOf(\n                pair(\"user.count\", charactersByUuid.size()),\n                pair(\"token.count\", 0),\n                pair(\"pendingAuthentication.count\", 0)\n        ));\n    }\n\n    private Response profiles(Request request) throws IOException {\n        List<String> names = JsonUtils.fromNonNullJsonFully(request.getSession().getInputStream(), listTypeOf(String.class));\n        return ok(names.stream().distinct()\n                .map(this::findCharacterByName)\n                .flatMap(Lang::toStream)\n                .map(Character::toSimpleResponse)\n                .collect(Collectors.toList()));\n    }\n\n    private Response hasJoined(Request request) {\n        if (!request.getQuery().containsKey(\"username\")) {\n            return badRequest();\n        }\n\n        Optional<Character> character = findCharacterByName(request.getQuery().get(\"username\"));\n\n        //Workaround for JDK-8138667\n        //noinspection OptionalIsPresent\n        if (character.isPresent()) {\n            return ok(character.get().toCompleteResponse(getRootUrl()));\n        } else {\n            return HttpServer.noContent();\n        }\n    }\n\n    private Response joinServer(Request request) {\n        return noContent();\n    }\n\n    private Response profile(Request request) {\n        String uuid = request.getPathVariables().group(\"uuid\");\n\n        Optional<Character> character = findCharacterByUuid(UUIDTypeAdapter.fromString(uuid));\n\n        //Workaround for JDK-8138667\n        //noinspection OptionalIsPresent\n        if (character.isPresent()) {\n            return ok(character.get().toCompleteResponse(getRootUrl()));\n        } else {\n            return HttpServer.noContent();\n        }\n    }\n\n    private Response texture(Request request) {\n        String hash = request.getPathVariables().group(\"hash\");\n\n        if (Texture.hasTexture(hash)) {\n            Texture texture = Texture.getTexture(hash);\n            byte[] data = PNGJavaFXUtils.writeImageToArray(texture.getImage());\n            Response response = newFixedLengthResponse(Response.Status.OK, \"image/png\", new ByteArrayInputStream(data), data.length);\n            response.addHeader(\"Etag\", String.format(\"\\\"%s\\\"\", hash));\n            response.addHeader(\"Cache-Control\", \"max-age=2592000, public\");\n            return response;\n        } else {\n            return notFound();\n        }\n    }\n\n    private Optional<Character> findCharacterByUuid(UUID uuid) {\n        return Optional.ofNullable(charactersByUuid.get(uuid));\n    }\n\n    private Optional<Character> findCharacterByName(String uuid) {\n        return Optional.ofNullable(charactersByName.get(uuid));\n    }\n\n    public void addCharacter(Character character) {\n        charactersByUuid.put(character.getUUID(), character);\n        charactersByName.put(character.getName(), character);\n    }\n\n    public static class Character {\n        private final UUID uuid;\n        private final String name;\n        private final Skin.LoadedSkin skin;\n\n        public Character(UUID uuid, String name, Skin.LoadedSkin skin) {\n            this.uuid = uuid;\n            this.name = name;\n            this.skin = skin;\n        }\n\n        public UUID getUUID() {\n            return uuid;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public GameProfile toSimpleResponse() {\n            return new GameProfile(uuid, name);\n        }\n\n        public Object toCompleteResponse(String rootUrl) {\n            Map<String, Object> realTextures = new HashMap<>();\n            if (skin != null && skin.getSkin() != null) {\n                if (skin.getModel() == TextureModel.SLIM) {\n                    realTextures.put(\"SKIN\", mapOf(\n                            pair(\"url\", rootUrl + \"/textures/\" + skin.getSkin().getHash()),\n                            pair(\"metadata\", mapOf(\n                                    pair(\"model\", \"slim\")\n                            ))));\n                } else {\n                    realTextures.put(\"SKIN\", mapOf(pair(\"url\", rootUrl + \"/textures/\" + skin.getSkin().getHash())));\n                }\n            }\n            if (skin != null && skin.getCape() != null) {\n                realTextures.put(\"CAPE\", mapOf(pair(\"url\", rootUrl + \"/textures/\" + skin.getCape().getHash())));\n            }\n\n            Map<String, Object> textureResponse = mapOf(\n                    pair(\"timestamp\", System.currentTimeMillis()),\n                    pair(\"profileId\", uuid),\n                    pair(\"profileName\", name),\n                    pair(\"textures\", realTextures)\n            );\n\n            return mapOf(\n                    pair(\"id\", uuid),\n                    pair(\"name\", name),\n                    pair(\"properties\", properties(true,\n                            pair(\"textures\", new String(\n                                    Base64.getEncoder().encode(\n                                            JsonUtils.GSON.toJson(textureResponse).getBytes(UTF_8)\n                                    ), UTF_8))))\n            );\n        }\n    }\n\n    // === Signature ===\n\n    private static final KeyPair keyPair = KeyUtils.generateKey();\n\n    public static PublicKey getSignaturePublicKey() {\n        return keyPair.getPublic();\n    }\n\n    private static String sign(String data) {\n        try {\n            Signature signature = Signature.getInstance(\"SHA1withRSA\");\n            signature.initSign(keyPair.getPrivate(), new SecureRandom());\n            signature.update(data.getBytes(UTF_8));\n            return Base64.getEncoder().encodeToString(signature.sign());\n        } catch (GeneralSecurityException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    // === properties ===\n\n    @SafeVarargs\n    public static List<?> properties(boolean sign, Pair<String, String>... entries) {\n        return Stream.of(entries)\n                .map(entry -> {\n                    LinkedHashMap<String, String> property = new LinkedHashMap<>();\n                    property.put(\"name\", entry.getKey());\n                    property.put(\"value\", entry.getValue());\n                    if (sign) {\n                        property.put(\"signature\", sign(entry.getValue()));\n                    }\n                    return property;\n                })\n                .toList();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/CompleteGameProfile.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.UUID;\n\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.JsonAdapter;\n\n/**\n * @author yushijinhun\n */\n@Immutable\npublic class CompleteGameProfile extends GameProfile {\n\n    @JsonAdapter(PropertyMapSerializer.class)\n    private final Map<String, String> properties;\n\n    public CompleteGameProfile(UUID id, String name, Map<String, String> properties) {\n        super(id, name);\n        this.properties = Objects.requireNonNull(properties);\n    }\n\n    public CompleteGameProfile(GameProfile profile, Map<String, String> properties) {\n        this(profile.getId(), profile.getName(), properties);\n    }\n\n    public Map<String, String> getProperties() {\n        return properties;\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        super.validate();\n\n        if (properties == null)\n            throw new JsonParseException(\"Game profile properties cannot be null\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport java.util.Objects;\nimport java.util.UUID;\n\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.JsonAdapter;\n\n/**\n * @author huangyuhui\n */\n@Immutable\npublic class GameProfile implements Validation {\n\n    @JsonAdapter(UUIDTypeAdapter.class)\n    private final UUID id;\n\n    private final String name;\n\n    public GameProfile(UUID id, String name) {\n        this.id = Objects.requireNonNull(id);\n        this.name = Objects.requireNonNull(name);\n    }\n\n    public UUID getId() {\n        return id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        Validation.requireNonNull(id, \"Game profile id cannot be null\");\n        Validation.requireNonNull(name, \"Game profile name cannot be null\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/PropertyMapSerializer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport com.google.gson.*;\n\nimport java.lang.reflect.Type;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport static java.util.Collections.unmodifiableMap;\n\npublic class PropertyMapSerializer implements JsonSerializer<Map<String, String>>, JsonDeserializer<Map<String, String>> {\n\n    @Override\n    public Map<String, String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n        Map<String, String> result = new LinkedHashMap<>();\n        for (JsonElement element : json.getAsJsonArray())\n            if (element instanceof JsonObject) {\n                JsonObject object = (JsonObject) element;\n                result.put(object.get(\"name\").getAsString(), object.get(\"value\").getAsString());\n            }\n\n        return unmodifiableMap(result);\n    }\n\n    @Override\n    public JsonElement serialize(Map<String, String> src, Type typeOfSrc, JsonSerializationContext context) {\n        JsonArray result = new JsonArray();\n        src.forEach((k, v) -> {\n            JsonObject object = new JsonObject();\n            object.addProperty(\"name\", k);\n            object.addProperty(\"value\", v);\n            result.add(object);\n        });\n        return result;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/RemoteAuthenticationException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport org.jackhuang.hmcl.auth.AuthenticationException;\n\npublic class RemoteAuthenticationException extends AuthenticationException {\n\n    private final String name;\n    private final String message;\n    private final String cause;\n\n    public RemoteAuthenticationException(String name, String message, String cause) {\n        super(buildMessage(name, message, cause));\n        this.name = name;\n        this.message = message;\n        this.cause = cause;\n    }\n\n    public String getRemoteName() {\n        return name;\n    }\n\n    public String getRemoteMessage() {\n        return message;\n    }\n\n    public String getRemoteCause() {\n        return cause;\n    }\n\n    private static String buildMessage(String name, String message, String cause) {\n        StringBuilder builder = new StringBuilder(name);\n        if (message != null)\n            builder.append(\": \").append(message);\n\n        if (cause != null)\n            builder.append(\": \").append(cause);\n\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Texture.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Map;\n\n@Immutable\npublic final class Texture {\n\n    private final String url;\n    private final Map<String, String> metadata;\n\n    public Texture() {\n        this(null, null);\n    }\n\n    public Texture(String url, Map<String, String> metadata) {\n        this.url = url;\n        this.metadata = metadata;\n    }\n\n    @Nullable\n    public String getUrl() {\n        return url;\n    }\n\n    @Nullable\n    public Map<String, String> getMetadata() {\n        return metadata;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/TextureModel.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\npublic enum TextureModel {\n    WIDE(\"default\"), SLIM(\"slim\");\n\n    public final String modelName;\n\n    TextureModel(String modelName) {\n        this.modelName = modelName;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/TextureType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\npublic enum TextureType {\n    SKIN, CAPE\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/User.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport com.google.gson.JsonParseException;\n\nimport java.util.Map;\n\nimport com.google.gson.annotations.JsonAdapter;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n *\n * @author huang\n */\n@Immutable\npublic final class User implements Validation {\n\n    private final String id;\n\n    @Nullable\n    @JsonAdapter(PropertyMapSerializer.class)\n    private final Map<String, String> properties;\n\n    public User(String id) {\n        this(id, null);\n    }\n\n    public User(String id, @Nullable Map<String, String> properties) {\n        this.id = id;\n        this.properties = properties;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    @Nullable\n    public Map<String, String> getProperties() {\n        return properties;\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (StringUtils.isBlank(id))\n            throw new JsonParseException(\"User id cannot be empty.\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport javafx.beans.binding.ObjectBinding;\nimport org.jackhuang.hmcl.auth.*;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\nimport org.jackhuang.hmcl.util.javafx.BindingMapping;\n\nimport java.nio.file.Path;\nimport java.util.*;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic abstract class YggdrasilAccount extends ClassicAccount {\n\n    protected final YggdrasilService service;\n    protected final UUID characterUUID;\n    protected final String username;\n\n    private boolean authenticated = false;\n    private YggdrasilSession session;\n\n    protected YggdrasilAccount(YggdrasilService service, String username, YggdrasilSession session) {\n        this.service = requireNonNull(service);\n        this.username = requireNonNull(username);\n        this.characterUUID = requireNonNull(session.getSelectedProfile().getId());\n        this.session = requireNonNull(session);\n\n        addProfilePropertiesListener();\n    }\n\n    protected YggdrasilAccount(YggdrasilService service, String username, String password, CharacterSelector selector) throws AuthenticationException {\n        this.service = requireNonNull(service);\n        this.username = requireNonNull(username);\n\n        YggdrasilSession acquiredSession = service.authenticate(username, password, randomClientToken());\n        if (acquiredSession.getSelectedProfile() == null) {\n            if (acquiredSession.getAvailableProfiles() == null || acquiredSession.getAvailableProfiles().isEmpty()) {\n                throw new NoCharacterException();\n            }\n\n            GameProfile characterToSelect = selector.select(service, acquiredSession.getAvailableProfiles());\n\n            session = service.refresh(\n                    acquiredSession.getAccessToken(),\n                    acquiredSession.getClientToken(),\n                    characterToSelect);\n            // response validity has been checked in refresh()\n        } else {\n            session = acquiredSession;\n        }\n\n        characterUUID = session.getSelectedProfile().getId();\n        authenticated = true;\n\n        addProfilePropertiesListener();\n    }\n\n    private ObjectBinding<Optional<CompleteGameProfile>> profilePropertiesBinding;\n    private void addProfilePropertiesListener() {\n        // binding() is thread-safe\n        // hold the binding so that it won't be garbage-collected\n        profilePropertiesBinding = service.getProfileRepository().binding(characterUUID, true);\n        // and it's safe to add a listener to an ObjectBinding which does not have any listener attached before (maybe tricky)\n        profilePropertiesBinding.addListener((a, b, c) -> this.invalidate());\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getCharacter() {\n        return session.getSelectedProfile().getName();\n    }\n\n    @Override\n    public UUID getUUID() {\n        return session.getSelectedProfile().getId();\n    }\n\n    @Override\n    public String getIdentifier() {\n        return getUsername() + \":\" + getUUID();\n    }\n\n    @Override\n    public synchronized AuthInfo logIn() throws AuthenticationException {\n        if (!authenticated) {\n            if (service.validate(session.getAccessToken(), session.getClientToken())) {\n                authenticated = true;\n            } else {\n                YggdrasilSession acquiredSession;\n                try {\n                    acquiredSession = service.refresh(session.getAccessToken(), session.getClientToken(), null);\n                } catch (RemoteAuthenticationException e) {\n                    if (\"ForbiddenOperationException\".equals(e.getRemoteName())) {\n                        throw new CredentialExpiredException(e);\n                    } else {\n                        throw e;\n                    }\n                }\n                if (acquiredSession.getSelectedProfile() == null ||\n                        !acquiredSession.getSelectedProfile().getId().equals(characterUUID)) {\n                    throw new ServerResponseMalformedException(\"Selected profile changed\");\n                }\n\n                session = acquiredSession;\n\n                authenticated = true;\n                invalidate();\n            }\n        }\n\n        return session.toAuthInfo();\n    }\n\n    @Override\n    public synchronized AuthInfo logInWithPassword(String password) throws AuthenticationException {\n        YggdrasilSession acquiredSession = service.authenticate(username, password, randomClientToken());\n\n        if (acquiredSession.getSelectedProfile() == null) {\n            if (acquiredSession.getAvailableProfiles() == null || acquiredSession.getAvailableProfiles().isEmpty()) {\n                throw new CharacterDeletedException();\n            }\n\n            GameProfile characterToSelect = acquiredSession.getAvailableProfiles().stream()\n                    .filter(charatcer -> charatcer.getId().equals(characterUUID))\n                    .findFirst()\n                    .orElseThrow(CharacterDeletedException::new);\n\n            session = service.refresh(\n                    acquiredSession.getAccessToken(),\n                    acquiredSession.getClientToken(),\n                    characterToSelect);\n\n        } else {\n            if (!acquiredSession.getSelectedProfile().getId().equals(characterUUID)) {\n                throw new CharacterDeletedException();\n            }\n            session = acquiredSession;\n        }\n\n        authenticated = true;\n        invalidate();\n        return session.toAuthInfo();\n    }\n\n    @Override\n    public AuthInfo playOffline() throws AuthenticationException {\n        return session.toAuthInfo();\n    }\n\n    @Override\n    public Map<Object, Object> toStorage() {\n        Map<Object, Object> storage = new HashMap<>();\n        storage.put(\"username\", username);\n        storage.putAll(session.toStorage());\n        service.getProfileRepository().getImmediately(characterUUID).ifPresent(profile ->\n                storage.put(\"profileProperties\", profile.getProperties()));\n        return storage;\n    }\n\n    public YggdrasilService getYggdrasilService() {\n        return service;\n    }\n\n    @Override\n    public void clearCache() {\n        authenticated = false;\n        service.getProfileRepository().invalidate(characterUUID);\n    }\n\n    @Override\n    public ObjectBinding<Optional<Map<TextureType, Texture>>> getTextures() {\n        return BindingMapping.of(service.getProfileRepository().binding(getUUID()))\n                .map(profile -> profile.flatMap(it -> {\n                    try {\n                        return YggdrasilService.getTextures(it);\n                    } catch (ServerResponseMalformedException e) {\n                        LOG.warning(\"Failed to parse texture payload\", e);\n                        return Optional.empty();\n                    }\n                }));\n\n    }\n\n    @Override\n    public boolean canUploadSkin() {\n        return true;\n    }\n\n    @Override\n    public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {\n        service.uploadSkin(characterUUID, session.getAccessToken(), isSlim, file);\n    }\n\n    private static String randomClientToken() {\n        return UUIDTypeAdapter.fromUUID(UUID.randomUUID());\n    }\n\n    @Override\n    public String toString() {\n        return \"YggdrasilAccount[uuid=\" + characterUUID + \", username=\" + username + \"]\";\n    }\n\n    @Override\n    public int hashCode() {\n        return characterUUID.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj) return true;\n        if (obj == null || obj.getClass() != YggdrasilAccount.class)\n            return false;\n        YggdrasilAccount another = (YggdrasilAccount) obj;\n        return isPortable() == another.isPortable() && characterUUID.equals(another.characterUUID);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport org.jackhuang.hmcl.auth.AuthenticationException;\n\nimport java.net.URI;\nimport java.util.UUID;\n\n/**\n * @see <a href=\"http://wiki.vg\">http://wiki.vg</a>\n */\npublic interface YggdrasilProvider {\n\n    URI getAuthenticationURL() throws AuthenticationException;\n\n    URI getRefreshmentURL() throws AuthenticationException;\n\n    URI getValidationURL() throws AuthenticationException;\n\n    URI getInvalidationURL() throws AuthenticationException;\n\n    /**\n     * URL to upload skin.\n     *\n     * Headers:\n     *     Authentication: Bearer &lt;access token&gt;\n     *\n     * Payload:\n     *     The payload for this API consists of multipart form data. There are two parts (order does not matter b/c of boundary):\n     *     model: Empty string for the default model and \"slim\" for the slim model\n     *     file: Raw image file data\n     *\n     * @see <a href=\"https://wiki.vg/Mojang_API#Upload_Skin\">https://wiki.vg/Mojang_API#Upload_Skin</a>\n     * @return url to upload skin\n     * @throws AuthenticationException if url cannot be generated. e.g. some parameter or query is malformed.\n     * @throws UnsupportedOperationException if the Yggdrasil provider does not support third-party skin uploading.\n     */\n    URI getSkinUploadURL(UUID uuid) throws AuthenticationException, UnsupportedOperationException;\n\n    URI getProfilePropertiesURL(UUID uuid) throws AuthenticationException;\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.auth.AuthenticationException;\nimport org.jackhuang.hmcl.auth.ServerDisconnectException;\nimport org.jackhuang.hmcl.auth.ServerResponseMalformedException;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\nimport org.jackhuang.hmcl.util.gson.ValidationTypeAdapterFactory;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.HttpMultipartRequest;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static java.util.Collections.unmodifiableList;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Lang.threadPool;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\npublic class YggdrasilService {\n\n    private static final ThreadPoolExecutor POOL = threadPool(\"YggdrasilProfileProperties\", true, 2, 10, TimeUnit.SECONDS);\n\n    private final YggdrasilProvider provider;\n    private final ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository;\n\n    public YggdrasilService(YggdrasilProvider provider) {\n        this.provider = provider;\n        this.profileRepository = new ObservableOptionalCache<>(\n                uuid -> {\n                    LOG.info(\"Fetching properties of \" + uuid + \" from \" + provider);\n                    return getCompleteGameProfile(uuid);\n                },\n                (uuid, e) -> LOG.warning(\"Failed to fetch properties of \" + uuid + \" from \" + provider, e),\n                POOL);\n    }\n\n    public ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> getProfileRepository() {\n        return profileRepository;\n    }\n\n    public YggdrasilSession authenticate(String username, String password, String clientToken) throws AuthenticationException {\n        Objects.requireNonNull(username);\n        Objects.requireNonNull(password);\n        Objects.requireNonNull(clientToken);\n\n        Map<String, Object> request = new HashMap<>();\n        request.put(\"agent\", mapOf(\n                pair(\"name\", \"Minecraft\"),\n                pair(\"version\", 1)\n        ));\n        request.put(\"username\", username);\n        request.put(\"password\", password);\n        request.put(\"clientToken\", clientToken);\n        request.put(\"requestUser\", true);\n\n        return handleAuthenticationResponse(request(provider.getAuthenticationURL(), request), clientToken);\n    }\n\n    private static Map<String, Object> createRequestWithCredentials(String accessToken, String clientToken) {\n        Map<String, Object> request = new HashMap<>();\n        request.put(\"accessToken\", accessToken);\n        request.put(\"clientToken\", clientToken);\n        return request;\n    }\n\n    public YggdrasilSession refresh(String accessToken, String clientToken, GameProfile characterToSelect) throws AuthenticationException {\n        Objects.requireNonNull(accessToken);\n        Objects.requireNonNull(clientToken);\n\n        Map<String, Object> request = createRequestWithCredentials(accessToken, clientToken);\n        request.put(\"requestUser\", true);\n\n        if (characterToSelect != null) {\n            request.put(\"selectedProfile\", mapOf(\n                    pair(\"id\", characterToSelect.getId()),\n                    pair(\"name\", characterToSelect.getName())));\n        }\n\n        YggdrasilSession response = handleAuthenticationResponse(request(provider.getRefreshmentURL(), request), clientToken);\n\n        if (characterToSelect != null) {\n            if (response.getSelectedProfile() == null ||\n                    !response.getSelectedProfile().getId().equals(characterToSelect.getId())) {\n                throw new ServerResponseMalformedException(\"Failed to select character\");\n            }\n        }\n\n        return response;\n    }\n\n    public boolean validate(String accessToken) throws AuthenticationException {\n        return validate(accessToken, null);\n    }\n\n    public boolean validate(String accessToken, String clientToken) throws AuthenticationException {\n        Objects.requireNonNull(accessToken);\n\n        try {\n            requireEmpty(request(provider.getValidationURL(), createRequestWithCredentials(accessToken, clientToken)));\n            return true;\n        } catch (RemoteAuthenticationException e) {\n            if (\"ForbiddenOperationException\".equals(e.getRemoteName())) {\n                return false;\n            }\n            throw e;\n        }\n    }\n\n    public void invalidate(String accessToken) throws AuthenticationException {\n        invalidate(accessToken, null);\n    }\n\n    public void invalidate(String accessToken, String clientToken) throws AuthenticationException {\n        Objects.requireNonNull(accessToken);\n\n        requireEmpty(request(provider.getInvalidationURL(), createRequestWithCredentials(accessToken, clientToken)));\n    }\n\n    public void uploadSkin(UUID uuid, String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {\n        try {\n            HttpURLConnection con = NetworkUtils.createHttpConnection(provider.getSkinUploadURL(uuid));\n            con.setRequestMethod(\"PUT\");\n            con.setRequestProperty(\"Authorization\", \"Bearer \" + accessToken);\n            con.setDoOutput(true);\n            try (HttpMultipartRequest request = new HttpMultipartRequest(con)) {\n                request.param(\"model\", isSlim ? \"slim\" : \"\");\n                try (InputStream fis = Files.newInputStream(file)) {\n                    request.file(\"file\", FileUtils.getName(file), \"image/\" + FileUtils.getExtension(file), fis);\n                }\n            }\n            requireEmpty(NetworkUtils.readFullyAsString(con));\n        } catch (IOException e) {\n            throw new AuthenticationException(e);\n        }\n    }\n\n    /**\n     * Get complete game profile.\n     *\n     * Game profile provided from authentication is not complete (no skin data in properties).\n     *\n     * @param uuid the uuid that the character corresponding to.\n     * @return the complete game profile(filled with more properties)\n     */\n    public Optional<CompleteGameProfile> getCompleteGameProfile(UUID uuid) throws AuthenticationException {\n        Objects.requireNonNull(uuid);\n\n        return Optional.ofNullable(fromJson(request(provider.getProfilePropertiesURL(uuid), null), CompleteGameProfile.class));\n    }\n\n    public static Optional<Map<TextureType, Texture>> getTextures(CompleteGameProfile profile) throws ServerResponseMalformedException {\n        Objects.requireNonNull(profile);\n\n        String encodedTextures = profile.getProperties().get(\"textures\");\n\n        if (encodedTextures != null) {\n            byte[] decodedBinary;\n            try {\n                decodedBinary = Base64.getDecoder().decode(encodedTextures);\n            } catch (IllegalArgumentException e) {\n                throw new ServerResponseMalformedException(e);\n            }\n            TextureResponse texturePayload = fromJson(new String(decodedBinary, UTF_8), TextureResponse.class);\n            return Optional.ofNullable(texturePayload.textures);\n        } else {\n            return Optional.empty();\n        }\n    }\n\n    private static YggdrasilSession handleAuthenticationResponse(String responseText, String clientToken) throws AuthenticationException {\n        AuthenticationResponse response = fromJson(responseText, AuthenticationResponse.class);\n        handleErrorMessage(response);\n\n        if (!clientToken.equals(response.clientToken))\n            throw new AuthenticationException(\"Client token changed from \" + clientToken + \" to \" + response.clientToken);\n\n        return new YggdrasilSession(\n                response.clientToken,\n                response.accessToken,\n                response.selectedProfile,\n                response.availableProfiles == null ? null : unmodifiableList(response.availableProfiles),\n                response.user == null ? null : response.user.getProperties());\n    }\n\n    private static void requireEmpty(String response) throws AuthenticationException {\n        if (StringUtils.isBlank(response))\n            return;\n\n        handleErrorMessage(fromJson(response, ErrorResponse.class));\n    }\n\n    private static void handleErrorMessage(ErrorResponse response) throws AuthenticationException {\n        if (!StringUtils.isBlank(response.error)) {\n            throw new RemoteAuthenticationException(response.error, response.errorMessage, response.cause);\n        }\n    }\n\n    private static String request(URI uri, Object payload) throws AuthenticationException {\n        try {\n            if (payload == null)\n                return NetworkUtils.doGet(uri);\n            else\n                return NetworkUtils.doPost(uri, payload instanceof String ? (String) payload : GSON.toJson(payload), \"application/json\");\n        } catch (IOException e) {\n            throw new ServerDisconnectException(e);\n        }\n    }\n\n    private static <T> T fromJson(String text, Class<T> typeOfT) throws ServerResponseMalformedException {\n        try {\n            return GSON.fromJson(text, typeOfT);\n        } catch (JsonParseException e) {\n            throw new ServerResponseMalformedException(text, e);\n        }\n    }\n\n    private final static class TextureResponse {\n        public Map<TextureType, Texture> textures;\n    }\n\n    private final static class AuthenticationResponse extends ErrorResponse {\n        public String accessToken;\n        public String clientToken;\n        public GameProfile selectedProfile;\n        public List<GameProfile> availableProfiles;\n        public User user;\n    }\n\n    private static class ErrorResponse {\n        public String error;\n        public String errorMessage;\n        public String cause;\n    }\n\n    private static final Gson GSON = new GsonBuilder()\n            .registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)\n            .registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)\n            .create();\n\n    public static final String PURCHASE_URL = \"https://www.xbox.com/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj\";\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilSession.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.auth.yggdrasil;\n\nimport com.google.gson.Gson;\nimport org.jackhuang.hmcl.auth.AuthInfo;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.logging.Logger;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Lang.tryCast;\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\n@Immutable\npublic class YggdrasilSession {\n\n    private final String clientToken;\n    private final String accessToken;\n    private final GameProfile selectedProfile;\n    private final List<GameProfile> availableProfiles;\n    @Nullable\n    private final Map<String, String> userProperties;\n\n    public YggdrasilSession(String clientToken, String accessToken, GameProfile selectedProfile, List<GameProfile> availableProfiles, Map<String, String> userProperties) {\n        this.clientToken = clientToken;\n        this.accessToken = accessToken;\n        this.selectedProfile = selectedProfile;\n        this.availableProfiles = availableProfiles;\n        this.userProperties = userProperties;\n\n        if (accessToken != null) Logger.registerAccessToken(accessToken);\n    }\n\n    public String getClientToken() {\n        return clientToken;\n    }\n\n    public String getAccessToken() {\n        return accessToken;\n    }\n\n    /**\n     * @return nullable (null if no character is selected)\n     */\n    public GameProfile getSelectedProfile() {\n        return selectedProfile;\n    }\n\n    /**\n     * @return nullable (null if the YggdrasilSession is loaded from storage)\n     */\n    public List<GameProfile> getAvailableProfiles() {\n        return availableProfiles;\n    }\n\n    public Map<String, String> getUserProperties() {\n        return userProperties;\n    }\n\n    public static YggdrasilSession fromStorage(Map<?, ?> storage) {\n        Objects.requireNonNull(storage);\n\n        UUID uuid = tryCast(storage.get(\"uuid\"), String.class).map(UUIDTypeAdapter::fromString).orElseThrow(() -> new IllegalArgumentException(\"uuid is missing\"));\n        String name = tryCast(storage.get(\"displayName\"), String.class).orElseThrow(() -> new IllegalArgumentException(\"displayName is missing\"));\n        String clientToken = tryCast(storage.get(\"clientToken\"), String.class).orElseThrow(() -> new IllegalArgumentException(\"clientToken is missing\"));\n        String accessToken = tryCast(storage.get(\"accessToken\"), String.class).orElseThrow(() -> new IllegalArgumentException(\"accessToken is missing\"));\n        @SuppressWarnings(\"unchecked\")\n        Map<String, String> userProperties = tryCast(storage.get(\"userProperties\"), Map.class).orElse(null);\n        return new YggdrasilSession(clientToken, accessToken, new GameProfile(uuid, name), null, userProperties);\n    }\n\n    public Map<Object, Object> toStorage() {\n        if (selectedProfile == null)\n            throw new IllegalStateException(\"No character is selected\");\n\n        return mapOf(\n                pair(\"clientToken\", clientToken),\n                pair(\"accessToken\", accessToken),\n                pair(\"uuid\", UUIDTypeAdapter.fromUUID(selectedProfile.getId())),\n                pair(\"displayName\", selectedProfile.getName()),\n                pair(\"userProperties\", userProperties));\n    }\n\n    public AuthInfo toAuthInfo() {\n        if (selectedProfile == null)\n            throw new IllegalStateException(\"No character is selected\");\n\n        return new AuthInfo(selectedProfile.getName(), selectedProfile.getId(), accessToken, AuthInfo.USER_TYPE_MSA,\n                Optional.ofNullable(userProperties)\n                        .map(properties -> properties.entrySet().stream()\n                                .collect(Collectors.toMap(Map.Entry::getKey,\n                                        e -> Collections.singleton(e.getValue()))))\n                        .map(GSON_PROPERTIES::toJson).orElse(\"{}\"));\n    }\n\n    private static final Gson GSON_PROPERTIES = new Gson();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/AbstractDependencyManager.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\n/**\n *\n * @author huangyuhui\n */\npublic abstract class AbstractDependencyManager implements DependencyManager {\n\n    public abstract DownloadProvider getDownloadProvider();\n\n    @Override\n    public abstract DefaultCacheRepository getCacheRepository();\n\n    @Override\n    public VersionList<?> getVersionList(String id) {\n        return getDownloadProvider().getVersionListById(id);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/ArtifactMalformedException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport java.io.IOException;\n\npublic class ArtifactMalformedException extends IOException {\n    public ArtifactMalformedException(String message) {\n        super(message);\n    }\n\n    public ArtifactMalformedException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/AutoDownloadProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport java.net.URI;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.function.Function;\n\n/// @author huangyuhui\npublic final class AutoDownloadProvider implements DownloadProvider {\n    private final List<DownloadProvider> versionListProviders;\n    private final List<DownloadProvider> fileProviders;\n    private final ConcurrentMap<String, VersionList<?>> versionLists = new ConcurrentHashMap<>();\n\n    public AutoDownloadProvider(\n            List<DownloadProvider> versionListProviders,\n            List<DownloadProvider> fileProviders) {\n        if (versionListProviders == null || versionListProviders.isEmpty()) {\n            throw new IllegalArgumentException(\"versionListProviders must not be null or empty\");\n        }\n\n        if (fileProviders == null || fileProviders.isEmpty()) {\n            throw new IllegalArgumentException(\"fileProviders must not be null or empty\");\n        }\n\n        this.versionListProviders = versionListProviders;\n        this.fileProviders = fileProviders;\n    }\n\n    public AutoDownloadProvider(DownloadProvider... downloadProviderCandidate) {\n        if (downloadProviderCandidate.length == 0) {\n            throw new IllegalArgumentException(\"Download provider must have at least one download provider\");\n        }\n\n        this.versionListProviders = List.of(downloadProviderCandidate);\n        this.fileProviders = versionListProviders;\n    }\n\n    private DownloadProvider getPreferredDownloadProvider() {\n        return fileProviders.get(0);\n    }\n\n    private static List<URI> getAll(\n            List<DownloadProvider> providers,\n            Function<DownloadProvider, List<URI>> function) {\n        LinkedHashSet<URI> result = new LinkedHashSet<>();\n        for (DownloadProvider provider : providers) {\n            result.addAll(function.apply(provider));\n        }\n        return List.copyOf(result);\n    }\n\n    @Override\n    public List<URI> getVersionListURLs() {\n        return getAll(versionListProviders, DownloadProvider::getVersionListURLs);\n    }\n\n    @Override\n    public String injectURL(String baseURL) {\n        return getPreferredDownloadProvider().injectURL(baseURL);\n    }\n\n    @Override\n    public List<URI> getAssetObjectCandidates(String assetObjectLocation) {\n        return getAll(fileProviders, provider -> provider.getAssetObjectCandidates(assetObjectLocation));\n    }\n\n    @Override\n    public List<URI> injectURLWithCandidates(String baseURL) {\n        return getAll(fileProviders, provider -> provider.injectURLWithCandidates(baseURL));\n    }\n\n    @Override\n    public List<URI> injectURLsWithCandidates(List<String> urls) {\n        return getAll(fileProviders, provider -> provider.injectURLsWithCandidates(urls));\n    }\n\n    @Override\n    public VersionList<?> getVersionListById(String id) {\n        return versionLists.computeIfAbsent(id, value -> {\n            VersionList<?>[] lists = new VersionList<?>[versionListProviders.size()];\n            for (int i = 0; i < versionListProviders.size(); i++) {\n                lists[i] = versionListProviders.get(i).getVersionListById(value);\n            }\n            return new MultipleSourceVersionList(lists);\n        });\n    }\n\n    @Override\n    public int getConcurrency() {\n        return getPreferredDownloadProvider().getConcurrency();\n    }\n\n    @Override\n    public String toString() {\n        return \"AutoDownloadProvider[versionListProviders=%s, fileProviders=%s]\".formatted(versionListProviders, fileProviders);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.download.cleanroom.CleanroomVersionList;\nimport org.jackhuang.hmcl.download.fabric.FabricAPIVersionList;\nimport org.jackhuang.hmcl.download.fabric.FabricVersionList;\nimport org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList;\nimport org.jackhuang.hmcl.download.game.GameVersionList;\nimport org.jackhuang.hmcl.download.legacyfabric.LegacyFabricAPIVersionList;\nimport org.jackhuang.hmcl.download.legacyfabric.LegacyFabricVersionList;\nimport org.jackhuang.hmcl.download.liteloader.LiteLoaderBMCLVersionList;\nimport org.jackhuang.hmcl.download.neoforge.NeoForgeBMCLVersionList;\nimport org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;\nimport org.jackhuang.hmcl.download.quilt.QuiltAPIVersionList;\nimport org.jackhuang.hmcl.download.quilt.QuiltVersionList;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.net.URI;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\n/**\n *\n * @author huang\n */\npublic final class BMCLAPIDownloadProvider implements DownloadProvider {\n    private final String apiRoot;\n    private final GameVersionList game;\n    private final FabricVersionList fabric;\n    private final FabricAPIVersionList fabricApi;\n    private final ForgeBMCLVersionList forge;\n    private final CleanroomVersionList cleanroom;\n    private final LegacyFabricVersionList legacyFabric;\n    private final LegacyFabricAPIVersionList legacyFabricApi;\n    private final NeoForgeBMCLVersionList neoforge;\n    private final LiteLoaderBMCLVersionList liteLoader;\n    private final OptiFineBMCLVersionList optifine;\n    private final QuiltVersionList quilt;\n    private final QuiltAPIVersionList quiltApi;\n    private final List<Pair<String, String>> replacement;\n    private final List<Pair<String, String>> fallbackReplacement;\n\n    public BMCLAPIDownloadProvider(String apiRoot) {\n        this.apiRoot = apiRoot;\n        this.game = new GameVersionList(this);\n        this.fabric = new FabricVersionList(this);\n        this.fabricApi = new FabricAPIVersionList(this);\n        this.forge = new ForgeBMCLVersionList(apiRoot);\n        this.cleanroom = new CleanroomVersionList(this);\n        this.neoforge = new NeoForgeBMCLVersionList(apiRoot);\n        this.liteLoader = new LiteLoaderBMCLVersionList(this);\n        this.optifine = new OptiFineBMCLVersionList(apiRoot);\n        this.quilt = new QuiltVersionList(this);\n        this.quiltApi = new QuiltAPIVersionList(this);\n        this.legacyFabric = new LegacyFabricVersionList(this);\n        this.legacyFabricApi = new LegacyFabricAPIVersionList(this);\n\n        this.replacement = List.of(\n                pair(\"https://bmclapi2.bangbang93.com\", apiRoot),\n                pair(\"https://launchermeta.mojang.com\", apiRoot),\n                pair(\"https://piston-meta.mojang.com\", apiRoot),\n                pair(\"https://piston-data.mojang.com\", apiRoot),\n                pair(\"https://launcher.mojang.com\", apiRoot),\n                pair(\"https://libraries.minecraft.net\", apiRoot + \"/libraries\"),\n                pair(\"http://files.minecraftforge.net/maven\", apiRoot + \"/maven\"),\n                pair(\"https://files.minecraftforge.net/maven\", apiRoot + \"/maven\"),\n                pair(\"https://maven.minecraftforge.net\", apiRoot + \"/maven\"),\n                pair(\"https://maven.neoforged.net/releases/\", apiRoot + \"/maven/\"),\n                pair(\"http://dl.liteloader.com/versions/versions.json\", apiRoot + \"/maven/com/mumfrey/liteloader/versions.json\"),\n                pair(\"http://dl.liteloader.com/versions\", apiRoot + \"/maven\"),\n                pair(\"https://meta.fabricmc.net\", apiRoot + \"/fabric-meta\"),\n                pair(\"https://maven.fabricmc.net\", apiRoot + \"/maven\"),\n                pair(\"https://authlib-injector.yushi.moe\", apiRoot + \"/mirrors/authlib-injector\"),\n                pair(\"https://repo1.maven.org/maven2\", \"https://mirrors.cloud.tencent.com/nexus/repository/maven-public\"),\n                pair(\"https://repo.maven.apache.org/maven2\", \"https://mirrors.cloud.tencent.com/nexus/repository/maven-public\"),\n                pair(\"https://hmcl.glavo.site/metadata/cleanroom\", \"https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/cleanroom\"),\n                pair(\"https://hmcl.glavo.site/metadata/fmllibs\", \"https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/fmllibs\"),\n                pair(\"https://zkitefly.github.io/unlisted-versions-of-minecraft\", \"https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto\")\n        );\n\n        this.fallbackReplacement = List.of(\n                // https://github.com/mcmod-info-mirror/mcim-rust-api\n                pair(\"https://api.modrinth.com\", \"https://mod.mcimirror.top/modrinth\"),\n                pair(\"https://cdn.modrinth.com\", \"https://mod.mcimirror.top\"),\n                pair(\"https://api.curseforge.com\", \"https://mod.mcimirror.top/curseforge\"),\n                pair(\"https://edge.forgecdn.net\", \"https://mod.mcimirror.top\")\n        );\n    }\n\n    public String getApiRoot() {\n        return apiRoot;\n    }\n\n    @Override\n    public List<URI> getVersionListURLs() {\n        return List.of(URI.create(apiRoot + \"/mc/game/version_manifest.json\"));\n    }\n\n    @Override\n    public List<URI> getAssetObjectCandidates(String assetObjectLocation) {\n        return List.of(NetworkUtils.toURI(apiRoot + \"/assets/\" + assetObjectLocation));\n    }\n\n    @Override\n    public VersionList<?> getVersionListById(String id) {\n        return switch (id) {\n            case \"game\" -> game;\n            case \"fabric\" -> fabric;\n            case \"fabric-api\" -> fabricApi;\n            case \"forge\" -> forge;\n            case \"cleanroom\" -> cleanroom;\n            case \"neoforge\" -> neoforge;\n            case \"liteloader\" -> liteLoader;\n            case \"optifine\" -> optifine;\n            case \"quilt\" -> quilt;\n            case \"quilt-api\" -> quiltApi;\n            case \"legacyfabric\" -> legacyFabric;\n            case \"legacyfabric-api\" -> legacyFabricApi;\n            default -> throw new IllegalArgumentException(\"Unrecognized version list id: \" + id);\n        };\n    }\n\n    private static String injectURL(List<Pair<String, String>> replacement, String baseURL) {\n        for (Pair<String, String> pair : replacement) {\n            if (baseURL.startsWith(pair.getKey())) {\n                return pair.getValue() + baseURL.substring(pair.getKey().length());\n            }\n        }\n        return baseURL;\n    }\n\n    @Override\n    public String injectURL(String baseURL) {\n        return injectURL(replacement, baseURL);\n    }\n\n    @Override\n    public List<URI> injectURLWithCandidates(String baseURL) {\n        String injected = injectURL(replacement, baseURL);\n        if (injected.equals(baseURL)) {\n            String fallbackInjected = injectURL(fallbackReplacement, baseURL);\n            if (fallbackInjected.equals(baseURL)) {\n                return List.of(NetworkUtils.toURI(baseURL));\n            } else {\n                return List.of(\n                        NetworkUtils.toURI(baseURL),\n                        NetworkUtils.toURI(fallbackInjected)\n                );\n            }\n        } else {\n            return List.of(NetworkUtils.toURI(injected));\n        }\n    }\n\n    @Override\n    public int getConcurrency() {\n        return Math.max(Runtime.getRuntime().availableProcessors() * 2, 6);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultCacheRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.game.LibraryDownloadTask;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.LibraryDownloadInfo;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.BufferedWriter;\nimport java.io.IOException;\nimport java.io.OutputStreamWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.locks.Lock;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class DefaultCacheRepository extends CacheRepository {\n    private Path librariesDir;\n    private Path indexFile;\n    private Index index = null;\n\n    public DefaultCacheRepository() {\n        this(OperatingSystem.getWorkingDirectory(\"minecraft\"));\n    }\n\n    public DefaultCacheRepository(Path commonDirectory) {\n        changeDirectory(commonDirectory);\n    }\n\n    @Override\n    public void changeDirectory(Path commonDir) {\n        super.changeDirectory(commonDir);\n\n        librariesDir = commonDir.resolve(\"libraries\");\n        indexFile = getCacheDirectory().resolve(\"index.json\");\n\n        lock.writeLock().lock();\n        try {\n            if (Files.isRegularFile(indexFile)) {\n                index = JsonUtils.fromJsonFile(indexFile, Index.class);\n                if (index == null) {\n                    throw new JsonParseException(\"Index file is empty or invalid\");\n                }\n            } else {\n                index = new Index();\n            }\n        } catch (Exception e) {\n            LOG.warning(\"Unable to read index file\", e);\n            index = new Index();\n        } finally {\n            lock.writeLock().unlock();\n        }\n    }\n\n    /**\n     * Try to cache the library given.\n     * This library will be cached only if it is verified.\n     * If cannot be verified, the library will not be cached.\n     *\n     * @param library the library being cached\n     * @param jar     the file of library\n     */\n    public void tryCacheLibrary(Library library, Path jar) {\n        lock.readLock().lock();\n        try {\n            if (index.getLibraries().stream().anyMatch(it -> library.getName().equals(it.getName())))\n                return;\n        } finally {\n            lock.readLock().unlock();\n        }\n\n        try {\n            LibraryDownloadInfo info = library.getDownload();\n            String hash = info.getSha1();\n            if (hash != null) {\n                String checksum = DigestUtils.digestToString(\"SHA-1\", jar);\n                if (hash.equalsIgnoreCase(checksum))\n                    cacheLibrary(library, jar, false);\n            } else if (library.getChecksums() != null && !library.getChecksums().isEmpty()) {\n                if (LibraryDownloadTask.checksumValid(jar, library.getChecksums()))\n                    cacheLibrary(library, jar, true);\n            } else {\n                // or we will not cache the library\n            }\n        } catch (IOException e) {\n            LOG.warning(\"Unable to calc hash value of file \" + jar, e);\n        }\n    }\n\n    /**\n     * Get the path of cached library, empty if not cached\n     *\n     * @param library the library we check if cached.\n     * @return the cached path if exists, otherwise empty\n     */\n    public Optional<Path> getLibrary(Library library) {\n        LibraryDownloadInfo info = library.getDownload();\n        String hash = info.getSha1();\n\n        if (fileExists(SHA1, hash))\n            return Optional.of(getFile(SHA1, hash));\n\n        Lock readLock = lock.readLock();\n        readLock.lock();\n\n        try {\n            // check if this library is from Forge\n            List<LibraryIndex> libraries = index.getLibraries().stream()\n                    .filter(it -> it.getName().equals(library.getName()))\n                    .collect(Collectors.toList());\n            for (LibraryIndex libIndex : libraries) {\n                if (fileExists(SHA1, libIndex.getHash())) {\n                    Path file = getFile(SHA1, libIndex.getHash());\n                    if (libIndex.getType().equalsIgnoreCase(LibraryIndex.TYPE_FORGE)) {\n                        if (LibraryDownloadTask.checksumValid(file, library.getChecksums()))\n                            return Optional.of(file);\n                    }\n                }\n            }\n        } finally {\n            readLock.unlock();\n        }\n\n        // check old common directory\n        Path jar = librariesDir.resolve(info.getPath());\n        if (Files.exists(jar)) {\n            try {\n                if (hash != null) {\n                    String checksum = DigestUtils.digestToString(\"SHA-1\", jar);\n                    if (hash.equalsIgnoreCase(checksum))\n                        return Optional.of(restore(jar, () -> cacheLibrary(library, jar, false)));\n                } else if (library.getChecksums() != null && !library.getChecksums().isEmpty()) {\n                    if (LibraryDownloadTask.checksumValid(jar, library.getChecksums()))\n                        return Optional.of(restore(jar, () -> cacheLibrary(library, jar, true)));\n                } else {\n                    return Optional.of(jar);\n                }\n            } catch (IOException e) {\n                // we cannot check the hashcode or unable to move file.\n            }\n        }\n\n        return Optional.empty();\n    }\n\n    /**\n     * Caches the library file to repository.\n     *\n     * @param library the library to cache\n     * @param path    the file being cached, must be verified\n     * @param forge   true if this library is provided by Forge\n     * @return cached file location\n     * @throws IOException if failed to calculate hash code of {@code path} or copy the file to cache\n     */\n    public Path cacheLibrary(Library library, Path path, boolean forge) throws IOException {\n        String hash = library.getDownload().getSha1();\n        if (hash == null)\n            hash = DigestUtils.digestToString(SHA1, path);\n\n        Path cache = getFile(SHA1, hash);\n        FileUtils.copyFile(path, cache);\n\n        Lock writeLock = lock.writeLock();\n        writeLock.lock();\n        try {\n            LibraryIndex libIndex = new LibraryIndex(library.getName(), hash, forge ? LibraryIndex.TYPE_FORGE : LibraryIndex.TYPE_JAR);\n            index.getLibraries().add(libIndex);\n            saveIndex();\n        } finally {\n            writeLock.unlock();\n        }\n\n        return cache;\n    }\n\n    private void saveIndex() {\n        if (indexFile == null || index == null) return;\n        try {\n            Files.createDirectories(indexFile.getParent());\n            FileUtils.saveSafely(indexFile, outputStream -> {\n                try (var writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) {\n                    JsonUtils.GSON.toJson(index, writer);\n                }\n            });\n        } catch (IOException e) {\n            LOG.error(\"Unable to save index.json\", e);\n        }\n    }\n\n    /// ```json\n    /// {\n    ///     \"libraries\": {\n    ///         // allow a library has multiple hash code.\n    ///         [\n    ///             \"name\": \"net.minecraftforge:forge:1.11.2-13.20.0.2345\",\n    ///             \"hash\": \"blablabla\",\n    ///             \"type\": \"forge\"\n    ///         ]\n    ///     }\n    /// }\n    ///```\n    /// assets and versions will not be included in index.\n    private static final class Index implements Validation {\n        private final Set<LibraryIndex> libraries;\n\n        public Index() {\n            this(new HashSet<>());\n        }\n\n        public Index(Set<LibraryIndex> libraries) {\n            this.libraries = Objects.requireNonNull(libraries);\n        }\n\n        @NotNull\n        public Set<LibraryIndex> getLibraries() {\n            return libraries;\n        }\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            if (libraries == null)\n                throw new JsonParseException(\"Index.libraries cannot be null\");\n        }\n    }\n\n    private static final class LibraryIndex implements Validation {\n        private final String name;\n        private final String hash;\n        private final String type;\n\n        public LibraryIndex() {\n            this(\"\", \"\", \"\");\n        }\n\n        public LibraryIndex(String name, String hash, String type) {\n            this.name = name;\n            this.hash = hash;\n            this.type = type;\n        }\n\n        @NotNull\n        public String getName() {\n            return name;\n        }\n\n        @NotNull\n        public String getHash() {\n            return hash;\n        }\n\n        @NotNull\n        public String getType() {\n            return type;\n        }\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            if (name == null || hash == null || type == null)\n                throw new JsonParseException(\"Index.LibraryIndex.* cannot be null\");\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            LibraryIndex that = (LibraryIndex) o;\n            return Objects.equals(name, that.name) &&\n                    Objects.equals(hash, that.hash) &&\n                    Objects.equals(type, that.type);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(name, hash, type);\n        }\n\n        public static final String TYPE_FORGE = \"forge\";\n        public static final String TYPE_JAR = \"jar\";\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.download.cleanroom.CleanroomInstallTask;\nimport org.jackhuang.hmcl.download.forge.ForgeInstallTask;\nimport org.jackhuang.hmcl.download.game.GameAssetDownloadTask;\nimport org.jackhuang.hmcl.download.game.GameDownloadTask;\nimport org.jackhuang.hmcl.download.game.GameLibrariesTask;\nimport org.jackhuang.hmcl.download.neoforge.NeoForgeInstallTask;\nimport org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Note: This class has no state.\n *\n * @author huangyuhui\n */\npublic class DefaultDependencyManager extends AbstractDependencyManager {\n\n    private final DefaultGameRepository repository;\n    private final DownloadProvider downloadProvider;\n    private final DefaultCacheRepository cacheRepository;\n\n    public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, DefaultCacheRepository cacheRepository) {\n        this.repository = repository;\n        this.downloadProvider = downloadProvider;\n        this.cacheRepository = cacheRepository;\n    }\n\n    @Override\n    public DefaultGameRepository getGameRepository() {\n        return repository;\n    }\n\n    @Override\n    public DownloadProvider getDownloadProvider() {\n        return downloadProvider;\n    }\n\n    @Override\n    public DefaultCacheRepository getCacheRepository() {\n        return cacheRepository;\n    }\n\n    @Override\n    public GameBuilder gameBuilder() {\n        return new DefaultGameBuilder(this);\n    }\n\n    @Override\n    public Task<?> checkGameCompletionAsync(Version version, boolean integrityCheck) {\n        return Task.allOf(\n                Task.composeAsync(() -> {\n                    Path versionJar = repository.getVersionJar(version);\n\n                    return Files.notExists(versionJar) || FileUtils.size(versionJar) == 0L\n                            ? new GameDownloadTask(this, null, version)\n                            : null;\n                }).thenComposeAsync(checkPatchCompletionAsync(version, integrityCheck)),\n                new GameAssetDownloadTask(this, version, GameAssetDownloadTask.DOWNLOAD_INDEX_IF_NECESSARY, integrityCheck)\n                        .setSignificance(Task.TaskSignificance.MODERATE),\n                new GameLibrariesTask(this, version, integrityCheck)\n        );\n    }\n\n    @Override\n    public Task<?> checkLibraryCompletionAsync(Version version, boolean integrityCheck) {\n        return new GameLibrariesTask(this, version, integrityCheck, version.getLibraries());\n    }\n\n    @Override\n    public Task<?> checkPatchCompletionAsync(Version version, boolean integrityCheck) {\n        return Task.composeAsync(() -> {\n            List<Task<?>> tasks = new ArrayList<>(0);\n\n            String gameVersion = repository.getGameVersion(version).orElse(null);\n            if (gameVersion == null) return null;\n\n            Version original = repository.getVersion(version.getId());\n            Version resolved = original.resolvePreservingPatches(repository);\n\n            LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved, gameVersion);\n            for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {\n                if (!analyzer.has(type))\n                    continue;\n\n                if (type == LibraryAnalyzer.LibraryType.OPTIFINE) {\n                    String optifinePatchVersion = analyzer.getVersion(type)\n                            .map(optifineVersion -> {\n                                Matcher matcher = Pattern.compile(\"^([0-9.]+)_(?<optifine>HD_.+)$\").matcher(optifineVersion);\n                                return matcher.find() ? matcher.group(\"optifine\") : optifineVersion;\n                            })\n                            .orElseGet(() -> resolved.getPatches().stream()\n                                    .filter(patch -> \"optifine\".equals(patch.getId()))\n                                    .findAny()\n                                    .map(Version::getVersion)\n                                    .orElse(null));\n\n                    boolean needsReInstallation = version.getLibraries().stream()\n                            .anyMatch(library -> !library.hasDownloadURL()\n                                    && \"optifine\".equals(library.getGroupId())\n                                    && GameLibrariesTask.shouldDownloadLibrary(repository, version, library, integrityCheck));\n\n                    if (needsReInstallation) {\n                        Library installer = new Library(new Artifact(\"optifine\", \"OptiFine\", gameVersion + \"_\" + optifinePatchVersion, \"installer\"));\n                        if (GameLibrariesTask.shouldDownloadLibrary(repository, version, installer, integrityCheck)) {\n                            tasks.add(installLibraryAsync(gameVersion, original, \"optifine\", optifinePatchVersion));\n                        } else {\n                            tasks.add(OptiFineInstallTask.install(this, original, repository.getLibraryFile(version, installer)));\n                        }\n                    }\n                }\n            }\n\n            return Task.allOf(tasks);\n        });\n    }\n\n    @Override\n    public Task<Version> installLibraryAsync(String gameVersion, Version baseVersion, String libraryId, String libraryVersion) {\n        if (baseVersion.isResolved()) throw new IllegalArgumentException(\"Version should not be resolved\");\n\n        VersionList<?> versionList = getVersionList(libraryId);\n        return versionList.loadAsync(gameVersion)\n                .thenComposeAsync(() -> installLibraryAsync(baseVersion, versionList.getVersion(gameVersion, libraryVersion)\n                        .orElseThrow(() -> new IOException(\"Remote library \" + libraryId + \" has no version \" + libraryVersion))))\n                .withStage(String.format(\"hmcl.install.%s:%s\", libraryId, libraryVersion));\n    }\n\n    @Override\n    public Task<Version> installLibraryAsync(Version baseVersion, RemoteVersion libraryVersion) {\n        if (baseVersion.isResolved()) throw new IllegalArgumentException(\"Version should not be resolved\");\n\n        AtomicReference<Version> removedLibraryVersion = new AtomicReference<>();\n\n        return removeLibraryAsync(baseVersion.resolvePreservingPatches(repository), libraryVersion.getLibraryId())\n                .thenComposeAsync(version -> {\n                    removedLibraryVersion.set(version);\n                    return libraryVersion.getInstallTask(this, version);\n                })\n                .thenApplyAsync(patch -> {\n                    if (patch == null) {\n                        return removedLibraryVersion.get();\n                    } else {\n                        return removedLibraryVersion.get().addPatch(patch);\n                    }\n                })\n                .withStage(String.format(\"hmcl.install.%s:%s\", libraryVersion.getLibraryId(), libraryVersion.getSelfVersion()));\n    }\n\n    public Task<Version> installLibraryAsync(Version oldVersion, Path installer) {\n        if (oldVersion.isResolved()) throw new IllegalArgumentException(\"Version should not be resolved\");\n\n        return Task\n                .composeAsync(() -> {\n                    try {\n                        return CleanroomInstallTask.install(this, oldVersion, installer);\n                    } catch (IOException ignore) {\n                    }\n\n                    try {\n                        return NeoForgeInstallTask.install(this, oldVersion, installer);\n                    } catch (IOException ignore) {\n                    }\n\n                    try {\n                        return ForgeInstallTask.install(this, oldVersion, installer);\n                    } catch (IOException ignore) {\n                    }\n\n                    try {\n                        return OptiFineInstallTask.install(this, oldVersion, installer);\n                    } catch (IOException ignore) {\n                    }\n\n                    throw new UnsupportedLibraryInstallerException();\n                })\n                .thenApplyAsync(oldVersion::addPatch);\n    }\n\n    public static class UnsupportedLibraryInstallerException extends Exception {\n    }\n\n    /**\n     * Remove installed library.\n     * Will try to remove libraries and patches.\n     *\n     * @param version not resolved version\n     * @param libraryId forge/liteloader/optifine/fabric\n     * @return task to remove the specified library\n     */\n    public Task<Version> removeLibraryAsync(Version version, String libraryId) {\n        // MaintainTask requires version that does not inherits from any version.\n        // If we want to remove a library in dependent version, we should keep the dependents not changed\n        // So resolving this game version to preserve all information in this version.json is necessary.\n        if (version.isResolved())\n            throw new IllegalArgumentException(\"removeLibraryWithoutSavingAsync requires non-resolved version\");\n        Version independentVersion = version.resolvePreservingPatches(repository);\n        String gameVersion = repository.getGameVersion(independentVersion).orElse(null);\n\n        return Task.supplyAsync(() -> LibraryAnalyzer.analyze(independentVersion, gameVersion).removeLibrary(libraryId).build());\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\n\nimport java.util.ArrayList;\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\npublic class DefaultGameBuilder extends GameBuilder {\n\n    private final DefaultDependencyManager dependencyManager;\n\n    public DefaultGameBuilder(DefaultDependencyManager dependencyManager) {\n        this.dependencyManager = dependencyManager;\n    }\n\n    public DefaultDependencyManager getDependencyManager() {\n        return dependencyManager;\n    }\n\n    @Override\n    public Task<?> buildAsync() {\n        var hints = new ArrayList<Task.StagesHint>();\n\n        Task<Version> libraryTask = Task.supplyAsync(() -> new Version(name));\n        libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, \"game\", gameVersion));\n        hints.add(new Task.StagesHint(\"hmcl.install.game:\" + gameVersion));\n        hints.add(new Task.StagesHint(\"hmcl.install.libraries\"));\n        hints.add(new Task.StagesHint(\"hmcl.install.assets\"));\n\n        for (Map.Entry<String, String> entry : toolVersions.entrySet()) {\n            libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue()));\n            hints.add(new Task.StagesHint(String.format(\"hmcl.install.%s:%s\", entry.getKey(), entry.getValue())));\n        }\n\n        for (RemoteVersion remoteVersion : remoteVersions) {\n            libraryTask = libraryTask.thenComposeAsync(version -> dependencyManager.installLibraryAsync(version, remoteVersion));\n            hints.add(new Task.StagesHint(String.format(\"hmcl.install.%s:%s\", remoteVersion.getLibraryId(), remoteVersion.getSelfVersion())));\n        }\n\n        return libraryTask.thenComposeAsync(dependencyManager.getGameRepository()::saveAsync).whenComplete(exception -> {\n            if (exception != null)\n                dependencyManager.getGameRepository().removeVersionFromDisk(name);\n        }).withStagesHints(hints);\n    }\n\n    private ExceptionalFunction<Version, Task<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId, String libraryVersion) {\n        return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, libraryVersion);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/DependencyManager.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.game.GameRepository;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.CacheRepository;\n\n/**\n * Do everything that will connect to Internet.\n * Downloading Minecraft files.\n *\n * @author huangyuhui\n */\npublic interface DependencyManager {\n\n    /**\n     * The relied game repository.\n     */\n    GameRepository getGameRepository();\n\n    /**\n     * The cache repository\n     */\n    CacheRepository getCacheRepository();\n\n    /**\n     * Check if the game is complete.\n     * Check libraries, assets files and so on.\n     *\n     * @return the task to check game completion.\n     */\n    Task<?> checkGameCompletionAsync(Version version, boolean integrityCheck);\n\n    /**\n     * Check if libraries of this version in complete.\n     * If not, download missing libraries if possible.\n     *\n     * @return the task to check game completion.\n     */\n    Task<?> checkLibraryCompletionAsync(Version version, boolean integrityCheck);\n\n    /**\n     * Check if patches of this version in complete.\n     * If not, reinstall the patch if possible.\n     *\n     * @param version the version to be checked\n     * @param integrityCheck check if some libraries are corrupt.\n     * @return the task to check patches completion.\n     */\n    Task<?> checkPatchCompletionAsync(Version version, boolean integrityCheck);\n\n    /**\n     * The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine.\n     */\n    GameBuilder gameBuilder();\n\n    /**\n     * Install a library to a version.\n     * **Note**: Installing a library may change the version.json.\n     *\n     * @param gameVersion the Minecraft version that the library relies on.\n     * @param baseVersion the version.json.\n     * @param libraryId the type of being installed library. i.e. \"forge\", \"liteloader\", \"optifine\"\n     * @param libraryVersion the version of being installed library.\n     * @return the task to install the specific library.\n     */\n    Task<?> installLibraryAsync(String gameVersion, Version baseVersion, String libraryId, String libraryVersion);\n\n    /**\n     * Install a library to a version.\n     * **Note**: Installing a library may change the version.json.\n     *\n     * @param baseVersion the version.json.\n     * @param libraryVersion the remote version of being installed library.\n     * @return the task to install the specific library.\n     */\n    Task<?> installLibraryAsync(Version baseVersion, RemoteVersion libraryVersion);\n\n    /**\n     * Get registered version list.\n     *\n     * @param id the id of version list. i.e. game, forge, liteloader, optifine\n     * @throws IllegalArgumentException if the version list of specific id is not found.\n     */\n    VersionList<?> getVersionList(String id);\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.net.URI;\nimport java.util.LinkedHashSet;\nimport java.util.List;\n\n/// The service provider that provides Minecraft online file downloads.\n///\n/// @author huangyuhui\npublic interface DownloadProvider {\n\n    List<URI> getVersionListURLs();\n\n    List<URI> getAssetObjectCandidates(String assetObjectLocation);\n\n    /// Inject into original URL provided by Mojang and Forge.\n    ///\n    /// Since there are many provided URLs that are written in JSONs and are unmodifiable,\n    /// this method provides a way to change them.\n    ///\n    /// @param baseURL original URL provided by Mojang and Forge.\n    /// @return the URL that is equivalent to `baseURL``, but belongs to your own service provider.\n    String injectURL(String baseURL);\n\n    /// Inject into original URL provided by Mojang and Forge.\n    ///\n    /// Since there are many provided URLs that are written in JSONs and are unmodifiable,\n    /// this method provides a way to change them.\n    ///\n    /// @param baseURL original URL provided by Mojang and Forge.\n    /// @return the URL that is equivalent to `baseURL`, but belongs to your own service provider.\n    default List<URI> injectURLWithCandidates(String baseURL) {\n        return List.of(NetworkUtils.toURI(injectURL(baseURL)));\n    }\n\n    default List<URI> injectURLsWithCandidates(List<String> urls) {\n        LinkedHashSet<URI> result = new LinkedHashSet<>();\n        for (String url : urls) {\n            result.addAll(injectURLWithCandidates(url));\n        }\n        return List.copyOf(result);\n    }\n\n    /// the specific version list that this download provider provides. i.e. \"fabric\", \"forge\", \"liteloader\", \"game\", \"optifine\"\n    ///\n    /// @param id the id of specific version list that this download provider provides. i.e. \"fabric\", \"forge\", \"liteloader\", \"game\", \"optifine\"\n    /// @return the version list\n    /// @throws IllegalArgumentException if the version list does not exist\n    VersionList<?> getVersionListById(String id);\n\n    /// The maximum download concurrency that this download provider supports.\n    ///\n    /// @return the maximum download concurrency.\n    int getConcurrency();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProviderWrapper.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.net.URI;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * @author Glavo\n */\npublic final class DownloadProviderWrapper implements DownloadProvider {\n\n    private volatile DownloadProvider provider;\n\n    public DownloadProviderWrapper(DownloadProvider provider) {\n        this.provider = provider;\n    }\n\n    public DownloadProvider getProvider() {\n        return this.provider;\n    }\n\n    public void setProvider(DownloadProvider provider) {\n        this.provider = Objects.requireNonNull(provider);\n    }\n\n    @Override\n    public List<URI> getAssetObjectCandidates(String assetObjectLocation) {\n        return getProvider().getAssetObjectCandidates(assetObjectLocation);\n    }\n\n    @Override\n    public List<URI> getVersionListURLs() {\n        return getProvider().getVersionListURLs();\n    }\n\n    @Override\n    public String injectURL(String baseURL) {\n        return getProvider().injectURL(baseURL);\n    }\n\n    @Override\n    public List<URI> injectURLWithCandidates(String baseURL) {\n        return getProvider().injectURLWithCandidates(baseURL);\n    }\n\n    @Override\n    public List<URI> injectURLsWithCandidates(List<String> urls) {\n        return getProvider().injectURLsWithCandidates(urls);\n    }\n\n    @Override\n    public VersionList<?> getVersionListById(String id) {\n\n        return new VersionList<>() {\n            @Override\n            public boolean hasType() {\n                return getProvider().getVersionListById(id).hasType();\n            }\n\n            @Override\n            public Task<?> refreshAsync() {\n                throw new UnsupportedOperationException();\n            }\n\n            @Override\n            public Task<?> refreshAsync(String gameVersion) {\n                return getProvider().getVersionListById(id).refreshAsync(gameVersion)\n                        .thenComposeAsync(() -> {\n                            lock.writeLock().lock();\n                            try {\n                                versions.putAll(gameVersion, getProvider().getVersionListById(id).getVersions(gameVersion));\n                            } finally {\n                                lock.writeLock().unlock();\n                            }\n                            return null;\n                        });\n            }\n        };\n    }\n\n    @Override\n    public int getConcurrency() {\n        return getProvider().getConcurrency();\n    }\n\n    @Override\n    public String toString() {\n        return \"DownloadProviderWrapper[provider=%s]\".formatted(provider);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/GameBuilder.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.util.*;\n\n/**\n * The builder which provide a task to build Minecraft environment.\n *\n * @author huangyuhui\n */\npublic abstract class GameBuilder {\n\n    protected String name = \"\";\n    protected String gameVersion = \"\";\n    protected final Map<String, String> toolVersions = new HashMap<>();\n    protected final Set<RemoteVersion> remoteVersions = new HashSet<>();\n\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * The new game version name, for .minecraft/&lt;version name&gt;.\n     *\n     * @param name the name of new game version.\n     */\n    public GameBuilder name(String name) {\n        this.name = Objects.requireNonNull(name);\n        return this;\n    }\n\n    public GameBuilder gameVersion(String version) {\n        this.gameVersion = Objects.requireNonNull(version);\n        return this;\n    }\n\n    /**\n     * @param id the core library id. i.e. \"forge\", \"liteloader\", \"optifine\"\n     * @param version the version of the core library. For documents, you can first try [VersionList.versions]\n     */\n    public GameBuilder version(String id, String version) {\n        if (\"game\".equals(id))\n            gameVersion(version);\n        else\n            toolVersions.put(id, version);\n        return this;\n    }\n\n    public GameBuilder version(RemoteVersion remoteVersion) {\n        remoteVersions.add(remoteVersion);\n        return this;\n    }\n\n    /**\n     * @return the task that can build the whole Minecraft environment\n     */\n    public abstract Task<?> buildAsync();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.intellij.lang.annotations.Language;\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jackhuang.hmcl.util.versioning.VersionRange;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\npublic final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMark> {\n    private Version version;\n    private final Map<String, Pair<Library, String>> libraries;\n\n    private LibraryAnalyzer(Version version, Map<String, Pair<Library, String>> libraries) {\n        this.version = version;\n        this.libraries = libraries;\n    }\n\n    public Optional<String> getVersion(LibraryType type) {\n        return getVersion(type.getPatchId());\n    }\n\n    public Optional<String> getVersion(String type) {\n        return Optional.ofNullable(libraries.get(type)).map(Pair::getValue);\n    }\n\n    public Optional<Library> getLibrary(LibraryType type) {\n        return Optional.ofNullable(libraries.get(type.getPatchId())).map(Pair::getKey);\n    }\n\n    /**\n     * If a library is provided in $.patches, it's structure is so clear that we can do any operation.\n     * Otherwise, we must guess how are these libraries mixed.\n     * Maybe a guessing implementation will be provided in the future. But by now, we simply set it to JUST_EXISTED.\n     */\n    public LibraryMark.LibraryStatus getLibraryStatus(String type) {\n        return version.hasPatch(type) ? LibraryMark.LibraryStatus.CLEAR : LibraryMark.LibraryStatus.JUST_EXISTED;\n    }\n\n    @NotNull\n    @Override\n    public Iterator<LibraryMark> iterator() {\n        return new Iterator<LibraryMark>() {\n            Iterator<Map.Entry<String, Pair<Library, String>>> impl = libraries.entrySet().iterator();\n\n            @Override\n            public boolean hasNext() {\n                return impl.hasNext();\n            }\n\n            @Override\n            public LibraryMark next() {\n                Map.Entry<String, Pair<Library, String>> entry = impl.next();\n                return new LibraryMark(entry.getKey(), entry.getValue().getValue(), getLibraryStatus(entry.getKey()));\n            }\n        };\n    }\n\n    public boolean has(LibraryType type) {\n        return has(type.getPatchId());\n    }\n\n    public boolean has(String type) {\n        return libraries.containsKey(type);\n    }\n\n    public boolean hasModLoader() {\n        return libraries.keySet().stream().map(LibraryType::fromPatchId)\n                .filter(Objects::nonNull)\n                .anyMatch(LibraryType::isModLoader);\n    }\n\n    public boolean hasModLauncher() {\n        return LibraryAnalyzer.MOD_LAUNCHER_MAIN.equals(version.getMainClass()) || version.getPatches().stream().anyMatch(\n                patch -> LibraryAnalyzer.MOD_LAUNCHER_MAIN.equals(patch.getMainClass())\n        );\n    }\n\n    private Version removingMatchedLibrary(Version version, String libraryId) {\n        LibraryType type = LibraryType.fromPatchId(libraryId);\n        if (type == null) return version;\n\n        List<Library> libraries = new ArrayList<>();\n        List<Library> rawLibraries = version.getLibraries();\n        for (Library library : rawLibraries) {\n            if (type.matchLibrary(library, rawLibraries)) {\n                // skip\n            } else {\n                libraries.add(library);\n            }\n        }\n        return version.setLibraries(libraries);\n    }\n\n    /**\n     * Remove library by library id\n     *\n     * @param libraryId patch id or \"forge\"/\"optifine\"/\"liteloader\"/\"fabric\"/\"quilt\"/\"neoforge\"/\"cleanroom\"\n     * @return this\n     */\n    public LibraryAnalyzer removeLibrary(String libraryId) {\n        if (!has(libraryId)) return this;\n        version = removingMatchedLibrary(version, libraryId)\n                .setPatches(version.getPatches().stream()\n                        .filter(patch -> !libraryId.equals(patch.getId()))\n                        .map(patch -> removingMatchedLibrary(patch, libraryId))\n                        .collect(Collectors.toList()));\n        return this;\n    }\n\n    public Version build() {\n        return version;\n    }\n\n    public static LibraryAnalyzer analyze(Version version, String gameVersion) {\n        if (version.getInheritsFrom() != null)\n            throw new IllegalArgumentException(\"LibraryAnalyzer can only analyze independent game version\");\n\n        Map<String, Pair<Library, String>> libraries = new HashMap<>();\n\n        if (gameVersion != null) {\n            libraries.put(LibraryType.MINECRAFT.getPatchId(), pair(null, gameVersion));\n        }\n\n        List<Library> rawLibraries = version.resolve(null).getLibraries();\n        for (Library library : rawLibraries) {\n            for (LibraryType type : LibraryType.values()) {\n                if (type.matchLibrary(library, rawLibraries)) {\n                    libraries.put(type.getPatchId(), pair(library, type.patchVersion(version, library.getVersion())));\n                    break;\n                }\n            }\n        }\n\n        for (Version patch : version.getPatches()) {\n            if (patch.isHidden()) continue;\n            libraries.put(patch.getId(), pair(null, patch.getVersion()));\n        }\n\n        return new LibraryAnalyzer(version, libraries);\n    }\n\n    public static boolean isModded(VersionProvider provider, Version version) {\n        Version resolvedVersion = version.resolve(provider);\n        String mainClass = resolvedVersion.getMainClass();\n        return mainClass != null && (LAUNCH_WRAPPER_MAIN.equals(mainClass)\n                || mainClass.startsWith(\"net.minecraftforge\")\n                || mainClass.startsWith(\"net.neoforged\")\n                || mainClass.startsWith(\"top.outlands\") //Cleanroom\n                || mainClass.startsWith(\"net.fabricmc\")\n                || mainClass.startsWith(\"org.quiltmc\")\n                || mainClass.startsWith(\"cpw.mods\"));\n    }\n\n    public Set<ModLoaderType> getModLoaders() {\n        return Arrays.stream(LibraryType.values())\n                .filter(LibraryType::isModLoader)\n                .filter(this::has)\n                .map(LibraryType::getModLoaderType)\n                .filter(Objects::nonNull)\n                .collect(Collectors.toSet());\n    }\n\n    public enum LibraryType {\n        MINECRAFT(true, \"game\", \"^$\", \"^$\", null),\n        LEGACY_FABRIC(true, \"legacyfabric\", \"net\\\\.fabricmc\", \"fabric-loader\", ModLoaderType.LEGACY_FABRIC) {\n            @Override\n            protected boolean matchLibrary(Library library, List<Library> libraries) {\n                if (!super.matchLibrary(library, libraries)) {\n                    return false;\n                }\n                for (Library l : libraries) {\n                    if (\"net.legacyfabric\".equals(l.getGroupId())) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n        },\n        LEGACY_FABRIC_API(false, \"legacyfabric-api\", \"net\\\\.legacyfabric\", \"legacyfabric-api\", null),\n        FABRIC(true, \"fabric\", \"net\\\\.fabricmc\", \"fabric-loader\", ModLoaderType.FABRIC) {\n            @Override\n            protected boolean matchLibrary(Library library, List<Library> libraries) {\n                if (!super.matchLibrary(library, libraries)) {\n                    return false;\n                }\n                for (Library l : libraries) {\n                    if (\"net.legacyfabric\".equals(l.getGroupId())) {\n                        return false;\n                    }\n                }\n                return true;\n            }\n        },\n        FABRIC_API(true, \"fabric-api\", \"net\\\\.fabricmc\", \"fabric-api\", null),\n        FORGE(true, \"forge\", \"net\\\\.minecraftforge\", \"(forge|fmlloader)\", ModLoaderType.FORGE) {\n            private final Pattern FORGE_VERSION_MATCHER = Pattern.compile(\"^([0-9.]+)-(?<forge>[0-9.]+)(-([0-9.]+))?$\");\n\n            @Override\n            protected String patchVersion(Version gameVersion, String libraryVersion) {\n                Matcher matcher = FORGE_VERSION_MATCHER.matcher(libraryVersion);\n                if (matcher.find()) {\n                    return matcher.group(\"forge\");\n                }\n                return super.patchVersion(gameVersion, libraryVersion);\n            }\n\n            @Override\n            protected boolean matchLibrary(Library library, List<Library> libraries) {\n                for (Library l : libraries) {\n                    if (NEO_FORGE.matchLibrary(l, libraries)) {\n                        return false;\n                    }\n                }\n                return super.matchLibrary(library, libraries);\n            }\n        },\n        CLEANROOM(true, \"cleanroom\", \"com\\\\.cleanroommc\", \"cleanroom\", ModLoaderType.CLEANROOM),\n        NEO_FORGE(true, \"neoforge\", \"net\\\\.neoforged\\\\.fancymodloader\", \"(core|loader)\", ModLoaderType.NEO_FORGED) {\n            private final Pattern NEO_FORGE_VERSION_MATCHER = Pattern.compile(\"^([0-9.]+)-(?<forge>[0-9.]+)(-([0-9.]+))?$\");\n\n            @Override\n            protected String patchVersion(Version gameVersion, String libraryVersion) {\n                String res = scanVersion(gameVersion);\n                if (res != null) {\n                    return res;\n                }\n\n                for (Version patch : gameVersion.getPatches()) {\n                    res = scanVersion(patch);\n                    if (res != null) {\n                        return res;\n                    }\n                }\n\n                Matcher matcher = NEO_FORGE_VERSION_MATCHER.matcher(libraryVersion);\n                if (matcher.find()) {\n                    return matcher.group(\"forge\");\n                }\n\n                return super.patchVersion(gameVersion, libraryVersion);\n            }\n\n            private String scanVersion(Version version) {\n                Optional<Arguments> optArgument = version.getArguments();\n                if (!optArgument.isPresent()) {\n                    return null;\n                }\n                List<Argument> gameArguments = optArgument.get().getGame();\n                if (gameArguments == null) {\n                    return null;\n                }\n\n                for (int i = 0; i < gameArguments.size() - 1; i++) {\n                    Argument argument = gameArguments.get(i);\n                    if (argument instanceof StringArgument) {\n                        String argumentValue = ((StringArgument) argument).getArgument();\n                        if (\"--fml.neoForgeVersion\".equals(argumentValue) || \"--fml.forgeVersion\".equals(argumentValue)) {\n                            Argument next = gameArguments.get(i + 1);\n                            if (next instanceof StringArgument) {\n                                return ((StringArgument) next).getArgument();\n                            }\n                            return null; // Normally, there should not be two --fml.neoForgeVersion argument.\n                        }\n                    }\n                }\n                return null;\n            }\n\n        },\n        LITELOADER(true, \"liteloader\", \"com\\\\.mumfrey\", \"liteloader\", ModLoaderType.LITE_LOADER),\n        OPTIFINE(false, \"optifine\", \"(net\\\\.)?optifine\", \"^(?!.*launchwrapper).*$\", null),\n        QUILT(true, \"quilt\", \"org\\\\.quiltmc\", \"quilt-loader\", ModLoaderType.QUILT),\n        QUILT_API(true, \"quilt-api\", \"org\\\\.quiltmc\", \"quilt-api\", null),\n        BOOTSTRAP_LAUNCHER(false, \"\", \"cpw\\\\.mods\", \"bootstraplauncher\", null);\n\n        private final boolean modLoader;\n        private final String patchId;\n        private final Pattern group, artifact;\n        private final ModLoaderType modLoaderType;\n\n        private static final Map<String, LibraryType> PATCH_ID_MAP = new HashMap<>();\n\n        static {\n            for (LibraryType type : values()) {\n                PATCH_ID_MAP.put(type.getPatchId(), type);\n            }\n        }\n\n        LibraryType(boolean modLoader, String patchId, @Language(\"RegExp\") String group, @Language(\"RegExp\") String artifact, ModLoaderType modLoaderType) {\n            this.modLoader = modLoader;\n            this.patchId = patchId;\n            this.group = Pattern.compile(group);\n            this.artifact = Pattern.compile(artifact);\n            this.modLoaderType = modLoaderType;\n        }\n\n        public boolean isModLoader() {\n            return modLoader;\n        }\n\n        public String getPatchId() {\n            return patchId;\n        }\n\n        public ModLoaderType getModLoaderType() {\n            return modLoaderType;\n        }\n\n        public static LibraryType fromPatchId(String patchId) {\n            return PATCH_ID_MAP.get(patchId);\n        }\n\n        protected boolean matchLibrary(Library library, List<Library> libraries) {\n            return group.matcher(library.getGroupId()).matches() && artifact.matcher(library.getArtifactId()).matches();\n        }\n\n        protected String patchVersion(Version gameVersion, String libraryVersion) {\n            return libraryVersion;\n        }\n    }\n\n    public final static class LibraryMark {\n        /**\n         * If a library is provided in $.patches, it's structure is so clear that we can do any operation.\n         * Otherwise, we must guess how are these libraries mixed.\n         * Maybe a guessing implementation will be provided in the future. But by now, we simply set it to JUST_EXISTED.\n         */\n        public enum LibraryStatus {\n            CLEAR, UNSURE, JUST_EXISTED\n        }\n\n        private final String libraryId;\n        private final String libraryVersion;\n        /**\n         * If this version is installed by HMCL, instead of external process,\n         * which means $.patches contains this library, structureClear is true.\n         */\n        private final LibraryStatus status;\n\n        private LibraryMark(@NotNull String libraryId, @Nullable String libraryVersion, LibraryStatus status) {\n            this.libraryId = libraryId;\n            this.libraryVersion = libraryVersion;\n            this.status = status;\n        }\n\n        @NotNull\n        public String getLibraryId() {\n            return libraryId;\n        }\n\n        @Nullable\n        public String getLibraryVersion() {\n            return libraryVersion;\n        }\n\n        public LibraryStatus getStatus() {\n            return status;\n        }\n    }\n\n    public static final String VANILLA_MAIN = \"net.minecraft.client.main.Main\";\n    public static final String LAUNCH_WRAPPER_MAIN = \"net.minecraft.launchwrapper.Launch\";\n    public static final String MOD_LAUNCHER_MAIN = \"cpw.mods.modlauncher.Launcher\";\n    public static final String BOOTSTRAP_LAUNCHER_MAIN = \"cpw.mods.bootstraplauncher.BootstrapLauncher\";\n    public static final String FORGE_BOOTSTRAP_MAIN = \"net.minecraftforge.bootstrap.ForgeBootstrap\";\n    public static final String NEO_FORGED_BOOTSTRAP_MAIN = \"net.neoforged.fml.startup.Client\";\n\n    public static final Set<String> FORGE_OPTIFINE_MAIN = Set.of(\n            LibraryAnalyzer.VANILLA_MAIN,\n            LibraryAnalyzer.LAUNCH_WRAPPER_MAIN,\n            LibraryAnalyzer.MOD_LAUNCHER_MAIN,\n            LibraryAnalyzer.BOOTSTRAP_LAUNCHER_MAIN,\n            LibraryAnalyzer.FORGE_BOOTSTRAP_MAIN,\n            LibraryAnalyzer.NEO_FORGED_BOOTSTRAP_MAIN\n    );\n\n    public static final VersionRange<VersionNumber> FORGE_OPTIFINE_BROKEN_RANGE = VersionNumber.between(\"48.0.0\", \"49.0.50\");\n\n    public static final String[] FORGE_TWEAKERS = new String[]{\n            \"net.minecraftforge.legacy._1_5_2.LibraryFixerTweaker\", // 1.5.2\n            \"cpw.mods.fml.common.launcher.FMLTweaker\", // 1.6.1 ~ 1.7.10\n            \"net.minecraftforge.fml.common.launcher.FMLTweaker\" // 1.8 ~ 1.12.2\n    };\n    public static final String[] OPTIFINE_TWEAKERS = new String[]{\n            \"optifine.OptiFineTweaker\",\n            \"optifine.OptiFineForgeTweaker\"\n    };\n    public static final String LITELOADER_TWEAKER = \"com.mumfrey.liteloader.launch.LiteLoaderTweaker\";\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.SimpleMultimap;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.StandardCopyOption;\nimport java.util.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class MaintainTask extends Task<Version> {\n    private final GameRepository repository;\n    private final Version version;\n\n    public MaintainTask(GameRepository repository, Version version) {\n        this.repository = repository;\n        this.version = version;\n\n        if (version.getInheritsFrom() != null)\n            throw new IllegalArgumentException(\"MaintainTask requires independent game version\");\n    }\n\n    @Override\n    public void execute() {\n        setResult(maintain(repository, version));\n    }\n\n    public static Version maintain(GameRepository repository, Version version) {\n        if (version.getInheritsFrom() != null)\n            throw new IllegalArgumentException(\"MaintainTask requires independent game version\");\n\n        String mainClass = version.resolve(null).getMainClass();\n\n        if (mainClass != null && mainClass.equals(LibraryAnalyzer.LAUNCH_WRAPPER_MAIN)) {\n            version = maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(repository, unique(version), true), false);\n        } else if (mainClass != null && mainClass.equals(LibraryAnalyzer.MOD_LAUNCHER_MAIN)) {\n            // Forge 1.13 and OptiFine\n            version = maintainOptiFineLibrary(repository, maintainGameWithCpwModLauncher(repository, unique(version)), true);\n        } else if (mainClass != null && mainClass.equals(LibraryAnalyzer.BOOTSTRAP_LAUNCHER_MAIN)) {\n            // Forge 1.17\n            version = maintainGameWithCpwBoostrapLauncher(repository, unique(version));\n        } else {\n            // Vanilla Minecraft does not need maintain\n            // Fabric does not need maintain, nothing compatible with fabric now.\n            version = maintainOptiFineLibrary(repository, unique(version), false);\n        }\n\n        List<Library> libraries = version.getLibraries();\n        if (!libraries.isEmpty()) {\n            // HMCL once use log4j-patch to prevent virus. But now, we only modify log4j2.xml.\n            // Therefore, we remove this library.\n            Library library = libraries.get(0);\n            if (\"org.glavo\".equals(library.getGroupId())\n                    && (\"log4j-patch\".equals(library.getArtifactId()) || \"log4j-patch-beta9\".equals(library.getArtifactId()))\n                    && \"1.0\".equals(library.getVersion())\n                    && library.getDownload() == null) {\n                version = version.setLibraries(libraries.subList(1, libraries.size()));\n            }\n        }\n\n        return version;\n    }\n\n    public static Version maintainPreservingPatches(GameRepository repository, Version version) {\n        if (!version.isResolvedPreservingPatches())\n            throw new IllegalArgumentException(\"MaintainTask requires independent game version\");\n        Version newVersion = maintain(repository, version.resolve(repository));\n        return newVersion.setPatches(version.getPatches()).markAsUnresolved();\n    }\n\n    private static Version maintainGameWithLaunchWrapper(GameRepository repository, Version version, boolean reorderTweakClass) {\n        LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);\n        VersionLibraryBuilder builder = new VersionLibraryBuilder(version);\n        String mainClass = null;\n\n        // Installing Forge will override the Minecraft arguments in json, so LiteLoader and OptiFine Tweaker are being re-added.\n        if (libraryAnalyzer.has(LITELOADER) && !libraryAnalyzer.hasModLauncher()) {\n            builder.replaceTweakClass(LibraryAnalyzer.LITELOADER_TWEAKER, LibraryAnalyzer.LITELOADER_TWEAKER, !reorderTweakClass, reorderTweakClass);\n        } else {\n            builder.removeTweakClass(LibraryAnalyzer.LITELOADER_TWEAKER);\n        }\n\n        if (libraryAnalyzer.has(OPTIFINE)) {\n            if (!libraryAnalyzer.has(LITELOADER) && !libraryAnalyzer.has(FORGE)) {\n                if (builder.hasTweakClass(LibraryAnalyzer.OPTIFINE_TWEAKERS[1])) {\n                    builder.replaceTweakClass(LibraryAnalyzer.OPTIFINE_TWEAKERS[1], LibraryAnalyzer.OPTIFINE_TWEAKERS[0], !reorderTweakClass, reorderTweakClass);\n                }\n            } else {\n                if (libraryAnalyzer.hasModLauncher()) {\n                    // If ModLauncher installed, we use ModLauncher in place of LaunchWrapper.\n                    mainClass = LibraryAnalyzer.MOD_LAUNCHER_MAIN;\n                    for (String optiFineTweaker : LibraryAnalyzer.OPTIFINE_TWEAKERS) {\n                        builder.removeTweakClass(optiFineTweaker);\n                    }\n                } else if (builder.hasTweakClass(LibraryAnalyzer.OPTIFINE_TWEAKERS[0])) {\n                    // If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.\n                    builder.replaceTweakClass(LibraryAnalyzer.OPTIFINE_TWEAKERS[0], LibraryAnalyzer.OPTIFINE_TWEAKERS[1], !reorderTweakClass, reorderTweakClass);\n                }\n\n            }\n        } else {\n            for (String optiFineTweaker : LibraryAnalyzer.OPTIFINE_TWEAKERS) {\n                builder.removeTweakClass(optiFineTweaker);\n            }\n        }\n\n        boolean hasForge = libraryAnalyzer.has(FORGE), hasModLauncher = libraryAnalyzer.hasModLauncher();\n        for (String forgeTweaker : LibraryAnalyzer.FORGE_TWEAKERS) {\n            if (!hasForge) {\n                builder.removeTweakClass(forgeTweaker);\n            } else if (!hasModLauncher && builder.hasTweakClass(forgeTweaker)) {\n                builder.replaceTweakClass(forgeTweaker, forgeTweaker, !reorderTweakClass, reorderTweakClass);\n            }\n        }\n\n        Version ret = builder.build();\n        return mainClass == null ? ret : ret.setMainClass(mainClass);\n    }\n\n    private static Version maintainGameWithCpwModLauncher(GameRepository repository, Version version) {\n        LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);\n        VersionLibraryBuilder builder = new VersionLibraryBuilder(version);\n\n        if (!libraryAnalyzer.has(FORGE)) return version;\n\n        if (libraryAnalyzer.has(OPTIFINE)) {\n            Library hmclTransformerDiscoveryService = new Library(new Artifact(\"org.jackhuang.hmcl\", \"transformer-discovery-service\", \"1.0\"));\n            Optional<Library> optiFine = version.getLibraries().stream().filter(library -> library.is(\"optifine\", \"OptiFine\")).findAny();\n            boolean libraryExisting = version.getLibraries().stream().anyMatch(library -> library.is(\"org.jackhuang.hmcl\", \"transformer-discovery-service\"));\n            optiFine.ifPresent(library -> {\n                builder.addJvmArgument(\"-Dhmcl.transformer.candidates=${library_directory}/\" + library.getPath());\n                if (!libraryExisting) builder.addLibrary(hmclTransformerDiscoveryService);\n                Path libraryPath = repository.getLibraryFile(version, hmclTransformerDiscoveryService);\n                try (InputStream input = MaintainTask.class.getResourceAsStream(\"/assets/game/HMCLTransformerDiscoveryService-1.0.jar\")) {\n                    Files.createDirectories(libraryPath.getParent());\n                    Files.copy(Objects.requireNonNull(input, \"Bundled HMCLTransformerDiscoveryService is missing.\"), libraryPath, StandardCopyOption.REPLACE_EXISTING);\n                } catch (IOException | NullPointerException e) {\n                    LOG.warning(\"Unable to unpack HMCLTransformerDiscoveryService\", e);\n                }\n            });\n        }\n\n        return builder.build();\n    }\n\n    private static String updateIgnoreList(GameRepository repository, Version version, String ignoreList) {\n        String[] ignores = ignoreList.split(\",\");\n        List<String> newIgnoreList = new ArrayList<>();\n\n        // To resolve the problem that name of primary jar may conflict with the module naming convention,\n        // we need to manually ignore ${primary_jar}.\n        newIgnoreList.add(\"${primary_jar}\");\n\n        Path libraryDirectory = repository.getLibrariesDirectory(version).toAbsolutePath().normalize();\n\n        // The default ignoreList is too loose and may cause some problems, we replace them with the absolute version.\n        // For example, if \"client-extra\" is in ignoreList, and game directory contains \"client-extra\" component, all\n        // libraries will be ignored, which is not expected.\n        for (String classpathName : repository.getClasspath(version)) {\n            Path classpathFile = Paths.get(classpathName).toAbsolutePath();\n            String fileName = classpathFile.getFileName().toString();\n            if (Stream.of(ignores).anyMatch(fileName::contains)) {\n                // This library should be ignored for Jigsaw module finding by Forge.\n                String absolutePath;\n                if (classpathFile.startsWith(libraryDirectory)) {\n                    // Note: It's assumed using \"/\" instead of File.separator in classpath\n                    absolutePath = \"${library_directory}${file_separator}\" + libraryDirectory.relativize(classpathFile).toString().replace(File.separator, \"${file_separator}\");\n                } else {\n                    absolutePath = classpathFile.toString();\n                }\n                newIgnoreList.add(StringUtils.substringBefore(absolutePath, \",\"));\n            }\n        }\n        return String.join(\",\", newIgnoreList);\n    }\n\n    // Fix wrong configurations when launching 1.17+ with Forge.\n    private static Version maintainGameWithCpwBoostrapLauncher(GameRepository repository, Version version) {\n        LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);\n        VersionLibraryBuilder builder = new VersionLibraryBuilder(version);\n\n        if (!libraryAnalyzer.has(FORGE) && !libraryAnalyzer.has(NEO_FORGE)) return version;\n\n        Optional<String> bslVersion = libraryAnalyzer.getVersion(BOOTSTRAP_LAUNCHER);\n\n        if (bslVersion.isPresent()) {\n            if (VersionNumber.compare(bslVersion.get(), \"0.1.17\") < 0) {\n                // The default ignoreList will be applied to all components of libraries in classpath,\n                // so if game directory located in some directory like /Users/asm, all libraries will be ignored,\n                // which is not expected. We fix this here.\n                List<Argument> jvm = builder.getMutableJvmArguments();\n                for (int i = 0; i < jvm.size(); i++) {\n                    Argument jvmArg = jvm.get(i);\n                    if (jvmArg instanceof StringArgument) {\n                        String jvmArgStr = jvmArg.toString();\n                        if (jvmArgStr.startsWith(\"-DignoreList=\")) {\n                            jvm.set(i, new StringArgument(\"-DignoreList=\" + updateIgnoreList(repository, version, jvmArgStr.substring(\"-DignoreList=\".length()))));\n                        }\n                    }\n                }\n            } else {\n                // bootstraplauncher 0.1.17 will only apply ignoreList to file name of libraries in classpath.\n                // So we only fixes name of primary jar.\n                List<Argument> jvm = builder.getMutableJvmArguments();\n                for (int i = 0; i < jvm.size(); i++) {\n                    Argument jvmArg = jvm.get(i);\n                    if (jvmArg instanceof StringArgument) {\n                        String jvmArgStr = jvmArg.toString();\n                        if (jvmArgStr.startsWith(\"-DignoreList=\")) {\n                            jvm.set(i, new StringArgument(jvmArgStr + \",${primary_jar_name}\"));\n                        }\n                    }\n                }\n            }\n        }\n\n        return builder.build();\n    }\n\n    private static Version maintainOptiFineLibrary(GameRepository repository, Version version, boolean remove) {\n        LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);\n        List<Library> libraries = new ArrayList<>(version.getLibraries());\n\n        if (libraryAnalyzer.has(OPTIFINE)) {\n            if (libraryAnalyzer.has(LITELOADER) || libraryAnalyzer.has(FORGE)) {\n                // If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.\n                // And we should load the installer jar instead of patch jar.\n                if (repository != null) {\n                    for (int i = 0; i < version.getLibraries().size(); ++i) {\n                        Library library = libraries.get(i);\n                        if (library.is(\"optifine\", \"OptiFine\")) {\n                            Library newLibrary = new Library(new Artifact(\"optifine\", \"OptiFine\", library.getVersion(), \"installer\"));\n                            if (Files.exists(repository.getLibraryFile(version, newLibrary))) {\n                                libraries.set(i, null);\n                                // OptiFine should be loaded after Forge in classpath.\n                                // Although we have altered priority of OptiFine higher than Forge,\n                                // there still exists a situation that Forge is installed without patch.\n                                // Here we manually alter the position of OptiFine library in classpath.\n                                if (!remove) libraries.add(newLibrary);\n                            }\n                        }\n\n                        if (library.is(\"optifine\", \"launchwrapper-of\")) {\n                            // With MinecraftForge installed, the custom launchwrapper installed by OptiFine will conflicts\n                            // with the one installed by MinecraftForge or LiteLoader or ModLoader.\n                            // Simply removing it works.\n                            libraries.set(i, null);\n                        }\n                    }\n                }\n            }\n        }\n\n        return version.setLibraries(libraries.stream().filter(Objects::nonNull).collect(Collectors.toList()));\n    }\n\n    public static Version unique(Version version) {\n        List<Library> libraries = new ArrayList<>();\n\n        SimpleMultimap<String, Integer, List<Integer>> multimap = new SimpleMultimap<>(HashMap::new, ArrayList::new);\n\n        for (Library library : version.getLibraries()) {\n            String id = library.getGroupId() + \":\" + library.getArtifactId();\n            VersionNumber number = VersionNumber.asVersion(library.getVersion());\n            String serialized = JsonUtils.GSON.toJson(library);\n\n            if (multimap.containsKey(id)) {\n                boolean duplicate = false;\n                for (int otherLibraryIndex : multimap.get(id)) {\n                    Library otherLibrary = libraries.get(otherLibraryIndex);\n                    VersionNumber otherNumber = VersionNumber.asVersion(otherLibrary.getVersion());\n                    if (CompatibilityRule.equals(library.getRules(), otherLibrary.getRules())) { // rules equal, ignore older version.\n                        boolean flag = true;\n                        if (number.compareTo(otherNumber) > 0) { // if this library is newer\n                            // replace [otherLibrary] with [library]\n                            libraries.set(otherLibraryIndex, library);\n                        } else if (number.compareTo(otherNumber) == 0) { // same library id.\n                            // prevent from duplicated libraries\n                            if (library.equals(otherLibrary)) {\n                                String otherSerialized = JsonUtils.GSON.toJson(otherLibrary);\n                                // A trick, the library that has more information is better, which can be\n                                // considered whose serialized JSON text will be longer.\n                                if (serialized.length() > otherSerialized.length()) {\n                                    libraries.set(otherLibraryIndex, library);\n                                }\n                            } else {\n                                // for text2speech, which have same library id as well as version number,\n                                // but its library and native library does not equal\n                                flag = false;\n                            }\n                        }\n                        if (flag) {\n                            duplicate = true;\n                            break;\n                        }\n                    }\n                }\n\n                if (!duplicate) {\n                    multimap.put(id, libraries.size());\n                    libraries.add(library);\n                }\n            } else {\n                multimap.put(id, libraries.size());\n                libraries.add(library);\n            }\n        }\n\n        return version.setLibraries(libraries);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.download.cleanroom.CleanroomVersionList;\nimport org.jackhuang.hmcl.download.fabric.FabricAPIVersionList;\nimport org.jackhuang.hmcl.download.fabric.FabricVersionList;\nimport org.jackhuang.hmcl.download.forge.ForgeVersionList;\nimport org.jackhuang.hmcl.download.game.GameVersionList;\nimport org.jackhuang.hmcl.download.legacyfabric.LegacyFabricAPIVersionList;\nimport org.jackhuang.hmcl.download.legacyfabric.LegacyFabricVersionList;\nimport org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;\nimport org.jackhuang.hmcl.download.neoforge.NeoForgeOfficialVersionList;\nimport org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;\nimport org.jackhuang.hmcl.download.quilt.QuiltAPIVersionList;\nimport org.jackhuang.hmcl.download.quilt.QuiltVersionList;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.net.URI;\nimport java.util.List;\n\n/**\n * @author huangyuhui\n * @see <a href=\"http://wiki.vg\">http://wiki.vg</a>\n */\npublic class MojangDownloadProvider implements DownloadProvider {\n    private final GameVersionList game;\n    private final FabricVersionList fabric;\n    private final FabricAPIVersionList fabricApi;\n    private final ForgeVersionList forge;\n    private final NeoForgeOfficialVersionList neoforge;\n    private final CleanroomVersionList cleanroom;\n    private final LiteLoaderVersionList liteLoader;\n    private final OptiFineBMCLVersionList optifine;\n    private final QuiltVersionList quilt;\n    private final QuiltAPIVersionList quiltApi;\n    private final LegacyFabricVersionList legacyFabric;\n    private final LegacyFabricAPIVersionList legacyFabricApi;\n\n    public MojangDownloadProvider() {\n        // If there is no official download channel available, fallback to BMCLAPI.\n        String apiRoot = \"https://bmclapi2.bangbang93.com\";\n\n        this.game = new GameVersionList(this);\n        this.fabric = new FabricVersionList(this);\n        this.fabricApi = new FabricAPIVersionList(this);\n        this.forge = new ForgeVersionList(this);\n        this.neoforge = new NeoForgeOfficialVersionList(this);\n        this.cleanroom = new CleanroomVersionList(this);\n        this.liteLoader = new LiteLoaderVersionList(this);\n        this.optifine = new OptiFineBMCLVersionList(apiRoot);\n        this.quilt = new QuiltVersionList(this);\n        this.quiltApi = new QuiltAPIVersionList(this);\n        this.legacyFabric = new LegacyFabricVersionList(this);\n        this.legacyFabricApi = new LegacyFabricAPIVersionList(this);\n    }\n\n    @Override\n    public List<URI> getVersionListURLs() {\n        return List.of(URI.create(\"https://piston-meta.mojang.com/mc/game/version_manifest.json\"));\n    }\n\n    @Override\n    public List<URI> getAssetObjectCandidates(String assetObjectLocation) {\n        return List.of(NetworkUtils.toURI(\"https://resources.download.minecraft.net/\" + assetObjectLocation));\n    }\n\n    @Override\n    public VersionList<?> getVersionListById(String id) {\n        return switch (id) {\n            case \"game\" -> game;\n            case \"fabric\" -> fabric;\n            case \"fabric-api\" -> fabricApi;\n            case \"forge\" -> forge;\n            case \"cleanroom\" -> cleanroom;\n            case \"neoforge\" -> neoforge;\n            case \"liteloader\" -> liteLoader;\n            case \"optifine\" -> optifine;\n            case \"quilt\" -> quilt;\n            case \"quilt-api\" -> quiltApi;\n            case \"legacyfabric\" -> legacyFabric;\n            case \"legacyfabric-api\" -> legacyFabricApi;\n            default -> throw new IllegalArgumentException(\"Unrecognized version list id: \" + id);\n        };\n    }\n\n    @Override\n    public String injectURL(String baseURL) {\n        return baseURL;\n    }\n\n    @Override\n    public int getConcurrency() {\n        return 6;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/MultipleSourceVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class MultipleSourceVersionList extends VersionList<RemoteVersion> {\n\n    private final VersionList<?>[] backends;\n\n    MultipleSourceVersionList(VersionList<?>[] backends) {\n        this.backends = backends;\n\n        assert (backends.length >= 1);\n    }\n\n    @Override\n    public boolean hasType() {\n        boolean hasType = backends[0].hasType();\n        assert (Arrays.stream(backends).allMatch(versionList -> versionList.hasType() == hasType));\n        return hasType;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        throw new UnsupportedOperationException(\"MultipleSourceVersionList does not support loading the entire remote version list.\");\n    }\n\n    private Task<?> refreshAsync(String gameVersion, int sourceIndex) {\n        VersionList<?> versionList = backends[sourceIndex];\n        Task<?> refreshTask = versionList.refreshAsync(gameVersion);\n\n        return new Task<>() {\n            private Task<?> nextTask = null;\n\n            {\n                setSignificance(TaskSignificance.MODERATE);\n                setName(\"MultipleSourceVersionList.refreshAsync(task=%s, index=%d, all=%d)\".formatted(\n                        refreshTask.getName(), sourceIndex, backends.length)\n                );\n            }\n\n            @Override\n            public Collection<Task<?>> getDependents() {\n                return List.of(refreshTask);\n            }\n\n            @Override\n            public Collection<? extends Task<?>> getDependencies() {\n                return nextTask != null ? List.of(nextTask) : List.of();\n            }\n\n            @Override\n            public boolean isRelyingOnDependents() {\n                return false;\n            }\n\n            @Override\n            public void execute() throws Exception {\n                if (isDependentsSucceeded()) {\n                    lock.writeLock().lock();\n                    try {\n                        versions.putAll(gameVersion, versionList.getVersions(gameVersion));\n                    } finally {\n                        lock.writeLock().unlock();\n                    }\n\n                    setResult(refreshTask.getResult());\n                } else {\n                    Exception exception = refreshTask.getException();\n                    assert exception != null;\n\n                    if (sourceIndex == backends.length - 1) {\n                        LOG.warning(\"Failed to fetch versions list from all sources\", exception);\n                        setSignificance(TaskSignificance.MINOR);\n                        throw exception;\n                    } else {\n                        LOG.warning(\"Failed to fetch versions list and try to fetch from other source\", exception);\n                        nextTask = refreshAsync(gameVersion, sourceIndex + 1);\n                        nextTask.storeTo(this::setResult);\n                    }\n                }\n            }\n        };\n    }\n\n    @Override\n    public Task<?> refreshAsync(String gameVersion) {\n        versions.clear(gameVersion);\n        return refreshAsync(gameVersion, 0);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * The remote version.\n *\n * @author huangyuhui\n */\npublic class RemoteVersion implements Comparable<RemoteVersion> {\n\n    private final String libraryId;\n    private final String gameVersion;\n    private final String selfVersion;\n    private final Instant releaseDate;\n    private final List<String> urls;\n    private final Type type;\n\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param urls        the installer or universal jar original URL.\n     */\n    public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, List<String> urls) {\n        this(libraryId, gameVersion, selfVersion, releaseDate, Type.UNCATEGORIZED, urls);\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param urls        the installer or universal jar URL.\n     */\n    public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, Type type, List<String> urls) {\n        this.libraryId = Objects.requireNonNull(libraryId);\n        this.gameVersion = Objects.requireNonNull(gameVersion);\n        this.selfVersion = Objects.requireNonNull(selfVersion);\n        this.releaseDate = releaseDate;\n        this.urls = Objects.requireNonNull(urls);\n        this.type = Objects.requireNonNull(type);\n    }\n\n    public String getLibraryId() {\n        return libraryId;\n    }\n\n    public String getGameVersion() {\n        return gameVersion;\n    }\n\n    public String getSelfVersion() {\n        return selfVersion;\n    }\n\n    public String getFullVersion() {\n        return getSelfVersion();\n    }\n\n    public Instant getReleaseDate() {\n        return releaseDate;\n    }\n\n    public List<String> getUrls() {\n        return urls;\n    }\n\n    public Type getVersionType() {\n        return type;\n    }\n\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        throw new UnsupportedOperationException(this + \" cannot be installed yet\");\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return obj instanceof RemoteVersion && Objects.equals(selfVersion, ((RemoteVersion) obj).selfVersion);\n    }\n\n    @Override\n    public int hashCode() {\n        return selfVersion.hashCode();\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"selfVersion\", selfVersion)\n                .append(\"gameVersion\", gameVersion)\n                .toString();\n    }\n\n    @Override\n    public int compareTo(RemoteVersion o) {\n        // newer versions are smaller than older versions\n        return VersionNumber.asVersion(o.selfVersion).compareTo(VersionNumber.asVersion(selfVersion));\n    }\n\n    public enum Type {\n        UNCATEGORIZED,\n        RELEASE,\n        SNAPSHOT,\n        OLD,\n        PENDING,\n        UNOBFUSCATED,\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/UnsupportedInstallationException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\npublic class UnsupportedInstallationException extends Exception {\n\n    private final int reason;\n\n    public UnsupportedInstallationException(int reason) {\n        this.reason = reason;\n    }\n\n    public int getReason() {\n        return reason;\n    }\n\n    // e.g. Forge is not compatible with fabric.\n    public static final int UNSUPPORTED_LAUNCH_WRAPPER = 1;\n\n    // 1.17: OptiFine>=H1 Pre2 is compatible with Forge.\n    public static final int FORGE_1_17_OPTIFINE_H1_PRE2 = 2;\n\n    public static final int FABRIC_NOT_COMPATIBLE_WITH_FORGE = 3;\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.SimpleMultimap;\n\nimport java.util.*;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\n\n/**\n * The remote version list.\n *\n * @param <T> The subclass of {@code RemoteVersion}, the type of RemoteVersion.\n * @author huangyuhui\n */\npublic abstract class VersionList<T extends RemoteVersion> {\n\n    /**\n     * the remote version list.\n     * key: game version.\n     * values: corresponding remote versions.\n     */\n    protected final SimpleMultimap<String, T, TreeSet<T>> versions = new SimpleMultimap<>(HashMap::new, TreeSet::new);\n\n    /**\n     * True if the version list has been loaded.\n     */\n    public boolean isLoaded() {\n        return !versions.isEmpty();\n    }\n\n    /**\n     * True if the version list that contains the remote versions which depends on the specific game version has been loaded.\n     *\n     * @param gameVersion the remote version depends on\n     */\n    public boolean isLoaded(String gameVersion) {\n        return !versions.get(gameVersion).isEmpty();\n    }\n\n    public abstract boolean hasType();\n\n    protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();\n\n    /**\n     * @return the task to reload the remote version list.\n     */\n    public abstract Task<?> refreshAsync();\n\n    /**\n     * @param gameVersion the remote version depends on\n     * @return the task to reload the remote version list.\n     */\n    public Task<?> refreshAsync(String gameVersion) {\n        return refreshAsync();\n    }\n\n    public Task<?> loadAsync(String gameVersion) {\n        return Task.composeAsync(() -> {\n            lock.readLock().lock();\n            try {\n                return isLoaded(gameVersion) ? null : refreshAsync(gameVersion);\n            } finally {\n                lock.readLock().unlock();\n            }\n        });\n    }\n\n    protected Collection<T> getVersionsImpl(String gameVersion) {\n        return versions.get(gameVersion);\n    }\n\n    /**\n     * Get the remote versions that specifics Minecraft version.\n     *\n     * @param gameVersion the Minecraft version that remote versions belong to\n     * @return the collection of specific remote versions\n     */\n    public final Collection<T> getVersions(String gameVersion) {\n        lock.readLock().lock();\n        try {\n            return Collections.unmodifiableCollection(new ArrayList<>(getVersionsImpl(gameVersion)));\n        } finally {\n            lock.readLock().unlock();\n        }\n    }\n\n    /**\n     * Get the specific remote version.\n     *\n     * @param gameVersion   the Minecraft version that remote versions belong to\n     * @param remoteVersion the version of the remote version.\n     * @return the specific remote version, null if it is not found.\n     */\n    public Optional<T> getVersion(String gameVersion, String remoteVersion) {\n        lock.readLock().lock();\n        try {\n            T result = null;\n            TreeSet<T> remoteVersions = versions.get(gameVersion);\n            for (T it : remoteVersions)\n                if (remoteVersion.equals(it.getSelfVersion()))\n                    result = it;\n            if (result == null)\n                for (T it : remoteVersions)\n                    if (remoteVersion.equals(it.getFullVersion()))\n                        result = it;\n            return Optional.ofNullable(result);\n        } finally {\n            lock.readLock().unlock();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionMismatchException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download;\n\npublic class VersionMismatchException extends Exception {\n\n    private final String expect, actual;\n\n    public VersionMismatchException(String expect, String actual) {\n        super(\"Mismatched game version requirement, library requires game to be \" + expect + \", but actual is \" + actual);\n        this.expect = expect;\n        this.actual = actual;\n    }\n\n    public String getExpect() {\n        return expect;\n    }\n\n    public String getActual() {\n        return actual;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.cleanroom;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.UnsupportedInstallationException;\nimport org.jackhuang.hmcl.download.VersionMismatchException;\nimport org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile;\nimport org.jackhuang.hmcl.download.forge.ForgeNewInstallTask;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\n\nimport java.io.IOException;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Optional;\n\npublic final class CleanroomInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final CleanroomRemoteVersion remote;\n    private Path installer;\n    private FileDownloadTask dependent;\n    private Task<Version> task;\n    private String selfVersion;\n\n    public CleanroomInstallTask(DefaultDependencyManager dependencyManager, Version version, CleanroomRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remote = remoteVersion;\n\n        setSignificance(TaskSignificance.MODERATE);\n    }\n\n    public CleanroomInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.selfVersion = selfVersion;\n        this.remote = null;\n        this.installer = installer;\n\n        setSignificance(TaskSignificance.MODERATE);\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        if (installer == null) {\n            installer = Files.createTempFile(\"cleanroom-installer\", \".jar\");\n\n            dependent = new FileDownloadTask(\n                    dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),\n                    installer, null);\n            dependent.setCacheRepository(dependencyManager.getCacheRepository());\n            dependent.setCaching(true);\n            dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);\n        }\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        if (remote != null) {\n            Files.deleteIfExists(installer);\n        }\n\n        setResult(task.getResult());\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependent == null ? Collections.emptySet() : Collections.singleton(dependent);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return Collections.singleton(task);\n    }\n\n    @Override\n    public void execute() throws IOException, VersionMismatchException, UnsupportedInstallationException {\n        if (selfVersion == null) {\n            task = new ForgeNewInstallTask(dependencyManager, version, remote.getSelfVersion(), installer).thenApplyAsync((version) -> version.setId(LibraryAnalyzer.LibraryType.CLEANROOM.getPatchId()));\n        } else {\n            task = new ForgeNewInstallTask(dependencyManager, version, selfVersion, installer).thenApplyAsync((version) -> version.setId(LibraryAnalyzer.LibraryType.CLEANROOM.getPatchId()));\n        }\n    }\n\n    public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {\n        Optional<String> gameVersion = dependencyManager.getGameRepository().getGameVersion(version);\n        if (gameVersion.isEmpty()) throw new IOException();\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {\n            String installProfileText = Files.readString(fs.getPath(\"install_profile.json\"));\n            Map<?, ?> installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class);\n            if (LibraryAnalyzer.LibraryType.CLEANROOM.getPatchId().equals(installProfile.get(\"profile\"))) {\n                ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);\n                if (!gameVersion.get().equals(profile.getMinecraft()))\n                    throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());\n                return new CleanroomInstallTask(dependencyManager, version, modifyVersion(profile.getVersion()), installer);\n            } else {\n                throw new IOException();\n            }\n        }\n    }\n\n    private static String modifyVersion(String version) {\n        return version.replace(\"cleanroom-\", \"\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.cleanroom;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.time.Instant;\nimport java.util.List;\n\npublic class CleanroomRemoteVersion extends RemoteVersion {\n    public CleanroomRemoteVersion(String gameVersion, String selfVersion, Instant releaseDate, List<String> url) {\n        super(LibraryAnalyzer.LibraryType.CLEANROOM.getPatchId(), gameVersion, selfVersion, releaseDate, url);\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new CleanroomInstallTask(dependencyManager, baseVersion, this);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.cleanroom;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.time.Instant;\nimport java.util.Collections;\n\npublic final class CleanroomVersionList extends VersionList<CleanroomRemoteVersion> {\n    private final DownloadProvider downloadProvider;\n    private static final String LOADER_LIST_URL = \"https://hmcl.glavo.site/metadata/cleanroom/index.json\";\n    private static final String INSTALLER_URL = \"https://hmcl.glavo.site/metadata/cleanroom/files/cleanroom-%s-installer.jar\";\n\n    public CleanroomVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return Task.allOf(\n                new GetTask(downloadProvider.injectURLWithCandidates(LOADER_LIST_URL)).thenGetJsonAsync(ReleaseResult[].class)\n        ).thenAcceptAsync(results -> {\n            lock.writeLock().lock();\n\n            try {\n                versions.clear();\n                for (ReleaseResult version : results.get(0)) {\n                    versions.put(\"1.12.2\", new CleanroomRemoteVersion(\n                            \"1.12.2\", version.name, Instant.parse(version.created_at),\n                            Collections.singletonList(\n                                    String.format(INSTALLER_URL, version.name)\n                            )\n                    ));\n                }\n            } finally {\n                lock.writeLock().unlock();\n            }\n        });\n    }\n\n    private final static class ReleaseResult {\n        String name;\n        String created_at;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.fabric;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * <b>Note</b>: Fabric should be installed first.\n *\n * @author huangyuhui\n */\npublic final class FabricAPIInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final FabricAPIRemoteVersion remote;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    public FabricAPIInstallTask(DefaultDependencyManager dependencyManager, Version version, FabricAPIRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remote = remoteVersion;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws IOException {\n        dependencies.add(new FileDownloadTask(\n                remote.getVersion().getFile().getUrl(),\n                dependencyManager.getGameRepository().getModsDirectory(version.getId()).resolve(\"fabric-api-\" + remote.getVersion().getVersion() + \".jar\"),\n                remote.getVersion().getFile().getIntegrityCheck())\n        );\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.fabric;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.time.Instant;\nimport java.util.List;\n\npublic class FabricAPIRemoteVersion extends RemoteVersion {\n    private final String fullVersion;\n    private final RemoteMod.Version version;\n\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param urls        the installer or universal jar original URL.\n     */\n    FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List<String> urls) {\n        super(LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);\n\n        this.fullVersion = fullVersion;\n        this.version = version;\n    }\n\n    @Override\n    public String getFullVersion() {\n        return fullVersion;\n    }\n\n    public RemoteMod.Version getVersion() {\n        return version;\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new FabricAPIInstallTask(dependencyManager, baseVersion, this);\n    }\n\n    @Override\n    public int compareTo(RemoteVersion o) {\n        if (!(o instanceof FabricAPIRemoteVersion)) return 0;\n        return -this.getReleaseDate().compareTo(o.getReleaseDate());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.fabric;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Lang;\n\nimport java.util.Collections;\n\npublic class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {\n\n    private final DownloadProvider downloadProvider;\n\n    public FabricAPIVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return Task.runAsync(() -> {\n            for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById(downloadProvider, \"P7dR8mSH\"))) {\n                for (String gameVersion : modVersion.getGameVersions()) {\n                    versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,\n                            Collections.singletonList(modVersion.getFile().getUrl())));\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.fabric;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.UnsupportedInstallationException;\nimport org.jackhuang.hmcl.game.Arguments;\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.download.UnsupportedInstallationException.FABRIC_NOT_COMPATIBLE_WITH_FORGE;\n\n/**\n * <b>Note</b>: Fabric should be installed first.\n *\n * @author huangyuhui\n */\npublic final class FabricInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final FabricRemoteVersion remote;\n    private final GetTask launchMetaTask;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    public FabricInstallTask(DefaultDependencyManager dependencyManager, Version version, FabricRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remote = remoteVersion;\n\n        launchMetaTask = new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()));\n        launchMetaTask.setCacheRepository(dependencyManager.getCacheRepository());\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        if (!Objects.equals(\"net.minecraft.client.main.Main\", version.resolve(dependencyManager.getGameRepository()).getMainClass()))\n            throw new UnsupportedInstallationException(FABRIC_NOT_COMPATIBLE_WITH_FORGE);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return Collections.singleton(launchMetaTask);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws IOException {\n        FabricInfo fabricInfo = JsonUtils.GSON.fromJson(launchMetaTask.getResult(), FabricInfo.class);\n        if (fabricInfo == null)\n            throw new IOException(\"Fabric metadata is invalid\");\n\n        setResult(getPatch(fabricInfo, remote.getGameVersion(), remote.getSelfVersion()));\n\n        dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));\n    }\n\n    private Version getPatch(FabricInfo fabricInfo, String gameVersion, String loaderVersion) {\n        JsonObject launcherMeta = fabricInfo.launcherMeta;\n        Arguments arguments = new Arguments();\n\n        String mainClass;\n        if (!launcherMeta.get(\"mainClass\").isJsonObject()) {\n            mainClass = launcherMeta.get(\"mainClass\").getAsString();\n        } else {\n            mainClass = launcherMeta.get(\"mainClass\").getAsJsonObject().get(\"client\").getAsString();\n        }\n\n        if (launcherMeta.has(\"launchwrapper\")) {\n            String clientTweaker = launcherMeta.get(\"launchwrapper\").getAsJsonObject().get(\"tweakers\").getAsJsonObject().get(\"client\").getAsJsonArray().get(0).getAsString();\n            arguments = arguments.addGameArguments(\"--tweakClass\", clientTweaker);\n        }\n\n        JsonObject librariesObject = launcherMeta.getAsJsonObject(\"libraries\");\n        List<Library> libraries = new ArrayList<>();\n\n        // \"common, server\" is hard coded in fabric installer.\n        // Don't know the purpose of ignoring client libraries.\n        for (String side : new String[]{\"common\", \"server\"}) {\n            for (JsonElement element : librariesObject.getAsJsonArray(side)) {\n                libraries.add(JsonUtils.GSON.fromJson(element, Library.class));\n            }\n        }\n\n        libraries.add(new Library(Artifact.fromDescriptor(fabricInfo.intermediary.maven), \"https://maven.fabricmc.net/\", null));\n        libraries.add(new Library(Artifact.fromDescriptor(fabricInfo.loader.maven), \"https://maven.fabricmc.net/\", null));\n\n        return new Version(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), loaderVersion, Version.PRIORITY_LOADER, arguments, mainClass, libraries);\n    }\n\n    @JsonSerializable\n    public static class FabricInfo {\n        private final LoaderInfo loader;\n        private final IntermediaryInfo intermediary;\n        private final JsonObject launcherMeta;\n\n        public FabricInfo(LoaderInfo loader, IntermediaryInfo intermediary, JsonObject launcherMeta) {\n            this.loader = loader;\n            this.intermediary = intermediary;\n            this.launcherMeta = launcherMeta;\n        }\n\n        public LoaderInfo getLoader() {\n            return loader;\n        }\n\n        public IntermediaryInfo getIntermediary() {\n            return intermediary;\n        }\n\n        public JsonObject getLauncherMeta() {\n            return launcherMeta;\n        }\n    }\n\n    @JsonSerializable\n    public static class LoaderInfo {\n        private final String separator;\n        private final int build;\n        private final String maven;\n        private final String version;\n        private final boolean stable;\n\n        public LoaderInfo(String separator, int build, String maven, String version, boolean stable) {\n            this.separator = separator;\n            this.build = build;\n            this.maven = maven;\n            this.version = version;\n            this.stable = stable;\n        }\n\n        public String getSeparator() {\n            return separator;\n        }\n\n        public int getBuild() {\n            return build;\n        }\n\n        public String getMaven() {\n            return maven;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        public boolean isStable() {\n            return stable;\n        }\n    }\n\n    @JsonSerializable\n    public static class IntermediaryInfo {\n        private final String maven;\n        private final String version;\n        private final boolean stable;\n\n        public IntermediaryInfo(String maven, String version, boolean stable) {\n            this.maven = maven;\n            this.version = version;\n            this.stable = stable;\n        }\n\n        public String getMaven() {\n            return maven;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        public boolean isStable() {\n            return stable;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.fabric;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.util.List;\n\npublic class FabricRemoteVersion extends RemoteVersion {\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param urls        the installer or universal jar original URL.\n     */\n    FabricRemoteVersion(String gameVersion, String selfVersion, List<String> urls) {\n        super(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), gameVersion, selfVersion, null, urls);\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new FabricInstallTask(dependencyManager, baseVersion, this);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.fabric;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\n\npublic final class FabricVersionList extends VersionList<FabricRemoteVersion> {\n    private final DownloadProvider downloadProvider;\n\n    public FabricVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return Task.runAsync(() -> {\n            List<String> gameVersions = getGameVersions(GAME_META_URL);\n            List<String> loaderVersions = getGameVersions(LOADER_META_URL);\n\n            lock.writeLock().lock();\n\n            try {\n                for (String gameVersion : gameVersions)\n                    for (String loaderVersion : loaderVersions)\n                        versions.put(gameVersion, new FabricRemoteVersion(gameVersion, loaderVersion,\n                                Collections.singletonList(getLaunchMetaUrl(gameVersion, loaderVersion))));\n            } finally {\n                lock.writeLock().unlock();\n            }\n        });\n    }\n\n    private static final String LOADER_META_URL = \"https://meta.fabricmc.net/v2/versions/loader\";\n    private static final String GAME_META_URL = \"https://meta.fabricmc.net/v2/versions/game\";\n\n    private List<String> getGameVersions(String metaUrl) throws IOException {\n        String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl));\n        return JsonUtils.GSON.fromJson(json, listTypeOf(GameVersion.class))\n                .stream().map(GameVersion::getVersion).collect(Collectors.toList());\n    }\n\n    private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) {\n        return String.format(\"https://meta.fabricmc.net/v2/versions/loader/%s/%s\", gameVersion, loaderVersion);\n    }\n\n    private static class GameVersion {\n        private final String version;\n        private final String maven;\n        private final boolean stable;\n\n        public GameVersion() {\n            this(\"\", null, false);\n        }\n\n        public GameVersion(String version, String maven, boolean stable) {\n            this.version = version;\n            this.maven = maven;\n            this.stable = stable;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        @Nullable\n        public String getMaven() {\n            return maven;\n        }\n\n        public boolean isStable() {\n            return stable;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.time.Instant;\nimport java.time.format.DateTimeParseException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion> {\n    private final String apiRoot;\n\n    /**\n     * @param apiRoot API Root of BMCLAPI implementations\n     */\n    public ForgeBMCLVersionList(String apiRoot) {\n        this.apiRoot = apiRoot;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        throw new UnsupportedOperationException(\"ForgeBMCLVersionList does not support loading the entire Forge remote version list.\");\n    }\n\n    private static String toLookupVersion(String gameVersion) {\n        return \"1.7.10-pre4\".equals(gameVersion) ? \"1.7.10_pre4\" : gameVersion;\n    }\n\n    private static String fromLookupVersion(String lookupVersion) {\n        return \"1.7.10_pre4\".equals(lookupVersion) ? \"1.7.10-pre4\" : lookupVersion;\n    }\n\n    private static String toLookupBranch(String gameVersion, String branch) {\n        if (\"1.7.10-pre4\".equals(gameVersion)) {\n            return \"prerelease\";\n        }\n        return Lang.requireNonNullElse(branch, \"\");\n    }\n\n    @Override\n    public Task<?> refreshAsync(String gameVersion) {\n        String lookupVersion = toLookupVersion(gameVersion);\n\n        return new GetTask(apiRoot + \"/forge/minecraft/\" + lookupVersion).thenGetJsonAsync(listTypeOf(ForgeVersion.class))\n                .thenAcceptAsync(forgeVersions -> {\n                    lock.writeLock().lock();\n                    try {\n                        versions.clear(gameVersion);\n                        if (forgeVersions == null) return;\n                        for (ForgeVersion version : forgeVersions) {\n                            if (version == null)\n                                continue;\n                            List<String> urls = new ArrayList<>();\n                            for (ForgeVersion.File file : version.getFiles())\n                                if (\"installer\".equals(file.getCategory()) && \"jar\".equals(file.getFormat())) {\n                                    String branch = toLookupBranch(gameVersion, version.getBranch());\n\n                                    String classifier = lookupVersion + \"-\" + version.getVersion() + (branch.isEmpty() ? \"\" : '-' + branch);\n                                    String fileName1 = \"forge-\" + classifier + \"-\" + file.getCategory() + \".\" + file.getFormat();\n                                    String fileName2 = \"forge-\" + classifier + \"-\" + lookupVersion + \"-\" + file.getCategory() + \".\" + file.getFormat();\n                                    urls.add(\"https://files.minecraftforge.net/maven/net/minecraftforge/forge/\" + classifier + \"/\" + fileName1);\n                                    urls.add(\"https://files.minecraftforge.net/maven/net/minecraftforge/forge/\" + classifier + \"-\" + lookupVersion + \"/\" + fileName2);\n                                    urls.add(NetworkUtils.withQuery(\"https://bmclapi2.bangbang93.com/forge/download\", mapOf(\n                                            pair(\"mcversion\", version.getGameVersion()),\n                                            pair(\"version\", version.getVersion()),\n                                            pair(\"branch\", branch),\n                                            pair(\"category\", file.getCategory()),\n                                            pair(\"format\", file.getFormat())\n                                    )));\n                                }\n\n                            if (urls.isEmpty())\n                                continue;\n\n                            Instant releaseDate = null;\n                            if (version.getModified() != null) {\n                                try {\n                                    releaseDate = Instant.parse(version.getModified());\n                                } catch (DateTimeParseException e) {\n                                    LOG.warning(\"Failed to parse instant \" + version.getModified(), e);\n                                }\n                            }\n\n                            versions.put(gameVersion, new ForgeRemoteVersion(\n                                    fromLookupVersion(version.getGameVersion()), version.getVersion(), releaseDate, urls));\n                        }\n                    } finally {\n                        lock.writeLock().unlock();\n                    }\n                });\n    }\n\n    @Override\n    public Optional<ForgeRemoteVersion> getVersion(String gameVersion, String remoteVersion) {\n        remoteVersion = StringUtils.substringAfter(remoteVersion, \"-\", remoteVersion);\n        return super.getVersion(gameVersion, remoteVersion);\n    }\n\n    @Immutable\n    public static final class ForgeVersion implements Validation {\n\n        private final String branch;\n        private final int build;\n        private final String mcversion;\n        private final String modified;\n        private final String version;\n        private final List<File> files;\n\n        /**\n         * No-arg constructor for Gson.\n         */\n        @SuppressWarnings(\"unused\")\n        public ForgeVersion() {\n            this(null, 0, \"\", null, \"\", Collections.emptyList());\n        }\n\n        public ForgeVersion(String branch, int build, String mcversion, String modified, String version, List<File> files) {\n            this.branch = branch;\n            this.build = build;\n            this.mcversion = mcversion;\n            this.modified = modified;\n            this.version = version;\n            this.files = files;\n        }\n\n        @Nullable\n        public String getBranch() {\n            return branch;\n        }\n\n        public int getBuild() {\n            return build;\n        }\n\n        @NotNull\n        public String getGameVersion() {\n            return mcversion;\n        }\n\n        @Nullable\n        public String getModified() {\n            return modified;\n        }\n\n        @NotNull\n        public String getVersion() {\n            return version;\n        }\n\n        @NotNull\n        public List<File> getFiles() {\n            return files;\n        }\n\n        @Override\n        public void validate() throws JsonParseException {\n            if (files == null)\n                throw new JsonParseException(\"ForgeVersion files cannot be null\");\n            if (version == null)\n                throw new JsonParseException(\"ForgeVersion version cannot be null\");\n            if (mcversion == null)\n                throw new JsonParseException(\"ForgeVersion mcversion cannot be null\");\n        }\n\n        @Immutable\n        public static final class File {\n            private final String format;\n            private final String category;\n            private final String hash;\n\n            public File() {\n                this(\"\", \"\", \"\");\n            }\n\n            public File(String format, String category, String hash) {\n                this.format = format;\n                this.category = category;\n                this.hash = hash;\n            }\n\n            public String getFormat() {\n                return format;\n            }\n\n            public String getCategory() {\n                return category;\n            }\n\n            public String getHash() {\n                return hash;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstall.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.util.Immutable;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class ForgeInstall {\n\n    private final String profileName;\n    private final String target;\n    private final Artifact path;\n    private final String version;\n    private final String filePath;\n    private final String welcome;\n    private final String minecraft;\n    private final String mirrorList;\n    private final String logo;\n\n    public ForgeInstall() {\n        this(null, null, null, null, null, null, null, null, null);\n    }\n\n    public ForgeInstall(String profileName, String target, Artifact path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) {\n        this.profileName = profileName;\n        this.target = target;\n        this.path = path;\n        this.version = version;\n        this.filePath = filePath;\n        this.welcome = welcome;\n        this.minecraft = minecraft;\n        this.mirrorList = mirrorList;\n        this.logo = logo;\n    }\n\n    public String getProfileName() {\n        return profileName;\n    }\n\n    public String getTarget() {\n        return target;\n    }\n\n    public Artifact getPath() {\n        return path;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public String getFilePath() {\n        return filePath;\n    }\n\n    public String getWelcome() {\n        return welcome;\n    }\n\n    public String getMinecraft() {\n        return minecraft;\n    }\n\n    public String getMirrorList() {\n        return mirrorList;\n    }\n\n    public String getLogo() {\n        return logo;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class ForgeInstallProfile implements Validation {\n\n    @SerializedName(\"install\")\n    private final ForgeInstall install;\n\n    @SerializedName(\"versionInfo\")\n    private final Version versionInfo;\n\n    public ForgeInstallProfile(ForgeInstall install, Version versionInfo) {\n        this.install = install;\n        this.versionInfo = versionInfo;\n    }\n\n    public ForgeInstall getInstall() {\n        return install;\n    }\n\n    public Version getVersionInfo() {\n        return versionInfo;\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (install == null)\n            throw new JsonParseException(\"InstallProfile install cannot be null\");\n\n        if (versionInfo == null)\n            throw new JsonParseException(\"InstallProfile versionInfo cannot be null\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport org.jackhuang.hmcl.download.*;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.io.IOException;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.download.UnsupportedInstallationException.UNSUPPORTED_LAUNCH_WRAPPER;\nimport static org.jackhuang.hmcl.util.StringUtils.removePrefix;\nimport static org.jackhuang.hmcl.util.StringUtils.removeSuffix;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class ForgeInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private Path installer;\n    private final ForgeRemoteVersion remote;\n    private FileDownloadTask dependent;\n    private Task<Version> dependency;\n\n    public ForgeInstallTask(DefaultDependencyManager dependencyManager, Version version, ForgeRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remote = remoteVersion;\n        setSignificance(TaskSignificance.MODERATE);\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        installer = Files.createTempFile(\"forge-installer\", \".jar\");\n\n        dependent = new FileDownloadTask(\n                dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),\n                installer, null);\n        dependent.setCacheRepository(dependencyManager.getCacheRepository());\n        dependent.setCaching(true);\n        dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        Files.deleteIfExists(installer);\n        setResult(dependency.getResult());\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return Collections.singleton(dependent);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return Collections.singleton(dependency);\n    }\n\n    @Override\n    public void execute() throws IOException, VersionMismatchException, UnsupportedInstallationException {\n        String originalMainClass = version.resolve(dependencyManager.getGameRepository()).getMainClass();\n        if (GameVersionNumber.compare(\"1.13\", remote.getGameVersion()) <= 0) {\n            // Forge 1.13 is not compatible with fabric.\n            if (!LibraryAnalyzer.FORGE_OPTIFINE_MAIN.contains(originalMainClass))\n                throw new UnsupportedInstallationException(UNSUPPORTED_LAUNCH_WRAPPER);\n        }\n\n        if (detectForgeInstallerType(dependencyManager, version, installer))\n            dependency = new ForgeNewInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);\n        else\n            dependency = new ForgeOldInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);\n    }\n\n    /**\n     * Detect Forge installer type.\n     *\n     * @param dependencyManager game repository\n     * @param version version.json\n     * @param installer the Forge installer, either the new or old one.\n     * @return true for new, false for old\n     * @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.\n     * @throws VersionMismatchException if required game version of installer does not match the actual one.\n     */\n    public static boolean detectForgeInstallerType(DependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {\n        Optional<String> gameVersion = dependencyManager.getGameRepository().getGameVersion(version);\n        if (!gameVersion.isPresent()) throw new IOException();\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {\n            String installProfileText = Files.readString(fs.getPath(\"install_profile.json\"));\n            Map<?, ?> installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class);\n            if (installProfile.containsKey(\"spec\")) {\n                ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);\n                if (!gameVersion.get().equals(profile.getMinecraft()))\n                    throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());\n                return true;\n            } else if (installProfile.containsKey(\"install\") && installProfile.containsKey(\"versionInfo\")) {\n                ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class);\n                if (!gameVersion.get().equals(profile.getInstall().getMinecraft()))\n                    throw new VersionMismatchException(profile.getInstall().getMinecraft(), gameVersion.get());\n                return false;\n            } else {\n                throw new IOException();\n            }\n        }\n    }\n\n    /**\n     * Install Forge library from existing local file.\n     * This method will try to identify this installer whether it is in old or new format.\n     *\n     * @param dependencyManager game repository\n     * @param version version.json\n     * @param installer the Forge installer, either the new or old one.\n     * @return the task to install library\n     * @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.\n     * @throws VersionMismatchException if required game version of installer does not match the actual one.\n     */\n    public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {\n        Optional<String> gameVersion = dependencyManager.getGameRepository().getGameVersion(version);\n        if (!gameVersion.isPresent()) throw new IOException();\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {\n            String installProfileText = Files.readString(fs.getPath(\"install_profile.json\"));\n            Map<?, ?> installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class);\n            if (installProfile.containsKey(\"spec\")) {\n                ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);\n                if (!gameVersion.get().equals(profile.getMinecraft()))\n                    throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());\n                return new ForgeNewInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getVersion()), installer);\n            } else if (installProfile.containsKey(\"install\") && installProfile.containsKey(\"versionInfo\")) {\n                ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class);\n                if (!gameVersion.get().equals(profile.getInstall().getMinecraft()))\n                    throw new VersionMismatchException(profile.getInstall().getMinecraft(), gameVersion.get());\n                return new ForgeOldInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getInstall().getPath().getVersion().replaceAll(\"(?i)forge\", \"\")), installer);\n            } else {\n                throw new IOException();\n            }\n        }\n    }\n\n    private static String modifyVersion(String gameVersion, String version) {\n        return removePrefix(removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, \"\").trim(), \"-\"), \"-\"), \"_\"), \"_\"), \"forge-\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallProfile.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n@Immutable\npublic class ForgeNewInstallProfile implements Validation {\n\n    private final int spec;\n    private final String minecraft;\n    private final String json;\n    private final String version;\n    private final Artifact path;\n    private final List<Library> libraries;\n    private final List<Processor> processors;\n    private final Map<String, Datum> data;\n\n    public ForgeNewInstallProfile(int spec, String minecraft, String json, String version, Artifact path, List<Library> libraries, List<Processor> processors, Map<String, Datum> data) {\n        this.spec = spec;\n        this.minecraft = minecraft;\n        this.json = json;\n        this.version = version;\n        this.path = path;\n        this.libraries = libraries;\n        this.processors = processors;\n        this.data = data;\n    }\n\n    /**\n     * Specification for install_profile.json.\n     */\n    public int getSpec() {\n        return spec;\n    }\n\n    /**\n     * Vanilla game version that this installer supports.\n     */\n    public String getMinecraft() {\n        return minecraft;\n    }\n\n    /**\n     * Version json to be installed.\n     * @return path of the version json relative to the installer JAR file.\n     */\n    public String getJson() {\n        return json;\n    }\n\n    /**\n     *\n     * @return forge version.\n     */\n    public String getVersion() {\n        return version;\n    }\n\n    /**\n     * Maven artifact path for the main jar to install.\n     * @return artifact path of the main jar.\n     */\n    public Optional<Artifact> getPath() {\n        return Optional.ofNullable(path);\n    }\n\n    /**\n     * Libraries that processors depend on.\n     * @return the required dependencies.\n     */\n    public List<Library> getLibraries() {\n        return libraries == null ? Collections.emptyList() : libraries;\n    }\n\n    /**\n     * Tasks to be executed to setup modded environment.\n     */\n    public List<Processor> getProcessors() {\n        if (processors == null) return Collections.emptyList();\n        return processors.stream().filter(p -> p.isSide(\"client\")).collect(Collectors.toList());\n    }\n\n    /**\n     * Data for processors.\n     *\n     * @return a mutable data map for processors.\n     */\n    public Map<String, String> getData() {\n        if (data == null)\n            return new HashMap<>();\n\n        return data.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getClient()));\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        if (minecraft == null || json == null || version == null)\n            throw new JsonParseException(\"ForgeNewInstallProfile is malformed\");\n    }\n\n    public static class Processor implements Validation {\n        private final List<String> sides;\n        private final Artifact jar;\n        private final List<Artifact> classpath;\n        private final List<String> args;\n        private final Map<String, String> outputs;\n\n        public Processor(List<String> sides, Artifact jar, List<Artifact> classpath, List<String> args, Map<String, String> outputs) {\n            this.sides = sides;\n            this.jar = jar;\n            this.classpath = classpath;\n            this.args = args;\n            this.outputs = outputs;\n        }\n\n        /**\n         * Check which side this processor should be run on. We only support client install currently.\n         * @param side can be one of \"client\", \"server\", \"extract\".\n         * @return true if the processor can run on the side.\n         */\n        public boolean isSide(String side) {\n            return sides == null || sides.contains(side);\n        }\n\n        /**\n         * The executable jar of this processor task. Will be executed in installation process.\n         * @return the artifact path of executable jar.\n         */\n        public Artifact getJar() {\n            return jar;\n        }\n\n        /**\n         * The dependencies of this processor task.\n         * @return the artifact path of dependencies.\n         */\n        public List<Artifact> getClasspath() {\n            return classpath == null ? Collections.emptyList() : classpath;\n        }\n\n        /**\n         * Arguments to pass to the processor jar.\n         * Each item can be in one of the following formats:\n         * [artifact]: An artifact path, used for locating files.\n         * {entry}: Get corresponding value of the entry in {@link ForgeNewInstallProfile#getData()}\n         * {MINECRAFT_JAR}: path of the Minecraft jar.\n         * {SIDE}: values other than \"client\" will be ignored.\n         * @return arguments to pass to the processor jar.\n         * @see ForgeNewInstallTask#parseLiteral(String, Map, ExceptionalFunction)\n         */\n        public List<String> getArgs() {\n            return args == null ? Collections.emptyList() : args;\n        }\n\n        /**\n         * File-checksum pairs, used for verifying the output file is correct.\n         * Arguments to pass to the processor jar.\n         * Keys can be in one of [artifact] or {entry}. Should be file path.\n         * Values can be in one of {entry} or 'literal'. Should be SHA-1 checksum.\n         * @return files output from this processor.\n         * @see ForgeNewInstallTask#parseLiteral(String, Map, ExceptionalFunction)\n         */\n        public Map<String, String> getOutputs() {\n            return outputs == null ? Collections.emptyMap() : outputs;\n        }\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            if (jar == null)\n                throw new JsonParseException(\"Processor::jar cannot be null\");\n        }\n    }\n\n    public static class Datum {\n        private final String client;\n\n        public Datum(String client) {\n            this.client = client;\n        }\n\n        /**\n         * Can be in the following formats:\n         * [value]: An artifact path.\n         * 'value': A string literal.\n         * value: A file in the installer package, to be extracted to a temp folder, and then have the absolute path in replacements.\n         * @return Value to use for the client install\n         */\n        public String getClient() {\n            return client;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport org.jackhuang.hmcl.download.ArtifactMalformedException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile.Processor;\nimport org.jackhuang.hmcl.download.game.GameLibrariesTask;\nimport org.jackhuang.hmcl.download.game.VersionJsonDownloadTask;\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.game.DownloadInfo;\nimport org.jackhuang.hmcl.game.DownloadType;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.ChecksumMismatchException;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.CommandBuilder;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\nimport java.util.zip.ZipException;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson;\n\npublic class ForgeNewInstallTask extends Task<Version> {\n\n    private class ProcessorTask extends Task<Void> {\n\n        private Processor processor;\n        private Map<String, String> vars;\n\n        public ProcessorTask(@NotNull Processor processor, @NotNull Map<String, String> vars) {\n            this.processor = processor;\n            this.vars = vars;\n            setSignificance(TaskSignificance.MODERATE);\n        }\n\n        @Override\n        public void execute() throws Exception {\n            Map<String, String> outputs = new HashMap<>();\n            boolean miss = false;\n\n            for (Map.Entry<String, String> entry : processor.getOutputs().entrySet()) {\n                String key = entry.getKey();\n                String value = entry.getValue();\n\n                key = parseLiteral(key, vars);\n                value = parseLiteral(value, vars);\n\n                if (key == null || value == null) {\n                    throw new ArtifactMalformedException(\"Invalid forge installation configuration\");\n                }\n\n                outputs.put(key, value);\n\n                Path artifact = Paths.get(key);\n                if (Files.exists(artifact)) {\n                    String code;\n                    try (InputStream stream = Files.newInputStream(artifact)) {\n                        code = (DigestUtils.digestToString(\"SHA-1\", stream));\n                    }\n\n                    if (!Objects.equals(code, value)) {\n                        Files.delete(artifact);\n                        LOG.info(\"Found existing file is not valid: \" + artifact);\n\n                        miss = true;\n                    }\n                } else {\n                    miss = true;\n                }\n            }\n\n            if (!processor.getOutputs().isEmpty() && !miss) {\n                return;\n            }\n\n            Path jar = gameRepository.getArtifactFile(version, processor.getJar());\n            if (!Files.isRegularFile(jar))\n                throw new FileNotFoundException(\"Game processor file not found, should be downloaded in preprocess\");\n\n            String mainClass;\n            try (JarFile jarFile = new JarFile(jar.toFile())) {\n                mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);\n            }\n\n            if (StringUtils.isBlank(mainClass))\n                throw new Exception(\"Game processor jar does not have main class \" + jar);\n\n            List<String> command = new ArrayList<>();\n            command.add(JavaRuntime.getDefault().getBinary().toString());\n            command.add(\"-cp\");\n\n            List<String> classpath = new ArrayList<>(processor.getClasspath().size() + 1);\n            for (Artifact artifact : processor.getClasspath()) {\n                Path file = gameRepository.getArtifactFile(version, artifact);\n                if (!Files.isRegularFile(file))\n                    throw new Exception(\"Game processor dependency missing\");\n                classpath.add(file.toString());\n            }\n            classpath.add(jar.toString());\n            command.add(String.join(File.pathSeparator, classpath));\n\n            command.add(mainClass);\n\n            List<String> args = new ArrayList<>(processor.getArgs().size());\n            for (String arg : processor.getArgs()) {\n                String parsed = parseLiteral(arg, vars);\n                if (parsed == null)\n                    throw new ArtifactMalformedException(\"Invalid forge installation configuration\");\n                args.add(parsed);\n            }\n\n            command.addAll(args);\n\n            LOG.info(\"Executing external processor \" + processor.getJar().toString() + \", command line: \" + new CommandBuilder().addAll(command).toString());\n            int exitCode = SystemUtils.callExternalProcess(command);\n            if (exitCode != 0)\n                throw new IOException(\"Game processor exited abnormally with code \" + exitCode);\n\n            for (Map.Entry<String, String> entry : outputs.entrySet()) {\n                Path artifact = Paths.get(entry.getKey());\n                if (!Files.isRegularFile(artifact))\n                    throw new FileNotFoundException(\"File missing: \" + artifact);\n\n                String code;\n                try (InputStream stream = Files.newInputStream(artifact)) {\n                    code = DigestUtils.digestToString(\"SHA-1\", stream);\n                }\n\n                if (!Objects.equals(code, entry.getValue())) {\n                    Files.delete(artifact);\n                    throw new ChecksumMismatchException(\"SHA-1\", entry.getValue(), code);\n                }\n            }\n        }\n    }\n\n    private final DefaultDependencyManager dependencyManager;\n    private final DefaultGameRepository gameRepository;\n    private final Version version;\n    private final Path installer;\n    private final List<Task<?>> dependents = new ArrayList<>(1);\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    private ForgeNewInstallProfile profile;\n    private List<Processor> processors;\n    private Version forgeVersion;\n    private final String selfVersion;\n\n    private Path tempDir;\n    private AtomicInteger processorDoneCount = new AtomicInteger(0);\n\n    public ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {\n        this.dependencyManager = dependencyManager;\n        this.gameRepository = dependencyManager.getGameRepository();\n        this.version = version;\n        this.installer = installer;\n        this.selfVersion = selfVersion;\n\n        setSignificance(TaskSignificance.MAJOR);\n    }\n\n    private static String replaceTokens(Map<String, String> tokens, String value) {\n        StringBuilder buf = new StringBuilder();\n        for (int x = 0; x < value.length(); x++) {\n            char c = value.charAt(x);\n            if (c == '\\\\') {\n                if (x == value.length() - 1)\n                    throw new IllegalArgumentException(\"Illegal pattern (Bad escape): \" + value);\n                buf.append(value.charAt(++x));\n            } else if (c == '{' || c == '\\'') {\n                StringBuilder key = new StringBuilder();\n                for (int y = x + 1; y <= value.length(); y++) {\n                    if (y == value.length())\n                        throw new IllegalArgumentException(\"Illegal pattern (Unclosed \" + c + \"): \" + value);\n                    char d = value.charAt(y);\n                    if (d == '\\\\') {\n                        if (y == value.length() - 1)\n                            throw new IllegalArgumentException(\"Illegal pattern (Bad escape): \" + value);\n                        key.append(value.charAt(++y));\n                    } else {\n                        if (c == '{' && d == '}') {\n                            x = y;\n                            break;\n                        }\n                        if (c == '\\'' && d == '\\'') {\n                            x = y;\n                            break;\n                        }\n                        key.append(d);\n                    }\n                }\n                if (c == '\\'') {\n                    buf.append(key);\n                } else {\n                    if (!tokens.containsKey(key.toString()))\n                        throw new IllegalArgumentException(\"Illegal pattern: \" + value + \" Missing Key: \" + key);\n                    buf.append(tokens.get(key.toString()));\n                }\n            } else {\n                buf.append(c);\n            }\n        }\n        return buf.toString();\n    }\n\n    private <E extends Exception> String parseLiteral(String literal, Map<String, String> var, ExceptionalFunction<String, String, E> plainConverter) throws E {\n        if (StringUtils.isSurrounded(literal, \"{\", \"}\"))\n            return var.get(StringUtils.removeSurrounding(literal, \"{\", \"}\"));\n        else if (StringUtils.isSurrounded(literal, \"'\", \"'\"))\n            return StringUtils.removeSurrounding(literal, \"'\");\n        else if (StringUtils.isSurrounded(literal, \"[\", \"]\"))\n            return gameRepository.getArtifactFile(version, Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, \"[\", \"]\"))).toString();\n        else\n            return plainConverter.apply(replaceTokens(var, literal));\n    }\n\n    private String parseLiteral(String literal, Map<String, String> var) {\n        return parseLiteral(literal, var, ExceptionalFunction.identity());\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {\n            profile = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(\"install_profile.json\")), ForgeNewInstallProfile.class);\n            processors = profile.getProcessors();\n            forgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.getJson())), Version.class);\n\n            for (Library library : profile.getLibraries()) {\n                Path file = fs.getPath(\"maven\").resolve(library.getPath());\n                if (Files.exists(file)) {\n                    Path dest = gameRepository.getLibraryFile(version, library);\n                    FileUtils.copyFile(file, dest);\n                }\n            }\n\n            if (profile.getPath().isPresent()) {\n                Path mainJar = profile.getPath().get().getPath(fs.getPath(\"maven\"));\n                if (Files.exists(mainJar)) {\n                    Path dest = gameRepository.getArtifactFile(version, profile.getPath().get());\n                    FileUtils.copyFile(mainJar, dest);\n                }\n            }\n        } catch (ZipException ex) {\n            throw new ArtifactMalformedException(\"Malformed forge installer file\", ex);\n        }\n\n        dependents.add(new GameLibrariesTask(dependencyManager, version, true, profile.getLibraries()));\n    }\n\n    private Map<String, String> parseOptions(List<String> args, Map<String, String> vars) {\n        Map<String, String> options = new LinkedHashMap<>();\n        String optionName = null;\n        for (String arg : args) {\n            if (arg.startsWith(\"--\")) {\n                if (optionName != null) {\n                    options.put(optionName, \"\");\n                }\n                optionName = arg.substring(2);\n            } else {\n                if (optionName == null) {\n                    // ignore\n                } else {\n                    options.put(optionName, parseLiteral(arg, vars));\n                    optionName = null;\n                }\n            }\n        }\n        if (optionName != null) {\n            options.put(optionName, \"\");\n        }\n        return options;\n    }\n\n    private Task<?> patchDownloadMojangMappingsTask(Processor processor, Map<String, String> vars) {\n        Map<String, String> options = parseOptions(processor.getArgs(), vars);\n        if (!\"DOWNLOAD_MOJMAPS\".equals(options.get(\"task\")) || !\"client\".equals(options.get(\"side\")))\n            return null;\n        String version = options.get(\"version\");\n        String output = options.get(\"output\");\n        if (version == null || output == null)\n            return null;\n\n        LOG.info(\"Patching DOWNLOAD_MOJMAPS task\");\n        return new VersionJsonDownloadTask(version, dependencyManager)\n                .thenComposeAsync(json -> {\n                    DownloadInfo mappings = fromNonNullJson(json, Version.class)\n                            .getDownloads().get(DownloadType.CLIENT_MAPPINGS);\n                    if (mappings == null) {\n                        throw new Exception(\"client_mappings download info not found\");\n                    }\n\n                    List<URI> mappingsUrl = dependencyManager.getDownloadProvider()\n                            .injectURLWithCandidates(mappings.getUrl());\n                    var mappingsTask = new FileDownloadTask(\n                            mappingsUrl,\n                            Path.of(output),\n                            FileDownloadTask.IntegrityCheck.of(\"SHA-1\", mappings.getSha1()));\n                    mappingsTask.setCaching(true);\n                    mappingsTask.setCacheRepository(dependencyManager.getCacheRepository());\n                    return mappingsTask;\n                });\n    }\n\n    private Task<?> createProcessorTask(Processor processor, Map<String, String> vars) {\n        Task<?> task = patchDownloadMojangMappingsTask(processor, vars);\n        if (task == null) {\n            task = new ProcessorTask(processor, vars);\n        }\n        task.onDone().register(\n                () -> updateProgress(processorDoneCount.incrementAndGet(), processors.size()));\n        return task;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        tempDir = Files.createTempDirectory(\"forge_installer\");\n\n        Map<String, String> vars = new HashMap<>();\n\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {\n            for (Map.Entry<String, String> entry : profile.getData().entrySet()) {\n                String key = entry.getKey();\n                String value = entry.getValue();\n\n                vars.put(key, parseLiteral(value,\n                        Collections.emptyMap(),\n                        str -> {\n                            Path dest = Files.createTempFile(tempDir, null, null);\n                            FileUtils.copyFile(fs.getPath(str), dest);\n                            return dest.toString();\n                        }));\n            }\n        } catch (ZipException ex) {\n            throw new ArtifactMalformedException(\"Malformed forge installer file\", ex);\n        }\n\n        vars.put(\"SIDE\", \"client\");\n        vars.put(\"MINECRAFT_JAR\", FileUtils.getAbsolutePath(gameRepository.getVersionJar(version)));\n        vars.put(\"MINECRAFT_VERSION\", FileUtils.getAbsolutePath(gameRepository.getVersionJar(version)));\n        vars.put(\"ROOT\", FileUtils.getAbsolutePath(gameRepository.getBaseDirectory()));\n        vars.put(\"INSTALLER\", installer.toAbsolutePath().toString());\n        vars.put(\"LIBRARY_DIR\", FileUtils.getAbsolutePath(gameRepository.getLibrariesDirectory(version)));\n\n        updateProgress(0, processors.size());\n\n        Task<?> processorsTask = Task.runSequentially(\n                processors.stream()\n                        .map(processor -> createProcessorTask(processor, vars))\n                        .toArray(Task<?>[]::new));\n\n        dependencies.add(\n                processorsTask.thenComposeAsync(\n                        dependencyManager.checkLibraryCompletionAsync(forgeVersion, true)));\n\n        setResult(forgeVersion\n                .setPriority(Version.PRIORITY_LOADER)\n                .setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())\n                .setVersion(selfVersion));\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        FileUtils.deleteDirectory(tempDir);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport org.jackhuang.hmcl.download.ArtifactMalformedException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.*;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipException;\nimport java.util.zip.ZipFile;\n\npublic class ForgeOldInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final Path installer;\n    private final String selfVersion;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    ForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.installer = installer;\n        this.selfVersion = selfVersion;\n\n        setSignificance(TaskSignificance.MAJOR);\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        try (ZipFile zipFile = new ZipFile(installer.toFile())) {\n            InputStream stream = zipFile.getInputStream(zipFile.getEntry(\"install_profile.json\"));\n            if (stream == null)\n                throw new ArtifactMalformedException(\"Malformed forge installer file, install_profile.json does not exist.\");\n            ForgeInstallProfile installProfile = JsonUtils.fromNonNullJsonFully(stream, ForgeInstallProfile.class);\n\n            // unpack the universal jar in the installer file.\n            Library forgeLibrary = new Library(installProfile.getInstall().getPath());\n            Path forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary);\n            Files.createDirectories(forgeFile.getParent());\n\n            ZipEntry forgeEntry = zipFile.getEntry(installProfile.getInstall().getFilePath());\n            try (InputStream is = zipFile.getInputStream(forgeEntry);\n                 OutputStream os = Files.newOutputStream(forgeFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {\n                is.transferTo(os);\n            }\n\n            setResult(installProfile.getVersionInfo()\n                    .setPriority(Version.PRIORITY_LOADER)\n                    .setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())\n                    .setVersion(selfVersion));\n            dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo(), true));\n        } catch (ZipException ex) {\n            throw new ArtifactMalformedException(\"Malformed forge installer file\", ex);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.time.Instant;\nimport java.util.List;\n\npublic class ForgeRemoteVersion extends RemoteVersion {\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param url         the installer or universal jar original URL.\n     */\n    public ForgeRemoteVersion(String gameVersion, String selfVersion, Instant releaseDate, List<String> url) {\n        super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, releaseDate, url);\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new ForgeInstallTask(dependencyManager, baseVersion, this);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class ForgeVersion implements Validation {\n\n    private final String branch;\n    private final String mcversion;\n    private final String jobver;\n    private final String version;\n    private final int build;\n    private final long modified;\n    private final String[][] files;\n\n    /**\n     * No-arg constructor for Gson.\n     */\n    @SuppressWarnings(\"unused\")\n    public ForgeVersion() {\n        this(null, null, null, null, 0, 0, null);\n    }\n\n    public ForgeVersion(String branch, String mcversion, String jobver, String version, int build, long modified, String[][] files) {\n        this.branch = branch;\n        this.mcversion = mcversion;\n        this.jobver = jobver;\n        this.version = version;\n        this.build = build;\n        this.modified = modified;\n        this.files = files;\n    }\n\n    public String getBranch() {\n        return branch;\n    }\n\n    public String getGameVersion() {\n        return mcversion;\n    }\n\n    public String getJobver() {\n        return jobver;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public int getBuild() {\n        return build;\n    }\n\n    public long getModified() {\n        return modified;\n    }\n\n    public String[][] getFiles() {\n        return files;\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (files == null)\n            throw new JsonParseException(\"ForgeVersion files cannot be null\");\n        if (version == null)\n            throw new JsonParseException(\"ForgeVersion version cannot be null\");\n        if (mcversion == null)\n            throw new JsonParseException(\"ForgeVersion mcversion cannot be null\");\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.net.URI;\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {\n    private final DownloadProvider downloadProvider;\n\n    public ForgeVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    private static String toLookupVersion(String gameVersion) {\n        return \"1.7.10-pre4\".equals(gameVersion) ? \"1.7.10_pre4\" : gameVersion;\n    }\n\n    private static String fromLookupVersion(String lookupVersion) {\n        return \"1.7.10_pre4\".equals(lookupVersion) ? \"1.7.10-pre4\" : lookupVersion;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return new GetTask(FORGE_LIST).thenGetJsonAsync(ForgeVersionRoot.class)\n                .thenAcceptAsync(root -> {\n                    lock.writeLock().lock();\n\n                    try {\n                        if (root == null)\n                            return;\n                        versions.clear();\n\n                        for (Map.Entry<String, int[]> entry : root.getGameVersions().entrySet()) {\n                            String gameVersion = fromLookupVersion(VersionNumber.normalize(entry.getKey()));\n                            for (int v : entry.getValue()) {\n                                ForgeVersion version = root.getNumber().get(v);\n                                if (version == null)\n                                    continue;\n                                String jar = null;\n                                for (String[] file : version.getFiles())\n                                    if (file.length > 1 && \"installer\".equals(file[1])) {\n                                        String classifier = version.getGameVersion() + \"-\" + version.getVersion()\n                                                + (StringUtils.isNotBlank(version.getBranch()) ? \"-\" + version.getBranch() : \"\");\n                                        String fileName = root.getArtifact() + \"-\" + classifier + \"-\" + file[1] + \".\" + file[0];\n                                        jar = root.getWebPath() + classifier + \"/\" + fileName;\n                                    }\n\n                                if (jar == null)\n                                    continue;\n\n                                versions.put(gameVersion, new ForgeRemoteVersion(\n                                        toLookupVersion(version.getGameVersion()),\n                                        version.getVersion(),\n                                        version.getModified() > 0 ? Instant.ofEpochSecond(version.getModified()) : null,\n                                        Collections.singletonList(jar)\n                                ));\n                            }\n                        }\n                    } finally {\n                        lock.writeLock().unlock();\n                    }\n                });\n    }\n\n    public static final URI FORGE_LIST = URI.create(\"https://hmcl.glavo.site/metadata/forge/\");\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.forge;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class ForgeVersionRoot implements Validation {\n\n    private final String artifact;\n    private final String webpath;\n    private final String adfly;\n    private final String homepage;\n    private final String name;\n    private final Map<String, int[]> branches;\n    private final Map<String, int[]> mcversion;\n    private final Map<String, Integer> promos;\n    private final Map<Integer, ForgeVersion> number;\n\n    /**\n     * No-arg constructor for Gson.\n     */\n    @SuppressWarnings(\"unused\")\n    public ForgeVersionRoot() {\n        this(null, null, null, null, null, null, null, null, null);\n    }\n\n    public ForgeVersionRoot(String artifact, String webpath, String adfly, String homepage, String name, Map<String, int[]> branches, Map<String, int[]> mcversion, Map<String, Integer> promos, Map<Integer, ForgeVersion> number) {\n        this.artifact = artifact;\n        this.webpath = webpath;\n        this.adfly = adfly;\n        this.homepage = homepage;\n        this.name = name;\n        this.branches = branches;\n        this.mcversion = mcversion;\n        this.promos = promos;\n        this.number = number;\n    }\n\n    public String getArtifact() {\n        return artifact;\n    }\n\n    public String getWebPath() {\n        return webpath;\n    }\n\n    public String getAdfly() {\n        return adfly;\n    }\n\n    public String getHomePage() {\n        return homepage;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public Map<String, int[]> getBranches() {\n        return branches;\n    }\n\n    public Map<String, int[]> getGameVersions() {\n        return mcversion;\n    }\n\n    public Map<String, Integer> getPromos() {\n        return promos;\n    }\n\n    public Map<Integer, ForgeVersion> getNumber() {\n        return number;\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (number == null)\n            throw new JsonParseException(\"ForgeVersionRoot number cannot be null\");\n        if (mcversion == null)\n            throw new JsonParseException(\"ForgeVersionRoot mcversion cannot be null\");\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.AbstractDependencyManager;\nimport org.jackhuang.hmcl.game.AssetIndex;\nimport org.jackhuang.hmcl.game.AssetIndexInfo;\nimport org.jackhuang.hmcl.game.AssetObject;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.CacheRepository;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class GameAssetDownloadTask extends Task<Void> {\n    \n    private final AbstractDependencyManager dependencyManager;\n    private final Version version;\n    private final AssetIndexInfo assetIndexInfo;\n    private final Path assetIndexFile;\n    private final boolean integrityCheck;\n    private final List<Task<?>> dependents = new ArrayList<>(1);\n    private final List<Task<?>> dependencies = new ArrayList<>();\n\n    /**\n     * Constructor.\n     *\n     * @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}\n     * @param version the game version\n     */\n    public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version, boolean forceDownloadingIndex, boolean integrityCheck) {\n        this.dependencyManager = dependencyManager;\n        this.version = version.resolve(dependencyManager.getGameRepository());\n        this.assetIndexInfo = this.version.getAssetIndex();\n        this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());\n        this.integrityCheck = integrityCheck;\n\n        setStage(\"hmcl.install.assets\");\n        dependents.add(new GameAssetIndexDownloadTask(dependencyManager, this.version, forceDownloadingIndex));\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        AssetIndex index;\n        try {\n            index = JsonUtils.fromNonNullJson(Files.readString(assetIndexFile), AssetIndex.class);\n        } catch (IOException | JsonParseException e) {\n            throw new GameAssetIndexDownloadTask.GameAssetIndexMalformedException();\n        }\n\n        int progress = 0;\n        for (AssetObject assetObject : index.getObjects().values()) {\n            if (isCancelled())\n                throw new InterruptedException();\n\n            Path file = dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject);\n            boolean download = !Files.isRegularFile(file);\n            try {\n                if (!download && integrityCheck && !assetObject.validateChecksum(file, true))\n                    download = true;\n            } catch (IOException e) {\n                LOG.warning(\"Unable to calc hash value of file \" + file, e);\n            }\n            if (download) {\n                List<URI> uris = dependencyManager.getDownloadProvider().getAssetObjectCandidates(assetObject.getLocation());\n\n                var task = new FileDownloadTask(uris, file, new FileDownloadTask.IntegrityCheck(\"SHA-1\", assetObject.getHash()));\n                task.setName(assetObject.getHash());\n                task.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory()\n                        .resolve(\"assets\").resolve(\"objects\").resolve(assetObject.getLocation()));\n                task.setCacheRepository(dependencyManager.getCacheRepository());\n                task.setCaching(true);\n                dependencies.add(task.withCounter(\"hmcl.install.assets\"));\n            } else {\n                dependencyManager.getCacheRepository().tryCacheFile(file, CacheRepository.SHA1, assetObject.getHash());\n            }\n\n            updateProgress(++progress, index.getObjects().size());\n        }\n\n        if (!dependencies.isEmpty()) {\n            getProperties().put(\"total\", dependencies.size());\n            notifyPropertiesChanged();\n        }\n    }\n\n    public static final boolean DOWNLOAD_INDEX_FORCIBLY = true;\n    public static final boolean DOWNLOAD_INDEX_IF_NECESSARY = false;\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.AbstractDependencyManager;\nimport org.jackhuang.hmcl.game.AssetIndex;\nimport org.jackhuang.hmcl.game.AssetIndexInfo;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * This task is to download asset index file provided in minecraft.json.\n *\n * @author huangyuhui\n */\npublic final class GameAssetIndexDownloadTask extends Task<Void> {\n\n    private final AbstractDependencyManager dependencyManager;\n    private final Version version;\n    private final boolean forceDownloading;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    /**\n     * Constructor.\n     *\n     * @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}\n     * @param version the <b>resolved</b> version\n     */\n    public GameAssetIndexDownloadTask(AbstractDependencyManager dependencyManager, Version version, boolean forceDownloading) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.forceDownloading = forceDownloading;\n        setSignificance(TaskSignificance.MODERATE);\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() {\n        AssetIndexInfo assetIndexInfo = version.getAssetIndex();\n        Path assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());\n        boolean verifyHashCode = StringUtils.isNotBlank(assetIndexInfo.getSha1()) && assetIndexInfo.getUrl().contains(assetIndexInfo.getSha1());\n\n        if (Files.exists(assetIndexFile) && !forceDownloading) {\n            // verify correctness of file content\n            if (verifyHashCode) {\n                try {\n                    String actualSum = DigestUtils.digestToString(\"SHA-1\", assetIndexFile);\n                    if (actualSum.equalsIgnoreCase(assetIndexInfo.getSha1()))\n                        return;\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to calculate sha1sum of file \" + assetIndexInfo, e);\n                    // continue downloading\n                }\n            } else {\n                try {\n                    JsonUtils.fromNonNullJson(Files.readString(assetIndexFile), AssetIndex.class);\n                    return;\n                } catch (IOException | JsonParseException ignore) {\n                }\n            }\n        }\n\n        // We should not check the hash code of asset index file since this file is not consistent\n        // And Mojang will modify this file anytime. So assetIndex.hash might be outdated.\n        var task = new FileDownloadTask(\n                dependencyManager.getDownloadProvider().injectURLWithCandidates(assetIndexInfo.getUrl()),\n                assetIndexFile,\n                verifyHashCode ? new FileDownloadTask.IntegrityCheck(\"SHA-1\", assetIndexInfo.getSha1()) : null\n        );\n        task.setCacheRepository(dependencyManager.getCacheRepository());\n        dependencies.add(task);\n    }\n\n    public static class GameAssetIndexMalformedException extends IOException {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.CacheRepository;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * Task to download Minecraft jar\n * @author huangyuhui\n */\npublic final class GameDownloadTask extends Task<Void> {\n    private final DefaultDependencyManager dependencyManager;\n    private final String gameVersion;\n    private final Version version;\n    private final List<Task<?>> dependencies = new ArrayList<>();\n\n    public GameDownloadTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version) {\n        this.dependencyManager = dependencyManager;\n        this.gameVersion = gameVersion;\n        this.version = version.resolve(dependencyManager.getGameRepository());\n\n        setSignificance(TaskSignificance.MODERATE);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() {\n        Path jar = dependencyManager.getGameRepository().getVersionJar(version);\n\n        var task = new FileDownloadTask(\n                dependencyManager.getDownloadProvider().injectURLWithCandidates(version.getDownloadInfo().getUrl()),\n                jar,\n                FileDownloadTask.IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1()));\n        task.setCaching(true);\n        task.setCacheRepository(dependencyManager.getCacheRepository());\n\n        if (gameVersion != null)\n            task.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory().resolve(\"jars\").resolve(gameVersion + \".jar\"));\n\n        dependencies.add(task);\n    }\n    \n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;\n\npublic class GameInstallTask extends Task<Version> {\n\n    private final DefaultGameRepository gameRepository;\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final GameRemoteVersion remote;\n    private final VersionJsonDownloadTask downloadTask;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    public GameInstallTask(DefaultDependencyManager dependencyManager, Version version, GameRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.gameRepository = dependencyManager.getGameRepository();\n        this.version = version;\n        this.remote = remoteVersion;\n        this.downloadTask = new VersionJsonDownloadTask(remoteVersion.getGameVersion(), dependencyManager);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return Collections.singleton(downloadTask);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        Version patch = JsonUtils.fromNonNullJson(downloadTask.getResult(), Version.class)\n                .setId(MINECRAFT.getPatchId()).setVersion(remote.getGameVersion()).setJar(null).setPriority(Version.PRIORITY_MC);\n        setResult(patch);\n\n        Version version = new Version(this.version.getId()).addPatch(patch);\n        dependencies.add(Task.allOf(\n                new GameDownloadTask(dependencyManager, remote.getGameVersion(), version),\n                Task.allOf(\n                        new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true),\n                        new GameLibrariesTask(dependencyManager, version, true)\n                ).withRunAsync(() -> {\n                    // ignore failure\n                })\n        ).thenComposeAsync(gameRepository.saveAsync(version)));\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.download.AbstractDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.game.GameRepository;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * This task is to download game libraries.\n * This task should be executed last(especially after game downloading, Forge, LiteLoader and OptiFine install task).\n *\n * @author huangyuhui\n */\npublic final class GameLibrariesTask extends Task<Void> {\n\n    private final AbstractDependencyManager dependencyManager;\n    private final Version version;\n    private final boolean integrityCheck;\n    private final List<Library> libraries;\n    private final List<Task<?>> dependencies = new ArrayList<>();\n\n    /**\n     * Constructor.\n     *\n     * @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}\n     * @param version           the game version\n     */\n    public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version, boolean integrityCheck) {\n        this(dependencyManager, version, integrityCheck, version.resolve(dependencyManager.getGameRepository()).getLibraries());\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}\n     * @param version           the game version\n     */\n    public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version, boolean integrityCheck, List<Library> libraries) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.integrityCheck = integrityCheck;\n        this.libraries = libraries;\n\n        setStage(\"hmcl.install.libraries\");\n        setSignificance(TaskSignificance.MODERATE);\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    public static boolean shouldDownloadLibrary(GameRepository gameRepository, Version version, Library library, boolean integrityCheck) {\n        Path file = gameRepository.getLibraryFile(version, library);\n        if (!Files.isRegularFile(file)) return true;\n\n        if (!integrityCheck) {\n            return false;\n        }\n        try {\n            if (!library.getDownload().validateChecksum(file, true)) {\n                return true;\n            }\n            if (library.getChecksums() != null && !library.getChecksums().isEmpty() && !LibraryDownloadTask.checksumValid(file, library.getChecksums())) {\n                return true;\n            }\n            if (FileUtils.getExtension(file).equals(\"jar\")) {\n                try {\n                    FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER.checkIntegrity(file, file);\n                } catch (IOException ignored) {\n                    // the Jar file is malformed, so re-download it.\n                    return true;\n                }\n            }\n        } catch (IOException e) {\n            LOG.warning(\"Unable to calc hash value of file \" + file, e);\n        }\n\n        return false;\n    }\n\n    private static boolean shouldDownloadFMLLib(FMLLib fmlLib, Path file) {\n        if (!Files.isRegularFile(file))\n            return true;\n\n        try {\n            return !DigestUtils.digestToString(\"SHA-1\", file).equalsIgnoreCase(fmlLib.sha1);\n        } catch (IOException e) {\n            LOG.warning(\"Unable to calc hash value of file \" + file, e);\n            return true;\n        }\n    }\n\n    @Override\n    public void execute() throws IOException {\n        int progress = 0;\n        GameRepository gameRepository = dependencyManager.getGameRepository();\n        for (Library library : libraries) {\n            if (!library.appliesToCurrentEnvironment()) {\n                continue;\n            }\n\n            // https://github.com/HMCL-dev/HMCL/issues/3975\n            if (\"net.minecraftforge\".equals(library.getGroupId()) && \"minecraftforge\".equals(library.getArtifactId())\n                    && gameRepository instanceof DefaultGameRepository defaultGameRepository) {\n                List<FMLLib> fmlLibs = getFMLLibs(library.getVersion());\n                if (fmlLibs != null) {\n                    Path libDir = defaultGameRepository.getBaseDirectory().resolve(\"lib\")\n                            .toAbsolutePath().normalize();\n\n                    for (FMLLib fmlLib : fmlLibs) {\n                        Path file = libDir.resolve(fmlLib.name);\n                        if (shouldDownloadFMLLib(fmlLib, file)) {\n                            List<URI> uris = dependencyManager.getDownloadProvider()\n                                    .injectURLWithCandidates(fmlLib.downloadUrl());\n                            dependencies.add(new FileDownloadTask(uris, file)\n                                    .withCounter(\"hmcl.install.libraries\"));\n                        }\n                    }\n                }\n            }\n\n            Path file = gameRepository.getLibraryFile(version, library);\n            if (\"optifine\".equals(library.getGroupId()) && Files.exists(file) && GameVersionNumber.asGameVersion(gameRepository.getGameVersion(version)).compareTo(\"1.20.4\") == 0) {\n                String forgeVersion = LibraryAnalyzer.analyze(version, \"1.20.4\")\n                        .getVersion(LibraryAnalyzer.LibraryType.FORGE)\n                        .orElse(null);\n                if (forgeVersion != null && LibraryAnalyzer.FORGE_OPTIFINE_BROKEN_RANGE.contains(VersionNumber.asVersion(forgeVersion))) {\n                    try (FileSystem fs2 = CompressingUtils.createWritableZipFileSystem(file)) {\n                        Files.deleteIfExists(fs2.getPath(\"/META-INF/mods.toml\"));\n                    } catch (IOException e) {\n                        throw new IOException(\"Cannot fix optifine\", e);\n                    }\n                }\n            }\n            if (shouldDownloadLibrary(gameRepository, version, library, integrityCheck) && (library.hasDownloadURL() || !\"optifine\".equals(library.getGroupId()))) {\n                dependencies.add(new LibraryDownloadTask(dependencyManager, file, library).withCounter(\"hmcl.install.libraries\"));\n            } else {\n                dependencyManager.getCacheRepository().tryCacheLibrary(library, file);\n            }\n\n            updateProgress(++progress, libraries.size());\n        }\n\n        if (!dependencies.isEmpty()) {\n            getProperties().put(\"total\", dependencies.size());\n            notifyPropertiesChanged();\n        }\n    }\n\n    private static @Nullable List<FMLLib> getFMLLibs(String forgeVersion) {\n        if (forgeVersion == null)\n            return null;\n\n        // Minecraft 1.5.2\n        if (forgeVersion.startsWith(\"7.8.1.\")) {\n            return List.of(\n                    new FMLLib(\"argo-small-3.2.jar\", \"58912ea2858d168c50781f956fa5b59f0f7c6b51\"),\n                    new FMLLib(\"guava-14.0-rc3.jar\", \"931ae21fa8014c3ce686aaa621eae565fefb1a6a\",\n                            \"https://repo1.maven.org/maven2/com/google/guava/guava/14.0-rc3/guava-14.0-rc3.jar\"),\n                    new FMLLib(\"asm-all-4.1.jar\", \"054986e962b88d8660ae4566475658469595ef58\",\n                            \"https://repo1.maven.org/maven2/org/ow2/asm/asm-all/4.1/asm-all-4.1.jar\"),\n                    new FMLLib(\"bcprov-jdk15on-148.jar\", \"960dea7c9181ba0b17e8bab0c06a43f0a5f04e65\",\n                            \"https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48.jar\"),\n                    new FMLLib(\"deobfuscation_data_1.5.2.zip\", \"446e55cd986582c70fcf12cb27bc00114c5adfd9\"),\n                    new FMLLib(\"scala-library.jar\", \"458d046151ad179c85429ed7420ffb1eaf6ddf85\")\n            );\n        }\n\n        return null;\n    }\n\n    private record FMLLib(String name, String sha1, String downloadUrl) {\n        FMLLib(String name, String sha1) {\n            this(name, sha1, \"https://hmcl.glavo.site/metadata/fmllibs/\" + name);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteLatestVersions.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\n\n/**\n * @author huangyuhui\n */\n@Immutable\n@JsonSerializable\npublic record GameRemoteLatestVersions(\n        @SerializedName(\"snapshot\") String snapshot,\n        @SerializedName(\"release\") String release) {\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.ReleaseType;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.time.Instant;\nimport java.util.List;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class GameRemoteVersion extends RemoteVersion {\n\n    private final ReleaseType type;\n\n    public GameRemoteVersion(String gameVersion, String selfVersion, List<String> url, ReleaseType type, Instant releaseDate) {\n        super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, releaseDate, getReleaseType(type), url);\n        this.type = type;\n    }\n\n    public ReleaseType getType() {\n        return type;\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new GameInstallTask(dependencyManager, baseVersion, this);\n    }\n\n    @Override\n    public int compareTo(RemoteVersion o) {\n        if (!(o instanceof GameRemoteVersion)) {\n            return 0;\n        }\n\n        int dateCompare = o.getReleaseDate().compareTo(getReleaseDate());\n        if (dateCompare != 0) {\n            return dateCompare;\n        }\n\n        return GameVersionNumber.compare(o.getSelfVersion(), getSelfVersion());\n    }\n\n    private static Type getReleaseType(ReleaseType type) {\n        if (type == null) return Type.UNCATEGORIZED;\n        return switch (type) {\n            case RELEASE -> Type.RELEASE;\n            case SNAPSHOT -> Type.SNAPSHOT;\n            case UNKNOWN -> Type.UNCATEGORIZED;\n            case PENDING -> Type.PENDING;\n            case UNOBFUSCATED -> Type.UNOBFUSCATED;\n            default -> Type.OLD;\n        };\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersionInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.game.ReleaseType;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport java.time.Instant;\n\n/**\n *\n * @author huangyuhui\n */\n@JsonSerializable\npublic record GameRemoteVersionInfo(\n        @SerializedName(\"id\") String gameVersion,\n        @SerializedName(\"time\") Instant time,\n        @SerializedName(\"releaseTime\") Instant releaseTime,\n        @SerializedName(\"type\") ReleaseType type,\n        @SerializedName(\"url\") String url) implements Validation {\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (StringUtils.isBlank(gameVersion))\n            throw new JsonParseException(\"GameRemoteVersion id cannot be blank\");\n        if (StringUtils.isBlank(url))\n            throw new JsonParseException(\"GameRemoteVersion url cannot be blank\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersions.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport java.util.List;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\n@JsonSerializable\npublic record GameRemoteVersions(\n        @SerializedName(\"versions\") List<GameRemoteVersionInfo> versions,\n        @SerializedName(\"latest\") GameRemoteLatestVersions latest) implements Validation {\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (versions == null)\n            throw new JsonParseException(\"GameRemoteVersions.versions cannot be null\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * Remove class digital verification file in game jar\n * @author huangyuhui\n */\npublic final class GameVerificationFixTask extends Task<Void> {\n    private final DefaultDependencyManager dependencyManager;\n    private final String gameVersion;\n    private final Version version;\n    private final List<Task<?>> dependencies = new ArrayList<>();\n\n    public GameVerificationFixTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version) {\n        this.dependencyManager = dependencyManager;\n        this.gameVersion = gameVersion;\n        this.version = version;\n\n        if (!version.isResolved()) {\n            throw new IllegalArgumentException(\"GameVerificationFixTask requires a resolved game version\");\n        }\n\n        setSignificance(TaskSignificance.MODERATE);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() throws IOException {\n        Path jar = dependencyManager.getGameRepository().getVersionJar(version);\n        LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion);\n\n        if (Files.exists(jar) && GameVersionNumber.compare(gameVersion, \"1.6\") < 0 && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {\n            try (FileSystem fs = CompressingUtils.createWritableZipFileSystem(jar, StandardCharsets.UTF_8)) {\n                Files.deleteIfExists(fs.getPath(\"META-INF/MOJANG_C.DSA\"));\n                Files.deleteIfExists(fs.getPath(\"META-INF/MOJANG_C.SF\"));\n            }\n        }\n    }\n    \n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.util.Collection;\nimport java.util.Collections;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class GameVersionList extends VersionList<GameRemoteVersion> {\n    private final DownloadProvider downloadProvider;\n\n    public GameVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return true;\n    }\n\n    @Override\n    protected Collection<GameRemoteVersion> getVersionsImpl(String gameVersion) {\n        return versions.values();\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return new GetTask(downloadProvider.getVersionListURLs()).thenGetJsonAsync(GameRemoteVersions.class)\n                .thenAcceptAsync(root -> {\n                    GameRemoteVersions unlistedVersions = null;\n\n                    //noinspection DataFlowIssue\n                    try (Reader input = new InputStreamReader(\n                            GameVersionList.class.getResourceAsStream(\"/assets/game/unlisted-versions.json\"))) {\n                        unlistedVersions = JsonUtils.GSON.fromJson(input, GameRemoteVersions.class);\n                    } catch (Throwable e) {\n                        LOG.error(\"Failed to load unlisted versions\", e);\n                    }\n\n                    lock.writeLock().lock();\n                    try {\n                        versions.clear();\n\n                        if (unlistedVersions != null) {\n                            for (GameRemoteVersionInfo unlistedVersion : unlistedVersions.versions()) {\n                                versions.put(unlistedVersion.gameVersion(), new GameRemoteVersion(\n                                        unlistedVersion.gameVersion(),\n                                        unlistedVersion.gameVersion(),\n                                        Collections.singletonList(unlistedVersion.url()),\n                                        unlistedVersion.type(), unlistedVersion.releaseTime()));\n                            }\n                        }\n\n                        for (GameRemoteVersionInfo remoteVersion : root.versions()) {\n                            versions.put(remoteVersion.gameVersion(), new GameRemoteVersion(\n                                    remoteVersion.gameVersion(),\n                                    remoteVersion.gameVersion(),\n                                    Collections.singletonList(remoteVersion.url()),\n                                    remoteVersion.type(), remoteVersion.releaseTime()));\n                        }\n                    } finally {\n                        lock.writeLock().unlock();\n                    }\n                });\n    }\n\n    @Override\n    public String toString() {\n        return \"GameVersionList[downloadProvider=%s]\".formatted(downloadProvider);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.game.Library;\nimport org.jetbrains.annotations.NotNull;\n\npublic class LibraryDownloadException extends Exception {\n    private final Library library;\n\n    public LibraryDownloadException(Library library, @NotNull Throwable cause) {\n        super(\"Unable to download library \" + library, cause);\n\n        this.library = library;\n    }\n\n    public Library getLibrary() {\n        return library;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.download.AbstractDependencyManager;\nimport org.jackhuang.hmcl.download.DefaultCacheRepository;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.task.DownloadException;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.CancellationException;\nimport java.util.jar.JarEntry;\nimport java.util.jar.JarInputStream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class LibraryDownloadTask extends Task<Void> {\n    private FileDownloadTask task;\n    protected final Path jar;\n    protected final DefaultCacheRepository cacheRepository;\n    protected final AbstractDependencyManager dependencyManager;\n    protected final Library library;\n    protected final String url;\n    private final Library originalLibrary;\n    private boolean cached = false;\n\n    public LibraryDownloadTask(AbstractDependencyManager dependencyManager, Path file, Library library) {\n        this.dependencyManager = dependencyManager;\n        this.originalLibrary = library;\n\n        setSignificance(TaskSignificance.MODERATE);\n\n        if (library.is(\"net.minecraftforge\", \"forge\"))\n            library = library.setClassifier(\"universal\");\n\n        this.library = library;\n        this.cacheRepository = dependencyManager.getCacheRepository();\n\n        url = library.getDownload().getUrl();\n        jar = file;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        if (cached) return Collections.emptyList();\n        else return Collections.singleton(task);\n    }\n\n    @Override\n    public boolean isRelyingOnDependents() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        if (cached) return;\n\n        if (!isDependentsSucceeded()) {\n            // Since FileDownloadTask wraps the actual exception with DownloadException.\n            // We should extract it letting the error message clearer.\n            Exception t = task.getException();\n            if (t instanceof DownloadException)\n                throw new LibraryDownloadException(library, t.getCause());\n            else if (t instanceof CancellationException)\n                throw new CancellationException();\n            else\n                throw new LibraryDownloadException(library, t);\n        }\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() {\n        Optional<Path> libPath = cacheRepository.getLibrary(originalLibrary);\n        if (libPath.isPresent()) {\n            try {\n                FileUtils.copyFile(libPath.get(), jar);\n                cached = true;\n                return;\n            } catch (IOException e) {\n                LOG.warning(\"Failed to copy file from cache\", e);\n                // We cannot copy cached file to current location\n                // so we try to download a new one.\n            }\n        }\n\n\n        List<URI> uris = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);\n        task = new FileDownloadTask(uris, jar,\n                library.getDownload().getSha1() != null ? new IntegrityCheck(\"SHA-1\", library.getDownload().getSha1()) : null);\n        task.setCacheRepository(cacheRepository);\n        task.setCaching(true);\n        task.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        if (!cached) {\n            try {\n                cacheRepository.cacheLibrary(library, jar, false);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to cache downloaded library \" + library, e);\n            }\n        }\n    }\n\n    public static boolean checksumValid(Path libPath, List<String> checksums) {\n        try {\n            if (checksums == null || checksums.isEmpty()) {\n                return true;\n            }\n            byte[] fileData = Files.readAllBytes(libPath);\n            boolean valid = checksums.contains(DigestUtils.digestToString(\"SHA-1\", fileData));\n            if (!valid && FileUtils.getName(libPath).endsWith(\".jar\")) {\n                valid = validateJar(fileData, checksums);\n            }\n            return valid;\n        } catch (IOException e) {\n            LOG.warning(\"Failed to validate \" + libPath, e);\n        }\n        return false;\n    }\n\n    private static boolean validateJar(byte[] data, List<String> checksums) throws IOException {\n        HashMap<String, String> files = new HashMap<>();\n        String[] hashes = null;\n        JarInputStream jar = new JarInputStream(new ByteArrayInputStream(data));\n        JarEntry entry = jar.getNextJarEntry();\n        while (entry != null) {\n            byte[] eData = jar.readAllBytes();\n            if (entry.getName().equals(\"checksums.sha1\")) {\n                hashes = new String(eData, StandardCharsets.UTF_8).split(\"\\n\");\n            }\n            if (!entry.isDirectory()) {\n                files.put(entry.getName(), DigestUtils.digestToString(\"SHA-1\", eData));\n            }\n            entry = jar.getNextJarEntry();\n        }\n        jar.close();\n        if (hashes != null) {\n            boolean failed = !checksums.contains(files.get(\"checksums.sha1\"));\n            if (!failed) {\n                for (String hash : hashes) {\n                    if (!hash.trim().isEmpty() && hash.contains(\" \")) {\n                        String[] e = hash.split(\" \");\n                        String validChecksum = e[0];\n                        String target = hash.substring(validChecksum.length() + 1);\n                        String checksum = files.get(target);\n                        if ((!files.containsKey(target)) || (checksum == null)) {\n                            LOG.warning(\"    \" + target + \" : missing\");\n                            failed = true;\n                            break;\n                        } else if (!checksum.equals(validChecksum)) {\n                            LOG.warning(\"    \" + target + \" : failed (\" + checksum + \", \" + validChecksum + \")\");\n                            failed = true;\n                            break;\n                        }\n                    }\n                }\n            }\n            return !failed;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonDownloadTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class VersionJsonDownloadTask extends Task<String> {\n    private final String gameVersion;\n    private final DefaultDependencyManager dependencyManager;\n    private final List<Task<?>> dependents = new ArrayList<>(1);\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n    private final VersionList<?> gameVersionList;\n\n    public VersionJsonDownloadTask(String gameVersion, DefaultDependencyManager dependencyManager) {\n        this.gameVersion = gameVersion;\n        this.dependencyManager = dependencyManager;\n        this.gameVersionList = dependencyManager.getVersionList(\"game\");\n\n        dependents.add(gameVersionList.loadAsync(gameVersion));\n\n        setSignificance(TaskSignificance.MODERATE);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public void execute() throws IOException {\n        RemoteVersion remoteVersion = gameVersionList.getVersion(gameVersion, gameVersion)\n                .orElseThrow(() -> new IOException(\"Cannot find specific version \" + gameVersion + \" in remote repository\"));\n        dependencies.add(new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls())).storeTo(this::setResult));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonSaveTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.game;\n\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\n/**\n * This task is to save the version json.\n *\n * @author huangyuhui\n */\npublic final class VersionJsonSaveTask extends Task<Version> {\n\n    private final DefaultGameRepository repository;\n    private final Version version;\n\n    /**\n     * Constructor.\n     *\n     * @param repository the game repository\n     * @param version the game version\n     */\n    public VersionJsonSaveTask(DefaultGameRepository repository, Version version) {\n        this.repository = repository;\n        this.version = version;\n\n        setSignificance(TaskSignificance.MODERATE);\n        setResult(version);\n    }\n\n    @Override\n    public void execute() throws Exception {\n        Path json = repository.getVersionJson(version.getId()).toAbsolutePath();\n        Files.createDirectories(json.getParent());\n        JsonUtils.writeToJsonFile(json, version);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDistribution.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java;\n\nimport java.util.Set;\n\n/**\n * @author Glavo\n */\npublic interface JavaDistribution<V extends JavaRemoteVersion> {\n    String getDisplayName();\n\n    Set<JavaPackageType> getSupportedPackageTypes();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaPackageType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java;\n\n/**\n * @author Glavo\n */\npublic enum JavaPackageType {\n    JRE(false, false),\n    JDK(true, false),\n    JREFX(false, true),\n    JDKFX(true, true);\n\n    private final boolean jdk;\n    private final boolean javafx;\n\n    JavaPackageType(boolean jdk, boolean javafx) {\n        this.jdk = jdk;\n        this.javafx = javafx;\n    }\n\n    public static JavaPackageType of(boolean jdk, boolean javafx) {\n        if (jdk)\n            return javafx ? JDKFX : JDK;\n        else\n            return javafx ? JREFX : JRE;\n    }\n\n    public boolean isJDK() {\n        return jdk;\n    }\n\n    public boolean isJavaFXBundled() {\n        return javafx;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java;\n\n/**\n * @author Glavo\n */\npublic interface JavaRemoteVersion {\n    int getJdkVersion();\n\n    String getJavaVersion();\n\n    String getDistributionVersion();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoFetchJavaListTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.disco;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.java.JavaPackageType;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.util.*;\n\n/**\n * @author Glavo\n */\npublic final class DiscoFetchJavaListTask extends Task<EnumMap<JavaPackageType, TreeMap<Integer, DiscoJavaRemoteVersion>>> {\n\n    public static final String API_ROOT = System.getProperty(\"hmcl.discoapi.override\", \"https://api.foojay.io/disco/v3.0\");\n\n    private final DiscoJavaDistribution distribution;\n    private final String archiveType;\n    private final Task<String> fetchPackagesTask;\n\n    public DiscoFetchJavaListTask(DownloadProvider downloadProvider, DiscoJavaDistribution distribution, Platform platform) {\n        this.distribution = distribution;\n        this.archiveType = platform.getOperatingSystem() == OperatingSystem.WINDOWS ? \"zip\" : \"tar.gz\";\n\n        HashMap<String, String> params = new HashMap<>();\n        params.put(\"distribution\", distribution.getApiParameter());\n        params.put(\"operating_system\", platform.getOperatingSystem().getCheckedName());\n        params.put(\"architecture\", platform.getArchitecture().getCheckedName());\n        params.put(\"archive_type\", archiveType);\n        params.put(\"directly_downloadable\", \"true\");\n        if (platform.getOperatingSystem() == OperatingSystem.LINUX)\n            params.put(\"lib_c_type\", \"glibc\");\n\n        this.fetchPackagesTask = new GetTask(downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(API_ROOT + \"/packages\", params)));\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return Collections.singleton(fetchPackagesTask);\n    }\n\n    @Override\n    public void execute() throws Exception {\n        String json = fetchPackagesTask.getResult();\n        List<DiscoJavaRemoteVersion> list = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoJavaRemoteVersion.class)).getResult();\n        EnumMap<JavaPackageType, TreeMap<Integer, DiscoJavaRemoteVersion>> result = new EnumMap<>(JavaPackageType.class);\n\n        for (DiscoJavaRemoteVersion version : list) {\n            if (!distribution.getApiParameter().equals(version.getDistribution())\n                    || !version.isDirectlyDownloadable()\n                    || !archiveType.equals(version.getArchiveType()))\n                continue;\n\n            if (!distribution.testVersion(version))\n                continue;\n\n            JavaPackageType packageType = JavaPackageType.of(\"jdk\".equals(version.getPackageType()), version.isJavaFXBundled());\n            TreeMap<Integer, DiscoJavaRemoteVersion> map = result.computeIfAbsent(packageType, ignored -> new TreeMap<>());\n\n            int jdkVersion = version.getJdkVersion();\n            DiscoJavaRemoteVersion oldVersion = map.get(jdkVersion);\n            if (oldVersion == null || VersionNumber.compare(version.getDistributionVersion(), oldVersion.getDistributionVersion()) > 0)\n                map.put(jdkVersion, version);\n        }\n\n        setResult(result);\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaDistribution.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.disco;\n\nimport org.jackhuang.hmcl.download.java.JavaDistribution;\nimport org.jackhuang.hmcl.download.java.JavaPackageType;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\n\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.download.java.JavaPackageType.*;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.platform.Architecture.*;\nimport static org.jackhuang.hmcl.util.platform.OperatingSystem.*;\n\n/**\n * @author Glavo\n * @see <a href=\"https://github.com/foojayio/discoapi\">discoapi</a>\n */\npublic enum DiscoJavaDistribution implements JavaDistribution<DiscoJavaRemoteVersion> {\n    TEMURIN(\"Eclipse Temurin\", \"temurin\", \"Adoptium\",\n            EnumSet.of(JDK, JRE),\n            pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),\n            pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64, PPC64LE, S390X, SPARCV9)),\n            pair(MACOS, EnumSet.of(X86_64, ARM64))),\n    // Fucking javac generates an anonymous inner class here, whose constructor takes no @SafeVarargs annotation, causing a compile-time warning.\n    @SuppressWarnings({\"unchecked\", \"RedundantSuppression\"})\n    LIBERICA(\"BellSoft Liberica\", \"liberica\", \"BellSoft\",\n            EnumSet.of(JDK, JRE, JDKFX, JREFX),\n            pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),\n            pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),\n            pair(MACOS, EnumSet.of(X86_64, ARM64))) {\n        @Override\n        public boolean testVersion(DiscoJavaRemoteVersion version) {\n            if (!super.testVersion(version))\n                return false;\n\n            String fileName = version.getFileName();\n            return !fileName.endsWith(\"-lite.tar.gz\") && !fileName.endsWith(\"-lite.zip\");\n        }\n    },\n    ZULU(\"Azul Zulu\", \"zulu\", \"Azul\",\n            EnumSet.of(JDK, JRE, JDKFX, JREFX),\n            pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),\n            pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),\n            pair(MACOS, EnumSet.of(X86_64, ARM64))),\n    GRAALVM(\"Oracle GraalVM\", \"graalvm\", \"Oracle\",\n            EnumSet.of(JDK),\n            pair(WINDOWS, EnumSet.of(X86_64)),\n            pair(LINUX, EnumSet.of(X86_64, ARM64)),\n            pair(MACOS, EnumSet.of(X86_64, ARM64))),\n    SEMERU(\"IBM Semeru (OpenJ9)\", \"semeru\", \"IBM\",\n            EnumSet.of(JDK, JRE),\n            pair(WINDOWS, EnumSet.of(X86_64)),\n            pair(LINUX, EnumSet.of(X86_64, ARM64, PPC64LE, S390X)),\n            pair(MACOS, EnumSet.of(X86_64, ARM64))\n    ),\n    CORRETTO(\"Amazon Corretto\", \"corretto\", \"Amazon\",\n            EnumSet.of(JDK),\n            pair(WINDOWS, EnumSet.of(X86_64)),\n            pair(LINUX, EnumSet.of(X86_64, ARM64)),\n            pair(MACOS, EnumSet.of(X86_64, ARM64))\n    );\n\n    public static DiscoJavaDistribution of(String name) {\n        for (DiscoJavaDistribution distribution : values()) {\n            if (distribution.apiParameter.equalsIgnoreCase(name) || distribution.name().equalsIgnoreCase(name)) {\n                return distribution;\n            }\n        }\n\n        return null;\n    }\n\n    private final String displayName;\n    private final String apiParameter;\n    private final String vendor;\n    private final Set<JavaPackageType> supportedPackageTypes;\n    private final Map<OperatingSystem, EnumSet<Architecture>> supportedPlatforms = new EnumMap<>(OperatingSystem.class);\n\n    @SafeVarargs\n    DiscoJavaDistribution(String displayName, String apiParameter, String vendor, Set<JavaPackageType> supportedPackageTypes, Pair<OperatingSystem, EnumSet<Architecture>>... supportedPlatforms) {\n        this.displayName = displayName;\n        this.apiParameter = apiParameter;\n        this.vendor = vendor;\n        this.supportedPackageTypes = supportedPackageTypes;\n\n        for (Pair<OperatingSystem, EnumSet<Architecture>> platform : supportedPlatforms) {\n            this.supportedPlatforms.put(platform.getKey(), platform.getValue());\n        }\n    }\n\n    @Override\n    public String getDisplayName() {\n        return displayName;\n    }\n\n    public String getApiParameter() {\n        return apiParameter;\n    }\n\n    public String getVendor() {\n        return vendor;\n    }\n\n    @Override\n    public Set<JavaPackageType> getSupportedPackageTypes() {\n        return supportedPackageTypes;\n    }\n\n    public boolean isSupport(Platform platform) {\n        EnumSet<Architecture> architectures = supportedPlatforms.get(platform.getOperatingSystem());\n        return architectures != null && architectures.contains(platform.getArchitecture());\n    }\n\n    public boolean testVersion(DiscoJavaRemoteVersion version) {\n        return this.getApiParameter().equals(version.getDistribution());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.disco;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.download.java.JavaRemoteVersion;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\n/**\n * @author Glavo\n */\npublic final class DiscoJavaRemoteVersion implements JavaRemoteVersion {\n    @SerializedName(\"id\")\n    private final String id;\n\n    @SerializedName(\"archive_type\")\n    private final String archiveType;\n\n    @SerializedName(\"distribution\")\n    private final String distribution;\n\n    @SerializedName(\"major_version\")\n    private final int majorVersion;\n\n    @SerializedName(\"java_version\")\n    private final String javaVersion;\n\n    @SerializedName(\"distribution_version\")\n    private final String distributionVersion;\n\n    @SerializedName(\"jdk_version\")\n    private final int jdkVersion;\n\n    @SerializedName(\"latest_build_available\")\n    private final boolean latestBuildAvailable;\n\n    @SerializedName(\"release_status\")\n    private final String releaseStatus;\n\n    @SerializedName(\"term_of_support\")\n    private final String termOfSupport;\n\n    @SerializedName(\"operating_system\")\n    private final String operatingSystem;\n\n    @SerializedName(\"lib_c_type\")\n    private final String libCType;\n\n    @SerializedName(\"architecture\")\n    private final String architecture;\n\n    @SerializedName(\"fpu\")\n    private final String fpu;\n\n    @SerializedName(\"package_type\")\n    private final String packageType;\n\n    @SerializedName(\"javafx_bundled\")\n    private final boolean javafxBundled;\n\n    @SerializedName(\"directly_downloadable\")\n    private final boolean directlyDownloadable;\n\n    @SerializedName(\"filename\")\n    private final String fileName;\n\n    @SerializedName(\"links\")\n    private final Links links;\n\n    @SerializedName(\"free_use_in_production\")\n    private final boolean freeUseInProduction;\n\n    @SerializedName(\"tck_tested\")\n    private final String tckTested;\n\n    @SerializedName(\"tck_cert_uri\")\n    private final String tckCertUri;\n\n    @SerializedName(\"aqavit_certified\")\n    private final String aqavitCertified;\n\n    @SerializedName(\"aqavit_cert_uri\")\n    private final String aqavitCertUri;\n\n    @SerializedName(\"size\")\n    private final long size;\n\n    public DiscoJavaRemoteVersion(String id, String archiveType, String distribution, int majorVersion, String javaVersion, String distributionVersion, int jdkVersion, boolean latestBuildAvailable, String releaseStatus, String termOfSupport, String operatingSystem, String libCType, String architecture, String fpu, String packageType, boolean javafxBundled, boolean directlyDownloadable, String fileName, Links links, boolean freeUseInProduction, String tckTested, String tckCertUri, String aqavitCertified, String aqavitCertUri, long size) {\n        this.id = id;\n        this.archiveType = archiveType;\n        this.distribution = distribution;\n        this.majorVersion = majorVersion;\n        this.javaVersion = javaVersion;\n        this.distributionVersion = distributionVersion;\n        this.jdkVersion = jdkVersion;\n        this.latestBuildAvailable = latestBuildAvailable;\n        this.releaseStatus = releaseStatus;\n        this.termOfSupport = termOfSupport;\n        this.operatingSystem = operatingSystem;\n        this.libCType = libCType;\n        this.architecture = architecture;\n        this.fpu = fpu;\n        this.packageType = packageType;\n        this.javafxBundled = javafxBundled;\n        this.directlyDownloadable = directlyDownloadable;\n        this.fileName = fileName;\n        this.links = links;\n        this.freeUseInProduction = freeUseInProduction;\n        this.tckTested = tckTested;\n        this.tckCertUri = tckCertUri;\n        this.aqavitCertified = aqavitCertified;\n        this.aqavitCertUri = aqavitCertUri;\n        this.size = size;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public String getArchiveType() {\n        return archiveType;\n    }\n\n    public String getDistribution() {\n        return distribution;\n    }\n\n    public int getMajorVersion() {\n        return majorVersion;\n    }\n\n    @Override\n    public String getJavaVersion() {\n        return javaVersion;\n    }\n\n    @Override\n    public String getDistributionVersion() {\n        return distributionVersion;\n    }\n\n    @Override\n    public int getJdkVersion() {\n        return jdkVersion;\n    }\n\n    public boolean isLatestBuildAvailable() {\n        return latestBuildAvailable;\n    }\n\n    public String getReleaseStatus() {\n        return releaseStatus;\n    }\n\n    public String getTermOfSupport() {\n        return termOfSupport;\n    }\n\n    public boolean isLTS() {\n        return \"lts\".equals(termOfSupport);\n    }\n\n    public String getOperatingSystem() {\n        return operatingSystem;\n    }\n\n    public String getLibCType() {\n        return libCType;\n    }\n\n    public String getArchitecture() {\n        return architecture;\n    }\n\n    public String getFpu() {\n        return fpu;\n    }\n\n    public String getPackageType() {\n        return packageType;\n    }\n\n    public boolean isJavaFXBundled() {\n        return javafxBundled;\n    }\n\n    public boolean isDirectlyDownloadable() {\n        return directlyDownloadable;\n    }\n\n    public String getFileName() {\n        return fileName;\n    }\n\n    public Links getLinks() {\n        return links;\n    }\n\n    public boolean isFreeUseInProduction() {\n        return freeUseInProduction;\n    }\n\n    public String getTckTested() {\n        return tckTested;\n    }\n\n    public String getTckCertUri() {\n        return tckCertUri;\n    }\n\n    public String getAqavitCertified() {\n        return aqavitCertified;\n    }\n\n    public String getAqavitCertUri() {\n        return aqavitCertUri;\n    }\n\n    public long getSize() {\n        return size;\n    }\n\n    @Override\n    public String toString() {\n        return \"DiscoJavaRemoteVersion \" + JsonUtils.GSON.toJson(this);\n    }\n\n    public static final class Links {\n        @SerializedName(\"pkg_info_uri\")\n        private final String pkgInfoUri;\n\n        @SerializedName(\"pkg_download_redirect\")\n        private final String pkgDownloadRedirect;\n\n        public Links(String pkgInfoUri, String pkgDownloadRedirect) {\n            this.pkgInfoUri = pkgInfoUri;\n            this.pkgDownloadRedirect = pkgDownloadRedirect;\n        }\n\n        public String getPkgInfoUri() {\n            return pkgInfoUri;\n        }\n\n        public String getPkgDownloadRedirect() {\n            return pkgDownloadRedirect;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoRemoteFileInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.disco;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * @author Glavo\n */\npublic final class DiscoRemoteFileInfo {\n    @SerializedName(\"filename\")\n    private final String fileName;\n\n    @SerializedName(\"direct_download_uri\")\n    private final String directDownloadUri;\n\n    @SerializedName(\"checksum_type\")\n    private final String checksumType;\n\n    @SerializedName(\"checksum\")\n    private final String checksum;\n\n    @SerializedName(\"checksum_uri\")\n    private final String checksumUri;\n\n    public DiscoRemoteFileInfo(String fileName, String directDownloadUri, String checksumType, String checksum, String checksumUri) {\n        this.fileName = fileName;\n        this.directDownloadUri = directDownloadUri;\n        this.checksumType = checksumType;\n        this.checksum = checksum;\n        this.checksumUri = checksumUri;\n    }\n\n    public String getFileName() {\n        return fileName;\n    }\n\n    public String getDirectDownloadUri() {\n        return directDownloadUri;\n    }\n\n    public String getChecksumType() {\n        return checksumType;\n    }\n\n    public String getChecksum() {\n        return checksum;\n    }\n\n    public String getChecksumUri() {\n        return checksumUri;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoResult.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.disco;\n\nimport com.google.gson.reflect.TypeToken;\n\nimport java.util.List;\n\n/**\n * @author Glavo\n */\npublic final class DiscoResult<T> {\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> TypeToken<DiscoResult<T>> typeOf(Class<T> argType) {\n        return (TypeToken<DiscoResult<T>>) TypeToken.getParameterized(DiscoResult.class, argType);\n    }\n\n    private final List<T> result;\n    private final String message;\n\n    private DiscoResult(List<T> result, String message) {\n        this.result = result;\n        this.message = message;\n    }\n\n    public List<T> getResult() {\n        return result;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDistribution.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.mojang;\n\nimport org.jackhuang.hmcl.download.java.JavaDistribution;\nimport org.jackhuang.hmcl.download.java.JavaPackageType;\nimport org.jackhuang.hmcl.download.java.JavaRemoteVersion;\n\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * @author Glavo\n */\npublic final class MojangJavaDistribution implements JavaDistribution<JavaRemoteVersion> {\n\n    public static final MojangJavaDistribution DISTRIBUTION = new MojangJavaDistribution();\n\n    private MojangJavaDistribution() {\n    }\n\n    @Override\n    public String getDisplayName() {\n        return \"Mojang\";\n    }\n\n    @Override\n    public Set<JavaPackageType> getSupportedPackageTypes() {\n        return Collections.singleton(JavaPackageType.JRE);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloadTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.mojang;\n\nimport org.jackhuang.hmcl.download.ArtifactMalformedException;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.game.DownloadInfo;\nimport org.jackhuang.hmcl.game.GameJavaVersion;\nimport org.jackhuang.hmcl.java.JavaInfo;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.ChecksumMismatchException;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.UnsupportedPlatformException;\nimport org.tukaani.xz.LZMAInputStream;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.StandardCopyOption;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class MojangJavaDownloadTask extends Task<MojangJavaDownloadTask.Result> {\n\n    private final DownloadProvider downloadProvider;\n    private final Path target;\n    private final Task<MojangJavaRemoteFiles> javaDownloadsTask;\n    private final List<Task<?>> dependencies = new ArrayList<>();\n\n    private volatile MojangJavaDownloads.JavaDownload download;\n\n    public MojangJavaDownloadTask(DownloadProvider downloadProvider, Path target, GameJavaVersion javaVersion, String platform) {\n        this.target = target;\n        this.downloadProvider = downloadProvider;\n        this.javaDownloadsTask = new GetTask(downloadProvider.injectURLWithCandidates(\n                \"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json\"))\n        .thenComposeAsync(javaDownloadsJson -> {\n            MojangJavaDownloads allDownloads = JsonUtils.fromNonNullJson(javaDownloadsJson, MojangJavaDownloads.class);\n\n            Map<String, List<MojangJavaDownloads.JavaDownload>> osDownloads = allDownloads.getDownloads().get(platform);\n            if (osDownloads == null || !osDownloads.containsKey(javaVersion.component()))\n                throw new UnsupportedPlatformException(\"Unsupported platform: \" + platform);\n            List<MojangJavaDownloads.JavaDownload> candidates = osDownloads.get(javaVersion.component());\n            for (MojangJavaDownloads.JavaDownload download : candidates) {\n                if (JavaInfo.parseVersion(download.getVersion().getName()) >= javaVersion.majorVersion()) {\n                    this.download = download;\n                    return new GetTask(downloadProvider.injectURLWithCandidates(download.getManifest().getUrl()));\n                }\n            }\n            throw new UnsupportedPlatformException(\"Candidates: \" + JsonUtils.GSON.toJson(candidates));\n        })\n        .thenApplyAsync(javaDownloadJson -> JsonUtils.fromNonNullJson(javaDownloadJson, MojangJavaRemoteFiles.class));\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return Collections.singleton(javaDownloadsTask);\n    }\n\n    @Override\n    public void execute() throws Exception {\n        for (Map.Entry<String, MojangJavaRemoteFiles.Remote> entry : javaDownloadsTask.getResult().getFiles().entrySet()) {\n            Path dest = target.resolve(entry.getKey());\n            if (entry.getValue() instanceof MojangJavaRemoteFiles.RemoteFile) {\n                MojangJavaRemoteFiles.RemoteFile file = ((MojangJavaRemoteFiles.RemoteFile) entry.getValue());\n\n                // Use local file if it already exists\n                try {\n                    BasicFileAttributes localFileAttributes = Files.readAttributes(dest, BasicFileAttributes.class);\n                    if (localFileAttributes.isRegularFile() && file.getDownloads().containsKey(\"raw\")) {\n                        DownloadInfo downloadInfo = file.getDownloads().get(\"raw\");\n                        if (localFileAttributes.size() == downloadInfo.getSize()) {\n                            ChecksumMismatchException.verifyChecksum(dest, \"SHA-1\", downloadInfo.getSha1());\n                            LOG.info(\"Skip existing file: \" + dest);\n                            continue;\n                        }\n                    }\n                } catch (IOException ignored) {\n                }\n\n                if (file.getDownloads().containsKey(\"lzma\")) {\n                    DownloadInfo download = file.getDownloads().get(\"lzma\");\n                    Path tempFile = target.resolve(entry.getKey() + \".lzma\");\n                    var task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), tempFile, new FileDownloadTask.IntegrityCheck(\"SHA-1\", download.getSha1()));\n                    task.setName(entry.getKey());\n                    dependencies.add(task.thenRunAsync(() -> {\n                        Path decompressed = target.resolve(entry.getKey() + \".tmp\");\n                        try (LZMAInputStream input = new LZMAInputStream(Files.newInputStream(tempFile))) {\n                            Files.copy(input, decompressed, StandardCopyOption.REPLACE_EXISTING);\n                        } catch (IOException e) {\n                            throw new ArtifactMalformedException(\"File \" + entry.getKey() + \" is malformed\", e);\n                        }\n\n                        try {\n                            Files.deleteIfExists(tempFile);\n                        } catch (IOException e) {\n                            LOG.warning(\"Failed to delete temporary file: \" + tempFile, e);\n                        }\n\n                        Files.move(decompressed, dest, StandardCopyOption.REPLACE_EXISTING);\n                        if (file.isExecutable()) {\n                            FileUtils.setExecutable(dest);\n                        }\n                    }));\n                } else if (file.getDownloads().containsKey(\"raw\")) {\n                    DownloadInfo download = file.getDownloads().get(\"raw\");\n                    var task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), dest, new FileDownloadTask.IntegrityCheck(\"SHA-1\", download.getSha1()));\n                    task.setName(entry.getKey());\n                    if (file.isExecutable()) {\n                        dependencies.add(task.thenRunAsync(() -> FileUtils.setExecutable(dest)));\n                    } else {\n                        dependencies.add(task);\n                    }\n                } else {\n                    continue;\n                }\n            } else if (entry.getValue() instanceof MojangJavaRemoteFiles.RemoteDirectory) {\n                Files.createDirectories(dest);\n            } else if (entry.getValue() instanceof MojangJavaRemoteFiles.RemoteLink) {\n                MojangJavaRemoteFiles.RemoteLink link = ((MojangJavaRemoteFiles.RemoteLink) entry.getValue());\n                Files.deleteIfExists(dest);\n                Files.createSymbolicLink(dest, Paths.get(link.getTarget()));\n            }\n        }\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        setResult(new Result(download, javaDownloadsTask.getResult()));\n    }\n\n    public static final class Result {\n        public final MojangJavaDownloads.JavaDownload download;\n        public final MojangJavaRemoteFiles remoteFiles;\n\n        public Result(MojangJavaDownloads.JavaDownload download, MojangJavaRemoteFiles remoteFiles) {\n            this.download = download;\n            this.remoteFiles = remoteFiles;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloads.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.mojang;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport org.jackhuang.hmcl.game.DownloadInfo;\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.lang.reflect.Type;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;\n\n@JsonAdapter(MojangJavaDownloads.Adapter.class)\npublic class MojangJavaDownloads {\n\n    private final Map<String, Map<String, List<JavaDownload>>> downloads;\n\n    public MojangJavaDownloads(Map<String, Map<String, List<JavaDownload>>> downloads) {\n        this.downloads = downloads;\n    }\n\n    public Map<String, Map<String, List<JavaDownload>>> getDownloads() {\n        return downloads;\n    }\n\n    public static class Adapter implements JsonSerializer<MojangJavaDownloads>, JsonDeserializer<MojangJavaDownloads> {\n\n        @Override\n        public JsonElement serialize(MojangJavaDownloads src, Type typeOfSrc, JsonSerializationContext context) {\n            return context.serialize(src.downloads);\n        }\n\n        @Override\n        public MojangJavaDownloads deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            return new MojangJavaDownloads(context.deserialize(json, mapTypeOf(String.class, mapTypeOf(String.class, listTypeOf(JavaDownload.class))).getType()));\n        }\n    }\n\n    @Immutable\n    public static class JavaDownload {\n        private final Availability availability;\n        private final DownloadInfo manifest;\n        private final Version version;\n\n        public JavaDownload() {\n            this(new Availability(), new DownloadInfo(), new Version());\n        }\n\n        public JavaDownload(Availability availability, DownloadInfo manifest, Version version) {\n            this.availability = availability;\n            this.manifest = manifest;\n            this.version = version;\n        }\n\n        public Availability getAvailability() {\n            return availability;\n        }\n\n        public DownloadInfo getManifest() {\n            return manifest;\n        }\n\n        public Version getVersion() {\n            return version;\n        }\n    }\n\n    @Immutable\n    public static class Availability {\n        private final int group;\n        private final int progress;\n\n        public Availability() {\n            this(0, 0);\n        }\n\n        public Availability(int group, int progress) {\n            this.group = group;\n            this.progress = progress;\n        }\n\n        public int getGroup() {\n            return group;\n        }\n\n        public int getProgress() {\n            return progress;\n        }\n    }\n\n    @Immutable\n    public static class Version {\n        private final String name;\n        private final String released;\n\n        public Version() {\n            this(\"\", \"\");\n        }\n\n        public Version(String name, String released) {\n            this.name = name;\n            this.released = released;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public String getReleased() {\n            return released;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaRemoteFiles.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.mojang;\n\nimport org.jackhuang.hmcl.game.DownloadInfo;\nimport org.jackhuang.hmcl.util.gson.JsonSubtype;\nimport org.jackhuang.hmcl.util.gson.JsonType;\n\nimport java.util.Collections;\nimport java.util.Map;\n\npublic final class MojangJavaRemoteFiles {\n    private final Map<String, Remote> files;\n\n    public MojangJavaRemoteFiles(Map<String, Remote> files) {\n        this.files = files;\n    }\n\n    public Map<String, Remote> getFiles() {\n        return files;\n    }\n\n    @JsonType(\n            property = \"type\",\n            subtypes = {\n                    @JsonSubtype(clazz = RemoteFile.class, name = \"file\"),\n                    @JsonSubtype(clazz = RemoteDirectory.class, name = \"directory\"),\n                    @JsonSubtype(clazz = RemoteLink.class, name = \"link\")\n            }\n    )\n    public static class Remote {\n        private final String type;\n\n        public Remote(String type) {\n            this.type = type;\n        }\n\n        public String getType() {\n            return type;\n        }\n    }\n\n    public static class RemoteFile extends Remote {\n        private final boolean executable;\n        private final Map<String, DownloadInfo> downloads;\n\n        public RemoteFile(boolean executable, Map<String, DownloadInfo> downloads) {\n            super(\"file\");\n            this.executable = executable;\n            this.downloads = downloads;\n        }\n\n        public boolean isExecutable() {\n            return executable;\n        }\n\n        public Map<String, DownloadInfo> getDownloads() {\n            return downloads == null ? Collections.emptyMap() : downloads;\n        }\n    }\n\n    public static class RemoteDirectory extends Remote {\n        public RemoteDirectory() {\n            super(\"directory\");\n        }\n    }\n\n    public static class RemoteLink extends Remote {\n        private final String target;\n\n        public RemoteLink(String target) {\n            super(\"link\");\n            this.target = target;\n        }\n\n        public String getTarget() {\n            return target;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.java.mojang;\n\nimport org.jackhuang.hmcl.download.java.JavaRemoteVersion;\nimport org.jackhuang.hmcl.game.GameJavaVersion;\n\n/**\n * @author Glavo\n */\npublic final class MojangJavaRemoteVersion implements JavaRemoteVersion {\n    private final GameJavaVersion gameJavaVersion;\n\n    public MojangJavaRemoteVersion(GameJavaVersion gameJavaVersion) {\n        this.gameJavaVersion = gameJavaVersion;\n    }\n\n    public GameJavaVersion getGameJavaVersion() {\n        return gameJavaVersion;\n    }\n\n    @Override\n    public int getJdkVersion() {\n        return gameJavaVersion.majorVersion();\n    }\n\n    @Override\n    public String getJavaVersion() {\n        return String.valueOf(getJdkVersion());\n    }\n\n    @Override\n    public String getDistributionVersion() {\n        return String.valueOf(getJdkVersion());\n    }\n\n    @Override\n    public String toString() {\n        return \"MojangJavaRemoteVersion[gameJavaVersion=\" + gameJavaVersion + \"]\";\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/legacyfabric/LegacyFabricAPIInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.legacyfabric;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\npublic final class LegacyFabricAPIInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final LegacyFabricAPIRemoteVersion remote;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    public LegacyFabricAPIInstallTask(DefaultDependencyManager dependencyManager, Version version, LegacyFabricAPIRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remote = remoteVersion;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws IOException {\n        dependencies.add(new FileDownloadTask(\n                remote.getVersion().getFile().getUrl(),\n                dependencyManager.getGameRepository().getModsDirectory(version.getId()).resolve(\"legacy-fabric-api-\" + remote.getVersion().getVersion() + \".jar\"),\n                remote.getVersion().getFile().getIntegrityCheck())\n        );\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/legacyfabric/LegacyFabricAPIRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.legacyfabric;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.time.Instant;\nimport java.util.List;\n\npublic class LegacyFabricAPIRemoteVersion extends RemoteVersion {\n    private final String fullVersion;\n    private final RemoteMod.Version version;\n\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param urls        the installer or universal jar original URL.\n     */\n    LegacyFabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List<String> urls) {\n        super(LibraryAnalyzer.LibraryType.LEGACY_FABRIC_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);\n\n        this.fullVersion = fullVersion;\n        this.version = version;\n    }\n\n    @Override\n    public String getFullVersion() {\n        return fullVersion;\n    }\n\n    public RemoteMod.Version getVersion() {\n        return version;\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new LegacyFabricAPIInstallTask(dependencyManager, baseVersion, this);\n    }\n\n    @Override\n    public int compareTo(RemoteVersion o) {\n        if (!(o instanceof LegacyFabricAPIRemoteVersion)) return 0;\n        return -this.getReleaseDate().compareTo(o.getReleaseDate());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/legacyfabric/LegacyFabricAPIVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.legacyfabric;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Lang;\n\nimport java.util.Collections;\n\npublic class LegacyFabricAPIVersionList extends VersionList<LegacyFabricAPIRemoteVersion> {\n\n    private final DownloadProvider downloadProvider;\n\n    public LegacyFabricAPIVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return Task.runAsync(() -> {\n            for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById(downloadProvider, \"legacy-fabric-api\"))) {\n                for (String gameVersion : modVersion.getGameVersions()) {\n                    versions.put(gameVersion, new LegacyFabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,\n                            Collections.singletonList(modVersion.getFile().getUrl())));\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/legacyfabric/LegacyFabricInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.legacyfabric;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.fabric.FabricInstallTask;\nimport org.jackhuang.hmcl.game.Arguments;\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\npublic final class LegacyFabricInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final LegacyFabricRemoteVersion remote;\n    private final GetTask launchMetaTask;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    public LegacyFabricInstallTask(DefaultDependencyManager dependencyManager, Version version, LegacyFabricRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remote = remoteVersion;\n\n        launchMetaTask = new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()));\n        launchMetaTask.setCacheRepository(dependencyManager.getCacheRepository());\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return Collections.singleton(launchMetaTask);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() {\n        setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), FabricInstallTask.FabricInfo.class), remote.getGameVersion(), remote.getSelfVersion()));\n\n        dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));\n    }\n\n    private Version getPatch(FabricInstallTask.FabricInfo legacyFabricInfo, String gameVersion, String loaderVersion) {\n        JsonObject launcherMeta = legacyFabricInfo.getLauncherMeta();\n        Arguments arguments = new Arguments();\n\n        String mainClass;\n        if (!launcherMeta.get(\"mainClass\").isJsonObject()) {\n            mainClass = launcherMeta.get(\"mainClass\").getAsString();\n        } else {\n            mainClass = launcherMeta.get(\"mainClass\").getAsJsonObject().get(\"client\").getAsString();\n        }\n\n        if (launcherMeta.has(\"launchwrapper\")) {\n            String clientTweaker = launcherMeta.get(\"launchwrapper\").getAsJsonObject().get(\"tweakers\").getAsJsonObject().get(\"client\").getAsJsonArray().get(0).getAsString();\n            arguments = arguments.addGameArguments(\"--tweakClass\", clientTweaker);\n        }\n\n        JsonObject librariesObject = launcherMeta.getAsJsonObject(\"libraries\");\n        List<Library> libraries = new ArrayList<>();\n\n        // \"common, server\" is hard coded in fabric installer.\n        // Don't know the purpose of ignoring client libraries.\n        for (String side : new String[]{\"common\", \"server\"}) {\n            for (JsonElement element : librariesObject.getAsJsonArray(side)) {\n                libraries.add(JsonUtils.GSON.fromJson(element, Library.class));\n            }\n        }\n\n        // libraries.add(new Library(Artifact.fromDescriptor(legacyFabricInfo.hashed.maven), getMavenRepositoryByGroup(legacyFabricInfo.hashed.maven), null));\n        libraries.add(new Library(Artifact.fromDescriptor(legacyFabricInfo.getIntermediary().getMaven()), getMavenRepositoryByGroup(legacyFabricInfo.getIntermediary().getMaven()), null));\n        libraries.add(new Library(Artifact.fromDescriptor(legacyFabricInfo.getLoader().getMaven()), getMavenRepositoryByGroup(legacyFabricInfo.getLoader().getMaven()), null));\n\n        return new Version(LibraryAnalyzer.LibraryType.LEGACY_FABRIC.getPatchId(), loaderVersion, Version.PRIORITY_LOADER, arguments, mainClass, libraries);\n    }\n\n    private static String getMavenRepositoryByGroup(String maven) {\n        Artifact artifact = Artifact.fromDescriptor(maven);\n        return switch (artifact.getGroup()) {\n            case \"net.fabricmc\" -> \"https://maven.fabricmc.net/\";\n            case \"net.legacyfabric\" -> \"https://maven.legacyfabric.net/\";\n            default -> \"https://maven.fabricmc.net/\";\n        };\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/legacyfabric/LegacyFabricRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.legacyfabric;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.util.List;\n\npublic class LegacyFabricRemoteVersion extends RemoteVersion {\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param urls        the installer or universal jar original URL.\n     */\n    LegacyFabricRemoteVersion(String gameVersion, String selfVersion, List<String> urls) {\n        super(LibraryAnalyzer.LibraryType.LEGACY_FABRIC.getPatchId(), gameVersion, selfVersion, null, urls);\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new LegacyFabricInstallTask(dependencyManager, baseVersion, this);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/legacyfabric/LegacyFabricVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.legacyfabric;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\n\npublic final class LegacyFabricVersionList extends VersionList<LegacyFabricRemoteVersion> {\n    private final DownloadProvider downloadProvider;\n\n    public LegacyFabricVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return Task.runAsync(() -> {\n            List<String> gameVersions = getGameVersions(GAME_META_URL);\n            List<String> loaderVersions = getGameVersions(LOADER_META_URL);\n\n            lock.writeLock().lock();\n\n            try {\n                for (String gameVersion : gameVersions)\n                    for (String loaderVersion : loaderVersions)\n                        versions.put(gameVersion, new LegacyFabricRemoteVersion(gameVersion, loaderVersion,\n                                Collections.singletonList(getLaunchMetaUrl(gameVersion, loaderVersion))));\n            } finally {\n                lock.writeLock().unlock();\n            }\n        });\n    }\n\n    private static final String LOADER_META_URL = \"https://meta.legacyfabric.net/v2/versions/loader\";\n    private static final String GAME_META_URL = \"https://meta.legacyfabric.net/v2/versions/game\";\n\n    private List<String> getGameVersions(String metaUrl) throws IOException {\n        String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl));\n        return JsonUtils.GSON.fromJson(json, listTypeOf(GameVersion.class))\n                .stream().map(GameVersion::getVersion).collect(Collectors.toList());\n    }\n\n    private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) {\n        return String.format(\"https://meta.legacyfabric.net/v2/versions/loader/%s/%s\", gameVersion, loaderVersion);\n    }\n\n    private static class GameVersion {\n        private final String version;\n        private final String maven;\n        private final boolean stable;\n\n        public GameVersion() {\n            this(\"\", null, false);\n        }\n\n        public GameVersion(String version, String maven, boolean stable) {\n            this.version = version;\n            this.maven = maven;\n            this.stable = stable;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        @Nullable\n        public String getMaven() {\n            return maven;\n        }\n\n        public boolean isStable() {\n            return stable;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBMCLVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.util.Collections;\nimport java.util.Map;\n\n/**\n * @author huangyuhui\n */\npublic final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemoteVersion> {\n    private final BMCLAPIDownloadProvider downloadProvider;\n\n    public LiteLoaderBMCLVersionList(BMCLAPIDownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    private static final class LiteLoaderBMCLVersion {\n\n        private final LiteLoaderVersion build;\n        private final String version;\n\n        public LiteLoaderBMCLVersion(LiteLoaderVersion build, String version) {\n            this.build = build;\n            this.version = version;\n        }\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Task<?> refreshAsync(String gameVersion) {\n        return new GetTask(\n                NetworkUtils.withQuery(downloadProvider.getApiRoot() + \"/liteloader/list\", Map.of(\n                        \"mcversion\", gameVersion\n                )))\n                .thenApplyAsync(json -> JsonUtils.fromMaybeMalformedJson(json, LiteLoaderBMCLVersion.class))\n                .thenAcceptAsync(v -> {\n                    lock.writeLock().lock();\n                    try {\n                        versions.clear();\n                        if (v == null)\n                            return;\n                        versions.put(gameVersion, new LiteLoaderRemoteVersion(\n                                gameVersion, v.version, RemoteVersion.Type.UNCATEGORIZED,\n                                Collections.singletonList(NetworkUtils.withQuery(\n                                        downloadProvider.getApiRoot() + \"/liteloader/download\",\n                                        Collections.singletonMap(\"version\", v.version)\n                                )),\n                                v.build.getTweakClass(), v.build.getLibraries()\n                        ));\n                    } finally {\n                        lock.writeLock().unlock();\n                    }\n                });\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBranch.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class LiteLoaderBranch {\n\n    @SerializedName(\"libraries\")\n    private final Collection<Library> libraries;\n\n    @SerializedName(\"com.mumfrey:liteloader\")\n    private final Map<String, LiteLoaderVersion> liteLoader;\n\n    /**\n     * No-arg constructor for Gson.\n     */\n    @SuppressWarnings(\"unused\")\n    public LiteLoaderBranch() {\n        this(Collections.emptySet(), Collections.emptyMap());\n    }\n\n    public LiteLoaderBranch(Collection<Library> libraries, Map<String, LiteLoaderVersion> liteLoader) {\n        this.libraries = libraries;\n        this.liteLoader = liteLoader;\n    }\n\n    public Collection<Library> getLibraries() {\n        return Collections.unmodifiableCollection(libraries);\n    }\n\n    public Map<String, LiteLoaderVersion> getLiteLoader() {\n        return Collections.unmodifiableMap(liteLoader);\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderGameVersions.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class LiteLoaderGameVersions {\n\n    @SerializedName(\"repo\")\n    private final LiteLoaderRepository repoitory;\n\n    @SerializedName(\"artefacts\")\n    private final LiteLoaderBranch artifacts;\n\n    @SerializedName(\"snapshots\")\n    private final LiteLoaderBranch snapshots;\n\n    /**\n     * No-arg constructor for Gson.\n     */\n    @SuppressWarnings(\"unused\")\n    public LiteLoaderGameVersions() {\n        this(null, null, null);\n    }\n\n    public LiteLoaderGameVersions(LiteLoaderRepository repoitory, LiteLoaderBranch artifacts, LiteLoaderBranch snapshots) {\n        this.repoitory = repoitory;\n        this.artifacts = artifacts;\n        this.snapshots = snapshots;\n    }\n\n    public LiteLoaderRepository getRepoitory() {\n        return repoitory;\n    }\n\n    public LiteLoaderBranch getArtifacts() {\n        return artifacts;\n    }\n\n    public LiteLoaderBranch getSnapshots() {\n        return snapshots;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Lang;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Note: LiteLoader must be installed after Forge.\n *\n * @author huangyuhui\n */\npublic final class LiteLoaderInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final LiteLoaderRemoteVersion remote;\n    private final List<Task<?>> dependents = new ArrayList<>();\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    public LiteLoaderInstallTask(DefaultDependencyManager dependencyManager, Version version, LiteLoaderRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remote = remoteVersion;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() {\n        Library library = new Library(\n                new Artifact(\"com.mumfrey\", \"liteloader\", remote.getSelfVersion()),\n                \"http://dl.liteloader.com/versions/\",\n                new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrls().get(0)))\n        );\n\n        setResult(new Version(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(),\n                remote.getSelfVersion(),\n                60000,\n                new Arguments().addGameArguments(\"--tweakClass\", \"com.mumfrey.liteloader.launch.LiteLoaderTweaker\"),\n                LibraryAnalyzer.LAUNCH_WRAPPER_MAIN,\n                Lang.merge(remote.getLibraries(), Collections.singleton(library)))\n                .setLogging(Collections.emptyMap()) // Mods may log in malformed format, causing XML parser to crash. So we suppress using official log4j configuration\n        );\n\n        dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.util.Collection;\nimport java.util.List;\n\npublic class LiteLoaderRemoteVersion extends RemoteVersion {\n    private final String tweakClass;\n    private final Collection<Library> libraries;\n\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param urls        the installer or universal jar original URL.\n     */\n    LiteLoaderRemoteVersion(String gameVersion, String selfVersion, Type type, List<String> urls, String tweakClass, Collection<Library> libraries) {\n        super(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(), gameVersion, selfVersion, null, type, urls);\n\n        this.tweakClass = tweakClass;\n        this.libraries = libraries;\n    }\n\n    public Collection<Library> getLibraries() {\n        return libraries;\n    }\n\n    public String getTweakClass() {\n        return tweakClass;\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new LiteLoaderInstallTask(dependencyManager, baseVersion, this);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class LiteLoaderRepository {\n\n    @SerializedName(\"stream\")\n    private final String stream;\n\n    @SerializedName(\"type\")\n    private final String type;\n\n    @SerializedName(\"url\")\n    private final String url;\n\n    @SerializedName(\"classifier\")\n    private final String classifier;\n\n    /**\n     * No-arg constructor for Gson.\n     */\n    @SuppressWarnings(\"unused\")\n    public LiteLoaderRepository() {\n        this(\"\", \"\", \"\", \"\");\n    }\n\n    public LiteLoaderRepository(String stream, String type, String url, String classifier) {\n        this.stream = stream;\n        this.type = type;\n        this.url = url;\n        this.classifier = classifier;\n    }\n\n    public String getStream() {\n        return stream;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public String getClassifier() {\n        return classifier;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class LiteLoaderVersion {\n    private final String tweakClass;\n    private final String file;\n    private final String version;\n    private final String md5;\n    private final String timestamp;\n    private final int lastSuccessfulBuild;\n    private final Collection<Library> libraries;\n\n    /**\n     * No-arg constructor for Gson.\n     */\n    @SuppressWarnings(\"unused\")\n    public LiteLoaderVersion() {\n        this(\"\", \"\", \"\", \"\", \"\", 0, Collections.emptySet());\n    }\n\n    public LiteLoaderVersion(String tweakClass, String file, String version, String md5, String timestamp, int lastSuccessfulBuild, Collection<Library> libraries) {\n        this.tweakClass = tweakClass;\n        this.file = file;\n        this.version = version;\n        this.md5 = md5;\n        this.timestamp = timestamp;\n        this.lastSuccessfulBuild = lastSuccessfulBuild;\n        this.libraries = libraries;\n    }\n\n    public String getTweakClass() {\n        return tweakClass;\n    }\n\n    public String getFile() {\n        return file;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public String getMd5() {\n        return md5;\n    }\n\n    public String getTimestamp() {\n        return timestamp;\n    }\n\n    public int getLastSuccessfulBuild() {\n        return lastSuccessfulBuild;\n    }\n\n    public Collection<Library> getLibraries() {\n        return Collections.unmodifiableCollection(libraries);\n    }\n    \n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.io.HttpRequest;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\n\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * @author huangyuhui\n */\npublic final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVersion> {\n\n    private final DownloadProvider downloadProvider;\n\n    public LiteLoaderVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return true;\n    }\n\n    public static final String LITELOADER_LIST = \"https://dl.liteloader.com/versions/versions.json\";\n\n    @Override\n    public Task<?> refreshAsync(String gameVersion) {\n        return new GetTask(downloadProvider.injectURLWithCandidates(LITELOADER_LIST))\n                .thenGetJsonAsync(LiteLoaderVersionsRoot.class)\n                .thenAcceptAsync(root -> {\n                    LiteLoaderGameVersions versions = root.getVersions().get(gameVersion);\n                    if (versions == null) {\n                        return;\n                    }\n\n                    LiteLoaderRemoteVersion snapshot = null;\n                    if (versions.getSnapshots() != null) {\n                        try {\n                            snapshot = loadSnapshotVersion(gameVersion, versions.getSnapshots().getLiteLoader().get(\"latest\"));\n                        } catch (IOException e) {\n                            throw new UncheckedIOException(e);\n                        }\n                    }\n\n                    lock.writeLock().lock();\n                    try {\n                        this.versions.clear();\n\n                        if (versions.getRepoitory() != null && versions.getArtifacts() != null) {\n                            loadArtifactVersion(gameVersion, versions.getRepoitory(), versions.getArtifacts());\n                        }\n\n                        if (snapshot != null) {\n                            this.versions.put(gameVersion, snapshot);\n                        }\n                    } finally {\n                        lock.writeLock().unlock();\n                    }\n                });\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        throw new UnsupportedOperationException();\n    }\n\n    private void loadArtifactVersion(String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch) {\n        for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {\n            String branchName = entry.getKey();\n            LiteLoaderVersion v = entry.getValue();\n            if (\"latest\".equals(branchName))\n                continue;\n\n            versions.put(gameVersion, new LiteLoaderRemoteVersion(\n                    gameVersion, v.getVersion(), RemoteVersion.Type.RELEASE,\n                    Collections.singletonList(repository.getUrl() + \"com/mumfrey/liteloader/\" + gameVersion + \"/\" + v.getFile()),\n                    v.getTweakClass(), v.getLibraries()\n            ));\n        }\n    }\n\n    // Workaround for https://github.com/HMCL-dev/HMCL/issues/3147: Some LiteLoader artifacts aren't published on http://dl.liteloader.com/repo\n    private static final String SNAPSHOT_METADATA = \"https://repo.mumfrey.com/content/repositories/snapshots/com/mumfrey/liteloader/%s-SNAPSHOT/maven-metadata.xml\";\n    private static final String SNAPSHOT_FILE = \"https://repo.mumfrey.com/content/repositories/snapshots/com/mumfrey/liteloader/%s-SNAPSHOT/liteloader-%s-%s-%s-release.jar\";\n\n    private LiteLoaderRemoteVersion loadSnapshotVersion(String gameVersion, LiteLoaderVersion v) throws IOException {\n        String root = HttpRequest.GET(String.format(SNAPSHOT_METADATA, gameVersion)).getString();\n        Document document = Jsoup.parseBodyFragment(root);\n        String timestamp = Objects.requireNonNull(document.select(\"timestamp\"), \"timestamp\").text();\n        String buildNumber = Objects.requireNonNull(document.select(\"buildNumber\"), \"buildNumber\").text();\n\n        return new LiteLoaderRemoteVersion(\n                gameVersion, timestamp + \"-\" + buildNumber, RemoteVersion.Type.SNAPSHOT,\n                Collections.singletonList(String.format(SNAPSHOT_FILE, gameVersion, gameVersion, timestamp, buildNumber)),\n                v.getTweakClass(), v.getLibraries()\n        );\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsMeta.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class LiteLoaderVersionsMeta {\n\n    @SerializedName(\"description\")\n    private final String description;\n\n    @SerializedName(\"authors\")\n    private final String authors;\n\n    @SerializedName(\"url\")\n    private final String url;\n\n    public LiteLoaderVersionsMeta() {\n        this(\"\", \"\", \"\");\n    }\n\n    public LiteLoaderVersionsMeta(String description, String authors, String url) {\n        this.description = description;\n        this.authors = authors;\n        this.url = url;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public String getAuthors() {\n        return authors;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsRoot.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.liteloader;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.util.Collections;\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class LiteLoaderVersionsRoot {\n\n    @SerializedName(\"versions\")\n    private final Map<String, LiteLoaderGameVersions> versions;\n\n    @SerializedName(\"meta\")\n    private final LiteLoaderVersionsMeta meta;\n\n    public LiteLoaderVersionsRoot() {\n        this(Collections.emptyMap(), null);\n    }\n\n    public LiteLoaderVersionsRoot(Map<String, LiteLoaderGameVersions> versions, LiteLoaderVersionsMeta meta) {\n        this.versions = versions;\n        this.meta = meta;\n    }\n\n    public Map<String, LiteLoaderGameVersions> getVersions() {\n        return Collections.unmodifiableMap(versions);\n    }\n\n    public LiteLoaderVersionsMeta getMeta() {\n        return meta;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.neoforge;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport java.util.Collections;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\n\npublic final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVersion> {\n    private final String apiRoot;\n\n    /**\n     * @param apiRoot API Root of BMCLAPI implementations\n     */\n    public NeoForgeBMCLVersionList(String apiRoot) {\n        this.apiRoot = apiRoot;\n    }\n\n    @Override\n    public boolean hasType() {\n        return true;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        throw new UnsupportedOperationException(\"NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list.\");\n    }\n\n    @Override\n    public Optional<NeoForgeRemoteVersion> getVersion(String gameVersion, String remoteVersion) {\n        if (gameVersion.equals(\"1.20.1\")) {\n            remoteVersion = NeoForgeRemoteVersion.normalize(remoteVersion);\n        }\n        return super.getVersion(gameVersion, remoteVersion);\n    }\n\n    @Override\n    public Task<?> refreshAsync(String gameVersion) {\n        return new GetTask(apiRoot + \"/neoforge/list/\" + gameVersion).thenGetJsonAsync(listTypeOf(NeoForgeVersion.class))\n                .thenAcceptAsync(neoForgeVersions -> {\n                    lock.writeLock().lock();\n\n                    try {\n                        versions.clear(gameVersion);\n                        for (NeoForgeVersion neoForgeVersion : neoForgeVersions) {\n                            versions.put(gameVersion, new NeoForgeRemoteVersion(\n                                    neoForgeVersion.mcVersion,\n                                    NeoForgeRemoteVersion.normalize(neoForgeVersion.version),\n                                    Collections.singletonList(apiRoot + \"/neoforge/version/\" + neoForgeVersion.version + \"/download/installer.jar\")\n                            ));\n                        }\n                    } finally {\n                        lock.writeLock().unlock();\n                    }\n                });\n    }\n\n    @Immutable\n    private static final class NeoForgeVersion implements Validation {\n        private final String rawVersion;\n\n        private final String version;\n\n        @SerializedName(\"mcversion\")\n        private final String mcVersion;\n\n        public NeoForgeVersion(String rawVersion, String version, String mcVersion) {\n            this.rawVersion = rawVersion;\n            this.version = version;\n            this.mcVersion = mcVersion;\n        }\n\n        @Override\n        public void validate() throws JsonParseException {\n            if (this.rawVersion == null) {\n                throw new JsonParseException(\"NeoForgeVersion rawVersion cannot be null.\");\n            }\n            if (this.version == null) {\n                throw new JsonParseException(\"NeoForgeVersion version cannot be null.\");\n            }\n            if (this.mcVersion == null) {\n                throw new JsonParseException(\"NeoForgeVersion mcversion cannot be null.\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java",
    "content": "package org.jackhuang.hmcl.download.neoforge;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.VersionMismatchException;\nimport org.jackhuang.hmcl.download.forge.*;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\n\nimport java.io.IOException;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.StringUtils.removePrefix;\nimport static org.jackhuang.hmcl.util.StringUtils.removeSuffix;\n\npublic final class NeoForgeInstallTask extends Task<Version> {\n    private final DefaultDependencyManager dependencyManager;\n\n    private final Version version;\n\n    private final NeoForgeRemoteVersion remoteVersion;\n\n    private Path installer = null;\n\n    private FileDownloadTask dependent;\n\n    private Task<Version> dependency;\n\n    public NeoForgeInstallTask(DefaultDependencyManager dependencyManager, Version version, NeoForgeRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remoteVersion = remoteVersion;\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        installer = Files.createTempFile(\"neoforge-installer\", \".jar\");\n\n        dependent = new FileDownloadTask(\n                dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()),\n                installer, null\n        );\n        dependent.setCacheRepository(dependencyManager.getCacheRepository());\n        dependent.setCaching(true);\n        dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        Files.deleteIfExists(installer);\n        this.setResult(dependency.getResult());\n    }\n\n    @Override\n    public Collection<? extends Task<?>> getDependents() {\n        return Collections.singleton(dependent);\n    }\n\n    @Override\n    public Collection<? extends Task<?>> getDependencies() {\n        return Collections.singleton(dependency);\n    }\n\n    @Override\n    public void execute() throws Exception {\n        dependency = install(dependencyManager, version, installer);\n    }\n\n    public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {\n        Optional<String> gameVersion = dependencyManager.getGameRepository().getGameVersion(version);\n        if (!gameVersion.isPresent()) throw new IOException();\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {\n            String installProfileText = Files.readString(fs.getPath(\"install_profile.json\"));\n            Map<?, ?> installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class);\n            if (LibraryAnalyzer.LibraryType.FORGE.getPatchId().equals(installProfile.get(\"profile\")) && (Files.exists(fs.getPath(\"META-INF/NEOFORGE.RSA\")) || installProfileText.contains(\"neoforge\"))) {\n                ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);\n                if (!gameVersion.get().equals(profile.getMinecraft()))\n                    throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());\n                return new ForgeNewInstallTask(dependencyManager, version, modifyNeoForgeOldVersion(gameVersion.get(), profile.getVersion()), installer).thenApplyAsync(neoForgeVersion -> {\n                    if (!neoForgeVersion.getId().equals(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) || neoForgeVersion.getVersion() == null) {\n                        throw new IOException(\"Invalid neoforge version.\");\n                    }\n                    return neoForgeVersion.setId(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId())\n                            .setVersion(\n                                    removePrefix(neoForgeVersion.getVersion().replace(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), \"\"), \"-\")\n                            );\n                });\n            } else if (LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId().equals(installProfile.get(\"profile\")) || \"NeoForge\".equals(installProfile.get(\"profile\"))) {\n                ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);\n                if (!gameVersion.get().equals(profile.getMinecraft()))\n                    throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());\n                return new NeoForgeOldInstallTask(dependencyManager, version, modifyNeoForgeNewVersion(profile.getVersion()), installer);\n            } else {\n                throw new IOException();\n            }\n        }\n    }\n\n    private static String modifyNeoForgeOldVersion(String gameVersion, String version) {\n        return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, \"\").trim(), \"-\"), \"-\"), \"_\"), \"_\");\n    }\n\n    private static String modifyNeoForgeNewVersion(String version) {\n        return removePrefix(version.replace(\"neoforge\", \"\"), \"-\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOfficialVersionList.java",
    "content": "package org.jackhuang.hmcl.download.neoforge;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemoteVersion> {\n    private final DownloadProvider downloadProvider;\n\n    public NeoForgeOfficialVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return true;\n    }\n\n    private static final String OLD_URL = \"https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge\";\n    private static final String META_URL = \"https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge\";\n\n    @Override\n    public Optional<NeoForgeRemoteVersion> getVersion(String gameVersion, String remoteVersion) {\n        if (gameVersion.equals(\"1.20.1\")) {\n            remoteVersion = NeoForgeRemoteVersion.normalize(remoteVersion);\n        }\n        return super.getVersion(gameVersion, remoteVersion);\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return Task.allOf(\n                new GetTask(downloadProvider.injectURLWithCandidates(OLD_URL)).thenGetJsonAsync(OfficialAPIResult.class),\n                new GetTask(downloadProvider.injectURLWithCandidates(META_URL)).thenGetJsonAsync(OfficialAPIResult.class)\n        ).thenAcceptAsync(results -> {\n            lock.writeLock().lock();\n\n            try {\n                versions.clear();\n\n                for (String version : results.get(0).versions) {\n                    versions.put(\"1.20.1\", new NeoForgeRemoteVersion(\n                            \"1.20.1\", NeoForgeRemoteVersion.normalize(version),\n                            Collections.singletonList(\n                                    \"https://maven.neoforged.net/releases/net/neoforged/forge/\" + version + \"/forge-\" + version + \"-installer.jar\"\n                            )\n                    ));\n                }\n\n                for (String version : results.get(1).versions) {\n                    String mcVersion;\n\n                    try {\n                        int si1 = version.indexOf('.'), si2 = version.indexOf('.', version.indexOf('.') + 1);\n                        int majorVersion = Integer.parseInt(version.substring(0, si1));\n                        if (majorVersion == 0) { // Snapshot version.\n                            mcVersion = version.substring(si1 + 1, si2);\n                        } else {\n                            String ver = version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2);\n                            if (majorVersion >= 26) {\n                                int separator = version.indexOf('+');\n                                mcVersion = separator < 0 ? ver : ver + \"-\" + version.substring(separator + 1);\n                            } else {\n                                mcVersion = \"1.\" + ver;\n                            }\n                        }\n                    } catch (RuntimeException e) {\n                        LOG.warning(String.format(\"Cannot parse NeoForge version %s for cracking its mc version.\", version), e);\n                        continue;\n                    }\n\n                    versions.put(mcVersion, new NeoForgeRemoteVersion(\n                            mcVersion, NeoForgeRemoteVersion.normalize(version),\n                            Collections.singletonList(\n                                    \"https://maven.neoforged.net/releases/net/neoforged/neoforge/\" + version + \"/neoforge-\" + version + \"-installer.jar\"\n                            )\n                    ));\n                }\n            } finally {\n                lock.writeLock().unlock();\n            }\n        });\n    }\n\n    private static final class OfficialAPIResult {\n        private final boolean isSnapshot;\n\n        private final List<String> versions;\n\n        public OfficialAPIResult(boolean isSnapshot, List<String> versions) {\n            this.isSnapshot = isSnapshot;\n            this.versions = versions;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.neoforge;\n\nimport org.jackhuang.hmcl.download.ArtifactMalformedException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile;\nimport org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile.Processor;\nimport org.jackhuang.hmcl.download.game.GameLibrariesTask;\nimport org.jackhuang.hmcl.download.game.VersionJsonDownloadTask;\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.ChecksumMismatchException;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.CommandBuilder;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\nimport java.util.zip.ZipException;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson;\n\npublic class NeoForgeOldInstallTask extends Task<Version> {\n\n    private class ProcessorTask extends Task<Void> {\n\n        private final Processor processor;\n        private final Map<String, String> vars;\n\n        public ProcessorTask(@NotNull Processor processor, @NotNull Map<String, String> vars) {\n            this.processor = processor;\n            this.vars = vars;\n            setSignificance(TaskSignificance.MODERATE);\n        }\n\n        @Override\n        public void execute() throws Exception {\n            Map<String, String> outputs = new HashMap<>();\n            boolean miss = false;\n\n            for (Map.Entry<String, String> entry : processor.getOutputs().entrySet()) {\n                String key = entry.getKey();\n                String value = entry.getValue();\n\n                key = parseLiteral(key, vars);\n                value = parseLiteral(value, vars);\n\n                if (key == null || value == null) {\n                    throw new ArtifactMalformedException(\"Invalid forge installation configuration\");\n                }\n\n                outputs.put(key, value);\n\n                Path artifact = Paths.get(key);\n                if (Files.exists(artifact)) {\n                    String code;\n                    try (InputStream stream = Files.newInputStream(artifact)) {\n                        code = (DigestUtils.digestToString(\"SHA-1\", stream));\n                    }\n\n                    if (!Objects.equals(code, value)) {\n                        Files.delete(artifact);\n                        LOG.info(\"Found existing file is not valid: \" + artifact);\n\n                        miss = true;\n                    }\n                } else {\n                    miss = true;\n                }\n            }\n\n            if (!processor.getOutputs().isEmpty() && !miss) {\n                return;\n            }\n\n            Path jar = gameRepository.getArtifactFile(version, processor.getJar());\n            if (!Files.isRegularFile(jar))\n                throw new FileNotFoundException(\"Game processor file not found, should be downloaded in preprocess\");\n\n            String mainClass;\n            try (JarFile jarFile = new JarFile(jar.toFile())) {\n                mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);\n            }\n\n            if (StringUtils.isBlank(mainClass))\n                throw new Exception(\"Game processor jar does not have main class \" + jar);\n\n            List<String> command = new ArrayList<>();\n            command.add(JavaRuntime.getDefault().getBinary().toString());\n            command.add(\"-cp\");\n\n            List<String> classpath = new ArrayList<>(processor.getClasspath().size() + 1);\n            for (Artifact artifact : processor.getClasspath()) {\n                Path file = gameRepository.getArtifactFile(version, artifact);\n                if (!Files.isRegularFile(file))\n                    throw new Exception(\"Game processor dependency missing\");\n                classpath.add(file.toString());\n            }\n            classpath.add(jar.toString());\n            command.add(String.join(File.pathSeparator, classpath));\n\n            command.add(mainClass);\n\n            List<String> args = new ArrayList<>(processor.getArgs().size());\n            for (String arg : processor.getArgs()) {\n                String parsed = parseLiteral(arg, vars);\n                if (parsed == null)\n                    throw new ArtifactMalformedException(\"Invalid forge installation configuration\");\n                args.add(parsed);\n            }\n\n            command.addAll(args);\n\n            LOG.info(\"Executing external processor \" + processor.getJar().toString() + \", command line: \" + new CommandBuilder().addAll(command).toString());\n            int exitCode = SystemUtils.callExternalProcess(command);\n            if (exitCode != 0)\n                throw new IOException(\"Game processor exited abnormally with code \" + exitCode);\n\n            for (Map.Entry<String, String> entry : outputs.entrySet()) {\n                Path artifact = Paths.get(entry.getKey());\n                if (!Files.isRegularFile(artifact))\n                    throw new FileNotFoundException(\"File missing: \" + artifact);\n\n                String code;\n                try (InputStream stream = Files.newInputStream(artifact)) {\n                    code = DigestUtils.digestToString(\"SHA-1\", stream);\n                }\n\n                if (!Objects.equals(code, entry.getValue())) {\n                    Files.delete(artifact);\n                    throw new ChecksumMismatchException(\"SHA-1\", entry.getValue(), code);\n                }\n            }\n        }\n    }\n\n    private final DefaultDependencyManager dependencyManager;\n    private final DefaultGameRepository gameRepository;\n    private final Version version;\n    private final Path installer;\n    private final List<Task<?>> dependents = new ArrayList<>(1);\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    private ForgeNewInstallProfile profile;\n    private List<Processor> processors;\n    private Version neoForgeVersion;\n    private final String selfVersion;\n\n    private Path tempDir;\n    private AtomicInteger processorDoneCount = new AtomicInteger(0);\n\n    NeoForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {\n        this.dependencyManager = dependencyManager;\n        this.gameRepository = dependencyManager.getGameRepository();\n        this.version = version;\n        this.installer = installer;\n        this.selfVersion = selfVersion;\n\n        setSignificance(TaskSignificance.MAJOR);\n    }\n\n    private static String replaceTokens(Map<String, String> tokens, String value) {\n        StringBuilder buf = new StringBuilder();\n        for (int x = 0; x < value.length(); x++) {\n            char c = value.charAt(x);\n            if (c == '\\\\') {\n                if (x == value.length() - 1)\n                    throw new IllegalArgumentException(\"Illegal pattern (Bad escape): \" + value);\n                buf.append(value.charAt(++x));\n            } else if (c == '{' || c == '\\'') {\n                StringBuilder key = new StringBuilder();\n                for (int y = x + 1; y <= value.length(); y++) {\n                    if (y == value.length())\n                        throw new IllegalArgumentException(\"Illegal pattern (Unclosed \" + c + \"): \" + value);\n                    char d = value.charAt(y);\n                    if (d == '\\\\') {\n                        if (y == value.length() - 1)\n                            throw new IllegalArgumentException(\"Illegal pattern (Bad escape): \" + value);\n                        key.append(value.charAt(++y));\n                    } else {\n                        if (c == '{' && d == '}') {\n                            x = y;\n                            break;\n                        }\n                        if (c == '\\'' && d == '\\'') {\n                            x = y;\n                            break;\n                        }\n                        key.append(d);\n                    }\n                }\n                if (c == '\\'') {\n                    buf.append(key);\n                } else {\n                    if (!tokens.containsKey(key.toString()))\n                        throw new IllegalArgumentException(\"Illegal pattern: \" + value + \" Missing Key: \" + key);\n                    buf.append(tokens.get(key.toString()));\n                }\n            } else {\n                buf.append(c);\n            }\n        }\n        return buf.toString();\n    }\n\n    private <E extends Exception> String parseLiteral(String literal, Map<String, String> var, ExceptionalFunction<String, String, E> plainConverter) throws E {\n        if (StringUtils.isSurrounded(literal, \"{\", \"}\"))\n            return var.get(StringUtils.removeSurrounding(literal, \"{\", \"}\"));\n        else if (StringUtils.isSurrounded(literal, \"'\", \"'\"))\n            return StringUtils.removeSurrounding(literal, \"'\");\n        else if (StringUtils.isSurrounded(literal, \"[\", \"]\"))\n            return gameRepository.getArtifactFile(version, Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, \"[\", \"]\"))).toString();\n        else\n            return plainConverter.apply(replaceTokens(var, literal));\n    }\n\n    private String parseLiteral(String literal, Map<String, String> var) {\n        return parseLiteral(literal, var, ExceptionalFunction.identity());\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {\n            profile = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(\"install_profile.json\")), ForgeNewInstallProfile.class);\n            processors = profile.getProcessors();\n            neoForgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.getJson())), Version.class);\n\n            for (Library library : profile.getLibraries()) {\n                Path file = fs.getPath(\"maven\").resolve(library.getPath());\n                if (Files.exists(file)) {\n                    Path dest = gameRepository.getLibraryFile(version, library);\n                    FileUtils.copyFile(file, dest);\n                }\n            }\n\n            if (profile.getPath().isPresent()) {\n                Path mainJar = profile.getPath().get().getPath(fs.getPath(\"maven\"));\n                if (Files.exists(mainJar)) {\n                    Path dest = gameRepository.getArtifactFile(version, profile.getPath().get());\n                    FileUtils.copyFile(mainJar, dest);\n                }\n            }\n        } catch (ZipException ex) {\n            throw new ArtifactMalformedException(\"Malformed forge installer file\", ex);\n        }\n\n        dependents.add(new GameLibrariesTask(dependencyManager, version, true, profile.getLibraries()));\n    }\n\n    private Map<String, String> parseOptions(List<String> args, Map<String, String> vars) {\n        Map<String, String> options = new LinkedHashMap<>();\n        String optionName = null;\n        for (String arg : args) {\n            if (arg.startsWith(\"--\")) {\n                if (optionName != null) {\n                    options.put(optionName, \"\");\n                }\n                optionName = arg.substring(2);\n            } else {\n                if (optionName == null) {\n                    // ignore\n                } else {\n                    options.put(optionName, parseLiteral(arg, vars));\n                    optionName = null;\n                }\n            }\n        }\n        if (optionName != null) {\n            options.put(optionName, \"\");\n        }\n        return options;\n    }\n\n    private Task<?> patchDownloadMojangMappingsTask(Processor processor, Map<String, String> vars) {\n        Map<String, String> options = parseOptions(processor.getArgs(), vars);\n        if (!\"DOWNLOAD_MOJMAPS\".equals(options.get(\"task\")) || !\"client\".equals(options.get(\"side\")))\n            return null;\n        String version = options.get(\"version\");\n        String output = options.get(\"output\");\n        if (version == null || output == null)\n            return null;\n\n        LOG.info(\"Patching DOWNLOAD_MOJMAPS task\");\n        return new VersionJsonDownloadTask(version, dependencyManager)\n                .thenComposeAsync(json -> {\n                    DownloadInfo mappings = fromNonNullJson(json, Version.class)\n                            .getDownloads().get(DownloadType.CLIENT_MAPPINGS);\n                    if (mappings == null) {\n                        throw new Exception(\"client_mappings download info not found\");\n                    }\n\n                    List<URI> mappingsUri = dependencyManager.getDownloadProvider()\n                            .injectURLWithCandidates(mappings.getUrl());\n                    var mappingsTask = new FileDownloadTask(\n                            mappingsUri,\n                            Path.of(output),\n                            FileDownloadTask.IntegrityCheck.of(\"SHA-1\", mappings.getSha1()));\n                    mappingsTask.setCaching(true);\n                    mappingsTask.setCacheRepository(dependencyManager.getCacheRepository());\n                    return mappingsTask;\n                });\n    }\n\n    private Task<?> createProcessorTask(Processor processor, Map<String, String> vars) {\n        Task<?> task = patchDownloadMojangMappingsTask(processor, vars);\n        if (task == null) {\n            task = new ProcessorTask(processor, vars);\n        }\n        task.onDone().register(\n                () -> updateProgress(processorDoneCount.incrementAndGet(), processors.size()));\n        return task;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        tempDir = Files.createTempDirectory(\"neoforge_installer\");\n\n        Map<String, String> vars = new HashMap<>();\n\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {\n            for (Map.Entry<String, String> entry : profile.getData().entrySet()) {\n                String key = entry.getKey();\n                String value = entry.getValue();\n\n                vars.put(key, parseLiteral(value,\n                        Collections.emptyMap(),\n                        str -> {\n                            Path dest = Files.createTempFile(tempDir, null, null);\n                            FileUtils.copyFile(fs.getPath(str), dest);\n                            return dest.toString();\n                        }));\n            }\n        } catch (ZipException ex) {\n            throw new ArtifactMalformedException(\"Malformed neoforge installer file\", ex);\n        }\n\n        vars.put(\"SIDE\", \"client\");\n        vars.put(\"MINECRAFT_JAR\", FileUtils.getAbsolutePath(gameRepository.getVersionJar(version)));\n        vars.put(\"MINECRAFT_VERSION\", FileUtils.getAbsolutePath(gameRepository.getVersionJar(version)));\n        vars.put(\"ROOT\", FileUtils.getAbsolutePath(gameRepository.getBaseDirectory()));\n        vars.put(\"INSTALLER\", installer.toAbsolutePath().toString());\n        vars.put(\"LIBRARY_DIR\", FileUtils.getAbsolutePath(gameRepository.getLibrariesDirectory(version)));\n\n        updateProgress(0, processors.size());\n\n        Task<?> processorsTask = Task.runSequentially(\n                processors.stream()\n                        .map(processor -> createProcessorTask(processor, vars))\n                        .toArray(Task<?>[]::new));\n\n        dependencies.add(\n                processorsTask.thenComposeAsync(\n                        dependencyManager.checkLibraryCompletionAsync(neoForgeVersion, true)));\n\n        setResult(neoForgeVersion\n                .setPriority(Version.PRIORITY_LOADER)\n                .setId(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId())\n                .setVersion(selfVersion));\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        FileUtils.deleteDirectory(tempDir);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeRemoteVersion.java",
    "content": "package org.jackhuang.hmcl.download.neoforge;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.util.List;\n\npublic class NeoForgeRemoteVersion extends RemoteVersion {\n    public NeoForgeRemoteVersion(String gameVersion, String selfVersion, List<String> urls) {\n        super(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId(), gameVersion, selfVersion, null, getType(selfVersion), urls);\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new NeoForgeInstallTask(dependencyManager, baseVersion, this);\n    }\n\n    private static Type getType(String version) {\n        return version.contains(\"beta\") || version.contains(\"alpha\") ? Type.SNAPSHOT : Type.RELEASE;\n    }\n\n    public static String normalize(String version) {\n        if (version.startsWith(\"1.20.1-\")) {\n            if (version.startsWith(\"forge-\", \"1.20.1-\".length())) {\n                return version.substring(\"1.20.1-forge-\".length());\n            } else {\n                return version.substring(\"1.20.1-\".length());\n            }\n        } else {\n            return version;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.optifine;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\n\n/**\n * @author huangyuhui\n */\npublic final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVersion> {\n    private final String apiRoot;\n\n    /**\n     * @param apiRoot API Root of BMCLAPI implementations\n     */\n    public OptiFineBMCLVersionList(String apiRoot) {\n        this.apiRoot = apiRoot;\n    }\n\n    @Override\n    public boolean hasType() {\n        return true;\n    }\n\n    private String fromLookupVersion(String version) {\n        switch (version) {\n            case \"1.8.0\":\n                return \"1.8\";\n            case \"1.9.0\":\n                return \"1.9\";\n            default:\n                return version;\n        }\n    }\n\n    private String toLookupVersion(String version) {\n        switch (version) {\n            case \"1.8\":\n                return \"1.8.0\";\n            case \"1.9\":\n                return \"1.9.0\";\n            default:\n                return version;\n        }\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return new GetTask(apiRoot + \"/optifine/versionlist\").thenGetJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> {\n            lock.writeLock().lock();\n\n            try {\n                versions.clear();\n                Set<String> duplicates = new HashSet<>();\n                for (OptiFineVersion element : root) {\n                    String version = element.getType() + \"_\" + element.getPatch();\n                    String mirror = apiRoot + \"/optifine/\" + toLookupVersion(element.getGameVersion()) + \"/\" + element.getType() + \"/\" + element.getPatch();\n                    if (!duplicates.add(mirror))\n                        continue;\n\n                    boolean isPre = element.getPatch() != null && (element.getPatch().startsWith(\"pre\") || element.getPatch().startsWith(\"alpha\"));\n\n                    if (StringUtils.isBlank(element.getGameVersion()))\n                        continue;\n\n                    String gameVersion = VersionNumber.normalize(fromLookupVersion(element.getGameVersion()));\n                    versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Collections.singletonList(mirror), isPre));\n                }\n            } finally {\n                lock.writeLock().unlock();\n            }\n        });\n    }\n\n    /**\n     * @author huangyuhui\n     */\n    private static final class OptiFineVersion {\n\n        @SerializedName(\"dl\")\n        private final String downloadLink;\n\n        @SerializedName(\"ver\")\n        private final String version;\n\n        @SerializedName(\"date\")\n        private final String date;\n\n        @SerializedName(\"type\")\n        private final String type;\n\n        @SerializedName(\"patch\")\n        private final String patch;\n\n        @SerializedName(\"mirror\")\n        private final String mirror;\n\n        @SerializedName(\"mcversion\")\n        private final String gameVersion;\n\n        public OptiFineVersion() {\n            this(null, null, null, null, null, null, null);\n        }\n\n        public OptiFineVersion(String downloadLink, String version, String date, String type, String patch, String mirror, String gameVersion) {\n            this.downloadLink = downloadLink;\n            this.version = version;\n            this.date = date;\n            this.type = type;\n            this.patch = patch;\n            this.mirror = mirror;\n            this.gameVersion = gameVersion;\n        }\n\n        public String getDownloadLink() {\n            return downloadLink;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        public String getDate() {\n            return date;\n        }\n\n        public String getType() {\n            return type;\n        }\n\n        public String getPatch() {\n            return patch;\n        }\n\n        public String getMirror() {\n            return mirror;\n        }\n\n        public String getGameVersion() {\n            return gameVersion;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.optifine;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.UnsupportedInstallationException;\nimport org.jackhuang.hmcl.download.VersionMismatchException;\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.CommandBuilder;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jenkinsci.constant_pool_scanner.ConstantPool;\nimport org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;\nimport org.jenkinsci.constant_pool_scanner.ConstantType;\nimport org.jenkinsci.constant_pool_scanner.Utf8Constant;\n\nimport java.io.IOException;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.Lang.getOrDefault;\n\n/**\n * <b>Note</b>: OptiFine should be installed in the end.\n *\n * @author huangyuhui\n */\npublic final class OptiFineInstallTask extends Task<Version> {\n\n    private final DefaultGameRepository gameRepository;\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final OptiFineRemoteVersion remote;\n    private final Path installer;\n    private final List<Task<?>> dependents = new ArrayList<>(0);\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n    private Path dest;\n\n    private final Library optiFineLibrary;\n    private final Library optiFineInstallerLibrary;\n\n    public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) {\n        this(dependencyManager, version, remoteVersion, null);\n    }\n\n    public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion, Path installer) {\n        this.dependencyManager = dependencyManager;\n        this.gameRepository = dependencyManager.getGameRepository();\n        this.version = version;\n        this.remote = remoteVersion;\n        this.installer = installer;\n\n        String mavenVersion = remote.getGameVersion() + \"_\" + remote.getSelfVersion();\n\n        optiFineLibrary = new Library(new Artifact(\"optifine\", \"OptiFine\", mavenVersion));\n\n        optiFineInstallerLibrary = new Library(\n                new Artifact(\"optifine\", \"OptiFine\", mavenVersion, \"installer\"), null,\n                new LibrariesDownloadInfo(new LibraryDownloadInfo(\n                        \"optifine/OptiFine/\" + mavenVersion + \"/OptiFine-\" + mavenVersion + \"-installer.jar\",\n                        remote.getUrls().get(0).toString()))\n        );\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        dest = Files.createTempFile(\"optifine-installer\", \".jar\");\n\n        if (installer == null) {\n            var task = new FileDownloadTask(\n                    dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),\n                    dest, null);\n            task.setCacheRepository(dependencyManager.getCacheRepository());\n            task.setCaching(true);\n            dependents.add(task);\n        } else {\n            FileUtils.copyFile(installer, dest);\n        }\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        String originalMainClass = version.resolve(dependencyManager.getGameRepository()).getMainClass();\n        if (!LibraryAnalyzer.FORGE_OPTIFINE_MAIN.contains(originalMainClass))\n            throw new UnsupportedInstallationException(UnsupportedInstallationException.UNSUPPORTED_LAUNCH_WRAPPER);\n\n        List<Library> libraries = new ArrayList<>(4);\n        libraries.add(optiFineLibrary);\n\n        Path optiFineInstallerLibraryPath = gameRepository.getLibraryFile(version, optiFineInstallerLibrary);\n        FileUtils.copyFile(dest, optiFineInstallerLibraryPath);\n\n        try (FileSystem fs2 = CompressingUtils.createWritableZipFileSystem(optiFineInstallerLibraryPath)) {\n            Files.deleteIfExists(fs2.getPath(\"/META-INF/mods.toml\"));\n        }\n\n        // Install launch wrapper modified by OptiFine\n        boolean hasLaunchWrapper = false;\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(dest)) {\n            Path optiFineLibraryPath = gameRepository.getLibraryFile(version, optiFineLibrary);\n            if (Files.exists(fs.getPath(\"optifine/Patcher.class\"))) {\n                String[] command = {\n                        JavaRuntime.getDefault().getBinary().toString(),\n                        \"-cp\",\n                        dest.toString(),\n                        \"optifine.Patcher\",\n                        gameRepository.getVersionJar(version).toAbsolutePath().normalize().toString(),\n                        dest.toString(),\n                        optiFineLibraryPath.toString()\n                };\n                int exitCode = SystemUtils.callExternalProcess(command);\n                if (exitCode != 0)\n                    throw new IOException(\"OptiFine patcher failed, command: \" + new CommandBuilder().addAll(Arrays.asList(command)));\n            } else {\n                FileUtils.copyFile(dest, optiFineLibraryPath);\n            }\n\n            try (FileSystem fs2 = CompressingUtils.createWritableZipFileSystem(optiFineLibraryPath)) {\n                Files.deleteIfExists(fs2.getPath(\"/META-INF/mods.toml\"));\n            }\n\n            Path launchWrapper2 = fs.getPath(\"launchwrapper-2.0.jar\");\n            if (Files.exists(launchWrapper2)) {\n                Library launchWrapper = new Library(new Artifact(\"optifine\", \"launchwrapper\", \"2.0\"));\n                Path launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper);\n                Files.createDirectories(launchWrapperFile.toAbsolutePath().getParent());\n                FileUtils.copyFile(launchWrapper2, launchWrapperFile);\n                hasLaunchWrapper = true;\n                libraries.add(launchWrapper);\n            }\n\n            Path launchWrapperVersionText = fs.getPath(\"launchwrapper-of.txt\");\n            if (Files.exists(launchWrapperVersionText)) {\n                String launchWrapperVersion = Files.readString(launchWrapperVersionText).trim();\n                Path launchWrapperJar = fs.getPath(\"launchwrapper-of-\" + launchWrapperVersion + \".jar\");\n\n                Library launchWrapper = new Library(new Artifact(\"optifine\", \"launchwrapper-of\", launchWrapperVersion));\n\n                if (Files.exists(launchWrapperJar)) {\n                    Path launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper);\n                    Files.createDirectories(launchWrapperFile.toAbsolutePath().getParent());\n                    FileUtils.copyFile(launchWrapperJar, launchWrapperFile);\n\n                    hasLaunchWrapper = true;\n                    libraries.add(launchWrapper);\n                }\n            }\n\n            Path buildofText = fs.getPath(\"buildof.txt\");\n            if (Files.exists(buildofText)) {\n                String buildof = Files.readString(buildofText).trim();\n                VersionNumber buildofVer = VersionNumber.asVersion(buildof);\n\n                if (LibraryAnalyzer.BOOTSTRAP_LAUNCHER_MAIN.equals(originalMainClass)) {\n                    // OptiFine H1 Pre2+ is compatible with Forge 1.17\n                    if (buildofVer.compareTo(\"20210924-190833\") < 0) {\n                        throw new UnsupportedInstallationException(UnsupportedInstallationException.FORGE_1_17_OPTIFINE_H1_PRE2);\n                    }\n                }\n            }\n        }\n\n        if (!hasLaunchWrapper) {\n            libraries.add(new Library(new Artifact(\"net.minecraft\", \"launchwrapper\", \"1.12\")));\n        }\n\n        setResult(new Version(\n                LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(),\n                remote.getSelfVersion(),\n                10000,\n                new Arguments().addGameArguments(\"--tweakClass\", \"optifine.OptiFineTweaker\"),\n                LibraryAnalyzer.LAUNCH_WRAPPER_MAIN,\n                libraries\n        ));\n\n        dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));\n    }\n\n    /**\n     * Install OptiFine library from existing local file.\n     *\n     * @param dependencyManager game repository\n     * @param version           version.json\n     * @param installer         the OptiFine installer\n     * @return the task to install library\n     * @throws IOException              if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.\n     * @throws VersionMismatchException if required game version of installer does not match the actual one.\n     */\n    public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {\n        Optional<String> gameVersion = dependencyManager.getGameRepository().getGameVersion(version);\n        if (!gameVersion.isPresent()) throw new IOException();\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {\n            Path configClass = fs.getPath(\"Config.class\");\n            if (!Files.exists(configClass)) configClass = fs.getPath(\"net/optifine/Config.class\");\n            if (!Files.exists(configClass)) configClass = fs.getPath(\"notch/net/optifine/Config.class\");\n            if (!Files.exists(configClass)) throw new IOException(\"Unrecognized installer\");\n            ConstantPool pool = ConstantPoolScanner.parse(Files.readAllBytes(configClass), ConstantType.UTF8);\n            List<String> constants = new ArrayList<>();\n            pool.list(Utf8Constant.class).forEach(utf8 -> constants.add(utf8.get()));\n            String mcVersion = getOrDefault(constants, constants.indexOf(\"MC_VERSION\") + 1, null);\n            String ofEdition = getOrDefault(constants, constants.indexOf(\"OF_EDITION\") + 1, null);\n            String ofRelease = getOrDefault(constants, constants.indexOf(\"OF_RELEASE\") + 1, null);\n\n            if (mcVersion == null || ofEdition == null || ofRelease == null)\n                throw new IOException(\"Unrecognized OptiFine installer\");\n\n            if (!mcVersion.equals(gameVersion.get()))\n                throw new VersionMismatchException(mcVersion, gameVersion.get());\n\n            return new OptiFineInstallTask(dependencyManager, version,\n                    new OptiFineRemoteVersion(mcVersion, ofEdition + \"_\" + ofRelease, Collections.singletonList(\"\"), false), installer);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.optifine;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.util.List;\n\npublic class OptiFineRemoteVersion extends RemoteVersion {\n\n    public OptiFineRemoteVersion(String gameVersion, String selfVersion, List<String> urls, boolean snapshot) {\n        super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, null, snapshot ? Type.SNAPSHOT : Type.RELEASE, urls);\n    }\n\n    @Override\n    public String getFullVersion() {\n        return getGameVersion() + \"_\" + getSelfVersion();\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new OptiFineInstallTask(dependencyManager, baseVersion, this);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.quilt;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * <b>Note</b>: Quilt should be installed first.\n *\n * @author huangyuhui\n */\npublic final class QuiltAPIInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final QuiltAPIRemoteVersion remote;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    public QuiltAPIInstallTask(DefaultDependencyManager dependencyManager, Version version, QuiltAPIRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remote = remoteVersion;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws IOException {\n        dependencies.add(new FileDownloadTask(\n                remote.getVersion().getFile().getUrl(),\n                dependencyManager.getGameRepository().getModsDirectory(version.getId()).resolve(\"quilt-api-\" + remote.getVersion().getVersion() + \".jar\"),\n                remote.getVersion().getFile().getIntegrityCheck())\n        );\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.quilt;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.time.Instant;\nimport java.util.List;\n\npublic class QuiltAPIRemoteVersion extends RemoteVersion {\n    private final String fullVersion;\n    private final RemoteMod.Version version;\n\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param urls        the installer or universal jar original URL.\n     */\n    QuiltAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List<String> urls) {\n        super(LibraryAnalyzer.LibraryType.QUILT_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);\n\n        this.fullVersion = fullVersion;\n        this.version = version;\n    }\n\n    @Override\n    public String getFullVersion() {\n        return fullVersion;\n    }\n\n    public RemoteMod.Version getVersion() {\n        return version;\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new QuiltAPIInstallTask(dependencyManager, baseVersion, this);\n    }\n\n    @Override\n    public int compareTo(RemoteVersion o) {\n        if (!(o instanceof QuiltAPIRemoteVersion)) return 0;\n        return -this.getReleaseDate().compareTo(o.getReleaseDate());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.quilt;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Lang;\n\nimport java.util.Collections;\n\npublic class QuiltAPIVersionList extends VersionList<QuiltAPIRemoteVersion> {\n\n    private final DownloadProvider downloadProvider;\n\n    public QuiltAPIVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return Task.runAsync(() -> {\n            for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById(downloadProvider, \"qsl\"))) {\n                for (String gameVersion : modVersion.getGameVersions()) {\n                    versions.put(gameVersion, new QuiltAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,\n                            Collections.singletonList(modVersion.getFile().getUrl())));\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.quilt;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.UnsupportedInstallationException;\nimport org.jackhuang.hmcl.game.Arguments;\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.download.UnsupportedInstallationException.FABRIC_NOT_COMPATIBLE_WITH_FORGE;\n\n/**\n * <b>Note</b>: Quilt should be installed first.\n *\n * @author huangyuhui\n */\npublic final class QuiltInstallTask extends Task<Version> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Version version;\n    private final QuiltRemoteVersion remote;\n    private final GetTask launchMetaTask;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    public QuiltInstallTask(DefaultDependencyManager dependencyManager, Version version, QuiltRemoteVersion remoteVersion) {\n        this.dependencyManager = dependencyManager;\n        this.version = version;\n        this.remote = remoteVersion;\n\n        launchMetaTask = new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()));\n        launchMetaTask.setCacheRepository(dependencyManager.getCacheRepository());\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        if (!Objects.equals(\"net.minecraft.client.main.Main\", version.resolve(dependencyManager.getGameRepository()).getMainClass()))\n            throw new UnsupportedInstallationException(FABRIC_NOT_COMPATIBLE_WITH_FORGE);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return Collections.singleton(launchMetaTask);\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() {\n        setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), QuiltInfo.class), remote.getGameVersion(), remote.getSelfVersion()));\n\n        dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));\n    }\n\n    private Version getPatch(QuiltInfo quiltInfo, String gameVersion, String loaderVersion) {\n        JsonObject launcherMeta = quiltInfo.launcherMeta;\n        Arguments arguments = new Arguments();\n\n        String mainClass;\n        if (!launcherMeta.get(\"mainClass\").isJsonObject()) {\n            mainClass = launcherMeta.get(\"mainClass\").getAsString();\n        } else {\n            mainClass = launcherMeta.get(\"mainClass\").getAsJsonObject().get(\"client\").getAsString();\n        }\n\n        if (launcherMeta.has(\"launchwrapper\")) {\n            String clientTweaker = launcherMeta.get(\"launchwrapper\").getAsJsonObject().get(\"tweakers\").getAsJsonObject().get(\"client\").getAsJsonArray().get(0).getAsString();\n            arguments = arguments.addGameArguments(\"--tweakClass\", clientTweaker);\n        }\n\n        JsonObject librariesObject = launcherMeta.getAsJsonObject(\"libraries\");\n        List<Library> libraries = new ArrayList<>();\n\n        // \"common, server\" is hard coded in fabric installer.\n        // Don't know the purpose of ignoring client libraries.\n        for (String side : new String[]{\"common\", \"server\"}) {\n            for (JsonElement element : librariesObject.getAsJsonArray(side)) {\n                libraries.add(JsonUtils.GSON.fromJson(element, Library.class));\n            }\n        }\n\n        // libraries.add(new Library(Artifact.fromDescriptor(quiltInfo.hashed.maven), getMavenRepositoryByGroup(quiltInfo.hashed.maven), null));\n        libraries.add(new Library(Artifact.fromDescriptor(quiltInfo.intermediary.maven), getMavenRepositoryByGroup(quiltInfo.intermediary.maven), null));\n        libraries.add(new Library(Artifact.fromDescriptor(quiltInfo.loader.maven), getMavenRepositoryByGroup(quiltInfo.loader.maven), null));\n\n        return new Version(LibraryAnalyzer.LibraryType.QUILT.getPatchId(), loaderVersion, Version.PRIORITY_LOADER, arguments, mainClass, libraries);\n    }\n\n    private static String getMavenRepositoryByGroup(String maven) {\n        Artifact artifact = Artifact.fromDescriptor(maven);\n        switch (artifact.getGroup()) {\n            case \"net.fabricmc\":\n                return \"https://maven.fabricmc.net/\";\n            case \"org.quiltmc\":\n                return \"https://maven.quiltmc.org/repository/release/\";\n            default:\n                return \"https://maven.fabricmc.net/\";\n        }\n    }\n\n    @JsonSerializable\n    public static class QuiltInfo {\n        private final LoaderInfo loader;\n        private final IntermediaryInfo hashed;\n        private final IntermediaryInfo intermediary;\n        private final JsonObject launcherMeta;\n\n        public QuiltInfo(LoaderInfo loader, IntermediaryInfo hashed, IntermediaryInfo intermediary, JsonObject launcherMeta) {\n            this.loader = loader;\n            this.hashed = hashed;\n            this.intermediary = intermediary;\n            this.launcherMeta = launcherMeta;\n        }\n\n        public LoaderInfo getLoader() {\n            return loader;\n        }\n\n        public IntermediaryInfo getHashed() {\n            return hashed;\n        }\n\n        public IntermediaryInfo getIntermediary() {\n            return intermediary;\n        }\n\n        public JsonObject getLauncherMeta() {\n            return launcherMeta;\n        }\n    }\n\n    @JsonSerializable\n    public static class LoaderInfo {\n        private final String separator;\n        private final int build;\n        private final String maven;\n        private final String version;\n        private final boolean stable;\n\n        public LoaderInfo(String separator, int build, String maven, String version, boolean stable) {\n            this.separator = separator;\n            this.build = build;\n            this.maven = maven;\n            this.version = version;\n            this.stable = stable;\n        }\n\n        public String getSeparator() {\n            return separator;\n        }\n\n        public int getBuild() {\n            return build;\n        }\n\n        public String getMaven() {\n            return maven;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        public boolean isStable() {\n            return stable;\n        }\n    }\n\n    @JsonSerializable\n    public static class IntermediaryInfo {\n        private final String maven;\n        private final String version;\n\n        public IntermediaryInfo(String maven, String version) {\n            this.maven = maven;\n            this.version = version;\n        }\n\n        public String getMaven() {\n            return maven;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltRemoteVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.quilt;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.RemoteVersion;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.util.List;\n\npublic class QuiltRemoteVersion extends RemoteVersion {\n    /**\n     * Constructor.\n     *\n     * @param gameVersion the Minecraft version that this remote version suits.\n     * @param selfVersion the version string of the remote version.\n     * @param urls        the installer or universal jar original URL.\n     */\n    QuiltRemoteVersion(String gameVersion, String selfVersion, List<String> urls) {\n        super(LibraryAnalyzer.LibraryType.QUILT.getPatchId(), gameVersion, selfVersion, null, urls);\n    }\n\n    @Override\n    public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {\n        return new QuiltInstallTask(dependencyManager, baseVersion, this);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.download.quilt;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.download.VersionList;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\n\npublic final class QuiltVersionList extends VersionList<QuiltRemoteVersion> {\n    private final DownloadProvider downloadProvider;\n\n    public QuiltVersionList(DownloadProvider downloadProvider) {\n        this.downloadProvider = downloadProvider;\n    }\n\n    @Override\n    public boolean hasType() {\n        return false;\n    }\n\n    @Override\n    public Task<?> refreshAsync() {\n        return Task.runAsync(() -> {\n            List<String> gameVersions = getGameVersions(GAME_META_URL);\n            List<String> loaderVersions = getGameVersions(LOADER_META_URL);\n\n            lock.writeLock().lock();\n\n            try {\n                for (String gameVersion : gameVersions)\n                    for (String loaderVersion : loaderVersions)\n                        versions.put(gameVersion, new QuiltRemoteVersion(gameVersion, loaderVersion,\n                                Collections.singletonList(getLaunchMetaUrl(gameVersion, loaderVersion))));\n            } finally {\n                lock.writeLock().unlock();\n            }\n        });\n    }\n\n    private static final String LOADER_META_URL = \"https://meta.quiltmc.org/v3/versions/loader\";\n    private static final String GAME_META_URL = \"https://meta.quiltmc.org/v3/versions/game\";\n\n    private List<String> getGameVersions(String metaUrl) throws IOException {\n        String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl));\n        return JsonUtils.GSON.fromJson(json, listTypeOf(GameVersion.class))\n                .stream().map(GameVersion::getVersion).collect(Collectors.toList());\n    }\n\n    private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) {\n        return String.format(\"https://meta.quiltmc.org/v3/versions/loader/%s/%s\", gameVersion, loaderVersion);\n    }\n\n    private static class GameVersion {\n        private final String version;\n        private final String maven;\n        private final boolean stable;\n\n        public GameVersion() {\n            this(\"\", null, false);\n        }\n\n        public GameVersion(String version, String maven, boolean stable) {\n            this.version = version;\n            this.maven = maven;\n            this.stable = stable;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        @Nullable\n        public String getMaven() {\n            return maven;\n        }\n\n        public boolean isStable() {\n            return stable;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/Event.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jackhuang.hmcl.util.ToStringBuilder;\n\nimport java.util.Objects;\n\n/**\n *\n * @author huangyuhui\n */\npublic class Event {\n\n    /**\n     * The object on which the Event initially occurred.\n     */\n    protected final transient Object source;\n\n    /**\n     * Constructs a prototypical Event.\n     *\n     * @param source The object on which the Event initially occurred.\n     * @throws NullPointerException if source is null.\n     */\n    public Event(Object source) {\n        Objects.requireNonNull(source);\n\n        this.source = source;\n    }\n\n    /**\n     * The object on which the Event initially occurred.\n     *\n     * @return The object on which the Event initially occurred.\n     */\n    public Object getSource() {\n        return source;\n    }\n\n    /**\n     * Returns a String representation of this Event.\n     *\n     * @return A a String representation of this Event.\n     */\n    public String toString() {\n        return new ToStringBuilder(this).append(\"source\", source).toString();\n    }\n\n    private boolean canceled;\n\n    /**\n     * true if this event is canceled.\n     *\n     * @throws UnsupportedOperationException if trying to cancel a non-cancelable event.\n     */\n    public final boolean isCanceled() {\n        return canceled;\n    }\n\n    /**\n     * @param canceled new value\n     * @throws UnsupportedOperationException if trying to cancel a non-cancelable event.\n     */\n    public final void setCanceled(boolean canceled) {\n        if (!isCancelable())\n            throw new UnsupportedOperationException(\"Attempted to cancel a non-cancelable event: \" + getClass());\n        this.canceled = canceled;\n    }\n\n    /**\n     * true if this Event is cancelable.\n     */\n    public boolean isCancelable() {\n        return false;\n    }\n\n    public boolean hasResult() {\n        return false;\n    }\n\n    private Result result = Result.DEFAULT;\n\n    /**\n     * Returns the value set as the result of this event\n     */\n    public Result getResult() {\n        return result;\n    }\n\n    public void setResult(Result result) {\n        if (!hasResult())\n            throw new UnsupportedOperationException(\"Attempted to set result on a no result event: \" + this.getClass() + \" of type.\");\n        this.result = result;\n    }\n\n    public enum Result {\n        DENY,\n        DEFAULT,\n        ALLOW\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventBus.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author huangyuhui\npublic final class EventBus extends ClassValue<EventManager<?>> {\n\n    public static final EventBus EVENT_BUS = new EventBus();\n\n    private EventBus() {\n    }\n\n    @Override\n    protected EventManager<?> computeValue(@NotNull Class<?> type) {\n        return new EventManager<>();\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T extends Event> EventManager<T> channel(Class<T> clazz) {\n        return (EventManager<T>) get(clazz);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public Event.Result fireEvent(Event obj) {\n        LOG.info(obj + \" gets fired\");\n\n        return ((EventManager<Event>) get(obj.getClass())).fireEvent(obj);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventManager.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jetbrains.annotations.Contract;\n\nimport java.lang.ref.WeakReference;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.function.Consumer;\n\n/// @author huangyuhui\npublic final class EventManager<T extends Event> {\n\n    private static final int PRIORITY_COUNT = EventPriority.values().length;\n\n    private final ReentrantLock lock = new ReentrantLock();\n    @SuppressWarnings(\"unchecked\")\n    private final CopyOnWriteArrayList<Consumer<T>>[] allHandlers = (CopyOnWriteArrayList<Consumer<T>>[]) new CopyOnWriteArrayList<?>[PRIORITY_COUNT];\n\n    @Contract(\"_ -> param1\")\n    public Consumer<T> registerWeak(Consumer<T> consumer) {\n        register(new WeakListener<>(new WeakReference<>(consumer)));\n        return consumer;\n    }\n\n    @Contract(\"_, _ -> param1\")\n    public Consumer<T> registerWeak(Consumer<T> consumer, EventPriority priority) {\n        register(new WeakListener<>(new WeakReference<>(consumer)), priority);\n        return consumer;\n    }\n\n    public void register(Consumer<T> consumer) {\n        register(consumer, EventPriority.NORMAL);\n    }\n\n    public void register(Consumer<T> consumer, EventPriority priority) {\n        lock.lock();\n        try {\n            var handlers = allHandlers[priority.ordinal()];\n            if (handlers == null) {\n                handlers = new CopyOnWriteArrayList<>();\n                allHandlers[priority.ordinal()] = handlers;\n            }\n            handlers.add(consumer);\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    public void register(Runnable runnable) {\n        register(t -> runnable.run());\n    }\n\n    public void register(Runnable runnable, EventPriority priority) {\n        register(t -> runnable.run(), priority);\n    }\n\n    public Event.Result fireEvent(T event) {\n        lock.lock();\n        try {\n            for (var handlers : allHandlers) {\n                if (handlers != null) {\n                    for (Consumer<T> handler : handlers) {\n                        if (handler instanceof WeakListener<T> weakListener) {\n                            Consumer<T> consumer = weakListener.ref.get();\n                            if (consumer != null) {\n                                consumer.accept(event);\n                            } else {\n                                handlers.remove(weakListener);\n                            }\n                        } else {\n                            handler.accept(event);\n                        }\n                    }\n                }\n            }\n        } finally {\n            lock.unlock();\n        }\n\n        return event.hasResult() ? event.getResult() : Event.Result.DEFAULT;\n    }\n\n    private record WeakListener<T>(WeakReference<Consumer<T>> ref) implements Consumer<T> {\n        @Override\n        public void accept(T t) {\n            Consumer<T> listener = ref.get();\n            if (listener != null)\n                listener.accept(t);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventPriority.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\n/**\n *\n * @author huangyuhui\n */\npublic enum EventPriority {\n    HIGHEST,\n    HIGH,\n    NORMAL,\n    LOW,\n    LOWEST\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/GameJsonParseFailedEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jackhuang.hmcl.util.ToStringBuilder;\n\nimport java.nio.file.Path;\n\n/**\n * This event gets fired when json of a game version is malformed. You can do something here.\n * auto making up for the missing json, don't forget to set result to {@link Event.Result#ALLOW}.\n * and even asking for removing the redundant version folder.\n *\n * The result ALLOW means you have corrected the json.\n */\npublic final class GameJsonParseFailedEvent extends Event {\n    private final String version;\n    private final Path jsonFile;\n\n    /**\n     *\n     * @param source {@link org.jackhuang.hmcl.game.DefaultGameRepository}\n     * @param jsonFile the minecraft.json file.\n     * @param version the version name\n     */\n    public GameJsonParseFailedEvent(Object source, Path jsonFile, String version) {\n        super(source);\n        this.version = version;\n        this.jsonFile = jsonFile;\n    }\n\n    public Path getJsonFile() {\n        return jsonFile;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"source\", source)\n                .append(\"jsonFile\", jsonFile)\n                .append(\"version\", version)\n                .toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/JVMLaunchFailedEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\n\n/**\n * This event gets fired when we launch the JVM and it got crashed.\n * <br>\n * This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}\n *\n * @author huangyuhui\n */\npublic class JVMLaunchFailedEvent extends Event {\n\n    private final ManagedProcess process;\n\n    /**\n     * Constructor.\n     *\n     * @param source {@link org.jackhuang.hmcl.launch.ExitWaiter}\n     * @param process the crashed process.\n     */\n    public JVMLaunchFailedEvent(Object source, ManagedProcess process) {\n        super(source);\n        this.process = process;\n    }\n\n    public ManagedProcess getProcess() {\n        return process;\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"source\", source)\n                .append(\"process\", process)\n                .toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/LoadedOneVersionEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.util.ToStringBuilder;\n\n/**\n * This event gets fired when a minecraft version has been loaded.\n * <br>\n * This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}\n *\n * @author huangyuhui\n */\npublic final class LoadedOneVersionEvent extends Event {\n\n    private final Version version;\n\n    /**\n     *\n     * @param source {@link org.jackhuang.hmcl.game.GameRepository}\n     * @param version the version id.\n     */\n    public LoadedOneVersionEvent(Object source, Version version) {\n        super(source);\n        this.version = version;\n    }\n\n    public Version getVersion() {\n        return version;\n    }\n\n    @Override\n    public boolean hasResult() {\n        return true;\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"source\", source)\n                .append(\"version\", version)\n                .toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessExitedAbnormallyEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\n\n/**\n * This event gets fired when a JavaProcess exited abnormally and the exit code is not zero.\n *\n * This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}\n *\n * @author huangyuhui\n */\npublic final class ProcessExitedAbnormallyEvent extends Event {\n\n    private final ManagedProcess process;\n\n    /**\n     * Constructor.\n     *\n     * @param source  {@link org.jackhuang.hmcl.launch.ExitWaiter}\n     * @param process The process that exited abnormally.\n     */\n    public ProcessExitedAbnormallyEvent(Object source, ManagedProcess process) {\n        super(source);\n        this.process = process;\n    }\n\n    public ManagedProcess getProcess() {\n        return process;\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"source\", source)\n                .append(\"process\", process)\n                .toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessStoppedEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\n\n/**\n * This event gets fired when minecraft process exited successfully and the exit code is 0.\n * <br>\n * This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}\n *\n * @author huangyuhui\n */\npublic class ProcessStoppedEvent extends Event {\n\n    private final ManagedProcess process;\n\n    /**\n     * Constructor.\n     *\n     * @param source {@link org.jackhuang.hmcl.launch.ExitWaiter}\n     * @param process minecraft process\n     */\n    public ProcessStoppedEvent(Object source, ManagedProcess process) {\n        super(source);\n        this.process = process;\n    }\n\n    public ManagedProcess getProcess() {\n        return process;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshedVersionsEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\n/**\n * This event gets fired when all the versions in .minecraft folder are loaded.\n * <br>\n * This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}\n *\n * @author huangyuhui\n */\npublic final class RefreshedVersionsEvent extends Event {\n\n    /**\n     * Constructor.\n     *\n     * @param source {@link org.jackhuang.hmcl.game.GameRepository}\n     */\n    public RefreshedVersionsEvent(Object source) {\n        super(source);\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshingVersionsEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\n/**\n * This event gets fired when loading versions in a .minecraft folder.\n * <br>\n * This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}\n *\n * @author huangyuhui\n */\npublic final class RefreshingVersionsEvent extends Event {\n\n    /**\n     * Constructor.\n     *\n     * @param source {@link org.jackhuang.hmcl.game.GameRepository}\n     */\n    public RefreshingVersionsEvent(Object source) {\n        super(source);\n    }\n\n    @Override\n    public boolean hasResult() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/RemoveVersionEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jackhuang.hmcl.util.ToStringBuilder;\n\n/**\n * This event gets fired when a minecraft version is being removed.\n * <br>\n * This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}\n *\n * @author huangyuhui\n */\npublic class RemoveVersionEvent extends Event {\n\n    private final String version;\n\n    /**\n     *\n     * @param source {@link org.jackhuang.hmcl.game.GameRepository}\n     * @param version the version id.\n     */\n    public RemoveVersionEvent(Object source, String version) {\n        super(source);\n        this.version = version;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    @Override\n    public boolean hasResult() {\n        return true;\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"source\", source)\n                .append(\"version\", version)\n                .toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/event/RenameVersionEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.event;\n\nimport org.jackhuang.hmcl.util.ToStringBuilder;\n\n/**\n * This event gets fired when a minecraft version is being removed.\n * <br>\n * This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}\n *\n * @author huangyuhui\n */\npublic class RenameVersionEvent extends Event {\n\n    private final String from, to;\n\n    /**\n     *\n     * @param source {@link org.jackhuang.hmcl.game.GameRepository}\n     * @param from the version id.\n     */\n    public RenameVersionEvent(Object source, String from, String to) {\n        super(source);\n        this.from = from;\n        this.to = to;\n    }\n\n    public String getFromVersion() {\n        return from;\n    }\n\n    public String getToVersion() {\n        return to;\n    }\n\n    @Override\n    public boolean hasResult() {\n        return true;\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"source\", source)\n                .append(\"from\", from)\n                .append(\"to\", to)\n                .toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/Argument.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\n\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.lang.reflect.Type;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\n@JsonAdapter(Argument.Deserializer.class)\n@Immutable\npublic interface Argument extends Cloneable {\n\n    /**\n     * Parse this argument in form: ${key name} or simply a string.\n     *\n     * @param keys the parse map\n     * @param features the map that contains some features such as 'is_demo_user', 'has_custom_resolution'\n     * @return parsed argument element, empty if this argument is ignored and will not be added.\n     */\n    List<String> toString(Map<String, String> keys, Map<String, Boolean> features);\n\n    class Deserializer implements JsonDeserializer<Argument> {\n        @Override\n        public Argument deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (json.isJsonPrimitive())\n                return new StringArgument(json.getAsString());\n            else\n                return context.deserialize(json, RuledArgument.class);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class Arguments {\n\n    @SerializedName(\"game\")\n    private final List<Argument> game;\n    @SerializedName(\"jvm\")\n    private final List<Argument> jvm;\n\n    public Arguments() {\n        this(null, null);\n    }\n\n    public Arguments(List<Argument> game, List<Argument> jvm) {\n        this.game = game;\n        this.jvm = jvm;\n    }\n\n    @Nullable\n    public List<Argument> getGame() {\n        return game == null ? null : Collections.unmodifiableList(game);\n    }\n\n    public Arguments withGame(List<Argument> game) {\n        return new Arguments(game, jvm);\n    }\n\n    @Nullable\n    public List<Argument> getJvm() {\n        return jvm == null ? null : Collections.unmodifiableList(jvm);\n    }\n\n    public Arguments withJvm(List<Argument> jvm) {\n        return new Arguments(game, jvm);\n    }\n\n    public Arguments addGameArguments(String... gameArguments) {\n        return addGameArguments(Arrays.asList(gameArguments));\n    }\n\n    public Arguments addGameArguments(List<String> gameArguments) {\n        List<Argument> list = gameArguments.stream().map(StringArgument::new).collect(Collectors.toList());\n        return new Arguments(Lang.merge(getGame(), list), getJvm());\n    }\n\n    public Arguments addJVMArguments(String... jvmArguments) {\n        return addJVMArguments(Arrays.asList(jvmArguments));\n    }\n\n    public Arguments addJVMArguments(List<String> jvmArguments) {\n        return addJVMArgumentsDirect(jvmArguments.stream().map(StringArgument::new).collect(Collectors.toList()));\n    }\n\n    // TODO: How to distinguish addJVMArgumentsDirect from addJVMArguments? Naming is hard :)\n    public Arguments addJVMArgumentsDirect(List<Argument> jvmArguments) {\n        return new Arguments(getGame(), Lang.merge(getJvm(), jvmArguments));\n    }\n\n    public static Arguments merge(Arguments a, Arguments b) {\n        if (a == null)\n            return b;\n        else if (b == null)\n            return a;\n        else\n            return new Arguments(\n                    a.game == null && b.game == null ? null : Lang.merge(a.game, b.game),\n                    a.jvm == null && b.jvm == null ? null : Lang.merge(a.jvm, b.jvm));\n    }\n\n    public static List<String> parseStringArguments(List<String> arguments, Map<String, String> keys) {\n        return arguments.stream().filter(Objects::nonNull).flatMap(str -> new StringArgument(str).toString(keys, Collections.emptyMap()).stream()).collect(Collectors.toList());\n    }\n\n    public static List<String> parseArguments(List<Argument> arguments, Map<String, String> keys) {\n        return parseArguments(arguments, keys, Collections.emptyMap());\n    }\n\n    public static List<String> parseArguments(List<Argument> arguments, Map<String, String> keys, Map<String, Boolean> features) {\n        return arguments.stream().filter(Objects::nonNull).flatMap(arg -> arg.toString(keys, features).stream()).collect(Collectors.toList());\n    }\n\n    public static final List<Argument> DEFAULT_JVM_ARGUMENTS;\n    public static final List<Argument> DEFAULT_GAME_ARGUMENTS;\n\n    static {\n        List<Argument> jvm = new ArrayList<>(8);\n        jvm.add(new RuledArgument(Collections.singletonList(new CompatibilityRule(CompatibilityRule.Action.ALLOW, new OSRestriction(OperatingSystem.WINDOWS))), Collections.singletonList(\"-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\")));\n        jvm.add(new RuledArgument(Collections.singletonList(new CompatibilityRule(CompatibilityRule.Action.ALLOW, new OSRestriction(OperatingSystem.WINDOWS, \"^10\\\\.\"))), Arrays.asList(\"-Dos.name=Windows 10\", \"-Dos.version=10.0\")));\n        jvm.add(new StringArgument(\"-Djava.library.path=${natives_directory}\"));\n        jvm.add(new StringArgument(\"-Dminecraft.launcher.brand=${launcher_name}\"));\n        jvm.add(new StringArgument(\"-Dminecraft.launcher.version=${launcher_version}\"));\n        jvm.add(new StringArgument(\"-cp\"));\n        jvm.add(new StringArgument(\"${classpath}\"));\n        DEFAULT_JVM_ARGUMENTS = Collections.unmodifiableList(jvm);\n\n        List<Argument> game = new ArrayList<>(1);\n        game.add(new RuledArgument(Collections.singletonList(new CompatibilityRule(CompatibilityRule.Action.ALLOW, null, Collections.singletonMap(\"has_custom_resolution\", true))), Arrays.asList(\"--width\", \"${resolution_width}\", \"--height\", \"${resolution_height}\")));\n        DEFAULT_GAME_ARGUMENTS = Collections.unmodifiableList(game);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/Artifact.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonDeserializationContext;\nimport com.google.gson.JsonDeserializer;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonNull;\nimport com.google.gson.JsonParseException;\nimport com.google.gson.JsonPrimitive;\nimport com.google.gson.JsonSerializationContext;\nimport com.google.gson.JsonSerializer;\nimport com.google.gson.annotations.JsonAdapter;\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.lang.reflect.Type;\nimport java.nio.file.Path;\n\n@Immutable\n@JsonAdapter(Artifact.Serializer.class)\npublic final class Artifact {\n\n    private final String group;\n    private final String name;\n    private final String version;\n    private final String classifier;\n    private final String extension;\n\n    private final String descriptor;\n    private final String fileName;\n    private final String path;\n\n    public Artifact(String group, String name, String version) {\n        this(group, name, version, null);\n    }\n\n    public Artifact(String group, String name, String version, String classifier) {\n        this(group, name, version, classifier, null);\n    }\n\n    public Artifact(String group, String name, String version, String classifier, String extension) {\n        this.group = group;\n        this.name = name;\n        this.version = version;\n        this.classifier = classifier;\n        this.extension = extension == null ? \"jar\" : extension;\n\n        String fileName = this.name + \"-\" + this.version;\n        if (classifier != null) fileName += \"-\" + this.classifier;\n        this.fileName = fileName + \".\" + this.extension;\n        this.path = String.format(\"%s/%s/%s/%s\", this.group.replace('.', '/'), this.name, this.version, this.fileName);\n\n        // group:name:version:classifier@extension\n        String descriptor = String.format(\"%s:%s:%s\", group, name, version);\n        if (classifier != null) descriptor += \":\" + classifier;\n        if (!\"jar\".equals(this.extension)) descriptor += \"@\" + this.extension;\n        this.descriptor = descriptor;\n    }\n\n    public static Artifact fromDescriptor(String descriptor) {\n        String[] arr = descriptor.split(\":\", 4);\n        if (arr.length != 3 && arr.length != 4)\n            throw new IllegalArgumentException(\"Artifact name is malformed\");\n\n        String ext = null;\n        int last = arr.length - 1;\n        String[] splitted = arr[last].split(\"@\");\n        if (splitted.length == 2) {\n            arr[last] = splitted[0];\n            ext = splitted[1];\n        } else if (splitted.length > 2) {\n            throw new IllegalArgumentException(\"Artifact name is malformed\");\n        }\n\n        return new Artifact(arr[0].replace('\\\\', '/'), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, ext);\n    }\n\n    public String getGroup() {\n        return group;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public String getClassifier() {\n        return classifier;\n    }\n\n    public Artifact setClassifier(String classifier) {\n        return new Artifact(group, name, version, classifier, extension);\n    }\n\n    public String getExtension() {\n        return extension;\n    }\n\n    public String getFileName() {\n        return fileName;\n    }\n\n    public String getPath() { return path; }\n\n    public Path getPath(Path root) {\n        return root.resolve(path);\n    }\n\n    @Override\n    public String toString() {\n        return descriptor;\n    }\n\n    public static class Serializer implements JsonDeserializer<Artifact>, JsonSerializer<Artifact> {\n        @Override\n        public JsonElement serialize(Artifact src, Type typeOfSrc, JsonSerializationContext context) {\n            return src == null ? JsonNull.INSTANCE : new JsonPrimitive(src.toString());\n        }\n\n        @Override\n        public Artifact deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            return json.isJsonPrimitive() ? fromDescriptor(json.getAsJsonPrimitive().getAsString()) : null;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndex.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.ToStringBuilder;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class AssetIndex {\n\n    @SerializedName(\"virtual\")\n    private final boolean virtual;\n\n    @SerializedName(\"map_to_resources\")\n    private final boolean mapToResources;\n\n    @SerializedName(\"objects\")\n    private final Map<String, AssetObject> objects;\n\n    public AssetIndex() {\n        this(false, Collections.emptyMap());\n    }\n\n    public AssetIndex(boolean virtual, Map<String, AssetObject> objects) {\n        this.virtual = this.mapToResources = virtual;\n        this.objects = new HashMap<>(objects);\n    }\n\n    public boolean isVirtual() {\n        return virtual || mapToResources;\n    }\n\n    public boolean needMapToResources() {\n        return mapToResources;\n    }\n\n    public Map<String, AssetObject> getObjects() {\n        return Collections.unmodifiableMap(objects);\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this).append(\"virtual\", virtual).append(\"objects\", objects).toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndexInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.Immutable;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic class AssetIndexInfo extends IdDownloadInfo {\n\n    private final long totalSize;\n\n    public AssetIndexInfo() {\n        this(\"\", \"\");\n    }\n\n    public AssetIndexInfo(String id, String url) {\n        this(id, url, null);\n    }\n\n    public AssetIndexInfo(String id, String url, String sha1) {\n        this(id, url, sha1, 0);\n    }\n\n    public AssetIndexInfo(String id, String url, String sha1, int size) {\n        this(id, url, sha1, size, 0);\n    }\n\n    public AssetIndexInfo(String id, String url, String sha1, int size, long totalSize) {\n        super(id, url, sha1, size);\n        this.totalSize = totalSize;\n    }\n\n    public long getTotalSize() {\n        return totalSize;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetObject.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class AssetObject implements Validation {\n\n    private final String hash;\n    private final long size;\n\n    public AssetObject() {\n        this(\"\", 0);\n    }\n\n    public AssetObject(String hash, long size) {\n        this.hash = hash;\n        this.size = size;\n    }\n\n    public String getHash() {\n        return hash;\n    }\n\n    public long getSize() {\n        return size;\n    }\n\n    public String getLocation() {\n        return hash.substring(0, 2) + \"/\" + hash;\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (StringUtils.isBlank(hash) || hash.length() < 2)\n            throw new JsonParseException(\"AssetObject hash cannot be blank.\");\n    }\n\n    public boolean validateChecksum(Path file, boolean defaultValue) throws IOException {\n        if (hash == null) return defaultValue;\n        return DigestUtils.digestToString(\"SHA-1\", file).equalsIgnoreCase(hash);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Arrays;\n\n/// The Minecraft version for 1.5.x and earlier.\n///\n/// @author huangyuhui\npublic final class ClassicVersion extends Version {\n\n    public ClassicVersion() {\n        super(true, \"Classic\", null, null, \"${auth_player_name} ${auth_session} --workDir ${game_directory}\",\n                null, \"net.minecraft.client.Minecraft\", null, null, null, null, null, null,\n                Arrays.asList(new ClassicLibrary(\"lwjgl\"), new ClassicLibrary(\"jinput\"), new ClassicLibrary(\"lwjgl_util\")),\n                null, null, null, ReleaseType.UNKNOWN, Instant.now(), Instant.now(), 0, false, false, null);\n    }\n\n    private static final class ClassicLibrary extends Library {\n        public ClassicLibrary(String name) {\n            super(new Artifact(\"\", \"\", \"\"), null,\n                    new LibrariesDownloadInfo(new LibraryDownloadInfo(\"bin/\" + name + \".jar\"), null),\n                    null, null, null, null, null, null);\n        }\n    }\n\n    public static boolean hasClassicVersion(Path baseDirectory) {\n        Path bin = baseDirectory.resolve(\"bin\");\n        return Files.isDirectory(bin)\n                && Files.exists(bin.resolve(\"lwjgl.jar\"))\n                && Files.exists(bin.resolve(\"jinput.jar\"))\n                && Files.exists(bin.resolve(\"lwjgl_util.jar\"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/CompatibilityRule.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.util.*;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class CompatibilityRule {\n\n    private final Action action;\n    private final OSRestriction os;\n    private final Map<String, Boolean> features;\n\n    public CompatibilityRule() {\n        this(Action.ALLOW, null);\n    }\n\n    public CompatibilityRule(Action action, OSRestriction os) {\n        this(action, os, null);\n    }\n\n    public CompatibilityRule(Action action, OSRestriction os, Map<String, Boolean> features) {\n        this.action = action;\n        this.os = os;\n        this.features = features;\n    }\n\n    public Optional<Action> getAppliedAction(Map<String, Boolean> supportedFeatures) {\n        if (os != null && !os.allow())\n            return Optional.empty();\n\n        if (features != null)\n            for (Map.Entry<String, Boolean> entry : features.entrySet())\n                if (!Objects.equals(supportedFeatures.get(entry.getKey()), entry.getValue()))\n                    return Optional.empty();\n\n        return Optional.ofNullable(action);\n    }\n\n    public static boolean appliesToCurrentEnvironment(Collection<CompatibilityRule> rules) {\n        return appliesToCurrentEnvironment(rules, Collections.emptyMap());\n    }\n\n    public static boolean appliesToCurrentEnvironment(Collection<CompatibilityRule> rules, Map<String, Boolean> features) {\n        if (rules == null || rules.isEmpty())\n            return true;\n\n        Action action = Action.DISALLOW;\n        for (CompatibilityRule rule : rules) {\n            Optional<Action> thisAction = rule.getAppliedAction(features);\n            if (thisAction.isPresent())\n                action = thisAction.get();\n        }\n\n        return action == Action.ALLOW;\n    }\n\n    public static boolean equals(Collection<CompatibilityRule> rules1, Collection<CompatibilityRule> rules2) {\n        return Objects.hashCode(rules1) == Objects.hashCode(rules2);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        CompatibilityRule that = (CompatibilityRule) o;\n        return action == that.action &&\n                Objects.equals(os, that.os) &&\n                Objects.equals(features, that.features);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(action, os, features);\n    }\n\n    public enum Action {\n        ALLOW,\n        DISALLOW\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.intellij.lang.annotations.Language;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic final class CrashReportAnalyzer {\n\n    private CrashReportAnalyzer() {\n    }\n\n    public enum Rule {\n        OPENJ9(\"(Open J9 is not supported|OpenJ9 is incompatible|\\\\.J9VMInternals\\\\.)\"),\n        NEED_JDK11(\"(no such method: sun\\\\.misc\\\\.Unsafe\\\\.defineAnonymousClass\\\\(Class,byte\\\\[\\\\],Object\\\\[\\\\]\\\\)Class/invokeVirtual|java\\\\.lang\\\\.UnsupportedClassVersionError: icyllis/modernui/forge/MixinConnector 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 52\\\\.0|java\\\\.lang\\\\.IllegalArgumentException: The requested compatibility level JAVA_11 could not be set\\\\. Level is not supported by the active JRE or ASM version)\"),\n        TOO_OLD_JAVA(\"java\\\\.lang\\\\.UnsupportedClassVersionError: (.*?) version (?<expected>\\\\d+)\\\\.0\", \"expected\"),\n        JVM_32BIT(\"(Could not reserve enough space for (.*?)KB object heap|The specified size exceeds the maximum representable size|Invalid maximum heap size)\"),\n\n        // Some mods/shader packs do incorrect GL operations.\n        GL_OPERATION_FAILURE(\"(1282: Invalid operation|Maybe try a lower resolution resourcepack\\\\?)\"),\n\n        // Maybe software rendering? Suggest user for using a graphics card.\n        OPENGL_NOT_SUPPORTED(\"The driver does not appear to support OpenGL\"),\n        GRAPHICS_DRIVER(\"(Pixel format not accelerated|GLX: Failed to create context: GLXBadFBConfig|Couldn't set pixel format|net\\\\.minecraftforge\\\\.fml.client\\\\.SplashProgress|org\\\\.lwjgl\\\\.LWJGLException|EXCEPTION_ACCESS_VIOLATION(.|\\\\n|\\\\r)+# C {2}\\\\[(ig|atio|nvoglv))\"),\n        // macOS initializing OpenGL window issues\n        MACOS_FAILED_TO_FIND_SERVICE_PORT_FOR_DISPLAY(\"java\\\\.lang\\\\.IllegalStateException: GLFW error before init: \\\\[0x10008\\\\]Cocoa: Failed to find service port for display\"),\n        // Out of memory\n        OUT_OF_MEMORY(\"(java\\\\.lang\\\\.OutOfMemoryError|The system is out of physical RAM or swap space|Out of Memory Error|Error occurred during initialization of VM\\\\RToo small maximum heap)\"),\n        // Memory exceeded\n        MEMORY_EXCEEDED(\"There is insufficient memory for the Java Runtime Environment to continue\"),\n        // Too high resolution\n        RESOLUTION_TOO_HIGH(\"Maybe try a (lower resolution|lowerresolution) (resourcepack|texturepack)\\\\?\"),\n        // game can only run on Java 8. Version of uesr's JVM is too high.\n        JDK_9(\"java\\\\.lang\\\\.ClassCastException: (java\\\\.base/jdk|class jdk)\"),\n        // Forge and OptiFine with crash because the JVM compiled with a new version of Xcode\n        // https://github.com/sp614x/optifine/issues/4824\n        // https://github.com/MinecraftForge/MinecraftForge/issues/7546\n        MAC_JDK_8U261(\"Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSWindow drag regions should only be invalidated on the Main Thread!'\"),\n        // user modifies minecraft primary jar without changing hash file\n        FILE_CHANGED(\"java\\\\.lang\\\\.SecurityException: SHA1 digest error for (?<file>.*)|signer information does not match signer information of other classes in the same package\", \"file\"),\n        // mod loader/coremod injection fault, prompt user to reinstall game.\n        NO_SUCH_METHOD_ERROR(\"java\\\\.lang\\\\.NoSuchMethodError: (?<class>.*?)\", \"class\"),\n        // mod loader/coremod injection fault, prompt user to reinstall game.\n        NO_CLASS_DEF_FOUND_ERROR(\"java\\\\.lang\\\\.NoClassDefFoundError: (?<class>.*)\", \"class\"),\n        // coremod wants to access class without \"setAccessible\"\n        ILLEGAL_ACCESS_ERROR(\"java\\\\.lang\\\\.IllegalAccessError: tried to access class (.*?) from class (?<class>.*?)\", \"class\"),\n        // Some mods duplicated\n        DUPLICATED_MOD(\"Found a duplicate mod (?<name>.*) at (?<path>.*)\", \"name\", \"path\"),\n        // Fabric mod resolution\n        MOD_RESOLUTION(\"ModResolutionException: (?<reason>(.*)[\\\\n\\\\r]*( - (.*)[\\\\n\\\\r]*)+)\", \"reason\"),\n        FORGEMOD_RESOLUTION(\"Missing or unsupported mandatory dependencies:(?<reason>(.*)[\\\\n\\\\r]*(\\t(.*)[\\\\n\\\\r]*)+)\", \"reason\"),\n        FORGE_FOUND_DUPLICATE_MODS(\"Found duplicate mods:(?<reason>(.*)\\\\R*(\\t(.*)\\\\R*)+)\", \"reason\"),\n        MOD_RESOLUTION_CONFLICT(\"ModResolutionException: Found conflicting mods: (?<sourcemod>.*) conflicts with (?<destmod>.*)\", \"sourcemod\", \"destmod\"),\n        MOD_RESOLUTION_MISSING(\"ModResolutionException: Could not find required mod: (?<sourcemod>.*) requires (?<destmod>.*)\", \"sourcemod\", \"destmod\"),\n        MOD_RESOLUTION_MISSING_MINECRAFT(\"ModResolutionException: Could not find required mod: (?<mod>.*) requires \\\\{minecraft @ (?<version>.*)}\", \"mod\", \"version\"),\n        MOD_RESOLUTION_COLLECTION(\"ModResolutionException: Could not resolve valid mod collection \\\\(at: (?<sourcemod>.*) requires (?<destmod>.*)\\\\)\", \"sourcemod\", \"destmod\"),\n        // Some mods require a file not existing, asking user to manually delete it\n        FILE_ALREADY_EXISTS(\"java\\\\.nio\\\\.file\\\\.FileAlreadyExistsException: (?<file>.*)\", \"file\"),\n        // Forge found some mod crashed in game loading\n        LOADING_CRASHED_FORGE(\"LoaderExceptionModCrash: Caught exception from (?<name>.*?) \\\\((?<id>.*)\\\\)\", \"name\", \"id\"),\n        BOOTSTRAP_FAILED(\"Failed to create mod instance\\\\. ModID: (?<id>.*?),\", \"id\"),\n        // Fabric found some mod crashed in game loading\n        LOADING_CRASHED_FABRIC(\"Could not execute entrypoint stage '(.*?)' due to errors, provided by '(?<id>.*)'!\", \"id\"),\n        // Fabric may have breaking changes.\n        // https://github.com/FabricMC/fabric-loader/tree/master/src/main/legacyJava deprecated classes may be removed in the future.\n        FABRIC_VERSION_0_12(\"java\\\\.lang\\\\.NoClassDefFoundError: org/spongepowered/asm/mixin/transformer/FabricMixinTransformerProxy\"),\n        // Minecraft 1.16+Forge with crash because JDK-8273826\n        // https://github.com/McModLauncher/modlauncher/issues/91\n        MODLAUNCHER_8(\"java\\\\.lang\\\\.NoSuchMethodError: ('void sun\\\\.security\\\\.util\\\\.ManifestEntryVerifier\\\\.<init>\\\\(java\\\\.util\\\\.jar\\\\.Manifest\\\\)'|sun\\\\.security\\\\.util\\\\.ManifestEntryVerifier\\\\.<init>\\\\(Ljava/util/jar/Manifest;\\\\)V)\"),\n        // Manually triggered debug crash\n        DEBUG_CRASH(\"Manually triggered debug crash\"),\n        CONFIG(\"Failed loading config file (?<file>.*?) of type (.*?) for modid (?<id>.*)\", \"id\", \"file\"),\n        // Fabric gives some warnings\n        FABRIC_WARNINGS(\"(Warnings were found!|Incompatible mod set!|Incompatible mods found!)(.*?)[\\\\n\\\\r]+(?<reason>[^\\\\[]+)\\\\[\", \"reason\"),\n        // Game crashed when ticking entity\n        ENTITY(\"Entity Type: (?<type>.*)[\\\\w\\\\W\\\\n\\\\r]*?Entity's Exact location: (?<location>.*)\", \"type\", \"location\"),\n        // Game crashed when tessellating block model\n        BLOCK(\"Block: (?<type>.*)[\\\\w\\\\W\\\\n\\\\r]*?Block location: (?<location>.*)\", \"type\", \"location\"),\n        // Cannot find native libraries\n        UNSATISFIED_LINK_ERROR(\"java\\\\.lang\\\\.UnsatisfiedLinkError: Failed to locate library: (?<name>.*)\", \"name\"),\n\n        //https://github.com/HMCL-dev/HMCL/pull/1813\n        OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE(\"(java\\\\.lang\\\\.NoSuchMethodError: 'java\\\\.lang\\\\.Class sun\\\\.misc\\\\.Unsafe\\\\.defineAnonymousClass\\\\(java\\\\.lang\\\\.Class, byte\\\\[\\\\], java\\\\.lang\\\\.Object\\\\[\\\\]\\\\)'|java\\\\.lang\\\\.NoSuchMethodError: 'void net\\\\.minecraft\\\\.client\\\\.renderer\\\\.texture\\\\.SpriteContents\\\\.\\\\<init\\\\>\\\\(net\\\\.minecraft\\\\.resources\\\\.ResourceLocation, |java\\\\.lang\\\\.NoSuchMethodError: 'void net\\\\.minecraftforge\\\\.client\\\\.gui\\\\.overlay\\\\.ForgeGui\\\\.renderSelectedItemName\\\\(net\\\\.minecraft\\\\.client\\\\.gui\\\\.GuiGraphics, int\\\\)'|java\\\\.lang\\\\.NoSuchMethodError: 'java\\\\.lang\\\\.String com\\\\.mojang\\\\.blaze3d\\\\.systems\\\\.RenderSystem\\\\.getBackendDescription\\\\(\\\\)'|java\\\\.lang\\\\.NoSuchMethodError: 'net\\\\.minecraft\\\\.network\\\\.chat\\\\.FormattedText net\\\\.minecraft\\\\.client\\\\.gui\\\\.Font\\\\.ellipsize\\\\(net\\\\.minecraft\\\\.network\\\\.chat\\\\.FormattedText, int\\\\)'|java\\\\.lang\\\\.NoSuchMethodError: 'void net\\\\.minecraft\\\\.server\\\\.level\\\\.DistanceManager\\\\.(.*?)\\\\(net\\\\.minecraft\\\\.server\\\\.level\\\\.TicketType, net\\\\.minecraft\\\\.world\\\\.level\\\\.ChunkPos, int, java\\\\.lang\\\\.Object, boolean\\\\)'|java\\\\.lang\\\\.NoSuchMethodError: 'void net\\\\.minecraft\\\\.client\\\\.renderer\\\\.block\\\\.model\\\\.BakedQuad\\\\.\\\\<init\\\\>\\\\(int\\\\[\\\\], int, net\\\\.minecraft\\\\.core\\\\.Direction, net\\\\.minecraft\\\\.client\\\\.renderer\\\\.texture\\\\.TextureAtlasSprite, boolean, boolean\\\\)'|TRANSFORMER/net\\\\.optifine/net\\\\.optifine\\\\.reflect\\\\.Reflector\\\\.\\\\<clinit\\\\>\\\\(Reflector\\\\.java)\"),\n        MOD_FILES_ARE_DECOMPRESSED(\"(The directories below appear to be extracted jar files\\\\. Fix this before you continue|Extracted mod jars found, loading will NOT continue)\"),//Mod文件被解压\n        OPTIFINE_CAUSES_THE_WORLD_TO_FAIL_TO_LOAD(\"java\\\\.lang\\\\.NoSuchMethodError: net\\\\.minecraft\\\\.world\\\\.server\\\\.ChunkManager$ProxyTicketManager\\\\.shouldForceTicks\\\\(J\\\\)Z\"),//OptiFine导致无法加载世界 https://www.minecraftforum.net/forums/support/java-edition-support/3051132-exception-ticking-world\n        TOO_MANY_MODS_LEAD_TO_EXCEEDING_THE_ID_LIMIT(\"maximum id range exceeded\"),//Mod过多导致超出ID限制\n\n        // Mod issues\n        //https://github.com/HMCL-dev/HMCL/pull/2038\n        MODMIXIN_FAILURE(\"(MixinApplyError|Mixin prepare failed |Mixin apply failed |mixin\\\\.injection\\\\.throwables\\\\.|\\\\.mixins\\\\.json\\\\] FAILED during \\\\))\"),//ModMixin失败\n        MIXIN_APPLY_MOD_FAILED(\"Mixin apply for mod (?<id>.*) failed\", \"id\"),//Mixin应用失败\n        FORGE_ERROR(\"An exception was thrown, the game will display an error screen and halt\\\\.\\\\R*(?<reason>.*\\\\R*(\\\\s*at .*\\\\R)+)\", \"reason\"),//Forge报错,Forge可能已经提供了错误信息\n        MOD_RESOLUTION0(\"(\\tMod File:|-- MOD |\\tFailure message:)\"),\n        FORGE_REPEAT_INSTALLATION(\"MultipleArgumentsForOptionException: Found multiple arguments for option (.*?), but you asked for only one\"),//https://github.com/HMCL-dev/HMCL/issues/1880\n        OPTIFINE_REPEAT_INSTALLATION(\"ResolutionException: Module optifine reads another module named optifine\"),//Optifine 重复安装（及Mod文件夹有，自动安装也有）\n        JAVA_VERSION_IS_TOO_HIGH(\"(Unable to make protected final java\\\\.lang\\\\.Class java\\\\.lang\\\\.ClassLoader\\\\.defineClass|java\\\\.lang\\\\.NoSuchFieldException: ucp|Unsupported class file major version|because module java\\\\.base does not export|java\\\\.lang\\\\.ClassNotFoundException: jdk\\\\.nashorn\\\\.api\\\\.scripting\\\\.NashornScriptEngineFactory|java\\\\.lang\\\\.ClassNotFoundException: java\\\\.lang\\\\.invoke\\\\.LambdaMetafactory|Exception in thread \\\"main\\\" java\\\\.lang\\\\.NullPointerException: Cannot read the array length because \\\"urls\\\" is null)\"),//Java版本过高\n        INSTALL_MIXINBOOTSTRAP(\"java\\\\.lang\\\\.ClassNotFoundException: org\\\\.spongepowered\\\\.asm\\\\.launch\\\\.MixinTweaker\"),\n\n        //Forge 默认会把每一个 mod jar 都当做一个 JPMS 的模块（Module）加载。在这个 jar 没有给出 module-info 声明的情况下，JPMS 会采用这样的顺序决定 module 名字：\n        //1. META-INF/MANIFEST.MF 里的 Automatic-Module-Name\n        //2. 根据文件名生成。文件名里的 .jar 后缀名先去掉，然后检查是否有 -(\\\\d+(\\\\.|$)) 的部分，有的话只取 - 前面的部分，- 后面的部分成为 module 的版本号（即尝试判断文件名里是否有版本号，有的话去掉），然后把不是拉丁字母和数字的字符（正则表达式 [^A-Za-z0-9]）都换成点，然后把连续的多个点换成一个点，最后去掉开头和结尾的点。那么\n        //按照 2.，如果你的文件名是拔刀剑.jar，那么这么一通流程下来，你得到的 module 名就是空字符串，而这是不允许的。(来自 @Föhn 说明)\n        MOD_NAME(\"Invalid module name: '' is not a Java identifier\"),\n\n        //Forge 安装不完整\n        INCOMPLETE_FORGE_INSTALLATION(\"(java\\\\.io\\\\.UncheckedIOException: java\\\\.io\\\\.IOException: Invalid paths argument, contained no existing paths: \\\\[(.*?)(forge-(.*?)-client\\\\.jar|fmlcore-(.*?)\\\\.jar)\\\\]|Failed to find Minecraft resource version (.*?) at (.*?)forge-(.*?)-client\\\\.jar|Cannot find launch target fmlclient, unable to launch|java\\\\.lang\\\\.IllegalStateException: Could not find net/minecraft/client/Minecraft\\\\.class in classloader SecureModuleClassLoader)\"),\n\n        NIGHT_CONFIG_FIXES(\"com\\\\.electronwill\\\\.nightconfig\\\\.core\\\\.io\\\\.ParsingException: Not enough data available\"),//https://github.com/Fuzss/nightconfigfixes\n        //Shaders Mod detected. Please remove it, OptiFine has built-in support for shaders.\n        SHADERS_MOD(\"java\\\\.lang\\\\.RuntimeException: Shaders Mod detected\\\\. Please remove it, OptiFine has built-in support for shaders\\\\.\"),\n\n        // 一些模组与 Optifine 不兼容\n        MOD_FOREST_OPTIFINE(\"Error occurred applying transform of coremod META-INF/asm/multipart\\\\.js function render\"),\n        // PERFORMANT is not compatible with OptiFine\n        PERFORMANT_FOREST_OPTIFINE(\"org\\\\.spongepowered\\\\.asm\\\\.mixin\\\\.injection\\\\.throwables\\\\.InjectionError: Critical injection failure: Redirector OnisOnLadder\\\\(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/LivingEntity;\\\\)Z in performant\\\\.mixins\\\\.json:entity\\\\.LivingEntityMixin failed injection check, \\\\(0/1\\\\) succeeded\\\\. Scanned 1 target\\\\(s\\\\)\\\\. Using refmap performant\\\\.refmap\\\\.json\"),\n        // TwilightForest is not compatible with OptiFine on Minecraft 1.16\n        TWILIGHT_FOREST_OPTIFINE(\"java\\\\.lang\\\\.IllegalArgumentException: (.*) outside of image bounds (.*)\"),\n        // Jade is not compatible with OptiFine on Minecraft 1.20+\n        JADE_FOREST_OPTIFINE(\"Critical injection failure: LVT in net/minecraft/client/renderer/GameRenderer::m_109093_\\\\(FJZ\\\\)V has incompatible changes at opcode 760 in callback jade\\\\.mixins\\\\.json:GameRendererMixin-\\\\>@Inject::jade\\\\$runTick\\\\(FJZLorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;IILcom/mojang/blaze3d/platform/Window;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/gui/GuiGraphics;\\\\)V\\\\.\"),\n        // NeoForge 与 OptiFine 不兼容\n        NEOFORGE_FOREST_OPTIFINE(\"cpw\\\\.mods\\\\.modlauncher\\\\.InvalidLauncherSetupException: Invalid Services found OptiFine\"),\n\n        // 一些模组与 Sodium 不兼容\n        // https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\n        RTSS_FOREST_SODIUM(\"RivaTuner Statistics Server \\\\(RTSS\\\\) is not compatible with Sodium\");\n\n\n        private final Pattern pattern;\n        private final String[] groupNames;\n\n        Rule(@Language(\"RegExp\") String pattern, String... groupNames) {\n            this.pattern = Pattern.compile(pattern);\n            this.groupNames = groupNames;\n        }\n\n        public Pattern getPattern() {\n            return pattern;\n        }\n\n        public String[] getGroupNames() {\n            return groupNames;\n        }\n    }\n\n    public static class Result {\n        private final Rule rule;\n        private final String log;\n        private final Matcher matcher;\n\n        public Result(Rule rule, String log, Matcher matcher) {\n            this.rule = rule;\n            this.log = log;\n            this.matcher = matcher;\n        }\n\n        public Rule getRule() {\n            return rule;\n        }\n\n        public String getLog() {\n            return log;\n        }\n\n        public Matcher getMatcher() {\n            return matcher;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n\n            Result result = (Result) o;\n\n            if (rule != result.rule) return false;\n            if (!log.equals(result.log)) return false;\n            return matcher.equals(result.matcher);\n        }\n\n        @Override\n        public int hashCode() {\n            int result = rule.hashCode();\n            result = 31 * result + log.hashCode();\n            result = 31 * result + matcher.hashCode();\n            return result;\n        }\n    }\n\n    public static Set<Result> analyze(String log) {\n        Set<Result> results = new HashSet<>();\n        for (Rule rule : Rule.values()) {\n            Matcher matcher = rule.pattern.matcher(log);\n            if (matcher.find()) {\n                results.add(new Result(rule, log, matcher));\n            }\n        }\n        return results;\n    }\n\n    private static final Pattern CRASH_REPORT_LOCATION_PATTERN = Pattern.compile(\"#@!@# Game crashed! Crash report saved to: #@!@# (?<location>.*)\");\n\n    @Nullable\n    public static String findCrashReport(String log) throws IOException, InvalidPathException {\n        Matcher matcher = CRASH_REPORT_LOCATION_PATTERN.matcher(log);\n        if (matcher.find()) {\n            return Files.readString(Paths.get(matcher.group(\"location\")));\n        } else {\n            return null;\n        }\n    }\n\n    public static String extractCrashReport(String rawLog) {\n        int begin = rawLog.lastIndexOf(\"---- Minecraft Crash Report ----\");\n        int end = rawLog.lastIndexOf(\"#@!@# Game crashed! Crash report saved to\");\n        if (begin == -1 || end == -1 || begin >= end) return null;\n        return rawLog.substring(begin, end);\n    }\n\n    private static final Pattern CRASH_REPORT_STACK_TRACE_PATTERN = Pattern.compile(\"Description: (.*?)[\\\\n\\\\r]+(?<stacktrace>[\\\\w\\\\W\\\\n\\\\r]+)A detailed walkthrough of the error\");\n    private static final Pattern STACK_TRACE_LINE_PATTERN = Pattern.compile(\"at (?<method>.*?)\\\\((?<sourcefile>.*?)\\\\)\");\n    private static final Pattern STACK_TRACE_LINE_MODULE_PATTERN = Pattern.compile(\"\\\\{(?<tokens>.*)}\");\n    private static final Set<String> PACKAGE_KEYWORD_BLACK_LIST = new HashSet<>(Arrays.asList(\n            \"net\", \"minecraft\", \"item\", \"setup\", \"block\", \"assist\", \"optifine\", \"player\", \"unimi\", \"fastutil\", \"tileentity\", \"events\", \"common\", \"blockentity\", \"client\", \"entity\", \"mojang\", \"main\", \"gui\", \"world\", \"server\", \"dedicated\", \"map\", \"dsi\", // minecraft\n            \"renderer\", \"chunk\", \"model\", \"loading\", \"color\", \"pipeline\", \"inventory\", \"launcher\", \"physics\", \"particle\", \"gen\", \"registry\", \"worldgen\", \"texture\", \"biomes\", \"biome\",\n            \"monster\", \"passive\", \"ai\", \"integrated\", \"tile\", \"state\", \"play\", \"override\", \"transformers\", \"structure\", \"nbt\", \"pathfinding\", \"chunk\", \"audio\", \"entities\", \"items\", \"renderers\",\n            \"storage\", \"universal\", \"oshi\", \"platform\",\n            \"java\", \"lang\", \"util\", \"nio\", \"io\", \"sun\", \"reflect\", \"zip\", \"jar\", \"jdk\", \"nashorn\", \"scripts\", \"runtime\", \"internal\", // java\n            \"mods\", \"mod\", \"impl\", \"org\", \"com\", \"cn\", \"cc\", \"jp\", // title\n            \"core\", \"config\", \"registries\", \"lib\", \"ruby\", \"mc\", \"codec\", \"recipe\", \"channel\", \"embedded\", \"done\", \"net\", \"netty\", \"network\", \"load\", \"github\", \"handler\", \"content\", \"feature\", // misc\n            \"file\", \"machine\", \"shader\", \"general\", \"helper\", \"init\", \"library\", \"api\", \"integration\", \"engine\", \"preload\", \"preinit\",\n            \"hellominecraft\", \"jackhuang\", // hmcl\n            \"fml\", \"minecraftforge\", \"forge\", \"cpw\", \"modlauncher\", \"launchwrapper\", \"objectweb\", \"asm\", \"event\", \"eventhandler\", \"handshake\", \"modapi\", \"kcauldron\", // forge\n            \"fabricmc\", \"loader\", \"game\", \"knot\", \"launch\", \"mixin\" // fabric\n    ));\n\n    public static Set<String> findKeywordsFromCrashReport(String crashReport) {\n        Matcher matcher = CRASH_REPORT_STACK_TRACE_PATTERN.matcher(crashReport);\n        Set<String> result = new HashSet<>();\n        if (matcher.find()) {\n            for (String line : matcher.group(\"stacktrace\").split(\"\\\\n\")) {\n                Matcher lineMatcher = STACK_TRACE_LINE_PATTERN.matcher(line);\n                if (lineMatcher.find()) {\n                    String[] method = lineMatcher.group(\"method\").split(\"\\\\.\");\n                    for (int i = 0; i < method.length - 2; i++) {\n                        if (PACKAGE_KEYWORD_BLACK_LIST.contains(method[i])) {\n                            continue;\n                        }\n                        result.add(method[i]);\n                    }\n\n                    Matcher moduleMatcher = STACK_TRACE_LINE_MODULE_PATTERN.matcher(line);\n                    if (moduleMatcher.find()) {\n                        for (String module : moduleMatcher.group(\"tokens\").split(\",\")) {\n                            String[] split = module.split(\":\");\n                            if (split.length >= 2 && \"xf\".equals(split[0])) {\n                                if (PACKAGE_KEYWORD_BLACK_LIST.contains(split[1])) {\n                                    continue;\n                                }\n\n                                result.add(split[1]);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        return result;\n    }\n\n    public static int getJavaVersionFromMajorVersion(int majorVersion) {\n        if (majorVersion >= 46) {\n            return majorVersion - 44;\n        } else {\n            return -1;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.MaintainTask;\nimport org.jackhuang.hmcl.download.game.VersionJsonSaveTask;\nimport org.jackhuang.hmcl.event.*;\nimport org.jackhuang.hmcl.game.tlauncher.TLauncherVersion;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * An implementation of classic Minecraft game repository.\n *\n * @author huangyuhui\n */\npublic class DefaultGameRepository implements GameRepository {\n\n    private Path baseDirectory;\n    protected Map<String, Version> versions;\n    private final ConcurrentHashMap<Path, Optional<String>> gameVersions = new ConcurrentHashMap<>();\n\n    public DefaultGameRepository(Path baseDirectory) {\n        this.baseDirectory = baseDirectory;\n    }\n\n    public Path getBaseDirectory() {\n        return baseDirectory;\n    }\n\n    public void setBaseDirectory(Path baseDirectory) {\n        this.baseDirectory = baseDirectory;\n    }\n\n    @Override\n    public boolean hasVersion(String id) {\n        return id != null && versions.containsKey(id);\n    }\n\n    @Override\n    public Version getVersion(String id) {\n        if (!hasVersion(id))\n            throw new VersionNotFoundException(\"Version '\" + id + \"' does not exist in \" + versions.keySet() + \".\");\n        return versions.get(id);\n    }\n\n    @Override\n    public int getVersionCount() {\n        return versions.size();\n    }\n\n    @Override\n    public Collection<Version> getVersions() {\n        return versions.values();\n    }\n\n    @Override\n    public Path getLibrariesDirectory(Version version) {\n        return getBaseDirectory().resolve(\"libraries\");\n    }\n\n    @Override\n    public Path getLibraryFile(Version version, Library lib) {\n        if (\"local\".equals(lib.getHint())) {\n            if (lib.getFileName() != null) {\n                return getVersionRoot(version.getId()).resolve(\"libraries/\" + lib.getFileName());\n            }\n\n            return getVersionRoot(version.getId()).resolve(\"libraries/\" + lib.getArtifact().getFileName());\n        }\n\n        return getLibrariesDirectory(version).resolve(lib.getPath());\n    }\n\n    public Path getArtifactFile(Version version, Artifact artifact) {\n        return artifact.getPath(getBaseDirectory().resolve(\"libraries\"));\n    }\n\n    public GameDirectoryType getGameDirectoryType(String id) {\n        return GameDirectoryType.ROOT_FOLDER;\n    }\n\n    @Override\n    public Path getRunDirectory(String id) {\n        return switch (getGameDirectoryType(id)) {\n            case VERSION_FOLDER -> getVersionRoot(id);\n            case ROOT_FOLDER -> getBaseDirectory();\n            default -> throw new IllegalStateException();\n        };\n    }\n\n    @Override\n    public Path getVersionJar(Version version) {\n        Version v = version.resolve(this);\n        String id = Optional.ofNullable(v.getJar()).orElse(v.getId());\n        return getVersionRoot(id).resolve(id + \".jar\");\n    }\n\n    @Override\n    public Optional<String> getGameVersion(Version version) {\n        // This implementation may cause multiple flows against the same version entering\n        // this function, which is accepted because GameVersion::minecraftVersion should\n        // be consistent.\n        return gameVersions.computeIfAbsent(getVersionJar(version), versionJar -> {\n            Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);\n            if (gameVersion.isEmpty()) {\n                LOG.warning(\"Cannot find out game version of \" + version.getId() + \", primary jar: \" + versionJar.toString() + \", jar exists: \" + Files.exists(versionJar));\n            }\n            return gameVersion;\n        });\n    }\n\n    @Override\n    public Path getNativeDirectory(String id, Platform platform) {\n        return getVersionRoot(id).resolve(\"natives-\" + platform);\n    }\n\n    @Override\n    public Path getModsDirectory(String id) {\n        return getRunDirectory(id).resolve(\"mods\");\n    }\n\n    @Override\n    public Path getVersionRoot(String id) {\n        return getBaseDirectory().resolve(\"versions/\" + id);\n    }\n\n    public Path getVersionJson(String id) {\n        return getVersionRoot(id).resolve(id + \".json\");\n    }\n\n    public Version readVersionJson(String id) throws IOException, JsonParseException {\n        return readVersionJson(getVersionJson(id));\n    }\n\n    public Version readVersionJson(Path file) throws IOException, JsonParseException {\n        String jsonText = Files.readString(file);\n        try {\n            // Try TLauncher version json format\n            return JsonUtils.fromNonNullJson(jsonText, TLauncherVersion.class).toVersion();\n        } catch (JsonParseException ignored) {\n        }\n\n        try {\n            // Try official version json format\n            return JsonUtils.fromNonNullJson(jsonText, Version.class);\n        } catch (JsonParseException ignored) {\n        }\n\n        LOG.warning(\"Cannot parse version json: \" + file + \"\\n\" + jsonText);\n        throw new JsonParseException(\"Version json incorrect\");\n    }\n\n    @Override\n    public boolean renameVersion(String from, String to) {\n        if (EventBus.EVENT_BUS.fireEvent(new RenameVersionEvent(this, from, to)) == Event.Result.DENY)\n            return false;\n\n        try {\n            Version fromVersion = getVersion(from);\n            Path fromDir = getVersionRoot(from);\n            Path toDir = getVersionRoot(to);\n            Files.move(fromDir, toDir);\n\n            Path fromJson = toDir.resolve(from + \".json\");\n            Path fromJar = toDir.resolve(from + \".jar\");\n            Path toJson = toDir.resolve(to + \".json\");\n            Path toJar = toDir.resolve(to + \".jar\");\n\n            boolean hasJarFile = Files.exists(fromJar);\n\n            try {\n                Files.move(fromJson, toJson);\n                if (hasJarFile) Files.move(fromJar, toJar);\n            } catch (IOException e) {\n                // recovery\n                Lang.ignoringException(() -> Files.move(toJson, fromJson));\n                if (hasJarFile) Lang.ignoringException(() -> Files.move(toJar, fromJar));\n                Lang.ignoringException(() -> Files.move(toDir, fromDir));\n                throw e;\n            }\n\n            if (fromVersion.getId().equals(fromVersion.getJar()))\n                fromVersion = fromVersion.setJar(null);\n            JsonUtils.writeToJsonFile(toJson, fromVersion.setId(to));\n\n            // fix inheritsFrom of versions that inherits from version [from].\n            for (Version version : getVersions()) {\n                if (from.equals(version.getInheritsFrom())) {\n                    Path targetPath = getVersionJson(version.getId());\n                    Files.createDirectories(targetPath.getParent());\n                    JsonUtils.writeToJsonFile(targetPath, version.setInheritsFrom(to));\n                }\n            }\n            return true;\n        } catch (IOException | JsonParseException | VersionNotFoundException | InvalidPathException e) {\n            LOG.warning(\"Unable to rename version \" + from + \" to \" + to, e);\n            return false;\n        }\n    }\n\n    public boolean removeVersionFromDisk(String id) {\n        if (EventBus.EVENT_BUS.fireEvent(new RemoveVersionEvent(this, id)) == Event.Result.DENY)\n            return false;\n        if (!versions.containsKey(id))\n            return FileUtils.deleteDirectoryQuietly(getVersionRoot(id));\n        Path file = getVersionRoot(id);\n        if (Files.notExists(file))\n            return true;\n        // test if no file in this version directory is occupied.\n        Path removedFile = file.toAbsolutePath().resolveSibling(FileUtils.getName(file) + \"_removed\");\n        try {\n            Files.move(file, removedFile, StandardCopyOption.REPLACE_EXISTING);\n        } catch (IOException e) {\n            LOG.warning(\"Failed to rename file \" + file, e);\n            return false;\n        }\n\n        try {\n            versions.remove(id);\n\n            if (FileUtils.moveToTrash(removedFile)) {\n                return true;\n            }\n\n            // remove json files first to ensure HMCL will not recognize this folder as a valid version.\n\n            for (Path path : FileUtils.listFilesByExtension(removedFile, \"json\")) {\n                try {\n                    Files.delete(path);\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to delete file \" + path, e);\n                }\n            }\n\n            // remove the version from version list regardless of whether the directory was removed successfully or not.\n            try {\n                FileUtils.deleteDirectory(removedFile);\n            } catch (IOException e) {\n                LOG.warning(\"Unable to remove version folder: \" + file, e);\n            }\n            return true;\n        } finally {\n            refreshVersionsAsync().start();\n        }\n    }\n\n    protected void refreshVersionsImpl() {\n        Map<String, Version> versions = new TreeMap<>();\n\n        if (ClassicVersion.hasClassicVersion(getBaseDirectory())) {\n            Version version = new ClassicVersion();\n            versions.put(version.getId(), version);\n        }\n\n        SimpleVersionProvider provider = new SimpleVersionProvider();\n\n        Path versionsDir = getBaseDirectory().resolve(\"versions\");\n        if (Files.isDirectory(versionsDir)) {\n            try (Stream<Path> stream = Files.list(versionsDir)) {\n                stream.parallel().filter(Files::isDirectory).flatMap(dir -> {\n                    String id = FileUtils.getName(dir);\n                    Path json = dir.resolve(id + \".json\");\n\n                    // If user renamed the json file by mistake or created the json file in a wrong name,\n                    // we will find the only json and rename it to correct name.\n                    if (Files.notExists(json)) {\n                        List<Path> jsons = FileUtils.listFilesByExtension(dir, \"json\");\n                        if (jsons.size() == 1) {\n                            LOG.info(\"Renaming json file \" + jsons.get(0) + \" to \" + json);\n\n                            try {\n                                Files.move(jsons.get(0), json);\n                            } catch (IOException e) {\n                                LOG.warning(\"Cannot rename json file, ignoring version \" + id, e);\n                                return Stream.empty();\n                            }\n\n                            Path jar = dir.resolve(FileUtils.getNameWithoutExtension(jsons.get(0)) + \".jar\");\n                            if (Files.exists(jar)) {\n                                try {\n                                    Files.move(jar, dir.resolve(id + \".jar\"));\n                                } catch (IOException e) {\n                                    LOG.warning(\"Cannot rename jar file, ignoring version \" + id, e);\n                                    return Stream.empty();\n                                }\n                            }\n                        } else {\n                            LOG.info(\"No available json file found, ignoring version \" + id);\n                            return Stream.empty();\n                        }\n                    }\n\n                    Version version;\n                    try {\n                        version = readVersionJson(json);\n                    } catch (Exception e) {\n                        LOG.warning(\"Malformed version json \" + id, e);\n                        // JsonSyntaxException or IOException or NullPointerException(!!)\n                        if (EventBus.EVENT_BUS.fireEvent(new GameJsonParseFailedEvent(this, json, id)) != Event.Result.ALLOW)\n                            return Stream.empty();\n\n                        try {\n                            version = readVersionJson(json);\n                        } catch (Exception e2) {\n                            LOG.error(\"User corrected version json is still malformed\", e2);\n                            return Stream.empty();\n                        }\n                    }\n\n                    if (!id.equals(version.getId())) {\n                        try {\n                            String from = id;\n                            String to = version.getId();\n                            Path fromDir = getVersionRoot(from);\n                            Path toDir = getVersionRoot(to);\n                            Files.move(fromDir, toDir);\n\n                            Path fromJson = toDir.resolve(from + \".json\");\n                            Path fromJar = toDir.resolve(from + \".jar\");\n                            Path toJson = toDir.resolve(to + \".json\");\n                            Path toJar = toDir.resolve(to + \".jar\");\n\n                            try {\n                                Files.move(fromJson, toJson);\n                                if (Files.exists(fromJar))\n                                    Files.move(fromJar, toJar);\n                            } catch (IOException e) {\n                                // recovery\n                                Lang.ignoringException(() -> Files.move(toJson, fromJson));\n                                Lang.ignoringException(() -> Files.move(toJar, fromJar));\n                                Lang.ignoringException(() -> Files.move(toDir, fromDir));\n                                throw e;\n                            }\n                        } catch (IOException e) {\n                            LOG.warning(\"Ignoring version \" + version.getId() + \" because version id does not match folder name \" + id + \", and we cannot correct it.\", e);\n                            return Stream.empty();\n                        }\n                    }\n\n                    return Stream.of(version);\n                }).forEachOrdered(provider::addVersion);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to load versions from \" + versionsDir, e);\n            }\n        }\n\n        for (Version version : provider.getVersionMap().values()) {\n            try {\n                Version resolved = version.resolve(provider);\n\n                if (resolved.appliesToCurrentEnvironment() &&\n                        EventBus.EVENT_BUS.fireEvent(new LoadedOneVersionEvent(this, resolved)) != Event.Result.DENY)\n                    versions.put(version.getId(), version);\n            } catch (VersionNotFoundException e) {\n                LOG.warning(\"Ignoring version \" + version.getId() + \" because it inherits from a nonexistent version.\");\n            }\n        }\n\n        this.gameVersions.clear();\n        this.versions = versions;\n    }\n\n    @Override\n    public void refreshVersions() {\n        if (EventBus.EVENT_BUS.fireEvent(new RefreshingVersionsEvent(this)) == Event.Result.DENY)\n            return;\n\n        refreshVersionsImpl();\n        EventBus.EVENT_BUS.fireEvent(new RefreshedVersionsEvent(this));\n    }\n\n    @Override\n    public AssetIndex getAssetIndex(String version, String assetId) throws IOException {\n        try {\n            return Objects.requireNonNull(JsonUtils.fromJsonFile(getIndexFile(version, assetId), AssetIndex.class));\n        } catch (JsonParseException | NullPointerException e) {\n            throw new IOException(\"Asset index file malformed\", e);\n        }\n    }\n\n    @Override\n    public Path getActualAssetDirectory(String version, String assetId) {\n        try {\n            return reconstructAssets(version, assetId);\n        } catch (IOException | JsonParseException e) {\n            LOG.error(\"Unable to reconstruct asset directory\", e);\n            return getAssetDirectory(version, assetId);\n        }\n    }\n\n    @Override\n    public Path getAssetDirectory(String version, String assetId) {\n        return getBaseDirectory().resolve(\"assets\");\n    }\n\n    @Override\n    public Optional<Path> getAssetObject(String version, String assetId, String name) throws IOException {\n        try {\n            AssetObject assetObject = getAssetIndex(version, assetId).getObjects().get(name);\n            if (assetObject == null) return Optional.empty();\n            return Optional.of(getAssetObject(version, assetId, assetObject));\n        } catch (IOException e) {\n            throw e;\n        } catch (Exception e) {\n            throw new IOException(\"Unrecognized asset object \" + name + \" in asset \" + assetId + \" of version \" + version, e);\n        }\n    }\n\n    @Override\n    public Path getAssetObject(String version, String assetId, AssetObject obj) {\n        return getAssetObject(version, getAssetDirectory(version, assetId), obj);\n    }\n\n    public Path getAssetObject(String version, Path assetDir, AssetObject obj) {\n        return assetDir.resolve(\"objects\").resolve(obj.getLocation());\n    }\n\n    @Override\n    public Path getIndexFile(String version, String assetId) {\n        return getAssetDirectory(version, assetId).resolve(\"indexes\").resolve(assetId + \".json\");\n    }\n\n    @Override\n    public Path getLoggingObject(String version, String assetId, LoggingInfo loggingInfo) {\n        return getAssetDirectory(version, assetId).resolve(\"log_configs\").resolve(loggingInfo.getFile().getId());\n    }\n\n    protected Path reconstructAssets(String version, String assetId) throws IOException, JsonParseException {\n        Path assetsDir = getAssetDirectory(version, assetId);\n        Path indexFile = getIndexFile(version, assetId);\n        Path virtualRoot = assetsDir.resolve(\"virtual\").resolve(assetId);\n\n        if (!Files.isRegularFile(indexFile))\n            return assetsDir;\n\n        AssetIndex index = JsonUtils.fromJsonFile(indexFile, AssetIndex.class);\n\n        if (index == null)\n            return assetsDir;\n\n        if (index.isVirtual()) {\n            Path resourcesDir = getRunDirectory(version).resolve(\"resources\");\n\n            int cnt = 0;\n            int tot = index.getObjects().size();\n            for (Map.Entry<String, AssetObject> entry : index.getObjects().entrySet()) {\n                Path target = virtualRoot.resolve(entry.getKey());\n                Path original = getAssetObject(version, assetsDir, entry.getValue());\n                if (Files.exists(original)) {\n                    cnt++;\n                    if (!Files.isRegularFile(target))\n                        FileUtils.copyFile(original, target);\n\n                    if (index.needMapToResources()) {\n                        target = resourcesDir.resolve(entry.getKey());\n                        if (!Files.isRegularFile(target))\n                            FileUtils.copyFile(original, target);\n                    }\n                }\n            }\n\n            // If the scale new format existent file is lower then 0.1, use the old format.\n            if (cnt * 10 < tot)\n                return assetsDir;\n            else\n                return virtualRoot;\n        }\n\n        return assetsDir;\n    }\n\n    public Task<Version> saveAsync(Version version) {\n        this.gameVersions.remove(getVersionJar(version));\n        if (version.isResolvedPreservingPatches()) {\n            return new VersionJsonSaveTask(this, MaintainTask.maintainPreservingPatches(this, version));\n        } else {\n            return new VersionJsonSaveTask(this, version);\n        }\n    }\n\n    public boolean isLoaded() {\n        return versions != null;\n    }\n\n    public Path getModpackConfiguration(String version) {\n        return getVersionRoot(version).resolve(\"modpack.json\");\n    }\n\n    /**\n     * read modpack configuration for a version.\n     *\n     * @param version version installed as modpack\n     * @return modpack configuration object, or null if this version is not a modpack.\n     * @throws VersionNotFoundException if version does not exist.\n     * @throws IOException              if an i/o error occurs.\n     */\n    @Nullable\n    public ModpackConfiguration<?> readModpackConfiguration(String version) throws IOException, VersionNotFoundException {\n        if (!hasVersion(version)) throw new VersionNotFoundException(version);\n        Path file = getModpackConfiguration(version);\n        if (Files.notExists(file)) return null;\n        return JsonUtils.fromJsonFile(file, ModpackConfiguration.class);\n    }\n\n    public boolean isModpack(String version) {\n        return Files.exists(getModpackConfiguration(version));\n    }\n\n    public ModManager getModManager(String version) {\n        return new ModManager(this, version);\n    }\n\n    public Path getSavesDirectory(String id) {\n        return getRunDirectory(id).resolve(\"saves\");\n    }\n\n    public Path getBackupsDirectory(String id) {\n        return getRunDirectory(id).resolve(\"backups\");\n    }\n\n    public Path getSchematicsDirectory(String id) {\n        return getRunDirectory(id).resolve(\"schematics\");\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this)\n                .append(\"versions\", versions == null ? null : versions.keySet())\n                .append(\"baseDirectory\", baseDirectory)\n                .toString();\n    }\n\n    public Path getResourcepacksDirectory(String id) {\n        return getRunDirectory(id).resolve(\"resourcepacks\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/DownloadInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic class DownloadInfo implements Validation {\n\n    @SerializedName(\"url\")\n    private final String url;\n    @SerializedName(\"sha1\")\n    private final String sha1;\n    @SerializedName(\"size\")\n    private final int size;\n\n    public DownloadInfo() {\n        this(\"\");\n    }\n\n    public DownloadInfo(String url) {\n        this(url, null);\n    }\n\n    public DownloadInfo(String url, String sha1) {\n        this(url, sha1, 0);\n    }\n\n    public DownloadInfo(String url, String sha1, int size) {\n        this.url = url;\n        this.sha1 = sha1;\n        this.size = size;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public String getSha1() {\n        return \"invalid\".equals(sha1) ? null : sha1;\n    }\n\n    public int getSize() {\n        return size;\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this).append(\"url\", url).append(\"sha1\", sha1).append(\"size\", size).toString();\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        if (StringUtils.isBlank(url))\n            throw new TolerableValidationException();\n    }\n\n    public boolean validateChecksum(Path file, boolean defaultValue) throws IOException {\n        if (getSha1() == null) return defaultValue;\n        return DigestUtils.digestToString(\"SHA-1\", file).equalsIgnoreCase(getSha1());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/DownloadType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\n/**\n *\n * @author huangyuhui\n */\npublic enum DownloadType {\n    CLIENT,\n    SERVER,\n    WINDOWS_SERVER,\n    CLIENT_MAPPINGS,\n    SERVER_MAPPINGS\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/ExtractRules.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class ExtractRules {\n\n    public static final ExtractRules EMPTY = new ExtractRules();\n\n    private final List<String> exclude;\n\n    public ExtractRules() {\n        this.exclude = Collections.emptyList();\n    }\n\n    public ExtractRules(List<String> exclude) {\n        this.exclude = new ArrayList<>(exclude);\n    }\n\n    public List<String> getExclude() {\n        return Collections.unmodifiableList(exclude);\n    }\n\n    public boolean shouldExtract(String path) {\n        return exclude.stream().noneMatch(path::startsWith);\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameDirectoryType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\n/**\n * Determines where game runs in and game files such as mods.\n *\n * @author huangyuhui\n */\npublic enum GameDirectoryType {\n    /**\n     * .minecraft\n     */\n    ROOT_FOLDER,\n    /**\n     * .minecraft/versions/&lt;version name&gt;\n     */\n    VERSION_FOLDER,\n    /**\n     * user customized directory.\n     */\n    CUSTOM\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameDumpGenerator.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n\npackage org.jackhuang.hmcl.game;\n\nimport com.sun.tools.attach.AttachNotSupportedException;\nimport com.sun.tools.attach.VirtualMachine;\nimport org.jackhuang.hmcl.java.JavaInfo;\nimport org.jackhuang.hmcl.util.logging.Logger;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.io.*;\nimport java.nio.CharBuffer;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * Generate a JVM dump on a process.\n * WARNING: Initializing this class may cause NoClassDefFoundError.\n */\npublic final class GameDumpGenerator {\n    private GameDumpGenerator() {\n    }\n\n    private static final int TOOL_VERSION = 9;\n\n    private static final int DUMP_TIME = 3;\n\n    private static final int RETRY_TIME = 3;\n\n    public static void writeDumpTo(long pid, Path path) throws IOException, InterruptedException {\n        try (Writer writer = Files.newBufferedWriter(path)) {\n            // On a local machine, the lvmid and the pid are the same.\n            VirtualMachine vm = attachVM(String.valueOf(pid), writer);\n\n            try {\n                writeDumpHeadTo(vm, writer);\n\n                for (int i = 0; i < DUMP_TIME; i++) {\n                    if (i > 0)\n                        Thread.sleep(3000);\n\n                    writer.write(\"====================\\n\");\n                    writeDumpBodyTo(vm, writer);\n                }\n            } finally {\n                vm.detach();\n            }\n        }\n    }\n\n    private static void writeDumpHeadTo(VirtualMachine vm, Writer writer) throws IOException {\n        writer.write(\"===== Minecraft JStack Dump =====\\n\");\n\n        writeDumpHeadKeyValueTo(\"Tool Version\", String.valueOf(TOOL_VERSION), writer, false);\n        writeDumpHeadKeyValueTo(\"VM PID\", vm.id(), writer, false);\n\n        StringBuilder stringBuilder = new StringBuilder();\n        {\n            execute(vm, \"VM.command_line\", stringBuilder);\n            writeDumpHeadKeyValueTo(\n                    \"VM Command Line\",\n                    Logger.filterForbiddenToken(stringBuilder.toString()),\n                    writer,\n                    true\n            );\n        }\n        {\n            stringBuilder.setLength(0);\n            execute(vm, \"VM.version\", stringBuilder);\n            writeDumpHeadKeyValueTo(\"VM Version\", stringBuilder.toString(), writer, true);\n        }\n\n        writer.write(\"\\n\\n\");\n    }\n\n    public static void writeDumpHeadKeyValueTo(String key, String value, Writer writer, boolean multiline) throws IOException {\n        writer.write(key);\n        writer.write(':');\n        writer.write(' ');\n\n        if (multiline) {\n            writer.write('{');\n            writer.write('\\n');\n\n            int lineStart = 0;\n            int lineEnd = value.indexOf(\"\\n\", lineStart);\n\n            while (true) {\n                if (lineEnd == -1) {\n                    if (lineStart < value.length()) {\n                        writer.write(\"    \");\n                        writer.write(value, lineStart, value.length() - lineStart);\n                        writer.write('\\n');\n                    }\n                    break;\n                } else {\n                    writer.write(\"    \");\n                    writer.write(value, lineStart, lineEnd - lineStart);\n                    writer.write('\\n');\n                    lineStart = lineEnd + 1;\n                    lineEnd = value.indexOf(\"\\n\", lineStart);\n                }\n            }\n\n            writer.write('}');\n        } else {\n            writer.write(value);\n        }\n        writer.write('\\n');\n    }\n\n    private static void writeDumpBodyTo(VirtualMachine vm, Writer writer) throws IOException {\n        int vmVersion = -1;\n\n        try {\n            if (vm.getSystemProperties().get(\"java.version\") instanceof String javaVersion)\n                vmVersion = JavaInfo.parseVersion(javaVersion);\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get VM system properties\", e);\n        }\n\n        if (vmVersion >= 11)\n            execute(vm, \"Thread.print -e -l\", writer);\n        else\n            execute(vm, \"Thread.print -l\", writer);\n\n    }\n\n    private static VirtualMachine attachVM(String lvmid, Writer writer) throws IOException, InterruptedException {\n        for (int i = 0; i < RETRY_TIME; i++) {\n            try {\n                return VirtualMachine.attach(lvmid);\n            } catch (Throwable e) {\n                LOG.warning(\"An exception encountered while attaching vm \" + lvmid, e);\n                writer.write(StringUtils.getStackTrace(e));\n                writer.write('\\n');\n                Thread.sleep(3000);\n            }\n        }\n\n        String message = \"Cannot attach VM \" + lvmid;\n        writer.write(message);\n        throw new IOException(message);\n    }\n\n    private static void execute(VirtualMachine vm, String command, Appendable target) throws IOException {\n        try (Reader reader = new InputStreamReader(executeJVMCommand(vm, command), OperatingSystem.NATIVE_CHARSET)) {\n            char[] data = new char[256];\n            CharBuffer cb = CharBuffer.wrap(data);\n            int len;\n            while ((len = reader.read(data)) > 0) { // Directly read the data into a CharBuffer would cause useless array copy actions.\n                target.append(cb, 0, len);\n            }\n        } catch (Throwable throwable) {\n            LOG.warning(\"An exception encountered while executing jcmd \" + vm.id(), throwable);\n            target.append(StringUtils.getStackTrace(throwable));\n            target.append('\\n');\n        }\n    }\n\n    private static InputStream executeJVMCommand(VirtualMachine vm, String command) throws IOException, AttachNotSupportedException {\n        if (vm instanceof sun.tools.attach.HotSpotVirtualMachine) {\n            return ((sun.tools.attach.HotSpotVirtualMachine) vm).executeJCmd(command);\n        } else {\n            throw new AttachNotSupportedException(\"Unsupported VM implementation \" + vm.getClass().getName());\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\n/**\n *\n * @author huangyuhui\n */\npublic class GameException extends Exception {\n\n    public GameException() {\n    }\n\n    public GameException(String message) {\n        super(message);\n    }\n\n    public GameException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public GameException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\n@JsonSerializable\npublic record GameJavaVersion(String component, int majorVersion) {\n    public static final GameJavaVersion JAVA_25 = new GameJavaVersion(\"java-runtime-epsilon\", 25);\n    public static final GameJavaVersion JAVA_21 = new GameJavaVersion(\"java-runtime-delta\", 21);\n    public static final GameJavaVersion JAVA_17 = new GameJavaVersion(\"java-runtime-beta\", 17);\n    public static final GameJavaVersion JAVA_16 = new GameJavaVersion(\"java-runtime-alpha\", 16);\n    public static final GameJavaVersion JAVA_8 = new GameJavaVersion(\"jre-legacy\", 8);\n\n    public static final GameJavaVersion LATEST = JAVA_21;\n\n    public static GameJavaVersion getMinimumJavaVersion(GameVersionNumber gameVersion) {\n        if (gameVersion.compareTo(\"26.1\") >= 0)\n            return JAVA_25;\n        if (gameVersion.compareTo(\"1.20.5\") >= 0)\n            return JAVA_21;\n        if (gameVersion.compareTo(\"1.18\") >= 0)\n            return JAVA_17;\n        if (gameVersion.compareTo(\"1.17\") >= 0)\n            return JAVA_16;\n        if (gameVersion.compareTo(\"1.13\") >= 0)\n            return JAVA_8;\n        return null;\n    }\n\n    public static GameJavaVersion getCleanroomJavaVersion(String cleanroomVersion) {\n        VersionNumber versionNumber = VersionNumber.asVersion(StringUtils.removeSuffix(cleanroomVersion, \"-alpha\"));\n        if (versionNumber.compareTo(\"0.5.0\") >= 0)\n            return JAVA_25;\n        else\n            return JAVA_21;\n    }\n\n    public static GameJavaVersion get(int major) {\n        return switch (major) {\n            case 8 -> JAVA_8;\n            case 16 -> JAVA_16;\n            case 17 -> JAVA_17;\n            case 21 -> JAVA_21;\n            case 25 -> JAVA_25;\n            default -> null;\n        };\n    }\n\n    public static List<GameJavaVersion> getSupportedVersions(Platform platform) {\n        OperatingSystem operatingSystem = platform.getOperatingSystem();\n        Architecture architecture = platform.getArchitecture();\n        if (architecture == Architecture.X86) {\n            switch (operatingSystem) {\n                case WINDOWS:\n                    return Arrays.asList(JAVA_8, JAVA_16, JAVA_17);\n                case LINUX:\n                    return Collections.singletonList(JAVA_8);\n            }\n        } else if (architecture == Architecture.X86_64) {\n            switch (operatingSystem) {\n                case WINDOWS:\n                case LINUX:\n                case MACOS:\n                    return Arrays.asList(JAVA_8, JAVA_16, JAVA_17, JAVA_21, JAVA_25);\n            }\n        } else if (architecture == Architecture.ARM64) {\n            switch (operatingSystem) {\n                case WINDOWS:\n                case MACOS:\n                    return Arrays.asList(JAVA_17, JAVA_21, JAVA_25);\n            }\n        }\n\n        return Collections.emptyList();\n    }\n\n    @Override\n    public int hashCode() {\n        return majorVersion();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        return this == o || o instanceof GameJavaVersion that && this.majorVersion == that.majorVersion;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.platform.Platform;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collection;\nimport java.util.LinkedHashSet;\nimport java.util.Optional;\nimport java.util.Set;\n\n/**\n * Supports operations on versioning.\n * <p>\n * Note that game repository will not do any operations which need connection with Internet, if do,\n * see {@link org.jackhuang.hmcl.download.DependencyManager}\n *\n * @author huangyuhui\n */\npublic interface GameRepository extends VersionProvider {\n\n    /**\n     * Does the version of id exist?\n     *\n     * @param id the id of version\n     * @return true if the version exists\n     */\n    @Override\n    boolean hasVersion(String id);\n\n    /**\n     * Get the version\n     *\n     * @param id the id of version\n     * @return the version you want\n     * @throws VersionNotFoundException if no version is id.\n     */\n    @Override\n    Version getVersion(String id) throws VersionNotFoundException;\n\n    default Version getResolvedVersion(String id) throws VersionNotFoundException {\n        return getVersion(id).resolve(this);\n    }\n\n    default Version getResolvedPreservingPatchesVersion(String id) throws VersionNotFoundException {\n        return getVersion(id).resolvePreservingPatches(this);\n    }\n\n    /**\n     * How many version are there?\n     */\n    int getVersionCount();\n\n    /**\n     * Gets the collection of versions\n     *\n     * @return the collection of versions\n     */\n    Collection<Version> getVersions();\n\n    /**\n     * Load version list.\n     * <p>\n     * This method should be called before launching a version.\n     * A time-costly operation.\n     * You'd better execute this method in a new thread.\n     */\n    void refreshVersions();\n\n    default Task<Void> refreshVersionsAsync() {\n        return Task.runAsync(this::refreshVersions);\n    }\n\n    /**\n     * Gets the root folder of specific version.\n     * The root folders the versions must be unique.\n     * For example, .minecraft/versions/&lt;version name&gt;/.\n     */\n    Path getVersionRoot(String id);\n\n    /**\n     * Gets the current running directory of the given version for game.\n     *\n     * @param id the version id\n     */\n    Path getRunDirectory(String id);\n\n    Path getLibrariesDirectory(Version version);\n\n    /**\n     * Get the library file in disk.\n     * This method allows versions and libraries that are not loaded by this game repository.\n     *\n     * @param version the reference of game version\n     * @param lib the library, {@link Version#getLibraries()}\n     * @return the library file\n     */\n    Path getLibraryFile(Version version, Library lib);\n\n    /**\n     * Get the directory that native libraries will be unzipped to.\n     * <p>\n     * You'd better return a unique directory.\n     * Or if it returns a temporary directory, {@link org.jackhuang.hmcl.launch.Launcher#makeLaunchScript} will fail.\n     * If you do want to return a temporary directory, make {@link org.jackhuang.hmcl.launch.Launcher#makeLaunchScript}\n     * always fail({@code UnsupportedOperationException}) and not to use it.\n     *\n     * @param id version id\n     * @param platform the platform of native libraries\n     * @return the native directory\n     */\n    Path getNativeDirectory(String id, Platform platform);\n\n    /// Get the directory for placing mod files.\n    ///\n    /// @param id instance id\n    /// @return the mods directory\n    Path getModsDirectory(String id);\n\n    /**\n     * Get minecraft jar\n     *\n     * @param version resolvedVersion\n     * @return the minecraft jar\n     */\n    Path getVersionJar(Version version);\n\n    /**\n     * Detect game version.\n     * <p>\n     * This method is time-consuming, but the result will be cached.\n     * Consider running this job in IO scheduler.\n     *\n     * @param version version\n     * @return game version, or empty if an error occurred in detection.\n     */\n    Optional<String> getGameVersion(Version version);\n\n    /**\n     * Detect game version.\n     * <p>\n     * This method is time-consuming, but the result will be cached.\n     * Consider running this job in IO scheduler.\n     *\n     * @param versionId id of version\n     * @return game version, or empty if an error occurred in detection.\n     */\n    default Optional<String> getGameVersion(String versionId) throws VersionNotFoundException {\n        return getGameVersion(getVersion(versionId));\n    }\n\n    /**\n     * Get minecraft jar\n     *\n     * @param version version id\n     * @return the minecraft jar\n     */\n    default Path getVersionJar(String version) throws VersionNotFoundException {\n        return getVersionJar(getVersion(version).resolve(this));\n    }\n\n    /**\n     * Rename given version to new name.\n     *\n     * @param from The id of original version\n     * @param to The new id of the version\n     * @throws UnsupportedOperationException if this game repository does not support renaming a version\n     * @return true if the operation is done successfully, false if version `from` not found, version json is malformed or I/O errors occurred.\n     */\n    boolean renameVersion(String from, String to);\n\n    /**\n     * Get actual asset directory.\n     * Will reconstruct assets or do some blocking tasks if necessary.\n     * You'd better create a new thread to invoke this method.\n     *\n     * @param version the id of specific version that is relevant to {@code assetId}\n     * @param assetId the asset id, you can find it in {@link AssetIndexInfo#getId()} {@link Version#getAssetIndex()}\n     * @return the actual asset directory\n     */\n    Path getActualAssetDirectory(String version, String assetId);\n\n    /**\n     * Get the asset directory according to the asset id.\n     *\n     * @param version the id of specific version that is relevant to {@code assetId}\n     * @param assetId the asset id, you can find it in {@link AssetIndexInfo#getId()} {@link Version#getAssetIndex()}\n     * @return the asset directory\n     */\n    Path getAssetDirectory(String version, String assetId);\n\n    /**\n     * Get the file that given asset object refers to\n     *\n     * @param version the id of specific version that is relevant to {@code assetId}\n     * @param assetId the asset id, you can find it in {@link AssetIndexInfo#getId()} {@link Version#getAssetIndex()}\n     * @param name the asset object name, you can find it in keys of {@link AssetIndex#getObjects()}\n     * @throws java.io.IOException if I/O operation fails.\n     * @return the file that given asset object refers to\n     */\n    Optional<Path> getAssetObject(String version, String assetId, String name) throws IOException;\n\n    /**\n     * Get the file that given asset object refers to\n     *\n     * @param version the id of specific version that is relevant to {@code assetId}\n     * @param assetId the asset id, you can find it in {@link AssetIndexInfo#getId()} {@link Version#getAssetIndex()}\n     * @param obj the asset object, you can find it in {@link AssetIndex#getObjects()}\n     * @return the file that given asset object refers to\n     */\n    Path getAssetObject(String version, String assetId, AssetObject obj);\n\n    /**\n     * Get asset index that assetId represents\n     *\n     * @param version the id of specific version that is relevant to {@code assetId}\n     * @param assetId the asset id, you can find it in {@link AssetIndexInfo#getId()} {@link Version#getAssetIndex()}\n     * @return the asset index\n     */\n    AssetIndex getAssetIndex(String version, String assetId) throws IOException;\n\n    /**\n     * Get the asset_index.json which includes asset objects information.\n     *\n     * @param version the id of specific version that is relevant to {@code assetId}\n     * @param assetId the asset id, you can find it in {@link AssetIndexInfo#getId()} {@link Version#getAssetIndex()}\n     */\n    Path getIndexFile(String version, String assetId);\n\n    /**\n     * Get logging object\n     *\n     * @param version the id of specific version that is relevant to {@code assetId}\n     * @param assetId the asset id, you can find it in {@link AssetIndexInfo#getId()} {@link Version#getAssetIndex()}\n     * @param loggingInfo the logging info\n     * @return the file that loggingInfo refers to\n     */\n    Path getLoggingObject(String version, String assetId, LoggingInfo loggingInfo);\n\n    default Set<String> getClasspath(Version version) {\n        Set<String> classpath = new LinkedHashSet<>();\n        for (Library library : version.getLibraries())\n            if (library.appliesToCurrentEnvironment() && !library.isNative()) {\n                Path f = getLibraryFile(version, library);\n                if (Files.isRegularFile(f))\n                    classpath.add(FileUtils.getAbsolutePath(f));\n            }\n        return classpath;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jenkinsci.constant_pool_scanner.ConstantPool;\nimport org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;\nimport org.jenkinsci.constant_pool_scanner.ConstantType;\nimport org.jenkinsci.constant_pool_scanner.StringConstant;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.regex.Pattern;\nimport java.util.stream.StreamSupport;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huangyuhui\n */\nfinal class GameVersion {\n    private GameVersion() {\n    }\n\n    // For Minecraft 1.0 rc versions and versions earlier than Alpha 1.0.6,\n    // it is difficult to obtain the game version from the JAR.\n    // For these versions, we get the version number based on their SHA-1 hash.\n    private static final Map<String, String> KNOWN_VERSIONS = Map.<String, String>ofEntries(\n            Map.entry(\"4df7880d26414b400640f0b8e54344df2b66c51a\", \"1.0.0-rc1\"),\n            Map.entry(\"9e04e60eef3fb4657b406dcb3ad5e3a675ecf6af\", \"1.0.0-rc2-1\"),\n            Map.entry(\"6a6b67d34149afc47cf9608b3967582639097df9\", \"1.0.0-rc2-2\"),\n            Map.entry(\"6e54fbe19b7797f3e3a2cb9feb5da41a40926db8\", \"1.0.0-rc2-3\"),\n            Map.entry(\"fe189e91a3e7166d46fad8ce53ba0ce34b4c5f97\", \"a1.0.5\"),\n            Map.entry(\"73f569bf5556580979606049204835ae1a54f04d\", \"a1.0.5_01\"),\n            Map.entry(\"e5838277b3bb193e58408713f1fc6e005c5f3c0c\", \"a1.0.4\"),\n            Map.entry(\"31e9736457ef3e0bfea69c720137a1bd8ba7caae\", \"a1.0.3\"),\n            Map.entry(\"4f9ce27cfc6394af533fde11a90b6a233dd908bf\", \"a1.0.2_02\"),\n            Map.entry(\"7457e763ad81eee1e63628d628647f53806dab7c\", \"a1.0.2_01\"),\n            Map.entry(\"02c57723da508aab36455782904bfd6e3e1023e6\", \"a1.0.1_01\"),\n            Map.entry(\"88c1931650b0e5be349017e124a7785a745111e9\", \"inf-20100630-2\"),\n            Map.entry(\"121fff417950ad72005ca4d882ca6269e874547b\", \"inf-20100630-1\"),\n            Map.entry(\"eb50bce3cb542488b3039aa0f4c3c0ec7595ab24\", \"inf-20100629\"),\n            Map.entry(\"4d31259a71c5886b987b9eca6034ca5552079eed\", \"inf-20100627\"),\n            Map.entry(\"d9fc6416186e1454945ab135f37c730c7d2c1adc\", \"inf-20100625-2\"),\n            Map.entry(\"990b531a26ae8e475032915938763c12cdb2dcf9\", \"inf-20100625-1\"),\n            Map.entry(\"644c050e846035e06a6637bffa2afee1e5769c8c\", \"inf-20100624\"),\n            Map.entry(\"d3eb1dce5a6c86dd0d6483ba56223276dcf32c30\", \"inf-20100617-3\"),\n            Map.entry(\"06641eca013fe5032a5f1a9d1289599f0970a735\", \"inf-20100617-2\"),\n            Map.entry(\"89eab2c1a353707cc00f074dffba9cb7a4f5e304\", \"inf-20100618\"),\n            Map.entry(\"47518a623da068728b50b4b53436dea4621b7bf8\", \"inf-20100615\"),\n            Map.entry(\"421318a554f17463a56a271d08e9597941d066d9\", \"inf-20100611\"),\n            Map.entry(\"a9efb36c142bf835d3d410150856dc9ceeaae81b\", \"inf-20100608\"),\n            Map.entry(\"7bbf38d53dd47753af266be4e1c5865342a26974\", \"inf-20100607\"),\n            Map.entry(\"27010a5137abd2c8d8df85e99c14f5406ec197b3\", \"inf-20100420\"),\n            Map.entry(\"a91c9d8e0184eda610213b1a5425fbfa078cb191\", \"inf-20100415\"),\n            Map.entry(\"86dd3b1558352b38d4d15c7ec51b9131bd7aed4b\", \"inf-20100414\"),\n            Map.entry(\"7b39167f14d9f0ce7af6819433856be7b82d2412\", \"inf-20100413\"),\n            Map.entry(\"a74c8ee1ecd57999e242952697bbde6cc0904f99\", \"inf-20100330\"),\n            Map.entry(\"47b1b32430a211520993552ba0a5e00c1af44724\", \"inf-20100327\"),\n            Map.entry(\"99da3b55b4db292faca59824e3ec76bf53a7eae6\", \"inf-20100325\"),\n            Map.entry(\"2c89471a81858d37ab0b01e042131878b6853b38\", \"inf-20100321\"),\n            Map.entry(\"7f1c48fc6d61dd0cbfd41b84fb0b0a22944aa02c\", \"inf-20100320\"),\n            Map.entry(\"ad7b3cd706098ac05c7dba61dacb40bafcd47db6\", \"inf-20100316\"),\n            Map.entry(\"65a00a10001978538ab8eef1a2533f47d4ecbe23\", \"inf-20100313\"),\n            Map.entry(\"801ce486bb7fd1b43a56bc5d226dfb1370c08678\", \"in-20100223\"),\n            Map.entry(\"af3d7f95ca75e130a9c5c74be0a9c09600a15686\", \"in-20100219\"),\n            Map.entry(\"2ba9e9a2bdac1e8af6a36819e9bb01375889b078\", \"in-20100218\"),\n            Map.entry(\"dcbe38d0e4ac2caec7e5c0f9ebcb0ec9179dcdff\", \"in-20100214-2\"),\n            Map.entry(\"e6bb9306dab60626ba6ffd24fc9742fd272f5acb\", \"in-20100214-1\"),\n            Map.entry(\"f1ae7e37e52b33753b35402e581eb65dc5bba877\", \"in-20100212-2\"),\n            Map.entry(\"5275aaf68d6388ef8278b575e95ae83ad641fe3e\", \"in-20100212-1\"),\n            Map.entry(\"fa8525be5612d00f6001be7d4cdb764b66e88f9d\", \"in-20100207-2\"),\n            Map.entry(\"054e3d3f4e2c0463f80aa323767e018e6c23c1cd\", \"in-20100207-1\"),\n            Map.entry(\"049b002cdd164e5c5e9b78780b12ab4dc2e80120\", \"in-20100206-2103\"),\n            Map.entry(\"b2abb22e001abf01ca7555ced5d6024350955d70\", \"in-20100203\"),\n            Map.entry(\"38d4df5132077ac60f0bdf67564f5fff4ee309e2\", \"in-20100201-3\"),\n            Map.entry(\"1f2ca31fc761207bcabc07f0cf4b725a9a3286e4\", \"in-20100201-2\"),\n            Map.entry(\"c871e820d5356b88b3ad854789162f8b9227c80c\", \"in-20100130\"),\n            Map.entry(\"03b858d31c090b629f406aa1d548ac7b25341f02\", \"in-20100129\"),\n            Map.entry(\"3f2418f906d438b26ae6c9dbbadf3942f5845504\", \"in-20100128-2304\"),\n            Map.entry(\"baf0c7b1e231f0984e1c35e27f38eea2743f8ee2\", \"in-20100125-2\"),\n            Map.entry(\"2cd03bcfc26c95bcf31b5d5e1d4dda7dc071ca6a\", \"in-20100125-1\"),\n            Map.entry(\"a0b58472ebf12f7e562b09b8a51dcb4cacc57005\", \"in-20100111-1\"),\n            Map.entry(\"38958105bfe0f7064b3c4996905cb6978d4d4b0b\", \"in-20100105\"),\n            Map.entry(\"3161652a6835c61817fda6fe13245c57528ed418\", \"in-20091231-2\"),\n            Map.entry(\"94ee2e7aa7d093fa8dfc684baa8bd8afe002580f\", \"in-20091223-2\"),\n            Map.entry(\"54622801f5ef1bcc1549a842c5b04cb5d5583005\", \"c0.30_01c\"),\n            Map.entry(\"51bc951530207b538596941a6f353f87dfc24233\", \"c0.30-2\"),\n            Map.entry(\"619ea74c6d0ae5c0125d1e31e299105e100139ab\", \"c0.30-1\"),\n            Map.entry(\"6a6f92b691f9d6b7ca991a6db8a1cfc6e319815b\", \"c0.29_02\"),\n            Map.entry(\"bb5e7f1c231f45fd630f30a75570937c103f5b55\", \"c0.29_01\"),\n            Map.entry(\"7ccde270abacd028d3618be99537ccf7071a605b\", \"c0.28_01\"),\n            Map.entry(\"aff4060249dd6152012218e120d7aad5e758de83\", \"c0.27_st\"),\n            Map.entry(\"349630cb1b895335c38b499f84dc28d9f8a38513\", \"c0.25_05_st\"),\n            Map.entry(\"0b387d2087edda894fae4af00de5ac202dbffa7c\", \"c0.24_st_03\"),\n            Map.entry(\"85159cea8663ed720be88ca0ee008a5830b0829a\", \"c0.0.22a_05\"),\n            Map.entry(\"83b6483feb88136b6b4662b553d8f80f5f88efa5\", \"c0.0.21a\"),\n            Map.entry(\"c2f8fddde4691d7c567c0c049ad4d03eb6b9e61c\", \"c0.0.20a_01\"),\n            Map.entry(\"e2b248f1013933af9f801729418409fb7198de1b\", \"c0.0.19a_06-2\"),\n            Map.entry(\"a78468abd491d6c661c000f60d6270a692ba4710\", \"c0.0.18a_02\"),\n            Map.entry(\"ca840460a6589552c9d1978ca121bf3e7c16a010\", \"c0.0.17a\"),\n            Map.entry(\"741eb3f84097fdcc0327230e018a0f8cd39addfb\", \"c0.0.16a_02\"),\n            Map.entry(\"936d575b1ab1a04a341ad43d76e441e88d2cd987\", \"c0.0.13a\"),\n            Map.entry(\"e8aa74a5bee547097375d44ffb2e407b2ea8ee4d\", \"c0.0.14a_08\"),\n            Map.entry(\"b9884f960f2b28a36b34db3447963f1ff4058aa4\", \"c0.0.23a_01\"),\n            Map.entry(\"7ba9e63aec8a15a99ecd47900c848cdce8a51a03\", \"c0.0.13a_03\"),\n            Map.entry(\"501ea8a6274faffe0144d3b24ed56797ce0765ff\", \"c0.0.12a_03\"),\n            Map.entry(\"3a799f179b6dcac5f3a46846d687ebbd95856984\", \"c0.0.11a\"),\n            Map.entry(\"6323bd14ed7f83852e17ebc8ec418e55c97ddfe4\", \"rd-161348\"),\n            Map.entry(\"b100be8097195b6c9112046dc6a80d326c8df839\", \"rd-160052\"),\n            Map.entry(\"12dace5a458617d3f90337a7ebde86c0593a6899\", \"rd-132328\"),\n            Map.entry(\"393e8d4b4d708587e2accd7c5221db65365e1075\", \"rd-132211\")\n    );\n\n    private static Optional<String> getVersionFromJson(InputStream versionJson) {\n        try {\n            Map<?, ?> version = JsonUtils.fromNonNullJsonFully(versionJson, Map.class);\n            String id = (String) version.get(\"id\");\n            if (id != null && id.contains(\" / \"))\n                id = id.substring(0, id.indexOf(\" / \"));\n            return Optional.ofNullable(id);\n        } catch (IOException | JsonParseException | ClassCastException e) {\n            LOG.warning(\"Failed to parse version.json\", e);\n            return Optional.empty();\n        }\n    }\n\n    private static Optional<String> getVersionOfClassMinecraft(InputStream bytecode) throws IOException {\n        final String constantPrefix = \"Minecraft Minecraft \";\n        ConstantPool pool = ConstantPoolScanner.parse(bytecode, ConstantType.STRING);\n        for (StringConstant constant : pool.list(StringConstant.class)) {\n            String value = constant.get();\n            if (value.startsWith(constantPrefix)) {\n                return Optional.of(value.substring(constantPrefix.length()));\n            }\n        }\n        return Optional.empty();\n    }\n\n    private static Optional<String> getVersionFromClassMinecraftServer(InputStream bytecode) throws IOException {\n        ConstantPool pool = ConstantPoolScanner.parse(bytecode, ConstantType.STRING);\n\n        List<String> list = StreamSupport.stream(pool.list(StringConstant.class).spliterator(), false)\n                .map(StringConstant::get)\n                .toList();\n\n        int idx = -1;\n\n        for (int i = 0; i < list.size(); ++i)\n            if (list.get(i).startsWith(\"Can't keep up!\")) {\n                idx = i;\n                break;\n            }\n\n        Pattern pattern = Pattern.compile(\".*[0-9].*\");\n        for (int i = idx - 1; i >= 0; --i)\n            if (pattern.matcher(list.get(i)).matches())\n                return Optional.of(list.get(i));\n\n        return Optional.empty();\n    }\n\n    public static Optional<String> minecraftVersion(Path file) {\n        if (file == null || !Files.isRegularFile(file))\n            return Optional.empty();\n\n        try (var gameJar = new ZipFile(file.toFile())) {\n            ZipEntry versionJson = gameJar.getEntry(\"version.json\");\n            if (versionJson != null) {\n                Optional<String> result = getVersionFromJson(gameJar.getInputStream(versionJson));\n                if (result.isPresent())\n                    return result;\n            }\n\n            ZipEntry minecraft = gameJar.getEntry(\"net/minecraft/client/Minecraft.class\");\n            if (minecraft != null) {\n                try (InputStream is = gameJar.getInputStream(minecraft)) {\n                    Optional<String> result = getVersionOfClassMinecraft(is);\n                    if (result.isPresent()) {\n                        String version = result.get();\n                        // For Minecraft 1.0 rc1/rc2-1/rc2-2, this value is \"RC1\"\n                        // For Minecraft 1.0 rc2-3, this value is \"RC2\"\n                        if (!version.equals(\"RC1\") && !version.equals(\"RC2\")) {\n                            if (version.startsWith(\"Beta \")) {\n                                result = Optional.of(\"b\" + version.substring(\"Beta \".length()));\n                            } else if (version.startsWith(\"Alpha v\")) {\n                                result = Optional.of(\"a\" + version.substring(\"Alpha v\".length()));\n                            }\n                            return result;\n                        }\n                    }\n                }\n            }\n\n            ZipEntry minecraftServer = gameJar.getEntry(\"net/minecraft/server/MinecraftServer.class\");\n            if (minecraftServer != null) {\n                try (InputStream is = gameJar.getInputStream(minecraftServer)) {\n                    return getVersionFromClassMinecraftServer(is);\n                }\n            }\n        } catch (IOException ignored) {\n        }\n\n        try {\n            String digest = DigestUtils.digestToString(\"SHA-1\", file);\n            return Optional.ofNullable(KNOWN_VERSIONS.get(digest));\n        } catch (IOException e) {\n            return Optional.empty();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/IdDownloadInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic class IdDownloadInfo extends DownloadInfo {\n\n    @SerializedName(\"id\")\n    private final String id;\n\n    public IdDownloadInfo() {\n        this(\"\", \"\");\n    }\n\n    public IdDownloadInfo(String id, String url) {\n        this(id, url, null);\n    }\n\n    public IdDownloadInfo(String id, String url, String sha1) {\n        this(id, url, sha1, 0);\n    }\n\n    public IdDownloadInfo(String id, String url, String sha1, int size) {\n        super(url, sha1, size);\n        this.id = id;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        super.validate();\n\n        if (StringUtils.isBlank(id))\n            throw new JsonParseException(\"IdDownloadInfo id can not be null\");\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jackhuang.hmcl.util.versioning.VersionRange;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\nimport java.util.Objects;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LAUNCH_WRAPPER_MAIN;\n\npublic enum JavaVersionConstraint {\n    VANILLA(true, VersionRange.all(), VersionRange.all()) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            // Give priority to the Java version requirements specified in the version JSON\n            return version == null || version.getJavaVersion() == null;\n        }\n\n        @Override\n        public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {\n            GameJavaVersion minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersionNumber);\n            return minimumJavaVersion == null || java.getParsedVersion() >= minimumJavaVersion.majorVersion();\n        }\n    },\n    // Minecraft with suggested java version recorded in game json is restrictedly constrained.\n    GAME_JSON(true, VersionRange.all(), VersionRange.all()) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            if (version == null) return false;\n            // We only checks for 1.7.10 and above, since 1.7.2 with Forge can only run on Java 7, but it is recorded Java 8 in game json, which is not correct.\n            return gameVersionNumber.compareTo(\"1.7.10\") >= 0 && version.getJavaVersion() != null;\n        }\n\n        @Override\n        public VersionRange<VersionNumber> getJavaVersionRange(Version version, LibraryAnalyzer analyzer) {\n            String javaVersion;\n            if (Objects.requireNonNull(version.getJavaVersion()).majorVersion() >= 9) {\n                javaVersion = \"\" + version.getJavaVersion().majorVersion();\n            } else {\n                javaVersion = \"1.\" + version.getJavaVersion().majorVersion();\n            }\n            return VersionNumber.atLeast(javaVersion);\n        }\n    },\n    // Minecraft<=1.7.2+Forge requires Java<=7, But LegacyModFixer may fix that problem. So only suggest user using Java 7.\n    MODDED_JAVA_7(false, GameVersionNumber.atMost(\"1.7.2\"), VersionNumber.atMost(\"1.7.999\")) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)\n                    && super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer);\n        }\n    },\n    MODDED_JAVA_8(false, GameVersionNumber.between(\"1.7.10\", \"1.16.999\"), VersionNumber.between(\"1.8\", \"1.8.999\")) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)\n                    && super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer);\n        }\n    },\n    MODDED_JAVA_16(false, GameVersionNumber.between(\"1.17\", \"1.17.999\"), VersionNumber.between(\"16\", \"16.999\")) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)\n                    && super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer);\n        }\n    },\n    MODDED_JAVA_17(false, GameVersionNumber.between(\"1.18\", \"1.20.4\"), VersionNumber.between(\"17\", \"17.999\")) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)\n                    && super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer);\n        }\n    },\n    MODDED_JAVA_21(false, GameVersionNumber.atLeast(\"1.20.5\"), VersionNumber.between(\"21\", \"21.999\")) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)\n                    && super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer);\n        }\n    },\n    CLEANROOM(true, GameVersionNumber.between(\"1.12.2\", \"1.12.999\"), VersionRange.all()) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)\n                    && super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer);\n        }\n\n        @Override\n        public VersionRange<VersionNumber> getJavaVersionRange(Version version, LibraryAnalyzer analyzer) {\n            if (analyzer == null || !analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM))\n                return VersionRange.all();\n\n            String cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM).orElse(\"\");\n            if (cleanroomVersion.isEmpty())\n                return VersionRange.all();\n            else\n                return VersionNumber.atLeast(\n                        String.valueOf(GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion).majorVersion())\n                );\n        }\n    },\n    // LaunchWrapper<=1.12 will crash because LaunchWrapper assumes the system class loader is an instance of URLClassLoader (Java 8)\n    LAUNCH_WRAPPER(true, GameVersionNumber.atMost(\"1.12.999\"), VersionNumber.atMost(\"1.8.999\")) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            if (version == null) return false;\n            return super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer) && LAUNCH_WRAPPER_MAIN.equals(version.getMainClass()) &&\n                    version.getLibraries().stream()\n                            .filter(library -> \"launchwrapper\".equals(library.getArtifactId()))\n                            .anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion(\"1.13\")) < 0);\n        }\n    },\n    // Minecraft>=1.13 may crash when generating world on Java [1.8,1.8.0_51)\n    VANILLA_JAVA_8_51(false, GameVersionNumber.atLeast(\"1.13\"), VersionNumber.atLeast(\"1.8.0_51\")),\n    // On Linux, JDK 9+ cannot launch Minecraft<=1.12.2, since JDK 9+ does not accept loading native library built in different arch.\n    // For example, JDK 9+ 64-bit cannot load 32-bit lwjgl native library.\n    VANILLA_LINUX_JAVA_8(true, GameVersionNumber.atMost(\"1.12.999\"), VersionNumber.atMost(\"1.8.999\")) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            return OperatingSystem.CURRENT_OS == OperatingSystem.LINUX\n                    && Architecture.SYSTEM_ARCH == Architecture.X86_64\n                    && (java == null || java.getArchitecture() == Architecture.X86_64)\n                    && (analyzer == null || !analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM));\n        }\n\n        @Override\n        public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {\n            return java.getArchitecture() != Architecture.X86_64 || super.checkJava(gameVersionNumber, version, java, analyzer);\n        }\n    },\n    // Minecraft currently does not provide official support for architectures other than x86 and x86-64.\n    VANILLA_X86(false, VersionRange.all(), VersionRange.all()) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            if (java == null || java.getArchitecture() != Architecture.ARM64)\n                return false;\n\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)\n                return gameVersionNumber.compareTo(\"1.6\") < 0;\n\n            return false;\n        }\n\n        @Override\n        public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {\n            return java.getArchitecture().isX86();\n        }\n    },\n    // Minecraft 1.16+Forge with crash because JDK-8273826\n    MODLAUNCHER_8(false, GameVersionNumber.between(\"1.16.3\", \"1.17.1\"), VersionRange.all()) {\n        @Override\n        protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                               @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n            if (version == null || java == null || analyzer == null || !super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer))\n                return false;\n            VersionNumber forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE)\n                    .map(VersionNumber::asVersion)\n                    .orElse(null);\n            if (forgePatchVersion == null) {\n                return false;\n            }\n            switch (gameVersionNumber.toString()) {\n                case \"1.16.3\":\n                    return forgePatchVersion.compareTo(VersionNumber.asVersion(\"34.1.27\")) >= 0;\n                case \"1.16.4\":\n                    return true;\n                case \"1.16.5\":\n                    return forgePatchVersion.compareTo(VersionNumber.asVersion(\"36.2.23\")) <= 0;\n                case \"1.17.1\":\n                    return VersionNumber.between(\"37.0.60\", \"37.0.75\").contains(forgePatchVersion);\n                default:\n                    return false;\n            }\n        }\n\n        @Override\n        public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {\n            int parsedJavaVersion = java.getParsedVersion();\n            if (parsedJavaVersion > 17) {\n                return false;\n            } else if (parsedJavaVersion == 8) {\n                return java.getVersionNumber().compareTo(VersionNumber.asVersion(\"1.8.0_321\")) < 0;\n            } else if (parsedJavaVersion == 11) {\n                return java.getVersionNumber().compareTo(VersionNumber.asVersion(\"11.0.14\")) < 0;\n            } else if (parsedJavaVersion == 15) {\n                return java.getVersionNumber().compareTo(VersionNumber.asVersion(\"15.0.6\")) < 0;\n            } else if (parsedJavaVersion == 17) {\n                return java.getVersionNumber().compareTo(VersionNumber.asVersion(\"17.0.2\")) < 0;\n            } else {\n                return true;\n            }\n        }\n    };\n\n    private final boolean isMandatory;\n    private final VersionRange<GameVersionNumber> gameVersionRange;\n    private final VersionRange<VersionNumber> javaVersionRange;\n\n    JavaVersionConstraint(boolean isMandatory, VersionRange<GameVersionNumber> gameVersionRange, VersionRange<VersionNumber> javaVersionRange) {\n        this.isMandatory = isMandatory;\n        this.gameVersionRange = gameVersionRange;\n        this.javaVersionRange = javaVersionRange;\n    }\n\n    public boolean isMandatory() {\n        return isMandatory;\n    }\n\n    public VersionRange<GameVersionNumber> getGameVersionRange() {\n        return gameVersionRange;\n    }\n\n    public VersionRange<VersionNumber> getJavaVersionRange(Version version, LibraryAnalyzer analyzer) {\n        return javaVersionRange;\n    }\n\n    public final boolean appliesToVersion(@Nullable GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                          @Nullable JavaRuntime java, LibraryAnalyzer analyzer) {\n        return gameVersionRange.contains(gameVersionNumber)\n                && appliesToVersionImpl(gameVersionNumber, version, java, analyzer);\n    }\n\n    protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,\n                                           @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {\n        GameJavaVersion gameJavaVersion;\n        if (version == null || (gameJavaVersion = version.getJavaVersion()) == null) {\n            return true;\n        }\n\n        String versionNumber = gameJavaVersion.majorVersion() >= 9\n                ? String.valueOf(gameJavaVersion.majorVersion())\n                : \"1.\" + gameJavaVersion.majorVersion();\n\n        VersionRange<VersionNumber> range = getJavaVersionRange(version, analyzer);\n        VersionNumber maximum = range.getMaximum();\n\n        return maximum == null || maximum.compareTo(versionNumber) >= 0;\n    }\n\n    @SuppressWarnings(\"BooleanMethodIsAlwaysInverted\")\n    public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {\n        return getJavaVersionRange(version, analyzer).contains(java.getVersionNumber());\n    }\n\n    public static final List<JavaVersionConstraint> ALL = Lang.immutableListOf(values());\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.java.JavaRuntime;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.Serializable;\nimport java.nio.file.Path;\nimport java.util.*;\n\n/**\n *\n * @author huangyuhui\n */\npublic class LaunchOptions implements Serializable {\n\n    private Path gameDir;\n    private JavaRuntime java;\n    private String versionName;\n    private String versionType;\n    private String profileName;\n    private final List<String> gameArguments = new ArrayList<>();\n    private final List<String> overrideJavaArguments = new ArrayList<>();\n    private final List<String> javaArguments = new ArrayList<>();\n    private final List<String> javaAgents = new ArrayList<>(0);\n    private final Map<String, String> environmentVariables = new LinkedHashMap<>();\n    private Integer minMemory;\n    private Integer maxMemory;\n    private Integer metaspace;\n    private Integer width;\n    private Integer height;\n    private boolean fullscreen;\n    private QuickPlayOption quickPlayOption;\n    private String wrapper;\n    private ProxyOption proxyOption;\n    private boolean noGeneratedJVMArgs;\n    private boolean noGeneratedOptimizingJVMArgs;\n    private String preLaunchCommand;\n    private String postExitCommand;\n    private NativesDirectoryType nativesDirType;\n    private String nativesDir;\n    private ProcessPriority processPriority = ProcessPriority.NORMAL;\n    private Renderer renderer = Renderer.DEFAULT;\n    private boolean useNativeGLFW;\n    private boolean useNativeOpenAL;\n    private boolean enableDebugLogOutput;\n    private boolean daemon;\n\n    /**\n     * The game directory\n     */\n    public Path getGameDir() {\n        return gameDir;\n    }\n\n    /**\n     * The Java Environment that Minecraft runs on.\n     */\n    public JavaRuntime getJava() {\n        return java;\n    }\n\n    /**\n     * Will shown in the left bottom corner of the main menu of Minecraft.\n     * null if use the id of launch version.\n     */\n    public String getVersionName() {\n        return versionName;\n    }\n\n    /**\n     * Will shown in the left bottom corner of the main menu of Minecraft.\n     * null if use Version.versionType.\n     */\n    public String getVersionType() {\n        return versionType;\n    }\n\n    /**\n     * Don't know what the hell this is.\n     */\n    public String getProfileName() {\n        return profileName;\n    }\n\n    /**\n     * User custom additional minecraft command line arguments.\n     */\n    @NotNull\n    public List<String> getGameArguments() {\n        return Collections.unmodifiableList(gameArguments);\n    }\n\n    /**\n     * The highest priority JVM arguments (overrides the version setting)\n     */\n    @NotNull\n    public List<String> getOverrideJavaArguments() {\n        return Collections.unmodifiableList(overrideJavaArguments);\n    }\n\n    /**\n     * User custom additional java virtual machine command line arguments.\n     */\n    @NotNull\n    public List<String> getJavaArguments() {\n        return Collections.unmodifiableList(javaArguments);\n    }\n\n    @NotNull\n    public List<String> getJavaAgents() {\n        return Collections.unmodifiableList(javaAgents);\n    }\n\n    public Map<String, String> getEnvironmentVariables() {\n        return environmentVariables;\n    }\n\n    /**\n     * The minimum memory that the JVM can allocate.\n     */\n    public Integer getMinMemory() {\n        return minMemory;\n    }\n\n    /**\n     * The maximum memory that the JVM can allocate.\n     */\n    public Integer getMaxMemory() {\n        return maxMemory;\n    }\n\n    /**\n     * The maximum metaspace memory that the JVM can allocate.\n     * For Java 7 -XX:PermSize and Java 8 -XX:MetaspaceSize\n     * Containing class instances.\n     */\n    public Integer getMetaspace() {\n        return metaspace;\n    }\n\n    /**\n     * The initial game window width\n     */\n    public Integer getWidth() {\n        return width;\n    }\n\n    /**\n     * The initial game window height\n     */\n    public Integer getHeight() {\n        return height;\n    }\n\n    /**\n     * Is initial game window fullscreen.\n     */\n    public boolean isFullscreen() {\n        return fullscreen;\n    }\n\n    /// The quick play option.\n    ///\n    /// @see <a href=\"https://minecraft.wiki/w/Quick_Play\">Quick Play - Minecraft Wiki</a>\n    public QuickPlayOption getQuickPlayOption() {\n        return quickPlayOption;\n    }\n\n    /**\n     * i.e. optirun\n     */\n    public String getWrapper() {\n        return wrapper;\n    }\n\n    public ProxyOption getProxyOption() {\n        return proxyOption;\n    }\n\n    /**\n     * Prevent game launcher from generating default JVM arguments like max memory.\n     */\n    public boolean isNoGeneratedJVMArgs() {\n        return noGeneratedJVMArgs;\n    }\n\n    /**\n     * Prevent game launcher from generating optimizing JVM arguments.\n     */\n    public boolean isNoGeneratedOptimizingJVMArgs() {\n        return noGeneratedOptimizingJVMArgs;\n    }\n\n    /**\n     * Command called before game launches.\n     */\n    public String getPreLaunchCommand() {\n        return preLaunchCommand;\n    }\n\n    /**\n     * Command called after game exits.\n     */\n    public String getPostExitCommand() {\n        return postExitCommand;\n    }\n\n    /**\n     * 0 - ./minecraft/versions/&lt;version&gt;/natives\n     * 1 - custom natives directory\n     */\n    public NativesDirectoryType getNativesDirType() {\n        return nativesDirType;\n    }\n\n    /**\n     * Path to the natives directory, optional\n     */\n    public String getNativesDir() {\n        return nativesDir;\n    }\n\n    /**\n     * Process priority\n     */\n    public ProcessPriority getProcessPriority() {\n        return processPriority;\n    }\n\n    public Renderer getRenderer() {\n        return renderer;\n    }\n\n    public boolean isUseNativeGLFW() {\n        return useNativeGLFW;\n    }\n\n    public boolean isUseNativeOpenAL() {\n        return useNativeOpenAL;\n    }\n\n    public boolean isEnableDebugLogOutput() {\n        return enableDebugLogOutput;\n    }\n\n    /**\n     * Will launcher keeps alive after game launched or not.\n     */\n    public boolean isDaemon() {\n        return daemon;\n    }\n\n    public static final class Builder {\n\n        private final LaunchOptions options = new LaunchOptions();\n\n        public LaunchOptions create() {\n            return options;\n        }\n\n        /**\n         * User custom additional minecraft command line arguments.\n         */\n        public List<String> getGameArguments() {\n            return options.gameArguments;\n        }\n\n        /**\n         * The highest priority JVM arguments (overrides the version setting)\n         */\n        public List<String> getOverrideJavaArguments() {\n            return options.overrideJavaArguments;\n        }\n\n        /**\n         * User custom additional java virtual machine command line arguments.\n         */\n        public List<String> getJavaArguments() {\n            return options.javaArguments;\n        }\n\n        public List<String> getJavaAgents() {\n            return options.javaAgents;\n        }\n\n        public Builder setGameDir(Path gameDir) {\n            options.gameDir = gameDir;\n            return this;\n        }\n\n        public Builder setJava(JavaRuntime java) {\n            options.java = java;\n            return this;\n        }\n\n        public Builder setVersionName(String versionName) {\n            options.versionName = versionName;\n            return this;\n        }\n\n        public Builder setVersionType(String versionType) {\n            options.versionType = versionType;\n            return this;\n        }\n\n        public Builder setProfileName(String profileName) {\n            options.profileName = profileName;\n            return this;\n        }\n\n        public Builder setGameArguments(List<String> gameArguments) {\n            options.gameArguments.clear();\n            options.gameArguments.addAll(gameArguments);\n            return this;\n        }\n\n        public Builder setOverrideJavaArguments(List<String> overrideJavaArguments) {\n            options.overrideJavaArguments.clear();\n            options.overrideJavaArguments.addAll(overrideJavaArguments);\n            return this;\n        }\n\n        public Builder setJavaArguments(List<String> javaArguments) {\n            options.javaArguments.clear();\n            options.javaArguments.addAll(javaArguments);\n            return this;\n        }\n\n        public Builder setJavaAgents(List<String> javaAgents) {\n            options.javaAgents.clear();\n            options.javaAgents.addAll(javaAgents);\n            return this;\n        }\n\n        public Builder setEnvironmentVariables(Map<String, String> env) {\n            options.environmentVariables.clear();\n            options.environmentVariables.putAll(env);\n            return this;\n        }\n\n        public Builder setMinMemory(Integer minMemory) {\n            options.minMemory = minMemory;\n            return this;\n        }\n\n        public Builder setMaxMemory(Integer maxMemory) {\n            options.maxMemory = maxMemory;\n            return this;\n        }\n\n        public Builder setMetaspace(Integer metaspace) {\n            options.metaspace = metaspace;\n            return this;\n        }\n\n        public Builder setWidth(Integer width) {\n            options.width = width;\n            return this;\n        }\n\n        public Builder setHeight(Integer height) {\n            options.height = height;\n            return this;\n        }\n\n        public Builder setFullscreen(boolean fullscreen) {\n            options.fullscreen = fullscreen;\n            return this;\n        }\n\n        public Builder setQuickPlayOption(QuickPlayOption quickPlayOption) {\n            options.quickPlayOption = quickPlayOption;\n            return this;\n        }\n\n        public Builder setWrapper(String wrapper) {\n            options.wrapper = wrapper;\n            return this;\n        }\n\n        public Builder setProxyOption(ProxyOption proxyOption) {\n            options.proxyOption = proxyOption;\n            return this;\n        }\n\n        public Builder setNoGeneratedJVMArgs(boolean noGeneratedJVMArgs) {\n            options.noGeneratedJVMArgs = noGeneratedJVMArgs;\n            return this;\n        }\n\n        public Builder setNoGeneratedOptimizingJVMArgs(boolean noGeneratedOptimizingJVMArgs) {\n            options.noGeneratedOptimizingJVMArgs = noGeneratedOptimizingJVMArgs;\n            return this;\n        }\n\n        public Builder setPreLaunchCommand(String preLaunchCommand) {\n            options.preLaunchCommand = preLaunchCommand;\n            return this;\n        }\n\n        public Builder setPostExitCommand(String postExitCommand) {\n            options.postExitCommand = postExitCommand;\n            return this;\n        }\n\n        public Builder setNativesDirType(NativesDirectoryType nativesDirType) {\n            options.nativesDirType = nativesDirType;\n            return this;\n        }\n\n        public Builder setNativesDir(String nativesDir) {\n            options.nativesDir = nativesDir;\n            return this;\n        }\n\n        public Builder setProcessPriority(@NotNull ProcessPriority processPriority) {\n            options.processPriority = processPriority;\n            return this;\n        }\n\n        public Builder setRenderer(@NotNull Renderer renderer) {\n            options.renderer = renderer;\n            return this;\n        }\n\n        public Builder setUseNativeGLFW(boolean useNativeGLFW) {\n            options.useNativeGLFW = useNativeGLFW;\n            return this;\n        }\n\n        public Builder setUseNativeOpenAL(boolean useNativeOpenAL) {\n            options.useNativeOpenAL = useNativeOpenAL;\n            return this;\n        }\n\n        public Builder setDaemon(boolean daemon) {\n            options.daemon = daemon;\n            return this;\n        }\n\n        public Builder setEnableDebugLogOutput(boolean u) {\n            options.enableDebugLogOutput = u;\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibrariesDownloadInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class LibrariesDownloadInfo {\n\n    private final LibraryDownloadInfo artifact;\n    private final Map<String, LibraryDownloadInfo> classifiers;\n\n    public LibrariesDownloadInfo(LibraryDownloadInfo artifact) {\n        this(artifact, null);\n    }\n\n    public LibrariesDownloadInfo(LibraryDownloadInfo artifact, Map<String, LibraryDownloadInfo> classifiers) {\n        this.artifact = artifact;\n        this.classifiers = classifiers == null ? null : new HashMap<>(classifiers);\n    }\n\n    public LibraryDownloadInfo getArtifact() {\n        return artifact;\n    }\n\n    public Map<String, LibraryDownloadInfo> getClassifiers() {\n        return classifiers == null ? Collections.emptyMap() : Collections.unmodifiableMap(classifiers);\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Constants;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\n\n/**\n * A class that describes a Minecraft dependency.\n *\n * @author huangyuhui\n */\n@Immutable\npublic class Library implements Comparable<Library>, Validation {\n    /**\n     * <p>A possible native descriptors can be: [variant-]os[-key]</p>\n     *\n     * <p>\n     * Variant can be empty string, 'native', or 'natives'.\n     * Key can be empty string, system arch, or system arch bit count.\n     * </p>\n     */\n    private static final String[] POSSIBLE_NATIVE_DESCRIPTORS;\n\n    static {\n        String[] keys = {\n                \"\",\n                Architecture.SYSTEM_ARCH.name().toLowerCase(Locale.ROOT),\n                Architecture.SYSTEM_ARCH.getBits().getBit()\n        };\n        String[] variants = {\"\", \"native\", \"natives\"};\n\n        POSSIBLE_NATIVE_DESCRIPTORS = new String[keys.length * variants.length];\n        StringBuilder builder = new StringBuilder();\n        for (int i = 0; i < keys.length; i++) {\n            for (int j = 0; j < variants.length; j++) {\n                if (!variants[j].isEmpty()) {\n                    builder.append(variants[j]).append('-');\n                }\n                builder.append(OperatingSystem.CURRENT_OS.getMojangName());\n                if (!keys[i].isEmpty()) {\n                    builder.append('-').append(keys[i]);\n                }\n\n                POSSIBLE_NATIVE_DESCRIPTORS[i * variants.length + j] = builder.toString();\n                builder.setLength(0);\n            }\n        }\n    }\n\n    @SerializedName(\"name\")\n    private final Artifact artifact;\n    private final String url;\n    private final LibrariesDownloadInfo downloads;\n    private final ExtractRules extract;\n    private final Map<String, String> natives;\n    private final List<CompatibilityRule> rules;\n    private final List<String> checksums;\n\n    @SerializedName(value = \"hint\", alternate = {\"MMC-hint\"})\n    private final String hint;\n\n    @SerializedName(value = \"filename\", alternate = {\"MMC-filename\"})\n    private final String fileName;\n\n    public Library(Artifact artifact) {\n        this(artifact, null, null);\n    }\n\n    public Library(Artifact artifact, String url, LibrariesDownloadInfo downloads) {\n        this(artifact, url, downloads, null, null, null, null, null, null);\n    }\n\n    public Library(Artifact artifact, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<String, String> natives, List<CompatibilityRule> rules, String hint, String filename) {\n        this.artifact = artifact;\n        this.url = url;\n        this.downloads = downloads;\n        this.extract = extract;\n        this.natives = natives;\n        this.rules = rules;\n        this.checksums = checksums;\n        this.hint = hint;\n        this.fileName = filename;\n    }\n\n    public String getGroupId() {\n        return artifact.getGroup();\n    }\n\n    public String getArtifactId() {\n        return artifact.getName();\n    }\n\n    public String getName() {\n        return artifact.toString();\n    }\n\n    public String getVersion() {\n        return artifact.getVersion();\n    }\n\n    public String getClassifier() {\n        if (artifact.getClassifier() == null) {\n            if (natives != null) {\n                for (String nativeDescriptor : POSSIBLE_NATIVE_DESCRIPTORS) {\n                    String nd = natives.get(nativeDescriptor);\n                    if (nd != null) {\n                        return nd.replace(\"${arch}\", Architecture.SYSTEM_ARCH.getBits().getBit());\n                    }\n                }\n            } else if (downloads != null && downloads.getClassifiers() != null) {\n                for (String nativeDescriptor : POSSIBLE_NATIVE_DESCRIPTORS) {\n                    LibraryDownloadInfo info = downloads.getClassifiers().get(nativeDescriptor);\n                    if (info != null) {\n                        return nativeDescriptor;\n                    }\n                }\n            }\n\n            return null;\n        } else {\n            return artifact.getClassifier();\n        }\n    }\n\n    public ExtractRules getExtract() {\n        return extract == null ? ExtractRules.EMPTY : extract;\n    }\n\n    public boolean appliesToCurrentEnvironment() {\n        return CompatibilityRule.appliesToCurrentEnvironment(rules);\n    }\n\n    public boolean isNative() {\n        if (!appliesToCurrentEnvironment()) {\n            return false;\n        }\n        if (natives != null) {\n            return true;\n        }\n\n        return downloads != null && downloads.getClassifiers().keySet().stream().anyMatch(s -> s.startsWith(\"native\"));\n    }\n\n    public LibraryDownloadInfo getRawDownloadInfo() {\n        if (downloads != null) {\n            if (isNative())\n                return downloads.getClassifiers().get(getClassifier());\n            else\n                return downloads.getArtifact();\n        } else {\n            return null;\n        }\n    }\n\n    public Artifact getArtifact() {\n        return artifact;\n    }\n\n    public String getPath() {\n        LibraryDownloadInfo temp = getRawDownloadInfo();\n        if (temp != null && temp.getPath() != null)\n            return temp.getPath();\n        else\n            return artifact.setClassifier(getClassifier()).getPath();\n    }\n\n    public LibraryDownloadInfo getDownload() {\n        LibraryDownloadInfo temp = getRawDownloadInfo();\n        String path = getPath();\n        return new LibraryDownloadInfo(path,\n                computePath(temp, path),\n                temp != null ? temp.getSha1() : null,\n                temp != null ? temp.getSize() : 0\n        );\n    }\n\n    private String computePath(LibraryDownloadInfo raw, String path) {\n        if (raw != null) {\n            String url = raw.getUrl();\n            if (url != null) {\n                return url;\n            }\n        }\n\n        String repo = Lang.requireNonNullElse(url, Constants.DEFAULT_LIBRARY_URL);\n        if (!repo.endsWith(\"/\")) {\n            repo += '/';\n        }\n\n        return repo + path;\n    }\n\n    public boolean hasDownloadURL() {\n        LibraryDownloadInfo temp = getRawDownloadInfo();\n        if (temp != null) return temp.getUrl() != null;\n        else return url != null;\n    }\n\n    public List<String> getChecksums() {\n        return checksums;\n    }\n\n    public List<CompatibilityRule> getRules() {\n        return rules;\n    }\n\n    /**\n     * Hint for how to locate the library file.\n     *\n     * @return null for default, \"local\" for location in version/&lt;version&gt;/libraries/filename\n     */\n    @Nullable\n    public String getHint() {\n        return hint;\n    }\n\n    public Library withoutCommunityFields() {\n        return new Library(artifact, url, downloads, checksums, extract, natives, rules, null, null);\n    }\n\n    /**\n     * Available when hint is \"local\"\n     *\n     * @return the filename of the local library in version/&lt;version&gt;/libraries/$filename\n     */\n    @Nullable\n    public String getFileName() {\n        return fileName;\n    }\n\n    public boolean is(String groupId, String artifactId) {\n        return getGroupId().equals(groupId) && getArtifactId().equals(artifactId);\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this).append(\"name\", getName()).toString();\n    }\n\n    @Override\n    public int compareTo(Library o) {\n        if (getName().compareTo(o.getName()) == 0)\n            return Boolean.compare(isNative(), o.isNative());\n        else\n            return getName().compareTo(o.getName());\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (!(obj instanceof Library))\n            return false;\n\n        Library other = (Library) obj;\n        return getName().equals(other.getName()) && (isNative() == other.isNative());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(getName(), isNative());\n    }\n\n    public Library setClassifier(String classifier) {\n        return new Library(artifact.setClassifier(classifier), url, downloads, checksums, extract, natives, rules, hint, fileName);\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        if (artifact == null)\n            throw new JsonParseException(\"Library.name cannot be null\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibraryDownloadInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic class LibraryDownloadInfo extends DownloadInfo {\n\n    @SerializedName(\"path\")\n    private final String path;\n\n    public LibraryDownloadInfo() {\n        this(null);\n    }\n\n    public LibraryDownloadInfo(String path) {\n        this(path, \"\");\n    }\n\n    public LibraryDownloadInfo(String path, String url) {\n        this(path, url, null);\n    }\n\n    public LibraryDownloadInfo(String path, String url, String sha1) {\n        this(path, url, sha1, 0);\n    }\n\n    public LibraryDownloadInfo(String path, String url, String sha1, int size) {\n        super(url, sha1, size);\n        this.path = path;\n    }\n\n    public String getPath() {\n        return path;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/LoggingInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class LoggingInfo implements Validation {\n\n    @SerializedName(\"file\")\n    private final IdDownloadInfo file;\n    @SerializedName(\"argument\")\n    private final String argument;\n    @SerializedName(\"type\")\n    private final String type;\n\n    public LoggingInfo() {\n        this(new IdDownloadInfo());\n    }\n\n    public LoggingInfo(IdDownloadInfo file) {\n        this(file, \"\");\n    }\n\n    public LoggingInfo(IdDownloadInfo file, String argument) {\n        this(file, argument, \"\");\n    }\n\n    public LoggingInfo(IdDownloadInfo file, String argument, String type) {\n        this.file = file;\n        this.argument = argument;\n        this.type = type;\n    }\n\n    public IdDownloadInfo getFile() {\n        return file;\n    }\n\n    public String getArgument() {\n        return argument;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        file.validate();\n        if (StringUtils.isBlank(argument))\n            throw new JsonParseException(\"LoggingInfo.argument is empty.\");\n        if (StringUtils.isBlank(type))\n            throw new JsonParseException(\"LoggingInfo.type is empty.\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/NativesDirectoryType.java",
    "content": "package org.jackhuang.hmcl.game;\n\npublic enum NativesDirectoryType {\n    /**\n     * .minecraft/versions/&lt;version&gt;/natives\n     */\n    VERSION_FOLDER,\n    /**\n     * user customized directory.\n     */\n    CUSTOM\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/OSRestriction.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.util.regex.Pattern;\n\n/**\n * @author huangyuhui\n */\npublic final class OSRestriction {\n\n    private final String name;\n    private final String version;\n    private final String arch;\n\n    public OSRestriction() {\n        this(OperatingSystem.UNKNOWN);\n    }\n\n    public OSRestriction(OperatingSystem os) {\n        this(os, null);\n    }\n\n    public OSRestriction(OperatingSystem os, String version) {\n        this(os, version, null);\n    }\n\n    public OSRestriction(OperatingSystem os, String version, String arch) {\n        this.name = os.getMojangName();\n        this.version = version;\n        this.arch = arch;\n    }\n\n    public OSRestriction(String name, String version, String arch) {\n        this.name = name;\n        this.version = version;\n        this.arch = arch;\n    }\n\n    public boolean allow() {\n        // Some modpacks directly use { name: \"win-x86\" }\n        if (name != null) {\n            String[] parts = name.split(\"-\", 3);\n            if (parts.length == 2) {\n                OperatingSystem os = OperatingSystem.parseOSName(parts[0]);\n                Architecture arch = Architecture.parseArchName(parts[1]);\n\n                if (os != OperatingSystem.UNKNOWN && arch != Architecture.UNKNOWN) {\n                    if (os != OperatingSystem.CURRENT_OS && !(os == OperatingSystem.LINUX && OperatingSystem.CURRENT_OS.isLinuxOrBSD())) {\n                        return false;\n                    }\n\n                    if (arch != Architecture.SYSTEM_ARCH) {\n                        return false;\n                    }\n\n                    return true;\n                }\n            }\n        }\n\n        OperatingSystem os = OperatingSystem.parseOSName(name);\n        if (os != OperatingSystem.UNKNOWN\n                && os != OperatingSystem.CURRENT_OS\n                && !(os == OperatingSystem.LINUX && OperatingSystem.CURRENT_OS.isLinuxOrBSD()))\n            return false;\n\n        if (version != null)\n            if (Lang.test(() -> !Pattern.compile(version).matcher(OperatingSystem.SYSTEM_VERSION.getVersion()).matches()))\n                return false;\n\n        if (arch != null)\n            return !Lang.test(() -> !Pattern.compile(arch).matcher(Architecture.SYSTEM_ARCH.getCheckedName()).matches());\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/ProcessPriority.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\npublic enum ProcessPriority {\n    LOW,\n    BELOW_NORMAL,\n    NORMAL,\n    ABOVE_NORMAL,\n    HIGH\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/ProxyOption.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\n/// @author Glavo\npublic sealed interface ProxyOption {\n    final class Direct implements ProxyOption {\n        public static final Direct INSTANCE = new Direct();\n\n        private Direct() {\n        }\n    }\n\n    final class Default implements ProxyOption {\n        public static final Default INSTANCE = new Default();\n\n        private Default() {\n        }\n    }\n\n    record Http(@NotNull String host, int port, @Nullable String username,\n                @Nullable String password) implements ProxyOption {\n        public Http {\n            if (StringUtils.isBlank(host)) {\n                throw new IllegalArgumentException(\"Host cannot be blank\");\n            }\n            if (port < 0 || port > 0xFFFF) {\n                throw new IllegalArgumentException(\"Illegal port: \" + port);\n            }\n        }\n    }\n\n    record Socks(@NotNull String host, int port, @Nullable String username,\n                 @Nullable String password) implements ProxyOption {\n        public Socks {\n            if (StringUtils.isBlank(host)) {\n                throw new IllegalArgumentException(\"Host cannot be blank\");\n            }\n            if (port < 0 || port > 0xFFFF) {\n                throw new IllegalArgumentException(\"Illegal port: \" + port);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/QuickPlayOption.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\n/// The quick play option.\n///\n/// @see <a href=\"https://minecraft.wiki/w/Quick_Play\">Quick Play - Minecraft Wiki</a>\npublic sealed interface QuickPlayOption {\n    record SinglePlayer(String worldFolderName) implements QuickPlayOption {\n    }\n\n    record MultiPlayer(String serverIP) implements QuickPlayOption {\n    }\n\n    record Realm(String realmID) implements QuickPlayOption {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/ReleaseType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\n/**\n *\n * @author huangyuhui\n */\npublic enum ReleaseType {\n    RELEASE(\"release\"),\n    SNAPSHOT(\"snapshot\"),\n    MODIFIED(\"modified\"),\n    OLD_BETA(\"old-beta\"),\n    OLD_ALPHA(\"old-alpha\"),\n    PENDING(\"pending\"),\n    UNOBFUSCATED(\"unobfuscated\"),\n    UNKNOWN(\"unknown\");\n\n    private final String id;\n\n    ReleaseType(String id) {\n        this.id = id;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/Renderer.java",
    "content": "package org.jackhuang.hmcl.game;\n\npublic enum Renderer {\n    DEFAULT,\n    ZINK,\n    LLVMPIPE,\n    D3D12\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport java.lang.reflect.Type;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\n\n/**\n *\n * @author huangyuhui\n */\n@JsonAdapter(RuledArgument.Serializer.class)\n@Immutable\npublic class RuledArgument implements Argument {\n\n    private final List<CompatibilityRule> rules;\n    private final List<String> value;\n\n    public RuledArgument() {\n        this(null, null);\n    }\n\n    public RuledArgument(List<CompatibilityRule> rules, List<String> args) {\n        this.rules = rules;\n        this.value = args;\n    }\n\n    public List<CompatibilityRule> getRules() {\n        return Collections.unmodifiableList(rules);\n    }\n\n    public List<String> getValue() {\n        return Collections.unmodifiableList(value);\n    }\n\n    @Override\n    public Object clone() {\n        return new RuledArgument(\n                rules == null ? null : new ArrayList<>(rules),\n                value == null ? null : new ArrayList<>(value)\n        );\n    }\n\n    @Override\n    public List<String> toString(Map<String, String> keys, Map<String, Boolean> features) {\n        if (CompatibilityRule.appliesToCurrentEnvironment(rules, features) && value != null)\n            return value.stream()\n                    .filter(Objects::nonNull)\n                    .map(StringArgument::new)\n                    .map(str -> str.toString(keys, features).get(0))\n                    .collect(Collectors.toList());\n        return Collections.emptyList();\n    }\n\n    public static class Serializer implements JsonSerializer<RuledArgument>, JsonDeserializer<RuledArgument> {\n        @Override\n        public JsonElement serialize(RuledArgument src, Type typeOfSrc, JsonSerializationContext context) {\n            JsonObject obj = new JsonObject();\n            obj.add(\"rules\", context.serialize(src.rules));\n            obj.add(\"value\", context.serialize(src.value));\n            return obj;\n        }\n\n        @Override\n        public RuledArgument deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            JsonObject obj = json.getAsJsonObject();\n\n            List<CompatibilityRule> rules = context.deserialize(obj.get(\"rules\"), listTypeOf(CompatibilityRule.class).getType());\n\n            JsonElement valuesElement;\n            if (obj.has(\"values\")) {\n                valuesElement = obj.get(\"values\");\n            } else if (obj.has(\"value\")) {\n                valuesElement = obj.get(\"value\");\n            } else {\n                throw new JsonParseException(\"RuledArguments instance does not have either value or values member.\");\n            }\n\n            List<String> values;\n            if (valuesElement.isJsonPrimitive()) {\n                values = Collections.singletonList(valuesElement.getAsString());\n            } else {\n                values = context.deserialize(valuesElement, listTypeOf(String.class).getType());\n            }\n\n            return new RuledArgument(rules, values);\n        }\n\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/SimpleVersionProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n *\n * @author huangyuhui\n */\npublic class SimpleVersionProvider implements VersionProvider {\n\n    protected final Map<String, Version> versionMap = new HashMap<>();\n\n    @Override\n    public boolean hasVersion(String id) {\n        return versionMap.containsKey(id);\n    }\n\n    @Override\n    public Version getVersion(String id) {\n        if (!hasVersion(id))\n            throw new VersionNotFoundException(\"Version id \" + id + \" not found\");\n        else\n            return versionMap.get(id);\n    }\n\n    public void addVersion(Version version) {\n        versionMap.put(version.getId(), version);\n    }\n\n    public Map<String, Version> getVersionMap() {\n        return versionMap;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/StringArgument.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.Immutable;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonPrimitive;\nimport com.google.gson.JsonSerializationContext;\nimport com.google.gson.JsonSerializer;\nimport com.google.gson.annotations.JsonAdapter;\n\nimport java.lang.reflect.Type;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n *\n * @author huangyuhui\n */\n@JsonAdapter(StringArgument.Serializer.class)\n@Immutable\npublic final class StringArgument implements Argument {\n\n    private final String argument;\n\n    public StringArgument(String argument) {\n        this.argument = argument;\n    }\n\n    public String getArgument() {\n        return argument;\n    }\n\n    @Override\n    public List<String> toString(Map<String, String> keys, Map<String, Boolean> features) {\n        String res = argument;\n        Pattern pattern = Pattern.compile(\"\\\\$\\\\{(.*?)}\");\n        Matcher m = pattern.matcher(argument);\n        while (m.find()) {\n            String entry = m.group();\n            res = res.replace(entry, keys.getOrDefault(entry, entry));\n        }\n        return Collections.singletonList(res);\n    }\n\n    @Override\n    public String toString() {\n        return argument;\n    }\n\n    public static final class Serializer implements JsonSerializer<StringArgument> {\n        @Override\n        public JsonElement serialize(StringArgument src, Type typeOfSrc, JsonSerializationContext context) {\n            return new JsonPrimitive(src.getArgument());\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.gson.JsonMap;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.time.Instant;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic class Version implements Comparable<Version>, Validation {\n\n    /**\n     * Patches with higher priority can override info from other patches, such as mainClass.\n     */\n    public static final int PRIORITY_MC = 0, PRIORITY_LOADER = 30000;\n\n    private final String id;\n    private final String version;\n    private final Integer priority;\n    private final String minecraftArguments;\n    private final Arguments arguments;\n    private final String mainClass;\n    private final String inheritsFrom;\n    private final String jar;\n    private final AssetIndexInfo assetIndex;\n    private final String assets;\n    private final Integer complianceLevel;\n    @Nullable\n    private final GameJavaVersion javaVersion;\n    private final List<Library> libraries;\n    private final List<CompatibilityRule> compatibilityRules;\n    private final JsonMap<DownloadType, DownloadInfo> downloads;\n    private final JsonMap<DownloadType, LoggingInfo> logging;\n    private final ReleaseType type;\n    private final Instant time;\n    private final Instant releaseTime;\n    private final Integer minimumLauncherVersion;\n    private final Boolean root;\n    private final Boolean hidden;\n    private final List<Version> patches;\n\n    private transient final boolean resolved;\n\n    public Version(String id) {\n        this(false, id, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, false, true, null);\n    }\n\n    /**\n     * Constructor for patch\n     *\n     * @param id        patch id\n     * @param version   patch version\n     * @param priority  patch priority\n     * @param arguments patch additional arguments\n     * @param mainClass main class to override\n     * @param libraries additional libraries\n     */\n    public Version(String id, String version, int priority, Arguments arguments, String mainClass, List<Library> libraries) {\n        this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, null, null, libraries, null, null, null, null, null, null, null, null, null, null);\n    }\n\n    public Version(boolean resolved, String id, String version, Integer priority, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, Integer complianceLevel, GameJavaVersion javaVersion, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Instant time, Instant releaseTime, Integer minimumLauncherVersion, Boolean hidden, Boolean root, List<Version> patches) {\n        this.resolved = resolved;\n        this.id = id;\n        this.version = version;\n        this.priority = priority;\n        this.minecraftArguments = minecraftArguments;\n        this.arguments = arguments;\n        this.mainClass = mainClass;\n        this.inheritsFrom = inheritsFrom;\n        this.jar = jar;\n        this.assetIndex = assetIndex;\n        this.assets = assets;\n        this.complianceLevel = complianceLevel;\n        this.javaVersion = javaVersion;\n        this.libraries = Lang.copyList(libraries);\n        this.compatibilityRules = Lang.copyList(compatibilityRules);\n        this.downloads = downloads == null ? null : new JsonMap<>(downloads);\n        this.logging = logging == null ? null : new JsonMap<>(logging);\n        this.type = type;\n        this.time = time;\n        this.releaseTime = releaseTime;\n        this.minimumLauncherVersion = minimumLauncherVersion;\n        this.hidden = hidden;\n        this.root = root;\n        this.patches = Lang.copyList(patches);\n    }\n\n    public Optional<String> getMinecraftArguments() {\n        return Optional.ofNullable(minecraftArguments);\n    }\n\n    public Optional<Arguments> getArguments() {\n        return Optional.ofNullable(arguments);\n    }\n\n    public String getMainClass() {\n        return mainClass;\n    }\n\n    public Instant getTime() {\n        return time;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    /**\n     * Version of the patch.\n     * Exists only when this version object represents a patch.\n     * Example: 0.5.0.33 for fabric-loader, 28.0.46 for minecraft-forge.\n     */\n    @Nullable\n    public String getVersion() {\n        return version;\n    }\n\n    public int getPriority() {\n        return priority == null ? Integer.MIN_VALUE : priority;\n    }\n\n    public ReleaseType getType() {\n        return type == null ? ReleaseType.UNKNOWN : type;\n    }\n\n    public Instant getReleaseTime() {\n        return releaseTime;\n    }\n\n    public String getJar() {\n        return jar;\n    }\n\n    public String getInheritsFrom() {\n        return inheritsFrom;\n    }\n\n    public int getMinimumLauncherVersion() {\n        return minimumLauncherVersion == null ? 0 : minimumLauncherVersion;\n    }\n\n    public Integer getComplianceLevel() {\n        return complianceLevel;\n    }\n\n    public GameJavaVersion getJavaVersion() {\n        return javaVersion;\n    }\n\n    public boolean isHidden() {\n        return hidden == null ? false : hidden;\n    }\n\n    public boolean isRoot() {\n        return root == null ? false : root;\n    }\n\n    public boolean isResolved() {\n        return resolved;\n    }\n\n    public boolean isResolvedPreservingPatches() {\n        return inheritsFrom == null && !resolved;\n    }\n\n    public List<Version> getPatches() {\n        return patches == null ? Collections.emptyList() : patches;\n    }\n\n    public Map<DownloadType, LoggingInfo> getLogging() {\n        return logging == null ? Collections.emptyMap() : Collections.unmodifiableMap(logging);\n    }\n\n    public List<Library> getLibraries() {\n        return libraries == null ? Collections.emptyList() : Collections.unmodifiableList(libraries);\n    }\n\n    public List<CompatibilityRule> getCompatibilityRules() {\n        return compatibilityRules == null ? Collections.emptyList() : Collections.unmodifiableList(compatibilityRules);\n    }\n\n    public Map<DownloadType, DownloadInfo> getDownloads() {\n        return downloads == null ? Collections.emptyMap() : Collections.unmodifiableMap(downloads);\n    }\n\n    public DownloadInfo getDownloadInfo() {\n        DownloadInfo client = downloads == null ? null : downloads.get(DownloadType.CLIENT);\n        String jarName = jar == null ? id : jar;\n        if (client == null)\n            return new DownloadInfo(String.format(\"%s%s/%s.jar\", Constants.DEFAULT_VERSION_DOWNLOAD_URL, jarName, jarName));\n        else\n            return client;\n    }\n\n    public AssetIndexInfo getAssetIndex() {\n        String assetsId = assets == null ? \"legacy\" : assets;\n\n        if (assetIndex == null) {\n            String hash;\n            switch (assetsId) {\n                case \"1.8\":\n                    hash = \"f6ad102bcaa53b1a58358f16e376d548d44933ec\";\n                    break;\n                case \"14w31a\":\n                    hash = \"10a2a0e75b03cfb5a7196abbdf43b54f7fa61deb\";\n                    break;\n                case \"14w25a\":\n                    hash = \"32ff354a3be1c4dd83027111e6d79ee4d701d2c0\";\n                    break;\n                case \"1.7.4\":\n                    hash = \"545510a60f526b9aa8a38f9c0bc7a74235d21675\";\n                    break;\n                case \"1.7.10\":\n                    hash = \"1863782e33ce7b584fc45b037325a1964e095d3e\";\n                    break;\n                case \"1.7.3\":\n                    hash = \"f6cf726f4747128d13887010c2cbc44ba83504d9\";\n                    break;\n                case \"pre-1.6\":\n                    hash = \"3d8e55480977e32acd9844e545177e69a52f594b\";\n                    break;\n                case \"legacy\":\n                default:\n                    assetsId = \"legacy\";\n                    hash = \"770572e819335b6c0a053f8378ad88eda189fc14\";\n            }\n\n            String url = Constants.DEFAULT_INDEX_URL + hash + \"/\" + assetsId + \".json\";\n            return new AssetIndexInfo(assetsId, url);\n        } else {\n            return assetIndex;\n        }\n    }\n\n    public boolean appliesToCurrentEnvironment() {\n        return CompatibilityRule.appliesToCurrentEnvironment(compatibilityRules);\n    }\n\n    /**\n     * Resolve given version.\n     * Resolving version will list all patches within this version and its parents,\n     * which is for analysis.\n     */\n    public Version resolve(VersionProvider provider) throws VersionNotFoundException {\n        if (isResolved()) return this;\n        return resolve(provider, new HashSet<>()).markAsResolved();\n    }\n\n    protected Version merge(Version parent, boolean isPatch) {\n        return new Version(\n                true,\n                id,\n                null,\n                null,\n                minecraftArguments == null ? parent.minecraftArguments : minecraftArguments,\n                Arguments.merge(parent.arguments, arguments),\n                mainClass == null ? parent.mainClass : mainClass,\n                null, // inheritsFrom\n                jar == null ? parent.jar : jar,\n                assetIndex == null ? parent.assetIndex : assetIndex,\n                assets == null ? parent.assets : assets,\n                complianceLevel,\n                javaVersion == null ? parent.javaVersion : javaVersion,\n                Lang.merge(this.libraries, parent.libraries),\n                Lang.merge(parent.compatibilityRules, this.compatibilityRules),\n                downloads == null ? parent.downloads : downloads,\n                logging == null ? parent.logging : logging,\n                type == null ? parent.type : type,\n                time == null ? parent.time : time,\n                releaseTime == null ? parent.releaseTime : releaseTime,\n                Lang.merge(minimumLauncherVersion, parent.minimumLauncherVersion, Math::max),\n                hidden,\n                true,\n                isPatch ? parent.patches : Lang.merge(Lang.merge(parent.patches, Collections.singleton(toPatch())), patches));\n    }\n\n    protected Version resolve(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {\n        Version thisVersion;\n\n        if (inheritsFrom == null) {\n            if (isRoot()) {\n                // TODO: Breaking change, require much testing on versions installed with external installer, other launchers, and all kinds of versions.\n                thisVersion = patches != null ? new Version(id).setPatches(patches) : this;\n            } else {\n                thisVersion = this;\n            }\n            thisVersion = this.jar == null ? thisVersion.setJar(id) : thisVersion.setJar(this.jar);\n        } else {\n            // To maximize the compatibility.\n            if (!resolvedSoFar.add(id)) {\n                LOG.warning(\"Found circular dependency versions: \" + resolvedSoFar);\n                thisVersion = this.jar == null ? this.setJar(id) : this;\n            } else {\n                // It is supposed to auto install an version in getVersion.\n                thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar), false);\n            }\n        }\n\n        if (patches == null) {\n            // This is a version from external launcher. NO need to resolve the patches.\n            return thisVersion;\n        } else if (!patches.isEmpty()) {\n            // Assume patches themselves do not have patches recursively.\n            List<Version> sortedPatches = patches.stream()\n                    .sorted(Comparator.comparing(Version::getPriority))\n                    .collect(Collectors.toList());\n            for (Version patch : sortedPatches) {\n                thisVersion = patch.setJar(null).merge(thisVersion, true);\n            }\n        }\n\n        return thisVersion.setId(id);\n    }\n\n    private Version toPatch() {\n        return this.clearPatches().setHidden(true).setId(\"resolved.\" + getId());\n    }\n\n    /**\n     * Resolve the version preserving all dependencies and patches.\n     */\n    public Version resolvePreservingPatches(VersionProvider provider) throws VersionNotFoundException {\n        return resolvePreservingPatches(provider, new HashSet<>());\n    }\n\n    protected Version mergePreservingPatches(Version parent) {\n        return parent.addPatch(toPatch()).addPatches(patches);\n    }\n\n    protected Version resolvePreservingPatches(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {\n        Version thisVersion = isRoot() ? this : new Version(id).addPatch(toPatch()).addPatches(getPatches());\n\n        if (inheritsFrom == null) {\n            // keep thisVersion\n        } else {\n            // To maximize the compatibility.\n            if (!resolvedSoFar.add(id)) {\n                LOG.warning(\"Found circular dependency versions: \" + resolvedSoFar);\n                // keep thisVersion\n            } else {\n                // It is supposed to auto install an version in getVersion.\n                thisVersion = mergePreservingPatches(provider.getVersion(inheritsFrom).resolvePreservingPatches(provider, resolvedSoFar));\n            }\n        }\n\n        return thisVersion.setId(id).setJar(resolve(provider).getJar());\n    }\n\n    public Version markAsResolved() {\n        return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version markAsUnresolved() {\n        return new Version(false, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    private Version setHidden(Boolean hidden) {\n        return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setRoot(Boolean root) {\n        return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setId(String id) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setVersion(String version) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setPriority(Integer priority) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setMinecraftArguments(String minecraftArguments) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setJavaVersion(GameJavaVersion javaVersion) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setArguments(Arguments arguments) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setMainClass(String mainClass) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setInheritsFrom(String inheritsFrom) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setJar(String jar) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setAssetIndex(AssetIndexInfo assetIndex) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setLibraries(List<Library> libraries) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setDownload(JsonMap<DownloadType, DownloadInfo> downloads) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setLogging(Map<DownloadType, LoggingInfo> logging) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version setPatches(List<Version> patches) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version addPatch(Version... additional) {\n        return addPatches(Arrays.asList(additional));\n    }\n\n    public Version addPatches(@Nullable List<Version> additional) {\n        Set<String> patchIds = additional == null ? Collections.emptySet() : additional.stream().map(Version::getId).collect(Collectors.toSet());\n        List<Version> patches = Lang.merge(this.patches == null ? null : this.patches.stream().filter(patch -> !patchIds.contains(patch.getId())).collect(Collectors.toList()), additional);\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);\n    }\n\n    public Version clearPatches() {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, null);\n    }\n\n    public Version removePatchById(String patchId) {\n        return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root,\n                patches == null ? null : patches.stream().filter(patch -> !patchId.equals(patch.getId())).collect(Collectors.toList()));\n    }\n\n    public boolean hasPatch(String patchId) {\n        return patches != null && patches.stream().anyMatch(patch -> patchId.equals(patch.getId()));\n    }\n\n    @Override\n    public int hashCode() {\n        return id.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return obj instanceof Version && Objects.equals(id, ((Version) obj).id);\n    }\n\n    @Override\n    public int compareTo(Version o) {\n        return id.compareTo(o.id);\n    }\n\n    @Override\n    public String toString() {\n        return new ToStringBuilder(this).append(\"id\", id).toString();\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (StringUtils.isBlank(id))\n            throw new JsonParseException(\"Version ID cannot be blank\");\n        if (downloads != null)\n            for (Map.Entry<DownloadType, DownloadInfo> entry : downloads.entrySet()) {\n                if (!(entry.getKey() instanceof DownloadType))\n                    throw new JsonParseException(\"Version downloads key must be DownloadType\");\n                if (!(entry.getValue() instanceof DownloadInfo))\n                    throw new JsonParseException(\"Version downloads value must be DownloadInfo\");\n            }\n        if (logging != null)\n            for (Map.Entry<DownloadType, LoggingInfo> entry : logging.entrySet()) {\n                if (!(entry.getKey() instanceof DownloadType))\n                    throw new JsonParseException(\"Version logging key must be DownloadType\");\n                if (!(entry.getValue() instanceof LoggingInfo))\n                    throw new JsonParseException(\"Version logging value must be LoggingInfo\");\n            }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionJsonType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\npublic enum VersionJsonType {\n    OFFICIAL,\n    TLAUNCHER\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionLibraryBuilder.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.CommandBuilder;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class VersionLibraryBuilder {\n    private final Version version;\n    private final List<String> mcArgs;\n    private final List<Argument> game;\n    private final List<Argument> jvm;\n    private final List<Library> libraries;\n    private final boolean useMcArgs;\n    private boolean jvmChanged = false;\n\n    public VersionLibraryBuilder(Version version) {\n        this.version = version;\n        this.libraries = new ArrayList<>(version.getLibraries());\n        this.mcArgs = version.getMinecraftArguments().map(StringUtils::tokenize).map(ArrayList::new).orElse(null);\n        this.game = version.getArguments().map(Arguments::getGame).map(ArrayList::new).orElseGet(ArrayList::new);\n        this.jvm = new ArrayList<>(version.getArguments().map(Arguments::getJvm).orElse(Arguments.DEFAULT_JVM_ARGUMENTS));\n        this.useMcArgs = mcArgs != null;\n    }\n\n    public Version build() {\n        Version ret = version;\n        if (useMcArgs) {\n            // The official launcher will not parse the \"arguments\" property when it detects the presence of \"mcArgs\".\n            // The \"arguments\" property with the \"rule\" is simply ignored here.\n            this.mcArgs.addAll(this.game.stream().map(arg -> arg.toString(new HashMap<>(), new HashMap<>())).flatMap(Collection::stream).collect(Collectors.toList()));\n            ret = ret.setArguments(null);\n\n            // Since $ will be escaped in linux, and our maintain of minecraftArgument will not cause escaping,\n            // so we regenerate the minecraftArgument without escaping.\n            ret = ret.setMinecraftArguments(new CommandBuilder().addAllWithoutParsing(mcArgs).toString());\n        } else {\n            ret = ret.setArguments(ret.getArguments()\n                    .map(args -> args.withGame(game))\n                    .map(args -> jvmChanged ? args.withJvm(jvm) : args).orElse(new Arguments(game, jvmChanged ? jvm : null)));\n        }\n        return ret.setLibraries(libraries);\n    }\n\n    public boolean hasTweakClass(String tweakClass) {\n        return useMcArgs && mcArgs.contains(tweakClass) || game.stream().anyMatch(arg -> arg.toString().equals(tweakClass));\n    }\n\n    public void removeTweakClass(String target) {\n        replaceTweakClass(target, null, false);\n    }\n\n    /**\n     * Replace existing tweak class without reordering.\n     * If the tweak class does not exist, the new tweak class will be appended to the end of argument list.\n     * If the tweak class appears more than one time, the tweak classes will be removed excluding the first one.\n     *\n     * @param target the tweak class to replace\n     * @param replacement the new tweak class to be replaced with\n     */\n    public void replaceTweakClass(String target, String replacement) {\n        replaceTweakClass(target, replacement, true);\n    }\n\n    /**\n     * Replace existing tweak class and add the new tweak class to the end of argument list.\n     *\n     * @param target the tweak class to replace\n     * @param replacement the new tweak class to be replaced with\n     */\n    public void addTweakClass(String target, String replacement) {\n        replaceTweakClass(target, replacement, false);\n    }\n\n    /**\n     * Replace existing tweak class.\n     * If the tweak class does not exist, the new tweak class will be appended to the end of argument list.\n     * If the tweak class appears more than one time, the tweak classes will be removed excluding the first one.\n     *\n     * @param target the tweak class to replace\n     * @param replacement the new tweak class to be replaced with, if null, remove the tweak class only\n     * @param inPlace if true, replace the tweak class in place, otherwise add the tweak class to the end of the argument list without replacement.\n     */\n    public void replaceTweakClass(String target, String replacement, boolean inPlace) {\n        replaceTweakClass(target, replacement, inPlace, false);\n    }\n\n    /**\n     * Replace existing tweak class.\n     * If the tweak class does not exist, the new tweak class will be added to argument list.\n     * If the tweak class appears more than one time, the tweak classes will be removed excluding the first one.\n     *\n     * @param target the tweak class to replace\n     * @param replacement the new tweak class to be replaced with, if null, remove the tweak class only\n     * @param inPlace if true, replace the tweak class in place, otherwise add the tweak class to the end of the argument list without replacement.\n     * @param reserve if true, add the tweak class to the start of the argument list.\n     */\n    public void replaceTweakClass(String target, String replacement, boolean inPlace, boolean reserve) {\n        if (replacement == null && inPlace)\n            throw new IllegalArgumentException(\"Replacement cannot be null in replace mode\");\n\n        boolean replaced = false;\n        if (useMcArgs) {\n            for (int i = 0; i + 1 < mcArgs.size(); ++i) {\n                String arg0Str = mcArgs.get(i);\n                String arg1Str = mcArgs.get(i + 1);\n                if (arg0Str.equals(\"--tweakClass\") && arg1Str.equals(target)) {\n                    if (!replaced && inPlace) {\n                        // for the first one, we replace the tweak class only.\n                        mcArgs.set(i + 1, replacement);\n                        replaced = true;\n                    } else {\n                        // otherwise, we remove the duplicate tweak classes.\n                        mcArgs.remove(i);\n                        mcArgs.remove(i);\n                        --i;\n                    }\n                }\n            }\n        }\n\n        for (int i = 0; i + 1 < game.size(); ++i) {\n            Argument arg0 = game.get(i);\n            Argument arg1 = game.get(i + 1);\n            if (arg0 instanceof StringArgument && arg1 instanceof StringArgument) {\n                // We need to preserve the tokens\n                String arg0Str = arg0.toString();\n                String arg1Str = arg1.toString();\n                if (arg0Str.equals(\"--tweakClass\") && arg1Str.equals(target)) {\n                    if (!replaced && inPlace) {\n                        // for the first one, we replace the tweak class only.\n                        game.set(i + 1, new StringArgument(replacement));\n                        replaced = true;\n                    } else {\n                        // otherwise, we remove the duplicate tweak classes.\n                        game.remove(i);\n                        game.remove(i);\n                        --i;\n                    }\n                }\n            }\n        }\n\n        // if the tweak class does not exist, add a new one to the end.\n        if (!replaced && replacement != null) {\n            if (reserve) {\n                if (useMcArgs) {\n                    mcArgs.add(0, replacement);\n                    mcArgs.add(0, \"--tweakClass\");\n                } else {\n                    game.add(0, new StringArgument(replacement));\n                    game.add(0, new StringArgument(\"--tweakClass\"));\n                }\n            } else {\n                game.add(new StringArgument(\"--tweakClass\"));\n                game.add(new StringArgument(replacement));\n            }\n        }\n    }\n\n    public List<Argument> getMutableJvmArguments() {\n        jvmChanged = true;\n        return jvm;\n    }\n\n    public void addGameArgument(String... args) {\n        for (String arg : args)\n            game.add(new StringArgument(arg));\n    }\n\n    public void addJvmArgument(String... args) {\n        jvmChanged = true;\n        for (String arg : args)\n            jvm.add(new StringArgument(arg));\n    }\n\n    public void addLibrary(Library library) {\n        libraries.add(library);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionNotFoundException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class VersionNotFoundException extends RuntimeException {\n\n    public VersionNotFoundException() {\n    }\n\n    public VersionNotFoundException(String message) {\n        super(message);\n    }\n\n    public VersionNotFoundException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\n/**\n * Supports version accessing.\n *\n * @see Version#resolve\n * @author huangyuhui\n */\npublic interface VersionProvider {\n\n    /**\n     * Does the version of id exist?\n     *\n     * @param id the id of version\n     * @return true if the version exists\n     */\n    boolean hasVersion(String id);\n\n    /**\n     * Get the version\n     *\n     * @param id the id of version\n     * @return the version you want\n     */\n    Version getVersion(String id) throws VersionNotFoundException;\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport com.github.steveice10.opennbt.NBTIO;\nimport com.github.steveice10.opennbt.tag.builtin.*;\nimport javafx.scene.image.Image;\nimport org.jackhuang.hmcl.util.io.*;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.channels.FileLock;\nimport java.nio.channels.OverlappingFileLockException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.*;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.stream.Stream;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class World {\n\n    private final Path file;\n    private String fileName;\n    private Image icon;\n\n    private CompoundTag levelData;\n    private Path levelDataPath;\n\n    private CompoundTag worldGenSettingsDataBackingTag; // Use for writing back to the file\n    private CompoundTag normalizedWorldGenSettingsData; // Use for reading/modification\n    private Path worldGenSettingsDataPath;\n\n    private CompoundTag playerData; // Use for both reading/modification and writing back to the file\n    private Path playerDataPath;\n\n    public World(Path file) throws IOException {\n        this.file = file;\n\n        if (Files.isDirectory(file))\n            loadFromDirectory();\n        else if (Files.isRegularFile(file))\n            loadFromZip();\n        else\n            throw new IOException(\"Path \" + file + \" cannot be recognized as a Minecraft world\");\n    }\n\n    public Path getFile() {\n        return file;\n    }\n\n    public String getFileName() {\n        return fileName;\n    }\n\n    public String getWorldName() {\n        CompoundTag data = levelData.get(\"Data\");\n        StringTag levelNameTag = data.get(\"LevelName\");\n        return levelNameTag.getValue();\n    }\n\n    public void setWorldName(String worldName) throws IOException {\n        if (levelData.get(\"Data\") instanceof CompoundTag data && data.get(\"LevelName\") instanceof StringTag levelNameTag) {\n            levelNameTag.setValue(worldName);\n            writeLevelData();\n        }\n    }\n\n    public Path getSessionLockFile() {\n        return file.resolve(\"session.lock\");\n    }\n\n    public CompoundTag getLevelData() {\n        return levelData;\n    }\n\n    public @Nullable CompoundTag getNormalizedWorldGenSettingsData() {\n        return normalizedWorldGenSettingsData;\n    }\n\n    public @Nullable CompoundTag getPlayerData() {\n        return playerData;\n    }\n\n    public long getLastPlayed() {\n        CompoundTag data = levelData.get(\"Data\");\n        LongTag lastPlayedTag = data.get(\"LastPlayed\");\n        return lastPlayedTag.getValue();\n    }\n\n    public @Nullable GameVersionNumber getGameVersion() {\n        if (levelData.get(\"Data\") instanceof CompoundTag data &&\n                data.get(\"Version\") instanceof CompoundTag versionTag &&\n                versionTag.get(\"Name\") instanceof StringTag nameTag) {\n            return GameVersionNumber.asGameVersion(nameTag.getValue());\n        }\n        return null;\n    }\n\n    public @Nullable Long getSeed() {\n        // Valid after 1.16(20w20a)\n        if (normalizedWorldGenSettingsData != null && normalizedWorldGenSettingsData.get(\"seed\") instanceof LongTag seedTag) {\n            return seedTag.getValue();\n        }\n        // Valid before 1.16(20w20a)\n        if (levelData.get(\"Data\") instanceof CompoundTag data && data.get(\"RandomSeed\") instanceof LongTag seedTag) {\n            return seedTag.getValue();\n        }\n        return null;\n    }\n\n    public boolean isLargeBiomes() {\n        CompoundTag data = levelData.get(\"Data\");\n\n        // Valid before 1.16(20w20a)\n        if (data.get(\"generatorName\") instanceof StringTag generatorNameTag) {\n            return \"largeBiomes\".equals(generatorNameTag.getValue());\n        }\n        // Unified handling of logic after version 1.16\n        else if (normalizedWorldGenSettingsData != null\n                && normalizedWorldGenSettingsData.get(\"dimensions\") instanceof CompoundTag dimensionsTag) {\n            if (dimensionsTag.get(\"minecraft:overworld\") instanceof CompoundTag overworldTag\n                    && overworldTag.get(\"generator\") instanceof CompoundTag generatorTag) {\n                // Valid between 1.16(20w20a) and 1.18(21w37a)\n                if (generatorTag.get(\"biome_source\") instanceof CompoundTag biomeSourceTag\n                        && biomeSourceTag.get(\"large_biomes\") instanceof ByteTag largeBiomesTag) {\n                    return largeBiomesTag.getValue() == (byte) 1;\n                }\n                // Valid after 1.18(21w37a)\n                else if (generatorTag.get(\"settings\") instanceof StringTag settingsTag) {\n                    return \"minecraft:large_biomes\".equals(settingsTag.getValue());\n                }\n            }\n        }\n        return false;\n    }\n\n    public Image getIcon() {\n        return icon;\n    }\n\n    public boolean isLocked() {\n        return isLocked(getSessionLockFile());\n    }\n\n    public boolean supportDatapacks() {\n        return getGameVersion() != null && getGameVersion().isAtLeast(\"1.13\", \"17w43a\");\n    }\n\n    public boolean supportQuickPlay() {\n        return getGameVersion() != null && getGameVersion().isAtLeast(\"1.20\", \"23w14a\");\n    }\n\n    public static boolean supportQuickPlay(GameVersionNumber gameVersionNumber) {\n        return gameVersionNumber != null && gameVersionNumber.isAtLeast(\"1.20\", \"23w14a\");\n    }\n\n    private void loadFromDirectory() throws IOException {\n        fileName = FileUtils.getName(file);\n        Path levelDat = file.resolve(\"level.dat\");\n        if (!Files.exists(levelDat)) { // version 20w14infinite\n            levelDat = file.resolve(\"special_level.dat\");\n        }\n        if (!Files.exists(levelDat)) {\n            throw new IOException(\"Not a valid world directory since level.dat or special_level.dat cannot be found.\");\n        }\n        this.levelDataPath = levelDat;\n        loadAndCheckWorldData();\n\n        Path iconFile = file.resolve(\"icon.png\");\n        if (Files.isRegularFile(iconFile)) {\n            try (InputStream inputStream = Files.newInputStream(iconFile)) {\n                icon = new Image(inputStream, 64, 64, true, false);\n                if (icon.isError())\n                    throw icon.getException();\n            } catch (Exception e) {\n                LOG.warning(\"Failed to load world icon\", e);\n            }\n        }\n    }\n\n    private void loadFromZipImpl(Path root) throws IOException {\n        Path levelDat = root.resolve(\"level.dat\");\n        if (!Files.exists(levelDat)) { //version 20w14infinite\n            levelDat = root.resolve(\"special_level.dat\");\n        }\n        if (!Files.exists(levelDat)) {\n            throw new IOException(\"Not a valid world zip file since level.dat or special_level.dat cannot be found.\");\n        }\n        loadAndCheckLevelData(levelDat);\n\n        Path iconFile = root.resolve(\"icon.png\");\n        if (Files.isRegularFile(iconFile)) {\n            try (InputStream inputStream = Files.newInputStream(iconFile)) {\n                icon = new Image(inputStream, 64, 64, true, false);\n                if (icon.isError())\n                    throw icon.getException();\n            } catch (Exception e) {\n                LOG.warning(\"Failed to load world icon\", e);\n            }\n        }\n    }\n\n    private void loadFromZip() throws IOException {\n        try (FileSystem fs = CompressingUtils.readonly(file).setAutoDetectEncoding(true).build()) {\n            Path levelDatPath = fs.getPath(\"/level.dat\");\n            if (Files.isRegularFile(levelDatPath)) {\n                fileName = FileUtils.getName(file);\n                loadFromZipImpl(fs.getPath(\"/\"));\n                return;\n            }\n\n            try (Stream<Path> stream = Files.list(fs.getPath(\"/\"))) {\n                Path root = stream.filter(Files::isDirectory).findAny()\n                        .orElseThrow(() -> new IOException(\"Not a valid world zip file\"));\n                fileName = FileUtils.getName(root);\n                loadFromZipImpl(root);\n            }\n        }\n    }\n\n    private void loadAndCheckWorldData() throws IOException {\n        loadAndCheckLevelData(levelDataPath);\n        loadOtherData();\n    }\n\n    private void loadAndCheckLevelData(Path levelDat) throws IOException {\n        this.levelData = readTag(levelDat);\n        CompoundTag data = levelData.get(\"Data\");\n        if (data == null)\n            throw new IOException(\"level.dat missing Data\");\n\n        if (!(data.get(\"LevelName\") instanceof StringTag))\n            throw new IOException(\"level.dat missing LevelName\");\n\n        if (!(data.get(\"LastPlayed\") instanceof LongTag))\n            throw new IOException(\"level.dat missing LastPlayed\");\n    }\n\n    private void loadOtherData() throws IOException {\n        if (!(levelData.get(\"Data\") instanceof CompoundTag data)) return;\n\n        Path worldGenSettingsDatPath = file.resolve(\"data/minecraft/world_gen_settings.dat\");\n        if (data.get(\"WorldGenSettings\") instanceof CompoundTag worldGenSettingsTag) {\n            setWorldGenSettingsData(null, worldGenSettingsTag, worldGenSettingsTag);\n        } else if (Files.isRegularFile(worldGenSettingsDatPath)) {\n            CompoundTag raw = readTag(worldGenSettingsDatPath);\n            if (raw.get(\"data\") instanceof CompoundTag compoundTag) {\n                setWorldGenSettingsData(worldGenSettingsDatPath, raw, compoundTag);\n            } else {\n                setWorldGenSettingsData(null, null, null);\n            }\n        } else {\n            setWorldGenSettingsData(null, null, null);\n        }\n\n        if (data.get(\"Player\") instanceof CompoundTag playerTag) {\n            setPlayerData(null, playerTag);\n        } else if (data.get(\"singleplayer_uuid\") instanceof IntArrayTag uuidTag && uuidTag.getValue().length == 4) {\n            int[] uuidValue = uuidTag.getValue();\n            long mostSigBits = ((long) uuidValue[0] << 32) | (uuidValue[1] & 0xFFFFFFFFL);\n            long leastSigBits = ((long) uuidValue[2] << 32) | (uuidValue[3] & 0xFFFFFFFFL);\n            String playerUUID = new UUID(mostSigBits, leastSigBits).toString();\n            Path playerDatPath = file.resolve(\"players/data/\" + playerUUID + \".dat\");\n            if (Files.exists(playerDatPath)) {\n                setPlayerData(playerDatPath, readTag(playerDatPath));\n            } else {\n                setPlayerData(null, null);\n            }\n        } else {\n            setPlayerData(null, null);\n        }\n    }\n\n    private void setWorldGenSettingsData(Path worldGenSettingsDataPath, CompoundTag worldGenSettingsDataBackingTag, CompoundTag unifiedWorldGenSettingsData) {\n        this.worldGenSettingsDataPath = worldGenSettingsDataPath;\n        this.worldGenSettingsDataBackingTag = worldGenSettingsDataBackingTag;\n        this.normalizedWorldGenSettingsData = unifiedWorldGenSettingsData;\n    }\n\n    private void setPlayerData(Path playerDataPath, CompoundTag playerData) {\n        this.playerDataPath = playerDataPath;\n        this.playerData = playerData;\n    }\n\n    public void reloadWorldData() throws IOException {\n        loadAndCheckWorldData();\n    }\n\n    // The rename method is used to rename temporary world object during installation and copying,\n    // so there is no need to modify the `file` field.\n    public void rename(String newName) throws IOException {\n        if (!Files.isDirectory(file))\n            throw new IOException(\"Not a valid world directory\");\n\n        // Change the name recorded in level.dat\n        CompoundTag data = levelData.get(\"Data\");\n        data.put(new StringTag(\"LevelName\", newName));\n        writeLevelData();\n\n        // then change the folder's name\n        Files.move(file, file.resolveSibling(newName));\n    }\n\n    public void install(Path savesDir, String name) throws IOException {\n        Path worldDir;\n        try {\n            worldDir = savesDir.resolve(name);\n        } catch (InvalidPathException e) {\n            throw new IOException(e);\n        }\n\n        if (Files.isDirectory(worldDir)) {\n            throw new FileAlreadyExistsException(\"World already exists\");\n        }\n\n        if (Files.isRegularFile(file)) {\n            try (FileSystem fs = CompressingUtils.readonly(file).setAutoDetectEncoding(true).build()) {\n                Path levelDatPath = fs.getPath(\"/level.dat\");\n                if (Files.isRegularFile(levelDatPath)) {\n                    fileName = FileUtils.getName(file);\n\n                    new Unzipper(file, worldDir).unzip();\n                } else {\n                    try (Stream<Path> stream = Files.list(fs.getPath(\"/\"))) {\n                        List<Path> subDirs = stream.toList();\n                        if (subDirs.size() != 1) {\n                            throw new IOException(\"World zip malformed\");\n                        }\n                        String subDirectoryName = FileUtils.getName(subDirs.get(0));\n                        new Unzipper(file, worldDir)\n                                .setSubDirectory(\"/\" + subDirectoryName + \"/\")\n                                .unzip();\n                    }\n                }\n\n            }\n            new World(worldDir).rename(name);\n        } else if (Files.isDirectory(file)) {\n            FileUtils.copyDirectory(file, worldDir);\n        }\n    }\n\n    public void export(Path zip, String worldName) throws IOException {\n        if (!Files.isDirectory(file))\n            throw new IOException();\n\n        try (Zipper zipper = new Zipper(zip)) {\n            zipper.putDirectory(file, worldName);\n        }\n    }\n\n    public void delete() throws IOException {\n        if (isLocked()) {\n            throw new WorldLockedException(\"The world \" + getFile() + \" has been locked\");\n        }\n        FileUtils.forceDelete(file);\n    }\n\n    public void copy(String newName) throws IOException {\n        if (!Files.isDirectory(file)) {\n            throw new IOException(\"Not a valid world directory\");\n        }\n\n        if (isLocked()) {\n            throw new WorldLockedException(\"The world \" + getFile() + \" has been locked\");\n        }\n\n        Path newPath = file.resolveSibling(newName);\n        FileUtils.copyDirectory(file, newPath, path -> !path.contains(\"session.lock\"));\n        World newWorld = new World(newPath);\n        newWorld.rename(newName);\n    }\n\n    public FileChannel lock() throws WorldLockedException {\n        Path lockFile = getSessionLockFile();\n        FileChannel channel = null;\n        try {\n            channel = FileChannel.open(lockFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE);\n            channel.write(ByteBuffer.wrap(\"\\u2603\".getBytes(StandardCharsets.UTF_8)));\n            channel.force(true);\n            FileLock fileLock = channel.tryLock();\n            if (fileLock != null) {\n                return channel;\n            } else {\n                IOUtils.closeQuietly(channel);\n                throw new WorldLockedException(\"The world \" + getFile() + \" has been locked\");\n            }\n        } catch (IOException e) {\n            IOUtils.closeQuietly(channel);\n            throw new WorldLockedException(e);\n        }\n    }\n\n    public void writeWorldData() throws IOException {\n        if (!Files.isDirectory(file)) throw new IOException(\"Not a valid world directory\");\n\n        writeLevelData();\n\n        if (worldGenSettingsDataPath != null && worldGenSettingsDataBackingTag != null) {\n            writeTag(worldGenSettingsDataBackingTag, worldGenSettingsDataPath);\n        }\n\n        if (playerDataPath != null && playerData != null) {\n            writeTag(playerData, playerDataPath);\n        }\n    }\n\n    public void writeLevelData() throws IOException {\n        writeTag(levelData, levelDataPath);\n    }\n\n    private CompoundTag readTag(Path path) throws IOException {\n        try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) {\n            Tag tag = NBTIO.readTag(is);\n            if (tag instanceof CompoundTag compoundTag) return compoundTag;\n            throw new IOException(\"NBT file malformed: \" + path);\n        }\n    }\n\n    private void writeTag(CompoundTag nbt, Path path) throws IOException {\n        if (!Files.isDirectory(file)) throw new IOException(\"Not a valid world directory\");\n        FileUtils.saveSafely(path, os -> {\n            try (OutputStream gos = new GZIPOutputStream(os)) {\n                NBTIO.writeTag(gos, nbt);\n            }\n        });\n    }\n\n    private static boolean isLocked(Path sessionLockFile) {\n        try (FileChannel fileChannel = FileChannel.open(sessionLockFile, StandardOpenOption.WRITE)) {\n            return fileChannel.tryLock() == null;\n        } catch (AccessDeniedException | OverlappingFileLockException accessDeniedException) {\n            return true;\n        } catch (NoSuchFileException noSuchFileException) {\n            return false;\n        } catch (IOException e) {\n            LOG.warning(\"Failed to open the lock file \" + sessionLockFile, e);\n            return false;\n        }\n    }\n\n    public static List<World> getWorlds(Path savesDir) {\n        if (Files.exists(savesDir)) {\n            try (Stream<Path> stream = Files.list(savesDir)) {\n                return stream\n                        .filter(Files::isDirectory)\n                        .flatMap(world -> {\n                            try {\n                                return Stream.of(new World(world.toAbsolutePath().normalize()));\n                            } catch (IOException e) {\n                                LOG.warning(\"Failed to read world \" + world, e);\n                                return Stream.empty();\n                            }\n                        })\n                        .toList();\n            } catch (IOException e) {\n                LOG.warning(\"Failed to read saves\", e);\n            }\n        }\n        return List.of();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/WorldLockedException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport java.io.IOException;\n\n/**\n * @author Glavo\n */\npublic final class WorldLockedException extends IOException {\n    public WorldLockedException() {\n    }\n\n    public WorldLockedException(String message) {\n        super(message);\n    }\n\n    public WorldLockedException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public WorldLockedException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherLibrary.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game.tlauncher;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.game.CompatibilityRule;\nimport org.jackhuang.hmcl.game.ExtractRules;\nimport org.jackhuang.hmcl.game.LibrariesDownloadInfo;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.LibraryDownloadInfo;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\n\nimport java.util.List;\nimport java.util.Map;\n\n@Immutable\n@JsonSerializable\npublic final class TLauncherLibrary {\n\n    @SerializedName(\"name\")\n    private final Artifact name;\n    private final String url;\n    private final LibraryDownloadInfo artifact;\n\n    @SerializedName(\"classifies\") // stupid typo made by TLauncher\n    private final Map<String, LibraryDownloadInfo> classifiers;\n    private final ExtractRules extract;\n    private final Map<String, String> natives;\n    private final List<CompatibilityRule> rules;\n    private final List<String> checksums;\n\n    public TLauncherLibrary(Artifact name, String url, LibraryDownloadInfo artifact, Map<String, LibraryDownloadInfo> classifiers, ExtractRules extract, Map<String, String> natives, List<CompatibilityRule> rules, List<String> checksums) {\n        this.name = name;\n        this.url = url;\n        this.artifact = artifact;\n        this.classifiers = classifiers;\n        this.extract = extract;\n        this.natives = natives;\n        this.rules = rules;\n        this.checksums = checksums;\n    }\n\n    public Library toLibrary() {\n        return new Library(\n                name,\n                url,\n                new LibrariesDownloadInfo(artifact, classifiers),\n                checksums,\n                extract,\n                natives,\n                rules,\n                null,\n                null\n        );\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game.tlauncher;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.util.gson.JsonMap;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic final class TLauncherVersion implements Validation {\n\n    private final String id;\n    private final String minecraftArguments;\n    private final Arguments arguments;\n    private final String mainClass;\n    private final String inheritsFrom;\n    private final String jar;\n    private final AssetIndexInfo assetIndex;\n    private final String assets;\n    private final Integer complianceLevel;\n    @Nullable\n    private final GameJavaVersion javaVersion;\n    private final List<TLauncherLibrary> libraries;\n    private final List<CompatibilityRule> compatibilityRules;\n    private final JsonMap<DownloadType, DownloadInfo> downloads;\n    private final JsonMap<DownloadType, LoggingInfo> logging;\n    private final ReleaseType type;\n    private final Instant time;\n    private final Instant releaseTime;\n    private final Integer minimumLauncherVersion;\n    private final Integer tlauncherVersion;\n\n    public TLauncherVersion(String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, Integer complianceLevel, @Nullable GameJavaVersion javaVersion, List<TLauncherLibrary> libraries, List<CompatibilityRule> compatibilityRules, JsonMap<DownloadType, DownloadInfo> downloads, JsonMap<DownloadType, LoggingInfo> logging, ReleaseType type, Instant time, Instant releaseTime, Integer minimumLauncherVersion, Integer tlauncherVersion) {\n        this.id = id;\n        this.minecraftArguments = minecraftArguments;\n        this.arguments = arguments;\n        this.mainClass = mainClass;\n        this.inheritsFrom = inheritsFrom;\n        this.jar = jar;\n        this.assetIndex = assetIndex;\n        this.assets = assets;\n        this.complianceLevel = complianceLevel;\n        this.javaVersion = javaVersion;\n        this.libraries = libraries;\n        this.compatibilityRules = compatibilityRules;\n        this.downloads = downloads;\n        this.logging = logging;\n        this.type = type;\n        this.time = time;\n        this.releaseTime = releaseTime;\n        this.minimumLauncherVersion = minimumLauncherVersion;\n        this.tlauncherVersion = tlauncherVersion;\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        Validation.requireNonNull(tlauncherVersion, \"Not TLauncher version json format\");\n    }\n\n    public Version toVersion() {\n        return new Version(\n                false,\n                id,\n                null,\n                null,\n                minecraftArguments,\n                arguments,\n                mainClass,\n                inheritsFrom,\n                jar,\n                assetIndex,\n                assets,\n                complianceLevel,\n                javaVersion,\n                libraries == null ? null : libraries.stream().map(TLauncherLibrary::toLibrary).collect(Collectors.toList()),\n                compatibilityRules,\n                downloads,\n                logging,\n                type,\n                time,\n                releaseTime,\n                minimumLauncherVersion,\n                null,\n                null,\n                null\n        );\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.java;\n\nimport kala.compress.archivers.ArchiveEntry;\nimport org.jackhuang.hmcl.util.KeyValuePairUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jackhuang.hmcl.util.tree.ArchiveFileTree;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Map;\n\n/**\n * @author Glavo\n */\npublic final class JavaInfo {\n\n    public static int parseVersion(String version) {\n        int startIndex = version.startsWith(\"1.\") ? 2 : 0;\n        int endIndex = startIndex;\n\n        while (endIndex < version.length()) {\n            char ch = version.charAt(endIndex);\n            if (ch >= '0' && ch <= '9')\n                endIndex++;\n            else\n                break;\n        }\n\n        try {\n            return endIndex > startIndex ? Integer.parseInt(version.substring(startIndex, endIndex)) : -1;\n        } catch (Throwable e) {\n            // The version number is too long\n            return -1;\n        }\n    }\n\n    public static JavaInfo fromReleaseFile(BufferedReader reader) throws IOException {\n        Map<String, String> properties = KeyValuePairUtils.loadProperties(reader);\n        String osName = properties.get(\"OS_NAME\");\n        String osArch = properties.get(\"OS_ARCH\");\n        String vendor = properties.get(\"IMPLEMENTOR\");\n\n        OperatingSystem os = \"\".equals(osName) && \"OpenJDK BSD Porting Team\".equals(vendor)\n                ? OperatingSystem.FREEBSD\n                : OperatingSystem.parseOSName(osName);\n\n        Architecture arch = Architecture.parseArchName(osArch);\n        String javaVersion = properties.get(\"JAVA_VERSION\");\n\n        if (os == OperatingSystem.UNKNOWN)\n            throw new IOException(\"Unknown operating system: \" + osName);\n\n        if (arch == Architecture.UNKNOWN)\n            throw new IOException(\"Unknown architecture: \" + osArch);\n\n        if (javaVersion == null)\n            throw new IOException(\"Missing Java version\");\n\n        return new JavaInfo(Platform.getPlatform(os, arch), javaVersion, vendor);\n    }\n\n    public static JavaInfo fromReleaseFile(Path releaseFile) throws IOException {\n        try (BufferedReader reader = Files.newBufferedReader(releaseFile)) {\n            return fromReleaseFile(reader);\n        }\n    }\n\n    public static <F, E extends ArchiveEntry> JavaInfo fromArchive(ArchiveFileTree<F, E> tree) throws IOException {\n        if (tree.getRoot().getSubDirs().size() != 1 || !tree.getRoot().getFiles().isEmpty())\n            throw new IOException();\n\n        ArchiveFileTree.Dir<E> jdkRoot = tree.getRoot().getSubDirs().values().iterator().next();\n        E releaseEntry = jdkRoot.getFiles().get(\"release\");\n        if (releaseEntry == null)\n            throw new IOException(\"Missing release file\");\n\n        JavaInfo info;\n        try (BufferedReader reader = new BufferedReader(new InputStreamReader(tree.getInputStream(releaseEntry), StandardCharsets.UTF_8))) {\n            info = JavaInfo.fromReleaseFile(reader);\n        }\n\n        ArchiveFileTree.Dir<E> binDir = jdkRoot.getSubDirs().get(\"bin\");\n        if (binDir == null || binDir.getFiles().get(info.getPlatform().getOperatingSystem().getJavaExecutable()) == null)\n            throw new IOException(\"Missing java executable file\");\n\n        return info;\n    }\n\n    public static String normalizeVendor(String vendor) {\n        if (vendor == null)\n            return null;\n\n        switch (vendor) {\n            case \"N/A\":\n                return null;\n            case \"Oracle Corporation\":\n                return \"Oracle\";\n            case \"Azul Systems, Inc.\":\n                return \"Azul\";\n            case \"IBM Corporation\":\n            case \"International Business Machines Corporation\":\n            case \"Eclipse OpenJ9\":\n                return \"IBM\";\n            case \"Eclipse Adoptium\":\n                return \"Adoptium\";\n            case \"Amazon.com Inc.\":\n                return \"Amazon\";\n            default:\n                return vendor;\n        }\n    }\n\n    public static final JavaInfo CURRENT_ENVIRONMENT = new JavaInfo(Platform.CURRENT_PLATFORM, System.getProperty(\"java.version\"), System.getProperty(\"java.vendor\"));\n\n    private final Platform platform;\n    private final String version;\n    private final @Nullable String vendor;\n\n    private final transient int parsedVersion;\n    private final transient VersionNumber versionNumber;\n\n    public JavaInfo(Platform platform, String version, @Nullable String vendor) {\n        this.platform = platform;\n        this.version = version;\n        this.parsedVersion = parseVersion(version);\n        this.versionNumber = VersionNumber.asVersion(version);\n        this.vendor = vendor;\n    }\n\n    public Platform getPlatform() {\n        return platform;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public VersionNumber getVersionNumber() {\n        return versionNumber;\n    }\n\n    public int getParsedVersion() {\n        return parsedVersion;\n    }\n\n    public @Nullable String getVendor() {\n        return vendor;\n    }\n\n    @Override\n    public String toString() {\n        return JsonUtils.GSON.toJson(this);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.java;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.game.GameJavaVersion;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.platform.Platform;\n\nimport java.nio.file.Path;\nimport java.util.Collection;\n\n/**\n * @author Glavo\n */\npublic interface JavaRepository {\n\n    Path getJavaDir(Platform platform, String name);\n\n    Path getManifestFile(Platform platform, String name);\n\n    Collection<JavaRuntime> getAllJava(Platform platform);\n\n    Task<JavaRuntime> getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion);\n\n    Task<Void> getUninstallJavaTask(Platform platform, String name);\n\n    Task<Void> getUninstallJavaTask(JavaRuntime java);\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRuntime.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.java;\n\nimport org.jackhuang.hmcl.util.platform.Architecture;\nimport org.jackhuang.hmcl.util.platform.Bits;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.Platform;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\n/**\n * @author Glavo\n */\npublic final class JavaRuntime implements Comparable<JavaRuntime> {\n\n    public static JavaRuntime of(Path binary, JavaInfo info, boolean isManaged) {\n        String javacName = info.getPlatform().getOperatingSystem() == OperatingSystem.WINDOWS ? \"javac.exe\" : \"javac\";\n        return new JavaRuntime(binary, info, isManaged, Files.isRegularFile(binary.resolveSibling(javacName)));\n    }\n\n    private final Path binary;\n    private final JavaInfo info;\n    private final boolean isManaged;\n    private final boolean isJDK;\n\n    public JavaRuntime(Path binary, JavaInfo info, boolean isManaged, boolean isJDK) {\n        this.binary = binary;\n        this.info = info;\n        this.isManaged = isManaged;\n        this.isJDK = isJDK;\n    }\n\n    public boolean isManaged() {\n        return isManaged;\n    }\n\n    public Path getBinary() {\n        return binary;\n    }\n\n    public String getVersion() {\n        return info.getVersion();\n    }\n\n    public Platform getPlatform() {\n        return info.getPlatform();\n    }\n\n    public Architecture getArchitecture() {\n        return getPlatform().getArchitecture();\n    }\n\n    public Bits getBits() {\n        return getPlatform().getBits();\n    }\n\n    public VersionNumber getVersionNumber() {\n        return info.getVersionNumber();\n    }\n\n    /**\n     * The major version of Java installation.\n     */\n    public int getParsedVersion() {\n        return info.getParsedVersion();\n    }\n\n    public String getVendor() {\n        return info.getVendor();\n    }\n\n    public boolean isJDK() {\n        return isJDK;\n    }\n\n    @Override\n    public int compareTo(@NotNull JavaRuntime that) {\n        if (this.isManaged != that.isManaged) {\n            return this.isManaged ? -1 : 1;\n        }\n\n        int c = Integer.compare(this.getParsedVersion(), that.getParsedVersion());\n        if (c != 0)\n            return c;\n\n        c = this.getVersionNumber().compareTo(that.getVersionNumber());\n        if (c != 0)\n            return c;\n\n        c = this.getArchitecture().compareTo(that.getArchitecture());\n        if (c != 0)\n            return c;\n\n        return this.getBinary().compareTo(that.getBinary());\n    }\n\n    @Override\n    public int hashCode() {\n        return binary.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (!(o instanceof JavaRuntime)) return false;\n        JavaRuntime that = (JavaRuntime) o;\n        return this.getBinary().equals(that.getBinary());\n    }\n\n    public static final JavaRuntime CURRENT_JAVA;\n    public static final int CURRENT_VERSION;\n    public static final boolean CURRENT_JIT_ENABLED;\n\n    public static JavaRuntime getDefault() {\n        return CURRENT_JAVA;\n    }\n\n    static {\n        String javaHome = System.getProperty(\"java.home\");\n        Path executable = null;\n        if (javaHome != null) {\n            executable = Paths.get(javaHome, \"bin\", OperatingSystem.CURRENT_OS.getJavaExecutable());\n            try {\n                executable = executable.toRealPath();\n            } catch (IOException ignored) {\n            }\n\n            if (!Files.isRegularFile(executable)) {\n                executable = null;\n            }\n        }\n\n        CURRENT_JAVA = executable != null ? JavaRuntime.of(executable, JavaInfo.CURRENT_ENVIRONMENT, false) : null;\n        CURRENT_VERSION = JavaInfo.CURRENT_ENVIRONMENT.getParsedVersion();\n\n        String vmInfo = System.getProperty(\"java.vm.info\", \"\");\n        CURRENT_JIT_ENABLED = !vmInfo.contains(\"interpreted mode\") // HotSpot\n                && !vmInfo.contains(\"JIT disabled\"); // J9\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/CommandTooLongException.java",
    "content": "package org.jackhuang.hmcl.launch;\n\npublic class CommandTooLongException extends RuntimeException {\n    public CommandTooLongException() {\n    }\n\n    public CommandTooLongException(String message) {\n        super(message);\n    }\n\n    public CommandTooLongException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public CommandTooLongException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.launch;\n\nimport org.jackhuang.hmcl.auth.AuthInfo;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.*;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.ServerAddress;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.Unzipper;\nimport org.jackhuang.hmcl.util.platform.*;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.StandardCopyOption;\nimport java.util.*;\nimport java.util.function.Supplier;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huangyuhui\n */\npublic class DefaultLauncher extends Launcher {\n\n    private final LibraryAnalyzer analyzer;\n\n    public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {\n        this(repository, version, authInfo, options, null);\n    }\n\n    public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {\n        this(repository, version, authInfo, options, listener, true);\n    }\n\n    public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {\n        super(repository, version, authInfo, options, listener, daemon);\n\n        this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));\n    }\n\n    private Command generateCommandLine(Path nativeFolder) throws IOException {\n        CommandBuilder res = new CommandBuilder();\n\n        switch (options.getProcessPriority()) {\n            case HIGH:\n                if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                    // res.add(\"cmd\", \"/C\", \"start\", \"unused title\", \"/B\", \"/high\");\n                } else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n                    res.addAll(\"nice\", \"-n\", \"-5\");\n                }\n                break;\n            case ABOVE_NORMAL:\n                if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                    // res.add(\"cmd\", \"/C\", \"start\", \"unused title\", \"/B\", \"/abovenormal\");\n                } else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n                    res.addAll(\"nice\", \"-n\", \"-1\");\n                }\n                break;\n            case NORMAL:\n                // do nothing\n                break;\n            case BELOW_NORMAL:\n                if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                    // res.add(\"cmd\", \"/C\", \"start\", \"unused title\", \"/B\", \"/belownormal\");\n                } else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n                    res.addAll(\"nice\", \"-n\", \"1\");\n                }\n                break;\n            case LOW:\n                if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                    // res.add(\"cmd\", \"/C\", \"start\", \"unused title\", \"/B\", \"/low\");\n                } else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n                    res.addAll(\"nice\", \"-n\", \"5\");\n                }\n                break;\n        }\n\n        // Executable\n        if (StringUtils.isNotBlank(options.getWrapper()))\n            res.addAllWithoutParsing(StringUtils.tokenize(options.getWrapper(), getEnvVars()));\n\n        res.add(options.getJava().getBinary().toString());\n\n        res.addAllWithoutParsing(options.getOverrideJavaArguments());\n\n        if (options.getMaxMemory() != null && options.getMaxMemory() > 0)\n            res.addDefault(\"-Xmx\", options.getMaxMemory() + \"m\");\n\n        if (options.getMinMemory() != null && options.getMinMemory() > 0\n                && (options.getMaxMemory() == null || options.getMinMemory() <= options.getMaxMemory()))\n            res.addDefault(\"-Xms\", options.getMinMemory() + \"m\");\n\n        if (options.getMetaspace() != null && options.getMetaspace() > 0)\n            if (options.getJava().getParsedVersion() < 8)\n                res.addDefault(\"-XX:PermSize=\", options.getMetaspace() + \"m\");\n            else\n                res.addDefault(\"-XX:MetaspaceSize=\", options.getMetaspace() + \"m\");\n\n        res.addAllDefaultWithoutParsing(options.getJavaArguments());\n\n        Charset encoding = OperatingSystem.NATIVE_CHARSET;\n        String fileEncoding = res.addDefault(\"-Dfile.encoding=\", encoding.name());\n        if (fileEncoding != null && !\"-Dfile.encoding=COMPAT\".equals(fileEncoding)) {\n            try {\n                encoding = Charset.forName(fileEncoding.substring(\"-Dfile.encoding=\".length()));\n            } catch (Throwable ex) {\n                LOG.warning(\"Bad file encoding\", ex);\n            }\n        }\n\n        if (options.getJava().getParsedVersion() < 19) {\n            res.addDefault(\"-Dsun.stdout.encoding=\", encoding.name());\n            res.addDefault(\"-Dsun.stderr.encoding=\", encoding.name());\n        } else {\n            res.addDefault(\"-Dstdout.encoding=\", encoding.name());\n            res.addDefault(\"-Dstderr.encoding=\", encoding.name());\n        }\n\n        // Fix RCE vulnerability of log4j2\n        res.addDefault(\"-Djava.rmi.server.useCodebaseOnly=\", \"true\");\n        res.addDefault(\"-Dcom.sun.jndi.rmi.object.trustURLCodebase=\", \"false\");\n        res.addDefault(\"-Dcom.sun.jndi.cosnaming.object.trustURLCodebase=\", \"false\");\n\n        String formatMsgNoLookups = res.addDefault(\"-Dlog4j2.formatMsgNoLookups=\", \"true\");\n        if (isUsingLog4j() && (options.isEnableDebugLogOutput() || !\"-Dlog4j2.formatMsgNoLookups=false\".equals(formatMsgNoLookups))) {\n            res.addDefault(\"-Dlog4j.configurationFile=\", FileUtils.getAbsolutePath(getLog4jConfigurationFile()));\n        }\n\n        // Default JVM Args\n        if (!options.isNoGeneratedJVMArgs()) {\n            appendJvmArgs(res);\n\n            res.addDefault(\"-Dminecraft.client.jar=\", FileUtils.getAbsolutePath(repository.getVersionJar(version)));\n\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n                res.addDefault(\"-Xdock:name=\", \"Minecraft \" + version.getId());\n                repository.getAssetObject(version.getId(), version.getAssetIndex().getId(), \"icons/minecraft.icns\")\n                        .ifPresent(minecraftIcns -> {\n                            res.addDefault(\"-Xdock:icon=\", FileUtils.getAbsolutePath(minecraftIcns));\n                        });\n            }\n\n            if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)\n                res.addDefault(\"-Duser.home=\", options.getGameDir().toAbsolutePath().getParent().toString());\n\n            boolean addProxyOptions = res.noneMatch(arg ->\n                    arg.startsWith(\"-Djava.net.useSystemProxies=\")\n                            || arg.startsWith(\"-Dhttp.proxy\")\n                            || arg.startsWith(\"-Dhttps.proxy\")\n                            || arg.startsWith(\"-DsocksProxy\")\n                            || arg.startsWith(\"-Djava.net.socks.\")\n            );\n\n            if (addProxyOptions) {\n                if (options.getProxyOption() == null || options.getProxyOption() == ProxyOption.Default.INSTANCE) {\n                    res.add(\"-Djava.net.useSystemProxies=true\");\n                } else if (options.getProxyOption() instanceof ProxyOption.Http httpProxy) {\n                    res.add(\"-Dhttp.proxyHost=\" + httpProxy.host());\n                    res.add(\"-Dhttp.proxyPort=\" + httpProxy.port());\n                    res.add(\"-Dhttps.proxyHost=\" + httpProxy.host());\n                    res.add(\"-Dhttps.proxyPort=\" + httpProxy.port());\n\n                    if (StringUtils.isNotBlank(httpProxy.username())) {\n                        res.add(\"-Dhttp.proxyUser=\" + httpProxy.username());\n                        res.add(\"-Dhttp.proxyPassword=\" + Objects.requireNonNullElse(httpProxy.password(), \"\"));\n                        res.add(\"-Dhttps.proxyUser=\" + httpProxy.username());\n                        res.add(\"-Dhttps.proxyPassword=\" + Objects.requireNonNullElse(httpProxy.password(), \"\"));\n                    }\n                } else if (options.getProxyOption() instanceof ProxyOption.Socks socksProxy) {\n                    res.add(\"-DsocksProxyHost=\" + socksProxy.host());\n                    res.add(\"-DsocksProxyPort=\" + socksProxy.port());\n\n                    if (StringUtils.isNotBlank(socksProxy.username())) {\n                        res.add(\"-Djava.net.socks.username=\" + socksProxy.username());\n                        res.add(\"-Djava.net.socks.password=\" + Objects.requireNonNullElse(socksProxy.password(), \"\"));\n                    }\n                }\n            }\n\n            final int javaVersion = options.getJava().getParsedVersion();\n            final boolean is64bit = options.getJava().getBits() == Bits.BIT_64;\n\n            if (!options.isNoGeneratedOptimizingJVMArgs()) {\n                res.addUnstableDefault(\"UnlockExperimentalVMOptions\", true);\n                res.addUnstableDefault(\"UnlockDiagnosticVMOptions\", true);\n\n                // Using G1GC with its settings by default\n                if (javaVersion >= 8\n                        && res.noneMatch(arg -> \"-XX:-UseG1GC\".equals(arg) || (arg.startsWith(\"-XX:+Use\") && arg.endsWith(\"GC\")))) {\n                    res.addUnstableDefault(\"UseG1GC\", true);\n                    res.addUnstableDefault(\"G1MixedGCCountTarget\", \"5\");\n                    res.addUnstableDefault(\"G1NewSizePercent\", \"20\");\n                    res.addUnstableDefault(\"G1ReservePercent\", \"20\");\n                    res.addUnstableDefault(\"MaxGCPauseMillis\", \"50\");\n                    res.addUnstableDefault(\"G1HeapRegionSize\", \"32m\");\n                }\n\n                res.addUnstableDefault(\"OmitStackTraceInFastThrow\", false);\n\n                // JIT Options\n                if (javaVersion <= 8) {\n                    res.addUnstableDefault(\"MaxInlineLevel\", \"15\");\n                }\n                if (is64bit && SystemInfo.getTotalMemorySize() > 4L * 1024 * 1024 * 1024) {\n                    res.addUnstableDefault(\"DontCompileHugeMethods\", false);\n                    res.addUnstableDefault(\"MaxNodeLimit\", \"240000\");\n                    res.addUnstableDefault(\"NodeLimitFudgeFactor\", \"8000\");\n                    res.addUnstableDefault(\"TieredCompileTaskTimeout\", \"10000\");\n                    res.addUnstableDefault(\"ReservedCodeCacheSize\", \"400M\");\n                    if (javaVersion >= 9) {\n                        res.addUnstableDefault(\"NonNMethodCodeHeapSize\", \"12M\");\n                        res.addUnstableDefault(\"ProfiledCodeHeapSize\", \"194M\");\n                    }\n\n                    if (javaVersion >= 8) {\n                        res.addUnstableDefault(\"NmethodSweepActivity\", \"1\");\n                    }\n                }\n\n                if (is64bit && (javaVersion >= 25 && javaVersion <= 26)) {\n                    res.addUnstableDefault(\"UseCompactObjectHeaders\", true);\n                }\n\n                // As 32-bit JVM allocate 320KB for stack by default rather than 64-bit version allocating 1MB,\n                // causing Minecraft 1.13 crashed accounting for java.lang.StackOverflowError.\n                if (!is64bit) {\n                    res.addDefault(\"-Xss\", \"1m\");\n                }\n            }\n\n            if (javaVersion == 16)\n                res.addDefault(\"--illegal-access=\", \"permit\");\n\n            if (javaVersion == 24 || javaVersion == 25)\n                res.addDefault(\"--sun-misc-unsafe-memory-access=\", \"allow\");\n\n            res.addDefault(\"-Dfml.ignoreInvalidMinecraftCertificates=\", \"true\");\n            res.addDefault(\"-Dfml.ignorePatchDiscrepancies=\", \"true\");\n        }\n\n        Set<String> classpath = repository.getClasspath(version);\n\n        if (analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)) {\n            classpath.removeIf(c -> c.contains(\"2.9.4-nightly-20150209\"));\n        }\n\n        Path jar = repository.getVersionJar(version);\n        if (!Files.isRegularFile(jar))\n            throw new IOException(\"Minecraft jar does not exist\");\n        classpath.add(FileUtils.getAbsolutePath(jar.toAbsolutePath()));\n\n        // Provided Minecraft arguments\n        Path gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId());\n        Map<String, String> configuration = getConfigurations();\n        configuration.put(\"${classpath}\", String.join(File.pathSeparator, classpath));\n        configuration.put(\"${game_assets}\", FileUtils.getAbsolutePath(gameAssets));\n        configuration.put(\"${assets_root}\", FileUtils.getAbsolutePath(gameAssets));\n\n        Optional<String> gameVersion = repository.getGameVersion(version);\n\n        // lwjgl assumes path to native libraries encoded by ASCII.\n        // Here is a workaround for this issue: https://github.com/HMCL-dev/HMCL/issues/1141.\n        String nativeFolderPath = FileUtils.getAbsolutePath(nativeFolder);\n        Path tempNativeFolder = null;\n        if ((OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)\n                && !StringUtils.isASCII(nativeFolderPath)\n                && gameVersion.isPresent() && GameVersionNumber.compare(gameVersion.get(), \"1.19\") < 0) {\n            tempNativeFolder = Paths.get(\"/\", \"tmp\", \"hmcl-natives-\" + UUID.randomUUID());\n            nativeFolderPath = tempNativeFolder + File.pathSeparator + nativeFolderPath;\n        }\n        configuration.put(\"${natives_directory}\", nativeFolderPath);\n\n        res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration));\n        Arguments argumentsFromAuthInfo = authInfo.getLaunchArguments(options);\n        if (argumentsFromAuthInfo != null && argumentsFromAuthInfo.getJvm() != null && !argumentsFromAuthInfo.getJvm().isEmpty())\n            res.addAll(Arguments.parseArguments(argumentsFromAuthInfo.getJvm(), configuration));\n\n        for (String javaAgent : options.getJavaAgents()) {\n            res.add(\"-javaagent:\" + javaAgent);\n        }\n\n        res.add(version.getMainClass());\n\n        res.addAll(Arguments.parseStringArguments(version.getMinecraftArguments().map(StringUtils::tokenize).orElseGet(ArrayList::new), configuration));\n\n        Map<String, Boolean> features = getFeatures();\n        version.getArguments().map(Arguments::getGame).ifPresent(arguments -> res.addAll(Arguments.parseArguments(arguments, configuration, features)));\n        if (version.getMinecraftArguments().isPresent()) {\n            res.addAll(Arguments.parseArguments(this.getDefaultGameArguments(), configuration, features));\n        }\n        if (argumentsFromAuthInfo != null && argumentsFromAuthInfo.getGame() != null && !argumentsFromAuthInfo.getGame().isEmpty())\n            res.addAll(Arguments.parseArguments(argumentsFromAuthInfo.getGame(), configuration, features));\n\n        if (options.getQuickPlayOption() instanceof QuickPlayOption.MultiPlayer multiPlayer) {\n            String address = multiPlayer.serverIP();\n\n            try {\n                ServerAddress parsed = ServerAddress.parse(address);\n                if (World.supportQuickPlay(GameVersionNumber.asGameVersion(gameVersion))) {\n                    res.add(\"--quickPlayMultiplayer\");\n                    res.add(parsed.getPort() >= 0 ? address : parsed.getHost() + \":25565\");\n                } else {\n                    res.add(\"--server\");\n                    res.add(parsed.getHost());\n                    res.add(\"--port\");\n                    res.add(parsed.getPort() >= 0 ? String.valueOf(parsed.getPort()) : \"25565\");\n                }\n            } catch (IllegalArgumentException e) {\n                LOG.warning(\"Invalid server address: \" + address, e);\n            }\n        } else if (options.getQuickPlayOption() instanceof QuickPlayOption.SinglePlayer singlePlayer\n                && World.supportQuickPlay(GameVersionNumber.asGameVersion(gameVersion))) {\n            res.add(\"--quickPlaySingleplayer\");\n            res.add(singlePlayer.worldFolderName());\n        } else if (options.getQuickPlayOption() instanceof QuickPlayOption.Realm realm\n                && World.supportQuickPlay(GameVersionNumber.asGameVersion(gameVersion))) {\n            res.add(\"--quickPlayRealms\");\n            res.add(realm.realmID());\n        }\n\n        if (options.isFullscreen())\n            res.add(\"--fullscreen\");\n\n        // https://github.com/HMCL-dev/HMCL/issues/774\n        if (options.getProxyOption() instanceof ProxyOption.Socks socksProxy) {\n            res.add(\"--proxyHost\");\n            res.add(socksProxy.host());\n            res.add(\"--proxyPort\");\n            res.add(String.valueOf(socksProxy.port()));\n            if (StringUtils.isNotBlank(socksProxy.username())) {\n                res.add(\"--proxyUser\");\n                res.add(socksProxy.username());\n                res.add(\"--proxyPass\");\n                res.add(Objects.requireNonNullElse(socksProxy.password(), \"\"));\n            }\n        }\n\n        res.addAllWithoutParsing(Arguments.parseStringArguments(options.getGameArguments(), configuration));\n\n        res.removeIf(it -> getForbiddens().containsKey(it) && getForbiddens().get(it).get());\n        return new Command(res, tempNativeFolder, encoding);\n    }\n\n    public Map<String, Boolean> getFeatures() {\n        return Collections.singletonMap(\n                \"has_custom_resolution\",\n                options.getHeight() != null && options.getHeight() != 0 && options.getWidth() != null && options.getWidth() != 0\n        );\n    }\n\n    private final Map<String, Supplier<Boolean>> forbiddens = mapOf(\n            pair(\"-Xincgc\", () -> options.getJava().getParsedVersion() >= 9)\n    );\n\n    protected Map<String, Supplier<Boolean>> getForbiddens() {\n        return forbiddens;\n    }\n\n    protected List<Argument> getDefaultJVMArguments() {\n        return Arguments.DEFAULT_JVM_ARGUMENTS;\n    }\n\n    protected List<Argument> getDefaultGameArguments() {\n        return Arguments.DEFAULT_GAME_ARGUMENTS;\n    }\n\n    /**\n     * Do something here.\n     * i.e.\n     * -Dminecraft.launcher.version=&lt;Your launcher name&gt;\n     * -Dminecraft.launcher.brand=&lt;Your launcher version&gt;\n     * -Dlog4j.configurationFile=&lt;Your custom log4j configuration&gt;\n     */\n    protected void appendJvmArgs(CommandBuilder result) {\n    }\n\n    public void decompressNatives(Path destination) throws NotDecompressingNativesException {\n        try {\n            FileUtils.cleanDirectoryQuietly(destination);\n            for (Library library : version.getLibraries())\n                if (library.isNative())\n                    new Unzipper(repository.getLibraryFile(version, library), destination)\n                            .setFilter((zipEntry, destFile, relativePath) -> {\n                                if (!zipEntry.isDirectory() && !zipEntry.isUnixSymlink()\n                                        && Files.isRegularFile(destFile)\n                                        && zipEntry.getSize() == Files.size(destFile)) {\n                                    return false;\n                                }\n                                String ext = FileUtils.getExtension(destFile);\n                                if (ext.equals(\"sha1\") || ext.equals(\"git\"))\n                                    return false;\n\n                                if (options.isUseNativeGLFW() && FileUtils.getName(destFile).toLowerCase(Locale.ROOT).contains(\"glfw\")) {\n                                    return false;\n                                }\n                                if (options.isUseNativeOpenAL() && FileUtils.getName(destFile).toLowerCase(Locale.ROOT).contains(\"openal\")) {\n                                    return false;\n                                }\n\n                                return library.getExtract().shouldExtract(relativePath);\n                            })\n                            .setReplaceExistentFile(false).unzip();\n        } catch (IOException e) {\n            throw new NotDecompressingNativesException(e);\n        }\n    }\n\n    private boolean isUsingLog4j() {\n        return GameVersionNumber.compare(repository.getGameVersion(version).orElse(\"1.7\"), \"1.7\") >= 0;\n    }\n\n    public Path getLog4jConfigurationFile() {\n        return repository.getVersionRoot(version.getId()).resolve(\"log4j2.xml\");\n    }\n\n    public void extractLog4jConfigurationFile() throws IOException {\n        Path targetFile = getLog4jConfigurationFile();\n\n        String sourcePath;\n\n        if (GameVersionNumber.asGameVersion(repository.getGameVersion(version)).compareTo(\"1.12\") < 0) {\n            if (options.isEnableDebugLogOutput()) {\n                sourcePath = \"/assets/game/log4j2-1.7-debug.xml\";\n            } else {\n                sourcePath = \"/assets/game/log4j2-1.7.xml\";\n            }\n        } else {\n            if (options.isEnableDebugLogOutput()) {\n                sourcePath = \"/assets/game/log4j2-1.12-debug.xml\";\n            } else {\n                sourcePath = \"/assets/game/log4j2-1.12.xml\";\n            }\n        }\n\n        try (InputStream input = DefaultLauncher.class.getResourceAsStream(sourcePath)) {\n            Files.copy(input, targetFile, StandardCopyOption.REPLACE_EXISTING);\n        }\n    }\n\n    protected Map<String, String> getConfigurations() {\n        return mapOf(\n                // defined by Minecraft official launcher\n                pair(\"${auth_player_name}\", authInfo.getUsername()),\n                pair(\"${auth_session}\", authInfo.getAccessToken()),\n                pair(\"${auth_access_token}\", authInfo.getAccessToken()),\n                pair(\"${auth_uuid}\", UUIDTypeAdapter.fromUUID(authInfo.getUUID())),\n                pair(\"${version_name}\", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),\n                pair(\"${profile_name}\", Optional.ofNullable(options.getProfileName()).orElse(\"Minecraft\")),\n                pair(\"${version_type}\", Optional.ofNullable(options.getVersionType()).orElse(version.getType().getId())),\n                pair(\"${game_directory}\", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))),\n                pair(\"${user_type}\", authInfo.getUserType()),\n                pair(\"${assets_index_name}\", version.getAssetIndex().getId()),\n                pair(\"${user_properties}\", authInfo.getUserProperties()),\n                pair(\"${resolution_width}\", options.getWidth().toString()),\n                pair(\"${resolution_height}\", options.getHeight().toString()),\n                pair(\"${library_directory}\", FileUtils.getAbsolutePath(repository.getLibrariesDirectory(version))),\n                pair(\"${classpath_separator}\", File.pathSeparator),\n                pair(\"${primary_jar}\", FileUtils.getAbsolutePath(repository.getVersionJar(version))),\n                pair(\"${language}\", Locale.getDefault().toLanguageTag()),\n\n                // defined by HMCL\n                // libraries_directory stands for historical reasons here. We don't know the official launcher\n                // had already defined \"library_directory\" as the placeholder for path to \".minecraft/libraries\"\n                // when we propose this placeholder.\n                pair(\"${libraries_directory}\", FileUtils.getAbsolutePath(repository.getLibrariesDirectory(version))),\n                // file_separator is used in -DignoreList\n                pair(\"${file_separator}\", File.separator),\n                pair(\"${primary_jar_name}\", FileUtils.getName(repository.getVersionJar(version)))\n        );\n    }\n\n    @Override\n    public ManagedProcess launch() throws IOException, InterruptedException {\n        Path nativeFolder;\n        if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {\n            nativeFolder = repository.getNativeDirectory(version.getId(), options.getJava().getPlatform());\n        } else {\n            nativeFolder = Path.of(options.getNativesDir());\n        }\n\n        final Command command = generateCommandLine(nativeFolder);\n\n        // To guarantee that when failed to generate launch command line, we will not call pre-launch command\n        List<String> rawCommandLine = command.commandLine.asList();\n\n        if (command.tempNativeFolder != null) {\n            Files.deleteIfExists(command.tempNativeFolder);\n            Files.createSymbolicLink(command.tempNativeFolder, nativeFolder.toAbsolutePath());\n        }\n\n        if (rawCommandLine.stream().anyMatch(StringUtils::isBlank)) {\n            throw new IllegalStateException(\"Illegal command line \" + rawCommandLine);\n        }\n\n        if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {\n            decompressNatives(nativeFolder);\n        }\n\n        if (isUsingLog4j())\n            extractLog4jConfigurationFile();\n\n        Path runDirectory = repository.getRunDirectory(version.getId());\n\n        if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {\n            ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPreLaunchCommand(), getEnvVars())).directory(runDirectory.toFile());\n            builder.environment().putAll(getEnvVars());\n            SystemUtils.callExternalProcess(builder);\n        }\n\n        Process process;\n        try {\n            ProcessBuilder builder = new ProcessBuilder(rawCommandLine).directory(runDirectory.toFile());\n            if (listener == null) {\n                builder.inheritIO();\n            }\n            Path appdata = options.getGameDir().toAbsolutePath().getParent();\n            if (appdata != null) builder.environment().put(\"APPDATA\", appdata.toString());\n\n            builder.environment().putAll(getEnvVars());\n            process = builder.start();\n        } catch (IOException e) {\n            throw new ProcessCreationException(e);\n        }\n\n        ManagedProcess p = new ManagedProcess(process, rawCommandLine);\n        if (listener != null)\n            startMonitors(p, listener, command.encoding, daemon);\n        return p;\n    }\n\n    private Map<String, String> getEnvVars() {\n        String versionName = Optional.ofNullable(options.getVersionName()).orElse(version.getId());\n        Map<String, String> env = new LinkedHashMap<>();\n        env.put(\"INST_NAME\", versionName);\n        env.put(\"INST_ID\", versionName);\n        env.put(\"INST_DIR\", FileUtils.getAbsolutePath(repository.getVersionRoot(version.getId())));\n        env.put(\"INST_MC_DIR\", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId())));\n        env.put(\"INST_JAVA\", options.getJava().getBinary().toString());\n\n        Renderer renderer = options.getRenderer();\n        if (renderer != Renderer.DEFAULT) {\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n                if (renderer != Renderer.LLVMPIPE)\n                    env.put(\"GALLIUM_DRIVER\", renderer.name().toLowerCase(Locale.ROOT));\n            } else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) {\n                env.put(\"__GLX_VENDOR_LIBRARY_NAME\", \"mesa\");\n                switch (renderer) {\n                    case LLVMPIPE:\n                        env.put(\"LIBGL_ALWAYS_SOFTWARE\", \"1\");\n                        break;\n                    case ZINK:\n                        env.put(\"MESA_LOADER_DRIVER_OVERRIDE\", \"zink\");\n                        /*\n                         * The amdgpu DDX is missing support for modifiers, causing Zink to fail.\n                         * Disable DRI3 to workaround this issue.\n                         *\n                         * Link: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10093\n                         */\n                        env.put(\"LIBGL_KOPPER_DRI2\", \"1\");\n                        break;\n                }\n            }\n        }\n\n        if (analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {\n            env.put(\"INST_FORGE\", \"1\");\n        }\n        if (analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)) {\n            env.put(\"INST_CLEANROOM\", \"1\");\n        }\n        if (analyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE)) {\n            env.put(\"INST_NEOFORGE\", \"1\");\n        }\n        if (analyzer.has(LibraryAnalyzer.LibraryType.LITELOADER)) {\n            env.put(\"INST_LITELOADER\", \"1\");\n        }\n        if (analyzer.has(LibraryAnalyzer.LibraryType.FABRIC)) {\n            env.put(\"INST_FABRIC\", \"1\");\n        }\n        if (analyzer.has(LibraryAnalyzer.LibraryType.OPTIFINE)) {\n            env.put(\"INST_OPTIFINE\", \"1\");\n        }\n        if (analyzer.has(LibraryAnalyzer.LibraryType.QUILT)) {\n            env.put(\"INST_QUILT\", \"1\");\n        }\n        if (analyzer.has(LibraryAnalyzer.LibraryType.LEGACY_FABRIC)) {\n            env.put(\"INST_LEGACYFABRIC\", \"1\");\n        }\n\n        env.putAll(options.getEnvironmentVariables());\n\n        return env;\n    }\n\n    @Override\n    public void makeLaunchScript(Path scriptFile) throws IOException {\n        boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS;\n\n        Path nativeFolder;\n        if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {\n            nativeFolder = repository.getNativeDirectory(version.getId(), options.getJava().getPlatform());\n        } else {\n            nativeFolder = Path.of(options.getNativesDir());\n        }\n\n        if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {\n            decompressNatives(nativeFolder);\n        }\n\n        if (isUsingLog4j())\n            extractLog4jConfigurationFile();\n\n        String scriptExtension = FileUtils.getExtension(scriptFile);\n        boolean usePowerShell = \"ps1\".equals(scriptExtension);\n\n        if (!usePowerShell) {\n            if (isWindows && !scriptExtension.equals(\"bat\"))\n                throw new IllegalArgumentException(\"The extension of \" + scriptFile + \" is not 'bat' or 'ps1' in Windows\");\n            else if (!isWindows && !(scriptExtension.equals(\"sh\") || scriptExtension.equals(\"command\")))\n                throw new IllegalArgumentException(\"The extension of \" + scriptFile + \" is not 'sh', 'ps1' or 'command' in macOS/Linux\");\n        }\n\n        final Command commandLine = generateCommandLine(nativeFolder);\n        final String command = usePowerShell ? null : commandLine.commandLine.toString();\n        Map<String, String> envVars = getEnvVars();\n\n        if (isWindows && !usePowerShell) {\n            // https://stackoverflow.com/a/28452546\n            // https://learn.microsoft.com/troubleshoot/windows-client/shell-experience/command-line-string-limitation\n            if (command.length() > 32767) {\n                throw new CommandTooLongException();\n            }\n        }\n\n        Files.createDirectories(scriptFile.getParent());\n\n        try (OutputStream outputStream = Files.newOutputStream(scriptFile)) {\n            Charset charset = StandardCharsets.UTF_8;\n\n            if (isWindows) {\n                if (usePowerShell) {\n                    // Write UTF-8 BOM\n                    try {\n                        outputStream.write(0xEF);\n                        outputStream.write(0xBB);\n                        outputStream.write(0xBF);\n                    } catch (IOException e) {\n                        outputStream.close();\n                        throw e;\n                    }\n                } else {\n                    charset = OperatingSystem.NATIVE_CHARSET;\n                }\n            }\n\n            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset))) {\n                if (usePowerShell) {\n                    if (isWindows) {\n                        Path appdata = options.getGameDir().toAbsolutePath().getParent();\n                        if (appdata != null) {\n                            writer.write(\"$Env:APPDATA=\");\n                            writer.write(CommandBuilder.pwshString(appdata.toString()));\n                            writer.newLine();\n                        }\n                    }\n                    for (Map.Entry<String, String> entry : envVars.entrySet()) {\n                        writer.write(\"$Env:\" + entry.getKey() + \"=\");\n                        writer.write(CommandBuilder.pwshString(entry.getValue()));\n                        writer.newLine();\n                    }\n                    writer.write(\"Set-Location -LiteralPath \");\n                    writer.write(CommandBuilder.pwshString(FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))));\n                    writer.newLine();\n\n\n                    if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {\n                        writer.write('&');\n                        for (String rawCommand : StringUtils.tokenize(options.getPreLaunchCommand(), envVars)) {\n                            writer.write(' ');\n                            writer.write(CommandBuilder.pwshString(rawCommand));\n                        }\n                        writer.newLine();\n                    }\n\n                    writer.write('&');\n                    for (String rawCommand : commandLine.commandLine.asList()) {\n                        writer.write(' ');\n                        writer.write(CommandBuilder.pwshString(rawCommand));\n                    }\n                    writer.newLine();\n\n                    if (StringUtils.isNotBlank(options.getPostExitCommand())) {\n                        writer.write('&');\n                        for (String rawCommand : StringUtils.tokenize(options.getPostExitCommand(), envVars)) {\n                            writer.write(' ');\n                            writer.write(CommandBuilder.pwshString(rawCommand));\n                        }\n                        writer.newLine();\n                    }\n                } else {\n                    if (isWindows) {\n                        writer.write(\"@echo off\");\n                        writer.newLine();\n\n                        Path appdata = options.getGameDir().toAbsolutePath().getParent();\n                        if (appdata != null) {\n                            writer.write(\"set APPDATA=\" + appdata);\n                            writer.newLine();\n                        }\n\n                        for (Map.Entry<String, String> entry : envVars.entrySet()) {\n                            writer.write(\"set \" + entry.getKey() + \"=\" + CommandBuilder.toBatchStringLiteral(entry.getValue()));\n                            writer.newLine();\n                        }\n                        writer.newLine();\n                        writer.write(new CommandBuilder().addAll(\"cd\", \"/D\", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))).toString());\n                    } else {\n                        writer.write(\"#!/usr/bin/env bash\");\n                        writer.newLine();\n                        for (Map.Entry<String, String> entry : envVars.entrySet()) {\n                            writer.write(\"export \" + entry.getKey() + \"=\" + CommandBuilder.toShellStringLiteral(entry.getValue()));\n                            writer.newLine();\n                        }\n                        if (commandLine.tempNativeFolder != null) {\n                            writer.write(new CommandBuilder().addAll(\"ln\", \"-s\", FileUtils.getAbsolutePath(nativeFolder), commandLine.tempNativeFolder.toString()).toString());\n                            writer.newLine();\n                        }\n                        writer.write(new CommandBuilder().addAll(\"cd\", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))).toString());\n                    }\n                    writer.newLine();\n                    if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {\n                        writer.write(new CommandBuilder().addAll(StringUtils.tokenize(options.getPreLaunchCommand(), envVars)).toString());\n                        writer.newLine();\n                    }\n                    writer.write(command);\n                    writer.newLine();\n\n                    if (StringUtils.isNotBlank(options.getPostExitCommand())) {\n                        writer.write(new CommandBuilder().addAll(StringUtils.tokenize(options.getPostExitCommand(), envVars)).toString());\n                        writer.newLine();\n                    }\n\n                    if (isWindows) {\n                        writer.write(\"pause\");\n                        writer.newLine();\n                    }\n                    if (commandLine.tempNativeFolder != null) {\n                        writer.write(new CommandBuilder().addAll(\"rm\", commandLine.tempNativeFolder.toString()).toString());\n                        writer.newLine();\n                    }\n                }\n            }\n        }\n        FileUtils.setExecutable(scriptFile);\n        if (!Files.isExecutable(scriptFile))\n            throw new PermissionException();\n\n        if (usePowerShell && !CommandBuilder.hasExecutionPolicy())\n            throw new ExecutionPolicyLimitException();\n    }\n\n    private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, Charset encoding, boolean isDaemon) {\n        processListener.setProcess(managedProcess);\n        Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> {\n            processListener.onLog(it, false);\n            managedProcess.addLine(it);\n        }, encoding), \"stdout-pump\", isDaemon);\n        managedProcess.addRelatedThread(stdout);\n        Thread stderr = Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream(), it -> {\n            processListener.onLog(it, true);\n            managedProcess.addLine(it);\n        }, encoding), \"stderr-pump\", isDaemon);\n        managedProcess.addRelatedThread(stderr);\n        managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), (exitCode, exitType) -> {\n            processListener.onExit(exitCode, exitType);\n\n            if (StringUtils.isNotBlank(options.getPostExitCommand())) {\n                try {\n                    ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPostExitCommand(), getEnvVars())).directory(options.getGameDir().toFile());\n                    builder.environment().putAll(getEnvVars());\n                    SystemUtils.callExternalProcess(builder);\n                } catch (Throwable e) {\n                    LOG.warning(\"An Exception happened while running exit command.\", e);\n                }\n            }\n        }), \"exit-waiter\", isDaemon));\n    }\n\n    private static final class Command {\n        final CommandBuilder commandLine;\n        final Path tempNativeFolder;\n        final Charset encoding;\n\n        Command(CommandBuilder commandBuilder, Path tempNativeFolder, Charset encoding) {\n            this.commandLine = commandBuilder;\n            this.tempNativeFolder = tempNativeFolder;\n            this.encoding = encoding;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExecutionPolicyLimitException.java",
    "content": "package org.jackhuang.hmcl.launch;\n\npublic final class ExecutionPolicyLimitException extends RuntimeException {\n    public ExecutionPolicyLimitException() {\n    }\n\n    public ExecutionPolicyLimitException(String message) {\n        super(message);\n    }\n\n    public ExecutionPolicyLimitException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public ExecutionPolicyLimitException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.launch;\n\nimport org.jackhuang.hmcl.event.EventBus;\nimport org.jackhuang.hmcl.event.JVMLaunchFailedEvent;\nimport org.jackhuang.hmcl.event.ProcessExitedAbnormallyEvent;\nimport org.jackhuang.hmcl.event.ProcessStoppedEvent;\nimport org.jackhuang.hmcl.util.Log4jLevel;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.function.BiConsumer;\n\n/**\n * @author huangyuhui\n */\nfinal class ExitWaiter implements Runnable {\n\n    private final ManagedProcess process;\n    private final Collection<Thread> joins;\n    private final BiConsumer<Integer, ProcessListener.ExitType> watcher;\n\n    /**\n     * Constructor.\n     *\n     * @param process the process to wait for\n     * @param watcher the callback that will be called after process stops.\n     */\n    public ExitWaiter(ManagedProcess process, Collection<Thread> joins, BiConsumer<Integer, ProcessListener.ExitType> watcher) {\n        this.process = process;\n        this.joins = joins;\n        this.watcher = watcher;\n    }\n\n    @Override\n    public void run() {\n        try {\n            int exitCode = process.getProcess().waitFor();\n\n            for (Thread thread : joins)\n                thread.join();\n\n            List<String> errorLines = process.getLines(Log4jLevel::guessLogLineError);\n            ProcessListener.ExitType exitType;\n\n            // LaunchWrapper will catch the exception logged and will exit normally.\n            if (exitCode != 0 && StringUtils.containsOne(errorLines,\n                    \"Could not create the Java Virtual Machine.\",\n                    \"Error occurred during initialization of VM\",\n                    \"A fatal exception has occurred. Program will exit.\")) {\n                EventBus.EVENT_BUS.fireEvent(new JVMLaunchFailedEvent(this, process));\n                exitType = ProcessListener.ExitType.JVM_ERROR;\n            } else if (exitCode != 0 || StringUtils.containsOne(errorLines,\n                    \"Crash report saved to\", \"Could not save crash report to\", \"This crash report has been saved to:\",\n                    \"Unable to launch\", \"An exception was thrown, the game will display an error screen and halt.\")) {\n                EventBus.EVENT_BUS.fireEvent(new ProcessExitedAbnormallyEvent(this, process));\n\n                if (exitCode == 137 && OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {\n                    exitType = ProcessListener.ExitType.SIGKILL;\n                } else {\n                    exitType = ProcessListener.ExitType.APPLICATION_ERROR;\n                }\n            } else {\n                exitType = ProcessListener.ExitType.NORMAL;\n            }\n\n            EventBus.EVENT_BUS.fireEvent(new ProcessStoppedEvent(this, process));\n\n            watcher.accept(exitCode, exitType);\n        } catch (InterruptedException e) {\n            watcher.accept(1, ProcessListener.ExitType.INTERRUPTED);\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.launch;\n\nimport org.jackhuang.hmcl.auth.AuthInfo;\nimport org.jackhuang.hmcl.game.GameRepository;\nimport org.jackhuang.hmcl.game.LaunchOptions;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\n/**\n *\n * @author huangyuhui\n */\npublic abstract class Launcher {\n\n    protected final GameRepository repository;\n    protected final Version version;\n    protected final AuthInfo authInfo;\n    protected final LaunchOptions options;\n    protected final ProcessListener listener;\n    protected final boolean daemon;\n\n    public Launcher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {\n        this(repository, version, authInfo, options, null);\n    }\n\n    public Launcher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {\n        this(repository, version, authInfo, options, listener, true);\n    }\n\n    public Launcher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {\n        this.repository = repository;\n        this.version = version;\n        this.authInfo = authInfo;\n        this.options = options;\n        this.listener = listener;\n        this.daemon = daemon;\n    }\n\n    /**\n     * @param file the file path.\n     */\n    public abstract void makeLaunchScript(Path file) throws IOException;\n\n    public abstract ManagedProcess launch() throws IOException, InterruptedException;\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/NotDecompressingNativesException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.launch;\n\nimport java.io.IOException;\n\npublic class NotDecompressingNativesException extends IOException {\n    public NotDecompressingNativesException() {\n    }\n\n    public NotDecompressingNativesException(String message) {\n        super(message);\n    }\n\n    public NotDecompressingNativesException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public NotDecompressingNativesException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/PermissionException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.launch;\n\nimport java.io.IOException;\n\n/**\n * Threw if unable to make file executable.\n */\npublic class PermissionException extends IOException {\n    public PermissionException() {\n    }\n\n    public PermissionException(String message) {\n        super(message);\n    }\n\n    public PermissionException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public PermissionException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ProcessCreationException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.launch;\n\nimport java.io.IOException;\n\npublic class ProcessCreationException extends IOException {\n    public ProcessCreationException() {\n    }\n\n    public ProcessCreationException(String message) {\n        super(message);\n    }\n\n    public ProcessCreationException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public ProcessCreationException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ProcessListener.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.launch;\n\nimport org.jackhuang.hmcl.util.platform.ManagedProcess;\n\n/**\n *\n * @author huangyuhui\n */\npublic interface ProcessListener {\n\n    /**\n     * When a game launched, this method will be called to get the new process.\n     * You should not override this method when your ProcessListener is shared with all processes.\n     */\n    default void setProcess(ManagedProcess process) {\n    }\n\n    /**\n     * Called when receiving a log from stdout/stderr.\n     *\n     * Does not guarantee that this method is thread safe.\n     *\n     * @param log the log\n     */\n    void onLog(String log, boolean isErrorStream);\n\n    /**\n     * Called when the game process stops.\n     *\n     * @param exitCode the exit code\n     */\n    void onExit(int exitCode, ExitType exitType);\n\n    enum ExitType {\n        JVM_ERROR,\n        APPLICATION_ERROR,\n        SIGKILL,\n        NORMAL,\n        INTERRUPTED\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/launch/StreamPump.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.launch;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * Pump the given input stream.\n *\n * @author huangyuhui\n */\npublic final class StreamPump implements Runnable {\n\n    private final InputStream inputStream;\n    private final Consumer<String> callback;\n    private final Charset charset;\n\n    public StreamPump(InputStream inputStream) {\n        this(inputStream, s -> {});\n    }\n\n    public StreamPump(InputStream inputStream, Consumer<String> callback) {\n        this.inputStream = inputStream;\n        this.callback = callback;\n        this.charset = StandardCharsets.UTF_8;\n    }\n\n    public StreamPump(InputStream inputStream, Consumer<String> callback, Charset charset) {\n        this.inputStream = inputStream;\n        this.callback = callback;\n        this.charset = charset;\n    }\n\n    @Override\n    public void run() {\n        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, charset))) {\n            String line;\n            while ((line = bufferedReader.readLine()) != null) {\n                if (Thread.currentThread().isInterrupted()) {\n                    Thread.currentThread().interrupt();\n                    break;\n                }\n\n                callback.accept(line);\n            }\n        } catch (IOException e) {\n            LOG.error(\"An error occurred when reading stream\", e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport com.google.gson.JsonParseException;\nimport javafx.application.Platform;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport org.jackhuang.hmcl.mod.modinfo.PackMcMeta;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.Unzipper;\nimport org.jackhuang.hmcl.util.versioning.GameVersionNumber;\n\nimport java.io.IOException;\nimport java.nio.file.*;\nimport java.util.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class Datapack {\n    private static final String DISABLED_EXT = \"disabled\";\n    private static final String ZIP_EXT = \"zip\";\n\n    private final Path path;\n    private final ObservableList<Pack> packs = FXCollections.observableArrayList();\n\n    public Datapack(Path path) {\n        this.path = path;\n    }\n\n    public Path getPath() {\n        return path;\n    }\n\n    public ObservableList<Pack> getPacks() {\n        return packs;\n    }\n\n    public static void installPack(Path sourceDatapackPath, Path targetDatapackDirectory, GameVersionNumber gameVersionNumber) throws IOException {\n        boolean containsMultiplePacks;\n        Set<String> packs = new HashSet<>();\n        try (FileSystem fs = CompressingUtils.readonly(sourceDatapackPath).setAutoDetectEncoding(true).build()) {\n            Path datapacks = fs.getPath(\"datapacks\");\n            Path mcmeta = fs.getPath(\"pack.mcmeta\");\n\n            if (Files.exists(datapacks)) {\n                containsMultiplePacks = true;\n            } else if (Files.exists(mcmeta)) {\n                containsMultiplePacks = false;\n            } else {\n                throw new IOException(\"Malformed datapack zip\");\n            }\n\n            if (containsMultiplePacks) {\n                try (Stream<Path> s = Files.list(datapacks)) {\n                    packs = s.map(FileUtils::getNameWithoutExtension).collect(Collectors.toSet());\n                }\n            } else {\n                packs.add(FileUtils.getNameWithoutExtension(sourceDatapackPath));\n            }\n\n            try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDatapackDirectory)) {\n                for (Path dir : stream) {\n                    String packName = FileUtils.getName(dir);\n                    if (FileUtils.getExtension(dir).equals(DISABLED_EXT)) {\n                        packName = StringUtils.removeSuffix(packName, \".\" + DISABLED_EXT);\n                    }\n                    packName = FileUtils.getNameWithoutExtension(packName);\n                    if (packs.contains(packName)) {\n                        if (Files.isDirectory(dir)) {\n                            FileUtils.deleteDirectory(dir);\n                        } else if (Files.isRegularFile(dir)) {\n                            Files.delete(dir);\n                        }\n                    }\n                }\n            }\n        }\n\n        if (!containsMultiplePacks) {\n            FileUtils.copyFile(sourceDatapackPath, targetDatapackDirectory.resolve(FileUtils.getName(sourceDatapackPath)));\n        } else {\n            new Unzipper(sourceDatapackPath, targetDatapackDirectory)\n                    .setReplaceExistentFile(true)\n                    .setSubDirectory(\"/datapacks/\")\n                    .unzip();\n\n            Path targetResourceZipPath;\n            // When the version cannot be obtained, the old version logic is used by default.\n            boolean useNewResourcePath = gameVersionNumber != null\n                    && gameVersionNumber.compareTo(\"26.1-snapshot-6\") >= 0;\n\n            if (useNewResourcePath) {\n                Files.createDirectories(targetDatapackDirectory.getParent().resolve(\"resourcepacks\"));\n                targetResourceZipPath = targetDatapackDirectory.getParent().resolve(\"resourcepacks/resources.zip\");\n            } else {\n                targetResourceZipPath = targetDatapackDirectory.getParent().resolve(\"resources.zip\");\n            }\n\n            try (FileSystem outputResourcesZipFS = CompressingUtils.createWritableZipFileSystem(targetResourceZipPath);\n                 FileSystem inputPackZipFS = CompressingUtils.createReadOnlyZipFileSystem(sourceDatapackPath)) {\n                Path resourcesZip = inputPackZipFS.getPath(\"resources.zip\");\n                if (Files.isRegularFile(resourcesZip)) {\n                    Path tempResourcesFile = Files.createTempFile(\"hmcl\", \".zip\");\n                    try {\n                        Files.copy(resourcesZip, tempResourcesFile, StandardCopyOption.REPLACE_EXISTING);\n                        try (FileSystem resources = CompressingUtils.createReadOnlyZipFileSystem(tempResourcesFile)) {\n                            FileUtils.copyDirectory(resources.getPath(\"/\"), outputResourcesZipFS.getPath(\"/\"));\n                        }\n                    } finally {\n                        Files.deleteIfExists(tempResourcesFile);\n                    }\n                }\n                Path packMcMeta = outputResourcesZipFS.getPath(\"pack.mcmeta\");\n                String metaContent = \"\"\"\n                        {\n                            \"pack\": {\n                                \"pack_format\": 4,\n                                \"description\": \"Modified by HMCL.\"\n                            }\n                        }\n                        \"\"\";\n                Files.writeString(packMcMeta, metaContent, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);\n\n                Path packPng = outputResourcesZipFS.getPath(\"pack.png\");\n                if (Files.isRegularFile(packPng))\n                    Files.delete(packPng);\n            }\n        }\n    }\n\n    public void installPack(Path sourcePackPath, GameVersionNumber gameVersionNumber) throws IOException {\n        installPack(sourcePackPath, path, gameVersionNumber);\n        loadFromDir();\n    }\n\n    public void deletePack(Pack packToDelete) throws IOException {\n        Path pathToDelete = packToDelete.path;\n        if (Files.isDirectory(pathToDelete))\n            FileUtils.deleteDirectory(pathToDelete);\n        else if (Files.isRegularFile(pathToDelete))\n            Files.delete(pathToDelete);\n\n        Platform.runLater(() -> packs.removeIf(p -> p.getId().equals(packToDelete.getId())));\n    }\n\n    public void loadFromDir() {\n        try {\n            loadFromDir(path);\n        } catch (Exception e) {\n            LOG.warning(\"Failed to read datapacks \" + path, e);\n        }\n    }\n\n    private void loadFromDir(Path dir) throws IOException {\n        List<Pack> discoveredPacks;\n        try (Stream<Path> stream = Files.list(dir)) {\n            discoveredPacks = stream\n                    .parallel()\n                    .map(this::loadSinglePackFromPath)\n                    .flatMap(Optional::stream)\n                    .sorted(Comparator.comparing(Pack::getId, String.CASE_INSENSITIVE_ORDER))\n                    .collect(Collectors.toList());\n        }\n        Platform.runLater(() -> this.packs.setAll(discoveredPacks));\n    }\n\n    private Optional<Pack> loadSinglePackFromPath(Path path) {\n        if (Files.isDirectory(path)) {\n            return loadSinglePackFromDirectory(path);\n        } else if (Files.isRegularFile(path)) {\n            return loadSinglePackFromZipFile(path);\n        }\n        return Optional.empty();\n    }\n\n    private Optional<Pack> loadSinglePackFromDirectory(Path path) {\n        Path mcmeta = path.resolve(\"pack.mcmeta\");\n        Path mcmetaDisabled = path.resolve(\"pack.mcmeta.disabled\");\n\n        if (!Files.exists(mcmeta) && !Files.exists(mcmetaDisabled)) {\n            return Optional.empty();\n        }\n\n        Path targetPath = Files.exists(mcmeta) ? mcmeta : mcmetaDisabled;\n        return parsePack(path, true, FileUtils.getNameWithoutExtension(path), targetPath);\n    }\n\n    private Optional<Pack> loadSinglePackFromZipFile(Path path) {\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(path)) {\n            Path mcmeta = fs.getPath(\"pack.mcmeta\");\n\n            if (!Files.exists(mcmeta)) {\n                return Optional.empty();\n            }\n\n            String packName = FileUtils.getName(path);\n            if (FileUtils.getExtension(path).equals(DISABLED_EXT)) {\n                packName = FileUtils.getNameWithoutExtension(packName);\n            }\n            packName = FileUtils.getNameWithoutExtension(packName);\n\n            return parsePack(path, false, packName, mcmeta);\n        } catch (IOException e) {\n            LOG.warning(\"IO error reading \" + path, e);\n            return Optional.empty();\n        }\n    }\n\n    private Optional<Pack> parsePack(Path datapackPath, boolean isDirectory, String name, Path mcmetaPath) {\n        try {\n            PackMcMeta mcMeta = JsonUtils.fromNonNullJson(Files.readString(mcmetaPath), PackMcMeta.class);\n            return Optional.of(new Pack(datapackPath, isDirectory, name, mcMeta.pack().description(), this));\n        } catch (JsonParseException e) {\n            LOG.warning(\"Invalid pack.mcmeta format in \" + datapackPath, e);\n        } catch (IOException e) {\n            LOG.warning(\"IO error reading \" + datapackPath, e);\n        }\n        return Optional.empty();\n    }\n\n    public static class Pack {\n        private Path path;\n        private final boolean isDirectory;\n        private Path statusFile;\n        private final BooleanProperty activeProperty;\n        private final String id;\n        private final LocalModFile.Description description;\n        private final Datapack parentDatapack;\n\n        public Pack(Path path, boolean isDirectory, String id, LocalModFile.Description description, Datapack parentDatapack) {\n            this.path = path;\n            this.isDirectory = isDirectory;\n            this.id = id;\n            this.description = description;\n            this.parentDatapack = parentDatapack;\n\n            this.statusFile = initializeStatusFile(path, isDirectory);\n            this.activeProperty = initializeActiveProperty();\n        }\n\n        private Path initializeStatusFile(Path path, boolean isDirectory) {\n            if (isDirectory) {\n                Path mcmeta = path.resolve(\"pack.mcmeta\");\n                return Files.exists(mcmeta) ? mcmeta : path.resolve(\"pack.mcmeta.disabled\");\n            }\n            return path;\n        }\n\n        private BooleanProperty initializeActiveProperty() {\n            BooleanProperty property = new SimpleBooleanProperty(this, \"active\", !FileUtils.getExtension(this.statusFile).equals(DISABLED_EXT));\n            property.addListener((obs, wasActive, isNowActive) -> {\n                if (wasActive != isNowActive) {\n                    handleFileRename(isNowActive);\n                }\n            });\n            return property;\n        }\n\n        private void handleFileRename(boolean isNowActive) {\n            Path newStatusFile = calculateNewStatusFilePath(isNowActive);\n            if (statusFile.equals(newStatusFile)) {\n                return;\n            }\n            try {\n                Files.move(this.statusFile, newStatusFile);\n                this.statusFile = newStatusFile;\n                if (!this.isDirectory) {\n                    this.path = newStatusFile;\n                }\n            } catch (IOException e) {\n                LOG.warning(\"Unable to rename file from \" + this.statusFile + \" to \" + newStatusFile, e);\n            }\n        }\n\n        private Path calculateNewStatusFilePath(boolean isActive) {\n            boolean isFileDisabled = DISABLED_EXT.equals(FileUtils.getExtension(this.statusFile));\n            if (isActive && isFileDisabled) {\n                return this.statusFile.getParent().resolve(FileUtils.getNameWithoutExtension(this.statusFile));\n            } else if (!isActive && !isFileDisabled) {\n                return this.statusFile.getParent().resolve(FileUtils.getName(this.statusFile) + \".\" + DISABLED_EXT);\n            }\n            return this.statusFile;\n        }\n\n        public String getId() {\n            return id;\n        }\n\n        public LocalModFile.Description getDescription() {\n            return description;\n        }\n\n        public Datapack getParentDatapack() {\n            return parentDatapack;\n        }\n\n        public BooleanProperty activeProperty() {\n            return activeProperty;\n        }\n\n        public boolean isActive() {\n            return activeProperty.get();\n        }\n\n        public void setActive(boolean active) {\n            this.activeProperty.set(active);\n        }\n\n        public Path getPath() {\n            return path;\n        }\n\n        public boolean isDirectory() {\n            return isDirectory;\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalMod.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport java.util.HashSet;\nimport java.util.Objects;\n\npublic class LocalMod {\n\n    private final String id;\n    private final ModLoaderType modLoaderType;\n    private final HashSet<LocalModFile> files = new HashSet<>();\n    private final HashSet<LocalModFile> oldFiles = new HashSet<>();\n\n    public LocalMod(String id, ModLoaderType modLoaderType) {\n        this.id = id;\n        this.modLoaderType = modLoaderType;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public ModLoaderType getModLoaderType() {\n        return modLoaderType;\n    }\n\n    public HashSet<LocalModFile> getFiles() {\n        return files;\n    }\n\n    public HashSet<LocalModFile> getOldFiles() {\n        return oldFiles;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        LocalMod localMod = (LocalMod) o;\n        return Objects.equals(id, localMod.id) && modLoaderType == localMod.modLoaderType;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, modLoaderType);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class LocalModFile implements Comparable<LocalModFile> {\n\n    private Path file;\n    private final ModManager modManager;\n    private final LocalMod mod;\n    private final String name;\n    private final Description description;\n    private final String authors;\n    private final String version;\n    private final String gameVersion;\n    private final String url;\n    private final String fileName;\n    private final String logoPath;\n    private final BooleanProperty activeProperty;\n\n    public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description) {\n        this(modManager, mod, file, name, description, \"\", \"\", \"\", \"\", \"\");\n    }\n\n    public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) {\n        this.modManager = modManager;\n        this.mod = mod;\n        this.file = file;\n        this.name = name;\n        this.description = description;\n        this.authors = authors;\n        this.version = version;\n        this.gameVersion = gameVersion;\n        this.url = url;\n        this.logoPath = logoPath;\n\n        activeProperty = new SimpleBooleanProperty(this, \"active\", !modManager.isDisabled(file)) {\n            @Override\n            protected void invalidated() {\n                if (isOld()) return;\n\n                Path path = LocalModFile.this.file.toAbsolutePath();\n\n                try {\n                    if (get())\n                        LocalModFile.this.file = modManager.enableMod(path);\n                    else\n                        LocalModFile.this.file = modManager.disableMod(path);\n                } catch (IOException e) {\n                    LOG.error(\"Unable to invert state of mod file \" + path, e);\n                }\n            }\n        };\n\n        fileName = FileUtils.getNameWithoutExtension(ModManager.getModName(file));\n\n        if (isOld()) {\n            mod.getOldFiles().add(this);\n        } else {\n            mod.getFiles().add(this);\n        }\n    }\n\n    public ModManager getModManager() {\n        return modManager;\n    }\n\n    public LocalMod getMod() {\n        return mod;\n    }\n\n    public Path getFile() {\n        return file;\n    }\n\n    public ModLoaderType getModLoaderType() {\n        return mod.getModLoaderType();\n    }\n\n    public String getId() {\n        return mod.getId();\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public Description getDescription() {\n        return description;\n    }\n\n    public String getAuthors() {\n        return authors;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public String getGameVersion() {\n        return gameVersion;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public String getLogoPath() {\n        return logoPath;\n    }\n\n    public BooleanProperty activeProperty() {\n        return activeProperty;\n    }\n\n    public boolean isActive() {\n        return activeProperty.get();\n    }\n\n    public void setActive(boolean active) {\n        activeProperty.set(active);\n    }\n\n    public String getFileName() {\n        return fileName;\n    }\n\n    public boolean isOld() {\n        return modManager.isOld(file);\n    }\n\n    public void setOld(boolean old) throws IOException {\n        file = modManager.setOld(this, old);\n\n        if (old) {\n            mod.getFiles().remove(this);\n            mod.getOldFiles().add(this);\n        } else {\n            mod.getOldFiles().remove(this);\n            mod.getFiles().add(this);\n        }\n    }\n\n    public void disable() throws IOException {\n        file = modManager.disableMod(file);\n    }\n\n    public ModUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteModRepository repository) throws IOException {\n        Optional<RemoteMod.Version> currentVersion = repository.getRemoteVersionByLocalFile(this, file);\n        if (!currentVersion.isPresent()) return null;\n        List<RemoteMod.Version> remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().getModid())\n                .filter(version -> version.getGameVersions().contains(gameVersion))\n                .filter(version -> version.getLoaders().contains(getModLoaderType()))\n                .filter(version -> version.getDatePublished().compareTo(currentVersion.get().getDatePublished()) > 0)\n                .sorted(Comparator.comparing(RemoteMod.Version::getDatePublished).reversed())\n                .toList();\n        if (remoteVersions.isEmpty()) return null;\n        return new ModUpdate(this, currentVersion.get(), remoteVersions.get(0));\n    }\n\n    @Override\n    public int compareTo(LocalModFile o) {\n        return getFileName().compareToIgnoreCase(o.getFileName());\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return obj instanceof LocalModFile && Objects.equals(getFileName(), ((LocalModFile) obj).getFileName());\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(getFileName());\n    }\n\n    public static class ModUpdate {\n        private final LocalModFile localModFile;\n        private final RemoteMod.Version currentVersion;\n        private final RemoteMod.Version candidate;\n\n        public ModUpdate(LocalModFile localModFile, RemoteMod.Version currentVersion, RemoteMod.Version candidate) {\n            this.localModFile = localModFile;\n            this.currentVersion = currentVersion;\n            this.candidate = candidate;\n        }\n\n        public LocalModFile getLocalMod() {\n            return localModFile;\n        }\n\n        public RemoteMod.Version getCurrentVersion() {\n            return currentVersion;\n        }\n\n        public RemoteMod.Version getCandidate() {\n            return candidate;\n        }\n    }\n\n    public static class Description {\n        private final List<Part> parts;\n\n        public Description(String text) {\n            this.parts = new ArrayList<>();\n            this.parts.add(new Part(text, \"black\"));\n        }\n\n        public Description(List<Part> parts) {\n            this.parts = parts;\n        }\n\n        public List<Part> getParts() {\n            return parts;\n        }\n\n        @Override\n        public String toString() {\n            StringBuilder builder = new StringBuilder();\n            for (Part part : parts) {\n                builder.append(part.text);\n            }\n            return builder.toString();\n        }\n\n        public static class Part {\n            private final String text;\n            private final String color;\n\n            public Part(String text) {\n                this(text, \"\");\n            }\n\n            public Part(String text, String color) {\n                this.text = Objects.requireNonNull(text);\n                this.color = Objects.requireNonNull(color);\n            }\n\n            public String getText() {\n                return text;\n            }\n\n            public String getColor() {\n                return color;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.tree.ArchiveFileTree;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.nio.file.*;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic final class MinecraftInstanceTask<T> extends Task<ModpackConfiguration<T>> {\n\n    private final Path zipFile;\n    private final Charset encoding;\n    private final List<String> subDirectories;\n    private final Path jsonFile;\n    private final T manifest;\n    private final String type;\n    private final String name;\n    private final String version;\n\n    public MinecraftInstanceTask(Path zipFile, Charset encoding, List<String> subDirectories, T manifest, ModpackProvider modpackProvider, String name, String version, Path jsonFile) {\n        this.zipFile = zipFile;\n        this.encoding = encoding;\n        this.subDirectories = subDirectories.stream().map(FileUtils::normalizePath).toList();\n        this.manifest = manifest;\n        this.jsonFile = jsonFile;\n        this.type = modpackProvider.getName();\n        this.name = name;\n        this.version = version;\n    }\n\n    private static void getOverrides(List<ModpackConfiguration.FileInformation> overrides,\n                                     ZipFileTree tree,\n                                     ArchiveFileTree.Dir<ZipArchiveEntry> dir,\n                                     List<String> names) throws IOException {\n        String prefix = String.join(\"/\", names);\n        if (!prefix.isEmpty())\n            prefix = prefix + \"/\";\n\n        for (Map.Entry<String, ZipArchiveEntry> entry : dir.getFiles().entrySet()) {\n            String hash;\n            try (InputStream input = tree.getInputStream(entry.getValue())) {\n                hash = DigestUtils.digestToString(\"SHA-1\", input);\n            }\n            overrides.add(new ModpackConfiguration.FileInformation(prefix + entry.getKey(), hash));\n        }\n\n        for (ArchiveFileTree.Dir<ZipArchiveEntry> subDir : dir.getSubDirs().values()) {\n            names.add(subDir.getName());\n            getOverrides(overrides, tree, subDir, names);\n            names.remove(names.size() - 1);\n        }\n    }\n\n    @Override\n    public void execute() throws Exception {\n        List<ModpackConfiguration.FileInformation> overrides = new ArrayList<>();\n\n        try (var tree = new ZipFileTree(CompressingUtils.openZipFileWithPossibleEncoding(zipFile, encoding))) {\n            for (String subDirectory : subDirectories) {\n                ArchiveFileTree.Dir<ZipArchiveEntry> root = tree.getDirectory(subDirectory);\n                if (root == null)\n                    continue;\n                var names = new ArrayList<String>();\n                getOverrides(overrides, tree, root, names);\n            }\n        }\n        ModpackConfiguration<T> configuration = new ModpackConfiguration<>(manifest, type, name, version, overrides);\n        Files.createDirectories(jsonFile.getParent());\n        JsonUtils.writeToJsonFile(jsonFile, configuration);\n        setResult(configuration);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MismatchedModpackTypeException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\npublic class MismatchedModpackTypeException extends Exception {\n    private final String required;\n    private final String found;\n\n    public MismatchedModpackTypeException(String required, String found) {\n        super(\"Required \" + required + \", but found \" + found);\n\n        this.required = required;\n        this.found = found;\n    }\n\n    public String getRequired() {\n        return required;\n    }\n\n    public String getFound() {\n        return found;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModAdviser.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport org.jackhuang.hmcl.util.Lang;\n\nimport java.io.File;\nimport java.util.List;\n\n/**\n * @author huangyuhui\n */\npublic interface ModAdviser {\n\n    /**\n     * Suggests the file should be displayed, hidden, or included by default.\n     * @param fileName full path of fileName\n     * @param isDirectory whether the path is directory\n     * @return the suggestion to the file\n     */\n    ModSuggestion advise(String fileName, boolean isDirectory);\n\n    enum ModSuggestion {\n        SUGGESTED,\n        NORMAL,\n        HIDDEN\n    }\n\n    List<String> MODPACK_BLACK_LIST = Lang.immutableListOf(\n        \"regex:(.*?)\\\\.log\",\n        \"regex:.*\\\\.dat_old$\", \"regex:.*\\\\.old$\", // Backup files\n        \"regex:.*\\\\.BakaCoreInfo$\", // BakaXL\n        \"regex:.*-natives\",\n        \"usernamecache.json\", \"usercache.json\", // Minecraft\n        \"launcher_profiles.json\", \"launcher.pack.lzma\", // Old Minecraft Launcher\n        \"launcher_accounts.json\", \"launcher_cef_log.txt\", \"launcher_log.txt\", \"launcher_msa_credentials.bin\", \"launcher_settings.json\", \"launcher_ui_state.json\", \"realms_persistence.json\", \"webcache2\", \"treatment_tags.json\", // New Minecraft Launcher\n        \"clientId.txt\", \"PCL.ini\", // Plain Craft Launcher\n        \"backup\", \"pack.json\", \"launcher.jar\", \"cache\", \"modpack.cfg\", \"log4j2.xml\", \"hmclversion.cfg\", // HMCL\n        \"manifest.json\", \"minecraftinstance.json\", \".curseclient\", // Curse\n        \"modrinth.index.json\", // Modrinth\n        \".fabric\", \".mixin.out\", \".optifine\", // Fabric/OptiFine\n        \"jars\", \"logs\", \"versions\", \"assets\", \"libraries\", \"crash-reports\", \"NVIDIA\", \"AMD\", \"screenshots\", \"natives\", \"native\", \"$native\", \"$natives\", \"server-resource-packs\", \"command_history.txt\", // Minecraft\n        \"downloads\", \"essential\", // Downloads and Essential\n        \"asm\", \"backups\", \"TCNodeTracker\", \"CustomDISkins\", \"data\", \"CustomSkinLoader/caches\", // Mods\n        \"debug\", // Debug files\n        \".replay_cache\", \"replay_recordings\", \"replay_videos\", // ReplayMod\n        \"irisUpdateInfo.json\", // Iris\n        \"modernfix\", // ModernFix\n        \"modtranslations\", // Mod translations\n        \"schematics\", // Schematics mod\n        \"journeymap/data\", // JourneyMap\n        \"mods/.connector\" // Sinytra Connector\n    );\n\n    List<String> MODPACK_SUGGESTED_BLACK_LIST = Lang.immutableListOf(\n            \"fonts\", // BetterFonts\n            \"saves\", \"servers.dat\", \"options.txt\", // Minecraft\n            \"blueprints\" /* BuildCraft */,\n            \"optionsof.txt\" /* OptiFine */,\n            \"journeymap\" /* JourneyMap */,\n            \"optionsshaders.txt\",\n            \"mods\" + File.separator + \"VoxelMods\");\n\n    static ModAdviser.ModSuggestion suggestMod(String fileName, boolean isDirectory) {\n        if (match(MODPACK_BLACK_LIST, fileName, isDirectory))\n            return ModAdviser.ModSuggestion.HIDDEN;\n        if (match(MODPACK_SUGGESTED_BLACK_LIST, fileName, isDirectory))\n            return ModAdviser.ModSuggestion.NORMAL;\n        else\n            return ModAdviser.ModSuggestion.SUGGESTED;\n    }\n\n    static boolean match(List<String> l, String fileName, boolean isDirectory) {\n        for (String s : l)\n            if (isDirectory) {\n                if (fileName.startsWith(s + File.separator))\n                    return true;\n            } else {\n                if (s.startsWith(\"regex:\")) {\n                    if (fileName.matches(s.substring(\"regex:\".length())))\n                        return true;\n                } else {\n                    if (fileName.equals(s))\n                        return true;\n                }\n            }\n        return false;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\npublic enum ModLoaderType {\n    UNKNOWN,\n    FORGE,\n    CLEANROOM,\n    NEO_FORGED,\n    FABRIC,\n    QUILT,\n    LITE_LOADER,\n    LEGACY_FABRIC,\n    PACK\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.GameRepository;\nimport org.jackhuang.hmcl.mod.modinfo.*;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.io.IOException;\nimport java.nio.file.*;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class ModManager {\n    @FunctionalInterface\n    private interface ModMetadataReader {\n        LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException;\n    }\n\n    private static final Map<String, List<Pair<ModMetadataReader, ModLoaderType>>> READERS;\n\n    static {\n        var map = new HashMap<String, List<Pair<ModMetadataReader, ModLoaderType>>>();\n        var zipReaders = List.<Pair<ModMetadataReader, ModLoaderType>>of(\n                pair(ForgeNewModMetadata::fromForgeFile, ModLoaderType.FORGE),\n                pair(ForgeNewModMetadata::fromNeoForgeFile, ModLoaderType.NEO_FORGED),\n                pair(ForgeOldModMetadata::fromFile, ModLoaderType.FORGE),\n                pair(FabricModMetadata::fromFile, ModLoaderType.FABRIC),\n                pair(QuiltModMetadata::fromFile, ModLoaderType.QUILT),\n                pair(PackMcMeta::fromFile, ModLoaderType.PACK)\n        );\n\n        map.put(\"zip\", zipReaders);\n        map.put(\"jar\", zipReaders);\n        map.put(\"litemod\", List.of(pair(LiteModMetadata::fromFile, ModLoaderType.LITE_LOADER)));\n\n        READERS = map;\n    }\n\n    private final GameRepository repository;\n    private final String id;\n    private final TreeSet<LocalModFile> localModFiles = new TreeSet<>();\n    private final HashMap<Pair<String, ModLoaderType>, LocalMod> localMods = new HashMap<>();\n    private LibraryAnalyzer analyzer;\n\n    private boolean loaded = false;\n\n    public ModManager(GameRepository repository, String id) {\n        this.repository = repository;\n        this.id = id;\n    }\n\n    public GameRepository getRepository() {\n        return repository;\n    }\n\n    public String getInstanceId() {\n        return id;\n    }\n\n    public Path getModsDirectory() {\n        return repository.getModsDirectory(id);\n    }\n\n    public LibraryAnalyzer getLibraryAnalyzer() {\n        return analyzer;\n    }\n\n    public LocalMod getLocalMod(String modId, ModLoaderType modLoaderType) {\n        return localMods.computeIfAbsent(pair(modId, modLoaderType),\n                x -> new LocalMod(x.getKey(), x.getValue()));\n    }\n\n    public boolean hasMod(String modId, ModLoaderType modLoaderType) {\n        return localMods.containsKey(pair(modId, modLoaderType));\n    }\n\n    private void addModInfo(Path file) {\n        String fileName = StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION, OLD_EXTENSION);\n        String extension = fileName.substring(fileName.lastIndexOf(\".\") + 1);\n\n        List<Pair<ModMetadataReader, ModLoaderType>> readersMap = READERS.get(extension);\n        if (readersMap == null) {\n            // Is not a mod file.\n            return;\n        }\n\n        Set<ModLoaderType> modLoaderTypes = analyzer.getModLoaders();\n\n        var supportedReaders = new ArrayList<ModMetadataReader>();\n        var unsupportedReaders = new ArrayList<ModMetadataReader>();\n\n        for (Pair<ModMetadataReader, ModLoaderType> reader : readersMap) {\n            if (modLoaderTypes.contains(reader.getValue())) {\n                supportedReaders.add(reader.getKey());\n            } else {\n                unsupportedReaders.add(reader.getKey());\n            }\n        }\n\n        LocalModFile modInfo = null;\n\n        List<Exception> exceptions = new ArrayList<>();\n        try (ZipFileTree tree = CompressingUtils.openZipTree(file)) {\n            for (ModMetadataReader reader : supportedReaders) {\n                try {\n                    modInfo = reader.fromFile(this, file, tree);\n                    break;\n                } catch (Exception e) {\n                    exceptions.add(e);\n                }\n            }\n\n            if (modInfo == null) {\n                for (ModMetadataReader reader : unsupportedReaders) {\n                    try {\n                        modInfo = reader.fromFile(this, file, tree);\n                        break;\n                    } catch (Exception ignored) {\n                    }\n                }\n            }\n        } catch (Exception e) {\n            LOG.warning(\"Failed to open mod file \" + file, e);\n        }\n\n        if (modInfo == null) {\n            Exception exception = new Exception(\"Failed to read mod metadata\");\n            for (Exception e : exceptions) {\n                exception.addSuppressed(e);\n            }\n            LOG.warning(\"Failed to read mod metadata\", exception);\n\n            String fileNameWithoutExtension = FileUtils.getNameWithoutExtension(file);\n\n            modInfo = new LocalModFile(this,\n                    getLocalMod(fileNameWithoutExtension, ModLoaderType.UNKNOWN),\n                    file,\n                    fileNameWithoutExtension,\n                    new LocalModFile.Description(\"litemod\".equals(extension) ? \"LiteLoader Mod\" : \"\")\n            );\n        }\n\n        if (!modInfo.isOld()) {\n            localModFiles.add(modInfo);\n        }\n    }\n\n    public void refreshMods() throws IOException {\n        localModFiles.clear();\n        localMods.clear();\n\n        analyzer = LibraryAnalyzer.analyze(getRepository().getResolvedPreservingPatchesVersion(id), null);\n\n        boolean supportSubfolders = analyzer.has(LibraryAnalyzer.LibraryType.FORGE)\n                || analyzer.has(LibraryAnalyzer.LibraryType.QUILT);\n\n        if (Files.isDirectory(getModsDirectory())) {\n            try (DirectoryStream<Path> modsDirectoryStream = Files.newDirectoryStream(getModsDirectory())) {\n                for (Path subitem : modsDirectoryStream) {\n                    if (supportSubfolders && Files.isDirectory(subitem) && !\".connector\".equalsIgnoreCase(subitem.getFileName().toString())) {\n                        try (DirectoryStream<Path> subitemDirectoryStream = Files.newDirectoryStream(subitem)) {\n                            for (Path subsubitem : subitemDirectoryStream) {\n                                addModInfo(subsubitem);\n                            }\n                        }\n                    } else {\n                        addModInfo(subitem);\n                    }\n                }\n            }\n        }\n        loaded = true;\n    }\n\n    public @Unmodifiable List<LocalModFile> getMods() throws IOException {\n        if (!loaded)\n            refreshMods();\n        return List.copyOf(localModFiles);\n    }\n\n    public void addMod(Path file) throws IOException {\n        if (!isFileNameMod(file))\n            throw new IllegalArgumentException(\"File \" + file + \" is not a valid mod file.\");\n\n        if (!loaded)\n            refreshMods();\n\n        Path modsDirectory = getModsDirectory();\n        Files.createDirectories(modsDirectory);\n\n        Path newFile = modsDirectory.resolve(file.getFileName());\n        FileUtils.copyFile(file, newFile);\n\n        addModInfo(newFile);\n    }\n\n    public void removeMods(LocalModFile... localModFiles) throws IOException {\n        for (LocalModFile localModFile : localModFiles) {\n            Files.deleteIfExists(localModFile.getFile());\n        }\n    }\n\n    public void rollback(LocalModFile from, LocalModFile to) throws IOException {\n        if (!loaded) {\n            throw new IllegalStateException(\"ModManager Not loaded\");\n        }\n        if (!localModFiles.contains(from)) {\n            throw new IllegalStateException(\"Rolling back an unknown mod \" + from.getFileName());\n        }\n        if (from.isOld()) {\n            throw new IllegalArgumentException(\"Rolling back an old mod \" + from.getFileName());\n        }\n        if (!to.isOld()) {\n            throw new IllegalArgumentException(\"Rolling back to an old path \" + to.getFileName());\n        }\n        if (from.getFileName().equals(to.getFileName())) {\n            // We cannot roll back to the mod with the same name.\n            return;\n        }\n\n        LocalMod mod = Objects.requireNonNull(from.getMod());\n        if (mod != to.getMod()) {\n            throw new IllegalArgumentException(\"Rolling back mod \" + from.getFileName() + \" to a different mod \" + to.getFileName());\n        }\n        if (!mod.getFiles().contains(from)\n                || !mod.getOldFiles().contains(to)) {\n            throw new IllegalStateException(\"LocalMod state corrupt\");\n        }\n\n        boolean active = from.isActive();\n        from.setActive(true);\n        from.setOld(true);\n        to.setOld(false);\n        to.setActive(active);\n    }\n\n    private Path backupMod(Path file) throws IOException {\n        Path newPath = file.resolveSibling(\n                StringUtils.addSuffix(\n                        StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION),\n                        OLD_EXTENSION\n                )\n        );\n        if (Files.exists(file)) {\n            Files.move(file, newPath, StandardCopyOption.REPLACE_EXISTING);\n        }\n        return newPath;\n    }\n\n    private Path restoreMod(Path file) throws IOException {\n        Path newPath = file.resolveSibling(\n                StringUtils.removeSuffix(FileUtils.getName(file), OLD_EXTENSION)\n        );\n        if (Files.exists(file)) {\n            Files.move(file, newPath, StandardCopyOption.REPLACE_EXISTING);\n        }\n        return newPath;\n    }\n\n    public Path setOld(LocalModFile modFile, boolean old) throws IOException {\n        Path newPath;\n        if (old) {\n            newPath = backupMod(modFile.getFile());\n            localModFiles.remove(modFile);\n        } else {\n            newPath = restoreMod(modFile.getFile());\n            localModFiles.add(modFile);\n        }\n        return newPath;\n    }\n\n    public Path disableMod(Path file) throws IOException {\n        if (isOld(file)) return file; // no need to disable an old mod.\n\n        String fileName = FileUtils.getName(file);\n        if (fileName.endsWith(DISABLED_EXTENSION)) return file;\n\n        Path disabled = file.resolveSibling(fileName + DISABLED_EXTENSION);\n        if (Files.exists(file))\n            Files.move(file, disabled, StandardCopyOption.REPLACE_EXISTING);\n        return disabled;\n    }\n\n    public Path enableMod(Path file) throws IOException {\n        if (isOld(file)) return file;\n        Path enabled = file.resolveSibling(StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION));\n        if (Files.exists(file))\n            Files.move(file, enabled, StandardCopyOption.REPLACE_EXISTING);\n        return enabled;\n    }\n\n    public static String getModName(Path file) {\n        return StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION, OLD_EXTENSION);\n    }\n\n    public boolean isOld(Path file) {\n        return FileUtils.getName(file).endsWith(OLD_EXTENSION);\n    }\n\n    public boolean isDisabled(Path file) {\n        return FileUtils.getName(file).endsWith(DISABLED_EXTENSION);\n    }\n\n    public static boolean isFileNameMod(Path file) {\n        String name = getModName(file);\n        return name.endsWith(\".zip\") || name.endsWith(\".jar\") || name.endsWith(\".litemod\");\n    }\n\n    public static boolean isFileMod(Path modFile) {\n        try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile)) {\n            if (Files.exists(fs.getPath(\"mcmod.info\")) || Files.exists(fs.getPath(\"META-INF/mods.toml\"))) {\n                // Forge mod\n                return true;\n            }\n\n            if (Files.exists(fs.getPath(\"fabric.mod.json\"))) {\n                // Fabric mod\n                return true;\n            }\n\n            if (Files.exists(fs.getPath(\"quilt.mod.json\"))) {\n                // Quilt mod\n                return true;\n            }\n\n            if (Files.exists(fs.getPath(\"litemod.json\"))) {\n                // Liteloader mod\n                return true;\n            }\n\n            if (Files.exists(fs.getPath(\"pack.mcmeta\"))) {\n                // resource pack, data pack\n                return true;\n            }\n\n            return false;\n        } catch (IOException e) {\n            return false;\n        }\n    }\n\n    /**\n     * Check if \"mods\" directory has mod file named \"fileName\" no matter the mod is disabled or not\n     *\n     * @param fileName name of the file whose existence is being checked\n     * @return true if the file exists\n     */\n    public boolean hasSimpleMod(String fileName) {\n        return Files.exists(getModsDirectory().resolve(StringUtils.removeSuffix(fileName, DISABLED_EXTENSION)))\n                || Files.exists(getModsDirectory().resolve(StringUtils.addSuffix(fileName, DISABLED_EXTENSION)));\n    }\n\n    public Path getSimpleModPath(String fileName) {\n        return getModsDirectory().resolve(fileName);\n    }\n\n    public static final String DISABLED_EXTENSION = \".disabled\";\n    public static final String OLD_EXTENSION = \".old\";\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\nimport java.util.List;\n\n/**\n *\n * @author huangyuhui\n */\npublic abstract class Modpack {\n    private String name;\n    private String author;\n    private String version;\n    private String gameVersion;\n    private String description;\n    private transient Charset encoding;\n    private ModpackManifest manifest;\n\n    public Modpack() {\n        this(\"\", null, null, null, null, null, null);\n    }\n\n    public Modpack(String name, String author, String version, String gameVersion, String description, Charset encoding, ModpackManifest manifest) {\n        this.name = name;\n        this.author = author;\n        this.version = version;\n        this.gameVersion = gameVersion;\n        this.description = description;\n        this.encoding = encoding;\n        this.manifest = manifest;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public Modpack setName(String name) {\n        this.name = name;\n        return this;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public Modpack setAuthor(String author) {\n        this.author = author;\n        return this;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public Modpack setVersion(String version) {\n        this.version = version;\n        return this;\n    }\n\n    public String getGameVersion() {\n        return gameVersion;\n    }\n\n    public Modpack setGameVersion(String gameVersion) {\n        this.gameVersion = gameVersion;\n        return this;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public Modpack setDescription(String description) {\n        this.description = description;\n        return this;\n    }\n\n    public Charset getEncoding() {\n        return encoding;\n    }\n\n    public Modpack setEncoding(Charset encoding) {\n        this.encoding = encoding;\n        return this;\n    }\n\n    public ModpackManifest getManifest() {\n        return manifest;\n    }\n\n    public Modpack setManifest(ModpackManifest manifest) {\n        this.manifest = manifest;\n        return this;\n    }\n\n    public abstract Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl);\n\n    public static boolean acceptFile(String path, List<String> blackList, List<String> whiteList) {\n        if (path.isEmpty())\n            return true;\n        if (ModAdviser.match(blackList, path, false))\n            return false;\n        if (whiteList == null || whiteList.isEmpty())\n            return true;\n        for (String s : whiteList)\n            if (path.equals(s))\n                return true;\n        return false;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackCompletionException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\npublic class ModpackCompletionException extends Exception {\n    public ModpackCompletionException() {\n    }\n\n    public ModpackCompletionException(String message) {\n        super(message);\n    }\n\n    public ModpackCompletionException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public ModpackCompletionException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.reflect.TypeToken;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n@Immutable\npublic final class ModpackConfiguration<T> implements Validation {\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> TypeToken<ModpackConfiguration<T>> typeOf(Class<T> clazz) {\n        return (TypeToken<ModpackConfiguration<T>>) TypeToken.getParameterized(ModpackConfiguration.class, clazz);\n    }\n\n    private final T manifest;\n    private final String type;\n    private final String name;\n    private final String version;\n    private final List<FileInformation> overrides;\n\n    public ModpackConfiguration() {\n        this(null, null, \"\", null, Collections.emptyList());\n    }\n\n    public ModpackConfiguration(T manifest, String type, String name, String version, List<FileInformation> overrides) {\n        this.manifest = manifest;\n        this.type = type;\n        this.name = name;\n        this.version = version;\n        this.overrides = new ArrayList<>(overrides);\n    }\n\n    public T getManifest() {\n        return manifest;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    @Nullable\n    public String getVersion() {\n        return version;\n    }\n\n    public ModpackConfiguration<T> setManifest(T manifest) {\n        return new ModpackConfiguration<>(manifest, type, name, version, overrides);\n    }\n\n    public ModpackConfiguration<T> setOverrides(List<FileInformation> overrides) {\n        return new ModpackConfiguration<>(manifest, type, name, version, overrides);\n    }\n\n    public ModpackConfiguration<T> setVersion(String version) {\n        return new ModpackConfiguration<>(manifest, type, name, version, overrides);\n    }\n\n    public List<FileInformation> getOverrides() {\n        return Collections.unmodifiableList(overrides);\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (manifest == null)\n            throw new JsonParseException(\"MinecraftInstanceConfiguration missing `manifest`\");\n        if (type == null)\n            throw new JsonParseException(\"MinecraftInstanceConfiguration missing `type`\");\n    }\n\n    @Immutable\n    public static class FileInformation implements Validation {\n        private final String path; // relative\n        private final String hash;\n        private final String downloadURL;\n\n        public FileInformation() {\n            this(null, null);\n        }\n\n        public FileInformation(String path, String hash) {\n            this(path, hash, null);\n        }\n\n        public FileInformation(String path, String hash, String downloadURL) {\n            this.path = path;\n            this.hash = hash;\n            this.downloadURL = downloadURL;\n        }\n\n        /**\n         * The relative path to Minecraft run directory\n         *\n         * @return the relative path to Minecraft run directory.\n         */\n        public String getPath() {\n            return path;\n        }\n\n        public String getDownloadURL() {\n            return downloadURL;\n        }\n\n        public String getHash() {\n            return hash;\n        }\n\n        @Override\n        public void validate() throws JsonParseException {\n            if (path == null)\n                throw new JsonParseException(\"FileInformation missing `path`.\");\n            if (hash == null)\n                throw new JsonParseException(\"FileInformation missing file hash code.\");\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackExportInfo.java",
    "content": "package org.jackhuang.hmcl.mod;\n\nimport org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class ModpackExportInfo {\n\n    private final List<String> whitelist = new ArrayList<>();\n    private String name;\n    private String author;\n    private String version;\n    private String description;\n    private String url;\n\n    private boolean forceUpdate;\n    private boolean packWithLauncher;\n\n    private String fileApi;\n    private int minMemory;\n    private List<Integer> supportedJavaVersions;\n    private String launchArguments;\n    private String javaArguments;\n\n    private String authlibInjectorServer;\n\n    private List<McbbsModpackManifest.Origin> origins = new ArrayList<>();\n\n    private boolean noCreateRemoteFiles;\n    private boolean skipCurseForgeRemoteFiles;\n\n    public ModpackExportInfo() {}\n\n    public List<String> getWhitelist() {\n        return whitelist;\n    }\n\n    public ModpackExportInfo setWhitelist(List<String> whitelist) {\n        this.whitelist.clear();\n        this.whitelist.addAll(whitelist);\n        return this;\n    }\n\n    /**\n     * Name of this modpack.\n     */\n    public String getName() {\n        return name;\n    }\n\n    public ModpackExportInfo setName(String name) {\n        this.name = name;\n        return this;\n    }\n\n    /**\n     * Author of this modpack.\n     */\n    public String getAuthor() {\n        return author;\n    }\n\n    public ModpackExportInfo setAuthor(String author) {\n        this.author = author;\n        return this;\n    }\n\n    /**\n     * Version of this modpack.\n     */\n    public String getVersion() {\n        return version;\n    }\n\n    public ModpackExportInfo setVersion(String version) {\n        this.version = version;\n        return this;\n    }\n\n    /**\n     * Description of this modpack.\n     *\n     * Supports plain HTML text.\n     */\n    public String getDescription() {\n        return description;\n    }\n\n    public ModpackExportInfo setDescription(String description) {\n        this.description = description;\n        return this;\n    }\n\n    public String getFileApi() {\n        return fileApi;\n    }\n\n    public ModpackExportInfo setFileApi(String fileApi) {\n        this.fileApi = fileApi;\n        return this;\n    }\n\n    /**\n     * Modpack official introduction webpage link.\n     */\n    public String getUrl() {\n        return url;\n    }\n\n    public ModpackExportInfo setUrl(String url) {\n        this.url = url;\n        return this;\n    }\n\n    public boolean isForceUpdate() {\n        return forceUpdate;\n    }\n\n    public ModpackExportInfo setForceUpdate(boolean forceUpdate) {\n        this.forceUpdate = forceUpdate;\n        return this;\n    }\n\n    public boolean isPackWithLauncher() {\n        return packWithLauncher;\n    }\n\n    public ModpackExportInfo setPackWithLauncher(boolean packWithLauncher) {\n        this.packWithLauncher = packWithLauncher;\n        return this;\n    }\n\n    public int getMinMemory() {\n        return minMemory;\n    }\n\n    public ModpackExportInfo setMinMemory(int minMemory) {\n        this.minMemory = minMemory;\n        return this;\n    }\n\n    @Nullable\n    public List<Integer> getSupportedJavaVersions() {\n        return supportedJavaVersions;\n    }\n\n    public ModpackExportInfo setSupportedJavaVersions(List<Integer> supportedJavaVersions) {\n        this.supportedJavaVersions = supportedJavaVersions;\n        return this;\n    }\n\n    public String getLaunchArguments() {\n        return launchArguments;\n    }\n\n    public ModpackExportInfo setLaunchArguments(String launchArguments) {\n        this.launchArguments = launchArguments;\n        return this;\n    }\n\n    public String getJavaArguments() {\n        return javaArguments;\n    }\n\n    public ModpackExportInfo setJavaArguments(String javaArguments) {\n        this.javaArguments = javaArguments;\n        return this;\n    }\n\n    public String getAuthlibInjectorServer() {\n        return authlibInjectorServer;\n    }\n\n    public ModpackExportInfo setAuthlibInjectorServer(String authlibInjectorServer) {\n        this.authlibInjectorServer = authlibInjectorServer;\n        return this;\n    }\n\n    public List<McbbsModpackManifest.Origin> getOrigins() {\n        return Collections.unmodifiableList(origins);\n    }\n\n    public ModpackExportInfo setOrigins(List<McbbsModpackManifest.Origin> origins) {\n        this.origins.clear();\n        this.origins.addAll(origins);\n        return this;\n    }\n\n    public boolean isNoCreateRemoteFiles() {\n        return noCreateRemoteFiles;\n    }\n\n    public void setNoCreateRemoteFiles(boolean noCreateRemoteFiles) {\n        this.noCreateRemoteFiles = noCreateRemoteFiles;\n    }\n\n    public boolean isSkipCurseForgeRemoteFiles() {\n        return skipCurseForgeRemoteFiles;\n    }\n\n    public void setSkipCurseForgeRemoteFiles(boolean skipCurseForgeRemoteFiles) {\n        this.skipCurseForgeRemoteFiles = skipCurseForgeRemoteFiles;\n    }\n\n    public ModpackExportInfo validate() throws NullPointerException {\n        return this;\n    }\n\n    public static class Options {\n        private boolean requireAuthor;\n        private boolean requireUrl;\n        private boolean requireForceUpdate;\n        private boolean requireFileApi;\n        private boolean validateFileApi;\n        private boolean requireMinMemory;\n        private boolean requireAuthlibInjectorServer;\n        private boolean requireLaunchArguments;\n        private boolean requireJavaArguments;\n        private boolean requireOrigins;\n        private boolean requireNoCreateRemoteFiles;\n        private boolean requireSkipCurseForgeRemoteFiles;\n\n        public Options() {\n        }\n\n        public boolean isRequireAuthor() {\n            return requireAuthor;\n        }\n\n        public boolean isRequireUrl() {\n            return requireUrl;\n        }\n\n        public boolean isRequireForceUpdate() {\n            return requireForceUpdate;\n        }\n\n        public boolean isRequireFileApi() {\n            return requireFileApi;\n        }\n\n        public boolean isValidateFileApi() {\n            return validateFileApi;\n        }\n\n        public boolean isRequireMinMemory() {\n            return requireMinMemory;\n        }\n\n        public boolean isRequireAuthlibInjectorServer() {\n            return requireAuthlibInjectorServer;\n        }\n\n        public boolean isRequireLaunchArguments() {\n            return requireLaunchArguments;\n        }\n\n        public boolean isRequireJavaArguments() {\n            return requireJavaArguments;\n        }\n\n        public boolean isRequireOrigins() {\n            return requireOrigins;\n        }\n\n        public boolean isRequireNoCreateRemoteFiles() {\n            return requireNoCreateRemoteFiles;\n        }\n\n        public boolean isRequireSkipCurseForgeRemoteFiles() {\n            return requireSkipCurseForgeRemoteFiles;\n        }\n\n        public Options requireAuthor() {\n            requireAuthor = true;\n            return this;\n        }\n\n        public Options requireUrl() {\n            requireUrl = true;\n            return this;\n        }\n\n        public Options requireForceUpdate() {\n            requireForceUpdate = true;\n            return this;\n        }\n\n        public Options requireFileApi(boolean optional) {\n            requireFileApi = true;\n            validateFileApi = !optional;\n            return this;\n        }\n\n        public Options requireMinMemory() {\n            requireMinMemory = true;\n            return this;\n        }\n\n        public Options requireAuthlibInjectorServer() {\n            requireAuthlibInjectorServer = true;\n            return this;\n        }\n\n        public Options requireLaunchArguments() {\n            requireLaunchArguments = true;\n            return this;\n        }\n\n        public Options requireJavaArguments() {\n            requireJavaArguments = true;\n            return this;\n        }\n\n        public Options requireOrigins() {\n            requireOrigins = true;\n            return this;\n        }\n\n        public Options requireNoCreateRemoteFiles() {\n            requireNoCreateRemoteFiles = true;\n            return this;\n        }\n\n        public Options requireSkipCurseForgeRemoteFiles() {\n            requireSkipCurseForgeRemoteFiles = true;\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.io.Unzipper;\n\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.function.Predicate;\n\npublic class ModpackInstallTask<T> extends Task<Void> {\n\n    private final Path modpackFile;\n    private final Path dest;\n    private final Charset charset;\n    private final List<String> subDirectories;\n    private final List<ModpackConfiguration.FileInformation> overrides;\n    private final Predicate<String> callback;\n\n    /// Constructor\n    ///\n    /// @param modpackFile      a zip file\n    /// @param dest             destination to store unpacked files\n    /// @param charset          charset of the zip file\n    /// @param subDirectories   the subdirectory of zip file to unpack\n    /// @param callback         test whether the file (given full path) in zip file should be unpacked or not\n    /// @param oldConfiguration old modpack information if upgrade\n    public ModpackInstallTask(Path modpackFile, Path dest, Charset charset, List<String> subDirectories, Predicate<String> callback, ModpackConfiguration<T> oldConfiguration) {\n        this.modpackFile = modpackFile;\n        this.dest = dest;\n        this.charset = charset;\n        this.subDirectories = subDirectories;\n        this.callback = callback;\n\n        if (oldConfiguration == null)\n            overrides = Collections.emptyList();\n        else\n            overrides = oldConfiguration.getOverrides();\n    }\n\n    @Override\n    public void execute() throws Exception {\n        Set<String> entries = new HashSet<>();\n        Files.createDirectories(dest);\n\n        HashMap<String, ModpackConfiguration.FileInformation> files = new HashMap<>();\n        for (ModpackConfiguration.FileInformation file : overrides)\n            files.put(file.getPath(), file);\n\n\n        for (String subDirectory : subDirectories) {\n            new Unzipper(modpackFile, dest)\n                    .setSubDirectory(subDirectory)\n                    .setTerminateIfSubDirectoryNotExists()\n                    .setReplaceExistentFile(true)\n                    .setEncoding(charset)\n                    .setFilter((zipEntry, destFile, relativePath) -> {\n                        if (zipEntry.isDirectory()) return true;\n                        if (!callback.test(relativePath)) return false;\n                        entries.add(relativePath);\n\n                        if (!files.containsKey(relativePath)) {\n                            // If old modpack does not have this entry, add this entry or override the file that user added.\n                            return true;\n                        } else if (!Files.exists(destFile)) {\n                            // If both old and new modpacks have this entry, but the file is deleted by user, leave it missing.\n                            return false;\n                        } else {\n                            // If both old and new modpacks have this entry, and user has modified this file,\n                            // we will not replace it since this modified file is what user expects.\n                            String fileHash = DigestUtils.digestToString(\"SHA-1\", destFile);\n                            String oldHash = files.get(relativePath).getHash();\n                            return Objects.equals(oldHash, fileHash);\n                        }\n                    }).unzip();\n        }\n\n        // If old modpack have this entry, and new modpack deleted it. Delete this file.\n        for (ModpackConfiguration.FileInformation file : overrides) {\n            Path original = dest.resolve(file.getPath());\n            if (Files.exists(original) && !entries.contains(file.getPath()))\n                Files.deleteIfExists(original);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackManifest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\npublic interface ModpackManifest {\n    ModpackProvider getProvider();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport com.google.gson.JsonParseException;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.LaunchOptions;\nimport org.jackhuang.hmcl.task.Task;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\n\npublic interface ModpackProvider {\n\n    String getName();\n\n    Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version);\n\n    Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException;\n\n    /**\n     * @param zipFile the opened modpack zip file.\n     * @param file the modpack zip file path.\n     * @param encoding encoding of zip file.\n     * @throws IOException if the file is not a valid zip file.\n     * @throws JsonParseException if the manifest.json is missing or malformed.\n     * @return the manifest.\n     */\n    Modpack readManifest(ZipArchiveReader zipFile, Path file, Charset encoding) throws IOException, JsonParseException;\n\n    default void injectLaunchOptions(String modpackConfigurationJson, LaunchOptions.Builder builder) {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackUpdateTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collection;\nimport java.util.Collections;\n\npublic class ModpackUpdateTask extends Task<Void> {\n\n    private final DefaultGameRepository repository;\n    private final String id;\n    private final Task<?> updateTask;\n    private final Path backupFolder;\n\n    public ModpackUpdateTask(DefaultGameRepository repository, String id, Task<?> updateTask) {\n        this.repository = repository;\n        this.id = id;\n        this.updateTask = updateTask;\n\n        Path backup = repository.getBaseDirectory().resolve(\"backup\");\n        while (true) {\n            int num = (int)(Math.random() * 10000000);\n            if (!Files.exists(backup.resolve(id + \"-\" + num))) {\n                backupFolder = backup.resolve(id + \"-\" + num);\n                break;\n            }\n        }\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return Collections.singleton(updateTask);\n    }\n\n    @Override\n    public void execute() throws Exception {\n        FileUtils.copyDirectory(repository.getVersionRoot(id), backupFolder);\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        if (isDependenciesSucceeded()) {\n            // Keep backup game version for further repair.\n        } else {\n            // Restore backup\n            repository.removeVersionFromDisk(id);\n\n            FileUtils.copyDirectory(backupFolder, repository.getVersionRoot(id));\n\n            repository.refreshVersionsAsync().start();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;\nimport org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\npublic final class RemoteMod {\n\n    public static final RemoteMod BROKEN = new RemoteMod(\"\", \"\", \"RemoteMod.BROKEN\", \"\", Collections.emptyList(), \"\", \"\", new RemoteMod.IMod() {\n        @Override\n        public List<RemoteMod> loadDependencies(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {\n            throw new IOException();\n        }\n\n        @Override\n        public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {\n            throw new IOException();\n        }\n    });\n\n    private final String slug;\n    private final String author;\n    private final String title;\n    private final String description;\n    private final List<String> categories;\n    private final String pageUrl;\n    private final String iconUrl;\n    private final IMod data;\n\n    public RemoteMod(String slug, String author, String title, String description, List<String> categories, String pageUrl, String iconUrl, IMod data) {\n        this.slug = slug;\n        this.author = author;\n        this.title = title;\n        this.description = description;\n        this.categories = categories;\n        this.pageUrl = pageUrl;\n        this.iconUrl = iconUrl;\n        this.data = data;\n    }\n\n    public String getSlug() {\n        return slug;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public List<String> getCategories() {\n        return categories;\n    }\n\n    public String getPageUrl() {\n        return pageUrl;\n    }\n\n    public String getIconUrl() {\n        return iconUrl;\n    }\n\n    public IMod getData() {\n        return data;\n    }\n\n    public enum VersionType {\n        Release,\n        Beta,\n        Alpha\n    }\n\n    public enum DependencyType {\n        REQUIRED,\n        OPTIONAL,\n        TOOL,\n        INCLUDE,\n        EMBEDDED,\n        INCOMPATIBLE,\n        BROKEN\n    }\n\n    public static final class Dependency {\n        private static Dependency BROKEN_DEPENDENCY = null;\n\n        private final DependencyType type;\n\n        private final RemoteModRepository remoteModRepository;\n\n        private final String id;\n\n        private transient RemoteMod remoteMod = null;\n\n        private Dependency(DependencyType type, RemoteModRepository remoteModRepository, String modid) {\n            this.type = type;\n            this.remoteModRepository = remoteModRepository;\n            this.id = modid;\n        }\n\n        public static Dependency ofGeneral(DependencyType type, RemoteModRepository remoteModRepository, String modid) {\n            if (type == DependencyType.BROKEN) {\n                return ofBroken();\n            } else {\n                return new Dependency(type, remoteModRepository, modid);\n            }\n        }\n\n        public static Dependency ofBroken() {\n            if (BROKEN_DEPENDENCY == null) {\n                BROKEN_DEPENDENCY = new Dependency(DependencyType.BROKEN, null, null);\n            }\n            return BROKEN_DEPENDENCY;\n        }\n\n        public DependencyType getType() {\n            return this.type;\n        }\n\n        public RemoteModRepository getRemoteModRepository() {\n            return this.remoteModRepository;\n        }\n\n        public String getId() {\n            return this.id;\n        }\n\n        public RemoteMod load(DownloadProvider downloadProvider) throws IOException {\n            if (this.remoteMod == null) {\n                if (this.type == DependencyType.BROKEN) {\n                    this.remoteMod = RemoteMod.BROKEN;\n                } else {\n                    this.remoteMod = this.remoteModRepository.resolveDependency(downloadProvider, this.id);\n                }\n            }\n            return this.remoteMod;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n\n            Dependency that = (Dependency) o;\n\n            if (type != that.type) return false;\n            if (!remoteModRepository.equals(that.remoteModRepository)) return false;\n            return id.equals(that.id);\n        }\n\n        @Override\n        public int hashCode() {\n            int result = type.hashCode();\n            result = 31 * result + remoteModRepository.hashCode();\n            result = 31 * result + id.hashCode();\n            return result;\n        }\n    }\n\n    public enum Type {\n        CURSEFORGE(CurseForgeRemoteModRepository.MODS),\n        MODRINTH(ModrinthRemoteModRepository.MODS);\n\n        private final RemoteModRepository remoteModRepository;\n\n        public RemoteModRepository getRemoteModRepository() {\n            return this.remoteModRepository;\n        }\n\n        Type(RemoteModRepository remoteModRepository) {\n            this.remoteModRepository = remoteModRepository;\n        }\n    }\n\n    public interface IMod {\n        List<RemoteMod> loadDependencies(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException;\n\n        Stream<Version> loadVersions(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException;\n    }\n\n    public interface IVersion {\n        Type getType();\n    }\n\n    public static class Version {\n        private final IVersion self;\n        private final String modid;\n        private final String name;\n        private final String version;\n        private final String changelog;\n        private final Instant datePublished;\n        private final VersionType versionType;\n        private final File file;\n        private final List<Dependency> dependencies;\n        private final List<String> gameVersions;\n        private final List<ModLoaderType> loaders;\n\n        public Version(IVersion self, String modid, String name, String version, String changelog, Instant datePublished, VersionType versionType, File file, List<Dependency> dependencies, List<String> gameVersions, List<ModLoaderType> loaders) {\n            this.self = self;\n            this.modid = modid;\n            this.name = name;\n            this.version = version;\n            this.changelog = changelog;\n            this.datePublished = datePublished;\n            this.versionType = versionType;\n            this.file = file;\n            this.dependencies = dependencies;\n            this.gameVersions = gameVersions;\n            this.loaders = loaders;\n        }\n\n        public IVersion getSelf() {\n            return self;\n        }\n\n        public String getModid() {\n            return modid;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        public String getChangelog() {\n            return changelog;\n        }\n\n        public Instant getDatePublished() {\n            return datePublished;\n        }\n\n        public VersionType getVersionType() {\n            return versionType;\n        }\n\n        public File getFile() {\n            return file;\n        }\n\n        public List<Dependency> getDependencies() {\n            return dependencies;\n        }\n\n        public List<String> getGameVersions() {\n            return gameVersions;\n        }\n\n        public List<ModLoaderType> getLoaders() {\n            return loaders;\n        }\n    }\n\n    public static class File {\n        private final Map<String, String> hashes;\n        private final String url;\n        private final String filename;\n\n        public File(Map<String, String> hashes, String url, String filename) {\n            this.hashes = hashes;\n            this.url = url;\n            this.filename = filename;\n        }\n\n        public Map<String, String> getHashes() {\n            return hashes;\n        }\n\n        public FileDownloadTask.IntegrityCheck getIntegrityCheck() {\n            if (hashes.containsKey(\"md5\")) {\n                return new FileDownloadTask.IntegrityCheck(\"MD5\", hashes.get(\"md5\"));\n            } else if (hashes.containsKey(\"sha1\")) {\n                return new FileDownloadTask.IntegrityCheck(\"SHA-1\", hashes.get(\"sha1\"));\n            } else if (hashes.containsKey(\"sha256\")) {\n                return new FileDownloadTask.IntegrityCheck(\"SHA-256\", hashes.get(\"sha256\"));\n            } else if (hashes.containsKey(\"sha512\")) {\n                return new FileDownloadTask.IntegrityCheck(\"SHA-512\", hashes.get(\"sha512\"));\n            } else {\n                return null;\n            }\n        }\n\n        public String getUrl() {\n            return url;\n        }\n\n        public String getFilename() {\n            return filename;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\npublic interface RemoteModRepository {\n\n    enum Type {\n        MOD,\n        MODPACK,\n        RESOURCE_PACK,\n        SHADER_PACK,\n        WORLD,\n        CUSTOMIZATION\n    }\n\n    Type getType();\n\n    enum SortType {\n        POPULARITY,\n        NAME,\n        DATE_CREATED,\n        LAST_UPDATED,\n        AUTHOR,\n        TOTAL_DOWNLOADS\n    }\n\n    enum SortOrder {\n        ASC,\n        DESC\n    }\n\n    class SearchResult {\n        private final Stream<RemoteMod> sortedResults;\n\n        private final Stream<RemoteMod> unsortedResults;\n\n        private final int totalPages;\n\n        public SearchResult(Stream<RemoteMod> sortedResults, Stream<RemoteMod> unsortedResults, int totalPages) {\n            this.sortedResults = sortedResults;\n            this.unsortedResults = unsortedResults;\n            this.totalPages = totalPages;\n        }\n\n        public SearchResult(Stream<RemoteMod> sortedResults, int pages) {\n            this.sortedResults = sortedResults;\n            this.unsortedResults = sortedResults;\n            this.totalPages = pages;\n        }\n\n        public Stream<RemoteMod> getResults() {\n            return this.sortedResults;\n        }\n\n        public Stream<RemoteMod> getUnsortedResults() {\n            return this.unsortedResults;\n        }\n\n        public int getTotalPages() {\n            return this.totalPages;\n        }\n    }\n\n    SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder)\n            throws IOException;\n\n    Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException;\n\n    RemoteMod getModById(DownloadProvider downloadProvider, String id) throws IOException;\n\n    default RemoteMod resolveDependency(DownloadProvider downloadProvider, String id) throws IOException {\n        return getModById(downloadProvider, id);\n    }\n\n    RemoteMod.File getModFile(String modId, String fileId) throws IOException;\n\n    Stream<RemoteMod.Version> getRemoteVersionsById(DownloadProvider downloadProvider, String id) throws IOException;\n\n    Stream<Category> getCategories() throws IOException;\n\n    class Category {\n        private final Object self;\n        private final String id;\n        private final List<Category> subcategories;\n\n        public Category(Object self, String id, List<Category> subcategories) {\n            this.self = self;\n            this.id = id;\n            this.subcategories = subcategories;\n        }\n\n        public Object getSelf() {\n            return self;\n        }\n\n        public String getId() {\n            return id;\n        }\n\n        public List<Category> getSubcategories() {\n            return subcategories;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/UnsupportedModpackException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod;\n\npublic class UnsupportedModpackException extends Exception {\n    public UnsupportedModpackException() {\n    }\n\n    public UnsupportedModpackException(String message) {\n        super(message);\n    }\n\n    public UnsupportedModpackException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public UnsupportedModpackException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.RemoteModRepository;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.util.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n@Immutable\npublic class CurseAddon implements RemoteMod.IMod {\n    public static final Map<Integer, RemoteMod.DependencyType> RELATION_TYPE = Lang.mapOf(\n            Pair.pair(1, RemoteMod.DependencyType.EMBEDDED),\n            Pair.pair(2, RemoteMod.DependencyType.OPTIONAL),\n            Pair.pair(3, RemoteMod.DependencyType.REQUIRED),\n            Pair.pair(4, RemoteMod.DependencyType.TOOL),\n            Pair.pair(5, RemoteMod.DependencyType.INCOMPATIBLE),\n            Pair.pair(6, RemoteMod.DependencyType.INCLUDE)\n    );\n\n    private final int id;\n    private final int gameId;\n    private final String name;\n    private final String slug;\n    private final Links links;\n    private final String summary;\n    private final int status;\n    private final int downloadCount;\n    private final boolean isFeatured;\n    private final int primaryCategoryId;\n    private final List<Category> categories;\n    private final int classId;\n    private final List<Author> authors;\n    private final Logo logo;\n    private final int mainFileId;\n    private final List<LatestFile> latestFiles;\n    private final List<LatestFileIndex> latestFileIndices;\n    private final Instant dateCreated;\n    private final Instant dateModified;\n    private final Instant dateReleased;\n    private final boolean allowModDistribution;\n    private final int gamePopularityRank;\n    private final boolean isAvailable;\n    private final int thumbsUpCount;\n\n    public CurseAddon(int id, int gameId, String name, String slug, Links links, String summary, int status, int downloadCount, boolean isFeatured, int primaryCategoryId, List<Category> categories, int classId, List<Author> authors, Logo logo, int mainFileId, List<LatestFile> latestFiles, List<LatestFileIndex> latestFileIndices, Instant dateCreated, Instant dateModified, Instant dateReleased, boolean allowModDistribution, int gamePopularityRank, boolean isAvailable, int thumbsUpCount) {\n        this.id = id;\n        this.gameId = gameId;\n        this.name = name;\n        this.slug = slug;\n        this.links = links;\n        this.summary = summary;\n        this.status = status;\n        this.downloadCount = downloadCount;\n        this.isFeatured = isFeatured;\n        this.primaryCategoryId = primaryCategoryId;\n        this.categories = categories;\n        this.classId = classId;\n        this.authors = authors;\n        this.logo = logo;\n        this.mainFileId = mainFileId;\n        this.latestFiles = latestFiles;\n        this.latestFileIndices = latestFileIndices;\n        this.dateCreated = dateCreated;\n        this.dateModified = dateModified;\n        this.dateReleased = dateReleased;\n        this.allowModDistribution = allowModDistribution;\n        this.gamePopularityRank = gamePopularityRank;\n        this.isAvailable = isAvailable;\n        this.thumbsUpCount = thumbsUpCount;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public int getGameId() {\n        return gameId;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getSlug() {\n        return slug;\n    }\n\n    public Links getLinks() {\n        return links;\n    }\n\n    public String getSummary() {\n        return summary;\n    }\n\n    public int getStatus() {\n        return status;\n    }\n\n    public int getDownloadCount() {\n        return downloadCount;\n    }\n\n    public boolean isFeatured() {\n        return isFeatured;\n    }\n\n    public int getPrimaryCategoryId() {\n        return primaryCategoryId;\n    }\n\n    public List<Category> getCategories() {\n        return categories;\n    }\n\n    public int getClassId() {\n        return classId;\n    }\n\n    public List<Author> getAuthors() {\n        return authors;\n    }\n\n    public Logo getLogo() {\n        return logo;\n    }\n\n    public int getMainFileId() {\n        return mainFileId;\n    }\n\n    public List<LatestFile> getLatestFiles() {\n        return latestFiles;\n    }\n\n    public List<LatestFileIndex> getLatestFileIndices() {\n        return latestFileIndices;\n    }\n\n    public Instant getDateCreated() {\n        return dateCreated;\n    }\n\n    public Instant getDateModified() {\n        return dateModified;\n    }\n\n    public Instant getDateReleased() {\n        return dateReleased;\n    }\n\n    public boolean isAllowModDistribution() {\n        return allowModDistribution;\n    }\n\n    public int getGamePopularityRank() {\n        return gamePopularityRank;\n    }\n\n    public boolean isAvailable() {\n        return isAvailable;\n    }\n\n    public int getThumbsUpCount() {\n        return thumbsUpCount;\n    }\n\n    @Override\n    public List<RemoteMod> loadDependencies(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {\n        Set<Integer> dependencies = latestFiles.stream()\n                .flatMap(latestFile -> latestFile.getDependencies().stream())\n                .filter(dep -> dep.getRelationType() == 3)\n                .map(Dependency::getModId)\n                .collect(Collectors.toSet());\n        List<RemoteMod> mods = new ArrayList<>();\n        for (int dependencyId : dependencies) {\n            mods.add(modRepository.getModById(downloadProvider, Integer.toString(dependencyId)));\n        }\n        return mods;\n    }\n\n    @Override\n    public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {\n        return modRepository.getRemoteVersionsById(downloadProvider, Integer.toString(id));\n    }\n\n    public RemoteMod toMod() {\n        String iconUrl = Optional.ofNullable(logo).map(Logo::getThumbnailUrl).orElse(\"\");\n\n        return new RemoteMod(\n                slug,\n                \"\",\n                name,\n                summary,\n                categories.stream().map(category -> Integer.toString(category.getId())).collect(Collectors.toList()),\n                links.websiteUrl,\n                iconUrl,\n                this\n        );\n    }\n\n    @Immutable\n    public static class Links {\n        private final String websiteUrl;\n        private final String wikiUrl;\n        private final String issuesUrl;\n        private final String sourceUrl;\n\n        public Links(String websiteUrl, String wikiUrl, String issuesUrl, String sourceUrl) {\n            this.websiteUrl = websiteUrl;\n            this.wikiUrl = wikiUrl;\n            this.issuesUrl = issuesUrl;\n            this.sourceUrl = sourceUrl;\n        }\n\n        public String getWebsiteUrl() {\n            return websiteUrl;\n        }\n\n        public String getWikiUrl() {\n            return wikiUrl;\n        }\n\n        @Nullable\n        public String getIssuesUrl() {\n            return issuesUrl;\n        }\n\n        @Nullable\n        public String getSourceUrl() {\n            return sourceUrl;\n        }\n    }\n\n    @Immutable\n    public static class Author {\n        private final int id;\n        private final String name;\n        private final String url;\n\n        public Author(int id, String name, String url) {\n            this.id = id;\n            this.name = name;\n            this.url = url;\n        }\n\n        public int getId() {\n            return id;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public String getUrl() {\n            return url;\n        }\n    }\n\n    @Immutable\n    public static class Logo {\n        private final int id;\n        private final int modId;\n        private final String title;\n        private final String description;\n        private final String thumbnailUrl;\n        private final String url;\n\n        public Logo(int id, int modId, String title, String description, String thumbnailUrl, String url) {\n            this.id = id;\n            this.modId = modId;\n            this.title = title;\n            this.description = description;\n            this.thumbnailUrl = thumbnailUrl;\n            this.url = url;\n        }\n\n        public int getId() {\n            return id;\n        }\n\n        public int getModId() {\n            return modId;\n        }\n\n        public String getTitle() {\n            return title;\n        }\n\n        public String getDescription() {\n            return description;\n        }\n\n        public String getThumbnailUrl() {\n            return thumbnailUrl;\n        }\n\n        public String getUrl() {\n            return url;\n        }\n    }\n\n    @Immutable\n    public static class Attachment {\n        private final int id;\n        private final int projectId;\n        private final String description;\n        private final boolean isDefault;\n        private final String thumbnailUrl;\n        private final String title;\n        private final String url;\n        private final int status;\n\n        public Attachment(int id, int projectId, String description, boolean isDefault, String thumbnailUrl, String title, String url, int status) {\n            this.id = id;\n            this.projectId = projectId;\n            this.description = description;\n            this.isDefault = isDefault;\n            this.thumbnailUrl = thumbnailUrl;\n            this.title = title;\n            this.url = url;\n            this.status = status;\n        }\n\n        public int getId() {\n            return id;\n        }\n\n        public int getProjectId() {\n            return projectId;\n        }\n\n        public String getDescription() {\n            return description;\n        }\n\n        public boolean isDefault() {\n            return isDefault;\n        }\n\n        public String getThumbnailUrl() {\n            return thumbnailUrl;\n        }\n\n        public String getTitle() {\n            return title;\n        }\n\n        public String getUrl() {\n            return url;\n        }\n\n        public int getStatus() {\n            return status;\n        }\n    }\n\n    @Immutable\n    public static class Dependency {\n        private final int modId;\n        private final int relationType;\n\n        public Dependency() {\n            this(0, 1);\n        }\n\n        public Dependency(int modId, int relationType) {\n            this.modId = modId;\n            this.relationType = relationType;\n        }\n\n        public int getModId() {\n            return modId;\n        }\n\n        public int getRelationType() {\n            return relationType;\n        }\n    }\n\n    /**\n     * @see <a href=\"https://docs.curseforge.com/#schemafilehash\">Schema</a>\n     */\n    @Immutable\n    public static class LatestFileHash {\n        private final String value;\n        private final int algo;\n\n        public LatestFileHash(String value, int algo) {\n            this.value = value;\n            this.algo = algo;\n        }\n\n        public String getValue() {\n            return value;\n        }\n\n        public int getAlgo() {\n            return algo;\n        }\n    }\n\n    /**\n     * @see <a href=\"https://docs.curseforge.com/#tocS_File\">Schema</a>\n     */\n    @Immutable\n    public static class LatestFile implements RemoteMod.IVersion {\n        private final int id;\n        private final int gameId;\n        private final int modId;\n        private final boolean isAvailable;\n        private final String displayName;\n        private final String fileName;\n        private final int releaseType;\n        private final int fileStatus;\n        private final List<LatestFileHash> hashes;\n        private final Instant fileDate;\n        private final int fileLength;\n        private final int downloadCount;\n        private final String downloadUrl;\n        private final List<String> gameVersions;\n        private final List<Dependency> dependencies;\n        private final int alternateFileId;\n        private final boolean isServerPack;\n        private final long fileFingerprint;\n\n        public LatestFile(int id, int gameId, int modId, boolean isAvailable, String displayName, String fileName, int releaseType, int fileStatus, List<LatestFileHash> hashes, Instant fileDate, int fileLength, int downloadCount, String downloadUrl, List<String> gameVersions, List<Dependency> dependencies, int alternateFileId, boolean isServerPack, long fileFingerprint) {\n            this.id = id;\n            this.gameId = gameId;\n            this.modId = modId;\n            this.isAvailable = isAvailable;\n            this.displayName = displayName;\n            this.fileName = fileName;\n            this.releaseType = releaseType;\n            this.fileStatus = fileStatus;\n            this.hashes = hashes;\n            this.fileDate = fileDate;\n            this.fileLength = fileLength;\n            this.downloadCount = downloadCount;\n            this.downloadUrl = downloadUrl;\n            this.gameVersions = gameVersions;\n            this.dependencies = dependencies;\n            this.alternateFileId = alternateFileId;\n            this.isServerPack = isServerPack;\n            this.fileFingerprint = fileFingerprint;\n        }\n\n        public int getId() {\n            return id;\n        }\n\n        public int getGameId() {\n            return gameId;\n        }\n\n        public int getModId() {\n            return modId;\n        }\n\n        public boolean isAvailable() {\n            return isAvailable;\n        }\n\n        public String getDisplayName() {\n            return displayName;\n        }\n\n        public String getFileName() {\n            return fileName;\n        }\n\n        public int getReleaseType() {\n            return releaseType;\n        }\n\n        public int getFileStatus() {\n            return fileStatus;\n        }\n\n        public List<LatestFileHash> getHashes() {\n            return hashes;\n        }\n\n        public Instant getFileDate() {\n            return fileDate;\n        }\n\n        public int getFileLength() {\n            return fileLength;\n        }\n\n        public int getDownloadCount() {\n            return downloadCount;\n        }\n\n        public String getDownloadUrl() {\n            if (downloadUrl == null) {\n                // This addon is not allowed for distribution, and downloadUrl will be null.\n                // We try to find its download url.\n                return String.format(\"https://edge.forgecdn.net/files/%d/%d/%s\", id / 1000, id % 1000, fileName);\n            }\n            return downloadUrl;\n        }\n\n        public List<String> getGameVersions() {\n            return gameVersions;\n        }\n\n        public List<Dependency> getDependencies() {\n            return dependencies;\n        }\n\n        public int getAlternateFileId() {\n            return alternateFileId;\n        }\n\n        public boolean isServerPack() {\n            return isServerPack;\n        }\n\n        public long getFileFingerprint() {\n            return fileFingerprint;\n        }\n\n        @Override\n        public RemoteMod.Type getType() {\n            return RemoteMod.Type.CURSEFORGE;\n        }\n\n        public RemoteMod.Version toVersion() {\n            RemoteMod.VersionType versionType;\n            switch (getReleaseType()) {\n                case 1:\n                    versionType = RemoteMod.VersionType.Release;\n                    break;\n                case 2:\n                    versionType = RemoteMod.VersionType.Beta;\n                    break;\n                case 3:\n                    versionType = RemoteMod.VersionType.Alpha;\n                    break;\n                default:\n                    versionType = RemoteMod.VersionType.Release;\n                    break;\n            }\n\n            return new RemoteMod.Version(\n                    this,\n                    Integer.toString(modId),\n                    getDisplayName(),\n                    getFileName(),\n                    null,\n                    getFileDate(),\n                    versionType,\n                    new RemoteMod.File(Collections.emptyMap(), getDownloadUrl(), getFileName()),\n                    dependencies.stream().map(dependency -> {\n                        if (!RELATION_TYPE.containsKey(dependency.getRelationType())) {\n                            throw new IllegalStateException(\"Broken datas.\");\n                        }\n                        return RemoteMod.Dependency.ofGeneral(RELATION_TYPE.get(dependency.getRelationType()), CurseForgeRemoteModRepository.MODS, Integer.toString(dependency.getModId()));\n                    }).distinct().filter(Objects::nonNull).collect(Collectors.toList()),\n                    gameVersions.stream().filter(ver -> ver.startsWith(\"1.\") || ver.contains(\"w\")).collect(Collectors.toList()),\n                    gameVersions.stream().flatMap(version -> {\n                        if (\"fabric\".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FABRIC);\n                        else if (\"forge\".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FORGE);\n                        else if (\"quilt\".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.QUILT);\n                        else if (\"neoforge\".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.NEO_FORGED);\n                        else return Stream.empty();\n                    }).collect(Collectors.toList())\n            );\n        }\n    }\n\n    /**\n     * @see <a href=\"https://docs.curseforge.com/#tocS_FileIndex\">Schema</a>\n     */\n    @Immutable\n    public static class LatestFileIndex {\n        private final String gameVersion;\n        private final int fileId;\n        private final String filename;\n        private final int releaseType;\n        private final int gameVersionTypeId;\n        private final int modLoader;\n\n        public LatestFileIndex(String gameVersion, int fileId, String filename, int releaseType, int gameVersionTypeId, int modLoader) {\n            this.gameVersion = gameVersion;\n            this.fileId = fileId;\n            this.filename = filename;\n            this.releaseType = releaseType;\n            this.gameVersionTypeId = gameVersionTypeId;\n            this.modLoader = modLoader;\n        }\n\n        public String getGameVersion() {\n            return gameVersion;\n        }\n\n        public int getFileId() {\n            return fileId;\n        }\n\n        public String getFilename() {\n            return filename;\n        }\n\n        public int getReleaseType() {\n            return releaseType;\n        }\n\n        @Nullable\n        public int getGameVersionTypeId() {\n            return gameVersionTypeId;\n        }\n\n        public int getModLoader() {\n            return modLoader;\n        }\n    }\n\n    @Immutable\n    public static class Category {\n        private final int id;\n        private final int gameId;\n        private final String name;\n        private final String slug;\n        private final String url;\n        private final String iconUrl;\n        private final Instant dateModified;\n        private final boolean isClass;\n        private final int classId;\n        private final int parentCategoryId;\n\n        private transient final List<Category> subcategories;\n\n        public Category() {\n            this(0, 0, \"\", \"\", \"\", \"\", Instant.now(), false, 0, 0);\n        }\n\n        public Category(int id, int gameId, String name, String slug, String url, String iconUrl, Instant dateModified, boolean isClass, int classId, int parentCategoryId) {\n            this.id = id;\n            this.gameId = gameId;\n            this.name = name;\n            this.slug = slug;\n            this.url = url;\n            this.iconUrl = iconUrl;\n            this.dateModified = dateModified;\n            this.isClass = isClass;\n            this.classId = classId;\n            this.parentCategoryId = parentCategoryId;\n\n            this.subcategories = new ArrayList<>();\n        }\n\n        public int getId() {\n            return id;\n        }\n\n        public int getGameId() {\n            return gameId;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public String getSlug() {\n            return slug;\n        }\n\n        public String getUrl() {\n            return url;\n        }\n\n        public String getIconUrl() {\n            return iconUrl;\n        }\n\n        public Instant getDateModified() {\n            return dateModified;\n        }\n\n        public boolean isClass() {\n            return isClass;\n        }\n\n        public int getClassId() {\n            return classId;\n        }\n\n        public int getParentCategoryId() {\n            return parentCategoryId;\n        }\n\n        public List<Category> getSubcategories() {\n            return subcategories;\n        }\n\n        public RemoteModRepository.Category toCategory() {\n            return new RemoteModRepository.Category(\n                    this,\n                    Integer.toString(id),\n                    getSubcategories().stream().map(Category::toCategory).collect(Collectors.toList()));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.mod.ModpackCompletionException;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * Complete the CurseForge version.\n *\n * @author huangyuhui\n */\npublic final class CurseCompletionTask extends Task<Void> {\n\n    private final DefaultDependencyManager dependency;\n    private final DefaultGameRepository repository;\n    private final ModManager modManager;\n    private final String version;\n    private CurseManifest manifest;\n    private List<Task<?>> dependencies;\n\n    private final AtomicBoolean allNameKnown = new AtomicBoolean(true);\n    private final AtomicInteger finished = new AtomicInteger(0);\n    private final AtomicBoolean notFound = new AtomicBoolean(false);\n\n    /**\n     * Constructor.\n     *\n     * @param dependencyManager the dependency manager.\n     * @param version           the existent and physical version.\n     */\n    public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        this(dependencyManager, version, null);\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param dependencyManager the dependency manager.\n     * @param version           the existent and physical version.\n     * @param manifest          the CurseForgeModpack manifest.\n     */\n    public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version, CurseManifest manifest) {\n        this.dependency = dependencyManager;\n        this.repository = dependencyManager.getGameRepository();\n        this.modManager = repository.getModManager(version);\n        this.version = version;\n        this.manifest = manifest;\n\n        if (manifest == null)\n            try {\n                Path manifestFile = repository.getVersionRoot(version).resolve(\"manifest.json\");\n                if (Files.exists(manifestFile))\n                    this.manifest = JsonUtils.fromJsonFile(manifestFile, CurseManifest.class);\n            } catch (Exception e) {\n                LOG.warning(\"Unable to read CurseForge modpack manifest.json\", e);\n            }\n\n        setStage(\"hmcl.modpack.download\");\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        if (manifest == null)\n            return;\n\n        Path root = repository.getVersionRoot(version);\n\n        // Because in China, Curse is too difficult to visit,\n        // if failed, ignore it and retry next time.\n        CurseManifest newManifest = manifest.setFiles(\n                manifest.files().parallelStream()\n                        .map(file -> {\n                            updateProgress(finished.incrementAndGet(), manifest.files().size());\n                            if (StringUtils.isBlank(file.fileName()) || file.url() == null) {\n                                try {\n                                    RemoteMod.File remoteFile = CurseForgeRemoteModRepository.MODS.getModFile(Integer.toString(file.projectID()), Integer.toString(file.fileID()));\n                                    return file.withFileName(remoteFile.getFilename()).withURL(remoteFile.getUrl());\n                                } catch (FileNotFoundException fof) {\n                                    LOG.warning(\"Could not query api.curseforge.com for deleted mods: \" + file.projectID() + \", \" + file.fileID(), fof);\n                                    notFound.set(true);\n                                    return file;\n                                } catch (IOException | JsonParseException e) {\n                                    LOG.warning(\"Unable to fetch the file name projectID=\" + file.projectID() + \", fileID=\" + file.fileID(), e);\n                                    allNameKnown.set(false);\n                                    return file;\n                                }\n                            } else {\n                                return file;\n                            }\n                        })\n                        .collect(Collectors.toList()));\n        JsonUtils.writeToJsonFile(root.resolve(\"manifest.json\"), newManifest);\n\n        Path versionRoot = repository.getVersionRoot(modManager.getInstanceId());\n        Path resourcePacksRoot = versionRoot.resolve(\"resourcepacks\");\n        Path shaderPacksRoot = versionRoot.resolve(\"shaderpacks\");\n        finished.set(0);\n        dependencies = newManifest.files()\n                .stream().parallel()\n                .filter(f -> f.fileName() != null)\n                .flatMap(f -> {\n                    try {\n                        Path path = guessFilePath(f, dependency.getDownloadProvider(), resourcePacksRoot, shaderPacksRoot);\n                        if (path == null) {\n                            return Stream.empty();\n                        }\n\n                        var task = new FileDownloadTask(f.url(), path);\n                        task.setCacheRepository(dependency.getCacheRepository());\n                        task.setCaching(true);\n                        return Stream.of(task.withCounter(\"hmcl.modpack.download\"));\n                    } catch (IOException e) {\n                        LOG.warning(\"Could not query api.curseforge.com for mod: \" + f.projectID() + \", \" + f.fileID(), e);\n                        return Stream.empty(); // Ignore this file.\n                    } finally {\n                        updateProgress(finished.incrementAndGet(), newManifest.files().size());\n                    }\n                })\n                .collect(Collectors.toList());\n\n        if (!dependencies.isEmpty()) {\n            getProperties().put(\"total\", dependencies.size());\n            notifyPropertiesChanged();\n        }\n    }\n\n    /**\n     * Guess where to store the file.\n     *\n     * @param file              The file.\n     * @param downloadProvider\n     * @param resourcePacksRoot ./resourcepacks.\n     * @param shaderPacksRoot   ./shaderpacks.\n     * @return ./resourcepacks/$filename or ./shaderpacks/$filename or ./mods/$filename if the file doesn't exist. null if the file existed.\n     * @throws IOException If IOException was encountered during getting data from CurseForge.\n     */\n    private Path guessFilePath(CurseManifestFile file, DownloadProvider downloadProvider, Path resourcePacksRoot, Path shaderPacksRoot) throws IOException {\n        RemoteMod mod = CurseForgeRemoteModRepository.MODS.getModById(downloadProvider, Integer.toString(file.projectID()));\n        int classID = ((CurseAddon) mod.getData()).getClassId();\n        String fileName = file.fileName();\n        return switch (classID) {\n            case 12,       // Resource pack\n                 6552 -> { // Shader pack\n                Path res = (classID == 12 ? resourcePacksRoot : shaderPacksRoot).resolve(fileName);\n                yield Files.exists(res) ? null : res;\n            }\n            default -> {\n                if (modManager.hasSimpleMod(fileName)) {\n                    yield null;\n                }\n                yield modManager.getSimpleModPath(fileName);\n            }\n        };\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        // Let this task fail if the curse manifest has not been completed.\n        // But continue other downloads.\n        if (notFound.get())\n            throw new ModpackCompletionException(new FileNotFoundException());\n        if (!allNameKnown.get() || !isDependenciesSucceeded())\n            throw new ModpackCompletionException();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.RemoteModRepository;\nimport org.jackhuang.hmcl.util.MurmurHash2;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.io.HttpRequest;\nimport org.jackhuang.hmcl.util.io.JarUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.Semaphore;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class CurseForgeRemoteModRepository implements RemoteModRepository {\n\n    private static final String PREFIX = \"https://api.curseforge.com\";\n    private static final String apiKey = System.getProperty(\"hmcl.curseforge.apikey\", JarUtils.getAttribute(\"hmcl.curseforge.apikey\", \"\"));\n    private static final Semaphore SEMAPHORE = new Semaphore(16);\n\n    private static final int WORD_PERFECT_MATCH_WEIGHT = 5;\n\n    private static <R extends HttpRequest> R withApiKey(R request) {\n        if (request.getUrl().startsWith(PREFIX) && !apiKey.isEmpty()) {\n            request.header(\"X-API-KEY\", apiKey);\n        }\n        return request;\n    }\n\n    public static boolean isAvailable() {\n        return !apiKey.isEmpty();\n    }\n\n    private final Type type;\n    private final int section;\n\n    public CurseForgeRemoteModRepository(Type type, int section) {\n        this.type = type;\n        this.section = section;\n    }\n\n    @Override\n    public Type getType() {\n        return type;\n    }\n\n    private int toModsSearchSortField(SortType sort) {\n        // https://docs.curseforge.com/#tocS_ModsSearchSortField\n        switch (sort) {\n            case DATE_CREATED:\n                return 1;\n            case POPULARITY:\n                return 2;\n            case LAST_UPDATED:\n                return 3;\n            case NAME:\n                return 4;\n            case AUTHOR:\n                return 5;\n            case TOTAL_DOWNLOADS:\n                return 6;\n            default:\n                return 8;\n        }\n    }\n\n    private String toSortOrder(SortOrder sortOrder) {\n        // https://docs.curseforge.com/#tocS_SortOrder\n        switch (sortOrder) {\n            case ASC:\n                return \"asc\";\n            case DESC:\n                return \"desc\";\n        }\n        return \"asc\";\n    }\n\n    private int calculateTotalPages(Response<List<CurseAddon>> response, int pageSize) {\n        return (int) Math.ceil((double) Math.min(response.pagination.totalCount, 10000) / pageSize);\n    }\n\n    @Override\n    public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            int categoryId = 0;\n            if (category != null && category.getSelf() instanceof CurseAddon.Category) {\n                categoryId = ((CurseAddon.Category) category.getSelf()).getId();\n            }\n\n            var query = new LinkedHashMap<String, String>();\n            query.put(\"gameId\", \"432\");\n            query.put(\"classId\", Integer.toString(section));\n            if (categoryId != 0)\n                query.put(\"categoryId\", Integer.toString(categoryId));\n            query.put(\"gameVersion\", gameVersion);\n            query.put(\"searchFilter\", searchFilter);\n            query.put(\"sortField\", Integer.toString(toModsSearchSortField(sortType)));\n            query.put(\"sortOrder\", toSortOrder(sortOrder));\n            query.put(\"index\", Integer.toString(pageOffset * pageSize));\n            query.put(\"pageSize\", Integer.toString(pageSize));\n\n            Response<List<CurseAddon>> response = null;\n\n            IOException exception = null;\n            List<URI> candidates = downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(PREFIX + \"/v1/mods/search\", query));\n            for (URI candidate : candidates) {\n                LOG.info(\"Fetching \" + candidate);\n                try {\n                    response = withApiKey(HttpRequest.GET(candidate.toString()))\n                            .getJson(Response.typeOf(listTypeOf(CurseAddon.class)));\n                    if (searchFilter.isEmpty()) {\n                        return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));\n                    }\n                    break;\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to search addons: \" + candidate, e);\n                    if (candidates.size() == 1) {\n                        exception = e;\n                    } else {\n                        if (exception == null) {\n                            exception = new IOException(\"Failed to search addons\");\n                        }\n                        exception.addSuppressed(e);\n                    }\n                }\n            }\n\n            if (response == null) {\n                throw exception != null ? exception : new IOException(\"No candidates found\");\n            }\n\n            // https://github.com/HMCL-dev/HMCL/issues/1549\n            String lowerCaseSearchFilter = searchFilter.toLowerCase(Locale.ROOT);\n            Map<String, Integer> searchFilterWords = new HashMap<>();\n            for (String s : StringUtils.tokenize(lowerCaseSearchFilter)) {\n                searchFilterWords.put(s, searchFilterWords.getOrDefault(s, 0) + 1);\n            }\n\n            StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();\n\n            return new SearchResult(response.getData().stream().map(CurseAddon::toMod).map(remoteMod -> {\n                String lowerCaseResult = remoteMod.getTitle().toLowerCase(Locale.ROOT);\n                int diff = levCalculator.calc(lowerCaseSearchFilter, lowerCaseResult);\n\n                for (String s : StringUtils.tokenize(lowerCaseResult)) {\n                    if (searchFilterWords.containsKey(s)) {\n                        diff -= WORD_PERFECT_MATCH_WEIGHT * searchFilterWords.get(s) * s.length();\n                    }\n                }\n\n                return pair(remoteMod, diff);\n            }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    @Override\n    public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        try (InputStream stream = Files.newInputStream(file)) {\n            byte[] buf = new byte[1024];\n            int len;\n            while ((len = stream.read(buf, 0, buf.length)) != -1) {\n                for (int i = 0; i < len; i++) {\n                    byte b = buf[i];\n                    if (b != 0x9 && b != 0xa && b != 0xd && b != 0x20) {\n                        baos.write(b);\n                    }\n                }\n            }\n        }\n\n        long hash = Integer.toUnsignedLong(MurmurHash2.hash32(baos.toByteArray(), baos.size(), 1));\n        if (hash == 811513880) { // Workaround for https://github.com/HMCL-dev/HMCL/issues/4597\n            return Optional.empty();\n        }\n\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            Response<FingerprintMatchesResult> response = withApiKey(HttpRequest.POST(PREFIX + \"/v1/fingerprints/432\"))\n                    .json(mapOf(pair(\"fingerprints\", Collections.singletonList(hash))))\n                    .getJson(Response.typeOf(FingerprintMatchesResult.class));\n\n            if (response.getData().getExactMatches() == null || response.getData().getExactMatches().isEmpty()) {\n                return Optional.empty();\n            }\n\n            return Optional.of(response.getData().getExactMatches().get(0).getFile().toVersion());\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    @Override\n    public RemoteMod getModById(DownloadProvider downloadProvider, String id) throws IOException {\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            Response<CurseAddon> response = withApiKey(HttpRequest.GET(PREFIX + \"/v1/mods/\" + id))\n                    .getJson(Response.typeOf(CurseAddon.class));\n            return response.data.toMod();\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    @Override\n    public RemoteMod.File getModFile(String modId, String fileId) throws IOException {\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            Response<CurseAddon.LatestFile> response = withApiKey(HttpRequest.GET(String.format(\"%s/v1/mods/%s/files/%s\", PREFIX, modId, fileId)))\n                    .getJson(Response.typeOf(CurseAddon.LatestFile.class));\n            return response.getData().toVersion().getFile();\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    @Override\n    public Stream<RemoteMod.Version> getRemoteVersionsById(DownloadProvider downloadProvider, String id) throws IOException {\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            Response<List<CurseAddon.LatestFile>> response = withApiKey(HttpRequest.GET(PREFIX + \"/v1/mods/\" + id + \"/files\",\n                    pair(\"pageSize\", \"10000\")))\n                    .getJson(Response.typeOf(listTypeOf(CurseAddon.LatestFile.class)));\n            return response.getData().stream().map(CurseAddon.LatestFile::toVersion);\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    @Override\n    public Stream<RemoteModRepository.Category> getCategories() throws IOException {\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            Response<List<CurseAddon.Category>> categories = withApiKey(HttpRequest.GET(PREFIX + \"/v1/categories\", pair(\"gameId\", \"432\")))\n                    .getJson(Response.typeOf(listTypeOf(CurseAddon.Category.class)));\n            return reorganizeCategories(categories.getData(), section).stream().map(CurseAddon.Category::toCategory);\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    private List<CurseAddon.Category> reorganizeCategories(List<CurseAddon.Category> categories, int rootId) {\n        List<CurseAddon.Category> result = new ArrayList<>();\n\n        Map<Integer, CurseAddon.Category> categoryMap = new HashMap<>();\n        for (CurseAddon.Category category : categories) {\n            categoryMap.put(category.getId(), category);\n        }\n        for (CurseAddon.Category category : categories) {\n            if (category.getParentCategoryId() == rootId) {\n                result.add(category);\n            } else {\n                CurseAddon.Category parentCategory = categoryMap.get(category.getParentCategoryId());\n                if (parentCategory == null) {\n                    // Category list is not correct, so we ignore this item.\n                    continue;\n                }\n                parentCategory.getSubcategories().add(category);\n            }\n        }\n        return result;\n    }\n\n    public static final int SECTION_BUKKIT_PLUGIN = 5;\n    public static final int SECTION_MOD = 6;\n    public static final int SECTION_RESOURCE_PACK = 12;\n    public static final int SECTION_WORLD = 17;\n    public static final int SECTION_MODPACK = 4471;\n    public static final int SECTION_SHADER = 6552;\n    public static final int SECTION_CUSTOMIZATION = 4546;\n    public static final int SECTION_ADDONS = 4559; // For Pocket Edition\n    public static final int SECTION_UNKNOWN1 = 4944;\n    public static final int SECTION_UNKNOWN2 = 4979;\n    public static final int SECTION_UNKNOWN3 = 4984;\n\n    public static final CurseForgeRemoteModRepository MODS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.MOD, SECTION_MOD);\n    public static final CurseForgeRemoteModRepository MODPACKS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.MODPACK, SECTION_MODPACK);\n    public static final CurseForgeRemoteModRepository RESOURCE_PACKS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.RESOURCE_PACK, SECTION_RESOURCE_PACK);\n    public static final CurseForgeRemoteModRepository WORLDS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.WORLD, SECTION_WORLD);\n    public static final CurseForgeRemoteModRepository CUSTOMIZATIONS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.CUSTOMIZATION, SECTION_CUSTOMIZATION);\n    public static final CurseForgeRemoteModRepository SHADERS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.SHADER_PACK, SECTION_SHADER);\n\n    public static class Pagination {\n        private final int index;\n        private final int pageSize;\n        private final int resultCount;\n        private final int totalCount;\n\n        public Pagination(int index, int pageSize, int resultCount, int totalCount) {\n            this.index = index;\n            this.pageSize = pageSize;\n            this.resultCount = resultCount;\n            this.totalCount = totalCount;\n        }\n\n        public int getIndex() {\n            return index;\n        }\n\n        public int getPageSize() {\n            return pageSize;\n        }\n\n        public int getResultCount() {\n            return resultCount;\n        }\n\n        public int getTotalCount() {\n            return totalCount;\n        }\n    }\n\n    public static class Response<T> {\n\n        @SuppressWarnings(\"unchecked\")\n        public static <T> TypeToken<Response<T>> typeOf(Class<T> responseType) {\n            return (TypeToken<Response<T>>) TypeToken.getParameterized(Response.class, responseType);\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        public static <T> TypeToken<Response<T>> typeOf(TypeToken<T> responseType) {\n            return (TypeToken<Response<T>>) TypeToken.getParameterized(Response.class, responseType.getType());\n        }\n\n        private final T data;\n        private final Pagination pagination;\n\n        public Response(T data, Pagination pagination) {\n            this.data = data;\n            this.pagination = pagination;\n        }\n\n        public T getData() {\n            return data;\n        }\n\n        public Pagination getPagination() {\n            return pagination;\n        }\n    }\n\n    /**\n     * @see <a href=\"https://docs.curseforge.com/#tocS_FingerprintsMatchesResult\">Schema</a>\n     */\n    private static class FingerprintMatchesResult {\n        private final boolean isCacheBuilt;\n        private final List<FingerprintMatch> exactMatches;\n        private final List<Long> exactFingerprints;\n\n        public FingerprintMatchesResult(boolean isCacheBuilt, List<FingerprintMatch> exactMatches, List<Long> exactFingerprints) {\n            this.isCacheBuilt = isCacheBuilt;\n            this.exactMatches = exactMatches;\n            this.exactFingerprints = exactFingerprints;\n        }\n\n        public boolean isCacheBuilt() {\n            return isCacheBuilt;\n        }\n\n        public List<FingerprintMatch> getExactMatches() {\n            return exactMatches;\n        }\n\n        public List<Long> getExactFingerprints() {\n            return exactFingerprints;\n        }\n    }\n\n    /**\n     * @see <a href=\"https://docs.curseforge.com/#tocS_FingerprintMatch\">Schema</a>\n     */\n    private static class FingerprintMatch {\n        private final int id;\n        private final CurseAddon.LatestFile file;\n        private final List<CurseAddon.LatestFile> latestFiles;\n\n        public FingerprintMatch(int id, CurseAddon.LatestFile file, List<CurseAddon.LatestFile> latestFiles) {\n            this.id = id;\n            this.file = file;\n            this.latestFiles = latestFiles;\n        }\n\n        public int getId() {\n            return id;\n        }\n\n        public CurseAddon.LatestFile getFile() {\n            return file;\n        }\n\n        public List<CurseAddon.LatestFile> getLatestFiles() {\n            return latestFiles;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.GameBuilder;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.*;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Install a downloaded CurseForge modpack.\n *\n * @author huangyuhui\n */\npublic final class CurseInstallTask extends Task<Void> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final DefaultGameRepository repository;\n    private final Path zipFile;\n    private final Modpack modpack;\n    private final CurseManifest manifest;\n    private final String name;\n    private final Path run;\n    private final ModpackConfiguration<CurseManifest> config;\n    private final List<Task<?>> dependents = new ArrayList<>(4);\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    /**\n     * Constructor.\n     *\n     * @param dependencyManager the dependency manager.\n     * @param zipFile           the CurseForge modpack file.\n     * @param manifest          The manifest content of given CurseForge modpack.\n     * @param name              the new version name\n     */\n    public CurseInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, Modpack modpack, CurseManifest manifest, String name) {\n        this.dependencyManager = dependencyManager;\n        this.zipFile = zipFile;\n        this.modpack = modpack;\n        this.manifest = manifest;\n        this.name = name;\n        this.repository = dependencyManager.getGameRepository();\n        this.run = repository.getRunDirectory(name);\n\n        Path json = repository.getModpackConfiguration(name);\n        if (repository.hasVersion(name) && Files.notExists(json))\n            throw new IllegalArgumentException(\"Version \" + name + \" already exists.\");\n\n        GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.minecraft().gameVersion());\n        for (CurseManifestModLoader modLoader : manifest.minecraft().modLoaders()) {\n            if (modLoader.id().startsWith(\"forge-\")) {\n                builder.version(\"forge\", modLoader.id().substring(\"forge-\".length()));\n            } else if (modLoader.id().startsWith(\"fabric-\")) {\n                builder.version(\"fabric\", modLoader.id().substring(\"fabric-\".length()));\n            } else if (modLoader.id().startsWith(\"neoforge-\")) {\n                builder.version(\"neoforge\", modLoader.id().substring(\"neoforge-\".length()));\n            }\n        }\n        dependents.add(builder.buildAsync());\n\n        onDone().register(event -> {\n            Exception ex = event.getTask().getException();\n            if (event.isFailed()) {\n                if (!(ex instanceof ModpackCompletionException)) {\n                    repository.removeVersionFromDisk(name);\n                }\n            }\n        });\n\n        ModpackConfiguration<CurseManifest> config = null;\n        try {\n            if (Files.exists(json)) {\n                config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(CurseManifest.class));\n\n                if (!CurseModpackProvider.INSTANCE.getName().equals(config.getType()))\n                    throw new IllegalArgumentException(\"Version \" + name + \" is not a Curse modpack. Cannot update this version.\");\n            }\n        } catch (JsonParseException | IOException ignore) {\n        }\n        this.config = config;\n        dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(manifest.overrides()), any -> true, config).withStage(\"hmcl.modpack\"));\n        dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(manifest.overrides()), manifest, CurseModpackProvider.INSTANCE, manifest.name(), manifest.version(), repository.getModpackConfiguration(name)).withStage(\"hmcl.modpack\"));\n\n        dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest));\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        if (config != null) {\n            // For update, remove mods not listed in new manifest\n            for (CurseManifestFile oldCurseManifestFile : config.getManifest().files()) {\n                if (StringUtils.isBlank(oldCurseManifestFile.fileName())) continue;\n                Path oldFile = run.resolve(\"mods/\" + oldCurseManifestFile.fileName());\n                if (Files.notExists(oldFile)) continue;\n                if (manifest.files().stream().noneMatch(oldCurseManifestFile::equals))\n                    Files.deleteIfExists(oldFile);\n            }\n        }\n\n        Path root = repository.getVersionRoot(name);\n        Files.createDirectories(root);\n        JsonUtils.writeToJsonFile(root.resolve(\"manifest.json\"), manifest);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.mod.ModpackManifest;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.List;\n\n/// @author huangyuhui\n@JsonSerializable\npublic record CurseManifest(@SerializedName(\"manifestType\") String manifestType,\n                            @SerializedName(\"manifestVersion\") int manifestVersion,\n                            @SerializedName(\"name\") String name,\n                            @SerializedName(\"version\") String version,\n                            @SerializedName(\"author\") String author,\n                            @SerializedName(\"overrides\") String overrides,\n                            @SerializedName(\"minecraft\") CurseManifestMinecraft minecraft,\n                            @SerializedName(\"files\") @Unmodifiable List<CurseManifestFile> files) implements ModpackManifest {\n\n    public CurseManifest setFiles(List<CurseManifestFile> files) {\n        return new CurseManifest(manifestType, manifestVersion, name, version, author, overrides, minecraft, files);\n    }\n\n    @Override\n    public ModpackProvider getProvider() {\n        return CurseModpackProvider.INSTANCE;\n    }\n\n    public static final String MINECRAFT_MODPACK = \"minecraftModpack\";\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifestFile.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Objects;\n\n/// @author huangyuhui\n@JsonSerializable\npublic record CurseManifestFile(@SerializedName(\"projectID\") int projectID,\n                                @SerializedName(\"fileID\") int fileID,\n                                @SerializedName(\"fileName\") String fileName,\n                                @SerializedName(\"url\") String url,\n                                @SerializedName(\"required\") boolean required) implements Validation {\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (projectID == 0 || fileID == 0)\n            throw new JsonParseException(\"Missing Project ID or File ID.\");\n    }\n\n    @Override\n    @Nullable\n    public String url() {\n        if (url == null) {\n            return fileName != null\n                    ? String.format(\"https://edge.forgecdn.net/files/%d/%d/%s\", fileID / 1000, fileID % 1000, fileName)\n                    : null;\n        } else {\n            return url;\n        }\n    }\n\n    public CurseManifestFile withFileName(String fileName) {\n        return new CurseManifestFile(projectID, fileID, fileName, url, required);\n    }\n\n    public CurseManifestFile withURL(String url) {\n        return new CurseManifestFile(projectID, fileID, fileName, url, required);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        return this == o || o instanceof CurseManifestFile that\n                && this.projectID == that.projectID\n                && this.fileID == that.fileID;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(projectID, fileID);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifestMinecraft.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/// @author huangyuhui\n@JsonSerializable\npublic record CurseManifestMinecraft(@SerializedName(\"version\") String gameVersion,\n                                     @SerializedName(\"modLoaders\") @Unmodifiable List<CurseManifestModLoader> modLoaders) implements Validation {\n\n    public CurseManifestMinecraft(String gameVersion, List<CurseManifestModLoader> modLoaders) {\n        this.gameVersion = gameVersion;\n        this.modLoaders = Collections.unmodifiableList(new ArrayList<>(modLoaders)); // TODO: Is the modLoaders nullable?\n    }\n\n    @Override\n    public List<CurseManifestModLoader> modLoaders() {\n        return Collections.unmodifiableList(modLoaders);\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (StringUtils.isBlank(gameVersion))\n            throw new JsonParseException(\"CurseForge Manifest.gameVersion cannot be blank.\");\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifestModLoader.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic record CurseManifestModLoader(@SerializedName(\"id\") String id,\n                                     @SerializedName(\"primary\") boolean primary) implements Validation {\n    public CurseManifestModLoader() {\n        this(\"\", false);\n    }\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (StringUtils.isBlank(id))\n            throw new JsonParseException(\"Curse Forge modpack manifest Mod loader id cannot be blank.\");\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseMetaMod.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.Immutable;\n\n/**\n * CurseMetaMod is JSON structure for\n * https://cursemeta.dries007.net/&lt;projectID&gt;/&lt;fileID&gt;.json\n * https://addons-ecs.forgesvc.net/api/v2/addon/&lt;projectID&gt;/file/&lt;fileID&gt;\n */\n@Immutable\npublic final class CurseMetaMod {\n    @SerializedName(value = \"Id\", alternate = \"id\")\n    private final int id;\n\n    @SerializedName(value = \"FileName\", alternate = \"fileName\")\n    private final String fileName;\n\n    @SerializedName(value = \"FileNameOnDisk\")\n    private final String fileNameOnDisk;\n\n    @SerializedName(value = \"DownloadURL\", alternate = \"downloadUrl\")\n    private final String downloadURL;\n\n    public CurseMetaMod() {\n        this(0, \"\", \"\", \"\");\n    }\n\n    public CurseMetaMod(int id, String fileName, String fileNameOnDisk, String downloadURL) {\n        this.id = id;\n        this.fileName = fileName;\n        this.fileNameOnDisk = fileNameOnDisk;\n        this.downloadURL = downloadURL;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public String getFileName() {\n        return fileName;\n    }\n\n    public String getFileNameOnDisk() {\n        return fileNameOnDisk;\n    }\n\n    public String getDownloadURL() {\n        return downloadURL;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseModpackProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport com.google.gson.JsonParseException;\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.mod.MismatchedModpackTypeException;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.mod.ModpackUpdateTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.IOUtils;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\n\npublic final class CurseModpackProvider implements ModpackProvider {\n    public static final CurseModpackProvider INSTANCE = new CurseModpackProvider();\n\n    @Override\n    public String getName() {\n        return \"Curse\";\n    }\n\n    @Override\n    public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        return new CurseCompletionTask(dependencyManager, version);\n    }\n\n    @Override\n    public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException {\n        if (!(modpack.getManifest() instanceof CurseManifest curseManifest))\n            throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());\n\n        return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new CurseInstallTask(dependencyManager, zipFile, modpack, curseManifest, name));\n    }\n\n    @Override\n    public Modpack readManifest(ZipArchiveReader zip, Path file, Charset encoding) throws IOException, JsonParseException {\n        CurseManifest manifest = JsonUtils.fromNonNullJson(CompressingUtils.readTextZipEntry(zip, \"manifest.json\"), CurseManifest.class);\n        String description = \"No description\";\n        try {\n            ZipArchiveEntry modlist = zip.getEntry(\"modlist.html\");\n            if (modlist != null)\n                description = IOUtils.readFullyAsString(zip.getInputStream(modlist));\n        } catch (Throwable ignored) {\n        }\n\n        return new Modpack(manifest.name(), manifest.author(), manifest.version(), manifest.minecraft().gameVersion(), description, encoding, manifest) {\n            @Override\n            public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {\n                return new CurseInstallTask(dependencyManager, zipFile, this, manifest, name);\n            }\n        };\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.mcbbs;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.mod.ModpackCompletionException;\nimport org.jackhuang.hmcl.mod.curse.CurseMetaMod;\nimport org.jackhuang.hmcl.task.*;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.Lang.wrap;\nimport static org.jackhuang.hmcl.util.Lang.wrapConsumer;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {\n\n    private final DefaultDependencyManager dependency;\n    private final DefaultGameRepository repository;\n    private final ModManager modManager;\n    private final String version;\n    private final Path configurationFile;\n    private ModpackConfiguration<McbbsModpackManifest> configuration;\n    private McbbsModpackManifest manifest;\n    private final List<Task<?>> dependencies = new ArrayList<>();\n\n    private final AtomicBoolean allNameKnown = new AtomicBoolean(true);\n    private final AtomicInteger finished = new AtomicInteger(0);\n    private final AtomicBoolean notFound = new AtomicBoolean(false);\n\n    public McbbsModpackCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        this(dependencyManager, version, null);\n    }\n\n    public McbbsModpackCompletionTask(DefaultDependencyManager dependencyManager, String version, ModpackConfiguration<McbbsModpackManifest> configuration) {\n        this.dependency = dependencyManager;\n        this.repository = dependencyManager.getGameRepository();\n        this.modManager = repository.getModManager(version);\n        this.version = version;\n        this.configurationFile = repository.getModpackConfiguration(version);\n        this.configuration = configuration;\n\n        setStage(\"hmcl.modpack.download\");\n    }\n\n    @Override\n    public CompletableFuture<Void> getFuture(TaskCompletableFuture executor) {\n        return breakable(CompletableFuture.runAsync(wrap(() -> {\n            if (configuration == null) {\n                // Load configuration from disk\n                try {\n                    configuration = JsonUtils.fromNonNullJson(Files.readString(configurationFile), ModpackConfiguration.typeOf(McbbsModpackManifest.class));\n                } catch (IOException | JsonParseException e) {\n                    throw new IOException(\"Malformed modpack configuration\");\n                }\n            }\n            manifest = configuration.getManifest();\n            if (manifest == null) throw new CustomException();\n        })).thenComposeAsync(unused -> {\n            // we first download latest manifest\n            return breakable(CompletableFuture.runAsync(wrap(() -> {\n                if (StringUtils.isBlank(manifest.getFileApi())) {\n                    // skip this phase\n                    throw new CustomException();\n                }\n            })).thenComposeAsync(wrap(unused1 -> {\n                return executor.one(new GetTask(manifest.getFileApi() + \"/manifest.json\"));\n            })).thenComposeAsync(wrap(remoteManifestJson -> {\n                McbbsModpackManifest remoteManifest;\n                // We needs to update modpack from online server.\n                try {\n                    remoteManifest = JsonUtils.fromNonNullJson(remoteManifestJson, McbbsModpackManifest.class);\n                } catch (JsonParseException e) {\n                    throw new IOException(\"Unable to parse server manifest.json from \" + manifest.getFileApi(), e);\n                }\n\n                Path rootPath = repository.getVersionRoot(version);\n                Files.createDirectories(rootPath);\n\n                Map<McbbsModpackManifest.File, McbbsModpackManifest.File> localFiles = manifest.getFiles().stream().collect(Collectors.toMap(Function.identity(), Function.identity()));\n\n                // for files in new modpack\n                List<McbbsModpackManifest.File> newFiles = new ArrayList<>(remoteManifest.getFiles().size());\n                List<Task<?>> tasks = new ArrayList<>();\n                for (McbbsModpackManifest.File file : remoteManifest.getFiles()) {\n                    Path actualPath = getFilePath(file);\n                    McbbsModpackManifest.File oldFile = localFiles.remove(file);\n                    boolean download = false;\n                    if (oldFile == null) {\n                        // If old modpack does not have this entry, download it\n                        download = true;\n                    } else if (actualPath != null) {\n                        if (!Files.exists(actualPath)) {\n                            // If both old and new modpacks have this entry, but the file is missing...\n                            // Re-download it since network problem may cause file missing\n                            download = true;\n                        } else if (getFileHash(file) != null) {\n                            // If user modified this entry file, we will not replace this file since this modified file is what user expects.\n                            // Or we have downloaded latest file in previous completion task, this time we have no need to download it again.\n                            String fileHash = DigestUtils.digestToString(\"SHA-1\", actualPath);\n                            String oldHash = getFileHash(oldFile);\n                            String newHash = getFileHash(file);\n                            if (oldHash == null) {\n                                // We don't know whether the file is modified or not, just update it.\n                                download = true;\n                            } else if (!Objects.equals(fileHash, newHash)) {\n                                if (file.isForce()) {\n                                    // this file is not allowed to be modified, required by modpack author.\n                                    download = true;\n                                } else if (Objects.equals(oldHash, fileHash)) {\n                                    download = true;\n                                }\n                            }\n                        }\n                    } else {\n                        // we resolve files with unknown path later.\n                    }\n\n                    if (download) {\n                        tasks.add(downloadFile(remoteManifest, file));\n                    }\n\n                    newFiles.add(mergeFile(oldFile, file));\n                }\n\n                // If old modpack have this entry, and new modpack deleted it. Delete this file.\n                // for-loop above removes still existing file in localFiles. Remaining elements\n                // are files removed by next modpack version.\n                // Notice that this loop will also remove Curse mods.\n                for (McbbsModpackManifest.File file : localFiles.keySet()) {\n                    Path actualPath = getFilePath(file);\n                    if (actualPath != null && Files.exists(actualPath))\n                        Files.deleteIfExists(actualPath);\n                }\n\n                manifest = remoteManifest.setFiles(newFiles);\n                return executor.all(tasks.stream().filter(Objects::nonNull).collect(Collectors.toList()));\n            })).thenAcceptAsync(wrapConsumer(unused1 -> {\n                Path manifestFile = repository.getModpackConfiguration(version);\n                JsonUtils.writeToJsonFile(manifestFile,\n                        new ModpackConfiguration<>(manifest, this.configuration.getType(), this.manifest.getName(), this.manifest.getVersion(),\n                                this.manifest.getFiles().stream()\n                                        .flatMap(file -> file instanceof McbbsModpackManifest.AddonFile\n                                                ? Stream.of((McbbsModpackManifest.AddonFile) file)\n                                                : Stream.empty())\n                                        .map(file -> new ModpackConfiguration.FileInformation(file.getPath(), file.getHash()))\n                                        .collect(Collectors.toList())));\n            })));\n        }).thenComposeAsync(unused -> {\n            AtomicBoolean allNameKnown = new AtomicBoolean(true);\n            AtomicInteger finished = new AtomicInteger(0);\n            AtomicBoolean notFound = new AtomicBoolean(false);\n\n            return breakable(CompletableFuture.completedFuture(null)\n                    .thenComposeAsync(wrap(unused1 -> {\n                        List<Task<?>> dependencies = new ArrayList<>();\n                        // Because in China, Curse is too difficult to visit,\n                        // if failed, ignore it and retry next time.\n                        McbbsModpackManifest newManifest = manifest.setFiles(\n                                manifest.getFiles().parallelStream()\n                                        .map(rawFile -> {\n                                            updateProgress(finished.incrementAndGet(), manifest.getFiles().size());\n                                            if (rawFile instanceof McbbsModpackManifest.CurseFile) {\n                                                McbbsModpackManifest.CurseFile file = (McbbsModpackManifest.CurseFile) rawFile;\n                                                if (StringUtils.isBlank(file.getFileName())) {\n                                                    try {\n                                                        return file.withFileName(NetworkUtils.detectFileName(NetworkUtils.toURI(file.getUrl())));\n                                                    } catch (IOException e) {\n                                                        try {\n                                                            String result = NetworkUtils.doGet(String.format(\"https://cursemeta.dries007.net/%d/%d.json\", file.getProjectID(), file.getFileID()));\n                                                            CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);\n                                                            return file.withFileName(mod.getFileNameOnDisk()).withURL(mod.getDownloadURL());\n                                                        } catch (FileNotFoundException fof) {\n                                                            LOG.warning(\"Could not query cursemeta for deleted mods: \" + file.getUrl(), fof);\n                                                            notFound.set(true);\n                                                            return file;\n                                                        } catch (IOException | JsonParseException e2) {\n                                                            try {\n                                                                String result = NetworkUtils.doGet(String.format(\"https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d\", file.getProjectID(), file.getFileID()));\n                                                                CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);\n                                                                return file.withFileName(mod.getFileName()).withURL(mod.getDownloadURL());\n                                                            } catch (FileNotFoundException fof) {\n                                                                LOG.warning(\"Could not query forgesvc for deleted mods: \" + file.getUrl(), fof);\n                                                                notFound.set(true);\n                                                                return file;\n                                                            } catch (IOException | JsonParseException e3) {\n                                                                LOG.warning(\"Unable to fetch the file name of URL: \" + file.getUrl(), e);\n                                                                LOG.warning(\"Unable to fetch the file name of URL: \" + file.getUrl(), e2);\n                                                                LOG.warning(\"Unable to fetch the file name of URL: \" + file.getUrl(), e3);\n                                                                allNameKnown.set(false);\n                                                                return file;\n                                                            }\n                                                        }\n                                                    }\n                                                } else {\n                                                    return file;\n                                                }\n                                            } else {\n                                                return rawFile;\n                                            }\n                                        })\n                                        .collect(Collectors.toList()));\n\n                        manifest = newManifest;\n                        configuration = configuration.setManifest(newManifest);\n                        JsonUtils.writeToJsonFile(configurationFile, configuration);\n\n                        for (McbbsModpackManifest.File file : newManifest.getFiles())\n                            if (file instanceof McbbsModpackManifest.CurseFile) {\n                                McbbsModpackManifest.CurseFile curseFile = (McbbsModpackManifest.CurseFile) file;\n                                if (StringUtils.isNotBlank(curseFile.getFileName())) {\n                                    if (!modManager.hasSimpleMod(curseFile.getFileName())) {\n                                        var task = new FileDownloadTask(curseFile.getUrl(), modManager.getSimpleModPath(curseFile.getFileName()));\n                                        task.setCacheRepository(dependency.getCacheRepository());\n                                        task.setCaching(true);\n                                        dependencies.add(task.withCounter(\"hmcl.modpack.download\"));\n                                    }\n                                }\n                            }\n\n                        if (!dependencies.isEmpty()) {\n                            getProperties().put(\"total\", dependencies.size());\n                            notifyPropertiesChanged();\n                        }\n\n                        return executor.all(dependencies);\n                    })).whenComplete(wrap((unused1, ex) -> {\n                        // Let this task fail if the curse manifest has not been completed.\n                        // But continue other downloads.\n                        if (notFound.get())\n                            throw new ModpackCompletionException(new FileNotFoundException());\n                        if (!allNameKnown.get() || ex != null)\n                            throw new ModpackCompletionException();\n                    })));\n        }));\n    }\n\n    @Nullable\n    private Path getFilePath(McbbsModpackManifest.File file) {\n        if (file instanceof McbbsModpackManifest.AddonFile) {\n            return modManager.getRepository().getRunDirectory(modManager.getInstanceId()).resolve(((McbbsModpackManifest.AddonFile) file).getPath());\n        } else if (file instanceof McbbsModpackManifest.CurseFile) {\n            String fileName = ((McbbsModpackManifest.CurseFile) file).getFileName();\n            if (fileName == null) return null;\n            return modManager.getSimpleModPath(fileName);\n        } else {\n            throw new IllegalArgumentException();\n        }\n    }\n\n    private String getFileHash(McbbsModpackManifest.File file) {\n        if (file instanceof McbbsModpackManifest.AddonFile) {\n            return ((McbbsModpackManifest.AddonFile) file).getHash();\n        } else {\n            return null;\n        }\n    }\n\n    private Task<?> downloadFile(McbbsModpackManifest remoteManifest, McbbsModpackManifest.File file) throws IOException {\n        if (file instanceof McbbsModpackManifest.AddonFile) {\n            McbbsModpackManifest.AddonFile addonFile = (McbbsModpackManifest.AddonFile) file;\n            return new FileDownloadTask(\n                    remoteManifest.getFileApi() + \"/overrides/\" + addonFile.getPath(),\n                    modManager.getSimpleModPath(addonFile.getPath()),\n                    addonFile.getHash() != null ? new FileDownloadTask.IntegrityCheck(\"SHA-1\", addonFile.getHash()) : null);\n        } else if (file instanceof McbbsModpackManifest.CurseFile) {\n            // we download it later.\n            return null;\n        } else {\n            throw new IllegalArgumentException();\n        }\n    }\n\n    @NotNull\n    private McbbsModpackManifest.File mergeFile(@Nullable McbbsModpackManifest.File oldFile, @NotNull McbbsModpackManifest.File newFile) {\n        if (newFile instanceof McbbsModpackManifest.AddonFile) {\n            return newFile;\n        } else if (newFile instanceof McbbsModpackManifest.CurseFile) {\n            // Preserves prefetched file names and urls.\n            return oldFile != null ? oldFile : newFile;\n        } else {\n            throw new IllegalArgumentException();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackExportTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.mcbbs;\n\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.mod.ModAdviser;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackExportInfo;\nimport org.jackhuang.hmcl.mod.curse.CurseManifest;\nimport org.jackhuang.hmcl.mod.curse.CurseManifestMinecraft;\nimport org.jackhuang.hmcl.mod.curse.CurseManifestModLoader;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.Zipper;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class McbbsModpackExportTask extends Task<Void> {\n    private final DefaultGameRepository repository;\n    private final String version;\n    private final ModpackExportInfo info;\n    private final Path modpackFile;\n\n    public McbbsModpackExportTask(DefaultGameRepository repository, String version, ModpackExportInfo info, Path modpackFile) {\n        this.repository = repository;\n        this.version = version;\n        this.info = info.validate();\n        this.modpackFile = modpackFile;\n\n        onDone().register(event -> {\n            if (event.isFailed()) {\n                try {\n                    Files.deleteIfExists(modpackFile);\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to delete modpack file: \" + modpackFile, e);\n                }\n            }\n        });\n    }\n\n    @Override\n    public void execute() throws Exception {\n        ArrayList<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);\n        blackList.add(version + \".jar\");\n        blackList.add(version + \".json\");\n        LOG.info(\"Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker\");\n        try (var zip = new Zipper(modpackFile)) {\n            Path runDirectory = repository.getRunDirectory(version);\n            List<McbbsModpackManifest.File> files = new ArrayList<>();\n            zip.putDirectory(runDirectory, \"overrides\", path -> {\n                if (Modpack.acceptFile(path, blackList, info.getWhitelist())) {\n                    Path file = runDirectory.resolve(path);\n                    if (Files.isRegularFile(file)) {\n                        String relativePath = runDirectory.relativize(file).normalize().toString().replace(File.separatorChar, '/');\n                        files.add(new McbbsModpackManifest.AddonFile(true, relativePath, DigestUtils.digestToString(\"SHA-1\", file)));\n                    }\n                    return true;\n                } else {\n                    return false;\n                }\n            });\n\n            String gameVersion = repository.getGameVersion(version)\n                    .orElseThrow(() -> new IOException(\"Cannot parse the version of \" + version));\n            LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(version), gameVersion);\n\n            // Mcbbs manifest\n            List<McbbsModpackManifest.Addon> addons = new ArrayList<>();\n            addons.add(new McbbsModpackManifest.Addon(MINECRAFT.getPatchId(), gameVersion));\n            analyzer.getVersion(FORGE).ifPresent(forgeVersion ->\n                    addons.add(new McbbsModpackManifest.Addon(FORGE.getPatchId(), forgeVersion)));\n            analyzer.getVersion(CLEANROOM).ifPresent(cleanroomVersion ->\n                    addons.add(new McbbsModpackManifest.Addon(CLEANROOM.getPatchId(), cleanroomVersion)));\n            analyzer.getVersion(NEO_FORGE).ifPresent(neoForgeVersion ->\n                    addons.add(new McbbsModpackManifest.Addon(NEO_FORGE.getPatchId(), neoForgeVersion)));\n            analyzer.getVersion(LITELOADER).ifPresent(liteLoaderVersion ->\n                    addons.add(new McbbsModpackManifest.Addon(LITELOADER.getPatchId(), liteLoaderVersion)));\n            analyzer.getVersion(OPTIFINE).ifPresent(optifineVersion ->\n                    addons.add(new McbbsModpackManifest.Addon(OPTIFINE.getPatchId(), optifineVersion)));\n            analyzer.getVersion(FABRIC).ifPresent(fabricVersion ->\n                    addons.add(new McbbsModpackManifest.Addon(FABRIC.getPatchId(), fabricVersion)));\n            analyzer.getVersion(QUILT).ifPresent(quiltVersion ->\n                    addons.add(new McbbsModpackManifest.Addon(QUILT.getPatchId(), quiltVersion)));\n            analyzer.getVersion(LEGACY_FABRIC).ifPresent(legacyfabricVersion ->\n                    addons.add(new McbbsModpackManifest.Addon(LEGACY_FABRIC.getPatchId(), legacyfabricVersion)));\n\n            List<Library> libraries = new ArrayList<>();\n            // TODO libraries\n\n            List<McbbsModpackManifest.Origin> origins = new ArrayList<>();\n            // TODO origins\n\n            McbbsModpackManifest.Settings settings = new McbbsModpackManifest.Settings();\n            McbbsModpackManifest.LaunchInfo launchInfo = new McbbsModpackManifest.LaunchInfo(info.getMinMemory(), info.getSupportedJavaVersions(), StringUtils.tokenize(info.getLaunchArguments()), StringUtils.tokenize(info.getJavaArguments()));\n\n            McbbsModpackManifest mcbbsManifest = new McbbsModpackManifest(McbbsModpackManifest.MANIFEST_TYPE, 2, info.getName(), info.getVersion(), info.getAuthor(), info.getDescription(), info.getFileApi() == null ? null : StringUtils.removeSuffix(info.getFileApi(), \"/\"), info.getUrl(), info.isForceUpdate(), origins, addons, libraries, files, settings, launchInfo);\n            zip.putTextFile(JsonUtils.GSON.toJson(mcbbsManifest), \"mcbbs.packmeta\");\n\n            // CurseForge manifest\n            List<CurseManifestModLoader> modLoaders = new ArrayList<>();\n            analyzer.getVersion(FORGE).ifPresent(forgeVersion -> modLoaders.add(new CurseManifestModLoader(\"forge-\" + forgeVersion, true)));\n            analyzer.getVersion(NEO_FORGE).ifPresent(forgeVersion -> modLoaders.add(new CurseManifestModLoader(\"neoforge-\" + forgeVersion, true)));\n            analyzer.getVersion(FABRIC).ifPresent(fabricVersion -> modLoaders.add(new CurseManifestModLoader(\"fabric-\" + fabricVersion, true)));\n            // OptiFine and LiteLoader are not supported by CurseForge modpack.\n            CurseManifest curseManifest = new CurseManifest(CurseManifest.MINECRAFT_MODPACK, 1, info.getName(), info.getVersion(), info.getAuthor(), \"overrides\", new CurseManifestMinecraft(gameVersion, modLoaders), Collections.emptyList());\n            zip.putTextFile(JsonUtils.GSON.toJson(curseManifest), \"manifest.json\");\n        }\n    }\n\n    public static final ModpackExportInfo.Options OPTION = new ModpackExportInfo.Options()\n            .requireFileApi(true)\n            .requireUrl()\n            .requireForceUpdate()\n            .requireMinMemory()\n            .requireAuthlibInjectorServer()\n            .requireJavaArguments()\n            .requireLaunchArguments()\n            .requireOrigins()\n            .requireAuthor();\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.mcbbs;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.GameBuilder;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.mod.MinecraftInstanceTask;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.mod.ModpackInstallTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\n\npublic final class McbbsModpackLocalInstallTask extends Task<Void> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final Path zipFile;\n    private final Modpack modpack;\n    private final McbbsModpackManifest manifest;\n    private final String name;\n    private final boolean update;\n    private final DefaultGameRepository repository;\n    private final MinecraftInstanceTask<McbbsModpackManifest> instanceTask;\n    private final List<Task<?>> dependencies = new ArrayList<>(2);\n    private final List<Task<?>> dependents = new ArrayList<>(4);\n\n    public McbbsModpackLocalInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, Modpack modpack, McbbsModpackManifest manifest, String name) {\n        this.dependencyManager = dependencyManager;\n        this.zipFile = zipFile;\n        this.modpack = modpack;\n        this.manifest = manifest;\n        this.name = name;\n        this.repository = dependencyManager.getGameRepository();\n        Path run = repository.getRunDirectory(name);\n\n        Path json = repository.getModpackConfiguration(name);\n        if (repository.hasVersion(name) && Files.notExists(json))\n            throw new IllegalArgumentException(\"Version \" + name + \" already exists.\");\n        this.update = repository.hasVersion(name);\n\n\n        GameBuilder builder = dependencyManager.gameBuilder().name(name);\n        for (McbbsModpackManifest.Addon addon : manifest.getAddons()) {\n            builder.version(addon.getId(), addon.getVersion());\n        }\n\n        dependents.add(builder.buildAsync());\n        onDone().register(event -> {\n            if (event.isFailed())\n                repository.removeVersionFromDisk(name);\n        });\n\n        ModpackConfiguration<McbbsModpackManifest> config = null;\n        try {\n            if (Files.exists(json)) {\n                config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(McbbsModpackManifest.class));\n\n                if (!McbbsModpackProvider.INSTANCE.getName().equals(config.getType()))\n                    throw new IllegalArgumentException(\"Version \" + name + \" is not a Mcbbs modpack. Cannot update this version.\");\n            }\n        } catch (JsonParseException | IOException ignore) {\n        }\n        dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(\"/overrides\"), any -> true, config).withStage(\"hmcl.modpack\"));\n        instanceTask = new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(\"/overrides\"), manifest, McbbsModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name));\n        dependents.add(instanceTask.withStage(\"hmcl.modpack\"));\n    }\n\n    @Override\n    public List<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        Version version = repository.readVersionJson(name);\n        Optional<Version> mcbbsPatch = version.getPatches().stream().filter(patch -> PATCH_NAME.equals(patch.getId())).findFirst();\n        if (!update) {\n            Version patch = new Version(PATCH_NAME).setLibraries(manifest.getLibraries());\n            dependencies.add(repository.saveAsync(version.addPatch(patch)));\n        } else if (mcbbsPatch.isPresent()) {\n            // This mcbbs modpack was installed by HMCL.\n            Version patch = mcbbsPatch.get().setLibraries(manifest.getLibraries());\n            dependencies.add(repository.saveAsync(version.addPatch(patch)));\n        } else {\n            // This mcbbs modpack was installed by other launchers.\n            // TODO: maintain libraries.\n        }\n\n        dependencies.add(new McbbsModpackCompletionTask(dependencyManager, name, instanceTask.getResult()));\n    }\n\n    private static final String PATCH_NAME = \"mcbbs\";\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackManifest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.mcbbs;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.LaunchOptions;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackManifest;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.*;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;\n\npublic class McbbsModpackManifest implements ModpackManifest, Validation {\n    public static final String MANIFEST_TYPE = \"minecraftModpack\";\n\n    private final String manifestType;\n    private final int manifestVersion;\n    private final String name;\n    private final String version;\n    private final String author;\n    private final String description;\n\n    @Nullable\n    private final String fileApi;\n    private final String url;\n    private final boolean forceUpdate;\n    @SerializedName(\"origin\")\n    private final List<Origin> origins;\n    private final List<Addon> addons;\n    private final List<Library> libraries;\n    private final List<File> files;\n    private final Settings settings;\n    private final LaunchInfo launchInfo;\n    // sandbox and antiCheating are both not supported.\n\n    public McbbsModpackManifest() {\n        this(MANIFEST_TYPE, 1, \"\", \"\", \"\", \"\", null, \"\", false, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), new Settings(), new LaunchInfo());\n    }\n\n    public McbbsModpackManifest(String manifestType, int manifestVersion, String name, String version, String author, String description, @Nullable String fileApi, String url, boolean forceUpdate, List<Origin> origins, List<Addon> addons, List<Library> libraries, List<File> files, Settings settings, LaunchInfo launchInfo) {\n        this.manifestType = manifestType;\n        this.manifestVersion = manifestVersion;\n        this.name = name;\n        this.version = version;\n        this.author = author;\n        this.description = description;\n        this.fileApi = fileApi;\n        this.url = url;\n        this.forceUpdate = forceUpdate;\n        this.origins = origins;\n        this.addons = addons;\n        this.libraries = libraries;\n        this.files = files;\n        this.settings = settings;\n        this.launchInfo = launchInfo;\n    }\n\n    public String getManifestType() {\n        return manifestType;\n    }\n\n    public int getManifestVersion() {\n        return manifestVersion;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public String getFileApi() {\n        return fileApi;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public boolean isForceUpdate() {\n        return forceUpdate;\n    }\n\n    public List<Origin> getOrigins() {\n        return origins;\n    }\n\n    public List<Addon> getAddons() {\n        return addons;\n    }\n\n    public List<Library> getLibraries() {\n        return libraries;\n    }\n\n    public List<File> getFiles() {\n        return files;\n    }\n\n    public Settings getSettings() {\n        return settings;\n    }\n\n    public LaunchInfo getLaunchInfo() {\n        return launchInfo;\n    }\n\n    public McbbsModpackManifest setFiles(List<File> files) {\n        return new McbbsModpackManifest(manifestType, manifestVersion, name, version, author, description, fileApi, url, forceUpdate, origins, addons, libraries, files, settings, launchInfo);\n    }\n\n    @Override\n    public ModpackProvider getProvider() {\n        return McbbsModpackProvider.INSTANCE;\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        if (!MANIFEST_TYPE.equals(manifestType))\n            throw new JsonParseException(\"McbbsModpackManifest.manifestType must be 'minecraftModpack'\");\n//        if (manifestVersion > 1)\n//            throw new JsonParseException(\"Only supports version 1 of McbbsModpackManifest\");\n        if (files == null)\n            throw new JsonParseException(\"McbbsModpackManifest.files cannot be null\");\n        if (addons == null)\n            throw new JsonParseException(\"McbbsModpackManifest.addons cannot be null\");\n    }\n\n    public static final class Origin {\n        private final String type;\n        private final int id;\n\n        public Origin() {\n            this(\"\", 0);\n        }\n\n        public Origin(String type, int id) {\n            this.type = type;\n            this.id = id;\n        }\n\n        public String getType() {\n            return type;\n        }\n\n        public int getId() {\n            return id;\n        }\n    }\n\n    public static final class Addon {\n        private final String id;\n        private final String version;\n\n        public Addon() {\n            this(\"\", \"\");\n        }\n\n        public Addon(String id, String version) {\n            this.id = id;\n            this.version = version;\n        }\n\n        public String getId() {\n            return id;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n    }\n\n    public static final class Settings {\n        @SerializedName(\"install_mods\")\n        private final boolean installMods;\n\n        @SerializedName(\"install_resourcepack\")\n        private final boolean installResourcepack;\n\n        public Settings() {\n            this(true, true);\n        }\n\n        public Settings(boolean installMods, boolean installResourcepack) {\n            this.installMods = installMods;\n            this.installResourcepack = installResourcepack;\n        }\n\n        public boolean isInstallMods() {\n            return installMods;\n        }\n\n        public boolean isInstallResourcepack() {\n            return installResourcepack;\n        }\n    }\n\n    @JsonType(\n            property = \"type\",\n            subtypes = {\n                    @JsonSubtype(clazz = AddonFile.class, name = \"addon\"),\n                    @JsonSubtype(clazz = CurseFile.class, name = \"curse\")\n            }\n    )\n    public static abstract class File implements Validation {\n        protected final boolean force;\n\n        public File(boolean force) {\n            this.force = force;\n        }\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n        }\n\n        public boolean isForce() {\n            return force;\n        }\n    }\n\n    public static final class AddonFile extends File {\n        private final String path;\n        private final String hash;\n\n        public AddonFile(boolean force, String path, String hash) {\n            super(force);\n            this.path = Objects.requireNonNull(path);\n            this.hash = Objects.requireNonNull(hash);\n        }\n\n        public String getPath() {\n            return path;\n        }\n\n        public String getHash() {\n            return hash;\n        }\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            super.validate();\n\n            Validation.requireNonNull(path, \"AddonFile.path cannot be null\");\n            Validation.requireNonNull(hash, \"AddonFile.hash cannot be null\");\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            AddonFile addonFile = (AddonFile) o;\n            return path.equals(addonFile.path);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(path);\n        }\n    }\n\n    public static final class CurseFile extends File {\n        private final int projectID;\n        private final int fileID;\n        private final String fileName;\n        private final String url;\n\n        public CurseFile() {\n            this(false, 0, 0, \"\", \"\");\n        }\n\n        public CurseFile(boolean force, int projectID, int fileID, String fileName, String url) {\n            super(force);\n            this.projectID = projectID;\n            this.fileID = fileID;\n            this.fileName = fileName;\n            this.url = url;\n        }\n\n        public int getProjectID() {\n            return projectID;\n        }\n\n        public int getFileID() {\n            return fileID;\n        }\n\n        @Nullable\n        public String getFileName() {\n            return fileName;\n        }\n\n        public String getUrl() {\n            return url == null\n                    ? \"https://www.curseforge.com/minecraft/mc-mods/\" + projectID + \"/download/\" + fileID + \"/file\"\n                    : url;\n        }\n\n        public CurseFile withFileName(String fileName) {\n            return new CurseFile(force, projectID, fileID, fileName, url);\n        }\n\n        public CurseFile withURL(String url) {\n            return new CurseFile(force, projectID, fileID, fileName, url);\n        }\n\n        @Override\n        public void validate() throws JsonParseException, TolerableValidationException {\n            super.validate();\n\n            if (projectID == 0 || fileID == 0) {\n                throw new JsonParseException(\"CurseFile.{projectID|fileID} cannot be empty.\");\n            }\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            CurseFile curseFile = (CurseFile) o;\n            return projectID == curseFile.projectID && fileID == curseFile.fileID;\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(projectID, fileID);\n        }\n    }\n\n    public static final class LaunchInfo {\n        private final int minMemory;\n        private final List<Integer> supportJava;\n        @SerializedName(\"launchArgument\")\n        private final List<String> launchArguments;\n        @SerializedName(\"javaArgument\")\n        private final List<String> javaArguments;\n\n        public LaunchInfo() {\n            this(0, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());\n        }\n\n        public LaunchInfo(int minMemory, List<Integer> supportJava, List<String> launchArguments, List<String> javaArguments) {\n            this.minMemory = minMemory;\n            this.supportJava = supportJava;\n            this.launchArguments = launchArguments;\n            this.javaArguments = javaArguments;\n        }\n\n        public int getMinMemory() {\n            return minMemory;\n        }\n\n        @Nullable\n        public List<Integer> getSupportJava() {\n            return supportJava;\n        }\n\n        public List<String> getLaunchArguments() {\n            return Optional.ofNullable(launchArguments).orElseGet(Collections::emptyList);\n        }\n\n        public List<String> getJavaArguments() {\n            return Optional.ofNullable(javaArguments).orElseGet(Collections::emptyList);\n        }\n    }\n\n    public static class ServerInfo {\n        private final String authlibInjectorServer;\n\n        public ServerInfo() {\n            this(null);\n        }\n\n        public ServerInfo(String authlibInjectorServer) {\n            this.authlibInjectorServer = authlibInjectorServer;\n        }\n\n        @Nullable\n        public String getAuthlibInjectorServer() {\n            return authlibInjectorServer;\n        }\n    }\n\n    public Modpack toModpack(Charset encoding) throws IOException {\n        String gameVersion = addons.stream().filter(x -> MINECRAFT.getPatchId().equals(x.id)).findAny()\n                .orElseThrow(() -> new IOException(\"Cannot find game version\")).getVersion();\n        return new Modpack(name, author, version, gameVersion, description, encoding, this) {\n            @Override\n            public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {\n                return new McbbsModpackLocalInstallTask(dependencyManager, zipFile, this, McbbsModpackManifest.this, name);\n            }\n        };\n    }\n\n    public void injectLaunchOptions(LaunchOptions.Builder launchOptions) {\n        launchOptions.getGameArguments().addAll(launchInfo.getLaunchArguments());\n        launchOptions.getJavaArguments().addAll(launchInfo.getJavaArguments());\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.mcbbs;\n\nimport com.google.gson.JsonParseException;\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.LaunchOptions;\nimport org.jackhuang.hmcl.mod.*;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\n\npublic final class McbbsModpackProvider implements ModpackProvider {\n    public static final McbbsModpackProvider INSTANCE = new McbbsModpackProvider();\n\n    @Override\n    public String getName() {\n        return \"Mcbbs\";\n    }\n\n    @Override\n    public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        return new McbbsModpackCompletionTask(dependencyManager, version);\n    }\n\n    @Override\n    public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException {\n        if (!(modpack.getManifest() instanceof McbbsModpackManifest mcbbsModpackManifest))\n            throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());\n\n        return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new McbbsModpackLocalInstallTask(dependencyManager, zipFile, modpack, mcbbsModpackManifest, name));\n    }\n\n    @Override\n    public void injectLaunchOptions(String modpackConfigurationJson, LaunchOptions.Builder builder) {\n        ModpackConfiguration<McbbsModpackManifest> config = JsonUtils.GSON.fromJson(modpackConfigurationJson, ModpackConfiguration.typeOf(McbbsModpackManifest.class));\n\n        if (!getName().equals(config.getType())) {\n            throw new IllegalArgumentException(\"Incorrect manifest type, actual=\" + config.getType() + \", expected=\" + getName());\n        }\n\n        config.getManifest().injectLaunchOptions(builder);\n    }\n\n    private static Modpack fromManifestFile(InputStream json, Charset encoding) throws IOException, JsonParseException {\n        McbbsModpackManifest manifest = JsonUtils.fromNonNullJsonFully(json, McbbsModpackManifest.class);\n        return manifest.toModpack(encoding);\n    }\n\n    @Override\n    public Modpack readManifest(ZipArchiveReader zip, Path file, Charset encoding) throws IOException, JsonParseException {\n        ZipArchiveEntry mcbbsPackMeta = zip.getEntry(\"mcbbs.packmeta\");\n        if (mcbbsPackMeta != null) {\n            return fromManifestFile(zip.getInputStream(mcbbsPackMeta), encoding);\n        }\n        ZipArchiveEntry manifestJson = zip.getEntry(\"manifest.json\");\n        if (manifestJson != null) {\n            return fromManifestFile(zip.getInputStream(manifestJson), encoding);\n        }\n        throw new IOException(\"`mcbbs.packmeta` or `manifest.json` cannot be found\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackRemoteInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.mcbbs;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.GameBuilder;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class McbbsModpackRemoteInstallTask extends Task<Void> {\n\n    private final String name;\n    private final DefaultDependencyManager dependency;\n    private final DefaultGameRepository repository;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n    private final List<Task<?>> dependents = new ArrayList<>(1);\n    private final McbbsModpackManifest manifest;\n\n    public McbbsModpackRemoteInstallTask(DefaultDependencyManager dependencyManager, McbbsModpackManifest manifest, String name) {\n        this.name = name;\n        this.dependency = dependencyManager;\n        this.repository = dependencyManager.getGameRepository();\n        this.manifest = manifest;\n\n        Path json = repository.getModpackConfiguration(name);\n        if (repository.hasVersion(name) && Files.notExists(json))\n            throw new IllegalArgumentException(\"Version \" + name + \" already exists.\");\n\n        GameBuilder builder = dependencyManager.gameBuilder().name(name);\n        for (McbbsModpackManifest.Addon addon : manifest.getAddons()) {\n            builder.version(addon.getId(), addon.getVersion());\n        }\n\n        dependents.add(builder.buildAsync());\n        onDone().register(event -> {\n            if (event.isFailed())\n                repository.removeVersionFromDisk(name);\n        });\n\n        ModpackConfiguration<McbbsModpackManifest> config;\n        try {\n            if (Files.exists(json)) {\n                config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(McbbsModpackManifest.class));\n\n                if (!MODPACK_TYPE.equals(config.getType()))\n                    throw new IllegalArgumentException(\"Version \" + name + \" is not a Mcbbs modpack. Cannot update this version.\");\n            }\n        } catch (JsonParseException | IOException ignore) {\n        }\n    }\n\n    @Override\n    public List<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        dependencies.add(new McbbsModpackCompletionTask(dependency, name, new ModpackConfiguration<>(manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), Collections.emptyList())));\n    }\n\n    public static final String MODPACK_TYPE = \"Server\";\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modinfo;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n@Immutable\npublic final class FabricModMetadata {\n    private final String id;\n    private final String name;\n    private final String version;\n    private final String description;\n    private final String icon;\n    private final List<FabricModAuthor> authors;\n    private final Map<String, String> contact;\n\n    public FabricModMetadata() {\n        this(\"\", \"\", \"\", \"\", \"\", Collections.emptyList(), Collections.emptyMap());\n    }\n\n    public FabricModMetadata(String id, String name, String version, String icon, String description, List<FabricModAuthor> authors, Map<String, String> contact) {\n        this.id = id;\n        this.name = name;\n        this.version = version;\n        this.icon = icon;\n        this.description = description;\n        this.authors = authors;\n        this.contact = contact;\n    }\n\n    public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {\n        ZipArchiveEntry mcmod = tree.getEntry(\"fabric.mod.json\");\n        if (mcmod == null)\n            throw new IOException(\"File \" + modFile + \" is not a Fabric mod.\");\n        FabricModMetadata metadata = JsonUtils.fromNonNullJsonFully(tree.getInputStream(mcmod), FabricModMetadata.class);\n        String authors = metadata.authors == null ? \"\" : metadata.authors.stream().map(author -> author.name).collect(Collectors.joining(\", \"));\n        return new LocalModFile(modManager, modManager.getLocalMod(metadata.id, ModLoaderType.FABRIC), modFile, metadata.name, new LocalModFile.Description(metadata.description),\n                authors, metadata.version, \"\", metadata.contact != null ? metadata.contact.getOrDefault(\"homepage\", \"\") : \"\", metadata.icon);\n    }\n\n    @JsonAdapter(FabricModAuthorSerializer.class)\n    public static final class FabricModAuthor {\n        private final String name;\n\n        public FabricModAuthor() {\n            this(\"\");\n        }\n\n        public FabricModAuthor(String name) {\n            this.name = name;\n        }\n    }\n\n    public static final class FabricModAuthorSerializer implements JsonSerializer<FabricModAuthor>, JsonDeserializer<FabricModAuthor> {\n        @Override\n        public FabricModAuthor deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            return json.isJsonPrimitive() ? new FabricModAuthor(json.getAsString()) : new FabricModAuthor(json.getAsJsonObject().getAsJsonPrimitive(\"name\").getAsString());\n        }\n\n        @Override\n        public JsonElement serialize(FabricModAuthor src, Type typeOfSrc, JsonSerializationContext context) {\n            return src == null ? JsonNull.INSTANCE : new JsonPrimitive(src.name);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java",
    "content": "package org.jackhuang.hmcl.mod.modinfo;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonDeserializationContext;\nimport com.google.gson.JsonDeserializer;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonParseException;\nimport com.google.gson.JsonPrimitive;\nimport com.google.gson.annotations.JsonAdapter;\nimport com.moandjiezana.toml.Toml;\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.Type;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.StringJoiner;\nimport java.util.jar.Attributes;\nimport java.util.jar.Manifest;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n@Immutable\npublic final class ForgeNewModMetadata {\n    private final String modLoader;\n\n    private final String loaderVersion;\n\n    private final String logoFile;\n\n    private final String license;\n\n    private final List<Mod> mods;\n\n    public ForgeNewModMetadata(String modLoader, String loaderVersion, String logoFile, String license, List<Mod> mods) {\n        this.modLoader = modLoader;\n        this.loaderVersion = loaderVersion;\n        this.logoFile = logoFile;\n        this.license = license;\n        this.mods = mods;\n    }\n\n    public String getModLoader() {\n        return modLoader;\n    }\n\n    public String getLoaderVersion() {\n        return loaderVersion;\n    }\n\n    public String getLogoFile() {\n        return logoFile;\n    }\n\n    public String getLicense() {\n        return license;\n    }\n\n    public List<Mod> getMods() {\n        return mods;\n    }\n\n    public static class Mod {\n        private final String modId;\n        private final String version;\n        private final String displayName;\n        private final String side;\n        private final String displayURL;\n        @JsonAdapter(AuthorDeserializer.class)\n        private final String authors;\n        private final String description;\n\n        public Mod() {\n            this(\"\", \"\", \"\", \"\", \"\", \"\", \"\");\n        }\n\n        public Mod(String modId, String version, String displayName, String side, String displayURL, String authors, String description) {\n            this.modId = modId;\n            this.version = version;\n            this.displayName = displayName;\n            this.side = side;\n            this.displayURL = displayURL;\n            this.authors = authors;\n            this.description = description;\n        }\n\n        public String getModId() {\n            return modId;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n\n        public String getDisplayName() {\n            return displayName;\n        }\n\n        public String getSide() {\n            return side;\n        }\n\n        public String getDisplayURL() {\n            return displayURL;\n        }\n\n        public String getAuthors() {\n            return authors;\n        }\n\n        public String getDescription() {\n            return description;\n        }\n\n        static final class AuthorDeserializer implements JsonDeserializer<String> {\n            @Override\n            public String deserialize(JsonElement authors, Type type, JsonDeserializationContext context) throws JsonParseException {\n                if (authors == null || authors.isJsonNull()) {\n                    return null;\n                } else if (authors instanceof JsonPrimitive primitive) {\n                    return primitive.getAsString();\n                } else if (authors instanceof JsonArray array) {\n                    var joiner = new StringJoiner(\", \");\n                    for (int i = 0; i < array.size(); i++) {\n                        if (!(array.get(i) instanceof JsonPrimitive element)) {\n                            return authors.toString();\n                        }\n\n                        joiner.add(element.getAsString());\n                    }\n                    return joiner.toString();\n                }\n\n                return authors.toString();\n            }\n        }\n    }\n\n    public static LocalModFile fromForgeFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException {\n        return fromFile(modManager, modFile, tree, ModLoaderType.FORGE);\n    }\n\n    public static LocalModFile fromNeoForgeFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException {\n        return fromFile(modManager, modFile, tree, ModLoaderType.NEO_FORGED);\n    }\n\n    private static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree, ModLoaderType modLoaderType) throws IOException {\n        if (modLoaderType != ModLoaderType.FORGE && modLoaderType != ModLoaderType.NEO_FORGED) {\n            throw new IOException(\"Invalid mod loader: \" + modLoaderType);\n        }\n\n        if (modLoaderType == ModLoaderType.NEO_FORGED) {\n            try {\n                return fromFile0(\"META-INF/neoforge.mods.toml\", modLoaderType, modManager, modFile, tree);\n            } catch (Exception ignored) {\n            }\n        }\n\n        try {\n            return fromFile0(\"META-INF/mods.toml\", modLoaderType, modManager, modFile, tree);\n        } catch (Exception ignored) {\n        }\n\n        try {\n            return fromEmbeddedMod(modManager, modFile, tree, modLoaderType);\n        } catch (Exception ignored) {\n        }\n\n        throw new IOException(\"File \" + modFile + \" is not a Forge 1.13+ or NeoForge mod.\");\n    }\n\n    private static LocalModFile fromFile0(\n            String tomlPath,\n            ModLoaderType modLoaderType,\n            ModManager modManager,\n            Path modFile,\n            ZipFileTree tree) throws IOException, JsonParseException {\n        ZipArchiveEntry modToml = tree.getEntry(tomlPath);\n        if (modToml == null)\n            throw new IOException(\"File \" + modFile + \" is not a Forge 1.13+ or NeoForge mod.\");\n        Toml toml = new Toml().read(tree.readTextEntry(modToml));\n        ForgeNewModMetadata metadata = toml.to(ForgeNewModMetadata.class);\n        if (metadata == null || metadata.getMods().isEmpty())\n            throw new IOException(\"Mod \" + modFile + \" `mods.toml` is malformed..\");\n        Mod mod = metadata.getMods().get(0);\n        ZipArchiveEntry manifestMF = tree.getEntry(\"META-INF/MANIFEST.MF\");\n        String jarVersion = \"\";\n        if (manifestMF != null) {\n            try (InputStream is = tree.getInputStream(manifestMF)) {\n                Manifest manifest = new Manifest(is);\n                jarVersion = manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to parse MANIFEST.MF in file \" + modFile);\n            }\n        }\n\n        ModLoaderType type = analyzeLoader(toml, mod.getModId(), modLoaderType);\n\n        return new LocalModFile(modManager, modManager.getLocalMod(mod.getModId(), type), modFile, mod.getDisplayName(), new LocalModFile.Description(mod.getDescription()),\n                mod.getAuthors(), jarVersion == null ? mod.getVersion() : mod.getVersion().replace(\"${file.jarVersion}\", jarVersion), \"\",\n                mod.getDisplayURL(),\n                metadata.getLogoFile());\n    }\n\n    private static LocalModFile fromEmbeddedMod(ModManager modManager, Path modFile, ZipFileTree tree, ModLoaderType modLoaderType) throws IOException {\n        ZipArchiveEntry manifestFile = tree.getEntry(\"META-INF/MANIFEST.MF\");\n        if (manifestFile == null)\n            throw new IOException(\"Missing MANIFEST.MF in file \" + modFile);\n\n        Manifest manifest;\n        try (InputStream input = tree.getInputStream(manifestFile)) {\n            manifest = new Manifest(input);\n        }\n\n        List<ZipArchiveEntry> embeddedModFiles = List.of();\n\n        String embeddedDependenciesMod = manifest.getMainAttributes().getValue(\"Embedded-Dependencies-Mod\");\n        if (embeddedDependenciesMod != null) {\n            ZipArchiveEntry embeddedModFile = tree.getEntry(embeddedDependenciesMod);\n            if (embeddedModFile == null) {\n                LOG.warning(\"Missing embedded-dependencies-mod: \" + embeddedDependenciesMod);\n                throw new IOException();\n            }\n            embeddedModFiles = List.of(embeddedModFile);\n        } else {\n            ZipArchiveEntry jarInJarMetadata = tree.getEntry(\"META-INF/jarjar/metadata.json\");\n            if (jarInJarMetadata != null) {\n                JarInJarMetadata metadata = JsonUtils.fromJsonFully(tree.getInputStream(jarInJarMetadata), JarInJarMetadata.class);\n                if (metadata == null)\n                    throw new IOException(\"Invalid metadata file: \" + jarInJarMetadata);\n\n                metadata.validate();\n\n                embeddedModFiles = new ArrayList<>();\n                for (EmbeddedJarMetadata jar : metadata.jars) {\n                    ZipArchiveEntry path = tree.getEntry(jar.path);\n                    if (path != null) {\n                        embeddedModFiles.add(path);\n                    } else {\n                        LOG.warning(\"Missing embedded-dependencies-mod: \" + jar.path);\n                    }\n                }\n            }\n        }\n\n        if (embeddedModFiles.isEmpty()) {\n            throw new IOException(\"Missing embedded mods\");\n        }\n\n        Path tempFile = Files.createTempFile(\"hmcl-\", \".zip\");\n        try {\n            for (ZipArchiveEntry embeddedModFile : embeddedModFiles) {\n                tree.extractTo(embeddedModFile, tempFile);\n                try (ZipFileTree embeddedTree = CompressingUtils.openZipTree(tempFile)) {\n                    return fromFile(modManager, modFile, embeddedTree, modLoaderType);\n                } catch (Exception ignored) {\n                }\n            }\n        } finally {\n            Files.deleteIfExists(tempFile);\n        }\n\n        throw new IOException();\n    }\n\n    private static ModLoaderType analyzeLoader(Toml toml, String modID, ModLoaderType loader) throws IOException {\n        List<HashMap<String, Object>> dependencies = null;\n        try {\n            dependencies = toml.getList(\"dependencies.\" + modID);\n        } catch (ClassCastException ignored) { // https://github.com/HMCL-dev/HMCL/issues/5068\n        }\n\n        if (dependencies == null) {\n            try {\n                dependencies = toml.getList(\"dependencies\"); // ??? I have no idea why some of the Forge mods use [[dependencies]]\n            } catch (ClassCastException e) {\n                try {\n                    Toml table = toml.getTable(\"dependencies\");\n                    if (table == null)\n                        return loader;\n\n                    dependencies = table.getList(modID);\n                } catch (Throwable ignored) {\n                }\n            }\n\n            if (dependencies == null) {\n                return loader;\n            }\n        }\n\n        ModLoaderType result = null;\n        loop:\n        for (HashMap<String, Object> dependency : dependencies) {\n            switch ((String) dependency.get(\"modId\")) {\n                case \"forge\":\n                    result = ModLoaderType.FORGE;\n                    break loop;\n                case \"neoforge\":\n                    result = ModLoaderType.NEO_FORGED;\n                    break loop;\n            }\n        }\n\n        if (result == loader)\n            return result;\n        else if (result != null)\n            throw new IOException(\"Loader mismatch\");\n        else {\n            LOG.warning(\"Cannot determine the mod loader for mod \" + modID + \", expected \" + loader);\n            return loader;\n        }\n    }\n\n    @JsonSerializable\n    private record JarInJarMetadata(List<EmbeddedJarMetadata> jars) implements Validation {\n        @Override\n        public void validate() throws JsonParseException {\n            Validation.requireNonNull(jars, \"jars\");\n            for (EmbeddedJarMetadata jar : jars) {\n                jar.validate();\n            }\n        }\n    }\n\n    @JsonSerializable\n    private record EmbeddedJarMetadata(\n            String path,\n            boolean isObfuscated\n    ) implements Validation {\n        @Override\n        public void validate() throws JsonParseException {\n            Validation.requireNonNull(path, \"path\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modinfo;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class ForgeOldModMetadata {\n    @SerializedName(\"modid\")\n    private final String modId;\n    private final String name;\n    private final String description;\n    private final String author;\n    private final String version;\n    private final String logoFile;\n    private final String mcversion;\n    private final String url;\n    private final String updateUrl;\n    private final String credits;\n    private final String[] authorList;\n    private final String[] authors;\n\n    public ForgeOldModMetadata() {\n        this(\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", new String[0], new String[0]);\n    }\n\n    public ForgeOldModMetadata(String modId, String name, String description, String author, String version, String logoFile, String mcversion, String url, String updateUrl, String credits, String[] authorList, String[] authors) {\n        this.modId = modId;\n        this.name = name;\n        this.description = description;\n        this.author = author;\n        this.version = version;\n        this.logoFile = logoFile;\n        this.mcversion = mcversion;\n        this.url = url;\n        this.updateUrl = updateUrl;\n        this.credits = credits;\n        this.authorList = authorList;\n        this.authors = authors;\n    }\n\n    public String getModId() {\n        return modId;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public String getLogoFile() {\n        return logoFile;\n    }\n\n    public String getGameVersion() {\n        return mcversion;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public String getUpdateUrl() {\n        return updateUrl;\n    }\n\n    public String getCredits() {\n        return credits;\n    }\n\n    public String[] getAuthorList() {\n        return authorList;\n    }\n\n    public String[] getAuthors() {\n        return authors;\n    }\n\n    public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {\n        ZipArchiveEntry mcmod = tree.getEntry(\"mcmod.info\");\n        if (mcmod == null)\n            throw new IOException(\"File \" + modFile + \" is not a Forge mod.\");\n\n        List<ForgeOldModMetadata> modList;\n\n        try (var reader = tree.getBufferedReader(mcmod);\n             var jsonReader = new JsonReader(reader)) {\n            JsonToken firstToken = jsonReader.peek();\n\n            if (firstToken == JsonToken.BEGIN_ARRAY)\n                modList = JsonUtils.GSON.fromJson(jsonReader, listTypeOf(ForgeOldModMetadata.class));\n            else if (firstToken == JsonToken.BEGIN_OBJECT) {\n                ForgeOldModMetadataLst list = JsonUtils.GSON.fromJson(jsonReader, ForgeOldModMetadataLst.class);\n                if (list == null)\n                    throw new IOException(\"Mod \" + modFile + \" `mcmod.info` is malformed\");\n                modList = list.modList();\n            } else {\n                throw new JsonParseException(\"Unexpected first token: \" + firstToken);\n            }\n        }\n\n        if (modList == null || modList.isEmpty())\n            throw new IOException(\"Mod \" + modFile + \" `mcmod.info` is malformed\");\n        ForgeOldModMetadata metadata = modList.get(0);\n        String authors = metadata.getAuthor();\n        if (StringUtils.isBlank(authors) && metadata.getAuthors().length > 0)\n            authors = String.join(\", \", metadata.getAuthors());\n        if (StringUtils.isBlank(authors) && metadata.getAuthorList().length > 0)\n            authors = String.join(\", \", metadata.getAuthorList());\n        if (StringUtils.isBlank(authors))\n            authors = metadata.getCredits();\n        return new LocalModFile(modManager, modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalModFile.Description(metadata.getDescription()),\n                authors, metadata.getVersion(), metadata.getGameVersion(),\n                StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url,\n                metadata.getLogoFile());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadataLst.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modinfo;\n\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\n\nimport java.util.List;\n\n/// @author Glavo\n@JsonSerializable\npublic record ForgeOldModMetadataLst(\n        int modListVersion,\n        List<ForgeOldModMetadata> modList) {\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modinfo;\n\nimport com.google.gson.JsonParseException;\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\n/**\n *\n * @author huangyuhui\n */\n@Immutable\npublic final class LiteModMetadata {\n    private final String name;\n    private final String version;\n    private final String mcversion;\n    private final String revision;\n    private final String author;\n    private final String[] classTransformerClasses;\n    private final String description;\n    private final String modpackName;\n    private final String modpackVersion;\n    private final String checkUpdateUrl;\n    private final String updateURI;\n\n    public LiteModMetadata() {\n        this(\"\", \"\", \"\", \"\", \"\", new String[]{\"\"}, \"\", \"\", \"\", \"\", \"\");\n    }\n\n    public LiteModMetadata(String name, String version, String mcversion, String revision, String author, String[] classTransformerClasses, String description, String modpackName, String modpackVersion, String checkUpdateUrl, String updateURI) {\n        this.name = name;\n        this.version = version;\n        this.mcversion = mcversion;\n        this.revision = revision;\n        this.author = author;\n        this.classTransformerClasses = classTransformerClasses;\n        this.description = description;\n        this.modpackName = modpackName;\n        this.modpackVersion = modpackVersion;\n        this.checkUpdateUrl = checkUpdateUrl;\n        this.updateURI = updateURI;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public String getGameVersion() {\n        return mcversion;\n    }\n\n    public String getRevision() {\n        return revision;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public String[] getClassTransformerClasses() {\n        return classTransformerClasses;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public String getModpackName() {\n        return modpackName;\n    }\n\n    public String getModpackVersion() {\n        return modpackVersion;\n    }\n\n    public String getCheckUpdateUrl() {\n        return checkUpdateUrl;\n    }\n\n    public String getUpdateURI() {\n        return updateURI;\n    }\n\n    public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {\n        ZipArchiveEntry entry = tree.getEntry(\"litemod.json\");\n        if (entry == null)\n            throw new IOException(\"File \" + modFile + \" is not a LiteLoader mod.\");\n        LiteModMetadata metadata = JsonUtils.fromJsonFully(tree.getInputStream(entry), LiteModMetadata.class);\n        if (metadata == null)\n            throw new IOException(\"Mod \" + modFile + \" `litemod.json` is malformed.\");\n        return new LocalModFile(modManager, modManager.getLocalMod(metadata.getName(), ModLoaderType.LITE_LOADER), modFile, metadata.getName(), new LocalModFile.Description(metadata.getDescription()), metadata.getAuthor(),\n                metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI(), \"\");\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modinfo;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport com.google.gson.annotations.SerializedName;\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n@JsonSerializable\npublic record PackMcMeta(@SerializedName(\"pack\") PackInfo pack) implements Validation {\n    @Override\n    public void validate() throws JsonParseException {\n        if (pack == null)\n            throw new JsonParseException(\"pack cannot be null\");\n    }\n\n    @JsonAdapter(PackInfoDeserializer.class)\n    public record PackInfo(@SerializedName(\"pack_format\") int packFormat,\n                           @SerializedName(\"min_format\") PackVersion minPackVersion,\n                           @SerializedName(\"max_format\") PackVersion maxPackVersion,\n                           @SerializedName(\"description\") LocalModFile.Description description) {\n        public PackVersion getEffectiveMinVersion() {\n            return !minPackVersion.isUnspecified() ? minPackVersion : new PackVersion(packFormat, 0);\n        }\n\n        public PackVersion getEffectiveMaxVersion() {\n            return !maxPackVersion.isUnspecified() ? maxPackVersion : new PackVersion(packFormat, 0);\n        }\n    }\n\n    public record PackVersion(int majorVersion, int minorVersion) implements Comparable<PackVersion> {\n\n        public static final PackVersion UNSPECIFIED = new PackVersion(0, 0);\n\n        @Override\n        public String toString() {\n            return minorVersion != 0 ? majorVersion + \".\" + minorVersion : String.valueOf(majorVersion);\n        }\n\n        @Override\n        public int compareTo(PackVersion other) {\n            int majorCompare = Integer.compare(this.majorVersion, other.majorVersion);\n            if (majorCompare != 0) {\n                return majorCompare;\n            }\n            return Integer.compare(this.minorVersion, other.minorVersion);\n        }\n\n        public boolean isUnspecified() {\n            return this.equals(UNSPECIFIED);\n        }\n\n        public static PackVersion fromJson(JsonElement element) throws JsonParseException {\n            if (element == null || element.isJsonNull()) {\n                return UNSPECIFIED;\n            }\n\n            try {\n                if (element instanceof JsonPrimitive primitive && primitive.isNumber()) {\n                    return new PackVersion(element.getAsInt(), 0);\n                } else if (element instanceof JsonArray jsonArray) {\n                    if (jsonArray.size() == 1 && jsonArray.get(0) instanceof JsonPrimitive) {\n                        return new PackVersion(jsonArray.get(0).getAsInt(), 0);\n                    } else if (jsonArray.size() == 2 && jsonArray.get(0) instanceof JsonPrimitive && jsonArray.get(1) instanceof JsonPrimitive) {\n                        return new PackVersion(jsonArray.get(0).getAsInt(), jsonArray.get(1).getAsInt());\n                    } else {\n                        LOG.warning(\"Datapack version array must have 1 or 2 elements, but got \" + jsonArray.size());\n                    }\n                }\n            } catch (NumberFormatException e) {\n                LOG.warning(\"Failed to parse datapack version component as a number. Value: \" + element, e);\n            }\n\n            return UNSPECIFIED;\n        }\n    }\n\n    public static final class PackInfoDeserializer implements JsonDeserializer<PackInfo> {\n\n        private List<LocalModFile.Description.Part> pairToPart(List<Pair<String, String>> lists, String color) {\n            List<LocalModFile.Description.Part> parts = new ArrayList<>();\n            for (Pair<String, String> list : lists) {\n                parts.add(new LocalModFile.Description.Part(list.getKey(), list.getValue().isEmpty() ? color : list.getValue()));\n            }\n            return parts;\n        }\n\n        private void parseComponent(JsonElement element, List<LocalModFile.Description.Part> parts, String parentColor) throws JsonParseException {\n            if (parentColor == null) {\n                parentColor = \"\";\n            }\n            String color = parentColor;\n            if (element instanceof JsonPrimitive primitive) {\n                parts.addAll(pairToPart(StringUtils.parseMinecraftColorCodes(primitive.getAsString()), color));\n            } else if (element instanceof JsonObject jsonObj) {\n                if (jsonObj.get(\"color\") instanceof JsonPrimitive primitive) {\n                    color = primitive.getAsString();\n                }\n                if (jsonObj.get(\"text\") instanceof JsonPrimitive primitive) {\n                    parts.addAll(pairToPart(StringUtils.parseMinecraftColorCodes(primitive.getAsString()), color));\n                }\n                if (jsonObj.get(\"extra\") instanceof JsonArray jsonArray) {\n                    parseComponent(jsonArray, parts, color);\n                }\n            } else if (element instanceof JsonArray jsonArray) {\n                if (!jsonArray.isEmpty() && jsonArray.get(0) instanceof JsonObject jsonObj && jsonObj.get(\"color\") instanceof JsonPrimitive primitive) {\n                    color = primitive.getAsString();\n                }\n\n                for (JsonElement childElement : jsonArray) {\n                    parseComponent(childElement, parts, color);\n                }\n            } else {\n                LOG.warning(\"Skipping unsupported element in description. Expected a string, object, or array, but got type \" + element.getClass().getSimpleName() + \". Value: \" + element);\n            }\n        }\n\n        private List<LocalModFile.Description.Part> parseDescription(JsonElement json) throws JsonParseException {\n            List<LocalModFile.Description.Part> parts = new ArrayList<>();\n\n            if (json == null || json.isJsonNull()) {\n                return parts;\n            }\n\n            try {\n                parseComponent(json, parts, \"\");\n            } catch (JsonParseException | IllegalStateException e) {\n                parts.clear();\n                LOG.warning(\"An unexpected error occurred while parsing a description component. The description may be incomplete.\", e);\n            }\n\n            return parts;\n        }\n\n        @Override\n        public PackInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            JsonObject packInfo = json.getAsJsonObject();\n            int packFormat;\n            if (packInfo.get(\"pack_format\") instanceof JsonPrimitive primitive && primitive.isNumber()) {\n                packFormat = primitive.getAsInt();\n            } else {\n                packFormat = 0;\n            }\n            PackVersion minVersion = PackVersion.fromJson(packInfo.get(\"min_format\"));\n            PackVersion maxVersion = PackVersion.fromJson(packInfo.get(\"max_format\"));\n\n            List<LocalModFile.Description.Part> parts = parseDescription(packInfo.get(\"description\"));\n            return new PackInfo(packFormat, minVersion, maxVersion, new LocalModFile.Description(parts));\n        }\n    }\n\n    public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {\n        ZipArchiveEntry mcmod = tree.getEntry(\"pack.mcmeta\");\n        if (mcmod == null)\n            throw new IOException(\"File \" + modFile + \" is not a resource pack.\");\n        PackMcMeta metadata = JsonUtils.fromNonNullJsonFully(tree.getInputStream(mcmod), PackMcMeta.class);\n        return new LocalModFile(\n                modManager,\n                modManager.getLocalMod(FileUtils.getNameWithoutExtension(modFile), ModLoaderType.PACK),\n                modFile,\n                FileUtils.getNameWithoutExtension(modFile),\n                metadata.pack.description,\n                \"\", \"\", \"\", \"\", \"\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java",
    "content": "package org.jackhuang.hmcl.mod.modinfo;\n\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParseException;\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\n\n@Immutable\npublic final class QuiltModMetadata {\n    private static final class QuiltLoader {\n        private static final class Metadata {\n            private final String name;\n            private final String description;\n            private final JsonObject contributors;\n            private final String icon;\n            private final JsonObject contact;\n\n            public Metadata(String name, String description, JsonObject contributors, String icon, JsonObject contact) {\n                this.name = name;\n                this.description = description;\n                this.contributors = contributors;\n                this.icon = icon;\n                this.contact = contact;\n            }\n        }\n\n        private final String id;\n        private final String version;\n        private final Metadata metadata;\n\n        public QuiltLoader(String id, String version, Metadata metadata) {\n            this.id = id;\n            this.version = version;\n            this.metadata = metadata;\n        }\n    }\n\n    private final int schema_version;\n    private final QuiltLoader quilt_loader;\n\n    public QuiltModMetadata(int schemaVersion, QuiltLoader quiltLoader) {\n        this.schema_version = schemaVersion;\n        this.quilt_loader = quiltLoader;\n    }\n\n    public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {\n        ZipArchiveEntry path = tree.getEntry(\"quilt.mod.json\");\n        if (path == null) {\n            throw new IOException(\"File \" + modFile + \" is not a Quilt mod.\");\n        }\n\n        QuiltModMetadata root = JsonUtils.fromNonNullJsonFully(tree.getInputStream(path), QuiltModMetadata.class);\n        if (root.schema_version != 1) {\n            throw new IOException(\"File \" + modFile + \" is not a supported Quilt mod.\");\n        }\n\n        return new LocalModFile(\n                modManager,\n                modManager.getLocalMod(root.quilt_loader.id, ModLoaderType.QUILT),\n                modFile,\n                root.quilt_loader.metadata.name,\n                new LocalModFile.Description(root.quilt_loader.metadata.description),\n                root.quilt_loader.metadata.contributors.entrySet().stream().map(entry -> String.format(\"%s (%s)\", entry.getKey(), entry.getValue().getAsJsonPrimitive().getAsString())).collect(Collectors.joining(\", \")),\n                root.quilt_loader.version,\n                \"\",\n                Optional.ofNullable(root.quilt_loader.metadata.contact.get(\"homepage\")).map(jsonElement -> jsonElement.getAsJsonPrimitive().getAsString()).orElse(\"\"),\n                root.quilt_loader.metadata.icon\n        );\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthCompletionTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modrinth;\n\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.mod.ModpackCompletionException;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class ModrinthCompletionTask extends Task<Void> {\n\n    private final DefaultDependencyManager dependency;\n    private final DefaultGameRepository repository;\n    private final ModManager modManager;\n    private final String version;\n    private ModrinthManifest manifest;\n    private final List<Task<?>> dependencies = new ArrayList<>();\n\n    private final AtomicBoolean allNameKnown = new AtomicBoolean(true);\n    private final AtomicInteger finished = new AtomicInteger(0);\n    private final AtomicBoolean notFound = new AtomicBoolean(false);\n\n    /**\n     * Constructor.\n     *\n     * @param dependencyManager the dependency manager.\n     * @param version           the existent and physical version.\n     */\n    public ModrinthCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        this(dependencyManager, version, null);\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param dependencyManager the dependency manager.\n     * @param version           the existent and physical version.\n     * @param manifest          the CurseForgeModpack manifest.\n     */\n    public ModrinthCompletionTask(DefaultDependencyManager dependencyManager, String version, ModrinthManifest manifest) {\n        this.dependency = dependencyManager;\n        this.repository = dependencyManager.getGameRepository();\n        this.modManager = repository.getModManager(version);\n        this.version = version;\n        this.manifest = manifest;\n\n        if (manifest == null)\n            try {\n                Path manifestFile = repository.getVersionRoot(version).resolve(\"modrinth.index.json\");\n                if (Files.exists(manifestFile))\n                    this.manifest = JsonUtils.fromJsonFile(manifestFile, ModrinthManifest.class);\n            } catch (Exception e) {\n                LOG.warning(\"Unable to read Modrinth modpack manifest.json\", e);\n            }\n\n        setStage(\"hmcl.modpack.download\");\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public boolean isRelyingOnDependencies() {\n        return false;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        if (manifest == null)\n            return;\n\n        Path runDirectory = FileUtils.toAbsolute(repository.getRunDirectory(version));\n        Path modsDirectory = runDirectory.resolve(\"mods\");\n\n        for (ModrinthManifest.File file : manifest.getFiles()) {\n            if (file.getEnv() != null && file.getEnv().getOrDefault(\"client\", \"required\").equals(\"unsupported\"))\n                continue;\n            if (file.getDownloads().isEmpty())\n                continue;\n\n            Path filePath = runDirectory.resolve(file.getPath()).toAbsolutePath().normalize();\n            if (!filePath.startsWith(runDirectory))\n                throw new IOException(\"Unsecure path: \" + file.getPath());\n\n            if (Files.exists(filePath))\n                continue;\n            if (modsDirectory.equals(filePath.getParent()) && this.modManager.hasSimpleMod(FileUtils.getName(filePath)))\n                continue;\n\n            var task = new FileDownloadTask(\n                    dependency.getDownloadProvider().injectURLsWithCandidates(file.getDownloads()),\n                    filePath);\n            task.setCacheRepository(dependency.getCacheRepository());\n            task.setCaching(true);\n            dependencies.add(task.withCounter(\"hmcl.modpack.download\"));\n        }\n\n        if (!dependencies.isEmpty()) {\n            getProperties().put(\"total\", dependencies.size());\n            notifyPropertiesChanged();\n        }\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        // Let this task fail if the curse manifest has not been completed.\n        // But continue other downloads.\n        if (notFound.get())\n            throw new ModpackCompletionException(new FileNotFoundException());\n        if (!allNameKnown.get() || !isDependenciesSucceeded())\n            throw new ModpackCompletionException();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modrinth;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.GameBuilder;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.*;\nimport org.jackhuang.hmcl.task.CacheFileTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class ModrinthInstallTask extends Task<Void> {\n    private static final Set<String> SUPPORTED_ICON_EXTS = Set.of(\"png\", \"jpg\", \"jpeg\", \"bmp\", \"gif\", \"webp\", \"apng\");\n\n    private final DefaultDependencyManager dependencyManager;\n    private final DefaultGameRepository repository;\n    private final Path zipFile;\n    private final Modpack modpack;\n    private final ModrinthManifest manifest;\n    private final String name;\n    private final String iconUrl;\n    private final Path run;\n    private final ModpackConfiguration<ModrinthManifest> config;\n    private String iconExt;\n    private Task<Path> downloadIconTask;\n    private final List<Task<?>> dependents = new ArrayList<>(4);\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n\n    public ModrinthInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, Modpack modpack, ModrinthManifest manifest, String name, String iconUrl) {\n        this.dependencyManager = dependencyManager;\n        this.zipFile = zipFile;\n        this.modpack = modpack;\n        this.manifest = manifest;\n        this.name = name;\n        this.iconUrl = iconUrl;\n        this.repository = dependencyManager.getGameRepository();\n        this.run = repository.getRunDirectory(name);\n\n        Path json = repository.getModpackConfiguration(name);\n        if (repository.hasVersion(name) && Files.notExists(json))\n            throw new IllegalArgumentException(\"Version \" + name + \" already exists.\");\n\n        GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion());\n        for (Map.Entry<String, String> modLoader : manifest.getDependencies().entrySet()) {\n            switch (modLoader.getKey()) {\n                case \"minecraft\":\n                    break;\n                case \"forge\":\n                    builder.version(\"forge\", modLoader.getValue());\n                    break;\n                case \"neoforge\":\n                // https://github.com/HMCL-dev/HMCL/pull/5170\n                case \"neo-forge\":\n                    builder.version(\"neoforge\", modLoader.getValue());\n                    break;\n                case \"fabric-loader\":\n                    builder.version(\"fabric\", modLoader.getValue());\n                    break;\n                case \"quilt-loader\":\n                    builder.version(\"quilt\", modLoader.getValue());\n                    break;\n                default:\n                    throw new IllegalStateException(\"Unsupported mod loader \" + modLoader.getKey());\n            }\n        }\n        dependents.add(builder.buildAsync());\n\n        onDone().register(event -> {\n            Exception ex = event.getTask().getException();\n            if (event.isFailed()) {\n                if (!(ex instanceof ModpackCompletionException)) {\n                    repository.removeVersionFromDisk(name);\n                }\n            }\n        });\n\n        ModpackConfiguration<ModrinthManifest> config = null;\n        try {\n            if (Files.exists(json)) {\n                config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(ModrinthManifest.class));\n\n                if (!ModrinthModpackProvider.INSTANCE.getName().equals(config.getType()))\n                    throw new IllegalArgumentException(\"Version \" + name + \" is not a Modrinth modpack. Cannot update this version.\");\n            }\n        } catch (JsonParseException | IOException ignore) {\n        }\n\n        this.config = config;\n        List<String> subDirectories = Arrays.asList(\"/client-overrides\", \"/overrides\");\n        dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), subDirectories, any -> true, config).withStage(\"hmcl.modpack\"));\n        dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), subDirectories, manifest, ModrinthModpackProvider.INSTANCE, manifest.getName(), manifest.getVersionId(), repository.getModpackConfiguration(name)).withStage(\"hmcl.modpack\"));\n\n        URI iconUri = NetworkUtils.toURIOrNull(iconUrl);\n        if (iconUri != null) {\n            String ext = FileUtils.getExtension(StringUtils.substringAfter(iconUri.getPath(), '/')).toLowerCase(Locale.ROOT);\n            if (SUPPORTED_ICON_EXTS.contains(ext)) {\n                iconExt = ext;\n\n                dependents.add(downloadIconTask = new CacheFileTask(dependencyManager.getDownloadProvider().injectURLWithCandidates(iconUrl)));\n            }\n        }\n        dependencies.add(new ModrinthCompletionTask(dependencyManager, name, manifest));\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        if (config != null) {\n            // For update, remove mods not listed in new manifest\n            for (ModrinthManifest.File oldManifestFile : config.getManifest().getFiles()) {\n                Path oldFile = run.resolve(oldManifestFile.getPath());\n                if (!Files.exists(oldFile)) continue;\n                if (manifest.getFiles().stream().noneMatch(oldManifestFile::equals)) {\n                    Files.deleteIfExists(oldFile);\n                }\n            }\n        }\n\n        Path root = repository.getVersionRoot(name);\n        Files.createDirectories(root);\n        JsonUtils.writeToJsonFile(root.resolve(\"modrinth.index.json\"), manifest);\n\n        if (iconExt != null) {\n            try {\n                Files.copy(downloadIconTask.getResult(), root.resolve(\"icon.\" + iconExt));\n            } catch (Exception e) {\n                LOG.warning(\"Failed to copy modpack icon\", e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthManifest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modrinth;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.mod.ModpackManifest;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\npublic class ModrinthManifest implements ModpackManifest, Validation {\n\n    private final String game;\n    private final int formatVersion;\n    private final String versionId;\n    private final String name;\n    private final @Nullable String summary;\n    private final List<File> files;\n    private final Map<String, String> dependencies;\n\n    public ModrinthManifest(String game, int formatVersion, String versionId, String name, @Nullable String summary, List<File> files, Map<String, String> dependencies) {\n        this.game = game;\n        this.formatVersion = formatVersion;\n        this.versionId = versionId;\n        this.name = name;\n        this.summary = summary;\n        this.files = files;\n        this.dependencies = dependencies;\n    }\n\n    public String getGame() {\n        return game;\n    }\n\n    public int getFormatVersion() {\n        return formatVersion;\n    }\n\n    public String getVersionId() {\n        return versionId;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getSummary() {\n        return summary == null ? \"\" : summary;\n    }\n\n    public List<File> getFiles() {\n        return files;\n    }\n\n    public Map<String, String> getDependencies() {\n        return dependencies;\n    }\n\n    public String getGameVersion() {\n        return dependencies.get(\"minecraft\");\n    }\n\n    @Override\n    public ModpackProvider getProvider() {\n        return ModrinthModpackProvider.INSTANCE;\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        if (dependencies == null || dependencies.get(\"minecraft\") == null) {\n            throw new JsonParseException(\"missing Modrinth.dependencies.minecraft\");\n        }\n    }\n\n    public static class File {\n        private final String path;\n        private final Map<String, String> hashes;\n        @Nullable\n        private final Map<String, String> env;\n        private final List<String> downloads;\n        private final int fileSize;\n\n        public File(String path, Map<String, String> hashes, @Nullable Map<String, String> env, List<String> downloads, int fileSize) {\n            this.path = path;\n            this.hashes = hashes;\n            this.env = env;\n            this.downloads = downloads;\n            this.fileSize = fileSize;\n        }\n\n        public String getPath() {\n            return path;\n        }\n\n        public Map<String, String> getHashes() {\n            return hashes;\n        }\n\n        @Nullable\n        public Map<String, String> getEnv() {\n            return env;\n        }\n\n        public List<String> getDownloads() {\n            return downloads;\n        }\n\n        public int getFileSize() {\n            return fileSize;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            File file = (File) o;\n            return fileSize == file.fileSize && path.equals(file.path) && hashes.equals(file.hashes) && Objects.equals(this.env, file.env) && downloads.equals(file.downloads);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(path, hashes, env, downloads, fileSize);\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthModpackExportTask.java",
    "content": "package org.jackhuang.hmcl.mod.modrinth;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\n\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.ModAdviser;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackExportInfo;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.Zipper;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class ModrinthModpackExportTask extends Task<Void> {\n    private final DefaultGameRepository repository;\n    private final String version;\n    private final ModpackExportInfo info;\n    private final Path modpackFile;\n\n    public ModrinthModpackExportTask(DefaultGameRepository repository, String version, ModpackExportInfo info, Path modpackFile) {\n        this.repository = repository;\n        this.version = version;\n        this.info = info.validate();\n        this.modpackFile = modpackFile;\n\n        onDone().register(event -> {\n            if (event.isFailed()) {\n                try {\n                    Files.deleteIfExists(modpackFile);\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to delete modpack file: \" + modpackFile, e);\n                }\n            }\n        });\n    }\n\n    private ModrinthManifest.File tryGetRemoteFile(Path file, String relativePath) throws IOException {\n        if (info.isNoCreateRemoteFiles()) {\n            return null;\n        }\n\n        boolean isDisabled = repository.getModManager(version).isDisabled(file);\n        if (isDisabled) {\n            relativePath = repository.getModManager(version).enableMod(Paths.get(relativePath)).toString();\n        }\n\n        LocalModFile localModFile = null;\n        Optional<RemoteMod.Version> modrinthVersion = Optional.empty();\n        Optional<RemoteMod.Version> curseForgeVersion = Optional.empty();\n\n        try {\n            modrinthVersion = ModrinthRemoteModRepository.MODS.getRemoteVersionByLocalFile(localModFile, file);\n        } catch (IOException e) {\n            LOG.warning(\"Failed to get remote file from Modrinth for: \" + file, e);\n        }\n\n        if (!info.isSkipCurseForgeRemoteFiles() && CurseForgeRemoteModRepository.isAvailable()) {\n            try {\n                curseForgeVersion = CurseForgeRemoteModRepository.MODS.getRemoteVersionByLocalFile(localModFile, file);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to get remote file from CurseForge for: \" + file, e);\n            }\n        }\n\n        if (modrinthVersion.isEmpty() && curseForgeVersion.isEmpty()) {\n            return null;\n        }\n\n        Map<String, String> hashes = new HashMap<>();\n        hashes.put(\"sha1\", DigestUtils.digestToString(\"SHA-1\", file));\n        hashes.put(\"sha512\", DigestUtils.digestToString(\"SHA-512\", file));\n\n        Map<String, String> env = null;\n        if (isDisabled) {\n            env = new HashMap<>();\n            env.put(\"client\", \"optional\");\n        }\n\n        List<String> downloads = new ArrayList<>();\n        if (modrinthVersion.isPresent())\n            downloads.add(modrinthVersion.get().getFile().getUrl());\n        if (curseForgeVersion.isPresent())\n            downloads.add(curseForgeVersion.get().getFile().getUrl());\n\n        long fileSize = Files.size(file);\n        if (fileSize > Integer.MAX_VALUE) {\n            LOG.warning(\"File \" + relativePath + \" is too large (size: \" + fileSize + \" bytes), precision may be lost when converting to int\");\n        }\n        return new ModrinthManifest.File(\n                relativePath,\n                hashes,\n                env,\n                downloads,\n                (int) fileSize\n        );\n    }\n\n    @Override\n    public void execute() throws Exception {\n        ArrayList<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);\n        blackList.add(version + \".jar\");\n        blackList.add(version + \".json\");\n        LOG.info(\"Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker\");\n        try (var zip = new Zipper(modpackFile)) {\n            Path runDirectory = repository.getRunDirectory(version);\n            List<ModrinthManifest.File> files = new ArrayList<>();\n            Set<String> filesInManifest = new HashSet<>();\n\n            String[] resourceDirs = {\"resourcepacks\", \"shaderpacks\", \"mods\"};\n            for (String dir : resourceDirs) {\n                Path dirPath = runDirectory.resolve(dir);\n                if (Files.exists(dirPath)) {\n                    Files.walk(dirPath)\n                            .filter(Files::isRegularFile)\n                            .forEach(file -> {\n                                try {\n                                    String relativePath = runDirectory.relativize(file).normalize().toString().replace(File.separatorChar, '/');\n\n                                    if (!info.getWhitelist().contains(relativePath)) {\n                                        return;\n                                    }\n\n                                    ModrinthManifest.File fileEntry = tryGetRemoteFile(file, relativePath);\n                                    if (fileEntry != null) {\n                                        files.add(fileEntry);\n                                        filesInManifest.add(relativePath);\n                                    }\n                                } catch (IOException e) {\n                                    LOG.warning(\"Failed to process file: \" + file, e);\n                                }\n                            });\n                }\n            }\n\n            zip.putDirectory(runDirectory, \"client-overrides\", path -> {\n                String relativePath = path.replace(File.separatorChar, '/');\n                if (filesInManifest.contains(relativePath)) {\n                    return false;\n                }\n                return Modpack.acceptFile(path, blackList, info.getWhitelist());\n            });\n\n            String gameVersion = repository.getGameVersion(version)\n                    .orElseThrow(() -> new IOException(\"Cannot parse the version of \" + version));\n            LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(version), gameVersion);\n\n            Map<String, String> dependencies = new HashMap<>();\n            dependencies.put(\"minecraft\", gameVersion);\n\n            analyzer.getVersion(FORGE).ifPresent(forgeVersion ->\n                    dependencies.put(\"forge\", forgeVersion));\n            analyzer.getVersion(NEO_FORGE).ifPresent(neoForgeVersion ->\n                    dependencies.put(\"neoforge\", neoForgeVersion));\n            analyzer.getVersion(FABRIC).ifPresent(fabricVersion ->\n                    dependencies.put(\"fabric-loader\", fabricVersion));\n            analyzer.getVersion(QUILT).ifPresent(quiltVersion ->\n                    dependencies.put(\"quilt-loader\", quiltVersion));\n\n            ModrinthManifest manifest = new ModrinthManifest(\n                    \"minecraft\",\n                    1,\n                    info.getVersion(),\n                    info.getName(),\n                    info.getDescription(),\n                    files,\n                    dependencies\n            );\n\n            zip.putTextFile(JsonUtils.GSON.toJson(manifest), \"modrinth.index.json\");\n        }\n    }\n\n    public static final ModpackExportInfo.Options OPTION = new ModpackExportInfo.Options()\n            .requireNoCreateRemoteFiles()\n            .requireSkipCurseForgeRemoteFiles();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthModpackProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modrinth;\n\nimport com.google.gson.JsonParseException;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.mod.MismatchedModpackTypeException;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.mod.ModpackUpdateTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\n\npublic final class ModrinthModpackProvider implements ModpackProvider {\n    public static final ModrinthModpackProvider INSTANCE = new ModrinthModpackProvider();\n\n    @Override\n    public String getName() {\n        return \"Modrinth\";\n    }\n\n    @Override\n    public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        return new ModrinthCompletionTask(dependencyManager, version);\n    }\n\n    @Override\n    public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException {\n        if (!(modpack.getManifest() instanceof ModrinthManifest modrinthManifest))\n            throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());\n\n        return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new ModrinthInstallTask(dependencyManager, zipFile, modpack, modrinthManifest, name, null));\n    }\n\n    @Override\n    public Modpack readManifest(ZipArchiveReader zip, Path file, Charset encoding) throws IOException, JsonParseException {\n        ModrinthManifest manifest = JsonUtils.fromNonNullJson(CompressingUtils.readTextZipEntry(zip, \"modrinth.index.json\"), ModrinthManifest.class);\n        return new Modpack(manifest.getName(), \"\", manifest.getVersionId(), manifest.getGameVersion(), manifest.getSummary(), encoding, manifest) {\n            @Override\n            public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {\n                return new ModrinthInstallTask(dependencyManager, zipFile, this, manifest, name, iconUrl);\n            }\n        };\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.modrinth;\n\nimport com.google.gson.annotations.SerializedName;\nimport com.google.gson.reflect.TypeToken;\nimport org.jackhuang.hmcl.download.DownloadProvider;\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.ModLoaderType;\nimport org.jackhuang.hmcl.mod.RemoteMod;\nimport org.jackhuang.hmcl.mod.RemoteModRepository;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.HttpRequest;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.io.ResponseCodeException;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.NoSuchFileException;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.Semaphore;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class ModrinthRemoteModRepository implements RemoteModRepository {\n    public static final ModrinthRemoteModRepository MODS = new ModrinthRemoteModRepository(\"mod\");\n    public static final ModrinthRemoteModRepository MODPACKS = new ModrinthRemoteModRepository(\"modpack\");\n    public static final ModrinthRemoteModRepository RESOURCE_PACKS = new ModrinthRemoteModRepository(\"resourcepack\");\n    public static final ModrinthRemoteModRepository SHADER_PACKS = new ModrinthRemoteModRepository(\"shader\");\n\n    private static final Semaphore SEMAPHORE = new Semaphore(16);\n\n    private static final String PREFIX = \"https://api.modrinth.com\";\n\n    private final String projectType;\n\n    private ModrinthRemoteModRepository(String projectType) {\n        this.projectType = projectType;\n    }\n\n    @Override\n    public Type getType() {\n        return Type.MOD;\n    }\n\n    private static String convertSortType(SortType sortType) {\n        switch (sortType) {\n            case DATE_CREATED:\n                return \"newest\";\n            case POPULARITY:\n            case NAME:\n            case AUTHOR:\n                return \"relevance\";\n            case LAST_UPDATED:\n                return \"updated\";\n            case TOTAL_DOWNLOADS:\n                return \"downloads\";\n            default:\n                throw new IllegalArgumentException(\"Unsupported sort type \" + sortType);\n        }\n    }\n\n    @Override\n    public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            List<List<String>> facets = new ArrayList<>();\n            facets.add(Collections.singletonList(\"project_type:\" + projectType));\n            if (StringUtils.isNotBlank(gameVersion)) {\n                facets.add(Collections.singletonList(\"versions:\" + gameVersion));\n            }\n            if (category != null && StringUtils.isNotBlank(category.getId())) {\n                facets.add(Collections.singletonList(\"categories:\" + category.getId()));\n            }\n            Map<String, String> query = mapOf(\n                    pair(\"query\", searchFilter),\n                    pair(\"facets\", JsonUtils.UGLY_GSON.toJson(facets)),\n                    pair(\"offset\", Integer.toString(pageOffset * pageSize)),\n                    pair(\"limit\", Integer.toString(pageSize)),\n                    pair(\"index\", convertSortType(sort))\n            );\n\n            List<URI> candidates = downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(PREFIX + \"/v2/search\", query));\n            IOException exception = null;\n            for (URI candidate : candidates) {\n                try {\n                    LOG.info(\"Fetching \" + candidate);\n                    Response<ProjectSearchResult> response = HttpRequest.GET(candidate.toString())\n                            .getJson(Response.typeOf(ProjectSearchResult.class));\n                    return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize));\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to search addons: \" + candidate, e);\n\n                    IOException wrapper = new IOException(\"Failed to search addons: \" + candidate, e);\n                    if (candidates.size() == 1) {\n                        exception = wrapper;\n                    } else {\n                        if (exception == null) {\n                            exception = new IOException(\"Failed to search addons\");\n                        }\n                        exception.addSuppressed(wrapper);\n                    }\n                }\n            }\n\n            throw exception != null ? exception : new IOException(\"No candidates found\");\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    @Override\n    public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {\n        String sha1 = DigestUtils.digestToString(\"SHA-1\", file);\n\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            ProjectVersion mod = HttpRequest.GET(PREFIX + \"/v2/version_file/\" + sha1,\n                            pair(\"algorithm\", \"sha1\"))\n                    .getJson(ProjectVersion.class);\n            return mod.toVersion();\n        } catch (ResponseCodeException e) {\n            if (e.getResponseCode() == 404) {\n                return Optional.empty();\n            } else {\n                throw e;\n            }\n        } catch (NoSuchFileException e) {\n            return Optional.empty();\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    @Override\n    public RemoteMod getModById(DownloadProvider downloadProvider, String id) throws IOException {\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            id = StringUtils.removePrefix(id, \"local-\");\n            List<URI> candidates = downloadProvider.injectURLWithCandidates(PREFIX + \"/v2/project/\" + id);\n            IOException exception = null;\n\n            for (URI candidate : candidates) {\n                try {\n                    Project project = HttpRequest.GET(candidate.toString()).getJson(Project.class);\n                    return project.toMod();\n                } catch (IOException e) {\n                    IOException wrapper = new IOException(\"Failed to get mod: \" + candidate, e);\n                    if (candidates.size() == 1) {\n                        exception = wrapper;\n                    } else {\n                        if (exception == null) {\n                            exception = new IOException(\"Failed to get mod\");\n                        }\n                        exception.addSuppressed(wrapper);\n                    }\n                }\n            }\n\n            throw exception != null ? exception : new IOException(\"No candidates found\");\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    @Override\n    public RemoteMod resolveDependency(DownloadProvider downloadProvider, String id) throws IOException {\n        try {\n            return getModById(downloadProvider, id);\n        } catch (ResponseCodeException e) {\n            if (e.getResponseCode() == 502 || e.getResponseCode() == 404) {\n                return RemoteMod.BROKEN;\n            }\n            throw e;\n        } catch (FileNotFoundException e) {\n            return RemoteMod.BROKEN;\n        }\n    }\n\n    @Override\n    public RemoteMod.File getModFile(String modId, String fileId) throws IOException {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Stream<RemoteMod.Version> getRemoteVersionsById(DownloadProvider downloadProvider, String id) throws IOException {\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            id = StringUtils.removePrefix(id, \"local-\");\n\n            List<URI> candidates = downloadProvider.injectURLWithCandidates(PREFIX + \"/v2/project/\" + id + \"/version?include_changelog=false\");\n            IOException exception = null;\n\n            for (URI candidate : candidates) {\n                try {\n                    List<ProjectVersion> versions = HttpRequest.GET(candidate.toString())\n                            .getJson(listTypeOf(ProjectVersion.class));\n                    return versions.stream().map(ProjectVersion::toVersion).flatMap(Lang::toStream);\n                } catch (IOException e) {\n                    IOException wrapper = new IOException(\"Failed to get remote versions: \" + candidate, e);\n                    if (candidates.size() == 1) {\n                        exception = wrapper;\n                    } else {\n                        if (exception == null) {\n                            exception = new IOException(\"Failed to get remote versions\");\n                        }\n                        exception.addSuppressed(wrapper);\n                    }\n                }\n            }\n\n            throw exception != null ? exception : new IOException(\"No candidates found\");\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    @Override\n    public Stream<RemoteModRepository.Category> getCategories() throws IOException {\n        SEMAPHORE.acquireUninterruptibly();\n        try {\n            List<Category> categories = HttpRequest.GET(PREFIX + \"/v2/tag/category\").getJson(listTypeOf(Category.class));\n            return categories.stream()\n                    .filter(category -> category.getProjectType().equals(projectType))\n                    .map(Category::toCategory);\n        } finally {\n            SEMAPHORE.release();\n        }\n    }\n\n    public static class Category {\n        private final String icon;\n\n        private final String name;\n\n        @SerializedName(\"project_type\")\n        private final String projectType;\n\n        public Category() {\n            this(\"\", \"\", \"\");\n        }\n\n        public Category(String icon, String name, String projectType) {\n            this.icon = icon;\n            this.name = name;\n            this.projectType = projectType;\n        }\n\n        public String getIcon() {\n            return icon;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public String getProjectType() {\n            return projectType;\n        }\n\n        public RemoteModRepository.Category toCategory() {\n            return new RemoteModRepository.Category(\n                    this,\n                    name,\n                    Collections.emptyList());\n        }\n    }\n\n    public static class Project implements RemoteMod.IMod {\n        private final String slug;\n\n        private final String title;\n\n        private final String description;\n\n        private final List<String> categories;\n\n        /**\n         * A long body describing project in detail.\n         */\n        private final String body;\n\n        @SerializedName(\"project_type\")\n        private final String projectType;\n\n        private final int downloads;\n\n        @SerializedName(\"icon_url\")\n        private final String iconUrl;\n\n        private final String id;\n\n        private final String team;\n\n        private final Instant published;\n\n        private final Instant updated;\n\n        private final List<String> versions;\n\n        public Project(String slug, String title, String description, List<String> categories, String body, String projectType, int downloads, String iconUrl, String id, String team, Instant published, Instant updated, List<String> versions) {\n            this.slug = slug;\n            this.title = title;\n            this.description = description;\n            this.categories = categories;\n            this.body = body;\n            this.projectType = projectType;\n            this.downloads = downloads;\n            this.iconUrl = iconUrl;\n            this.id = id;\n            this.team = team;\n            this.published = published;\n            this.updated = updated;\n            this.versions = versions;\n        }\n\n        public String getSlug() {\n            return slug;\n        }\n\n        public String getTitle() {\n            return title;\n        }\n\n        public String getDescription() {\n            return description;\n        }\n\n        public List<String> getCategories() {\n            return categories;\n        }\n\n        public String getBody() {\n            return body;\n        }\n\n        public String getProjectType() {\n            return projectType;\n        }\n\n        public int getDownloads() {\n            return downloads;\n        }\n\n        public String getIconUrl() {\n            return iconUrl;\n        }\n\n        public String getId() {\n            return id;\n        }\n\n        public String getTeam() {\n            return team;\n        }\n\n        public Instant getPublished() {\n            return published;\n        }\n\n        public Instant getUpdated() {\n            return updated;\n        }\n\n        public List<String> getVersions() {\n            return versions;\n        }\n\n        @Override\n        public List<RemoteMod> loadDependencies(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {\n            Set<RemoteMod.Dependency> dependencies = modRepository.getRemoteVersionsById(downloadProvider, getId())\n                    .flatMap(version -> version.getDependencies().stream())\n                    .collect(Collectors.toSet());\n            List<RemoteMod> mods = new ArrayList<>();\n            for (RemoteMod.Dependency dependency : dependencies) {\n                mods.add(dependency.load(downloadProvider));\n            }\n            return mods;\n        }\n\n        @Override\n        public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {\n            return modRepository.getRemoteVersionsById(downloadProvider, getId());\n        }\n\n        public RemoteMod toMod() {\n            return new RemoteMod(\n                    slug,\n                    \"\",\n                    title,\n                    description,\n                    categories,\n                    String.format(\"https://modrinth.com/%s/%s\", projectType, id),\n                    iconUrl,\n                    this\n            );\n        }\n    }\n\n    @Immutable\n    public static class Dependency {\n        @SerializedName(\"version_id\")\n        private final String versionId;\n\n        @SerializedName(\"project_id\")\n        private final String projectId;\n\n        @SerializedName(\"dependency_type\")\n        private final String dependencyType;\n\n        public Dependency(String versionId, String projectId, String dependencyType) {\n            this.versionId = versionId;\n            this.projectId = projectId;\n            this.dependencyType = dependencyType;\n        }\n\n        public String getVersionId() {\n            return versionId;\n        }\n\n        public String getProjectId() {\n            return projectId;\n        }\n\n        public String getDependencyType() {\n            return dependencyType;\n        }\n    }\n\n    public static class ProjectVersion implements RemoteMod.IVersion {\n        private static final Map<String, RemoteMod.@Nullable DependencyType> DEPENDENCY_TYPE = Lang.mapOf(\n                Pair.pair(\"required\", RemoteMod.DependencyType.REQUIRED),\n                Pair.pair(\"optional\", RemoteMod.DependencyType.OPTIONAL),\n                Pair.pair(\"embedded\", RemoteMod.DependencyType.EMBEDDED),\n                Pair.pair(\"incompatible\", RemoteMod.DependencyType.INCOMPATIBLE)\n        );\n\n        private final String name;\n\n        @SerializedName(\"version_number\")\n        private final String versionNumber;\n\n        private final String changelog;\n\n        private final List<Dependency> dependencies;\n\n        @SerializedName(\"game_versions\")\n        private final List<String> gameVersions;\n\n        @SerializedName(\"version_type\")\n        private final String versionType;\n\n        private final List<String> loaders;\n\n        private final boolean featured;\n\n        private final String id;\n\n        @SerializedName(\"project_id\")\n        private final String projectId;\n\n        @SerializedName(\"author_id\")\n        private final String authorId;\n\n        @SerializedName(\"date_published\")\n        private final Instant datePublished;\n\n        private final int downloads;\n\n        @SerializedName(\"changelog_url\")\n        private final String changelogUrl;\n\n        private final List<ProjectVersionFile> files;\n\n        public ProjectVersion(String name, String versionNumber, String changelog, List<Dependency> dependencies, List<String> gameVersions, String versionType, List<String> loaders, boolean featured, String id, String projectId, String authorId, Instant datePublished, int downloads, String changelogUrl, List<ProjectVersionFile> files) {\n            this.name = name;\n            this.versionNumber = versionNumber;\n            this.changelog = changelog;\n            this.dependencies = dependencies;\n            this.gameVersions = gameVersions;\n            this.versionType = versionType;\n            this.loaders = loaders;\n            this.featured = featured;\n            this.id = id;\n            this.projectId = projectId;\n            this.authorId = authorId;\n            this.datePublished = datePublished;\n            this.downloads = downloads;\n            this.changelogUrl = changelogUrl;\n            this.files = files;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public String getVersionNumber() {\n            return versionNumber;\n        }\n\n        public String getChangelog() {\n            return changelog;\n        }\n\n        public List<Dependency> getDependencies() {\n            return dependencies;\n        }\n\n        public List<String> getGameVersions() {\n            return gameVersions;\n        }\n\n        public String getVersionType() {\n            return versionType;\n        }\n\n        public List<String> getLoaders() {\n            return loaders;\n        }\n\n        public boolean isFeatured() {\n            return featured;\n        }\n\n        public String getId() {\n            return id;\n        }\n\n        public String getProjectId() {\n            return projectId;\n        }\n\n        public String getAuthorId() {\n            return authorId;\n        }\n\n        public Instant getDatePublished() {\n            return datePublished;\n        }\n\n        public int getDownloads() {\n            return downloads;\n        }\n\n        public String getChangelogUrl() {\n            return changelogUrl;\n        }\n\n        public List<ProjectVersionFile> getFiles() {\n            return files;\n        }\n\n        @Override\n        public RemoteMod.Type getType() {\n            return RemoteMod.Type.MODRINTH;\n        }\n\n        public Optional<RemoteMod.Version> toVersion() {\n            RemoteMod.VersionType type;\n            if (\"release\".equals(versionType)) {\n                type = RemoteMod.VersionType.Release;\n            } else if (\"beta\".equals(versionType)) {\n                type = RemoteMod.VersionType.Beta;\n            } else if (\"alpha\".equals(versionType)) {\n                type = RemoteMod.VersionType.Alpha;\n            } else {\n                type = RemoteMod.VersionType.Release;\n            }\n\n            if (files.size() == 0) {\n                return Optional.empty();\n            }\n\n            return Optional.of(new RemoteMod.Version(\n                    this,\n                    projectId,\n                    name,\n                    versionNumber,\n                    changelog,\n                    datePublished,\n                    type,\n                    files.get(0).toFile(),\n                    dependencies.stream().map(dependency -> {\n                        if (dependency.projectId == null) {\n                            return RemoteMod.Dependency.ofBroken();\n                        }\n\n                        if (!DEPENDENCY_TYPE.containsKey(dependency.dependencyType)) {\n                            throw new IllegalStateException(\"Broken datas\");\n                        }\n\n                        return RemoteMod.Dependency.ofGeneral(DEPENDENCY_TYPE.get(dependency.dependencyType), MODS, dependency.projectId);\n                    }).filter(Objects::nonNull).collect(Collectors.toList()),\n                    gameVersions,\n                    loaders.stream().flatMap(loader -> {\n                        if (\"fabric\".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FABRIC);\n                        else if (\"forge\".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FORGE);\n                        else if (\"neoforge\".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.NEO_FORGED);\n                        else if (\"quilt\".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.QUILT);\n                        else if (\"liteloader\".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.LITE_LOADER);\n                        else return Stream.empty();\n                    }).collect(Collectors.toList())\n            ));\n        }\n    }\n\n    public static class ProjectVersionFile {\n        private final Map<String, String> hashes;\n        private final String url;\n        private final String filename;\n        private final boolean primary;\n        private final int size;\n\n        public ProjectVersionFile(Map<String, String> hashes, String url, String filename, boolean primary, int size) {\n            this.hashes = hashes;\n            this.url = url;\n            this.filename = filename;\n            this.primary = primary;\n            this.size = size;\n        }\n\n        public Map<String, String> getHashes() {\n            return hashes;\n        }\n\n        public String getUrl() {\n            return url;\n        }\n\n        public String getFilename() {\n            return filename;\n        }\n\n        public boolean isPrimary() {\n            return primary;\n        }\n\n        public int getSize() {\n            return size;\n        }\n\n        public RemoteMod.File toFile() {\n            return new RemoteMod.File(hashes, url, filename);\n        }\n    }\n\n    public static class ProjectSearchResult implements RemoteMod.IMod {\n        private final String slug;\n\n        private final String title;\n\n        private final String description;\n\n        private final List<String> categories;\n\n        @SerializedName(\"display_categories\")\n        private final List<String> displayCategories;\n\n        @SerializedName(\"project_type\")\n        private final String projectType;\n\n        private final int downloads;\n\n        @SerializedName(\"icon_url\")\n        private final String iconUrl;\n\n        @SerializedName(\"project_id\")\n        private final String projectId;\n\n        private final String author;\n\n        private final List<String> versions;\n\n        @SerializedName(\"date_created\")\n        private final Instant dateCreated;\n\n        @SerializedName(\"date_modified\")\n        private final Instant dateModified;\n\n        @SerializedName(\"latest_version\")\n        private final String latestVersion;\n\n        public ProjectSearchResult(String slug, String title, String description, List<String> categories, List<String> displayCategories, String projectType, int downloads, String iconUrl, String projectId, String author, List<String> versions, Instant dateCreated, Instant dateModified, String latestVersion) {\n            this.slug = slug;\n            this.title = title;\n            this.description = description;\n            this.categories = categories;\n            this.displayCategories = displayCategories;\n            this.projectType = projectType;\n            this.downloads = downloads;\n            this.iconUrl = iconUrl;\n            this.projectId = projectId;\n            this.author = author;\n            this.versions = versions;\n            this.dateCreated = dateCreated;\n            this.dateModified = dateModified;\n            this.latestVersion = latestVersion;\n        }\n\n        public String getSlug() {\n            return slug;\n        }\n\n        public String getTitle() {\n            return title;\n        }\n\n        public String getDescription() {\n            return description;\n        }\n\n        public List<String> getCategories() {\n            return categories;\n        }\n\n        public List<String> getDisplayCategories() {\n            return displayCategories;\n        }\n\n        public String getProjectType() {\n            return projectType;\n        }\n\n        public int getDownloads() {\n            return downloads;\n        }\n\n        public String getIconUrl() {\n            return iconUrl;\n        }\n\n        public String getProjectId() {\n            return projectId;\n        }\n\n        public String getAuthor() {\n            return author;\n        }\n\n        public List<String> getVersions() {\n            return versions;\n        }\n\n        public Instant getDateCreated() {\n            return dateCreated;\n        }\n\n        public Instant getDateModified() {\n            return dateModified;\n        }\n\n        public String getLatestVersion() {\n            return latestVersion;\n        }\n\n        @Override\n        public List<RemoteMod> loadDependencies(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {\n            Set<RemoteMod.Dependency> dependencies = modRepository.getRemoteVersionsById(downloadProvider, getProjectId())\n                    .flatMap(version -> version.getDependencies().stream())\n                    .collect(Collectors.toSet());\n            List<RemoteMod> mods = new ArrayList<>();\n            for (RemoteMod.Dependency dependency : dependencies) {\n                mods.add(dependency.load(downloadProvider));\n            }\n            return mods;\n        }\n\n        @Override\n        public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {\n            return modRepository.getRemoteVersionsById(downloadProvider, getProjectId());\n        }\n\n        public RemoteMod toMod() {\n            return new RemoteMod(\n                    slug,\n                    author,\n                    title,\n                    description,\n                    displayCategories,\n                    String.format(\"https://modrinth.com/%s/%s\", projectType, projectId),\n                    iconUrl,\n                    this\n            );\n        }\n    }\n\n    public static class Response<T> {\n\n        @SuppressWarnings(\"unchecked\")\n        public static <T> TypeToken<Response<T>> typeOf(Class<T> responseType) {\n            return (TypeToken<Response<T>>) TypeToken.getParameterized(Response.class, responseType);\n        }\n\n        private final int offset;\n\n        private final int limit;\n\n        @SerializedName(\"total_hits\")\n        private final int totalHits;\n\n        private final List<T> hits;\n\n        public Response() {\n            this(0, 0, Collections.emptyList());\n        }\n\n        public Response(int offset, int limit, List<T> hits) {\n            this.offset = offset;\n            this.limit = limit;\n            this.totalHits = hits.size();\n            this.hits = hits;\n        }\n\n        public int getOffset() {\n            return offset;\n        }\n\n        public int getLimit() {\n            return limit;\n        }\n\n        public int getTotalHits() {\n            return totalHits;\n        }\n\n        public List<T> getHits() {\n            return hits;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java",
    "content": "package org.jackhuang.hmcl.mod.multimc;\n\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\n\npublic final class MultiMCComponents {\n\n    private MultiMCComponents() {\n    }\n\n    private static final Map<String, String> INSTALLER_PROFILE = new ConcurrentHashMap<>();\n\n    static {\n        // Please append a phrase below while fixing bugs or implementing new features for Instance Format transformer\n        INSTALLER_PROFILE.put(\"Patches\", \"recursive install, fabric & quilt intermediary\");\n\n        // Check whether MultiMCComponents is 'org.jackhuang.hmcl.mod.multimc.MultiMCComponents'.\n        // We use a base64-encoded value here to prevent string literals from being replaced by IDE if users trigger the 'Refactor' feature.\n        if (new String(\n                Base64.getDecoder().decode(\"b3JnLmphY2todWFuZy5obWNsLm1vZC5tdWx0aW1jLk11bHRpTUNDb21wb25lbnRz\"),\n                StandardCharsets.UTF_8\n        ).equals(MultiMCComponents.class.getName())) {\n            INSTALLER_PROFILE.put(\"Implementation\", \"Probably vanilla. Class location is not modified (org.jackhuang.hmcl.mod.multimc.MultiMCComponents).\");\n        } else {\n            INSTALLER_PROFILE.put(\"Implementation\", \"Not vanilla. Class location is \" + MultiMCComponents.class.getName());\n        }\n    }\n\n    public static void setImplementation(String implementation) {\n        INSTALLER_PROFILE.put(\"Implementation\", implementation);\n    }\n\n    public static String getInstallerProfile() {\n        StringBuilder builder = new StringBuilder();\n        for (Map.Entry<String, String> entry : INSTALLER_PROFILE.entrySet()) {\n            builder.append(entry.getKey()).append(\": \").append(entry.getValue()).append(\"\\n\");\n        }\n\n        if (builder.length() != 0) {\n            builder.setLength(builder.length() - 1);\n        }\n\n        return builder.toString();\n    }\n\n    private static final Map<String, LibraryAnalyzer.LibraryType> ID_TYPE = new HashMap<>();\n\n    static {\n        ID_TYPE.put(\"net.minecraft\", LibraryAnalyzer.LibraryType.MINECRAFT);\n        ID_TYPE.put(\"net.minecraftforge\", LibraryAnalyzer.LibraryType.FORGE);\n        ID_TYPE.put(\"net.neoforged\", LibraryAnalyzer.LibraryType.NEO_FORGE);\n        ID_TYPE.put(\"com.mumfrey.liteloader\", LibraryAnalyzer.LibraryType.LITELOADER);\n        ID_TYPE.put(\"net.fabricmc.fabric-loader\", LibraryAnalyzer.LibraryType.FABRIC);\n        ID_TYPE.put(\"org.quiltmc.quilt-loader\", LibraryAnalyzer.LibraryType.QUILT);\n    }\n\n    private static final Map<LibraryAnalyzer.LibraryType, String> TYPE_ID =\n            ID_TYPE.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));\n\n    private static final Collection<Map.Entry<String, LibraryAnalyzer.LibraryType>> PAIRS = Collections.unmodifiableCollection(ID_TYPE.entrySet());\n\n    static {\n        if (TYPE_ID.isEmpty()) {\n            throw new AssertionError(\"Please make sure TYPE_ID and PAIRS is initialized after ID_TYPE!\");\n        }\n    }\n\n    public static String getComponent(LibraryAnalyzer.LibraryType type) {\n        return TYPE_ID.get(type);\n    }\n\n    public static LibraryAnalyzer.LibraryType getComponent(String type) {\n        return ID_TYPE.get(type);\n    }\n\n    public static Collection<Map.Entry<String, LibraryAnalyzer.LibraryType>> getPairs() {\n        return PAIRS;\n    }\n\n    public static URI getMetaURL(String componentID, String version, String mcVersion) {\n        if (version == null) {\n            switch (componentID) {\n                case \"org.lwjgl\": {\n                    version = \"2.9.1\";\n                    break;\n                }\n                case \"org.lwjgl3\": {\n                    version = \"3.1.2\";\n                    break;\n                }\n                case \"net.fabricmc.intermediary\":\n                case \"org.quiltmc.hashed\": {\n                    version = mcVersion;\n                    break;\n                }\n            }\n        }\n\n        return NetworkUtils.toURI(String.format(\"https://meta.multimc.org/v1/%s/%s.json\", componentID, version));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstanceConfiguration.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.multimc;\n\nimport org.jackhuang.hmcl.mod.ModpackManifest;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.util.Lang;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Optional;\nimport java.util.Properties;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class MultiMCInstanceConfiguration implements ModpackManifest {\n\n    private final String instanceType; // InstanceType\n    private final String name; // name\n    private final String gameVersion; // IntendedVersion\n    private final Integer permGen; // PermGen\n    private final String wrapperCommand; // WrapperCommand\n    private final String preLaunchCommand; // PreLaunchCommand\n    private final String postExitCommand; // PostExitCommand\n    private final String notes; // notes\n    private final String javaPath; // JavaPath\n    private final String jvmArgs; // JvmArgs\n    private final boolean fullscreen; // LaunchMaximized\n    private final Integer width; // MinecraftWinWidth\n    private final Integer height; // MinecraftWinHeight\n    private final Integer maxMemory; // MaxMemAlloc\n    private final Integer minMemory; // MinMemAlloc\n    private final boolean showConsole; // ShowConsole\n    private final boolean showConsoleOnError; // ShowConsoleOnError\n    private final boolean autoCloseConsole; // AutoCloseConsole\n    private final boolean overrideMemory; // OverrideMemory\n    private final boolean overrideJavaLocation; // OverrideJavaLocation\n    private final boolean overrideJavaArgs; // OverrideJavaArgs\n    private final boolean overrideConsole; // OverrideConsole\n    private final boolean overrideCommands; // OverrideCommands\n    private final boolean overrideWindow; // OverrideWindow\n    private final String iconKey;\n\n    private final MultiMCManifest mmcPack;\n\n    MultiMCInstanceConfiguration(String defaultName, InputStream contentStream, MultiMCManifest mmcPack) throws IOException {\n        // instance.cfg is in .ini format. As it's like the .properties format, we use Properties to parse this file.\n        // Maybe this will provide some problems (and it does, such as https://github.com/HMCL-dev/HMCL/issues/2991), but this workaround works, doesn't it?\n        Properties p = new Properties();\n        p.load(new InputStreamReader(contentStream, StandardCharsets.UTF_8));\n\n        this.mmcPack = mmcPack;\n\n        instanceType = readValue(p, \"InstanceType\");\n        autoCloseConsole = Boolean.parseBoolean(readValue(p, \"AutoCloseConsole\"));\n        gameVersion = mmcPack != null ? mmcPack.getComponents().stream().filter(e -> \"net.minecraft\".equals(e.getUid())).findAny()\n                .orElseThrow(() -> new IOException(\"Malformed mmc-pack.json\")).getVersion() : readValue(p, \"IntendedVersion\");\n        javaPath = readValue(p, \"JavaPath\");\n        jvmArgs = readValue(p, \"JvmArgs\");\n        fullscreen = Boolean.parseBoolean(readValue(p, \"LaunchMaximized\"));\n        maxMemory = Lang.toIntOrNull(readValue(p, \"MaxMemAlloc\"));\n        minMemory = Lang.toIntOrNull(readValue(p, \"MinMemAlloc\"));\n        height = Lang.toIntOrNull(readValue(p, \"MinecraftWinHeight\"));\n        width = Lang.toIntOrNull(readValue(p, \"MinecraftWinWidth\"));\n        overrideCommands = Boolean.parseBoolean(readValue(p, \"OverrideCommands\"));\n        overrideConsole = Boolean.parseBoolean(readValue(p, \"OverrideConsole\"));\n        overrideJavaArgs = Boolean.parseBoolean(readValue(p, \"OverrideJavaArgs\"));\n        overrideJavaLocation = Boolean.parseBoolean(readValue(p, \"OverrideJavaLocation\"));\n        overrideMemory = Boolean.parseBoolean(readValue(p, \"OverrideMemory\"));\n        overrideWindow = Boolean.parseBoolean(readValue(p, \"OverrideWindow\"));\n        permGen = Lang.toIntOrNull(readValue(p, \"PermGen\"));\n        postExitCommand = readValue(p, \"PostExitCommand\");\n        preLaunchCommand = readValue(p, \"PreLaunchCommand\");\n        showConsole = Boolean.parseBoolean(readValue(p, \"ShowConsole\"));\n        showConsoleOnError = Boolean.parseBoolean(readValue(p, \"ShowConsoleOnError\"));\n        wrapperCommand = readValue(p, \"WrapperCommand\");\n        name = defaultName;\n        notes = Optional.ofNullable(readValue(p, \"notes\")).orElse(\"\");\n        iconKey = readValue(p, \"iconKey\");\n    }\n\n    /**\n     * A workaround for <a href=\"https://github.com/HMCL-dev/HMCL/issues/2991\">...</a>\n     */\n    private String readValue(Properties p, String key) {\n        String value = p.getProperty(key);\n        if (value == null) {\n            return null;\n        }\n\n        int l = value.length();\n        if (l >= 2 && value.charAt(0) == '\"' && value.charAt(l - 1) == ':') {\n            return value.substring(0, l - 1);\n        }\n        return value;\n    }\n\n    public MultiMCInstanceConfiguration(String instanceType, String name, String gameVersion, Integer permGen, String wrapperCommand, String preLaunchCommand, String postExitCommand, String notes, String javaPath, String jvmArgs, boolean fullscreen, Integer width, Integer height, Integer maxMemory, Integer minMemory, boolean showConsole, boolean showConsoleOnError, boolean autoCloseConsole, boolean overrideMemory, boolean overrideJavaLocation, boolean overrideJavaArgs, boolean overrideConsole, boolean overrideCommands, boolean overrideWindow, String iconKey) {\n        this.instanceType = instanceType;\n        this.name = name;\n        this.gameVersion = gameVersion;\n        this.permGen = permGen;\n        this.wrapperCommand = wrapperCommand;\n        this.preLaunchCommand = preLaunchCommand;\n        this.postExitCommand = postExitCommand;\n        this.notes = notes;\n        this.javaPath = javaPath;\n        this.jvmArgs = jvmArgs;\n        this.fullscreen = fullscreen;\n        this.width = width;\n        this.height = height;\n        this.maxMemory = maxMemory;\n        this.minMemory = minMemory;\n        this.showConsole = showConsole;\n        this.showConsoleOnError = showConsoleOnError;\n        this.autoCloseConsole = autoCloseConsole;\n        this.overrideMemory = overrideMemory;\n        this.overrideJavaLocation = overrideJavaLocation;\n        this.overrideJavaArgs = overrideJavaArgs;\n        this.overrideConsole = overrideConsole;\n        this.overrideCommands = overrideCommands;\n        this.overrideWindow = overrideWindow;\n        this.mmcPack = null;\n        this.iconKey = iconKey;\n    }\n\n    public String getInstanceType() {\n        return instanceType;\n    }\n\n    /**\n     * The instance's name.\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * The game version of the instance.\n     */\n    public String getGameVersion() {\n        return gameVersion;\n    }\n\n    /**\n     * The permanent generation size of JVM.\n     * Nullable.\n     */\n    public Integer getPermGen() {\n        return permGen;\n    }\n\n    /**\n     * The command to launch JVM.\n     */\n    public String getWrapperCommand() {\n        return wrapperCommand;\n    }\n\n    /**\n     * The command that will be executed before game launches.\n     */\n    public String getPreLaunchCommand() {\n        return preLaunchCommand;\n    }\n\n    /**\n     * The command that will be executed after game exits.\n     */\n    public String getPostExitCommand() {\n        return postExitCommand;\n    }\n\n    /**\n     * The description of the instance.\n     */\n    public String getNotes() {\n        return notes;\n    }\n\n    /**\n     * JVM installation location.\n     */\n    public String getJavaPath() {\n        return javaPath;\n    }\n\n    /**\n     * The JVM arguments\n     */\n    public String getJvmArgs() {\n        return jvmArgs;\n    }\n\n    /**\n     * True if Minecraft will start in fullscreen mode.\n     */\n    public boolean isFullscreen() {\n        return fullscreen;\n    }\n\n    /**\n     * The initial width of the game window.\n     * Nullable.\n     */\n    public Integer getWidth() {\n        return width;\n    }\n\n    /**\n     * The initial height of the game window.\n     * Nullable.\n     */\n    public Integer getHeight() {\n        return height;\n    }\n\n    /**\n     * The maximum memory that JVM can allocate.\n     * Nullable.\n     */\n    public Integer getMaxMemory() {\n        return maxMemory;\n    }\n\n    /**\n     * The minimum memory that JVM can allocate.\n     * Nullable.\n     */\n    public Integer getMinMemory() {\n        return minMemory;\n    }\n\n    /**\n     * True if show the console window when game launches.\n     */\n    public boolean isShowConsole() {\n        return showConsole;\n    }\n\n    /**\n     * True if show the console window when game crashes.\n     */\n    public boolean isShowConsoleOnError() {\n        return showConsoleOnError;\n    }\n\n    /**\n     * True if closes the console window when game stops.\n     */\n    public boolean isAutoCloseConsole() {\n        return autoCloseConsole;\n    }\n\n    /**\n     * True if {@link #getMaxMemory}, {@link #getMinMemory}, {@link #getPermGen} will come info force.\n     */\n    public boolean isOverrideMemory() {\n        return overrideMemory;\n    }\n\n    /**\n     * True if {@link #getJavaPath} will come info force.\n     */\n    public boolean isOverrideJavaLocation() {\n        return overrideJavaLocation;\n    }\n\n    /**\n     * True if {@link #getJvmArgs} will come info force.\n     */\n    public boolean isOverrideJavaArgs() {\n        return overrideJavaArgs;\n    }\n\n    /**\n     * True if {@link #isShowConsole}, {@link #isShowConsoleOnError}, {@link #isAutoCloseConsole} will come into force.\n     */\n    public boolean isOverrideConsole() {\n        return overrideConsole;\n    }\n\n    /**\n     * True if {@link #getPreLaunchCommand}, {@link #getPostExitCommand}, {@link #getWrapperCommand} will come into force.\n     */\n    public boolean isOverrideCommands() {\n        return overrideCommands;\n    }\n\n    /**\n     * True if {@link #getHeight}, {@link #getWidth}, {@link #isFullscreen} will come into force.\n     */\n    public boolean isOverrideWindow() {\n        return overrideWindow;\n    }\n\n    public String getIconKey() {\n        return iconKey;\n    }\n\n    public Properties toProperties() {\n        Properties p = new Properties();\n        if (instanceType != null) p.setProperty(\"InstanceType\", instanceType);\n        p.setProperty(\"AutoCloseConsole\", Boolean.toString(autoCloseConsole));\n        if (gameVersion != null) p.setProperty(\"IntendedVersion\", gameVersion);\n        if (javaPath != null) p.setProperty(\"JavaPath\", javaPath);\n        if (jvmArgs != null) p.setProperty(\"JvmArgs\", jvmArgs);\n        p.setProperty(\"LaunchMaximized\", Boolean.toString(fullscreen));\n        if (maxMemory != null) p.setProperty(\"MaxMemAlloc\", Integer.toString(maxMemory));\n        if (minMemory != null) p.setProperty(\"MinMemAlloc\", Integer.toString(minMemory));\n        if (height != null) p.setProperty(\"MinecraftWinHeight\", Integer.toString(height));\n        if (width != null) p.setProperty(\"MinecraftWinWidth\", Integer.toString(width));\n        p.setProperty(\"OverrideCommands\", Boolean.toString(overrideCommands));\n        p.setProperty(\"OverrideConsole\", Boolean.toString(overrideConsole));\n        p.setProperty(\"OverrideJavaArgs\", Boolean.toString(overrideJavaArgs));\n        p.setProperty(\"OverrideJavaLocation\", Boolean.toString(overrideJavaLocation));\n        p.setProperty(\"OverrideMemory\", Boolean.toString(overrideMemory));\n        p.setProperty(\"OverrideWindow\", Boolean.toString(overrideWindow));\n        if (permGen != null) p.setProperty(\"PermGen\", Integer.toString(permGen));\n        if (postExitCommand != null) p.setProperty(\"PostExitCommand\", postExitCommand);\n        if (preLaunchCommand != null) p.setProperty(\"PreLaunchCommand\", preLaunchCommand);\n        p.setProperty(\"ShowConsole\", Boolean.toString(showConsole));\n        p.setProperty(\"ShowConsoleOnError\", Boolean.toString(showConsoleOnError));\n        if (wrapperCommand != null) p.setProperty(\"WrapperCommand\", wrapperCommand);\n        if (name != null) p.setProperty(\"name\", name);\n        if (notes != null) p.setProperty(\"notes\", notes);\n        if (iconKey != null) p.setProperty(\"iconKey\", iconKey);\n        return p;\n    }\n\n    public MultiMCManifest getMmcPack() {\n        return mmcPack;\n    }\n\n    @Override\n    public ModpackProvider getProvider() {\n        return MultiMCModpackProvider.INSTANCE;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.multimc;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.Argument;\nimport org.jackhuang.hmcl.game.Arguments;\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.game.AssetIndexInfo;\nimport org.jackhuang.hmcl.game.CompatibilityRule;\nimport org.jackhuang.hmcl.game.DownloadType;\nimport org.jackhuang.hmcl.game.GameJavaVersion;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.OSRestriction;\nimport org.jackhuang.hmcl.game.RuledArgument;\nimport org.jackhuang.hmcl.game.StringArgument;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonMap;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.logging.Logger;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * @author huangyuhui\n */\n@Immutable\npublic final class MultiMCInstancePatch {\n    public static final Library BOOTSTRAP_LIBRARY = new Library(new Artifact(\"org.jackhuang.hmcl\", \"mmc-bootstrap\", \"1.0\"));\n\n    private final int formatVersion;\n\n    @SerializedName(\"uid\")\n    private final String id;\n\n    @SerializedName(\"version\")\n    private final String version;\n\n    @SerializedName(\"assetIndex\")\n    private final AssetIndexInfo assetIndex;\n\n    @SerializedName(\"minecraftArguments\")\n    private final String minecraftArguments;\n\n    @SerializedName(\"+jvmArgs\")\n    private final List<String> jvmArgs;\n\n    @SerializedName(\"mainClass\")\n    private final String mainClass;\n\n    @SerializedName(\"compatibleJavaMajors\")\n    private final int[] javaMajors;\n\n    @SerializedName(\"mainJar\")\n    private final Library mainJar;\n\n    @SerializedName(\"+traits\")\n    private final List<String> traits;\n\n    @SerializedName(\"+tweakers\")\n    private final List<String> tweakers;\n\n    @SerializedName(value = \"+libraries\")\n    private final List<Library> libraries0;\n    @SerializedName(value = \"libraries\")\n    private final List<Library> libraries1;\n    @SerializedName(value = \"mavenFiles\")\n    private final List<Library> mavenFiles;\n\n    @SerializedName(\"jarMods\")\n    private final List<Library> jarMods;\n\n    @SerializedName(\"requires\")\n    private final List<MultiMCManifest.MultiMCManifestCachedRequires> requires;\n\n    public MultiMCInstancePatch(int formatVersion, String id, String version, AssetIndexInfo assetIndex, String minecraftArguments, List<String> jvmArgs, String mainClass, int[] javaMajors, Library mainJar, List<String> traits, List<String> tweakers, List<Library> libraries0, List<Library> libraries1, List<Library> mavenFiles, List<Library> jarMods, List<MultiMCManifest.MultiMCManifestCachedRequires> requires) {\n        this.formatVersion = formatVersion;\n        this.id = id;\n        this.version = version;\n        this.assetIndex = assetIndex;\n        this.minecraftArguments = minecraftArguments;\n        this.jvmArgs = jvmArgs;\n        this.mainClass = mainClass;\n        this.javaMajors = javaMajors;\n        this.mainJar = mainJar;\n        this.traits = traits;\n        this.tweakers = tweakers;\n        this.libraries0 = libraries0;\n        this.libraries1 = libraries1;\n        this.mavenFiles = mavenFiles;\n        this.jarMods = jarMods;\n        this.requires = requires;\n    }\n\n    public int getFormatVersion() {\n        return formatVersion;\n    }\n\n    public String getID() {\n        return id;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public AssetIndexInfo getAssetIndex() {\n        return assetIndex;\n    }\n\n    public String getMinecraftArguments() {\n        return minecraftArguments;\n    }\n\n    public List<String> getJvmArgs() {\n        return nonNullOrEmpty(jvmArgs);\n    }\n\n    public String getMainClass() {\n        return mainClass;\n    }\n\n    public int[] getJavaMajors() {\n        return javaMajors;\n    }\n\n    public Library getMainJar() {\n        return mainJar;\n    }\n\n    public List<String> getTraits() {\n        return nonNullOrEmpty(traits);\n    }\n\n    public List<String> getTweakers() {\n        return nonNullOrEmpty(tweakers);\n    }\n\n    public List<Library> getLibraries() {\n        List<Library> list = new ArrayList<>();\n        if (libraries0 != null) {\n            list.addAll(libraries0);\n        }\n        if (libraries1 != null) {\n            list.addAll(libraries1);\n        }\n        return nonNullOrEmpty(list);\n    }\n\n    public List<Library> getMavenOnlyFiles() {\n        return nonNullOrEmpty(mavenFiles);\n    }\n\n    public List<Library> getJarMods() {\n        return nonNullOrEmpty(jarMods);\n    }\n\n    public List<MultiMCManifest.MultiMCManifestCachedRequires> getRequires() {\n        return nonNullOrEmpty(requires);\n    }\n\n    private static <T> List<T> nonNullOrEmpty(List<T> value) {\n        return value != null && !value.isEmpty() ? value : Collections.emptyList();\n    }\n\n    private static <T> List<T> dropDuplicate(List<T> original) {\n        // TODO: Maybe new ArrayList(new LinkedHashSet(original)) ?\n\n        Set<T> values = new HashSet<>();\n        List<T> result = new ArrayList<>();\n\n        for (T item : original) {\n            if (values.add(item)) {\n                result.add(item);\n            }\n        }\n\n        return result;\n    }\n\n    public static MultiMCInstancePatch read(String componentID, String text) {\n        try {\n            return JsonUtils.fromNonNullJson(text, MultiMCInstancePatch.class);\n        } catch (JsonParseException e) {\n            throw new IllegalArgumentException(\"Illegal Json-Patch: \" + componentID);\n        }\n    }\n\n    public static final class ResolvedInstance {\n        private final Version version;\n\n        private final String gameVersion;\n\n        private final Library mainJar;\n\n        private final List<String> jarModFileNames;\n        private final List<Library> mavenOnlyFiles;\n\n        public ResolvedInstance(Version version, String gameVersion, Library mainJar, List<String> jarModFileNames, List<Library> mavenOnlyFiles) {\n            this.version = version;\n            this.gameVersion = gameVersion;\n            this.mainJar = mainJar;\n            this.jarModFileNames = jarModFileNames;\n            this.mavenOnlyFiles = mavenOnlyFiles;\n        }\n\n        public Version getVersion() {\n            return version;\n        }\n\n        public String getGameVersion() {\n            return gameVersion;\n        }\n\n        public Library getMainJar() {\n            return mainJar;\n        }\n\n        public List<String> getJarModFileNames() {\n            return jarModFileNames;\n        }\n\n        public List<Library> getMavenOnlyFiles() {\n            return mavenOnlyFiles;\n        }\n    }\n\n    /**\n     * <p>Core methods transforming MultiMCModpack to Official Version Scheme.</p>\n     *\n     * <p>Most of the information can be transformed in a lossless manner, except for some inputs.\n     * See to do marks below for more information</p>\n     *\n     * @param patches   List of all Json-Patch.\n     * @param versionID the version ID. Used when constructing a Version.\n     * @return The resolved instance.\n     */\n    public static ResolvedInstance resolveArtifact(List<MultiMCInstancePatch> patches, String versionID) {\n        if (patches.isEmpty()) {\n            throw new IllegalArgumentException(\"Empty components.\");\n        }\n\n        for (MultiMCInstancePatch patch : patches) {\n            Objects.requireNonNull(patch, \"patch\");\n\n            if (patch.getFormatVersion() != 1) {\n                throw new UnsupportedOperationException(\n                        String.format(\"Unsupported JSON-Patch[%s] format version: %d\", patch.getID(), patch.getFormatVersion())\n                );\n            }\n        }\n\n        StringBuilder message = new StringBuilder();\n\n        List<String> minecraftArguments;\n        ArrayList<Argument> jvmArguments = new ArrayList<>(Arguments.DEFAULT_JVM_ARGUMENTS);\n        String mainClass;\n        AssetIndexInfo assetIndex;\n        int[] javaMajors;\n        Library mainJar;\n        List<String> traits;\n        List<String> tweakers;\n        /* TODO: MultiMC use a slightly different way to store jars containing jni files.\n            Transforming them to Official Scheme might boost compatibility with other launchers. */\n        List<Library> libraries;\n        List<Library> mavenOnlyFiles;\n        List<String> jarModFileNames;\n\n        {\n            MultiMCInstancePatch last = patches.get(patches.size() - 1);\n            minecraftArguments = last.getMinecraftArguments() == null ? null : StringUtils.tokenize(last.getMinecraftArguments());\n            mainClass = last.getMainClass();\n            assetIndex = last.getAssetIndex();\n            javaMajors = last.getJavaMajors();\n            mainJar = last.getMainJar();\n            traits = last.getTraits();\n            tweakers = last.getTweakers();\n            libraries = last.getLibraries();\n            mavenOnlyFiles = last.getMavenOnlyFiles();\n            jarModFileNames = last.getJarMods().stream().map(Library::getFileName).collect(Collectors.toList());\n        }\n\n        for (int i = patches.size() - 2; i >= 0; i--) {\n            MultiMCInstancePatch patch = patches.get(i);\n            if (minecraftArguments == null & patch.getMinecraftArguments() != null) {\n                minecraftArguments = StringUtils.tokenize(patch.getMinecraftArguments());\n            }\n            for (String jvmArg : patch.getJvmArgs()) {\n                jvmArguments.add(new StringArgument(jvmArg));\n            }\n            mainClass = Lang.requireNonNullElse(mainClass, patch.getMainClass());\n            assetIndex = Lang.requireNonNullElse(patch.getAssetIndex(), assetIndex);\n            javaMajors = Lang.requireNonNullElse(patch.getJavaMajors(), javaMajors);\n            mainJar = Lang.requireNonNullElse(patch.getMainJar(), mainJar);\n            traits = Lang.merge(patch.getTraits(), traits);\n            tweakers = Lang.merge(patch.getTweakers(), tweakers);\n            libraries = Lang.merge(patch.getLibraries(), libraries);\n            mavenOnlyFiles = Lang.merge(patch.getMavenOnlyFiles(), mavenOnlyFiles);\n            jarModFileNames = Lang.merge(patch.getJarMods().stream().map(Library::getFileName).collect(Collectors.toList()), jarModFileNames);\n        }\n\n        mainClass = Lang.requireNonNullElse(mainClass, \"net.minecraft.client.Minecraft\");\n\n        if (minecraftArguments == null) {\n            minecraftArguments = new ArrayList<>();\n        }\n\n        // '--tweakClass' can't be the last argument.\n        for (int i = minecraftArguments.size() - 2; i >= 0; i--) {\n            if (\"--tweakClass\".equals(minecraftArguments.get(i))) {\n                tweakers.add(minecraftArguments.get(i + 1));\n\n                minecraftArguments.remove(i);\n                minecraftArguments.remove(i);\n            }\n        }\n\n        traits = dropDuplicate(traits);\n        tweakers = dropDuplicate(tweakers);\n        jarModFileNames = dropDuplicate(jarModFileNames);\n\n        for (String tweaker : tweakers) {\n            minecraftArguments.add(\"--tweakClass\");\n            minecraftArguments.add(tweaker);\n        }\n\n        for (String trait : traits) {\n            switch (trait) {\n                case \"FirstThreadOnMacOS\": {\n                    jvmArguments.add(new RuledArgument(\n                            Collections.singletonList(\n                                    new CompatibilityRule(CompatibilityRule.Action.ALLOW, new OSRestriction(OperatingSystem.MACOS))\n                            ),\n                            Collections.singletonList(\"-XstartOnFirstThread\")\n                    ));\n                    break;\n                }\n                case \"XR:Initial\": // Flag for chat report. See https://discord.com/channels/132965178051526656/134843027553255425/1380885829702127616\n                case \"texturepacks\": // HMCL hasn't support checking whether a game version supports texture packs.\n                case \"no-texturepacks\": {\n                    break;\n                }\n                default: {\n                    message.append(\" - Trait: \").append(trait).append('\\n');\n                    break;\n                }\n            }\n        }\n\n        for (Library library : libraries) {\n            Artifact artifact = library.getArtifact();\n            if (\"io.github.zekerzhayard\".equals(artifact.getGroup()) && \"ForgeWrapper\".equals(artifact.getName())) {\n                jvmArguments.add(new StringArgument(\"-Dforgewrapper.librariesDir=${library_directory}\"));\n                jvmArguments.add(new StringArgument(\"-Dforgewrapper.minecraft=${primary_jar}\"));\n\n                for (Library lib : libraries) {\n                    Artifact ar = lib.getArtifact();\n                    if (\"net.neoforged\".equals(ar.getGroup()) && \"neoforge\".equals(ar.getName()) && \"installer\".equals(ar.getClassifier()) ||\n                            \"net.minecraftforge\".equals(ar.getGroup()) && \"forge\".equals(ar.getName()) && \"installer\".equals(ar.getClassifier())\n                    ) {\n                        jvmArguments.add(new StringArgument(\"-Dforgewrapper.installer=${library_directory}/\" + ar.getPath()));\n                    }\n                }\n            }\n        }\n\n        {\n            libraries.add(0, BOOTSTRAP_LIBRARY);\n            jvmArguments.add(new StringArgument(\"-Dhmcl.mmc.bootstrap=\" + NetworkUtils.withQuery(\"hmcl:///bootstrap_profile_v1/\", Map.of(\n                    \"main_class\", mainClass,\n                    \"installer\", MultiMCComponents.getInstallerProfile()\n            ))));\n            mainClass = \"org.jackhuang.hmcl.HMCLMultiMCBootstrap\";\n        }\n\n        Version version = new Version(versionID)\n                .setArguments(new Arguments().addGameArguments(minecraftArguments).addJVMArgumentsDirect(jvmArguments))\n                .setMainClass(mainClass)\n                .setLibraries(libraries)\n                .setAssetIndex(assetIndex)\n                .setDownload(new JsonMap<>(Collections.singletonMap(DownloadType.CLIENT, mainJar.getRawDownloadInfo())));\n\n        /* TODO: Official Version-Json can only store one pre-defined GameJavaVersion, including 8, 11, 16, 17 and 21.\n            An array of all suitable java versions are NOT supported.\n            For compatibility with official launcher and any other launchers, a transform is made between int[] and GameJavaVersion. */\n        javaMajors:\n        if (javaMajors != null) {\n            javaMajors = javaMajors.clone();\n            Arrays.sort(javaMajors);\n\n            for (int i = javaMajors.length - 1; i >= 0; i--) {\n                GameJavaVersion jv = GameJavaVersion.get(javaMajors[i]);\n                if (jv != null) {\n                    version = version.setJavaVersion(jv);\n                    break javaMajors;\n                }\n            }\n\n            message.append(\" - Java Version Range: \").append(Arrays.toString(javaMajors)).append('\\n');\n        }\n\n        version = version.markAsResolved();\n\n        String gameVersion = null;\n        for (MultiMCInstancePatch patch : patches) {\n            if (MultiMCComponents.getComponent(patch.getID()) == LibraryAnalyzer.LibraryType.MINECRAFT) {\n                gameVersion = patch.getVersion();\n                break;\n            }\n        }\n\n        if (message.length() != 0) {\n            if (message.charAt(message.length() - 1) == '\\n') {\n                message.setLength(message.length() - 1);\n            }\n            Logger.LOG.warning(\"Cannot fully parse MultiMC modpack with following unsupported features: \\n\" + message);\n        }\n        return new ResolvedInstance(version, gameVersion, mainJar, jarModFileNames, mavenOnlyFiles);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCManifest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.multimc;\n\nimport com.google.gson.annotations.SerializedName;\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.util.Immutable;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.util.List;\n\n@Immutable\npublic final class MultiMCManifest {\n\n    @SerializedName(\"formatVersion\")\n    private final int formatVersion;\n\n    @SerializedName(\"components\")\n    private final List<MultiMCManifestComponent> components;\n\n    public MultiMCManifest(int formatVersion, List<MultiMCManifestComponent> components) {\n        this.formatVersion = formatVersion;\n        this.components = components;\n    }\n\n    public int getFormatVersion() {\n        return formatVersion;\n    }\n\n    public List<MultiMCManifestComponent> getComponents() {\n        return components;\n    }\n\n    /**\n     * Read MultiMC modpack manifest from zip file\n     * @param zipFile the zip file\n     * @return the MultiMC modpack manifest.\n     * @throws IOException if zip file is malformed\n     * @throws com.google.gson.JsonParseException if manifest is malformed.\n     */\n    public static MultiMCManifest readMultiMCModpackManifest(ZipArchiveReader zipFile, String rootEntryName) throws IOException {\n        ZipArchiveEntry mmcPack = zipFile.getEntry(rootEntryName + \"mmc-pack.json\");\n        if (mmcPack == null)\n            return null;\n        MultiMCManifest manifest = JsonUtils.fromNonNullJsonFully(zipFile.getInputStream(mmcPack), MultiMCManifest.class);\n        if (manifest.getComponents() == null)\n            throw new IOException(\"mmc-pack.json malformed.\");\n\n        return manifest;\n    }\n\n    public static final class MultiMCManifestCachedRequires {\n        @SerializedName(\"equals\")\n        private final String equalsVersion;\n\n        @SerializedName(\"uid\")\n        private final String uid;\n\n        @SerializedName(\"suggests\")\n        private final String suggests;\n\n        public MultiMCManifestCachedRequires(String equalsVersion, String uid, String suggests) {\n            this.equalsVersion = equalsVersion;\n            this.uid = uid;\n            this.suggests = suggests;\n        }\n\n        public String getEqualsVersion() {\n            return equalsVersion;\n        }\n\n        public String getID() {\n            return uid;\n        }\n\n        public String getSuggests() {\n            return suggests;\n        }\n    }\n\n    public static final class MultiMCManifestComponent {\n        @SerializedName(\"cachedName\")\n        private final String cachedName;\n\n        @SerializedName(\"cachedRequires\")\n        private final List<MultiMCManifestCachedRequires> cachedRequires;\n\n        @SerializedName(\"cachedVersion\")\n        private final String cachedVersion;\n\n        @SerializedName(\"important\")\n        private final boolean important;\n\n        @SerializedName(\"dependencyOnly\")\n        private final boolean dependencyOnly;\n\n        @SerializedName(\"uid\")\n        private final String uid;\n\n        @SerializedName(\"version\")\n        private final String version;\n\n        public MultiMCManifestComponent(boolean important, boolean dependencyOnly, String uid, String version) {\n            this(null, null, null, important, dependencyOnly, uid, version);\n        }\n\n        public MultiMCManifestComponent(String cachedName, List<MultiMCManifestCachedRequires> cachedRequires, String cachedVersion, boolean important, boolean dependencyOnly, String uid, String version) {\n            this.cachedName = cachedName;\n            this.cachedRequires = cachedRequires;\n            this.cachedVersion = cachedVersion;\n            this.important = important;\n            this.dependencyOnly = dependencyOnly;\n            this.uid = uid;\n            this.version = version;\n        }\n\n        public String getCachedName() {\n            return cachedName;\n        }\n\n        public List<MultiMCManifestCachedRequires> getCachedRequires() {\n            return cachedRequires;\n        }\n\n        public String getCachedVersion() {\n            return cachedVersion;\n        }\n\n        public boolean isImportant() {\n            return important;\n        }\n\n        public boolean isDependencyOnly() {\n            return dependencyOnly;\n        }\n\n        public String getUid() {\n            return uid;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.multimc;\n\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.ModAdviser;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackExportInfo;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.Zipper;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * Export the game to a mod pack file.\n */\npublic class MultiMCModpackExportTask extends Task<Void> {\n    private final DefaultGameRepository repository;\n    private final String versionId;\n    private final List<String> whitelist;\n    private final MultiMCInstanceConfiguration configuration;\n    private final Path output;\n\n    /**\n     * @param output    mod pack file.\n     * @param versionId to locate version.json\n     */\n    public MultiMCModpackExportTask(DefaultGameRepository repository, String versionId, List<String> whitelist, MultiMCInstanceConfiguration configuration, Path output) {\n        this.repository = repository;\n        this.versionId = versionId;\n        this.whitelist = whitelist;\n        this.configuration = configuration;\n        this.output = output;\n\n        onDone().register(event -> {\n            if (event.isFailed()) {\n                try {\n                    Files.deleteIfExists(output);\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to delete modpack file: \" + output, e);\n                }\n            }\n        });\n    }\n\n    @Override\n    public void execute() throws Exception {\n        ArrayList<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);\n        blackList.add(versionId + \".jar\");\n        blackList.add(versionId + \".json\");\n        LOG.info(\"Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker\");\n        try (Zipper zip = new Zipper(output)) {\n            zip.putDirectory(repository.getRunDirectory(versionId), \".minecraft\", path -> Modpack.acceptFile(path, blackList, whitelist));\n\n            String gameVersion = repository.getGameVersion(versionId)\n                    .orElseThrow(() -> new IOException(\"Cannot parse the version of \" + versionId));\n            LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId), gameVersion);\n            List<MultiMCManifest.MultiMCManifestComponent> components = new ArrayList<>();\n            components.add(new MultiMCManifest.MultiMCManifestComponent(true, false, MultiMCComponents.getComponent(MINECRAFT), gameVersion));\n\n            for (Map.Entry<String, LibraryAnalyzer.LibraryType> pair : MultiMCComponents.getPairs()) {\n                if (pair.getValue().isModLoader()) {\n                    analyzer.getVersion(pair.getValue()).ifPresent(\n                            v -> components.add(new MultiMCManifest.MultiMCManifestComponent(false, false, pair.getKey(), v))\n                    );\n                }\n            }\n\n            MultiMCManifest mmcPack = new MultiMCManifest(1, components);\n            zip.putTextFile(JsonUtils.GSON.toJson(mmcPack), \"mmc-pack.json\");\n\n            StringWriter writer = new StringWriter();\n            configuration.toProperties().store(writer, \"Auto generated by Hello Minecraft! Launcher\");\n            zip.putTextFile(writer.toString(), \"instance.cfg\");\n\n            zip.putTextFile(\"\", \".packignore\");\n        }\n    }\n\n    public static final ModpackExportInfo.Options OPTION = new ModpackExportInfo.Options()\n            .requireAuthor()\n            .requireMinMemory();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.multimc;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.download.MaintainTask;\nimport org.jackhuang.hmcl.download.game.GameAssetDownloadTask;\nimport org.jackhuang.hmcl.download.game.GameDownloadTask;\nimport org.jackhuang.hmcl.download.game.GameLibrariesTask;\nimport org.jackhuang.hmcl.game.Artifact;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.game.Library;\nimport org.jackhuang.hmcl.game.Version;\nimport org.jackhuang.hmcl.mod.MinecraftInstanceTask;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.mod.ModpackInstallTask;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.DirectoryStream;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * <p>A task transforming MultiMC Modpack Scheme to Official Launcher Scheme.\n * The transforming process contains 7 stage:\n * <ul>\n *     <li>General Setup: Compute checksum and copy 'overrides' files.</li>\n *     <li>Load Components: Parse all local Json-Patch and prepare to fetch others from Internet.</li>\n *     <li>Resolve Json-Patch: Fetch remote Json-Patch and their dependencies.</li>\n *     <li>Build Artifact: Transform Json-Patch to Official Scheme lossily, without original structure.</li>\n *     <li>Copy Embedded Files: Copy embedded libraries and icon.</li>\n *     <li>Assemble Game: Prepare to download main jar, libraries and assets.</li>\n *     <li>Download Game: Download files.</li>\n *     <li>Apply JAR mods: Apply JAR mods into main jar.</li>\n * </ul>\n * See codes below for detailed implementation.\n *\n * @implNote To guarantee all features of MultiMC Modpack Scheme is super hard.\n * As f*** MMC never provides a detailed API docs, most codes below is guessed from its source code.\n * <b>FUNCTIONS OF GAMES MIGHT NOT BE COMPLETELY THE SAME WITH MMC.</b>\n * </p>\n */\npublic final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.ResolvedInstance> {\n\n    private final Path zipFile;\n    private final Modpack modpack;\n    private final MultiMCInstanceConfiguration manifest;\n    private final String name;\n    private final DefaultGameRepository repository;\n    private final List<Task<?>> dependents = new ArrayList<>();\n    private final List<Task<?>> dependencies = new ArrayList<>();\n    private final DefaultDependencyManager dependencyManager;\n\n    public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, Modpack modpack, MultiMCInstanceConfiguration manifest, String name) {\n        this.zipFile = zipFile;\n        this.modpack = modpack;\n        this.manifest = manifest;\n        this.name = name;\n        this.dependencyManager = dependencyManager;\n        this.repository = dependencyManager.getGameRepository();\n\n        Path json = repository.getModpackConfiguration(name);\n        if (repository.hasVersion(name) && Files.notExists(json))\n            throw new IllegalArgumentException(\"Version \" + name + \" already exists.\");\n\n        onDone().register(event -> {\n            if (event.isFailed())\n                repository.removeVersionFromDisk(name);\n        });\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        // Stage #0: General Setup\n        {\n            Path run = repository.getRunDirectory(name);\n            Path json = repository.getModpackConfiguration(name);\n\n            ModpackConfiguration<MultiMCInstanceConfiguration> config = null;\n            try {\n                if (Files.exists(json)) {\n                    config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(MultiMCInstanceConfiguration.class));\n\n                    if (!MultiMCModpackProvider.INSTANCE.getName().equals(config.getType()))\n                        throw new IllegalArgumentException(\"Version \" + name + \" is not a MultiMC modpack. Cannot update this version.\");\n                }\n            } catch (JsonParseException | IOException ignore) {\n            }\n\n            String mcDirectory;\n            try (FileSystem fs = openModpack()) {\n                mcDirectory = getRootPath(fs).resolve(\".minecraft\").toAbsolutePath().normalize().toString();\n            }\n\n            // TODO: Optimize unbearably slow ModpackInstallTask\n            dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(mcDirectory), any -> true, config).withStage(\"hmcl.modpack\"));\n            dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(mcDirectory), manifest, MultiMCModpackProvider.INSTANCE, manifest.getName(), null, repository.getModpackConfiguration(name)).withStage(\"hmcl.modpack\"));\n        }\n\n        // Stage #1: Load all related Json-Patch from meta maven or local mod pack.\n\n        try (FileSystem fs = openModpack()) {\n            Path root = getRootPath(fs);\n\n            List<MultiMCManifest.MultiMCManifestComponent> components = Objects.requireNonNull(\n                    Objects.requireNonNull(manifest.getMmcPack(), \"mmc-pack.json\").getComponents(), \"components\"\n            );\n            List<Task<MultiMCInstancePatch>> patches = new ArrayList<>();\n\n            String mcVersion = null;\n            for (MultiMCManifest.MultiMCManifestComponent component : components) {\n                if (MultiMCComponents.getComponent(component.getUid()) == LibraryAnalyzer.LibraryType.MINECRAFT) {\n                    mcVersion = component.getVersion();\n                    break;\n                }\n            }\n            if (mcVersion == null) {\n                throw new IllegalStateException(\"Cannot load modpacks without Minecraft.\");\n            }\n\n            for (MultiMCManifest.MultiMCManifestComponent component : components) {\n                String componentID = Objects.requireNonNull(component.getUid(), \"Component ID\");\n                Path patchPath = root.resolve(String.format(\"patches/%s.json\", componentID));\n\n                if (Files.exists(patchPath)) {\n                    if (!Files.isRegularFile(patchPath)) {\n                        throw new IllegalArgumentException(\"Json-Patch isn't a file: \" + componentID);\n                    }\n\n                    MultiMCInstancePatch patch = MultiMCInstancePatch.read(componentID, Files.readString(patchPath));\n                    patches.add(Task.supplyAsync(() -> patch)); // TODO: Task.completed has unclear compatibility issue.\n                } else {\n                    patches.add(\n                            new GetTask(MultiMCComponents.getMetaURL(componentID, component.getVersion(), mcVersion))\n                                    .thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s))\n                    );\n                }\n            }\n            dependents.add(new MMCInstancePatchesAssembleTask(patches, mcVersion));\n        }\n    }\n\n    private static final class MMCInstancePatchesAssembleTask extends Task<List<MultiMCInstancePatch>> {\n        private final List<Task<MultiMCInstancePatch>> patches;\n        private final String mcVersion;\n\n        public MMCInstancePatchesAssembleTask(List<Task<MultiMCInstancePatch>> patches, String mcVersion) {\n            this.patches = patches;\n            this.mcVersion = mcVersion;\n        }\n\n        @Override\n        public Collection<? extends Task<?>> getDependents() {\n            return patches;\n        }\n\n        @Override\n        public void execute() throws Exception {\n            Map<String, MultiMCInstancePatch> existed = new LinkedHashMap<>();\n            for (Task<MultiMCInstancePatch> patch : patches) {\n                MultiMCInstancePatch result = patch.getResult();\n\n                existed.put(result.getID(), result);\n            }\n\n            checking:\n            while (true) {\n                for (MultiMCInstancePatch patch : existed.values()) {\n                    for (MultiMCManifest.MultiMCManifestCachedRequires require : patch.getRequires()) {\n                        String componentID = require.getID();\n                        if (!existed.containsKey(componentID)) {\n                            Task<MultiMCInstancePatch> task = new GetTask(MultiMCComponents.getMetaURL(\n                                    componentID, Lang.requireNonNullElse(require.getEqualsVersion(), require.getSuggests()), mcVersion\n                            )).thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s));\n                            task.run();\n\n                            MultiMCInstancePatch result = Objects.requireNonNull(task.getResult());\n                            existed.put(result.getID(), result);\n                            continue checking;\n                        }\n                    }\n                }\n\n                break;\n            }\n\n            setResult(new ArrayList<>(existed.values()));\n        }\n    }\n\n    @Override\n    public List<Task<?>> getDependents() {\n        // Stage #2: Resolve all Json-Patch\n        return dependents;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        // Stage #3: Build Json-Patch artifact.\n        MultiMCInstancePatch.ResolvedInstance artifact = null;\n        for (int i = dependents.size() - 1; i >= 0; i--) {\n            Task<?> task = dependents.get(i);\n            if (task instanceof MMCInstancePatchesAssembleTask) {\n                artifact = MultiMCInstancePatch.resolveArtifact(((MMCInstancePatchesAssembleTask) task).getResult(), name);\n                break;\n            }\n        }\n        Objects.requireNonNull(artifact, \"artifact\");\n\n        // Stage #4: Copy embedded files.\n        try (FileSystem fs = openModpack()) {\n            Path root = getRootPath(fs);\n\n            Path libraries = root.resolve(\"libraries\");\n            if (Files.exists(libraries))\n                FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).resolve(\"libraries\"));\n\n            for (Library library : artifact.getVersion().getLibraries()) {\n                if (\"local\".equals(library.getHint())) {\n                    /* TODO: Determine whether we should erase community fields, like 'hint' and 'filename' from version json.\n                        Retain them will facilitate compatibility, as some embedded libraries may check where their JAR is.\n                        Meanwhile, potential compatibility issue with other launcher which never supports these fields might occur.\n                        Here, we make the file stored twice, to keep maximum compatibility. */\n                    Path from = repository.getLibraryFile(artifact.getVersion(), library);\n                    Path target = repository.getLibraryFile(artifact.getVersion(), library.withoutCommunityFields());\n                    Files.createDirectories(target.getParent());\n                    Files.copy(from, target, StandardCopyOption.REPLACE_EXISTING);\n                }\n            }\n\n            try (InputStream input = MaintainTask.class.getResourceAsStream(\"/assets/game/HMCLMultiMCBootstrap-1.0.jar\")) {\n                Path libraryPath = repository.getLibraryFile(artifact.getVersion(), MultiMCInstancePatch.BOOTSTRAP_LIBRARY);\n\n                Files.createDirectories(libraryPath.getParent());\n                Files.copy(Objects.requireNonNull(input, \"Bundled HMCLMultiMCBootstrap is missing.\"), libraryPath, StandardCopyOption.REPLACE_EXISTING);\n            }\n\n            String iconKey = this.manifest.getIconKey();\n            if (iconKey != null) {\n                Path iconFile = root.resolve(iconKey + \".png\");\n                if (Files.exists(iconFile)) {\n                    FileUtils.copyFile(iconFile, repository.getVersionRoot(name).resolve(\"icon.png\"));\n                }\n            }\n        }\n\n        // Stage #5: Assemble game files.\n        {\n            Version version = artifact.getVersion();\n\n            dependencies.add(repository.saveAsync(artifact.getVersion()));\n            dependencies.add(new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true));\n            dependencies.add(new GameLibrariesTask(\n                    dependencyManager,\n                    // TODO: check integrity of maven-only files when launching games?\n                    version.setLibraries(Lang.merge(version.getLibraries(), artifact.getMavenOnlyFiles())),\n                    true\n            ));\n\n            Artifact mainJarArtifact = artifact.getMainJar().getArtifact();\n            String gameVersion = artifact.getGameVersion();\n            if (gameVersion != null &&\n                    \"com.mojang\".equals(mainJarArtifact.getGroup()) &&\n                    \"minecraft\".equals(mainJarArtifact.getName()) &&\n                    Objects.equals(gameVersion, mainJarArtifact.getVersion()) &&\n                    \"client\".equals(mainJarArtifact.getClassifier())\n            ) {\n                dependencies.add(new GameDownloadTask(dependencyManager, gameVersion, version));\n            } else {\n                dependencies.add(new GameDownloadTask(dependencyManager, null, version));\n            }\n        }\n\n        setResult(artifact);\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        // Stage #6: Download game files.\n        return dependencies;\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        MultiMCInstancePatch.ResolvedInstance artifact = Objects.requireNonNull(getResult(), \"ResolvedInstance\");\n\n        List<String> files = artifact.getJarModFileNames();\n        if (!isDependenciesSucceeded() || files.isEmpty()) {\n            return;\n        }\n\n        // Stage #7: Apply jar mods.\n        try (FileSystem fs = openModpack()) {\n            Path root = getRootPath(fs).resolve(\"jarmods\");\n\n            try (FileSystem mc = CompressingUtils.writable(\n                    repository.getVersionRoot(name).resolve(name + \".jar\")\n            ).setAutoDetectEncoding(true).build()) {\n                for (String fileName : files) {\n                    try (FileSystem jm = CompressingUtils.readonly(root.resolve(fileName)).setAutoDetectEncoding(true).build()) {\n                        FileUtils.copyDirectory(jm.getPath(\"/\"), mc.getPath(\"/\"));\n                    }\n                }\n            }\n        }\n    }\n\n    private FileSystem openModpack() throws IOException {\n        return CompressingUtils.readonly(zipFile).setAutoDetectEncoding(true).setEncoding(modpack.getEncoding()).build();\n    }\n\n    private static boolean testPath(Path root) {\n        return Files.exists(root.resolve(\"instance.cfg\"));\n    }\n\n    private static Path getRootPath(FileSystem fs) throws IOException {\n        Path root = fs.getPath(\"/\");\n\n        if (testPath(root)) {\n            return root;\n        }\n\n        try (DirectoryStream<Path> stream = Files.newDirectoryStream(root)) {\n            for (Path candidate : stream) {\n                if (testPath(candidate)) {\n                    return candidate;\n                }\n            }\n        }\n\n        throw new IOException(\"Not a valid MultiMC modpack\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.multimc;\n\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.mod.MismatchedModpackTypeException;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.mod.ModpackUpdateTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.io.FileUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\n\npublic final class MultiMCModpackProvider implements ModpackProvider {\n    public static final MultiMCModpackProvider INSTANCE = new MultiMCModpackProvider();\n\n    @Override\n    public String getName() {\n        return \"MultiMC\";\n    }\n\n    @Override\n    public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        return null;\n    }\n\n    @Override\n    public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException {\n        if (!(modpack.getManifest() instanceof MultiMCInstanceConfiguration multiMCInstanceConfiguration))\n            throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());\n\n        return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new MultiMCModpackInstallTask(dependencyManager, zipFile, modpack, multiMCInstanceConfiguration, name));\n    }\n\n    private static String getRootEntryName(ZipArchiveReader file) throws IOException {\n        final String instanceFileName = \"instance.cfg\";\n\n        if (file.getEntry(instanceFileName) != null) return \"\";\n\n        for (ZipArchiveEntry entry : file.getEntries()) {\n            String entryName = entry.getName();\n\n            int idx = entryName.indexOf('/');\n            if (idx >= 0\n                    && entryName.length() == idx + instanceFileName.length() + 1\n                    && entryName.startsWith(instanceFileName, idx + 1))\n                return entryName.substring(0, idx + 1);\n        }\n\n        throw new IOException(\"Not a valid MultiMC modpack\");\n    }\n\n    @Override\n    public Modpack readManifest(ZipArchiveReader modpackFile, Path modpackPath, Charset encoding) throws IOException {\n        String rootEntryName = getRootEntryName(modpackFile);\n        MultiMCManifest manifest = MultiMCManifest.readMultiMCModpackManifest(modpackFile, rootEntryName);\n\n        String name = rootEntryName.isEmpty() ? FileUtils.getNameWithoutExtension(modpackPath) : rootEntryName.substring(0, rootEntryName.length() - 1);\n        ZipArchiveEntry instanceEntry = modpackFile.getEntry(rootEntryName + \"instance.cfg\");\n\n        if (instanceEntry == null)\n            throw new IOException(\"`instance.cfg` not found, \" + modpackFile + \" is not a valid MultiMC modpack.\");\n        try (InputStream instanceStream = modpackFile.getInputStream(instanceEntry)) {\n            MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, instanceStream, manifest);\n            return new Modpack(cfg.getName(), \"\", \"\", cfg.getGameVersion(), cfg.getNotes(), encoding, cfg) {\n                @Override\n                public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {\n                    return new MultiMCModpackInstallTask(dependencyManager, zipFile, this, cfg, name);\n                }\n            };\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.server;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.GameBuilder;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.ModManager;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.task.FileDownloadTask;\nimport org.jackhuang.hmcl.task.GetTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class ServerModpackCompletionTask extends Task<Void> {\n\n    private final DefaultDependencyManager dependencyManager;\n    private final DefaultGameRepository repository;\n    private final String version;\n    private ModpackConfiguration<ServerModpackManifest> manifest;\n    private GetTask dependent;\n    private ServerModpackManifest remoteManifest;\n    private final List<Task<?>> dependencies = new ArrayList<>();\n\n    public ServerModpackCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        this(dependencyManager, version, null);\n    }\n\n    public ServerModpackCompletionTask(DefaultDependencyManager dependencyManager, String version, ModpackConfiguration<ServerModpackManifest> manifest) {\n        this.dependencyManager = dependencyManager;\n        this.repository = dependencyManager.getGameRepository();\n        this.version = version;\n\n        if (manifest == null) {\n            try {\n                Path manifestFile = repository.getModpackConfiguration(version);\n                if (Files.exists(manifestFile)) {\n                    this.manifest = JsonUtils.fromJsonFile(manifestFile, ModpackConfiguration.typeOf(ServerModpackManifest.class));\n                }\n            } catch (Exception e) {\n                LOG.warning(\"Unable to read Server modpack manifest.json\", e);\n            }\n        } else {\n            this.manifest = manifest;\n        }\n\n        setStage(\"hmcl.modpack.download\");\n    }\n\n    @Override\n    public boolean doPreExecute() {\n        return true;\n    }\n\n    @Override\n    public void preExecute() throws Exception {\n        if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;\n        dependent = new GetTask(manifest.getManifest().getFileApi() + \"/server-manifest.json\");\n    }\n\n    @Override\n    public Collection<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public Collection<Task<?>> getDependents() {\n        return dependent == null ? Collections.emptySet() : Collections.singleton(dependent);\n    }\n\n    private Map<String, String> toMap(Collection<ServerModpackManifest.Addon> addons) {\n        return addons.stream().collect(Collectors.toMap(ServerModpackManifest.Addon::getId, ServerModpackManifest.Addon::getVersion));\n    }\n\n    @Override\n    public void execute() throws Exception {\n        if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;\n\n        try {\n            remoteManifest = JsonUtils.fromNonNullJson(dependent.getResult(), ServerModpackManifest.class);\n        } catch (JsonParseException e) {\n            throw new IOException(e);\n        }\n\n        Map<String, String> oldAddons = toMap(manifest.getManifest().getAddons());\n        Map<String, String> newAddons = toMap(remoteManifest.getAddons());\n        if (!Objects.equals(oldAddons, newAddons)) {\n            GameBuilder builder = dependencyManager.gameBuilder().name(version);\n            for (ServerModpackManifest.Addon addon : remoteManifest.getAddons()) {\n                builder.version(addon.getId(), addon.getVersion());\n            }\n\n            dependencies.add(builder.buildAsync());\n        }\n\n        Path rootPath = repository.getVersionRoot(version).toAbsolutePath().normalize();\n        Map<String, ModpackConfiguration.FileInformation> files = manifest.getManifest().getFiles().stream()\n                .collect(Collectors.toMap(ModpackConfiguration.FileInformation::getPath,\n                        Function.identity()));\n\n        Set<String> remoteFiles = remoteManifest.getFiles().stream().map(ModpackConfiguration.FileInformation::getPath)\n                .collect(Collectors.toSet());\n\n        Path runDirectory = repository.getRunDirectory(version).toAbsolutePath().normalize();\n        Path modsDirectory = runDirectory.resolve(\"mods\");\n\n        int total = 0;\n        // for files in new modpack\n        for (ModpackConfiguration.FileInformation file : remoteManifest.getFiles()) {\n            Path actualPath = rootPath.resolve(file.getPath()).toAbsolutePath().normalize();\n            String fileName = actualPath.getFileName().toString();\n\n            if (!actualPath.startsWith(rootPath)) {\n                throw new IOException(\"Unsecure path: \" + file.getPath());\n            }\n\n            boolean download;\n\n            boolean isModDisabled = modsDirectory.equals(actualPath.getParent()) &&\n                    (Files.exists(actualPath.resolveSibling(fileName + ModManager.DISABLED_EXTENSION)) ||\n                            Files.exists(actualPath.resolveSibling(fileName + ModManager.OLD_EXTENSION)));\n\n            if (isModDisabled) {\n                download = false;\n            } else if (!files.containsKey(file.getPath())) {\n                // If old modpack does not have this entry, download it\n                download = true;\n            } else if (!Files.exists(actualPath)) {\n                // If both old and new modpacks have this entry, but the file is missing...\n                // Re-download it since network problem may cause file missing\n                download = true;\n            } else {\n                // If user modified this entry file, we will not replace this file since this modified file is that user expects.\n                String fileHash = DigestUtils.digestToString(\"SHA-1\", actualPath);\n                String oldHash = files.get(file.getPath()).getHash();\n                download = !Objects.equals(oldHash, file.getHash()) && Objects.equals(oldHash, fileHash);\n            }\n\n            if (download) {\n                total++;\n                dependencies.add(new FileDownloadTask(\n                        remoteManifest.getFileApi() + \"/overrides/\" + file.getPath(),\n                        actualPath,\n                        new FileDownloadTask.IntegrityCheck(\"SHA-1\", file.getHash()))\n                        .withCounter(\"hmcl.modpack.download\"));\n            }\n        }\n\n        // If old modpack have this entry, and new modpack deleted it. Delete this file.\n        for (ModpackConfiguration.FileInformation file : manifest.getManifest().getFiles()) {\n            Path actualPath = rootPath.resolve(file.getPath());\n            if (Files.exists(actualPath) && !remoteFiles.contains(file.getPath()))\n                Files.deleteIfExists(actualPath);\n        }\n\n        getProperties().put(\"total\", dependencies.size());\n        notifyPropertiesChanged();\n    }\n\n    @Override\n    public boolean doPostExecute() {\n        return true;\n    }\n\n    @Override\n    public void postExecute() throws Exception {\n        if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;\n        Path manifestFile = repository.getModpackConfiguration(version);\n        Files.createDirectories(manifestFile.getParent());\n        JsonUtils.writeToJsonFile(manifestFile, new ModpackConfiguration<>(remoteManifest, this.manifest.getType(), this.manifest.getName(), this.manifest.getVersion(), remoteManifest.getFiles()));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackExportTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.server;\n\nimport org.jackhuang.hmcl.download.LibraryAnalyzer;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.ModAdviser;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.mod.ModpackExportInfo;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.Zipper;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class ServerModpackExportTask extends Task<Void> {\n    private final DefaultGameRepository repository;\n    private final String versionId;\n    private final ModpackExportInfo exportInfo;\n    private final Path modpackFile;\n\n    public ServerModpackExportTask(DefaultGameRepository repository, String version, ModpackExportInfo exportInfo, Path modpackFile) {\n        this.repository = repository;\n        this.versionId = version;\n        this.exportInfo = exportInfo.validate();\n        this.modpackFile = modpackFile;\n\n        onDone().register(event -> {\n            if (event.isFailed()) {\n                try {\n                    Files.deleteIfExists(modpackFile);\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to delete modpack file: \" + modpackFile, e);\n                }\n            }\n        });\n    }\n\n    @Override\n    public void execute() throws Exception {\n        ArrayList<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);\n        blackList.add(versionId + \".jar\");\n        blackList.add(versionId + \".json\");\n        LOG.info(\"Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker\");\n        try (Zipper zip = new Zipper(modpackFile)) {\n            Path runDirectory = repository.getRunDirectory(versionId);\n            List<ModpackConfiguration.FileInformation> files = new ArrayList<>();\n            zip.putDirectory(runDirectory, \"overrides\", path -> {\n                if (Modpack.acceptFile(path, blackList, exportInfo.getWhitelist())) {\n                    Path file = runDirectory.resolve(path);\n                    if (Files.isRegularFile(file)) {\n                        String relativePath = runDirectory.relativize(file).normalize().toString().replace(File.separatorChar, '/');\n                        files.add(new ModpackConfiguration.FileInformation(relativePath, DigestUtils.digestToString(\"SHA-1\", file)));\n                    }\n                    return true;\n                } else {\n                    return false;\n                }\n            });\n\n            String gameVersion = repository.getGameVersion(versionId)\n                    .orElseThrow(() -> new IOException(\"Cannot parse the version of \" + versionId));\n            LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId), gameVersion);\n            List<ServerModpackManifest.Addon> addons = new ArrayList<>();\n            addons.add(new ServerModpackManifest.Addon(MINECRAFT.getPatchId(), gameVersion));\n            analyzer.getVersion(FORGE).ifPresent(forgeVersion ->\n                    addons.add(new ServerModpackManifest.Addon(FORGE.getPatchId(), forgeVersion)));\n            analyzer.getVersion(NEO_FORGE).ifPresent(neoForgeVersion ->\n                    addons.add(new ServerModpackManifest.Addon(NEO_FORGE.getPatchId(), neoForgeVersion)));\n            analyzer.getVersion(LITELOADER).ifPresent(liteLoaderVersion ->\n                    addons.add(new ServerModpackManifest.Addon(LITELOADER.getPatchId(), liteLoaderVersion)));\n            analyzer.getVersion(OPTIFINE).ifPresent(optifineVersion ->\n                    addons.add(new ServerModpackManifest.Addon(OPTIFINE.getPatchId(), optifineVersion)));\n            analyzer.getVersion(FABRIC).ifPresent(fabricVersion ->\n                    addons.add(new ServerModpackManifest.Addon(FABRIC.getPatchId(), fabricVersion)));\n            analyzer.getVersion(QUILT).ifPresent(quiltVersion ->\n                    addons.add(new ServerModpackManifest.Addon(QUILT.getPatchId(), quiltVersion)));\n            ServerModpackManifest manifest = new ServerModpackManifest(exportInfo.getName(), exportInfo.getAuthor(), exportInfo.getVersion(), exportInfo.getDescription(), StringUtils.removeSuffix(exportInfo.getFileApi(), \"/\"), files, addons);\n            zip.putTextFile(JsonUtils.GSON.toJson(manifest), \"server-manifest.json\");\n        }\n    }\n\n    public static final ModpackExportInfo.Options OPTION = new ModpackExportInfo.Options()\n            .requireAuthor()\n            .requireFileApi(false);\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.server;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.GameBuilder;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.MinecraftInstanceTask;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.mod.ModpackInstallTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class ServerModpackLocalInstallTask extends Task<Void> {\n\n    private final Path zipFile;\n    private final Modpack modpack;\n    private final ServerModpackManifest manifest;\n    private final String name;\n    private final DefaultGameRepository repository;\n    private final List<Task<?>> dependencies = new ArrayList<>();\n    private final List<Task<?>> dependents = new ArrayList<>(4);\n\n    public ServerModpackLocalInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, Modpack modpack, ServerModpackManifest manifest, String name) {\n        this.zipFile = zipFile;\n        this.modpack = modpack;\n        this.manifest = manifest;\n        this.name = name;\n        this.repository = dependencyManager.getGameRepository();\n        Path run = repository.getRunDirectory(name);\n\n        Path json = repository.getModpackConfiguration(name);\n        if (repository.hasVersion(name) && Files.notExists(json))\n            throw new IllegalArgumentException(\"Version \" + name + \" already exists.\");\n\n        GameBuilder builder = dependencyManager.gameBuilder().name(name);\n        for (ServerModpackManifest.Addon addon : manifest.getAddons()) {\n            builder.version(addon.getId(), addon.getVersion());\n        }\n\n        dependents.add(builder.buildAsync());\n        onDone().register(event -> {\n            if (event.isFailed())\n                repository.removeVersionFromDisk(name);\n        });\n\n        ModpackConfiguration<ServerModpackManifest> config = null;\n        try {\n            if (Files.exists(json)) {\n                config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(ServerModpackManifest.class));\n\n                if (!ServerModpackProvider.INSTANCE.getName().equals(config.getType()))\n                    throw new IllegalArgumentException(\"Version \" + name + \" is not a Server modpack. Cannot update this version.\");\n            }\n        } catch (JsonParseException | IOException ignore) {\n        }\n        dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(\"/overrides\"), any -> true, config).withStage(\"hmcl.modpack\"));\n        dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(\"/overrides\"), manifest, ServerModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage(\"hmcl.modpack\"));\n    }\n\n    @Override\n    public List<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() throws Exception {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackManifest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.server;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.mod.ModpackManifest;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.TolerableValidationException;\nimport org.jackhuang.hmcl.util.gson.Validation;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;\n\npublic class ServerModpackManifest implements ModpackManifest, Validation {\n    private final String name;\n    private final String author;\n    private final String version;\n    private final String description;\n    private final String fileApi;\n    private final List<ModpackConfiguration.FileInformation> files;\n    private final List<Addon> addons;\n\n    public ServerModpackManifest() {\n        this(\"\", \"\", \"\", \"\", \"\", Collections.emptyList(), Collections.emptyList());\n    }\n\n    public ServerModpackManifest(String name, String author, String version, String description, String fileApi, List<ModpackConfiguration.FileInformation> files, List<Addon> addons) {\n        this.name = name;\n        this.author = author;\n        this.version = version;\n        this.description = description;\n        this.fileApi = fileApi;\n        this.files = files;\n        this.addons = addons;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public String getFileApi() {\n        return fileApi;\n    }\n\n    public List<ModpackConfiguration.FileInformation> getFiles() {\n        return files;\n    }\n\n    public List<Addon> getAddons() {\n        return addons;\n    }\n\n    @Override\n    public ModpackProvider getProvider() {\n        return ServerModpackProvider.INSTANCE;\n    }\n\n    @Override\n    public void validate() throws JsonParseException, TolerableValidationException {\n        if (fileApi == null)\n            throw new JsonParseException(\"ServerModpackManifest.fileApi cannot be blank\");\n        if (files == null)\n            throw new JsonParseException(\"ServerModpackManifest.files cannot be null\");\n    }\n\n    public static final class Addon {\n        private final String id;\n        private final String version;\n\n        public Addon() {\n            this(\"\", \"\");\n        }\n\n        public Addon(String id, String version) {\n            this.id = id;\n            this.version = version;\n        }\n\n        public String getId() {\n            return id;\n        }\n\n        public String getVersion() {\n            return version;\n        }\n    }\n\n    public Modpack toModpack(Charset encoding) throws IOException {\n        String gameVersion = addons.stream().filter(x -> MINECRAFT.getPatchId().equals(x.id)).findAny()\n                .orElseThrow(() -> new IOException(\"Cannot find game version\")).getVersion();\n        return new Modpack(name, author, version, gameVersion, description, encoding, this) {\n            @Override\n            public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {\n                return new ServerModpackLocalInstallTask(dependencyManager, zipFile, this, ServerModpackManifest.this, name);\n            }\n        };\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackProvider.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2022  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.server;\n\nimport com.google.gson.JsonParseException;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.mod.MismatchedModpackTypeException;\nimport org.jackhuang.hmcl.mod.Modpack;\nimport org.jackhuang.hmcl.mod.ModpackProvider;\nimport org.jackhuang.hmcl.mod.ModpackUpdateTask;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.file.Path;\n\npublic final class ServerModpackProvider implements ModpackProvider {\n    public static final ServerModpackProvider INSTANCE = new ServerModpackProvider();\n\n    @Override\n    public String getName() {\n        return \"Server\";\n    }\n\n    @Override\n    public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {\n        return new ServerModpackCompletionTask(dependencyManager, version);\n    }\n\n    @Override\n    public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException {\n        if (!(modpack.getManifest() instanceof ServerModpackManifest serverModpackManifest))\n            throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());\n\n        return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new ServerModpackLocalInstallTask(dependencyManager, zipFile, modpack, serverModpackManifest, name));\n    }\n\n    @Override\n    public Modpack readManifest(ZipArchiveReader zip, Path file, Charset encoding) throws IOException, JsonParseException {\n        String json = CompressingUtils.readTextZipEntry(zip, \"server-manifest.json\");\n        ServerModpackManifest manifest = JsonUtils.fromNonNullJson(json, ServerModpackManifest.class);\n        return manifest.toModpack(encoding);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.server;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.download.DefaultDependencyManager;\nimport org.jackhuang.hmcl.download.GameBuilder;\nimport org.jackhuang.hmcl.game.DefaultGameRepository;\nimport org.jackhuang.hmcl.mod.ModpackConfiguration;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class ServerModpackRemoteInstallTask extends Task<Void> {\n\n    private final String name;\n    private final DefaultDependencyManager dependency;\n    private final DefaultGameRepository repository;\n    private final List<Task<?>> dependencies = new ArrayList<>(1);\n    private final List<Task<?>> dependents = new ArrayList<>(1);\n    private final ServerModpackManifest manifest;\n\n    public ServerModpackRemoteInstallTask(DefaultDependencyManager dependencyManager, ServerModpackManifest manifest, String name) {\n        this.name = name;\n        this.dependency = dependencyManager;\n        this.repository = dependencyManager.getGameRepository();\n        this.manifest = manifest;\n\n        Path json = repository.getModpackConfiguration(name);\n        if (repository.hasVersion(name) && Files.notExists(json))\n            throw new IllegalArgumentException(\"Version \" + name + \" already exists.\");\n\n        GameBuilder builder = dependencyManager.gameBuilder().name(name);\n        for (ServerModpackManifest.Addon addon : manifest.getAddons()) {\n            builder.version(addon.getId(), addon.getVersion());\n        }\n\n        dependents.add(builder.buildAsync());\n        onDone().register(event -> {\n            if (event.isFailed())\n                repository.removeVersionFromDisk(name);\n        });\n\n        ModpackConfiguration<ServerModpackManifest> config;\n        try {\n            if (Files.exists(json)) {\n                config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(ServerModpackManifest.class));\n\n                if (!MODPACK_TYPE.equals(config.getType()))\n                    throw new IllegalArgumentException(\"Version \" + name + \" is not a Server modpack. Cannot update this version.\");\n            }\n        } catch (JsonParseException | IOException ignore) {\n        }\n    }\n\n    @Override\n    public List<Task<?>> getDependents() {\n        return dependents;\n    }\n\n    @Override\n    public List<Task<?>> getDependencies() {\n        return dependencies;\n    }\n\n    @Override\n    public void execute() throws Exception {\n        dependencies.add(new ServerModpackCompletionTask(dependency, name, new ModpackConfiguration<>(manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), Collections.emptyList())));\n    }\n\n    public static final String MODPACK_TYPE = \"Server\";\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/resourcepack/ResourcepackFile.java",
    "content": "package org.jackhuang.hmcl.resourcepack;\n\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Locale;\n\npublic interface ResourcepackFile {\n    @Nullable\n    LocalModFile.Description getDescription();\n\n    String getName();\n\n    Path getPath();\n\n    byte @Nullable [] getIcon();\n\n    static ResourcepackFile parse(Path path) throws IOException {\n        String fileName = path.getFileName().toString();\n        if (Files.isRegularFile(path) && fileName.toLowerCase(Locale.ROOT).endsWith(\".zip\")) {\n            return new ResourcepackZipFile(path);\n        } else if (Files.isDirectory(path) && Files.exists(path.resolve(\"pack.mcmeta\"))) {\n            return new ResourcepackFolder(path);\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/resourcepack/ResourcepackFolder.java",
    "content": "package org.jackhuang.hmcl.resourcepack;\n\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.modinfo.PackMcMeta;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class ResourcepackFolder implements ResourcepackFile {\n    private final Path path;\n    private final LocalModFile.Description description;\n    private final byte @Nullable [] icon;\n\n    public ResourcepackFolder(Path path) {\n        this.path = path;\n\n        LocalModFile.Description description = null;\n        try {\n            description = JsonUtils.fromJsonFile(path.resolve(\"pack.mcmeta\"), PackMcMeta.class).pack().description();\n        } catch (Exception e) {\n            LOG.warning(\"Failed to parse resourcepack meta\", e);\n        }\n\n        byte[] icon;\n        try {\n            icon = Files.readAllBytes(path.resolve(\"pack.png\"));\n        } catch (IOException e) {\n            icon = null;\n            LOG.warning(\"Failed to read resourcepack icon\", e);\n        }\n        this.icon = icon;\n\n        this.description = description;\n    }\n\n    @Override\n    public String getName() {\n        return path.getFileName().toString();\n    }\n\n    @Override\n    public Path getPath() {\n        return path;\n    }\n\n    @Override\n    public LocalModFile.Description getDescription() {\n        return description;\n    }\n\n    @Override\n    public byte @Nullable [] getIcon() {\n        return icon;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/resourcepack/ResourcepackZipFile.java",
    "content": "package org.jackhuang.hmcl.resourcepack;\n\nimport org.jackhuang.hmcl.mod.LocalModFile;\nimport org.jackhuang.hmcl.mod.modinfo.PackMcMeta;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Path;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class ResourcepackZipFile implements ResourcepackFile {\n    private final Path path;\n    private final byte @Nullable [] icon;\n    private final String name;\n    private final LocalModFile.Description description;\n\n    public ResourcepackZipFile(Path path) throws IOException {\n        this.path = path;\n        LocalModFile.Description description = null;\n\n        byte[] icon = null;\n\n        try (var zipFileTree = new ZipFileTree(CompressingUtils.openZipFile(path))) {\n            try {\n                description = JsonUtils.fromNonNullJson(zipFileTree.readTextEntry(\"/pack.mcmeta\"), PackMcMeta.class).pack().description();\n            } catch (Exception e) {\n                LOG.warning(\"Failed to parse resourcepack meta\", e);\n            }\n\n            var iconEntry = zipFileTree.getEntry(\"/pack.png\");\n            if (iconEntry != null) {\n                try (InputStream is = zipFileTree.getInputStream(iconEntry)) {\n                    icon = is.readAllBytes();\n                } catch (Exception e) {\n                    LOG.warning(\"Failed to load resourcepack icon\", e);\n                }\n            }\n        }\n\n        this.icon = icon;\n        this.description = description;\n\n        name = FileUtils.getNameWithoutExtension(path);\n    }\n\n    @Override\n    public String getName() {\n        return name;\n    }\n\n    @Override\n    public Path getPath() {\n        return path;\n    }\n\n    @Override\n    public LocalModFile.Description getDescription() {\n        return description;\n    }\n\n    @Override\n    public byte @Nullable [] getIcon() {\n        return icon;\n    }\n}\n\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/schematic/LitematicFile.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.schematic;\n\nimport com.github.steveice10.opennbt.NBTIO;\nimport com.github.steveice10.opennbt.tag.builtin.*;\nimport javafx.geometry.Point3D;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.zip.GZIPInputStream;\n\n/**\n * @author Glavo\n * @see <a href=\"https://litemapy.readthedocs.io/en/v0.9.0b0/litematics.html\">The Litematic file format</a>\n */\npublic final class LitematicFile {\n\n    private static int tryGetInt(Tag tag) {\n        return tag instanceof IntTag ? ((IntTag) tag).getValue() : 0;\n    }\n\n    private static @Nullable Instant tryGetLongTimestamp(Tag tag) {\n        if (tag instanceof LongTag) {\n            return Instant.ofEpochMilli(((LongTag) tag).getValue());\n        }\n        return null;\n    }\n\n    private static @Nullable String tryGetString(Tag tag) {\n        return tag instanceof StringTag ? ((StringTag) tag).getValue() : null;\n    }\n\n    public static LitematicFile load(Path file) throws IOException {\n\n        CompoundTag root;\n        try (InputStream in = new GZIPInputStream(Files.newInputStream(file))) {\n            root = (CompoundTag) NBTIO.readTag(in);\n        }\n\n        Tag versionTag = root.get(\"Version\");\n        if (versionTag == null)\n            throw new IOException(\"Version tag not found\");\n        else if (!(versionTag instanceof IntTag))\n            throw new IOException(\"Version tag is not an integer\");\n\n        Tag metadataTag = root.get(\"Metadata\");\n        if (metadataTag == null)\n            throw new IOException(\"Metadata tag not found\");\n        else if (!(metadataTag instanceof CompoundTag))\n            throw new IOException(\"Metadata tag is not a compound tag\");\n\n        int regions = 0;\n        Tag regionsTag = root.get(\"Regions\");\n        if (regionsTag instanceof CompoundTag)\n            regions = ((CompoundTag) regionsTag).size();\n\n        return new LitematicFile(file, (CompoundTag) metadataTag,\n                ((IntTag) versionTag).getValue(),\n                tryGetInt(root.get(\"SubVersion\")),\n                tryGetInt(root.get(\"MinecraftDataVersion\")),\n                regions\n        );\n    }\n\n    private final @NotNull Path file;\n\n    private final int version;\n    private final int subVersion;\n    private final int minecraftDataVersion;\n    private final int regionCount;\n    private final int[] previewImageData;\n    private final String name;\n    private final String author;\n    private final String description;\n    private final Instant timeCreated;\n    private final Instant timeModified;\n    private final int totalBlocks;\n    private final int totalVolume;\n    private final Point3D enclosingSize;\n\n    private LitematicFile(@NotNull Path file, @NotNull CompoundTag metadata,\n                          int version, int subVersion, int minecraftDataVersion, int regionCount) {\n        this.file = file;\n        this.version = version;\n        this.subVersion = subVersion;\n        this.minecraftDataVersion = minecraftDataVersion;\n        this.regionCount = regionCount;\n\n        Tag previewImageData = metadata.get(\"PreviewImageData\");\n        this.previewImageData = previewImageData instanceof IntArrayTag\n                ? ((IntArrayTag) previewImageData).getValue()\n                : null;\n\n        this.name = tryGetString(metadata.get(\"Name\"));\n        this.author = tryGetString(metadata.get(\"Author\"));\n        this.description = tryGetString(metadata.get(\"Description\"));\n        this.timeCreated = tryGetLongTimestamp(metadata.get(\"TimeCreated\"));\n        this.timeModified = tryGetLongTimestamp(metadata.get(\"TimeModified\"));\n        this.totalBlocks = tryGetInt(metadata.get(\"TotalBlocks\"));\n        this.totalVolume = tryGetInt(metadata.get(\"TotalVolume\"));\n\n\n        Point3D enclosingSize = null;\n        Tag enclosingSizeTag = metadata.get(\"EnclosingSize\");\n        if (enclosingSizeTag instanceof CompoundTag) {\n            CompoundTag list = (CompoundTag) enclosingSizeTag;\n            int x = tryGetInt(list.get(\"x\"));\n            int y = tryGetInt(list.get(\"y\"));\n            int z = tryGetInt(list.get(\"z\"));\n\n            if (x >= 0 && y >= 0 && z >= 0)\n                enclosingSize = new Point3D(x, y, z);\n        }\n        this.enclosingSize = enclosingSize;\n\n    }\n\n    public @NotNull Path getFile() {\n        return file;\n    }\n\n    public int getVersion() {\n        return version;\n    }\n\n    public int getSubVersion() {\n        return subVersion;\n    }\n\n    public int getMinecraftDataVersion() {\n        return minecraftDataVersion;\n    }\n\n    public int[] getPreviewImageData() {\n        return previewImageData != null ? previewImageData.clone() : null;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public Instant getTimeCreated() {\n        return timeCreated;\n    }\n\n    public Instant getTimeModified() {\n        return timeModified;\n    }\n\n    public int getTotalBlocks() {\n        return totalBlocks;\n    }\n\n    public int getTotalVolume() {\n        return totalVolume;\n    }\n\n    public Point3D getEnclosingSize() {\n        return enclosingSize;\n    }\n\n    public int getRegionCount() {\n        return regionCount;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport com.google.gson.JsonParseException;\nimport org.jackhuang.hmcl.util.Lang;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.concurrent.*;\n\nimport static org.jackhuang.hmcl.util.Lang.*;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class AsyncTaskExecutor extends TaskExecutor {\n\n    private CompletableFuture<Boolean> future;\n\n    public AsyncTaskExecutor(Task<?> task) {\n        super(task);\n    }\n\n    @Override\n    public TaskExecutor start() {\n        taskListeners.forEach(TaskListener::onStart);\n        future = executeTasks(null, Collections.singleton(firstTask))\n                .thenApplyAsync(exception -> {\n                    boolean success = exception == null;\n\n                    if (!success) {\n                        // We log exception stacktrace because some of exceptions occurred because of bugs.\n                        LOG.warning(\"An exception occurred in task execution\", exception);\n\n                        Throwable resolvedException = resolveException(exception);\n                        if (resolvedException instanceof RuntimeException &&\n                                !(resolvedException instanceof CancellationException) &&\n                                !(resolvedException instanceof JsonParseException) &&\n                                !(resolvedException instanceof RejectedExecutionException)) {\n                            // Track uncaught RuntimeException which are thrown mostly by our mistake\n                            if (uncaughtExceptionHandler != null)\n                                uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), resolvedException);\n                        }\n                    }\n\n                    taskListeners.forEach(it -> it.onStop(success, this));\n                    return success;\n                })\n                .exceptionally(e -> {\n                    Lang.handleUncaughtException(resolveException(e));\n                    return false;\n                });\n        return this;\n    }\n\n    @Override\n    public boolean test() {\n        start();\n        try {\n            return future.get();\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n        } catch (ExecutionException ignore) {\n            // We have dealt with ExecutionException in exception handling and uncaught exception handler.\n        } catch (CancellationException e) {\n            LOG.info(\"Task \" + firstTask + \" has been cancelled.\", e);\n        }\n        return false;\n    }\n\n    @Override\n    public synchronized void cancel() {\n        if (future == null) {\n            throw new IllegalStateException(\"Cannot cancel a not started TaskExecutor\");\n        }\n\n        cancelled = true;\n    }\n\n    private CompletableFuture<?> executeTasksExceptionally(Task<?> parentTask, Collection<? extends Task<?>> tasks) {\n        if (tasks == null || tasks.isEmpty())\n            return CompletableFuture.completedFuture(null);\n\n        return CompletableFuture.completedFuture(null)\n                .thenComposeAsync(unused -> {\n                    if (isCancelled()) {\n                        for (Task<?> task : tasks) task.setException(new CancellationException());\n                        return CompletableFuture.runAsync(this::checkCancellation);\n                    }\n\n                    return CompletableFuture.allOf(tasks.stream()\n                            .map(task -> CompletableFuture.completedFuture(null)\n                                    .thenComposeAsync(unused2 -> executeTask(parentTask, task))\n                            ).toArray(CompletableFuture<?>[]::new));\n                });\n    }\n\n    private CompletableFuture<Exception> executeTasks(Task<?> parentTask, Collection<? extends Task<?>> tasks) {\n        return executeTasksExceptionally(parentTask, tasks)\n                .thenApplyAsync(unused -> (Exception) null)\n                .exceptionally(throwable -> {\n                    Throwable resolved = resolveException(throwable);\n                    if (resolved instanceof Exception) {\n                        return (Exception) resolved;\n                    } else {\n                        // If an error occurred, we just rethrow it.\n                        throw new CompletionException(throwable);\n                    }\n                });\n    }\n\n    private <T> CompletableFuture<T> executeCompletableFutureTask(Task<?> parentTask, CompletableFutureTask<T> task) {\n        return CompletableFuture.completedFuture(null)\n                .thenComposeAsync(unused -> {\n                    checkCancellation();\n\n                    task.setCancelled(this::isCancelled);\n                    task.setState(Task.TaskState.READY);\n                    if (parentTask != null && task.getStage() == null)\n                        task.setStage(parentTask.getStage());\n\n                    if (task.getSignificance().shouldLog())\n                        LOG.trace(\"Executing task: \" + task.getName());\n\n                    taskListeners.forEach(it -> it.onReady(task));\n\n                    return task.getFuture(new TaskCompletableFuture() {\n                        @Override\n                        public <T2> CompletableFuture<T2> one(Task<T2> subtask) {\n                            return executeTask(task, subtask);\n                        }\n\n                        @Override\n                        public CompletableFuture<?> all(Collection<Task<?>> tasks) {\n                            return executeTasksExceptionally(task, tasks);\n                        }\n                    });\n                })\n                .thenApplyAsync(result -> {\n                    checkCancellation();\n\n                    if (task.getSignificance().shouldLog()) {\n                        LOG.trace(\"Task finished: \" + task.getName());\n                    }\n\n                    task.setResult(result);\n                    task.fireDoneEvent(this, false);\n                    taskListeners.forEach(it -> it.onFinished(task));\n\n                    task.setState(Task.TaskState.SUCCEEDED);\n\n                    return result;\n                })\n                .exceptionally(throwable -> {\n                    Throwable resolved = resolveException(throwable);\n                    if (resolved instanceof Exception e) {\n                        if (e instanceof InterruptedException || e instanceof CancellationException) {\n                            task.setException(null);\n                            if (task.getSignificance().shouldLog()) {\n                                LOG.trace(\"Task aborted: \" + task.getName());\n                            }\n                            task.fireDoneEvent(this, true);\n                            taskListeners.forEach(it -> it.onFailed(task, e));\n                        } else {\n                            task.setException(e);\n                            exception = e;\n                            if (task.getSignificance().shouldLog()) {\n                                LOG.trace(\"Task failed: \" + task.getName(), e);\n                            }\n                            task.fireDoneEvent(this, true);\n                            taskListeners.forEach(it -> it.onFailed(task, e));\n                        }\n\n                        task.setState(Task.TaskState.FAILED);\n                    }\n\n                    throw new CompletionException(resolved); // rethrow error\n                });\n    }\n\n    private <T> CompletableFuture<T> executeNormalTask(Task<?> parentTask, Task<T> task) {\n        return CompletableFuture.completedFuture(null)\n                .thenComposeAsync(unused -> {\n                    checkCancellation();\n\n                    task.setCancelled(this::isCancelled);\n                    task.setState(Task.TaskState.READY);\n                    if (task.getStage() != null) {\n                        task.setInheritedStage(task.getStage());\n                    } else if (parentTask != null) {\n                        task.setInheritedStage(parentTask.getInheritedStage());\n                    }\n                    task.setNotifyPropertiesChanged(() -> taskListeners.forEach(it -> it.onPropertiesUpdate(task)));\n\n                    if (task.getSignificance().shouldLog())\n                        LOG.trace(\"Executing task: \" + task.getName());\n\n                    taskListeners.forEach(it -> it.onReady(task));\n\n                    if (task.doPreExecute()) {\n                        return CompletableFuture.runAsync(wrap(task::preExecute), task.getExecutor());\n                    } else {\n                        return CompletableFuture.completedFuture(null);\n                    }\n                })\n                .thenComposeAsync(unused -> executeTasks(task, task.getDependents()))\n                .thenComposeAsync(dependentsException -> {\n                    boolean isDependentsSucceeded = dependentsException == null;\n\n                    if (isDependentsSucceeded) {\n                        task.setDependentsSucceeded();\n                    } else {\n                        task.setException(dependentsException);\n\n                        if (task.isRelyingOnDependents()) {\n                            rethrow(dependentsException);\n                        }\n                    }\n\n                    return CompletableFuture.runAsync(wrap(() -> {\n                        task.setState(Task.TaskState.RUNNING);\n                        taskListeners.forEach(it -> it.onRunning(task));\n                        task.execute();\n                    }), task.getExecutor()).whenComplete((unused, throwable) -> {\n                        task.setState(Task.TaskState.EXECUTED);\n                        rethrow(throwable);\n                    });\n                })\n                .thenComposeAsync(unused -> executeTasks(task, task.getDependencies()))\n                .thenComposeAsync(dependenciesException -> {\n                    boolean isDependenciesSucceeded = dependenciesException == null;\n\n                    if (isDependenciesSucceeded)\n                        task.setDependenciesSucceeded();\n\n                    if (task.doPostExecute()) {\n                        return CompletableFuture.runAsync(wrap(task::postExecute), task.getExecutor())\n                                .thenApply(unused -> dependenciesException);\n                    } else {\n                        return CompletableFuture.completedFuture(dependenciesException);\n                    }\n                })\n                .thenApplyAsync(dependenciesException -> {\n                    boolean isDependenciesSucceeded = dependenciesException == null;\n\n                    if (!isDependenciesSucceeded) {\n                        LOG.error(\"Subtasks failed for \" + task.getName());\n                        task.setException(dependenciesException);\n                        if (task.isRelyingOnDependencies()) {\n                            rethrow(dependenciesException);\n                        }\n                    }\n\n                    checkCancellation();\n\n                    if (task.getSignificance().shouldLog()) {\n                        LOG.trace(\"Task finished: \" + task.getName());\n                    }\n\n                    task.fireDoneEvent(this, false);\n                    taskListeners.forEach(it -> it.onFinished(task));\n\n                    task.setState(Task.TaskState.SUCCEEDED);\n\n                    return task.getResult();\n                })\n                .exceptionally(throwable -> {\n                    Throwable resolved = resolveException(throwable);\n                    if (resolved instanceof Exception) {\n                        Exception e = convertInterruptedException((Exception) resolved);\n                        task.setException(e);\n                        exception = e;\n                        if (e instanceof CancellationException) {\n                            if (task.getSignificance().shouldLog()) {\n                                LOG.trace(\"Task aborted: \" + task.getName());\n                            }\n                        } else {\n                            if (task.getSignificance().shouldLog()) {\n                                LOG.trace(\"Task failed: \" + task.getName(), e);\n                            }\n                        }\n                        task.fireDoneEvent(this, true);\n                        taskListeners.forEach(it -> it.onFailed(task, e));\n\n                        task.setState(Task.TaskState.FAILED);\n                    }\n\n                    throw new CompletionException(resolved); // rethrow error\n                });\n    }\n\n    private <T> CompletableFuture<T> executeTask(Task<?> parentTask, Task<T> task) {\n        if (task instanceof CompletableFutureTask<T> completableFutureTask) {\n            return executeCompletableFutureTask(parentTask, completableFutureTask);\n        } else {\n            return executeNormalTask(parentTask, task);\n        }\n    }\n\n    private void checkCancellation() {\n        if (isCancelled()) {\n            throw new CancellationException(\"Cancelled by user\");\n        }\n    }\n\n    private static Exception convertInterruptedException(Exception e) {\n        if (e instanceof InterruptedException) {\n            return new CancellationException(e.getMessage());\n        } else {\n            return e;\n        }\n    }\n\n    private static Thread.UncaughtExceptionHandler uncaughtExceptionHandler = null;\n\n    public static void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {\n        AsyncTaskExecutor.uncaughtExceptionHandler = uncaughtExceptionHandler;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/CacheFileTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport org.jackhuang.hmcl.util.CacheRepository;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.URI;\nimport java.net.http.HttpResponse;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * Download a file to cache repository.\n *\n * @author Glavo\n */\npublic final class CacheFileTask extends FetchTask<Path> {\n\n    public CacheFileTask(@NotNull String uri) {\n        this(NetworkUtils.toURI(uri));\n    }\n\n    public CacheFileTask(@NotNull URI uri) {\n        super(List.of(uri));\n        setName(uri.toString());\n\n        if (!NetworkUtils.isHttpUri(uri))\n            throw new IllegalArgumentException(uri.toString());\n    }\n\n    public CacheFileTask(@NotNull List<@NotNull URI> uris) {\n        super(uris);\n        setName(uris.get(0).toString());\n\n        if (!uris.stream().allMatch(NetworkUtils::isHttpUri))\n            throw new IllegalArgumentException(uris.toString());\n    }\n\n    @Override\n    protected EnumCheckETag shouldCheckETag() {\n        // Check cache\n        for (URI uri : uris) {\n            try {\n                setResult(repository.getCachedRemoteFile(uri, true));\n                LOG.info(\"Using cached file for \" + NetworkUtils.dropQuery(uri));\n                return EnumCheckETag.CACHED;\n            } catch (CacheRepository.CacheExpiredException e) {\n                LOG.info(\"Cache expired for \" + NetworkUtils.dropQuery(uri));\n            } catch (IOException ignored) {\n            }\n        }\n        return EnumCheckETag.CHECK_E_TAG;\n    }\n\n    @Override\n    protected void useCachedResult(Path cache) {\n        setResult(cache);\n    }\n\n    @Override\n    protected Context getContext(HttpResponse<?> response, boolean checkETag, String bmclapiHash) throws IOException {\n        assert checkETag;\n        assert response != null;\n\n        Path temp = Files.createTempFile(\"hmcl-download-\", null);\n        OutputStream fileOutput = Files.newOutputStream(temp);\n\n        return new Context() {\n            @Override\n            public void write(byte[] buffer, int offset, int len) throws IOException {\n                fileOutput.write(buffer, offset, len);\n            }\n\n            @Override\n            public void close() throws IOException {\n                try {\n                    fileOutput.close();\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to close file: \" + temp, e);\n                }\n\n                if (!isSuccess()) {\n                    try {\n                        Files.deleteIfExists(temp);\n                    } catch (IOException e) {\n                        LOG.warning(\"Failed to delete file: \" + temp, e);\n                    }\n                    return;\n                }\n\n                try {\n                    setResult(repository.cacheRemoteFile(response, temp));\n                } finally {\n                    try {\n                        Files.deleteIfExists(temp);\n                    } catch (IOException e) {\n                        LOG.warning(\"Failed to delete file: \" + temp, e);\n                    }\n                }\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/CompletableFutureTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport org.jackhuang.hmcl.util.Lang;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\n\npublic abstract class CompletableFutureTask<T> extends Task<T> {\n\n    @Override\n    public void execute() throws Exception {\n    }\n\n    public abstract CompletableFuture<T> getFuture(TaskCompletableFuture executor);\n\n    public static class CustomException extends RuntimeException {}\n\n    protected static CompletableFuture<Void> breakable(CompletableFuture<?> future) {\n        return future.thenApplyAsync(unused1 -> (Void) null).exceptionally(throwable -> {\n            if (Lang.resolveException(throwable) instanceof CustomException) return null;\n            else throw new CompletionException(throwable);\n        });\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/DownloadException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.net.URI;\n\nimport static java.util.Objects.requireNonNull;\n\npublic class DownloadException extends IOException {\n\n    private final URI uri;\n\n    public DownloadException(URI uri, @NotNull Throwable cause) {\n        super(\"Unable to download \" + uri + \", \" + cause.getMessage(), requireNonNull(cause));\n        this.uri = uri;\n    }\n\n    public URI getUri() {\n        return uri;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport org.jackhuang.hmcl.event.Event;\nimport org.jackhuang.hmcl.event.EventBus;\nimport org.jackhuang.hmcl.event.EventManager;\nimport org.jackhuang.hmcl.util.*;\nimport org.jackhuang.hmcl.util.io.ContentEncoding;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jackhuang.hmcl.util.io.ResponseCodeException;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.*;\nimport java.net.HttpURLConnection;\nimport java.net.URI;\nimport java.net.URLConnection;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.*;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static org.jackhuang.hmcl.util.Lang.threadPool;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic abstract class FetchTask<T> extends Task<T> {\n\n    protected static final int DEFAULT_RETRY = 3;\n\n    protected final List<URI> uris;\n    protected int retry = DEFAULT_RETRY;\n    protected CacheRepository repository = CacheRepository.getInstance();\n\n    public FetchTask(@NotNull List<@NotNull URI> uris) {\n        Objects.requireNonNull(uris);\n\n        this.uris = List.copyOf(uris);\n\n        if (this.uris.isEmpty())\n            throw new IllegalArgumentException(\"At least one URL is required\");\n\n        setExecutor(DOWNLOAD_EXECUTOR);\n    }\n\n    public void setRetry(int retry) {\n        if (retry <= 0)\n            throw new IllegalArgumentException(\"Retry count must be greater than 0\");\n\n        this.retry = retry;\n    }\n\n    public void setCacheRepository(CacheRepository repository) {\n        this.repository = repository;\n    }\n\n    protected void beforeDownload(URI uri) throws IOException {\n    }\n\n    protected abstract void useCachedResult(Path cachedFile) throws IOException;\n\n    protected abstract EnumCheckETag shouldCheckETag();\n\n    private Context getContext() throws IOException {\n        return getContext(null, false, null);\n    }\n\n    protected abstract Context getContext(@Nullable HttpResponse<?> response, boolean checkETag, String bmclapiHash) throws IOException;\n\n    @Override\n    public void execute() throws Exception {\n        boolean checkETag;\n        switch (shouldCheckETag()) {\n            case CHECK_E_TAG:\n                checkETag = true;\n                break;\n            case NOT_CHECK_E_TAG:\n                checkETag = false;\n                break;\n            default:\n                return;\n        }\n\n        ArrayList<DownloadException> exceptions = null;\n\n        if (SEMAPHORE != null)\n            SEMAPHORE.acquire();\n        try {\n            for (URI uri : uris) {\n                try {\n                    if (NetworkUtils.isHttpUri(uri))\n                        downloadHttp(uri, checkETag);\n                    else\n                        downloadNotHttp(uri);\n                    return;\n                } catch (DownloadException e) {\n                    if (exceptions == null)\n                        exceptions = new ArrayList<>();\n                    exceptions.add(e);\n                }\n            }\n        } catch (InterruptedException ignored) {\n            // Cancelled\n        } finally {\n            if (SEMAPHORE != null)\n                SEMAPHORE.release();\n        }\n\n        if (exceptions != null) {\n            DownloadException last = exceptions.remove(exceptions.size() - 1);\n            for (DownloadException exception : exceptions) {\n                last.addSuppressed(exception);\n            }\n            throw last;\n        }\n    }\n\n    private void download(Context context,\n                          InputStream inputStream,\n                          long contentLength,\n                          ContentEncoding contentEncoding) throws IOException, InterruptedException {\n        try (var ignored = context;\n             var counter = new CounterInputStream(inputStream);\n             var input = contentEncoding.wrap(counter)) {\n            long lastDownloaded = 0L;\n            byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];\n            while (true) {\n                if (isCancelled()) break;\n\n                int len = input.read(buffer);\n                if (len == -1) break;\n\n                context.write(buffer, 0, len);\n\n                if (contentLength >= 0) {\n                    // Update progress information per second\n                    updateProgress(counter.downloaded, contentLength);\n                }\n\n                updateDownloadSpeed(counter.downloaded - lastDownloaded);\n                lastDownloaded = counter.downloaded;\n            }\n\n            if (isCancelled())\n                throw new InterruptedException();\n\n            updateDownloadSpeed(counter.downloaded - lastDownloaded);\n\n            if (contentLength >= 0 && counter.downloaded != contentLength)\n                throw new IOException(\"Unexpected file size: \" + counter.downloaded + \", expected: \" + contentLength);\n\n            context.withResult(true);\n        }\n    }\n\n    private void downloadHttp(URI uri, boolean checkETag) throws DownloadException, InterruptedException {\n        if (checkETag) {\n            // Handle cache\n            try {\n                Path cache = repository.getCachedRemoteFile(uri, true);\n                useCachedResult(cache);\n                LOG.info(\"Using cached file for \" + NetworkUtils.dropQuery(uri));\n                return;\n            } catch (IOException ignored) {\n            }\n        }\n\n        ArrayList<Exception> exceptions = null;\n\n        // If loading the cache fails, the cache should not be loaded again.\n        boolean useCachedResult = true;\n        for (int retryTime = 0, retryLimit = retry; retryTime < retryLimit; retryTime++) {\n            if (isCancelled()) {\n                throw new InterruptedException();\n            }\n\n            List<URI> redirects = null;\n            try {\n                beforeDownload(uri);\n                updateProgress(0);\n\n                HttpResponse<InputStream> response;\n                String bmclapiHash;\n\n                URI currentURI = uri;\n\n                LinkedHashMap<String, String> headers = new LinkedHashMap<>();\n                headers.put(\"accept-encoding\", \"gzip\");\n                if (useCachedResult && checkETag)\n                    headers.putAll(repository.injectConnection(uri));\n\n                do {\n                    HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(currentURI)\n                            .timeout(Duration.ofMillis(NetworkUtils.TIME_OUT))\n                            .header(\"User-Agent\", NetworkUtils.USER_AGENT);\n\n                    headers.forEach(requestBuilder::header);\n                    response = Holder.HTTP_CLIENT.send(requestBuilder.build(), BODY_HANDLER);\n\n                    bmclapiHash = response.headers().firstValue(\"x-bmclapi-hash\").orElse(null);\n                    if (DigestUtils.isSha1Digest(bmclapiHash)) {\n                        Optional<Path> cache = repository.checkExistentFile(null, \"SHA-1\", bmclapiHash);\n                        if (cache.isPresent()) {\n                            useCachedResult(cache.get());\n                            LOG.info(\"Using cached file for \" + NetworkUtils.dropQuery(uri));\n                            return;\n                        }\n                    }\n\n                    int code = response.statusCode();\n                    if (code >= 300 && code <= 308 && code != 306 && code != 304) {\n                        if (redirects == null) {\n                            redirects = new ArrayList<>();\n                        } else if (redirects.size() >= 20) {\n                            throw new IOException(\"Too much redirects\");\n                        }\n\n                        String location = response.headers().firstValue(\"Location\").orElse(null);\n                        if (StringUtils.isBlank(location))\n                            throw new IOException(\"Redirected to an empty location\");\n\n                        URI target = currentURI.resolve(NetworkUtils.encodeLocation(location));\n                        redirects.add(target);\n\n                        if (!NetworkUtils.isHttpUri(target))\n                            throw new IOException(\"Redirected to not http URI: \" + target);\n\n                        currentURI = target;\n                    } else {\n                        break;\n                    }\n                } while (true);\n\n                int responseCode = response.statusCode();\n                if (useCachedResult && responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {\n                    // Handle cache\n                    try {\n                        Path cache = repository.getCachedRemoteFile(currentURI, false);\n                        useCachedResult(cache);\n                        LOG.info(\"Using cached file for \" + NetworkUtils.dropQuery(uri));\n                        return;\n                    } catch (CacheRepository.CacheExpiredException e) {\n                        LOG.info(\"Cache expired for \" + NetworkUtils.dropQuery(uri));\n                    } catch (IOException e) {\n                        LOG.warning(\"Unable to use cached file, redownload \" + NetworkUtils.dropQuery(uri), e);\n                        repository.removeRemoteEntry(currentURI);\n                        useCachedResult = false;\n                        // Now we must reconnect the server since 304 may result in empty content,\n                        // if we want to redownload the file, we must reconnect the server without etag settings.\n                        retryLimit++;\n                        continue;\n                    }\n                } else if (responseCode / 100 == 4) {\n                    throw new FileNotFoundException(uri.toString());\n                } else if (responseCode / 100 != 2) {\n                    throw new ResponseCodeException(uri, responseCode);\n                }\n\n                long contentLength = response.headers().firstValueAsLong(\"content-length\").orElse(-1L);\n                var contentEncoding = ContentEncoding.fromResponse(response);\n\n                download(getContext(response, checkETag, bmclapiHash),\n                        response.body(),\n                        contentLength,\n                        contentEncoding);\n                return;\n            } catch (InterruptedException e) {\n                throw e;\n            } catch (FileNotFoundException ex) {\n                LOG.warning(\"Failed to download \" + uri + \", not found\" + (redirects == null ? \"\" : \", redirects: \" + redirects), ex);\n                throw toDownloadException(uri, ex, exceptions); // we will not try this URL again\n            } catch (Exception ex) {\n                if (exceptions == null)\n                    exceptions = new ArrayList<>();\n\n                exceptions.add(ex);\n\n                LOG.warning(\"Failed to download \" + uri + \", repeat times: \" + retryTime + (redirects == null ? \"\" : \", redirects: \" + redirects), ex);\n\n                if (retryTime < retryLimit - 1) {\n                    // Wait for a while before retrying\n                    Thread.sleep(200);\n                }\n            }\n        }\n\n        throw toDownloadException(uri, null, exceptions);\n    }\n\n    private void downloadNotHttp(URI uri) throws DownloadException, InterruptedException {\n        ArrayList<Exception> exceptions = null;\n        for (int retryTime = 0; retryTime < retry; retryTime++) {\n            if (isCancelled()) {\n                throw new InterruptedException();\n            }\n\n            try {\n                beforeDownload(uri);\n                updateProgress(0);\n\n                URLConnection conn = NetworkUtils.createConnection(uri);\n                download(getContext(),\n                        conn.getInputStream(),\n                        conn.getContentLengthLong(),\n                        ContentEncoding.fromConnection(conn));\n                return;\n            } catch (InterruptedException e) {\n                throw e;\n            } catch (FileNotFoundException ex) {\n                LOG.warning(\"Failed to download \" + uri + \", not found\", ex);\n\n                throw toDownloadException(uri, ex, exceptions); // we will not try this URL again\n            } catch (Exception ex) {\n                if (exceptions == null)\n                    exceptions = new ArrayList<>();\n\n                exceptions.add(ex);\n                LOG.warning(\"Failed to download \" + uri + \", repeat times: \" + retryTime, ex);\n            }\n        }\n\n        throw toDownloadException(uri, null, exceptions);\n    }\n\n    private static DownloadException toDownloadException(URI uri, @Nullable Exception last, @Nullable ArrayList<Exception> exceptions) {\n        if (exceptions == null || exceptions.isEmpty()) {\n            return new DownloadException(uri, last != null\n                    ? last\n                    : new IOException(\"No exceptions\"));\n        } else {\n            if (last == null)\n                last = exceptions.remove(exceptions.size() - 1);\n\n            for (Exception e : exceptions) {\n                last.addSuppressed(e);\n            }\n            return new DownloadException(uri, last);\n        }\n    }\n\n    private static final Timer timer = new Timer(\"DownloadSpeedRecorder\", true);\n    private static final AtomicLong downloadSpeed = new AtomicLong(0L);\n    public static final EventManager<SpeedEvent> SPEED_EVENT = EventBus.EVENT_BUS.channel(SpeedEvent.class);\n\n    static {\n        timer.schedule(new TimerTask() {\n            @Override\n            public void run() {\n                SPEED_EVENT.fireEvent(new SpeedEvent(SPEED_EVENT, downloadSpeed.getAndSet(0)));\n            }\n        }, 0, 1000);\n    }\n\n    private static void updateDownloadSpeed(long speed) {\n        downloadSpeed.addAndGet(speed);\n    }\n\n    private static final class CounterInputStream extends FilterInputStream {\n        long downloaded;\n\n        CounterInputStream(InputStream in) {\n            super(in);\n        }\n\n        @Override\n        public int read() throws IOException {\n            int b = in.read();\n            if (b >= 0)\n                downloaded++;\n            return b;\n        }\n\n        @Override\n        public int read(byte[] b, int off, int len) throws IOException {\n            int n = in.read(b, off, len);\n            if (n >= 0)\n                downloaded += n;\n            return n;\n        }\n    }\n\n    public static class SpeedEvent extends Event {\n        private final long speed;\n\n        public SpeedEvent(Object source, long speed) {\n            super(source);\n\n            this.speed = speed;\n        }\n\n        /**\n         * Download speed in byte/sec.\n         *\n         * @return download speed\n         */\n        public long getSpeed() {\n            return speed;\n        }\n\n        @Override\n        public String toString() {\n            return new ToStringBuilder(this).append(\"speed\", speed).toString();\n        }\n    }\n\n    protected static abstract class Context implements Closeable {\n        private boolean success;\n\n        public abstract void write(byte[] buffer, int offset, int len) throws IOException;\n\n        public void withResult(boolean success) {\n            this.success = success;\n        }\n\n        protected boolean isSuccess() {\n            return success;\n        }\n    }\n\n    protected enum EnumCheckETag {\n        CHECK_E_TAG,\n        NOT_CHECK_E_TAG,\n        CACHED\n    }\n\n    private static final HttpResponse.BodyHandler<InputStream> BODY_HANDLER = responseInfo -> {\n        if (responseInfo.statusCode() / 100 == 2)\n            return HttpResponse.BodySubscribers.ofInputStream();\n        else\n            return HttpResponse.BodySubscribers.replacing(null);\n    };\n\n    public static int DEFAULT_CONCURRENCY = Math.min(Runtime.getRuntime().availableProcessors() * 4, 64);\n    private static int downloadExecutorConcurrency = DEFAULT_CONCURRENCY;\n\n    // For Java 21 or later, DOWNLOAD_EXECUTOR dispatches tasks to virtual threads, and concurrency is controlled by SEMAPHORE.\n    // For versions earlier than Java 21, DOWNLOAD_EXECUTOR is a ThreadPoolExecutor, SEMAPHORE is null, and concurrency is controlled by the thread pool size.\n\n    private static final ExecutorService DOWNLOAD_EXECUTOR;\n    private static final @Nullable Semaphore SEMAPHORE;\n\n    static {\n        ExecutorService executorService = Schedulers.newVirtualThreadPerTaskExecutor(\"Download\");\n        if (executorService != null) {\n            DOWNLOAD_EXECUTOR = executorService;\n            SEMAPHORE = new Semaphore(DEFAULT_CONCURRENCY);\n        } else {\n            DOWNLOAD_EXECUTOR = threadPool(\"Download\", true, downloadExecutorConcurrency, 10, TimeUnit.SECONDS);\n            SEMAPHORE = null;\n        }\n    }\n\n    @FXThread\n    public static void setDownloadExecutorConcurrency(int concurrency) {\n        concurrency = Math.max(concurrency, 1);\n\n        int prevDownloadExecutorConcurrency = downloadExecutorConcurrency;\n        int change = concurrency - prevDownloadExecutorConcurrency;\n        if (change == 0)\n            return;\n\n        downloadExecutorConcurrency = concurrency;\n        if (SEMAPHORE != null) {\n            if (change > 0) {\n                SEMAPHORE.release(change);\n            } else {\n                int permits = -change;\n                if (!SEMAPHORE.tryAcquire(permits)) {\n                    Schedulers.io().execute(() -> {\n                        try {\n                            for (int i = 0; i < permits; i++) {\n                                SEMAPHORE.acquire();\n                            }\n                        } catch (InterruptedException e) {\n                            throw new AssertionError(\"Unreachable\", e);\n                        }\n                    });\n                }\n            }\n        } else {\n            var downloadExecutor = (ThreadPoolExecutor) DOWNLOAD_EXECUTOR;\n\n            if (downloadExecutor.getMaximumPoolSize() <= concurrency) {\n                downloadExecutor.setMaximumPoolSize(concurrency);\n                downloadExecutor.setCorePoolSize(concurrency);\n            } else {\n                downloadExecutor.setCorePoolSize(concurrency);\n                downloadExecutor.setMaximumPoolSize(concurrency);\n            }\n        }\n    }\n\n    public static int getDownloadExecutorConcurrency() {\n        return downloadExecutorConcurrency;\n    }\n\n    private static volatile boolean initialized = false;\n\n    public static void notifyInitialized() {\n        initialized = true;\n    }\n\n    /// Ensure that [#HTTP_CLIENT] is initialized after ProxyManager has been initialized.\n    private static final class Holder {\n        private static final HttpClient HTTP_CLIENT;\n\n        static {\n            if (!initialized) {\n                throw new AssertionError(\"FetchTask.Holder accessed before ProxyManager initialization.\");\n            }\n\n            boolean useHttp2 = !\"false\".equalsIgnoreCase(System.getProperty(\"hmcl.http2\"));\n\n            HTTP_CLIENT = HttpClient.newBuilder()\n                    .connectTimeout(Duration.ofMillis(NetworkUtils.TIME_OUT))\n                    .version(useHttp2 ? HttpClient.Version.HTTP_2 : HttpClient.Version.HTTP_1_1)\n                    .build();\n        }\n\n        private Holder() {\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport org.jackhuang.hmcl.util.DigestUtils;\nimport org.jackhuang.hmcl.util.io.ChecksumMismatchException;\nimport org.jackhuang.hmcl.util.io.CompressingUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.URI;\nimport java.net.http.HttpResponse;\nimport java.nio.file.FileSystem;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.security.MessageDigest;\nimport java.util.*;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * A task that can download a file online.\n *\n * @author huangyuhui\n */\npublic class FileDownloadTask extends FetchTask<Void> {\n\n    public static class IntegrityCheck {\n        private final String algorithm;\n        private final String checksum;\n\n        public IntegrityCheck(String algorithm, String checksum) {\n            this.algorithm = requireNonNull(algorithm);\n            this.checksum = requireNonNull(checksum);\n        }\n\n        public static IntegrityCheck of(String algorithm, String checksum) {\n            if (checksum == null) return null;\n            else return new IntegrityCheck(algorithm, checksum);\n        }\n\n        public String getAlgorithm() {\n            return algorithm;\n        }\n\n        public String getChecksum() {\n            return checksum;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"IntegrityCheck[algorithm='%s', checksum='%s']\", algorithm, checksum);\n        }\n    }\n\n    private final Path file;\n    private final IntegrityCheck integrityCheck;\n    private boolean caching;\n    private Path candidate;\n    private final ArrayList<IntegrityCheckHandler> integrityCheckHandlers = new ArrayList<>();\n\n    /**\n     * @param uri  the URI of remote file.\n     * @param path the location that download to.\n     */\n    public FileDownloadTask(String uri, Path path) {\n        this(List.of(NetworkUtils.toURI(uri)), path, null);\n    }\n\n    /**\n     * @param uri            the URI of remote file.\n     * @param path           the location that download to.\n     * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed\n     */\n    public FileDownloadTask(String uri, Path path, IntegrityCheck integrityCheck) {\n        this(List.of(NetworkUtils.toURI(uri)), path, integrityCheck);\n    }\n\n    /**\n     * @param uri  the URI of remote file.\n     * @param path the location that download to.\n     */\n    public FileDownloadTask(URI uri, Path path) {\n        this(uri, path, null);\n    }\n\n    /**\n     * @param uri            the URI of remote file.\n     * @param path           the location that download to.\n     * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed\n     */\n    public FileDownloadTask(URI uri, Path path, IntegrityCheck integrityCheck) {\n        this(List.of(uri), path, integrityCheck);\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param uris uris of remote file, will be attempted in order.\n     * @param file the location that download to.\n     */\n    public FileDownloadTask(List<URI> uris, Path file) {\n        this(uris, file, null);\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param uris           uris of remote file, will be attempted in order.\n     * @param path           the location that download to.\n     * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed\n     */\n    public FileDownloadTask(List<URI> uris, Path path, IntegrityCheck integrityCheck) {\n        super(uris);\n        this.file = path;\n        this.integrityCheck = integrityCheck;\n\n        setName(path.getFileName().toString());\n    }\n\n    public Path getPath() {\n        return file;\n    }\n\n    public void setCaching(boolean caching) {\n        this.caching = caching;\n    }\n\n    public FileDownloadTask setCandidate(Path candidate) {\n        this.candidate = candidate;\n        return this;\n    }\n\n    public void addIntegrityCheckHandler(IntegrityCheckHandler handler) {\n        integrityCheckHandlers.add(Objects.requireNonNull(handler));\n    }\n\n    @Override\n    protected EnumCheckETag shouldCheckETag() {\n        // Check cache\n        if (integrityCheck != null && caching) {\n            Optional<Path> cache = repository.checkExistentFile(candidate, integrityCheck.getAlgorithm(), integrityCheck.getChecksum());\n            if (cache.isPresent()) {\n                try {\n                    FileUtils.copyFile(cache.get(), file);\n                    LOG.trace(\"Successfully verified file \" + file + \" from \" + uris.get(0));\n                    return EnumCheckETag.CACHED;\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to copy cache files\", e);\n                }\n            }\n            return EnumCheckETag.NOT_CHECK_E_TAG;\n        } else {\n            return EnumCheckETag.CHECK_E_TAG;\n        }\n    }\n\n    @Override\n    protected void beforeDownload(URI uri) {\n        LOG.trace(\"Downloading \" + uri + \" to \" + file);\n    }\n\n    @Override\n    protected void useCachedResult(Path cache) throws IOException {\n        FileUtils.copyFile(cache, file);\n    }\n\n    @Override\n    protected Context getContext(HttpResponse<?> response, boolean checkETag, String bmclapiHash) throws IOException {\n        Path temp = Files.createTempFile(null, null);\n\n        String algorithm;\n        String checksum;\n        if (integrityCheck != null) {\n            algorithm = integrityCheck.getAlgorithm();\n            checksum = integrityCheck.getChecksum();\n        } else if (bmclapiHash != null && DigestUtils.isSha1Digest(bmclapiHash)) {\n            algorithm = \"SHA-1\";\n            checksum = bmclapiHash;\n        } else {\n            algorithm = null;\n            checksum = null;\n        }\n\n        MessageDigest digest = algorithm != null ? DigestUtils.getDigest(algorithm) : null;\n\n        OutputStream fileOutput = Files.newOutputStream(temp);\n        return new Context() {\n            @Override\n            public void write(byte[] buffer, int offset, int len) throws IOException {\n                if (digest != null) {\n                    digest.update(buffer, offset, len);\n                }\n\n                fileOutput.write(buffer, offset, len);\n            }\n\n            @Override\n            public void close() throws IOException {\n                try {\n                    fileOutput.close();\n                } catch (IOException e) {\n                    LOG.warning(\"Failed to close file: \" + temp, e);\n                }\n\n                if (!isSuccess()) {\n                    try {\n                        Files.deleteIfExists(temp);\n                    } catch (IOException e) {\n                        LOG.warning(\"Failed to delete file: \" + temp, e);\n                    }\n                    return;\n                }\n\n                for (IntegrityCheckHandler handler : integrityCheckHandlers) {\n                    handler.checkIntegrity(temp, file);\n                }\n\n                Files.createDirectories(file.toAbsolutePath().getParent());\n\n                try {\n                    Files.move(temp, file, StandardCopyOption.REPLACE_EXISTING);\n                } catch (Exception e) {\n                    throw new IOException(\"Unable to move temp file from \" + temp + \" to \" + file, e);\n                }\n\n                // Integrity check\n                if (checksum != null) {\n                    String actualChecksum = HexFormat.of().formatHex(digest.digest());\n                    if (!checksum.equalsIgnoreCase(actualChecksum)) {\n                        throw new ChecksumMismatchException(algorithm, checksum, actualChecksum);\n                    }\n                }\n\n                if (caching && algorithm != null) {\n                    try {\n                        repository.cacheFile(file, algorithm, checksum);\n                    } catch (IOException e) {\n                        LOG.warning(\"Failed to cache file\", e);\n                    }\n                }\n\n                if (checkETag) {\n                    repository.cacheRemoteFile(response, file);\n                }\n            }\n        };\n    }\n\n    public interface IntegrityCheckHandler {\n        /**\n         * Check whether the file is corrupted or not.\n         *\n         * @param filePath        the file locates in (maybe in temp directory)\n         * @param destinationPath for real file name\n         * @throws IOException if the file is corrupted\n         */\n        void checkIntegrity(Path filePath, Path destinationPath) throws IOException;\n    }\n\n    public static final IntegrityCheckHandler ZIP_INTEGRITY_CHECK_HANDLER = (filePath, destinationPath) -> {\n        String ext = FileUtils.getExtension(destinationPath).toLowerCase(Locale.ROOT);\n        if (ext.equals(\"zip\") || ext.equals(\"jar\")) {\n            try (FileSystem ignored = CompressingUtils.createReadOnlyZipFileSystem(filePath)) {\n                // test for zip format\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\n\n/**\n * @author huangyuhui\n */\npublic final class GetTask extends FetchTask<String> {\n\n    public GetTask(String uri) {\n        this(NetworkUtils.toURI(uri));\n    }\n\n    public GetTask(URI url) {\n        this(List.of(url));\n        setName(url.toString());\n    }\n\n    public GetTask(List<URI> url) {\n        super(url);\n        setName(url.get(0).toString());\n    }\n\n    @Override\n    protected EnumCheckETag shouldCheckETag() {\n        return EnumCheckETag.CHECK_E_TAG;\n    }\n\n    @Override\n    protected void useCachedResult(Path cachedFile) throws IOException {\n        setResult(Files.readString(cachedFile));\n    }\n\n    @Override\n    protected Context getContext(HttpResponse<?> response, boolean checkETag, String bmclapiHash) {\n        long length = -1;\n        if (response != null)\n            length = response.headers().firstValueAsLong(\"content-length\").orElse(-1L);\n        final var baos = new ByteArrayOutputStream(length <= 0 ? 8192 : (int) length);\n\n        return new Context() {\n            @Override\n            public void write(byte[] buffer, int offset, int len) {\n                baos.write(buffer, offset, len);\n            }\n\n            @Override\n            public void close() throws IOException {\n                if (!isSuccess()) return;\n\n                Charset charset = StandardCharsets.UTF_8;\n                if (response != null)\n                    charset = NetworkUtils.getCharsetFromContentType(response.headers().firstValue(\"content-type\").orElse(null));\n\n                String result = baos.toString(charset);\n                setResult(result);\n\n                if (checkETag) {\n                    repository.cacheText(response, result);\n                }\n            }\n        };\n    }\n\n    public <T> Task<T> thenGetJsonAsync(Class<T> type) {\n        return thenGetJsonAsync(TypeToken.get(type));\n    }\n\n    public <T> Task<T> thenGetJsonAsync(TypeToken<T> type) {\n        return thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, type));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport javafx.application.Platform;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.util.concurrent.*;\nimport java.util.function.Function;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author huangyuhui\npublic final class Schedulers {\n\n    private Schedulers() {\n    }\n\n    private static final @Nullable Function<String, ExecutorService> NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR;\n\n    static {\n        if (Runtime.version().feature() >= 21) {\n            try {\n                MethodHandles.Lookup lookup = MethodHandles.publicLookup();\n\n                Class<?> vtBuilderCls = Class.forName(\"java.lang.Thread$Builder$OfVirtual\");\n\n                MethodHandle ofVirtualHandle = lookup.findStatic(Thread.class, \"ofVirtual\", MethodType.methodType(vtBuilderCls));\n                MethodHandle setNameHandle = lookup.findVirtual(vtBuilderCls, \"name\", MethodType.methodType(vtBuilderCls, String.class, long.class));\n                MethodHandle toFactoryHandle = lookup.findVirtual(vtBuilderCls, \"factory\", MethodType.methodType(ThreadFactory.class));\n                MethodHandle newThreadPerTaskExecutorFactory = lookup.findStatic(Executors.class, \"newThreadPerTaskExecutor\", MethodType.methodType(ExecutorService.class, ThreadFactory.class));\n\n                NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR = name -> {\n                    try {\n                        Object virtualThreadBuilder = ofVirtualHandle.invoke();\n                        setNameHandle.invoke(virtualThreadBuilder, name, 1L);\n                        ThreadFactory threadFactory = (ThreadFactory) toFactoryHandle.invoke(virtualThreadBuilder);\n\n                        return (ExecutorService) newThreadPerTaskExecutorFactory.invokeExact(threadFactory);\n                    } catch (Throwable e) {\n                        throw new AssertionError(\"Unreachable\", e);\n                    }\n                };\n            } catch (Throwable e) {\n                throw new AssertionError(\"Unreachable\", e);\n            }\n        } else {\n            NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR = null;\n        }\n    }\n\n    /// @return Returns null if the Java version is below 21, otherwise always returns a non-null value.\n    public static ExecutorService newVirtualThreadPerTaskExecutor(String name) {\n        if (NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR == null) {\n            return null;\n        }\n\n        return NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR.apply(name);\n    }\n\n    /// This thread pool is suitable for network and local I/O operations.\n    ///\n    /// For Java 21 or later, all tasks will be dispatched to virtual threads.\n    ///\n    /// @return Thread pool for I/O operations.\n    public static ExecutorService io() {\n        return Holder.IO_EXECUTOR;\n    }\n\n    public static Executor javafx() {\n        return Platform::runLater;\n    }\n\n    /// Default thread pool, equivalent to [ForkJoinPool#commonPool()].\n    ///\n    /// It is recommended to perform computation tasks on this thread pool. For I/O operations, please use [#io()].\n    public static Executor defaultScheduler() {\n        return ForkJoinPool.commonPool();\n    }\n\n    public static void shutdown() {\n        LOG.info(\"Shutting down executor services.\");\n\n        // shutdownNow will interrupt all threads.\n        // So when we want to close the app, no threads need to be waited for finish.\n        // Sometimes it resolves the problem that the app does not exit.\n    }\n\n    private static final class Holder {\n        private static final ExecutorService IO_EXECUTOR;\n\n        static {\n            //noinspection resource\n            ExecutorService vtExecutor = newVirtualThreadPerTaskExecutor(\"IO\");\n            IO_EXECUTOR = vtExecutor != null\n                    ? vtExecutor\n                    : Executors.newCachedThreadPool(Lang.counterThreadFactory(\"IO\", true));\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport javafx.application.Platform;\nimport javafx.beans.property.DoubleProperty;\nimport javafx.beans.property.ReadOnlyDoubleProperty;\nimport javafx.beans.property.SimpleDoubleProperty;\nimport org.jackhuang.hmcl.event.EventManager;\nimport org.jackhuang.hmcl.util.Result;\nimport org.jackhuang.hmcl.util.function.ExceptionalConsumer;\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\nimport org.jackhuang.hmcl.util.function.ExceptionalRunnable;\nimport org.jackhuang.hmcl.util.function.ExceptionalSupplier;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.util.*;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.function.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * Disposable task.\n *\n * @author huangyuhui\n */\npublic abstract class Task<T> {\n\n    /**\n     * True if not logging when executing this task.\n     */\n    private TaskSignificance significance = TaskSignificance.MAJOR;\n\n    public final TaskSignificance getSignificance() {\n        return significance;\n    }\n\n    public final Task<T> setSignificance(TaskSignificance significance) {\n        this.significance = significance;\n        return this;\n    }\n\n    // cancel\n    private BooleanSupplier cancelled;\n\n    final void setCancelled(BooleanSupplier cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    protected final boolean isCancelled() {\n        if (Thread.interrupted()) {\n            Thread.currentThread().interrupt();\n            return true;\n        }\n\n        return cancelled != null && cancelled.getAsBoolean();\n    }\n\n    // stage\n    private String stage = null;\n\n    /**\n     * Stage of task implies the goal of this task, for grouping tasks.\n     * Stage will inherit from the parent task.\n     */\n    public String getStage() {\n        return stage;\n    }\n\n    /**\n     * You must initialize stage in constructor.\n     *\n     * @param stage the stage\n     */\n    protected final void setStage(String stage) {\n        this.stage = stage;\n    }\n\n    private String inheritedStage = null;\n\n    public String getInheritedStage() {\n        return inheritedStage;\n    }\n\n    void setInheritedStage(String inheritedStage) {\n        this.inheritedStage = inheritedStage;\n    }\n\n    // properties\n    Map<String, Object> properties;\n\n    public Map<String, Object> getProperties() {\n        if (properties == null) properties = new HashMap<>();\n        return properties;\n    }\n\n    private Runnable notifyPropertiesChanged;\n\n    void setNotifyPropertiesChanged(Runnable runnable) {\n        this.notifyPropertiesChanged = runnable;\n    }\n\n    protected void notifyPropertiesChanged() {\n        if (notifyPropertiesChanged != null) {\n            notifyPropertiesChanged.run();\n        }\n    }\n\n    // state\n    private TaskState state = TaskState.READY;\n\n    public final TaskState getState() {\n        return state;\n    }\n\n    final void setState(TaskState state) {\n        this.state = state;\n    }\n\n    // last exception\n    private Exception exception;\n\n    /**\n     * When task has been cancelled, task.exception will be null.\n     *\n     * @return the exception thrown during execution, possibly from dependents or dependencies.\n     */\n    @Nullable\n    public final Exception getException() {\n        return exception;\n    }\n\n    final void setException(Exception e) {\n        exception = e;\n    }\n\n    private Executor executor = Schedulers.defaultScheduler();\n\n    /**\n     * The executor that decides how this task runs.\n     */\n    public final Executor getExecutor() {\n        return executor;\n    }\n\n    public final Task<T> setExecutor(Executor executor) {\n        this.executor = executor;\n        return this;\n    }\n\n    // dependents succeeded\n    private boolean dependentsSucceeded = false;\n\n    public boolean isDependentsSucceeded() {\n        return dependentsSucceeded;\n    }\n\n    void setDependentsSucceeded() {\n        dependentsSucceeded = true;\n    }\n\n    // dependencies succeeded\n    private boolean dependenciesSucceeded = false;\n\n    public boolean isDependenciesSucceeded() {\n        return dependenciesSucceeded;\n    }\n\n    void setDependenciesSucceeded() {\n        dependenciesSucceeded = true;\n    }\n\n    /**\n     * True if requires all {@link #getDependents} finishing successfully.\n     * <p>\n     * **Note** if this field is set false, you are not supposed to invoke [run]\n     */\n    public boolean isRelyingOnDependents() {\n        return true;\n    }\n\n    /**\n     * True if requires all {@link #getDependencies} finishing successfully.\n     * <p>\n     * **Note** if this field is set false, you are not supposed to invoke [run]\n     */\n    public boolean isRelyingOnDependencies() {\n        return true;\n    }\n\n    // name\n    private String name;\n\n    public String getName() {\n        return name != null ? name : getClass().getName();\n    }\n\n    public Task<T> setName(String name) {\n        this.name = name;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        if (getClass().getName().equals(getName()))\n            return getName();\n        else\n            return getClass().getName() + \"[\" + getName() + \"]\";\n    }\n\n    // result\n    private T result;\n    private Consumer<T> resultConsumer;\n\n    /**\n     * Returns the result of this task.\n     * <p>\n     * The result will be generated only if the execution is completed.\n     */\n    public T getResult() {\n        return result;\n    }\n\n    protected void setResult(T result) {\n        this.result = result;\n        if (resultConsumer != null)\n            resultConsumer.accept(result);\n    }\n\n    /**\n     * Sync the result of this task by given action.\n     *\n     * @param action the action to perform when result of this task changed\n     * @return this Task\n     */\n    public Task<T> storeTo(Consumer<T> action) {\n        this.resultConsumer = action;\n        action.accept(getResult());\n        return this;\n    }\n\n    // execution\n    public boolean doPreExecute() {\n        return false;\n    }\n\n    /**\n     * @throws InterruptedException if current thread is interrupted\n     * @see Thread#isInterrupted()\n     */\n    public void preExecute() throws Exception {\n    }\n\n    /**\n     * @throws InterruptedException if current thread is interrupted\n     * @see Thread#isInterrupted()\n     */\n    public abstract void execute() throws Exception;\n\n    public boolean doPostExecute() {\n        return false;\n    }\n\n    /**\n     * This method will be called after dependency tasks terminated all together.\n     * <p>\n     * You can check whether dependencies succeed in this method by calling\n     * {@link Task#isDependenciesSucceeded()} no matter when\n     * {@link Task#isRelyingOnDependencies()} returns true or false.\n     *\n     * @throws InterruptedException if current thread is interrupted\n     * @see Thread#isInterrupted()\n     * @see Task#isDependenciesSucceeded()\n     */\n    public void postExecute() throws Exception {\n    }\n\n    /**\n     * The collection of sub-tasks that should execute **before** this task running.\n     */\n    public Collection<? extends Task<?>> getDependents() {\n        return Collections.emptySet();\n    }\n\n    /**\n     * The collection of sub-tasks that should execute **after** this task running.\n     * Will not be executed if execution fails.\n     */\n    public Collection<? extends Task<?>> getDependencies() {\n        return Collections.emptySet();\n    }\n\n    private volatile EventManager<TaskEvent> onDone;\n\n    public EventManager<TaskEvent> onDone() {\n        EventManager<TaskEvent> onDone = this.onDone;\n        if (onDone == null) {\n            synchronized (this) {\n                onDone = this.onDone;\n                if (onDone == null) {\n                    this.onDone = onDone = new EventManager<>();\n                }\n            }\n        }\n\n        return onDone;\n    }\n\n    void fireDoneEvent(Object source, boolean failed) {\n        EventManager<TaskEvent> onDone = this.onDone;\n        if (onDone != null)\n            onDone.fireEvent(new TaskEvent(source, this, failed));\n    }\n\n    private final DoubleProperty progress = new SimpleDoubleProperty(this, \"progress\", -1);\n\n    public ReadOnlyDoubleProperty progressProperty() {\n        return progress;\n    }\n\n    private long lastUpdateProgressTime = 0L;\n\n    protected void updateProgress(long count, long total) {\n        if (count < 0 || total < 0)\n            throw new IllegalArgumentException(\"Invalid count or total: count=\" + count + \", total=\" + total);\n\n        updateProgress(count < total ? (double) count / total : 1.0);\n    }\n\n    protected void updateProgress(double progress) {\n        if (progress < 0 || progress > 1.0 || Double.isNaN(progress))\n            throw new IllegalArgumentException(\"Invalid progress: \" + progress);\n\n        long now = System.currentTimeMillis();\n        if (progress == 1.0 || now - lastUpdateProgressTime >= 1000L) {\n            updateProgressImmediately(progress);\n            lastUpdateProgressTime = now;\n        }\n    }\n\n    //region Helpers for updateProgressImmediately\n\n    @SuppressWarnings(\"FieldMayBeFinal\")\n    private volatile double pendingProgress = -1.0;\n\n    /// @see Task#pendingProgress\n    private static final VarHandle PENDING_PROGRESS_HANDLE;\n\n    static {\n        try {\n            PENDING_PROGRESS_HANDLE = MethodHandles.lookup()\n                    .findVarHandle(Task.class, \"pendingProgress\", double.class);\n        } catch (NoSuchFieldException | IllegalAccessException e) {\n            throw new ExceptionInInitializerError(e);\n        }\n    }\n    //endregion updateProgressImmediately\n\n    protected void updateProgressImmediately(double progress) {\n        // assert progress >= 0 && progress <= 1.0;\n        if ((double) PENDING_PROGRESS_HANDLE.getAndSet(this, progress) == -1.0) {\n            Platform.runLater(() -> this.progress.set((double) PENDING_PROGRESS_HANDLE.getAndSet(this, -1.0)));\n        }\n    }\n\n    public final T run() throws Exception {\n        if (getSignificance().shouldLog())\n            LOG.trace(\"Executing task: \" + getName());\n\n        for (Task<?> task : getDependents())\n            doSubTask(task);\n        execute();\n        for (Task<?> task : getDependencies())\n            doSubTask(task);\n        fireDoneEvent(this, false);\n\n        return getResult();\n    }\n\n    private void doSubTask(Task<?> task) throws Exception {\n        progress.bind(task.progress);\n        task.run();\n        progress.unbind();\n    }\n\n    public final TaskExecutor executor() {\n        return new AsyncTaskExecutor(this);\n    }\n\n    public final TaskExecutor executor(boolean start) {\n        TaskExecutor executor = new AsyncTaskExecutor(this);\n        if (start)\n            executor.start();\n        return executor;\n    }\n\n    public final TaskExecutor executor(TaskListener taskListener) {\n        TaskExecutor executor = new AsyncTaskExecutor(this);\n        executor.addTaskListener(taskListener);\n        return executor;\n    }\n\n    public final void start() {\n        executor().start();\n    }\n\n    public final boolean test() {\n        return executor().test();\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed using the default Executor, with this\n     * task's result as the argument to the supplied function.\n     *\n     * @param fn  the function to use to compute the value of the returned Task\n     * @param <U> the function's return type\n     * @return the new Task\n     */\n    public <U, E extends Exception> Task<U> thenApplyAsync(ExceptionalFunction<T, U, E> fn) {\n        return thenApplyAsync(Schedulers.defaultScheduler(), fn);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed using the supplied Executor, with this\n     * task's result as the argument to the supplied function.\n     *\n     * @param executor the executor to use for asynchronous execution\n     * @param fn       the function to use to compute the value of the returned Task\n     * @param <U>      the function's return type\n     * @return the new Task\n     */\n    public <U, E extends Exception> Task<U> thenApplyAsync(Executor executor, ExceptionalFunction<T, U, E> fn) {\n        return thenApplyAsync(getCaller(), executor, fn).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed using the supplied Executor, with this\n     * task's result as the argument to the supplied function.\n     *\n     * @param name     the name of this new Task for displaying\n     * @param executor the executor to use for asynchronous execution\n     * @param fn       the function to use to compute the value of the returned Task\n     * @param <U>      the function's return type\n     * @return the new Task\n     */\n    public <U, E extends Exception> Task<U> thenApplyAsync(String name, Executor executor, ExceptionalFunction<T, U, E> fn) {\n        return new UniApply<>(fn).setExecutor(executor).setName(name);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed using the default Executor, with this\n     * task's result as the argument to the supplied action.\n     *\n     * @param action the action to perform before completing the\n     *               returned Task\n     * @return the new Task\n     */\n    public <E extends Exception> Task<Void> thenAcceptAsync(ExceptionalConsumer<T, E> action) {\n        return thenAcceptAsync(Schedulers.defaultScheduler(), action);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed using the supplied Executor, with this\n     * task's result as the argument to the supplied action.\n     *\n     * @param action   the action to perform before completing the returned Task\n     * @param executor the executor to use for asynchronous execution\n     * @return the new Task\n     */\n    public <E extends Exception> Task<Void> thenAcceptAsync(Executor executor, ExceptionalConsumer<T, E> action) {\n        return thenAcceptAsync(getCaller(), executor, action).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed using the supplied Executor, with this\n     * task's result as the argument to the supplied action.\n     *\n     * @param name     the name of this new Task for displaying\n     * @param action   the action to perform before completing the returned Task\n     * @param executor the executor to use for asynchronous execution\n     * @return the new Task\n     */\n    public <E extends Exception> Task<Void> thenAcceptAsync(String name, Executor executor, ExceptionalConsumer<T, E> action) {\n        return thenApplyAsync(name, executor, result -> {\n            action.accept(result);\n            return null;\n        });\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, executes the given action using the default Executor.\n     *\n     * @param action the action to perform before completing the\n     *               returned Task\n     * @return the new Task\n     */\n    public <E extends Exception> Task<Void> thenRunAsync(ExceptionalRunnable<E> action) {\n        return thenRunAsync(Schedulers.defaultScheduler(), action);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, executes the given action using the supplied Executor.\n     *\n     * @param action   the action to perform before completing the\n     *                 returned Task\n     * @param executor the executor to use for asynchronous execution\n     * @return the new Task\n     */\n    public <E extends Exception> Task<Void> thenRunAsync(Executor executor, ExceptionalRunnable<E> action) {\n        return thenRunAsync(getCaller(), executor, action).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, executes the given action using the supplied Executor.\n     *\n     * @param name     the name of this new Task for displaying\n     * @param action   the action to perform before completing the\n     *                 returned Task\n     * @param executor the executor to use for asynchronous execution\n     * @return the new Task\n     */\n    public <E extends Exception> Task<Void> thenRunAsync(String name, Executor executor, ExceptionalRunnable<E> action) {\n        return thenApplyAsync(name, executor, ignore -> {\n            action.run();\n            return null;\n        });\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed using the default Executor.\n     *\n     * @param fn  the function to use to compute the value of the returned Task\n     * @param <U> the function's return type\n     * @return the new Task\n     */\n    public final <U> Task<U> thenSupplyAsync(Callable<U> fn) {\n        return thenComposeAsync(() -> Task.supplyAsync(fn));\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed using the default Executor.\n     *\n     * @param name the name of this new Task for displaying\n     * @param fn   the function to use to compute the value of the returned Task\n     * @param <U>  the function's return type\n     * @return the new Task\n     */\n    public final <U> Task<U> thenSupplyAsync(String name, Callable<U> fn) {\n        return thenComposeAsync(() -> Task.supplyAsync(name, fn));\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed.\n     *\n     * @param other the another Task\n     * @param <U>   the type of the returned Task's result\n     * @return the Task\n     */\n    public final <U> Task<U> thenComposeAsync(Task<U> other) {\n        return thenComposeAsync(() -> other);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed.\n     *\n     * @param fn  the function returning a new Task\n     * @param <U> the type of the returned Task's result\n     * @return the Task\n     */\n    public final <U> Task<U> thenComposeAsync(ExceptionalSupplier<Task<U>, ?> fn) {\n        return thenComposeAsync(Schedulers.defaultScheduler(), fn);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed.\n     *\n     * @param fn       the function returning a new Task\n     * @param executor the executor to use for asynchronous execution\n     * @param <U>      the type of the returned Task's result\n     * @return the Task\n     */\n    public final <U> Task<U> thenComposeAsync(Executor executor, ExceptionalSupplier<Task<U>, ?> fn) {\n        return new UniCompose<>(fn, true).setExecutor(executor);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed with result of this task as the argument\n     * to the supplied function.\n     *\n     * @param fn  the function returning a new Task\n     * @param <U> the type of the returned Task's result\n     * @return the Task\n     */\n    public <U, E extends Exception> Task<U> thenComposeAsync(ExceptionalFunction<T, Task<U>, E> fn) {\n        return thenComposeAsync(Schedulers.defaultScheduler(), fn);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, is executed with result of this task as the argument\n     * to the supplied function.\n     *\n     * @param fn       the function returning a new Task\n     * @param executor the executor to use for asynchronous execution\n     * @param <U>      the type of the returned Task's result\n     * @return the Task\n     */\n    public <U, E extends Exception> Task<U> thenComposeAsync(Executor executor, ExceptionalFunction<T, Task<U>, E> fn) {\n        return new UniCompose<>(fn, true).setExecutor(executor);\n    }\n\n    public final <U> Task<U> withComposeAsync(Task<U> other) {\n        return withComposeAsync(() -> other);\n    }\n\n    public final <U, E extends Exception> Task<U> withComposeAsync(ExceptionalSupplier<Task<U>, E> fn) {\n        return new UniCompose<>(fn, false);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, executes the given action using the default Executor.\n     *\n     * @param action the action to perform before completing the\n     *               returned Task\n     * @return the new Task\n     */\n    public <E extends Exception> Task<Void> withRunAsync(ExceptionalRunnable<E> action) {\n        return withRunAsync(Schedulers.defaultScheduler(), action);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, executes the given action using the supplied Executor.\n     *\n     * @param action   the action to perform before completing the\n     *                 returned Task\n     * @param executor the executor to use for asynchronous execution\n     * @return the new Task\n     */\n    public <E extends Exception> Task<Void> withRunAsync(Executor executor, ExceptionalRunnable<E> action) {\n        return withRunAsync(getCaller(), executor, action).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    /**\n     * Returns a new Task that, when this task completes\n     * normally, executes the given action using the supplied Executor.\n     *\n     * @param name     the name of this new Task for displaying\n     * @param action   the action to perform before completing the\n     *                 returned Task\n     * @param executor the executor to use for asynchronous execution\n     * @return the new Task\n     */\n    public <E extends Exception> Task<Void> withRunAsync(String name, Executor executor, ExceptionalRunnable<E> action) {\n        return new UniCompose<>(() -> Task.runAsync(name, executor, action), false);\n    }\n\n    /**\n     * Returns a new Task with the same exception as this task, that executes\n     * the given action when this task completes.\n     *\n     * <p>When this task is complete, the given action is invoked, a boolean\n     * value represents the execution status of this task, and the exception\n     * (or {@code null} if none) of this task as arguments.  The returned task\n     * is completed when the action returns.  If the supplied action itself\n     * encounters an exception, then the returned task exceptionally completes\n     * with this exception unless this task also completed exceptionally.\n     *\n     * @param action the action to perform\n     * @return the new Task\n     */\n    public final Task<Void> whenComplete(FinalizedCallback action) {\n        return whenComplete(Schedulers.defaultScheduler(), action);\n    }\n\n    /**\n     * Returns a new Task with the same exception as this task, that executes\n     * the given action when this task completes.\n     *\n     * <p>When this task is complete, the given action is invoked, a boolean\n     * value represents the execution status of this task, and the exception\n     * (or {@code null} if none, which means when isDependentSucceeded is false,\n     * exception may be null) of this task as arguments.  The returned task\n     * is completed when the action returns.  If the supplied action itself\n     * encounters an exception, then the returned task exceptionally completes\n     * with this exception unless this task also completed exceptionally.\n     *\n     * @param action   the action to perform\n     * @param executor the executor to use for asynchronous execution\n     * @return the new Task\n     */\n    public final Task<Void> whenComplete(Executor executor, FinalizedCallback action) {\n        return new Task<Void>() {\n            {\n                setSignificance(TaskSignificance.MODERATE);\n            }\n\n            @Override\n            public void execute() throws Exception {\n                if (isDependentsSucceeded() != (Task.this.getException() == null))\n                    throw new AssertionError(\"When whenComplete succeeded, Task.exception must be null.\", Task.this.getException());\n\n                action.execute(Task.this.getException());\n\n                if (!isDependentsSucceeded()) {\n                    setSignificance(TaskSignificance.MINOR);\n                    if (Task.this.getException() == null)\n                        throw new AssertionError(\"When failed, exception cannot be null\");\n                    else\n                        throw Task.this.getException();\n                }\n            }\n\n            @Override\n            public Collection<Task<?>> getDependents() {\n                return Collections.singleton(Task.this);\n            }\n\n            @Override\n            public boolean isRelyingOnDependents() {\n                return false;\n            }\n        }.setExecutor(executor).setName(getCaller()).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    /**\n     * Returns a new Task with the same exception as this task, that executes\n     * the given action when this task completes.\n     *\n     * <p>When this task is complete, the given action is invoked with the\n     * result (or {@code null} if none), a boolean value represents the\n     * execution status of this task, and the exception (or {@code null}\n     * if none) of this task as arguments.  The returned task is completed\n     * when the action returns.  If the supplied action itself encounters an\n     * exception, then the returned task exceptionally completes with this\n     * exception unless this task also completed exceptionally.\n     *\n     * @param action the action to perform\n     * @return the new Task\n     */\n    public Task<Void> whenComplete(Executor executor, FinalizedCallbackWithResult<T> action) {\n        return whenComplete(executor, (exception -> action.execute(getResult(), exception)));\n    }\n\n    public Task<Result<T>> wrapResult() {\n        return new Task<Result<T>>() {\n            {\n                setSignificance(TaskSignificance.MODERATE);\n            }\n\n            @Override\n            public void execute() throws Exception {\n                if (isDependentsSucceeded() != (Task.this.getException() == null))\n                    throw new AssertionError(\"When whenComplete succeeded, Task.exception must be null.\", Task.this.getException());\n\n                if (isDependentsSucceeded()) {\n                    setResult(Result.success(Task.this.getResult()));\n                } else {\n                    setSignificance(TaskSignificance.MINOR);\n                    setResult(Result.failure(Task.this.getException()));\n                }\n            }\n\n            @Override\n            public Collection<Task<?>> getDependents() {\n                return Collections.singleton(Task.this);\n            }\n\n            @Override\n            public boolean isRelyingOnDependents() {\n                return false;\n            }\n        }.setExecutor(executor).setName(getCaller()).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    /**\n     * Returns a new Task with the same exception as this task, that executes\n     * the given actions when this task completes.\n     *\n     * <p>When this task is complete, the given success action is invoked, the\n     * given failure action is invoked with the exception of this task.  The\n     * returned task is completed when the action returns.  If the supplied\n     * action itself encounters an exception, then the returned task exceptionally\n     * completes with this exception unless this task also\n     * completed exceptionally.\n     *\n     * @param success the action to perform when this task successfully completed\n     * @param failure the action to perform when this task exceptionally returned\n     * @return the new Task\n     */\n    public final <E1 extends Exception, E2 extends Exception> Task<Void> whenComplete(Executor executor, ExceptionalRunnable<E1> success, ExceptionalConsumer<Exception, E2> failure) {\n        return whenComplete(executor, exception -> {\n            if (exception == null) {\n                if (success != null)\n                    try {\n                        success.run();\n                    } catch (Exception e) {\n                        LOG.warning(\"Failed to execute \" + success, e);\n                        if (failure != null)\n                            failure.accept(e);\n                    }\n            } else {\n                if (failure != null)\n                    failure.accept(exception);\n            }\n        });\n    }\n\n    /**\n     * Returns a new Task with the same exception as this task, that executes\n     * the given actions when this task completes.\n     *\n     * <p>When this task is complete, the given success action is invoked with\n     * the result, the given failure action is invoked with the exception of\n     * this task.  The returned task is completed when the action returns.  If\n     * the supplied action itself encounters an exception, then the returned\n     * task exceptionally completes with this exception unless this task also\n     * completed exceptionally.\n     *\n     * @param success the action to perform when this task successfully completed\n     * @param failure the action to perform when this task exceptionally returned\n     * @return the new Task\n     */\n    public <E1 extends Exception, E2 extends Exception> Task<Void> whenComplete(Executor executor, ExceptionalConsumer<T, E1> success, ExceptionalConsumer<Exception, E2> failure) {\n        return whenComplete(executor, () -> success.accept(getResult()), failure);\n    }\n\n    public Task<T> withStage(String stage) {\n        return new StageTask(stage);\n    }\n\n    public Task<T> withFakeProgress(String name, BooleanSupplier done, double k) {\n        return new FakeProgressTask(done, k).setExecutor(Schedulers.defaultScheduler()).setName(name).setSignificance(TaskSignificance.MAJOR);\n    }\n\n    public record StagesHint(String stage, List<String> aliases) {\n        public StagesHint(String stage) {\n            this(stage, List.of());\n        }\n    }\n\n    public Task<T> withStagesHints(String... hints) {\n        return withStagesHints(Arrays.stream(hints).map(StagesHint::new).toList());\n    }\n\n    public Task<T> withStagesHints(StagesHint... hints) {\n        return new StagesHintTask(List.of(hints));\n    }\n\n    public Task<T> withStagesHints(List<StagesHint> hints) {\n        return new StagesHintTask(hints);\n    }\n\n    public class StagesHintTask extends Task<T> {\n        private final List<StagesHint> hints;\n\n        public StagesHintTask(List<StagesHint> hints) {\n            this.hints = hints;\n        }\n\n        @Override\n        public Collection<Task<?>> getDependents() {\n            return Collections.singleton(Task.this);\n        }\n\n        @Override\n        public void execute() {\n            setResult(Task.this.getResult());\n        }\n\n        public List<StagesHint> getHints() {\n            return hints;\n        }\n    }\n\n    public Task<T> withCounter(String countStage) {\n        return new CountTask(countStage);\n    }\n\n    public static Task<Void> runAsync(ExceptionalRunnable<?> closure) {\n        return runAsync(Schedulers.defaultScheduler(), closure);\n    }\n\n    public static Task<Void> runAsync(String name, ExceptionalRunnable<?> closure) {\n        return runAsync(name, Schedulers.defaultScheduler(), closure);\n    }\n\n    public static Task<Void> runAsync(Executor executor, ExceptionalRunnable<?> closure) {\n        return runAsync(getCaller(), executor, closure).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    public static Task<Void> runAsync(String name, Executor executor, ExceptionalRunnable<?> closure) {\n        return new SimpleTask<>(closure.toCallable()).setExecutor(executor).setName(name);\n    }\n\n    public static <T> Task<T> composeAsync(ExceptionalSupplier<Task<T>, ?> fn) {\n        return composeAsync(getCaller(), fn).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    public static <T> Task<T> composeAsync(String name, ExceptionalSupplier<Task<T>, ?> fn) {\n        return new Task<T>() {\n            Task<T> then;\n\n            @Override\n            public void execute() throws Exception {\n                then = fn.get();\n                if (then != null)\n                    then.storeTo(this::setResult);\n            }\n\n            @Override\n            public Collection<Task<?>> getDependencies() {\n                return then == null ? Collections.emptySet() : Collections.singleton(then);\n            }\n        }.setName(name);\n    }\n\n    public static <T> Task<T> composeAsync(Executor executor, ExceptionalSupplier<Task<T>, ?> fn) {\n        return composeAsync(fn).setExecutor(executor);\n    }\n\n    public static <V> Task<V> supplyAsync(Callable<V> callable) {\n        return supplyAsync(getCaller(), callable).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    public static <V> Task<V> supplyAsync(Executor executor, Callable<V> callable) {\n        return supplyAsync(getCaller(), executor, callable).setSignificance(TaskSignificance.MODERATE);\n    }\n\n    public static <V> Task<V> supplyAsync(String name, Callable<V> callable) {\n        return supplyAsync(name, Schedulers.defaultScheduler(), callable);\n    }\n\n    public static <V> Task<V> supplyAsync(String name, Executor executor, Callable<V> callable) {\n        return new SimpleTask<>(callable).setExecutor(executor).setName(name);\n    }\n\n    public static <V> Task<V> completed(V value) {\n        return fromCompletableFuture(CompletableFuture.completedFuture(value));\n    }\n\n    /**\n     * Returns a new Task that is completed when all of the given Tasks\n     * complete.  If any of the given Tasks complete exceptionally,\n     * then the returned Task also does so.  Otherwise, the results, if\n     * any, of the given Tasks are not reflected in the returned Task,\n     * but may be obtained by inspecting them individually. If no Tasks\n     * are provided, returns a Task completed with the value {@code null}.\n     *\n     * @param tasks the Tasks\n     * @return a new Task that is completed when all of the given Tasks complete\n     */\n    @SafeVarargs\n    public static <T> Task<List<T>> allOf(Task<? extends T>... tasks) {\n        return allOf(Arrays.asList(tasks));\n    }\n\n    /**\n     * Returns a new Task that is completed when all of the given Tasks\n     * complete.  If any of the given Tasks complete exceptionally,\n     * then the returned Task also does so.  Otherwise, the results, if\n     * any, of the given Tasks are not reflected in the returned Task,\n     * but may be obtained by inspecting them individually. If no Tasks\n     * are provided, returns a Task completed with the value {@code null}.\n     *\n     * @param tasks the Tasks\n     * @return a new Task that is completed when all of the given Tasks complete\n     */\n    public static <T> Task<List<T>> allOf(Collection<? extends Task<? extends T>> tasks) {\n        return new Task<>() {\n            {\n                setSignificance(TaskSignificance.MINOR);\n            }\n\n            @Override\n            public void execute() {\n                setResult(tasks.stream().map(Task::getResult).collect(Collectors.toList()));\n            }\n\n            @Override\n            public Collection<? extends Task<?>> getDependents() {\n                return tasks;\n            }\n        };\n    }\n\n    /**\n     * Returns a new task that runs the given tasks sequentially\n     * and returns the result of the last task.\n     *\n     * @param tasks tasks to run sequentially\n     * @return the combination of these tasks\n     */\n    public static Task<?> runSequentially(Task<?>... tasks) {\n        if (tasks.length == 0) {\n            return new SimpleTask<>(() -> null);\n        }\n\n        Task<?> task = tasks[0];\n        for (int i = 1; i < tasks.length; i++) {\n            task = task.thenComposeAsync(tasks[i]);\n        }\n        return task;\n    }\n\n    public static <T> Task<T> fromCompletableFuture(CompletableFuture<T> future) {\n        return new CompletableFutureTask<T>() {\n            @Override\n            public CompletableFuture<T> getFuture(TaskCompletableFuture executor) {\n                return future;\n            }\n        };\n    }\n\n    public enum TaskSignificance {\n        MAJOR,\n        MODERATE,\n        MINOR;\n\n        public boolean shouldLog() {\n            return this != MINOR;\n        }\n\n        public boolean shouldShow() {\n            return this == MAJOR;\n        }\n    }\n\n    public enum TaskState {\n        READY,\n        RUNNING,\n        EXECUTED,\n        SUCCEEDED,\n        FAILED\n    }\n\n    @FunctionalInterface\n    public interface FinalizedCallback {\n        void execute(Exception exception) throws Exception;\n    }\n\n    @FunctionalInterface\n    public interface FinalizedCallbackWithResult<T> {\n        void execute(T result, Exception exception) throws Exception;\n    }\n\n    private static final String PACKAGE_PREFIX = Task.class.getPackageName() + \".\";\n    private static final Predicate<StackWalker.StackFrame> PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX);\n    private static final Function<Stream<StackWalker.StackFrame>, Optional<StackWalker.StackFrame>> FUNCTION = stream -> stream.filter(PREDICATE).findFirst();\n    private static final Function<StackWalker.StackFrame, String> FRAME_MAPPING = frame -> {\n        String fileName = frame.getFileName();\n        if (fileName != null)\n            return frame.getClassName() + '.' + frame.getMethodName() + '(' + fileName + ':' + frame.getLineNumber() + ')';\n        else\n            return frame.getClassName() + '.' + frame.getMethodName();\n    };\n\n    private static String getCaller() {\n        return StackWalker.getInstance().walk(FUNCTION).map(FRAME_MAPPING).orElse(\"Unknown\");\n    }\n\n    private static final class SimpleTask<T> extends Task<T> {\n\n        private final Callable<T> callable;\n\n        SimpleTask(Callable<T> callable) {\n            this.callable = callable;\n        }\n\n        @Override\n        public void execute() throws Exception {\n            setResult(callable.call());\n        }\n    }\n\n    private class UniApply<R> extends Task<R> {\n        private final ExceptionalFunction<T, R, ?> callable;\n\n        UniApply(ExceptionalFunction<T, R, ?> callable) {\n            this.callable = callable;\n        }\n\n        @Override\n        public Collection<Task<?>> getDependents() {\n            return Collections.singleton(Task.this);\n        }\n\n        @Override\n        public void execute() throws Exception {\n            setResult(callable.apply(Task.this.getResult()));\n        }\n    }\n\n    /**\n     * A task that combines two tasks and make sure [pred] runs before succ.\n     *\n     * @author huangyuhui\n     */\n    private final class UniCompose<U> extends Task<U> {\n\n        private final boolean relyingOnDependents;\n        private Task<U> succ;\n        private final ExceptionalFunction<T, Task<U>, ?> fn;\n\n        /**\n         * A task that combines two tasks and make sure pred runs before succ.\n         *\n         * @param fn                  a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.\n         * @param relyingOnDependents true if this task chain will be broken when task pred fails.\n         */\n        UniCompose(ExceptionalSupplier<Task<U>, ?> fn, boolean relyingOnDependents) {\n            this(result -> fn.get(), relyingOnDependents);\n        }\n\n        /**\n         * A task that combines two tasks and make sure pred runs before succ.\n         *\n         * @param fn                  a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.\n         * @param relyingOnDependents true if this task chain will be broken when task pred fails.\n         */\n        UniCompose(ExceptionalFunction<T, Task<U>, ?> fn, boolean relyingOnDependents) {\n            this.fn = fn;\n            this.relyingOnDependents = relyingOnDependents;\n\n            setSignificance(TaskSignificance.MODERATE);\n            setName(fn.toString());\n        }\n\n        @Override\n        public void execute() throws Exception {\n            setName(fn.toString());\n            succ = fn.apply(Task.this.getResult());\n            if (succ != null)\n                succ.storeTo(this::setResult);\n        }\n\n        @Override\n        public Collection<Task<?>> getDependents() {\n            return Collections.singleton(Task.this);\n        }\n\n        @Override\n        public Collection<Task<?>> getDependencies() {\n            return succ == null ? Collections.emptySet() : Collections.singleton(succ);\n        }\n\n        @Override\n        public boolean isRelyingOnDependents() {\n            return relyingOnDependents;\n        }\n    }\n\n    private final class StageTask extends Task<T> {\n        private StageTask(String stage) {\n            this.setStage(stage);\n        }\n\n        @Override\n        public Collection<Task<?>> getDependents() {\n            return Collections.singleton(Task.this);\n        }\n\n        @Override\n        public void execute() {\n            setResult(Task.this.getResult());\n        }\n    }\n\n    private final class FakeProgressTask extends Task<T> {\n        private static final double MAX_VALUE = 0.98D;\n\n        private final BooleanSupplier done;\n\n        private final double k;\n\n        private FakeProgressTask(BooleanSupplier done, double k) {\n            this.done = done;\n            this.k = k;\n        }\n\n        @Override\n        public Collection<Task<?>> getDependents() {\n            return Collections.singleton(Task.this);\n        }\n\n        @Override\n        public void execute() throws InterruptedException {\n            if (!done.getAsBoolean()) {\n                updateProgress(0.0D);\n\n                final long start = System.currentTimeMillis();\n                final double k2 = k / MAX_VALUE;\n                while (!done.getAsBoolean()) {\n                    updateProgressImmediately(-k / ((System.currentTimeMillis() - start) / 1000D + k2) + MAX_VALUE);\n\n                    Thread.sleep(1000);\n                }\n            }\n\n            updateProgress(1.0D);\n            setResult(Task.this.getResult());\n        }\n    }\n\n    public final class CountTask extends Task<T> {\n        private final String countStage;\n\n        private CountTask(String countStage) {\n            this.countStage = countStage;\n            setSignificance(TaskSignificance.MINOR);\n        }\n\n        public String getCountStage() {\n            return countStage;\n        }\n\n        @Override\n        public Collection<Task<?>> getDependents() {\n            return Collections.singleton(Task.this);\n        }\n\n        @Override\n        public void execute() throws Exception {\n            setResult(Task.this.getResult());\n        }\n\n        @Override\n        public boolean doPostExecute() {\n            return true;\n        }\n\n        @Override\n        public void postExecute() throws Exception {\n            notifyPropertiesChanged();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskCompletableFuture.java",
    "content": "package org.jackhuang.hmcl.task;\n\nimport java.util.Collection;\nimport java.util.concurrent.CompletableFuture;\n\npublic interface TaskCompletableFuture {\n\n    <T> CompletableFuture<T> one(Task<T> task);\n\n    CompletableFuture<?> all(Collection<Task<?>> tasks);\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport org.jackhuang.hmcl.event.Event;\n\n/**\n *\n * @author huang\n */\npublic class TaskEvent extends Event {\n\n    private final Task<?> task;\n    private final boolean failed;\n\n    public TaskEvent(Object source, Task<?> task, boolean failed) {\n        super(source);\n        this.task = task;\n        this.failed = failed;\n    }\n\n    public Task<?> getTask() {\n        return task;\n    }\n\n    public boolean isFailed() {\n        return failed;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\n\npublic abstract class TaskExecutor {\n    protected final Task<?> firstTask;\n    protected final List<TaskListener> taskListeners = new ArrayList<>(0);\n    protected volatile boolean cancelled = false;\n    protected Exception exception;\n    private final List<Task.StagesHint> hints;\n\n    public TaskExecutor(Task<?> task) {\n        this.firstTask = task;\n        this.hints = task instanceof Task<?>.StagesHintTask hintTask\n                ? hintTask.getHints()\n                : List.of();\n    }\n\n    public void addTaskListener(TaskListener taskListener) {\n        taskListeners.add(taskListener);\n    }\n\n    /**\n     * Reason why the task execution failed.\n     * If cancelled, null is returned.\n     */\n    @Nullable\n    public Exception getException() {\n        return exception;\n    }\n\n    public abstract TaskExecutor start();\n\n    public abstract boolean test();\n\n    /**\n     * Cancel the subscription ant interrupt all tasks.\n     */\n    public abstract void cancel();\n\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    public List<Task.StagesHint> getHints() {\n        return hints;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.task;\n\nimport java.util.EventListener;\n\n/**\n *\n * @author huangyuhui\n */\npublic abstract class TaskListener implements EventListener {\n\n    /**\n     * Executed when a Task execution chain starts.\n     */\n    public void onStart() {\n    }\n\n    /**\n     * Executed before the task's pre-execution and dependents execution.\n     *\n     * TaskState of this task is READY.\n     *\n     * @param task the task that gets ready.\n     */\n    public void onReady(Task<?> task) {\n    }\n\n    /**\n     * Executed when the task's execution starts.\n     *\n     * TaskState of this task is RUNNING.\n     *\n     * @param task the task which is being run.\n     */\n    public void onRunning(Task<?> task) {\n    }\n\n    /**\n     * Executed after the task's dependencies and post-execution finished.\n     *\n     * TaskState of the task is EXECUTED.\n     *\n     * @param task the task which finishes its work.\n     */\n    public void onFinished(Task<?> task) {\n    }\n\n    /**\n     * Executed when an exception occurred during the task's execution.\n     *\n     * @param task the task which finishes its work.\n     */\n    public void onFailed(Task<?> task, Throwable throwable) {\n        onFinished(task);\n    }\n\n    /**\n     * Executed when the task execution chain stopped.\n     *\n     * @param success true if no error occurred during task execution.\n     * @param executor the task executor with responsibility to the task execution.\n     */\n    public void onStop(boolean success, TaskExecutor executor) {\n    }\n\n    public void onPropertiesUpdate(Task<?> task) {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/ByteArray.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.lang.invoke.VarHandle;\n\nimport static java.lang.invoke.MethodHandles.byteArrayViewVarHandle;\nimport static java.nio.ByteOrder.BIG_ENDIAN;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * @author Glavo\n */\npublic final class ByteArray {\n    private static final VarHandle SHORT_LE = byteArrayViewVarHandle(short[].class, LITTLE_ENDIAN);\n    private static final VarHandle INT_LE = byteArrayViewVarHandle(int[].class, LITTLE_ENDIAN);\n    private static final VarHandle LONG_LE = byteArrayViewVarHandle(long[].class, LITTLE_ENDIAN);\n\n    private static final VarHandle SHORT_BE = byteArrayViewVarHandle(short[].class, BIG_ENDIAN);\n    private static final VarHandle INT_BE = byteArrayViewVarHandle(int[].class, BIG_ENDIAN);\n    private static final VarHandle LONG_BE = byteArrayViewVarHandle(long[].class, BIG_ENDIAN);\n\n    // Get\n\n    public static byte getByte(byte[] array, int offset) {\n        return array[offset];\n    }\n\n    public static int getUnsignedByte(byte[] array, int offset) {\n        return Byte.toUnsignedInt(getByte(array, offset));\n    }\n\n    public static short getShortLE(byte[] array, int offset) {\n        return (short) SHORT_LE.get(array, offset);\n    }\n\n    public static int getUnsignedShortLE(byte[] array, int offset) {\n        return Short.toUnsignedInt(getShortLE(array, offset));\n    }\n\n    public static short getShortBE(byte[] array, int offset) {\n        return (short) SHORT_BE.get(array, offset);\n    }\n\n    public static int getUnsignedShortBE(byte[] array, int offset) {\n        return Short.toUnsignedInt(getShortBE(array, offset));\n    }\n\n    public static int getIntLE(byte[] array, int offset) {\n        return (int) INT_LE.get(array, offset);\n    }\n\n    public static long getUnsignedIntLE(byte[] array, int offset) {\n        return Integer.toUnsignedLong(getIntLE(array, offset));\n    }\n\n    public static int getIntBE(byte[] array, int offset) {\n        return (int) INT_BE.get(array, offset);\n    }\n\n    public static long getUnsignedIntBE(byte[] array, int offset) {\n        return Integer.toUnsignedLong(getIntBE(array, offset));\n    }\n\n    public static long getLongLE(byte[] array, int offset) {\n        return (long) LONG_LE.get(array, offset);\n    }\n\n    public static long getLongBE(byte[] array, int offset) {\n        return (long) LONG_BE.get(array, offset);\n    }\n\n    // Set\n\n    public static void setByte(byte[] array, int offset, byte value) {\n        array[offset] = value;\n    }\n\n    public static void setUnsignedByte(byte[] array, int offset, int value) {\n        array[offset] = (byte) (value & 0xff);\n    }\n\n    public static void setShortLE(byte[] array, int offset, short value) {\n        SHORT_LE.set(array, offset, value);\n    }\n\n    public static void setUnsignedShortLE(byte[] array, int offset, int value) {\n        setShortLE(array, offset, (short) (value & 0xffff));\n    }\n\n    public static void setShortBE(byte[] array, int offset, short value) {\n        SHORT_BE.set(array, offset, value);\n    }\n\n    public static void setUnsignedShortBE(byte[] array, int offset, int value) {\n        setShortBE(array, offset, (short) (value & 0xffff));\n    }\n\n    public static void setIntLE(byte[] array, int offset, int value) {\n        INT_LE.set(array, offset, value);\n    }\n\n    public static void setUnsignedIntLE(byte[] array, int offset, long value) {\n        setIntLE(array, offset, (int) (value & 0xffff_ffffL));\n    }\n\n    public static void setIntBE(byte[] array, int offset, int value) {\n        INT_BE.set(array, offset, value);\n    }\n\n    public static void setUnsignedIntBE(byte[] array, int offset, long value) {\n        setIntBE(array, offset, (int) (value & 0xffff_ffffL));\n    }\n\n    public static void setLongLE(byte[] array, int offset, long value) {\n        LONG_LE.set(array, offset, value);\n    }\n\n    public static void setLongBE(byte[] array, int offset, long value) {\n        LONG_BE.set(array, offset, value);\n    }\n\n    private ByteArray() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport com.google.gson.JsonSyntaxException;\nimport com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.function.ExceptionalSupplier;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.FileUtils;\nimport org.jackhuang.hmcl.util.io.NetworkUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.*;\nimport java.net.URI;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.channels.Channels;\nimport java.nio.channels.FileChannel;\nimport java.nio.channels.FileLock;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.nio.file.attribute.FileTime;\nimport java.time.Instant;\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.*;\nimport java.util.concurrent.locks.ReadWriteLock;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\nimport java.util.function.BiFunction;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.*;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class CacheRepository {\n    private Path commonDirectory;\n    private Path cacheDirectory;\n    private Path indexFile;\n    private FileTime indexFileLastModified;\n    private LinkedHashMap<URI, ETagItem> index;\n    protected final ReadWriteLock lock = new ReentrantReadWriteLock();\n\n    public void changeDirectory(Path commonDir) {\n        commonDirectory = commonDir;\n        cacheDirectory = commonDir.resolve(\"cache\");\n        indexFile = cacheDirectory.resolve(\"etag.json\");\n\n        lock.writeLock().lock();\n        try {\n            if (Files.isRegularFile(indexFile)) {\n                try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.READ);\n                     @SuppressWarnings(\"unused\") FileLock lock = channel.tryLock(0, Long.MAX_VALUE, true)) {\n                    FileTime lastModified = Lang.ignoringException(() -> Files.getLastModifiedTime(indexFile));\n                    ETagIndex raw = JsonUtils.GSON.fromJson(new BufferedReader(Channels.newReader(channel, UTF_8)), ETagIndex.class);\n                    index = raw != null ? joinETagIndexes(raw.eTag) : new LinkedHashMap<>();\n                    indexFileLastModified = lastModified;\n                }\n            } else {\n                index = new LinkedHashMap<>();\n                indexFileLastModified = null;\n            }\n        } catch (Exception e) {\n            LOG.warning(\"Unable to read index file\", e);\n            index = new LinkedHashMap<>();\n            indexFileLastModified = null;\n        } finally {\n            lock.writeLock().unlock();\n        }\n    }\n\n    public Path getCommonDirectory() {\n        return commonDirectory;\n    }\n\n    public Path getCacheDirectory() {\n        return cacheDirectory;\n    }\n\n    protected Path getFile(String algorithm, String hash) {\n        hash = hash.toLowerCase(Locale.ROOT);\n        return getCacheDirectory().resolve(algorithm).resolve(hash.substring(0, 2)).resolve(hash);\n    }\n\n    protected boolean fileExists(String algorithm, String hash) {\n        if (hash == null) return false;\n        Path file = getFile(algorithm, hash);\n        if (Files.exists(file)) {\n            try {\n                return DigestUtils.digestToString(algorithm, file).equalsIgnoreCase(hash);\n            } catch (IOException e) {\n                return false;\n            }\n        } else {\n            return false;\n        }\n    }\n\n    public void tryCacheFile(Path path, String algorithm, String hash) throws IOException {\n        Path cache = getFile(algorithm, hash);\n        if (Files.isRegularFile(cache)) return;\n        FileUtils.copyFile(path, cache);\n    }\n\n    public Path cacheFile(Path path, String algorithm, String hash) throws IOException {\n        Path cache = getFile(algorithm, hash);\n        FileUtils.copyFile(path, cache);\n        return cache;\n    }\n\n    public Optional<Path> checkExistentFile(@Nullable Path original, String algorithm, String hash) {\n        if (fileExists(algorithm, hash))\n            return Optional.of(getFile(algorithm, hash));\n\n        if (original != null && Files.exists(original)) {\n            if (hash != null) {\n                try {\n                    String checksum = DigestUtils.digestToString(algorithm, original);\n                    if (checksum.equalsIgnoreCase(hash))\n                        return Optional.of(restore(original, () -> cacheFile(original, algorithm, hash)));\n                } catch (IOException e) {\n                    // we cannot check the hashcode.\n                }\n            } else {\n                return Optional.of(original);\n            }\n        }\n\n        return Optional.empty();\n    }\n\n    protected Path restore(Path original, ExceptionalSupplier<Path, ? extends IOException> cacheSupplier) throws IOException {\n        Path cache = cacheSupplier.get();\n        Files.delete(original);\n        Files.createLink(original, cache);\n        return cache;\n    }\n\n    public Path getCachedRemoteFile(URI uri, boolean checkExpires) throws IOException {\n        lock.readLock().lock();\n        ETagItem eTagItem;\n        try {\n            eTagItem = index.get(NetworkUtils.dropQuery(uri));\n        } finally {\n            lock.readLock().unlock();\n        }\n        if (eTagItem == null) throw new IOException(\"Cannot find the URL\");\n        if (StringUtils.isBlank(eTagItem.hash) || !fileExists(SHA1, eTagItem.hash)) throw new FileNotFoundException();\n        if (checkExpires && System.currentTimeMillis() > eTagItem.expires)\n            throw new CacheExpiredException(eTagItem.expires);\n\n        Path file = getFile(SHA1, eTagItem.hash);\n        if (Files.getLastModifiedTime(file).toMillis() != eTagItem.localLastModified) {\n            String hash = DigestUtils.digestToString(SHA1, file);\n            if (!Objects.equals(hash, eTagItem.hash))\n                throw new IOException(\"This file is modified\");\n        }\n        return file;\n    }\n\n    public void removeRemoteEntry(URI uri) {\n        lock.writeLock().lock();\n        try {\n            index.remove(NetworkUtils.dropQuery(uri));\n        } finally {\n            lock.writeLock().unlock();\n        }\n    }\n\n    public @NotNull Map<String, String> injectConnection(URI uri) {\n        try {\n            uri = NetworkUtils.dropQuery(uri);\n        } catch (IllegalArgumentException e) {\n            return Map.of();\n        }\n\n        ETagItem eTagItem;\n        lock.readLock().lock();\n        try {\n            eTagItem = index.get(uri);\n        } finally {\n            lock.readLock().unlock();\n        }\n        if (eTagItem == null) return Map.of();\n        if (eTagItem.eTag != null)\n            return Map.of(\"if-none-match\", eTagItem.eTag);\n        // if (eTagItem.getRemoteLastModified() != null)\n        //     conn.setRequestProperty(\"If-Modified-Since\", eTagItem.getRemoteLastModified());\n        return Map.of();\n    }\n\n    public void injectConnection(URI uri, HttpRequest.Builder requestBuilder) {\n        try {\n            uri = NetworkUtils.dropQuery(uri);\n        } catch (IllegalArgumentException e) {\n            return;\n        }\n\n        ETagItem eTagItem;\n        lock.readLock().lock();\n        try {\n            eTagItem = index.get(uri);\n        } finally {\n            lock.readLock().unlock();\n        }\n        if (eTagItem == null) return;\n        if (eTagItem.eTag != null)\n            requestBuilder.header(\"if-none-match\", eTagItem.eTag);\n        // if (eTagItem.getRemoteLastModified() != null)\n        //     conn.setRequestProperty(\"If-Modified-Since\", eTagItem.getRemoteLastModified());\n    }\n\n    public Path cacheRemoteFile(HttpResponse<?> response, Path downloaded) throws IOException {\n        return cacheData(response, () -> {\n            String hash = DigestUtils.digestToString(SHA1, downloaded);\n            Path cached = cacheFile(downloaded, SHA1, hash);\n            return new CacheResult(hash, cached);\n        });\n    }\n\n    public Path cacheText(HttpResponse<?> response, String text) throws IOException {\n        return cacheBytes(response, text.getBytes(UTF_8));\n    }\n\n    public Path cacheBytes(HttpResponse<?> response, byte[] bytes) throws IOException {\n        return cacheData(response, () -> {\n            String hash = DigestUtils.digestToString(SHA1, bytes);\n            Path cached = getFile(SHA1, hash);\n            Files.createDirectories(cached.getParent());\n            Files.write(cached, bytes);\n            return new CacheResult(hash, cached);\n        });\n    }\n\n    private static final Pattern MAX_AGE = Pattern.compile(\"(s-maxage|max-age)=(?<time>[0-9]+)\");\n\n    private Path cacheData(HttpResponse<?> response, ExceptionalSupplier<CacheResult, IOException> cacheSupplier) throws IOException {\n        String eTag = response.headers().firstValue(\"etag\").orElse(null);\n        if (StringUtils.isBlank(eTag)) return null;\n        URI uri = NetworkUtils.dropQuery(response.uri());\n        long expires = 0L;\n\n        expires:\n        try {\n            String cacheControl = response.headers().firstValue(\"cache-control\").orElse(null);\n            if (StringUtils.isNotBlank(cacheControl)) {\n                if (cacheControl.contains(\"no-store\"))\n                    return null;\n\n                Matcher matcher = MAX_AGE.matcher(cacheControl);\n                if (matcher.find()) {\n                    long seconds = Long.parseLong(matcher.group(\"time\"));\n                    expires = Instant.now().plusSeconds(seconds).toEpochMilli();\n                    break expires;\n                }\n            }\n\n            String expiresHeader = response.headers().firstValue(\"expires\").orElse(null);\n            if (StringUtils.isNotBlank(expiresHeader)) {\n                expires = ZonedDateTime.parse(expiresHeader.trim(), DateTimeFormatter.RFC_1123_DATE_TIME)\n                        .toInstant().toEpochMilli();\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to parse expires time\", e);\n        }\n\n        String lastModified = response.headers().firstValue(\"last-modified\").orElse(null);\n\n        CacheResult cacheResult = cacheSupplier.get();\n        ETagItem eTagItem = new ETagItem(uri.toString(),\n                eTag,\n                cacheResult.hash,\n                Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(),\n                lastModified,\n                expires);\n        lock.writeLock().lock();\n        try {\n            index.compute(uri, updateEntity(eTagItem, true));\n            saveETagIndex();\n        } finally {\n            lock.writeLock().unlock();\n        }\n        return cacheResult.cachedFile;\n    }\n\n    private static final class CacheResult {\n        public String hash;\n        public Path cachedFile;\n\n        public CacheResult(String hash, Path cachedFile) {\n            this.hash = hash;\n            this.cachedFile = cachedFile;\n        }\n    }\n\n    private BiFunction<URI, ETagItem, ETagItem> updateEntity(ETagItem newItem, boolean force) {\n        return (key, oldItem) -> {\n            if (oldItem == null) {\n                return newItem;\n            } else if (force || oldItem.compareTo(newItem) < 0) {\n                if (!oldItem.hash.equalsIgnoreCase(newItem.hash)) {\n                    Path cached = getFile(SHA1, oldItem.hash);\n                    try {\n                        Files.deleteIfExists(cached);\n                    } catch (IOException e) {\n                        LOG.warning(\"Cannot delete old file\");\n                    }\n                }\n                return newItem;\n            } else {\n                return oldItem;\n            }\n        };\n    }\n\n    @SafeVarargs\n    private LinkedHashMap<URI, ETagItem> joinETagIndexes(Collection<ETagItem>... indexes) {\n        var eTags = new LinkedHashMap<URI, ETagItem>();\n        for (Collection<ETagItem> eTagItems : indexes) {\n            if (eTagItems != null) {\n                for (ETagItem eTag : eTagItems) {\n                    eTags.compute(NetworkUtils.toURI(eTag.url), updateEntity(eTag, false));\n                }\n            }\n        }\n        return eTags;\n    }\n\n    public void saveETagIndex() throws IOException {\n        try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);\n             @SuppressWarnings(\"unused\") FileLock lock = channel.lock()) {\n            FileTime lastModified = Lang.ignoringException(() -> Files.getLastModifiedTime(indexFile));\n            if (indexFileLastModified == null || lastModified == null || indexFileLastModified.compareTo(lastModified) < 0) {\n                try {\n                    ETagIndex indexOnDisk = GSON.fromJson(\n                            // Should not be closed\n                            new BufferedReader(Channels.newReader(channel, UTF_8)),\n                            ETagIndex.class\n                    );\n                    if (indexOnDisk != null) {\n                        index = joinETagIndexes(index.values(), indexOnDisk.eTag);\n                        indexFileLastModified = lastModified;\n                    }\n                } catch (JsonSyntaxException ignored) {\n                }\n            }\n\n            channel.truncate(0);\n            BufferedWriter writer = new BufferedWriter(Channels.newWriter(channel, UTF_8));\n            JsonUtils.GSON.toJson(new ETagIndex(index.values()), writer);\n            writer.flush();\n            channel.force(true);\n\n            this.indexFileLastModified = Lang.ignoringException(() -> Files.getLastModifiedTime(indexFile));\n        }\n    }\n\n    private static final class ETagIndex {\n        private final Collection<ETagItem> eTag;\n\n        public ETagIndex() {\n            this.eTag = new HashSet<>();\n        }\n\n        public ETagIndex(Collection<ETagItem> eTags) {\n            this.eTag = new HashSet<>(eTags);\n        }\n    }\n\n    private static final class ETagItem {\n        private final String url;\n        private final String eTag;\n        private final String hash;\n        @SerializedName(\"local\")\n        private final long localLastModified;\n        @SerializedName(\"remote\")\n        private final String remoteLastModified;\n        private final long expires;\n\n        /**\n         * For Gson.\n         */\n        public ETagItem() {\n            this(null, null, null, 0, null, 0L);\n        }\n\n        public ETagItem(String url, String eTag, String hash, long localLastModified, String remoteLastModified, long expires) {\n            this.url = url;\n            this.eTag = eTag;\n            this.hash = hash;\n            this.localLastModified = localLastModified;\n            this.remoteLastModified = remoteLastModified;\n            this.expires = expires;\n        }\n\n        public long getExpires() {\n            return expires;\n        }\n\n        public int compareTo(ETagItem other) {\n            if (!url.equals(other.url) && !NetworkUtils.toURI(url).equals(NetworkUtils.toURI(other.url)))\n                throw new IllegalArgumentException();\n\n            ZonedDateTime thisTime = Lang.ignoringException(() -> ZonedDateTime.parse(remoteLastModified, DateTimeFormatter.RFC_1123_DATE_TIME), null);\n            ZonedDateTime otherTime = Lang.ignoringException(() -> ZonedDateTime.parse(other.remoteLastModified, DateTimeFormatter.RFC_1123_DATE_TIME), null);\n            if (thisTime == null && otherTime == null) return 0;\n            else if (thisTime == null) return 1;\n            else if (otherTime == null) return -1;\n            else return thisTime.compareTo(otherTime);\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            return o instanceof ETagItem that\n                    && localLastModified == that.localLastModified\n                    && Objects.equals(url, that.url)\n                    && Objects.equals(eTag, that.eTag)\n                    && Objects.equals(hash, that.hash)\n                    && Objects.equals(remoteLastModified, that.remoteLastModified)\n                    && this.expires == that.expires;\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(url, eTag, hash, localLastModified, remoteLastModified, expires);\n        }\n\n        @Override\n        public String toString() {\n            return \"ETagItem[\" +\n                    \"url='\" + url + '\\'' +\n                    \", eTag='\" + eTag + '\\'' +\n                    \", hash='\" + hash + '\\'' +\n                    \", localLastModified=\" + localLastModified +\n                    \", remoteLastModified='\" + remoteLastModified + '\\'' +\n                    \", expires=\" + expires +\n                    ']';\n        }\n    }\n\n    private static CacheRepository instance = new CacheRepository();\n\n    public static CacheRepository getInstance() {\n        return instance;\n    }\n\n    public static void setInstance(CacheRepository instance) {\n        CacheRepository.instance = instance;\n    }\n\n    public static final String SHA1 = \"SHA-1\";\n\n    public static class CacheExpiredException extends IOException {\n        private final long expires;\n\n        public CacheExpiredException(long expires) {\n            this.expires = expires;\n        }\n\n        public CacheExpiredException(String message, long expires) {\n            super(message);\n            this.expires = expires;\n        }\n\n        public long getExpires() {\n            return expires;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/CircularArrayList.java",
    "content": "package org.jackhuang.hmcl.util;\n\nimport org.jetbrains.annotations.Contract;\n\nimport java.util.*;\n\n/**\n * @author Glavo\n */\n@SuppressWarnings(\"unchecked\")\npublic final class CircularArrayList<E> extends AbstractList<E> implements RandomAccess {\n\n    private static final int DEFAULT_CAPACITY = 10;\n    private static final Object[] EMPTY_ARRAY = new Object[0];\n\n    private Object[] elements;\n    private int begin = -1;\n    private int end = 0;\n\n    public CircularArrayList() {\n        this.elements = EMPTY_ARRAY;\n    }\n\n    public CircularArrayList(int initialCapacity) {\n        if (initialCapacity < 0) {\n            throw new IllegalArgumentException(\"illegal initialCapacity: \" + initialCapacity);\n        }\n\n        this.elements = initialCapacity == 0 ? EMPTY_ARRAY : new Object[initialCapacity];\n    }\n\n    private static int inc(int i, int capacity) {\n        return i + 1 >= capacity ? 0 : i + 1;\n    }\n\n    private static int inc(int i, int distance, int capacity) {\n        if ((i += distance) - capacity >= 0) {\n            i -= capacity;\n        }\n\n        return i;\n    }\n\n    private static int dec(int i, int capacity) {\n        return i - 1 < 0 ? capacity - 1 : i - 1;\n    }\n\n    private static int sub(int i, int distance, int capacity) {\n        if ((i -= distance) < 0) {\n            i += capacity;\n        }\n        return i;\n    }\n\n    private void grow() {\n        grow(elements.length + 1);\n    }\n\n    private void grow(int minCapacity) {\n        final int oldCapacity = elements.length;\n        final int size = size();\n        final int newCapacity = newCapacity(oldCapacity, minCapacity);\n\n        final Object[] newElements;\n        if (size == 0) {\n            newElements = new Object[newCapacity];\n        } else if (begin < end) {\n            newElements = Arrays.copyOf(elements, newCapacity, Object[].class);\n        } else {\n            newElements = new Object[newCapacity];\n            System.arraycopy(elements, begin, newElements, 0, elements.length - begin);\n            System.arraycopy(elements, 0, newElements, elements.length - begin, end);\n            begin = 0;\n            end = size;\n        }\n        this.elements = newElements;\n    }\n\n    private static int newCapacity(int oldCapacity, int minCapacity) {\n        return oldCapacity == 0\n                ? Math.max(DEFAULT_CAPACITY, minCapacity)\n                : Math.max(Math.max(oldCapacity, minCapacity), oldCapacity + (oldCapacity >> 1));\n    }\n\n    private static void checkElementIndex(int index, int size) throws IndexOutOfBoundsException {\n        if (index < 0 || index >= size) {\n            // Optimized for execution by hotspot\n            checkElementIndexFailed(index, size);\n        }\n    }\n\n    @Contract(\"_, _ -> fail\")\n    private static void checkElementIndexFailed(int index, int size) {\n        if (size < 0) {\n            throw new IllegalArgumentException(\"size(\" + size + \") < 0\");\n        }\n        if (index < 0) {\n            throw new IndexOutOfBoundsException(\"index(\" + index + \") < 0\");\n        }\n        if (index >= size) {\n            throw new IndexOutOfBoundsException(\"index(\" + index + \") >= size(\" + size + \")\");\n        }\n        throw new AssertionError();\n    }\n\n    private static void checkPositionIndex(int index, int size) throws IndexOutOfBoundsException {\n        if (index < 0 || index > size) {\n            // Optimized for execution by hotspot\n            checkPositionIndexFailed(index, size);\n        }\n    }\n\n    @Contract(\"_, _ -> fail\")\n    private static void checkPositionIndexFailed(int index, int size) {\n        if (size < 0) {\n            throw new IllegalArgumentException(\"size(\" + size + \") < 0\");\n        }\n        if (index < 0) {\n            throw new IndexOutOfBoundsException(\"index(\" + index + \") < 0\");\n        }\n        if (index > size) {\n            throw new IndexOutOfBoundsException(\"index(\" + index + \") > size(\" + size + \")\");\n        }\n        throw new AssertionError();\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return begin == -1;\n    }\n\n    @Override\n    public int size() {\n        if (isEmpty()) {\n            return 0;\n        } else if (begin < end) {\n            return end - begin;\n        } else {\n            return elements.length - begin + end;\n        }\n    }\n\n    @Override\n    public E get(int index) {\n        if (isEmpty()) {\n            throw new IndexOutOfBoundsException(\"Index out of range: \" + index);\n        } else if (begin < end) {\n            checkElementIndex(index, end - begin);\n            return (E) elements[begin + index];\n        } else {\n            checkElementIndex(index, elements.length - begin + end);\n            return (E) elements[inc(begin, index, elements.length)];\n        }\n    }\n\n    @Override\n    public E set(int index, E element) {\n        int arrayIndex;\n        if (isEmpty()) {\n            throw new IndexOutOfBoundsException();\n        } else if (begin < end) {\n            checkElementIndex(index, end - begin);\n            arrayIndex = begin + index;\n        } else {\n            final int size = elements.length - begin + end;\n            checkElementIndex(index, size);\n            arrayIndex = inc(begin, index, elements.length);\n        }\n\n        E oldValue = (E) elements[arrayIndex];\n        elements[arrayIndex] = element;\n        return oldValue;\n    }\n\n    @Override\n    public void add(int index, E element) {\n        if (index == 0) {\n            addFirst(element);\n            return;\n        }\n\n        final int oldSize = size();\n        if (index == oldSize) {\n            addLast(element);\n            return;\n        }\n\n        checkPositionIndex(index, oldSize);\n\n        if (oldSize == elements.length) {\n            grow();\n        }\n\n        if (begin < end) {\n            final int targetIndex = begin + index;\n            if (end < elements.length) {\n                System.arraycopy(elements, targetIndex, elements, targetIndex + 1, end - targetIndex);\n                end++;\n            } else {\n                System.arraycopy(elements, begin, elements, begin - 1, targetIndex - begin + 1);\n                begin--;\n            }\n            elements[targetIndex] = element;\n        } else {\n            int targetIndex = inc(begin, index, elements.length);\n            if (targetIndex <= end) {\n                System.arraycopy(elements, targetIndex, elements, targetIndex + 1, end - targetIndex);\n                elements[targetIndex] = element;\n                end++;\n            } else {\n                System.arraycopy(elements, begin, elements, begin - 1, targetIndex - begin);\n                elements[targetIndex - 1] = element;\n                begin--;\n            }\n        }\n    }\n\n    @Override\n    public E remove(int index) {\n        final int oldSize = size();\n        checkElementIndex(index, oldSize);\n\n        if (index == 0) {\n            return removeFirst();\n        }\n\n        if (index == oldSize - 1) {\n            return removeLast();\n        }\n\n        final Object res;\n\n        if (begin < end) {\n            final int targetIndex = begin + index;\n            res = elements[targetIndex];\n            System.arraycopy(elements, targetIndex + 1, elements, targetIndex, end - targetIndex - 1);\n            end--;\n        } else {\n            final int targetIndex = inc(begin, index, elements.length);\n            res = elements[targetIndex];\n            if (targetIndex < end) {\n                System.arraycopy(elements, targetIndex + 1, elements, targetIndex, end - targetIndex - 1);\n                end--;\n            } else {\n                System.arraycopy(elements, begin, elements, begin + 1, targetIndex - begin);\n                begin = inc(begin, elements.length);\n            }\n        }\n\n        return (E) res;\n    }\n\n    @Override\n    public void clear() {\n        if (isEmpty()) {\n            return;\n        }\n\n        if (begin < end) {\n            Arrays.fill(elements, begin, end, null);\n        } else {\n            Arrays.fill(elements, 0, end, null);\n            Arrays.fill(elements, begin, elements.length, null);\n        }\n\n        begin = -1;\n        end = 0;\n    }\n\n    // Deque\n\n    public void addFirst(E e) {\n        final int oldSize = size();\n        if (oldSize == elements.length) {\n            grow();\n        }\n\n        if (oldSize == 0) {\n            begin = elements.length - 1;\n        } else {\n            begin = dec(begin, elements.length);\n        }\n        elements[begin] = e;\n    }\n\n    public void addLast(E e) {\n        final int oldSize = size();\n        if (oldSize == elements.length) {\n            grow();\n        }\n        elements[end] = e;\n        end = inc(end, elements.length);\n\n        if (oldSize == 0) {\n            begin = 0;\n        }\n    }\n\n    public E removeFirst() {\n        final int oldSize = size();\n        if (oldSize == 0) {\n            throw new NoSuchElementException();\n        }\n\n        Object res = elements[begin];\n        elements[begin] = null;\n\n        if (oldSize == 1) {\n            begin = -1;\n            end = 0;\n        } else {\n            begin = inc(begin, elements.length);\n        }\n        return (E) res;\n    }\n\n    public E removeLast() {\n        final int oldSize = size();\n        if (oldSize == 0) {\n            throw new NoSuchElementException();\n        }\n        final int lastIdx = dec(end, elements.length);\n        E res = (E) elements[lastIdx];\n        elements[lastIdx] = null;\n\n        if (oldSize == 1) {\n            begin = -1;\n            end = 0;\n        } else {\n            end = lastIdx;\n        }\n        return res;\n    }\n\n    public E getFirst() {\n        if (isEmpty())\n            throw new NoSuchElementException();\n\n        return get(0);\n    }\n\n    public E getLast() {\n        if (isEmpty())\n            throw new NoSuchElementException();\n\n        return get(size() - 1);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\n/**\n * Constants.\n *\n * @author huangyuhui\n */\npublic final class Constants {\n\n    private Constants() {\n    }\n\n    public static final String DEFAULT_LIBRARY_URL = \"https://libraries.minecraft.net/\";\n    public static final String DEFAULT_VERSION_DOWNLOAD_URL = \"https://bmclapi2.bangbang93.com/versions/\";\n    public static final String DEFAULT_INDEX_URL = \"https://launchermeta.mojang.com/v1/packages/\";\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/DataSizeUnit.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.text.DecimalFormat;\n\n/**\n * @author Glavo\n */\npublic enum DataSizeUnit {\n    BYTES,\n    KILOBYTES,\n    MEGABYTES,\n    GIGABYTES,\n    TERABYTES;\n\n    private static final DataSizeUnit[] VALUES = values();\n    private static final DecimalFormat FORMAT = new DecimalFormat(\"#.##\");\n\n    public static String format(long bytes) {\n        for (int i = VALUES.length - 1; i > 0; i--) {\n            DataSizeUnit unit = VALUES[i];\n            if (bytes >= unit.bytes) {\n                return unit.formatBytes(bytes);\n            }\n        }\n\n        return bytes == 1 ? \"1 byte\" : bytes + \" bytes\";\n    }\n\n    private final long bytes = 1L << (ordinal() * 10);\n\n    private final char abbreviationChar = name().charAt(0);\n    private final String abbreviation = abbreviationChar == 'B' ? \"B\" : abbreviationChar + \"iB\";\n\n    public double convertFromBytes(long bytes) {\n        return (double) bytes / this.bytes;\n    }\n\n    public long convertToBytes(double amount) {\n        return (long) (amount * this.bytes);\n    }\n\n    private String format(double amount) {\n        return FORMAT.format(amount) + \" \" + this.abbreviation;\n    }\n\n    public String formatBytes(long bytes) {\n        return format(convertFromBytes(bytes));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/DigestUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.HexFormat;\n\n/**\n * @author huangyuhui\n */\npublic final class DigestUtils {\n\n    private DigestUtils() {\n    }\n\n    private static final int STREAM_BUFFER_LENGTH = 1024;\n\n    public static boolean isSha1Digest(String digest) {\n        if (digest == null || digest.length() != 40) return false;\n\n        for (int i = 0; i < digest.length(); i++) {\n            char ch = digest.charAt(i);\n            if ((ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') && (ch < 'A' || ch > 'F')) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    public static MessageDigest getDigest(String algorithm) {\n        try {\n            return MessageDigest.getInstance(algorithm);\n        } catch (NoSuchAlgorithmException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    public static byte[] digest(String algorithm, byte[] data) {\n        return getDigest(algorithm).digest(data);\n    }\n\n    public static byte[] digest(String algorithm, Path path) throws IOException {\n        try (InputStream is = Files.newInputStream(path)) {\n            return digest(algorithm, is);\n        }\n    }\n\n    public static byte[] digest(String algorithm, InputStream data) throws IOException {\n        return digest(getDigest(algorithm), data);\n    }\n\n    public static byte[] digest(MessageDigest digest, InputStream data) throws IOException {\n        return updateDigest(digest, data).digest();\n    }\n\n    public static String digestToString(String algorithm, byte[] data) throws IOException {\n        return HexFormat.of().formatHex(digest(algorithm, data));\n    }\n\n    public static String digestToString(String algorithm, Path path) throws IOException {\n        return HexFormat.of().formatHex(digest(algorithm, path));\n    }\n\n    public static String digestToString(String algorithm, InputStream data) throws IOException {\n        return HexFormat.of().formatHex(digest(algorithm, data));\n    }\n\n    private static final ThreadLocal<byte[]> threadLocalBuffer = ThreadLocal.withInitial(() -> new byte[STREAM_BUFFER_LENGTH]);\n\n    public static MessageDigest updateDigest(MessageDigest digest, InputStream data) throws IOException {\n        byte[] buffer = threadLocalBuffer.get();\n        int read = data.read(buffer, 0, STREAM_BUFFER_LENGTH);\n\n        while (read > -1) {\n            digest.update(buffer, 0, read);\n            read = data.read(buffer, 0, STREAM_BUFFER_LENGTH);\n        }\n\n        return digest;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/FXThread.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.lang.annotation.*;\n\n/**\n * Mark a method that can only be called on the JavaFX Application Thread,\n * or mark a field that can only be read/modified on the JavaFX Application Thread.\n *\n * @author Glavo\n */\n@Documented\n@Target({ElementType.METHOD, ElementType.FIELD})\n@Retention(RetentionPolicy.SOURCE)\npublic @interface FXThread {\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/FutureCallback.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\n@FunctionalInterface\npublic interface FutureCallback<T> {\n\n    /// Callback of future, called after future finishes.\n    /// This callback gives the feedback whether the result of future is acceptable or not,\n    /// if not, giving the reason, and future will be relaunched when necessary.\n    ///\n    /// @param result  result of the future\n    /// @param handler handler to accept or reject the result\n    void call(T result, ResultHandler handler);\n\n    interface ResultHandler {\n\n        /// Accept the result.\n        void resolve();\n\n        /// Reject the result with given reason.\n        void reject(String reason);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/Holder.java",
    "content": "package org.jackhuang.hmcl.util;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\n\nimport java.util.Objects;\n\npublic final class Holder<T> implements InvalidationListener {\n    public T value;\n\n    public Holder() {\n    }\n\n    public Holder(T value) {\n        this.value = value;\n    }\n\n    @Override\n    public void invalidated(Observable observable) {\n        // no-op\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hashCode(value);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj)\n            return true;\n\n        if (!(obj instanceof Holder))\n            return false;\n\n        return Objects.equals(this.value, ((Holder<?>) obj).value);\n    }\n\n    @Override\n    public String toString() {\n        return \"Holder[\" + value + \"]\";\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/Immutable.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Target;\n\n/**\n * Mark if instances of the class are immutable.\n *\n * @author huangyuhui\n */\n@Target(ElementType.TYPE)\npublic @interface Immutable {\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/InfiniteSizeList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n\npackage org.jackhuang.hmcl.util;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\n\npublic final class InfiniteSizeList<T> extends ArrayList<T> {\n    private int actualSize;\n\n    public InfiniteSizeList(int initialCapacity) {\n        super(initialCapacity);\n\n        this.actualSize = 0;\n    }\n\n    public InfiniteSizeList() {\n        this.actualSize = 0;\n    }\n\n    public InfiniteSizeList(@NotNull Collection<? extends T> c) {\n        super(c);\n\n        this.actualSize = 0;\n        for (int i = super.size() - 1; i >= 0; i--) {\n            if (super.get(i) != null) {\n                actualSize = i + 1;\n                break;\n            }\n        }\n    }\n\n    @Override\n    public T get(int index) {\n        if (index >= super.size()) {\n            return null;\n        }\n\n        return super.get(index);\n    }\n\n    @Override\n    public T set(int index, T element) {\n        if (element == null) { // The element is null.\n            if (index >= super.size()) {\n                return null; // The element (actually null) is out of the allocated size.\n            }\n\n            T previous = super.get(index);\n            if (previous != null) { // !null -> null\n                super.set(index, null);\n\n                if (index == this.actualSize - 1) { // Recalculate the actualSize.\n                    this.actualSize = 0;\n\n                    for (int i = index - 1; i >= 0; i--) {\n                        if (super.get(i) != null) {\n                            this.actualSize = i + 1;\n                            break;\n                        }\n                    }\n                }\n\n                return previous;\n            } else { // null -> null\n                return null;\n            }\n        } else {\n            // The element isn't null.\n\n            if (index >= super.size()) {\n                allocate0(index);\n            }\n\n            T previous = super.get(index);\n            super.set(index, element);\n            if (previous != null) { // !null -> !null\n                return previous;\n            } else { // null -> !null\n                if (index >= this.actualSize) {\n                    this.actualSize = index + 1;\n                }\n                return null;\n            }\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private void allocate0(int index) {\n        this.addAll(Lang.immutableListOf((T[])new Object[index + 1 - super.size()]));\n    }\n\n    @Override\n    public int size() {\n        return actualSize;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/InvocationDispatcher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.util.concurrent.Executor;\nimport java.util.function.Consumer;\n\n/// When [#accept(T)] is called, this class invokes the handler on another thread.\n/// If [#accept(T)] is called more than one time before the handler starts processing,\n/// the handler will only be invoked once, taking the latest argument as its input.\n///\n/// @author yushijinhun\npublic final class InvocationDispatcher<T> implements Consumer<T> {\n\n    private static final VarHandle PENDING_ARG_HANDLE;\n    static {\n        try {\n            PENDING_ARG_HANDLE = MethodHandles.lookup()\n                    .findVarHandle(InvocationDispatcher.class, \"pendingArg\", Holder.class);\n        } catch (NoSuchFieldException | IllegalAccessException e) {\n            throw new ExceptionInInitializerError(e);\n        }\n    }\n\n    /// @param executor The executor must dispatch all tasks to a single thread.\n    public static <T> InvocationDispatcher<T> runOn(Executor executor, Consumer<T> action) {\n        return new InvocationDispatcher<>(executor, action);\n    }\n\n    private final Executor executor;\n    private final Consumer<T> action;\n\n    /// @see #PENDING_ARG_HANDLE\n    @SuppressWarnings(\"unused\")\n    private volatile Holder<T> pendingArg;\n\n    private InvocationDispatcher(Executor executor, Consumer<T> action) {\n        this.executor = executor;\n        this.action = action;\n    }\n\n    @Override\n    public void accept(T t) {\n        if (PENDING_ARG_HANDLE.getAndSet(this, new Holder<>(t)) == null) {\n            executor.execute(() -> {\n                @SuppressWarnings(\"unchecked\")\n                var holder = (Holder<T>) PENDING_ARG_HANDLE.getAndSet(this, (Holder<T>) null);\n\n                // If the executor supports multiple underlying threads,\n                // we need to add synchronization, but for now we can omit it :)\n                // synchronized (InvocationDispatcher.this)\n                action.accept(holder.value);\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.security.*;\nimport java.util.Base64;\n\npublic final class KeyUtils {\n    private KeyUtils() {\n    }\n\n    public static KeyPair generateKey() {\n        try {\n            KeyPairGenerator gen = KeyPairGenerator.getInstance(\"RSA\");\n            gen.initialize(4096, new SecureRandom());\n            return gen.genKeyPair();\n        } catch (GeneralSecurityException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static String toPEMPublicKey(PublicKey key) {\n        byte[] encoded = key.getEncoded();\n        return \"-----BEGIN PUBLIC KEY-----\\n\" +\n                Base64.getMimeEncoder(76, new byte[]{'\\n'}).encodeToString(encoded) +\n                \"\\n-----END PUBLIC KEY-----\\n\";\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\n\n/**\n * @author Glavo\n */\npublic final class KeyValuePairUtils {\n    public static Map<String, String> loadProperties(Path file) throws IOException {\n        try (BufferedReader reader = Files.newBufferedReader(file)) {\n            return loadProperties(reader);\n        }\n    }\n\n    public static Map<String, String> loadProperties(BufferedReader reader) throws IOException {\n        try {\n            return loadProperties(reader.lines().iterator());\n        } catch (UncheckedIOException e) {\n            throw e.getCause();\n        }\n    }\n\n    public static Map<String, String> loadProperties(Iterator<String> lineIterator) {\n        Map<String, String> result = new LinkedHashMap<>();\n        while (lineIterator.hasNext()) {\n            String line = lineIterator.next();\n\n            if (line.startsWith(\"#\"))\n                continue;\n\n            int idx = line.indexOf('=');\n            if (idx <= 0)\n                continue;\n\n            String name = line.substring(0, idx);\n            String value;\n\n            if (line.length() > idx + 2 && line.charAt(idx + 1) == '\"' && line.charAt(line.length() - 1) == '\"') {\n                if (line.indexOf('\\\\', idx + 1) < 0) {\n                    value = line.substring(idx + 2, line.length() - 1);\n                } else {\n                    StringBuilder builder = new StringBuilder();\n                    for (int i = idx + 2, end = line.length() - 1; i < end; i++) {\n                        char ch = line.charAt(i);\n                        if (ch == '\\\\' && i < end - 1) {\n                            char nextChar = line.charAt(++i);\n                            switch (nextChar) {\n                                case 'n':\n                                    builder.append('\\n');\n                                    break;\n                                case 'r':\n                                    builder.append('\\r');\n                                    break;\n                                case 't':\n                                    builder.append('\\t');\n                                    break;\n                                case 'f':\n                                    builder.append('\\f');\n                                    break;\n                                case 'b':\n                                    builder.append('\\b');\n                                    break;\n                                default:\n                                    builder.append(nextChar);\n                                    break;\n                            }\n                        } else {\n                            builder.append(ch);\n                        }\n                    }\n                    value = builder.toString();\n                }\n            } else {\n                value = line.substring(idx + 1);\n            }\n\n            result.put(name, value);\n        }\n        return result;\n    }\n\n    public static Map<String, String> loadPairs(Path file) throws IOException {\n        try (BufferedReader reader = Files.newBufferedReader(file)) {\n            return loadPairs(reader);\n        }\n    }\n\n    public static Map<String, String> loadPairs(BufferedReader reader) throws IOException {\n        try {\n            return loadPairs(reader.lines().iterator());\n        } catch (UncheckedIOException e) {\n            throw e.getCause();\n        }\n    }\n\n    public static Map<String, String> loadPairs(Iterator<String> lineIterator) {\n        Map<String, String> result = new LinkedHashMap<>();\n        while (lineIterator.hasNext()) {\n            String line = lineIterator.next();\n\n            int idx = line.indexOf(':');\n            if (idx > 0) {\n                String name = line.substring(0, idx).trim();\n                String value = line.substring(idx + 1).trim();\n                result.put(name, value);\n            }\n        }\n        return result;\n    }\n\n    public static List<Map<String, String>> loadList(Path file) throws IOException {\n        try (BufferedReader reader = Files.newBufferedReader(file)) {\n            return loadList(reader);\n        }\n    }\n\n    public static List<Map<String, String>> loadList(BufferedReader reader) throws IOException {\n        try {\n            return loadList(reader.lines().iterator());\n        } catch (UncheckedIOException e) {\n            throw e.getCause();\n        }\n    }\n\n    public static List<Map<String, String>> loadList(Iterator<String> lineIterator) {\n        ArrayList<Map<String, String>> result = new ArrayList<>();\n        Map<String, String> current = new LinkedHashMap<>();\n\n        while (lineIterator.hasNext()) {\n            String line = lineIterator.next();\n            int idx = line.indexOf(':');\n\n            if (idx < 0) {\n                if (!current.isEmpty()) {\n                    result.add(current);\n                    current = new LinkedHashMap<>();\n                }\n                continue;\n            }\n\n            String name = line.substring(0, idx).trim();\n            String value = line.substring(idx + 1).trim();\n\n            current.put(name, value);\n        }\n\n        if (!current.isEmpty())\n            result.add(current);\n\n        return result;\n    }\n\n    private KeyValuePairUtils() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.util.function.*;\n\nimport java.util.*;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.*;\nimport java.util.stream.Stream;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class Lang {\n\n    private Lang() {\n    }\n\n    public static <T> T requireNonNullElse(T value, T defaultValue) {\n        return value != null ? value : defaultValue;\n    }\n\n    public static <T> T requireNonNullElseGet(T value, Supplier<? extends T> defaultValue) {\n        return value != null ? value : defaultValue.get();\n    }\n\n    public static <T, U> U requireNonNullElseGet(T value, Function<? super T, ? extends U> mapper, Supplier<? extends U> defaultValue) {\n        return value != null ? mapper.apply(value) : defaultValue.get();\n    }\n\n    /**\n     * Construct a mutable map by given key-value pairs.\n     *\n     * @param pairs entries in the new map\n     * @param <K>   the type of keys\n     * @param <V>   the type of values\n     * @return the map which contains data in {@code pairs}.\n     */\n    @SafeVarargs\n    public static <K, V> Map<K, V> mapOf(Pair<K, V>... pairs) {\n        return mapOf(Arrays.asList(pairs));\n    }\n\n    /**\n     * Construct a mutable map by given key-value pairs.\n     *\n     * @param pairs entries in the new map\n     * @param <K>   the type of keys\n     * @param <V>   the type of values\n     * @return the map which contains data in {@code pairs}.\n     */\n    public static <K, V> Map<K, V> mapOf(Iterable<Pair<K, V>> pairs) {\n        Map<K, V> map = new LinkedHashMap<>();\n        for (Pair<K, V> pair : pairs)\n            map.put(pair.getKey(), pair.getValue());\n        return map;\n    }\n\n    @SafeVarargs\n    public static <T> List<T> immutableListOf(T... elements) {\n        return Collections.unmodifiableList(Arrays.asList(elements));\n    }\n\n    public static <T extends Comparable<T>> T clamp(T min, T val, T max) {\n        if (val.compareTo(min) < 0) return min;\n        else if (val.compareTo(max) > 0) return max;\n        else return val;\n    }\n\n    public static double clamp(double min, double val, double max) {\n        return Math.max(min, Math.min(val, max));\n    }\n\n    public static int clamp(int min, int val, int max) {\n        return Math.max(min, Math.min(val, max));\n    }\n\n    public static boolean test(ExceptionalRunnable<?> r) {\n        try {\n            r.run();\n            return true;\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    public static <E extends Exception> boolean test(ExceptionalSupplier<Boolean, E> r) {\n        try {\n            return r.get();\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    public static <T> T ignoringException(ExceptionalSupplier<T, ?> supplier) {\n        return ignoringException(supplier, null);\n    }\n\n    public static <T> T ignoringException(ExceptionalSupplier<T, ?> supplier, T defaultValue) {\n        try {\n            return supplier.get();\n        } catch (Exception ignore) {\n            return defaultValue;\n        }\n    }\n\n    /**\n     * Cast {@code obj} to V dynamically.\n     *\n     * @param obj   the object reference to be cast.\n     * @param clazz the class reference of {@code V}.\n     * @param <V>   the type that {@code obj} is being cast to.\n     * @return {@code obj} in the type of {@code V}.\n     */\n    public static <V> Optional<V> tryCast(Object obj, Class<V> clazz) {\n        if (clazz.isInstance(obj)) {\n            return Optional.of(clazz.cast(obj));\n        } else {\n            return Optional.empty();\n        }\n    }\n\n    public static <T> T getOrDefault(List<T> a, int index, T defaultValue) {\n        return index < 0 || index >= a.size() ? defaultValue : a.get(index);\n    }\n\n    public static <T> T merge(T a, T b, BinaryOperator<T> operator) {\n        if (a == null) return b;\n        if (b == null) return a;\n        return operator.apply(a, b);\n    }\n\n    public static <T> List<T> removingDuplicates(List<T> list) {\n        LinkedHashSet<T> set = new LinkedHashSet<>(list.size());\n        set.addAll(list);\n        return new ArrayList<>(set);\n    }\n\n    /**\n     * Join two collections into one list.\n     *\n     * @param a   one collection, to be joined.\n     * @param b   another collection to be joined.\n     * @param <T> the super type of elements in {@code a} and {@code b}\n     * @return the joint collection\n     */\n    public static <T> List<T> merge(Collection<? extends T> a, Collection<? extends T> b) {\n        List<T> result = new ArrayList<>();\n        if (a != null)\n            result.addAll(a);\n        if (b != null)\n            result.addAll(b);\n        return result;\n    }\n\n    public static <T> List<T> copyList(List<T> list) {\n        return list == null ? null : list.isEmpty() ? null : new ArrayList<>(list);\n    }\n\n    public static <T> int indexWhere(List<T> list, Predicate<T> predicate) {\n        int idx = 0;\n        for (T value : list) {\n            if (predicate.test(value))\n                return idx;\n            idx++;\n        }\n        return -1;\n    }\n\n    public static void executeDelayed(Runnable runnable, TimeUnit timeUnit, long timeout, boolean isDaemon) {\n        thread(() -> {\n            try {\n                timeUnit.sleep(timeout);\n                runnable.run();\n            } catch (InterruptedException ignore) {\n            }\n\n        }, null, isDaemon);\n    }\n\n    /**\n     * Start a thread invoking {@code runnable} immediately.\n     *\n     * @param runnable code to run.\n     * @return the reference of the started thread\n     */\n    public static Thread thread(Runnable runnable) {\n        return thread(runnable, null);\n    }\n\n    /**\n     * Start a thread invoking {@code runnable} immediately.\n     *\n     * @param runnable code to run\n     * @param name     the name of thread\n     * @return the reference of the started thread\n     */\n    public static Thread thread(Runnable runnable, String name) {\n        return thread(runnable, name, false);\n    }\n\n    /**\n     * Start a thread invoking {@code runnable} immediately.\n     *\n     * @param runnable code to run\n     * @param name     the name of thread\n     * @param isDaemon true if thread will be terminated when only daemon threads are running.\n     * @return the reference of the started thread\n     */\n    public static Thread thread(Runnable runnable, String name, boolean isDaemon) {\n        Thread thread = new Thread(runnable);\n        if (isDaemon)\n            thread.setDaemon(true);\n        if (name != null)\n            thread.setName(name);\n        thread.start();\n        return thread;\n    }\n\n    public static ThreadPoolExecutor threadPool(String name, boolean daemon, int threads, long timeout, TimeUnit timeunit) {\n        ThreadPoolExecutor pool = new ThreadPoolExecutor(\n                threads, threads,\n                timeout, timeunit,\n                new LinkedBlockingQueue<>(),\n                counterThreadFactory(name, daemon));\n        pool.allowCoreThreadTimeOut(true);\n        return pool;\n    }\n\n    public static ThreadFactory counterThreadFactory(String name, boolean daemon) {\n        AtomicInteger counter = new AtomicInteger(1);\n        return r -> {\n            Thread t = new Thread(r, name + \"-\" + counter.getAndIncrement());\n            t.setDaemon(daemon);\n            return t;\n        };\n    }\n\n    public static int parseInt(Object string, int defaultValue) {\n        try {\n            return Integer.parseInt(string.toString());\n        } catch (NumberFormatException e) {\n            return defaultValue;\n        }\n    }\n\n    public static Integer toIntOrNull(Object string) {\n        try {\n            if (string == null) return null;\n            return Integer.parseInt(string.toString());\n        } catch (NumberFormatException e) {\n            return null;\n        }\n    }\n\n    public static Double toDoubleOrNull(Object string) {\n        try {\n            if (string == null) return null;\n            return Double.parseDouble(string.toString());\n        } catch (NumberFormatException e) {\n            return null;\n        }\n    }\n\n    public static Float toFloatOrNull(Object string) {\n        try {\n            if (string == null) return null;\n            return Float.parseFloat(string.toString());\n        } catch (NumberFormatException e) {\n            return null;\n        }\n    }\n\n    /**\n     * Find the first non-null reference in given list.\n     *\n     * @param t   nullable references list.\n     * @param <T> the type of nullable references\n     * @return the first non-null reference.\n     */\n    @SafeVarargs\n    public static <T> T nonNull(T... t) {\n        for (T a : t) if (a != null) return a;\n        return null;\n    }\n\n    public static <T> T apply(T t, Consumer<T> consumer) {\n        consumer.accept(t);\n        return t;\n    }\n\n    public static void rethrow(Throwable e) {\n        if (e == null)\n            return;\n        if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable\n            rethrow(e.getCause());\n        } else if (e instanceof RuntimeException) {\n            throw (RuntimeException) e;\n        } else {\n            throw new CompletionException(e);\n        }\n    }\n\n    public static Runnable wrap(ExceptionalRunnable<?> runnable) {\n        return () -> {\n            try {\n                runnable.run();\n            } catch (Exception e) {\n                rethrow(e);\n            }\n        };\n    }\n\n    public static <T> Supplier<T> wrap(ExceptionalSupplier<T, ?> supplier) {\n        return () -> {\n            try {\n                return supplier.get();\n            } catch (Exception e) {\n                rethrow(e);\n                throw new InternalError(\"Unreachable code\");\n            }\n        };\n    }\n\n    public static <T, R> Function<T, R> wrap(ExceptionalFunction<T, R, ?> fn) {\n        return t -> {\n            try {\n                return fn.apply(t);\n            } catch (Exception e) {\n                rethrow(e);\n                throw new InternalError(\"Unreachable code\");\n            }\n        };\n    }\n\n    public static <T> Consumer<T> wrapConsumer(ExceptionalConsumer<T, ?> fn) {\n        return t -> {\n            try {\n                fn.accept(t);\n            } catch (Exception e) {\n                rethrow(e);\n            }\n        };\n    }\n\n    public static <T, E> BiConsumer<T, E> wrap(ExceptionalBiConsumer<T, E, ?> fn) {\n        return (t, e) -> {\n            try {\n                fn.accept(t, e);\n            } catch (Exception ex) {\n                rethrow(ex);\n            }\n        };\n    }\n\n    @SafeVarargs\n    public static <T> Consumer<T> compose(Consumer<T>... consumers) {\n        return t -> {\n            for (Consumer<T> consumer : consumers) {\n                consumer.accept(t);\n            }\n        };\n    }\n\n    @SuppressWarnings(\"OptionalUsedAsFieldOrParameterType\")\n    public static <T> Stream<T> toStream(Optional<T> optional) {\n        return optional.map(Stream::of).orElseGet(Stream::empty);\n    }\n\n    public static <T> Iterable<T> toIterable(Enumeration<T> enumeration) {\n        if (enumeration == null) {\n            throw new NullPointerException();\n        }\n        return () -> new Iterator<T>() {\n            public boolean hasNext() {\n                return enumeration.hasMoreElements();\n            }\n\n            public T next() {\n                return enumeration.nextElement();\n            }\n\n            public void remove() {\n                throw new UnsupportedOperationException();\n            }\n        };\n    }\n\n    public static <T> Iterable<T> toIterable(Stream<T> stream) {\n        return stream::iterator;\n    }\n\n    public static <T> Iterable<T> toIterable(Iterator<T> iterator) {\n        return () -> iterator;\n    }\n\n    public static <T, U> void forEachZipped(Iterable<T> i1, Iterable<U> i2, BiConsumer<T, U> action) {\n        Iterator<T> it1 = i1.iterator();\n        Iterator<U> it2 = i2.iterator();\n        while (it1.hasNext() && it2.hasNext())\n            action.accept(it1.next(), it2.next());\n    }\n\n    public static Throwable resolveException(Throwable e) {\n        if (e instanceof ExecutionException || e instanceof CompletionException)\n            return resolveException(e.getCause());\n        else\n            return e;\n    }\n\n    /**\n     * This is a useful function to prevent exceptions being eaten when using CompletableFuture.\n     * You can write:\n     * ... .exceptionally(handleUncaught);\n     */\n    public static final Function<Throwable, Void> handleUncaught = e -> {\n        handleUncaughtException(e);\n        return null;\n    };\n\n    public static <R> R handleUncaughtException(Throwable e) {\n        Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);\n        return null;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n *\n * @author huangyuhui\n */\npublic enum Log4jLevel {\n    FATAL(1),\n    ERROR(2),\n    WARN(3),\n    INFO(4),\n    DEBUG(5),\n    TRACE(6),\n    ALL(2147483647);\n\n    private final int level;\n\n    Log4jLevel(int level) {\n        this.level = level;\n    }\n\n    public int getLevel() {\n        return level;\n    }\n\n    public boolean lessOrEqual(Log4jLevel level) {\n        return this.level <= level.level;\n    }\n\n    public static final Pattern MINECRAFT_LOGGER = Pattern.compile(\"\\\\[(?<timestamp>[0-9:]+)] \\\\[[^/]+/(?<level>[^]]+)]\");\n    public static final Pattern MINECRAFT_LOGGER_CATEGORY = Pattern.compile(\"\\\\[(?<timestamp>[0-9:]+)] \\\\[[^/]+/(?<level>[^]]+)] \\\\[(?<category>[^]]+)]\");\n    public static final String JAVA_SYMBOL = \"([a-zA-Z_$][a-zA-Z\\\\d_$]*\\\\.)+[a-zA-Z_$][a-zA-Z\\\\d_$]*\";\n\n    public static Log4jLevel guessLevel(String line) {\n        Log4jLevel level = null;\n        Matcher m = MINECRAFT_LOGGER.matcher(line);\n        if (m.find()) {\n            // New style logs from log4j\n            String levelStr = m.group(\"level\");\n            if (null != levelStr)\n                switch (levelStr) {\n                    case \"INFO\":\n                        level = INFO;\n                        break;\n                    case \"WARN\":\n                        level = WARN;\n                        break;\n                    case \"ERROR\":\n                        level = ERROR;\n                        break;\n                    case \"FATAL\":\n                        level = FATAL;\n                        break;\n                    case \"TRACE\":\n                        level = TRACE;\n                        break;\n                    case \"DEBUG\":\n                        level = DEBUG;\n                        break;\n                    default:\n                        break;\n                }\n            Matcher m2 = MINECRAFT_LOGGER_CATEGORY.matcher(line);\n            if (m2.find()) {\n                String level2Str = m2.group(\"category\");\n                if (null != level2Str)\n                    switch (level2Str) {\n                        case \"STDOUT\":\n                            level = INFO;\n                            break;\n                        case \"STDERR\":\n                            level = ERROR;\n                            break;\n                    }\n            }\n\n            if (line.contains(\"STDERR]\") || line.contains(\"[STDERR/]\")) {\n                level = ERROR;\n            }\n        } else {\n            if (line.contains(\"[INFO]\") || line.contains(\"[CONFIG]\") || line.contains(\"[FINE]\")\n                    || line.contains(\"[FINER]\") || line.contains(\"[FINEST]\"))\n                level = INFO;\n            if (line.contains(\"[SEVERE]\") || line.contains(\"[STDERR]\"))\n                level = ERROR;\n            if (line.contains(\"[WARNING]\"))\n                level = WARN;\n            if (line.contains(\"[DEBUG]\"))\n                level = DEBUG;\n        }\n        if (line.contains(\"overwriting existing\"))\n            level = FATAL;\n\n        /*if (line.contains(\"Exception in thread\")\n                || line.matches(\"\\\\s+at \" + JAVA_SYMBOL)\n                || line.matches(\"Caused by: \" + JAVA_SYMBOL)\n                || line.matches(\"([a-zA-Z_$][a-zA-Z\\\\d_$]*\\\\.)+[a-zA-Z_$]?[a-zA-Z\\\\d_$]*(Exception|Error|Throwable)\")\n                || line.matches(\"... \\\\d+ more$\"))\n            return ERROR;*/\n        return level;\n    }\n\n    public static boolean isError(Log4jLevel a) {\n        return a != null && a.lessOrEqual(Log4jLevel.ERROR);\n    }\n\n    public static Log4jLevel mergeLevel(Log4jLevel a, Log4jLevel b) {\n        if (a == null)\n            return b;\n        else if (b == null)\n            return a;\n        else\n            return a.level < b.level ? a : b;\n    }\n\n    public static boolean guessLogLineError(String log) {\n        return isError(guessLevel(log));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/MurmurHash2.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.nio.charset.StandardCharsets;\n\n/**\n * Implementation of the MurmurHash2 32-bit and 64-bit hash functions.\n *\n * <p>MurmurHash is a non-cryptographic hash function suitable for general\n * hash-based lookup. The name comes from two basic operations, multiply (MU)\n * and rotate (R), used in its inner loop. Unlike cryptographic hash functions,\n * it is not specifically designed to be difficult to reverse by an adversary,\n * making it unsuitable for cryptographic purposes.</p>\n *\n * <p>This contains a Java port of the 32-bit hash function {@code MurmurHash2}\n * and the 64-bit hash function {@code MurmurHash64A} from Austin Applyby's\n * original {@code c++} code in SMHasher.</p>\n *\n * <p>This is a re-implementation of the original C code plus some additional\n * features.</p>\n *\n * <p>This is public domain code with no copyrights. From home page of\n * <a href=\"https://github.com/aappleby/smhasher\">SMHasher</a>:</p>\n *\n * <blockquote>\n * \"All MurmurHash versions are public domain software, and the author\n * disclaims all copyright to their code.\"\n * </blockquote>\n *\n * @see <a href=\"https://en.wikipedia.org/wiki/MurmurHash\">MurmurHash</a>\n * @see <a href=\"https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp\">\n *   Original MurmurHash2 c++ code</a>\n * @since 1.13\n */\npublic final class MurmurHash2 {\n\n    // Constants for 32-bit variant\n    private static final int M32 = 0x5bd1e995;\n    private static final int R32 = 24;\n\n    // Constants for 64-bit variant\n    private static final long M64 = 0xc6a4a7935bd1e995L;\n    private static final int R64 = 47;\n\n    /**\n     * No instance methods.\n     */\n    private MurmurHash2() {\n    }\n\n    /**\n     * Generates a 32-bit hash from byte array with the given length and seed.\n     *\n     * @param data   The input byte array\n     * @param length The length of the array\n     * @param seed   The initial seed value\n     * @return The 32-bit hash\n     */\n    public static int hash32(final byte[] data, final int length, final int seed) {\n        // Initialize the hash to a random value\n        int h = seed ^ length;\n\n        // Mix 4 bytes at a time into the hash\n        final int nblocks = length >> 2;\n\n        // body\n        for (int i = 0; i < nblocks; i++) {\n            final int index = (i << 2);\n            int k = ByteArray.getIntLE(data, index);\n            k *= M32;\n            k ^= k >>> R32;\n            k *= M32;\n            h *= M32;\n            h ^= k;\n        }\n\n        // Handle the last few bytes of the input array\n        final int index = (nblocks << 2);\n        switch (length - index) {\n            case 3:\n                h ^= (data[index + 2] & 0xff) << 16;\n                // fallthrough\n            case 2:\n                h ^= (data[index + 1] & 0xff) << 8;\n                // fallthrough\n            case 1:\n                h ^= (data[index] & 0xff);\n                h *= M32;\n        }\n\n        // Do a few final mixes of the hash to ensure the last few\n        // bytes are well-incorporated.\n        h ^= h >>> 13;\n        h *= M32;\n        h ^= h >>> 15;\n\n        return h;\n    }\n\n    /**\n     * Generates a 32-bit hash from byte array with the given length and a default seed value.\n     * This is a helper method that will produce the same result as:\n     *\n     * <pre>\n     * int seed = 0x9747b28c;\n     * int hash = MurmurHash2.hash32(data, length, seed);\n     * </pre>\n     *\n     * @param data   The input byte array\n     * @param length The length of the array\n     * @return The 32-bit hash\n     * @see #hash32(byte[], int, int)\n     */\n    public static int hash32(final byte[] data, final int length) {\n        return hash32(data, length, 0x9747b28c);\n    }\n\n    /**\n     * Generates a 32-bit hash from a string with a default seed.\n     * <p>\n     * Before 1.14 the string was converted using default encoding.\n     * Since 1.14 the string is converted to bytes using UTF-8 encoding.\n     * </p>\n     * This is a helper method that will produce the same result as:\n     *\n     * <pre>\n     * int seed = 0x9747b28c;\n     * byte[] bytes = data.getBytes(StandardCharsets.UTF_8);\n     * int hash = MurmurHash2.hash32(bytes, bytes.length, seed);\n     * </pre>\n     *\n     * @param text The input string\n     * @return The 32-bit hash\n     * @see #hash32(byte[], int, int)\n     */\n    public static int hash32(final String text) {\n        final byte[] bytes = text.getBytes(StandardCharsets.UTF_8);\n        return hash32(bytes, bytes.length);\n    }\n\n    /**\n     * Generates a 32-bit hash from a substring with a default seed value.\n     * The string is converted to bytes using the default encoding.\n     * This is a helper method that will produce the same result as:\n     *\n     * <pre>\n     * int seed = 0x9747b28c;\n     * byte[] bytes = text.substring(from, from + length).getBytes(StandardCharsets.UTF_8);\n     * int hash = MurmurHash2.hash32(bytes, bytes.length, seed);\n     * </pre>\n     *\n     * @param text   The input string\n     * @param from   The starting index\n     * @param length The length of the substring\n     * @return The 32-bit hash\n     * @see #hash32(byte[], int, int)\n     */\n    public static int hash32(final String text, final int from, final int length) {\n        return hash32(text.substring(from, from + length));\n    }\n\n    /**\n     * Generates a 64-bit hash from byte array of the given length and seed.\n     *\n     * @param data   The input byte array\n     * @param length The length of the array\n     * @param seed   The initial seed value\n     * @return The 64-bit hash of the given array\n     */\n    public static long hash64(final byte[] data, final int length, final int seed) {\n        long h = (seed & 0xffffffffL) ^ (length * M64);\n\n        final int nblocks = length >> 3;\n\n        // body\n        for (int i = 0; i < nblocks; i++) {\n            final int index = (i << 3);\n            long k = ByteArray.getLongLE(data, index);\n\n            k *= M64;\n            k ^= k >>> R64;\n            k *= M64;\n\n            h ^= k;\n            h *= M64;\n        }\n\n        final int index = (nblocks << 3);\n        switch (length - index) {\n            case 7:\n                h ^= ((long) data[index + 6] & 0xff) << 48;\n                // fallthrough\n            case 6:\n                h ^= ((long) data[index + 5] & 0xff) << 40;\n                // fallthrough\n            case 5:\n                h ^= ((long) data[index + 4] & 0xff) << 32;\n                // fallthrough\n            case 4:\n                h ^= ((long) data[index + 3] & 0xff) << 24;\n                // fallthrough\n            case 3:\n                h ^= ((long) data[index + 2] & 0xff) << 16;\n                // fallthrough\n            case 2:\n                h ^= ((long) data[index + 1] & 0xff) << 8;\n                // fallthrough\n            case 1:\n                h ^= ((long) data[index] & 0xff);\n                h *= M64;\n        }\n\n        h ^= h >>> R64;\n        h *= M64;\n        h ^= h >>> R64;\n\n        return h;\n    }\n\n    /**\n     * Generates a 64-bit hash from byte array with given length and a default seed value.\n     * This is a helper method that will produce the same result as:\n     *\n     * <pre>\n     * int seed = 0xe17a1465;\n     * int hash = MurmurHash2.hash64(data, length, seed);\n     * </pre>\n     *\n     * @param data   The input byte array\n     * @param length The length of the array\n     * @return The 64-bit hash\n     * @see #hash64(byte[], int, int)\n     */\n    public static long hash64(final byte[] data, final int length) {\n        return hash64(data, length, 0xe17a1465);\n    }\n\n    /**\n     * Generates a 64-bit hash from a string with a default seed.\n     * <p>\n     * Before 1.14 the string was converted using default encoding.\n     * Since 1.14 the string is converted to bytes using UTF-8 encoding.\n     * </p>\n     * This is a helper method that will produce the same result as:\n     *\n     * <pre>\n     * int seed = 0xe17a1465;\n     * byte[] bytes = data.getBytes(StandardCharsets.UTF_8);\n     * int hash = MurmurHash2.hash64(bytes, bytes.length, seed);\n     * </pre>\n     *\n     * @param text The input string\n     * @return The 64-bit hash\n     * @see #hash64(byte[], int, int)\n     */\n    public static long hash64(final String text) {\n        final byte[] bytes = text.getBytes(StandardCharsets.UTF_8);\n        return hash64(bytes, bytes.length);\n    }\n\n    /**\n     * Generates a 64-bit hash from a substring with a default seed value.\n     * The string is converted to bytes using the default encoding.\n     * This is a helper method that will produce the same result as:\n     *\n     * <pre>\n     * int seed = 0xe17a1465;\n     * byte[] bytes = text.substring(from, from + length).getBytes(StandardCharsets.UTF_8);\n     * int hash = MurmurHash2.hash64(bytes, bytes.length, seed);\n     * </pre>\n     *\n     * @param text   The The input string\n     * @param from   The starting index\n     * @param length The length of the substring\n     * @return The 64-bit hash\n     * @see #hash64(byte[], int, int)\n     */\n    public static long hash64(final String text, final int from, final int length) {\n        return hash64(text.substring(from, from + length));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/Pair.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * @author huangyuhui\n */\npublic record Pair<K, V>(K key, V value) {\n\n    public static <K, V> Pair<K, V> pair(K key, V value) {\n        return new Pair<>(key, value);\n    }\n\n    public K getKey() {\n        return key;\n    }\n\n    public V getValue() {\n        return value;\n    }\n\n    @Override\n    public @NotNull String toString() {\n        return \"(\" + key + \", \" + value + \")\";\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/Result.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Objects;\n\n/// @author Glavo\npublic final class Result<T> {\n    public static <T> Result<T> success(T value) {\n        return new Result<>(value, null);\n    }\n\n    public static <T> Result<T> failure(@NotNull Throwable exception) {\n        Objects.requireNonNull(exception);\n        return new Result<>(null, exception);\n    }\n\n    private final T value;\n    private final Throwable exception;\n\n    private Result(T value, Throwable exception) {\n        this.value = value;\n        this.exception = exception;\n    }\n\n    public boolean isSuccess() {\n        return exception == null;\n    }\n\n    public boolean isFailure() {\n        return exception != null;\n    }\n\n    public T get() throws Throwable {\n        if (exception != null)\n            throw exception;\n        else\n            return value;\n    }\n\n    public @Nullable T getOrNull() {\n        return exception == null ? value : null;\n    }\n\n    public @Nullable Throwable getException() {\n        return exception;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(value, exception);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return this == obj || obj instanceof Result<?> other\n                && Objects.equals(value, other.value)\n                && Objects.equals(exception, other.exception);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/ServerAddress.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\n\n/**\n * @author Glavo\n */\npublic final class ServerAddress {\n\n    private static final int UNKNOWN_PORT = -1;\n\n    private static IllegalArgumentException illegalAddress(String address) {\n        return new IllegalArgumentException(\"Invalid server address: \" + address);\n    }\n\n    /**\n     * @throws IllegalArgumentException if the address is not a valid server address\n     */\n    public static @NotNull ServerAddress parse(@NotNull String address) {\n        Objects.requireNonNull(address);\n\n        if (!address.startsWith(\"[\")) {\n            int colonPos = address.indexOf(':');\n            if (colonPos >= 0) {\n                if (colonPos == address.length() - 1)\n                    throw illegalAddress(address);\n\n                String host = address.substring(0, colonPos);\n                int port;\n                try {\n                    port = Integer.parseInt(address.substring(colonPos + 1));\n                } catch (NumberFormatException e) {\n                    throw illegalAddress(address);\n                }\n                if (port < 0 || port > 0xFFFF)\n                    throw illegalAddress(address);\n                return new ServerAddress(host, port);\n            } else {\n                return new ServerAddress(address);\n            }\n        } else {\n            // Parse IPv6 address\n            int colonIndex = address.indexOf(':');\n            int closeBracketIndex = address.lastIndexOf(']');\n\n            if (colonIndex < 0 || closeBracketIndex < colonIndex)\n                throw illegalAddress(address);\n\n            String host = address.substring(1, closeBracketIndex);\n            if (closeBracketIndex == address.length() - 1)\n                return new ServerAddress(host);\n\n            if (address.length() < closeBracketIndex + 3 || address.charAt(closeBracketIndex + 1) != ':')\n                throw illegalAddress(address);\n\n            int port;\n            try {\n                port = Integer.parseInt(address.substring(closeBracketIndex + 2));\n            } catch (NumberFormatException e) {\n                throw illegalAddress(address);\n            }\n\n            if (port < 0 || port > 0xFFFF)\n                throw illegalAddress(address);\n\n            return new ServerAddress(host, port);\n        }\n    }\n\n    private final String host;\n    private final int port;\n\n    public ServerAddress(@NotNull String host) {\n        this(host, UNKNOWN_PORT);\n    }\n\n    public ServerAddress(@NotNull String host, int port) {\n        this.host = Objects.requireNonNull(host);\n        this.port = port;\n    }\n\n    public String getHost() {\n        return host;\n    }\n\n    public int getPort() {\n        return port;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof ServerAddress)) return false;\n        ServerAddress that = (ServerAddress) o;\n        return port == that.port && Objects.equals(host, that.host);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(host, port);\n    }\n\n    @Override\n    public String toString() {\n        return String.format(\"ServerAddress[host='%s', port=%d]\", host, port);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/SettingsMap.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/// A wrapper for `Map<String, Object>`, supporting type-safe reading and writing of values.\n///\n///  @author Glavo\npublic final class SettingsMap {\n    public record Key<T>(String key) {\n    }\n\n    private final Map<String, Object> map = new HashMap<>();\n\n    public SettingsMap() {\n    }\n\n    public Map<String, Object> asStringMap() {\n        return map;\n    }\n\n    public boolean containsKey(@NotNull Key<?> key) {\n        return map.containsKey(key.key);\n    }\n\n    public boolean containsKey(@NotNull String key) {\n        return map.containsKey(key);\n    }\n\n    public <T> @Nullable T get(@NotNull Key<T> key) {\n        @SuppressWarnings(\"unchecked\")\n        T value = (T) map.get(key.key);\n        return value;\n    }\n\n    public Object get(@NotNull String key) {\n        return map.get(key);\n    }\n\n    public <T> T getOrDefault(@NotNull Key<T> key, T defaultValue) {\n        @SuppressWarnings(\"unchecked\")\n        T value = (T) map.get(key.key);\n        return value != null ? value : defaultValue;\n    }\n\n    public <T> T put(@NotNull Key<T> key, @Nullable T value) {\n        @SuppressWarnings(\"unchecked\")\n        T result = (T) map.put(key.key, value);\n        return result;\n    }\n\n    public Object put(@NotNull String key, @Nullable Object value) {\n        return map.put(key, value);\n    }\n\n    public <T> T remove(@NotNull Key<T> key) {\n        @SuppressWarnings(\"unchecked\")\n        T result = (T) map.remove(key.key);\n        return result;\n    }\n\n    public Object remove(@NotNull String key) {\n        return map.remove(key);\n    }\n\n    public void clear() {\n        map.clear();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/SimpleMultimap.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\n/**\n * A simple implementation of Multimap.\n * Just a combination of map and set.\n *\n * @author huangyuhui\n */\npublic final class SimpleMultimap<K, V, M extends Collection<V>> {\n\n    private final Map<K, M> map;\n    private final Supplier<M> valuer;\n\n    public SimpleMultimap(Supplier<Map<K, M>> mapper, Supplier<M> valuer) {\n        this.map = mapper.get();\n        this.valuer = valuer;\n    }\n\n    public int size() {\n        return values().size();\n    }\n\n    public Set<K> keys() {\n        return map.keySet();\n    }\n\n    public Collection<V> values() {\n        Collection<V> res = valuer.get();\n        for (Map.Entry<K, M> entry : map.entrySet())\n            res.addAll(entry.getValue());\n        return res;\n    }\n\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    public boolean containsKey(K key) {\n        return map.containsKey(key) && !map.get(key).isEmpty();\n    }\n\n    public M get(K key) {\n        return map.computeIfAbsent(key, any -> valuer.get());\n    }\n\n    public void put(K key, V value) {\n        M set = get(key);\n        set.add(value);\n    }\n\n    public void putAll(K key, Collection<? extends V> value) {\n        M set = get(key);\n        set.addAll(value);\n    }\n\n    public M removeKey(K key) {\n        return map.remove(key);\n    }\n\n    public boolean removeValue(V value) {\n        boolean flag = false;\n        for (M c : map.values())\n            flag |= c.remove(value);\n        return flag;\n    }\n\n    public boolean removeValue(K key, V value) {\n        return get(key).remove(value);\n    }\n\n    public void clear() {\n        map.clear();\n    }\n\n    public void clear(K key) {\n        if (map.containsKey(key))\n            map.get(key).clear();\n        else\n            map.put(key, valuer.get());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author huangyuhui\n */\npublic final class StringUtils {\n\n    private StringUtils() {\n    }\n\n    public static String getStackTrace(Throwable throwable) {\n        StringWriter stringWriter = new StringWriter(512);\n        try (PrintWriter printWriter = new PrintWriter(stringWriter)) {\n            throwable.printStackTrace(printWriter);\n        }\n        return stringWriter.toString();\n    }\n\n    public static String getStackTrace(StackTraceElement[] elements) {\n        StringBuilder builder = new StringBuilder();\n        for (StackTraceElement element : elements)\n            builder.append(\"\\tat \").append(element).append(System.lineSeparator());\n        return builder.toString();\n    }\n\n    public static boolean isBlank(String str) {\n        return str == null || str.isBlank();\n    }\n\n    public static boolean isNotBlank(String str) {\n        return !isBlank(str);\n    }\n\n    public static String normalizeWhitespaces(String str) {\n        if (str == null)\n            return \"\";\n\n        int start = 0;\n        int end = str.length();\n\n        while (start < str.length() && Character.isWhitespace(str.charAt(start))) {\n            start++;\n        }\n        while (end > start && Character.isWhitespace(str.charAt(end - 1))) {\n            end--;\n        }\n\n        if (end == start) {\n            return \"\";\n        }\n\n        StringBuilder builder = null;\n\n        int i = start;\n        while (i < end) {\n            char ch = str.charAt(i);\n            if (Character.isWhitespace(ch)) {\n                int whitespaceEnd = i + 1;\n                while (whitespaceEnd < end && Character.isWhitespace(str.charAt(whitespaceEnd))) {\n                    whitespaceEnd++;\n                }\n\n                if (whitespaceEnd - i > 1 || ch != ' ') {\n                    if (builder == null) {\n                        builder = new StringBuilder(end - start);\n                        builder.append(str, start, i);\n                    }\n                    builder.append(' ');\n                    i = whitespaceEnd;\n                    continue;\n                }\n            }\n\n            if (builder != null) {\n                builder.append(ch);\n            }\n            i++;\n        }\n\n        return builder != null ? builder.toString() : str.substring(start, end);\n    }\n\n    public static String capitalizeFirst(String str) {\n        if (str == null || str.isEmpty())\n            return str;\n\n        return Character.toUpperCase(str.charAt(0)) + str.substring(1);\n    }\n\n    public static String substringBeforeLast(String str, char delimiter) {\n        return substringBeforeLast(str, delimiter, str);\n    }\n\n    public static String substringBeforeLast(String str, char delimiter, String missingDelimiterValue) {\n        int index = str.lastIndexOf(delimiter);\n        return index == -1 ? missingDelimiterValue : str.substring(0, index);\n    }\n\n    public static String substringBeforeLast(String str, String delimiter) {\n        return substringBeforeLast(str, delimiter, str);\n    }\n\n    public static String substringBeforeLast(String str, String delimiter, String missingDelimiterValue) {\n        int index = str.lastIndexOf(delimiter);\n        return index == -1 ? missingDelimiterValue : str.substring(0, index);\n    }\n\n    public static String substringBefore(String str, char delimiter) {\n        return substringBefore(str, delimiter, str);\n    }\n\n    public static String substringBefore(String str, char delimiter, String missingDelimiterValue) {\n        int index = str.indexOf(delimiter);\n        return index == -1 ? missingDelimiterValue : str.substring(0, index);\n    }\n\n    public static String substringBefore(String str, String delimiter) {\n        return substringBefore(str, delimiter, str);\n    }\n\n    public static String substringBefore(String str, String delimiter, String missingDelimiterValue) {\n        int index = str.indexOf(delimiter);\n        return index == -1 ? missingDelimiterValue : str.substring(0, index);\n    }\n\n    public static String substringAfterLast(String str, char delimiter) {\n        return substringAfterLast(str, delimiter, \"\");\n    }\n\n    public static String substringAfterLast(String str, char delimiter, String missingDelimiterValue) {\n        int index = str.lastIndexOf(delimiter);\n        return index == -1 ? missingDelimiterValue : str.substring(index + 1);\n    }\n\n    public static String substringAfterLast(String str, String delimiter) {\n        return substringAfterLast(str, delimiter, \"\");\n    }\n\n    public static String substringAfterLast(String str, String delimiter, String missingDelimiterValue) {\n        int index = str.lastIndexOf(delimiter);\n        return index == -1 ? missingDelimiterValue : str.substring(index + delimiter.length());\n    }\n\n    public static String substringAfter(String str, char delimiter) {\n        return substringAfter(str, delimiter, \"\");\n    }\n\n    public static String substringAfter(String str, char delimiter, String missingDelimiterValue) {\n        int index = str.indexOf(delimiter);\n        return index == -1 ? missingDelimiterValue : str.substring(index + 1);\n    }\n\n    public static String substringAfter(String str, String delimiter) {\n        return substringAfter(str, delimiter, \"\");\n    }\n\n    public static String substringAfter(String str, String delimiter, String missingDelimiterValue) {\n        int index = str.indexOf(delimiter);\n        return index == -1 ? missingDelimiterValue : str.substring(index + delimiter.length());\n    }\n\n    public static boolean isSurrounded(String str, String prefix, String suffix) {\n        return str.startsWith(prefix) && str.endsWith(suffix);\n    }\n\n    public static String removeSurrounding(String str, String delimiter) {\n        return removeSurrounding(str, delimiter, delimiter);\n    }\n\n    public static String removeSurrounding(String str, String prefix, String suffix) {\n        if ((str.length() >= prefix.length() + suffix.length()) && str.startsWith(prefix) && str.endsWith(suffix))\n            return str.substring(prefix.length(), str.length() - suffix.length());\n        else\n            return str;\n    }\n\n    public static String addPrefix(String str, String prefix) {\n        if (str.startsWith(prefix))\n            return str;\n        else\n            return prefix + str;\n    }\n\n    public static String addSuffix(String str, String suffix) {\n        if (str.endsWith(suffix))\n            return str;\n        else\n            return str + suffix;\n    }\n\n    public static String removePrefix(String str, String prefix) {\n        return str.startsWith(prefix) ? str.substring(prefix.length()) : str;\n    }\n\n    public static String removePrefix(String str, String... prefixes) {\n        for (String prefix : prefixes)\n            if (str.startsWith(prefix))\n                return str.substring(prefix.length());\n        return str;\n    }\n\n    public static String removeSuffix(String str, String suffix) {\n        return str.endsWith(suffix) ? str.substring(0, str.length() - suffix.length()) : str;\n    }\n\n    /**\n     * Remove one suffix of the suffixes of the string.\n     */\n    public static String removeSuffix(String str, String... suffixes) {\n        for (String suffix : suffixes)\n            if (str.endsWith(suffix))\n                return str.substring(0, str.length() - suffix.length());\n        return str;\n    }\n\n    public static boolean containsOne(Collection<String> patterns, String... targets) {\n        for (String pattern : patterns) {\n            String lowerPattern = pattern.toLowerCase(Locale.ROOT);\n            for (String target : targets)\n                if (lowerPattern.contains(target.toLowerCase(Locale.ROOT)))\n                    return true;\n        }\n        return false;\n    }\n\n    public static boolean containsOne(String pattern, String... targets) {\n        String lowerPattern = pattern.toLowerCase(Locale.ROOT);\n        for (String target : targets)\n            if (lowerPattern.contains(target.toLowerCase(Locale.ROOT)))\n                return true;\n        return false;\n    }\n\n    public static boolean containsChinese(String str) {\n        for (int i = 0; i < str.length(); i++) {\n            char ch = str.charAt(i);\n            if (ch >= '\\u4e00' && ch <= '\\u9fa5')\n                return true;\n        }\n        return false;\n    }\n\n    public static boolean containsEmoji(String str) {\n        for (int i = 0; i < str.length(); ) {\n            int ch = str.codePointAt(i);\n\n            if (ch >= 0x1F300 && ch <= 0x1FAFF)\n                return true;\n\n            i += Character.charCount(ch);\n        }\n\n        return false;\n    }\n\n    /// Check if the code point is a full-width character.\n    public static boolean isFullWidth(int codePoint) {\n        return codePoint >= '\\uff10' && codePoint <= '\\uff19'       // full-width digits\n                || codePoint >= '\\uff21' && codePoint <= '\\uff3a'   // full-width uppercase letters\n                || codePoint >= '\\uff41' && codePoint <= '\\uff5a'   // full-width lowercase letters\n                || codePoint == '\\uff08'                            // full-width left parenthesis\n                || codePoint == '\\uff09'                            // full-width right parenthesis\n                || codePoint == '\\uff0c'                            // full-width comma\n                || codePoint == '\\uff05'                            // full-width percent sign\n                || codePoint == '\\uff0e'                            // full-width period\n                || codePoint == '\\u3000'                            // full-width ideographic space\n                || codePoint == '\\uff03';                           // full-width number sign\n    }\n\n    /// Convert full-width characters to half-width characters.\n    public static String toHalfWidth(String str) {\n        int i = 0;\n        while (i < str.length()) {\n            int cp = str.codePointAt(i);\n\n            if (isFullWidth(cp)) {\n                break;\n            }\n\n            i += Character.charCount(cp);\n        }\n\n        if (i == str.length())\n            return str;\n\n        var builder = new StringBuilder(str.length());\n        builder.append(str, 0, i);\n        while (i < str.length()) {\n            int c = str.codePointAt(i);\n\n            if (c >= '\\uff10' && c <= '\\uff19') builder.append((char) (c - 0xfee0));\n            else if (c >= '\\uff21' && c <= '\\uff3a') builder.append((char) (c - 0xfee0));\n            else if (c >= '\\uff41' && c <= '\\uff5a') builder.append((char) (c - 0xfee0));\n            else if (c == '\\uff08') builder.append('(');\n            else if (c == '\\uff09') builder.append(')');\n            else if (c == '\\uff0c') builder.append(',');\n            else if (c == '\\uff05') builder.append('%');\n            else if (c == '\\uff0e') builder.append('.');\n            else if (c == '\\u3000') builder.append(' ');\n            else if (c == '\\uff03') builder.append('#');\n            else builder.appendCodePoint(c);\n\n            i += Character.charCount(c);\n        }\n        return builder.toString();\n    }\n\n    private static boolean isVarNameStart(char ch) {\n        return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';\n    }\n\n    private static boolean isVarNamePart(char ch) {\n        return isVarNameStart(ch) || (ch >= '0' && ch <= '9');\n    }\n\n    private static int findVarEnd(String str, int offset) {\n        if (offset < str.length() - 1 && isVarNameStart(str.charAt(offset))) {\n            int end = offset + 1;\n            while (end < str.length()) {\n                if (!isVarNamePart(str.charAt(end))) {\n                    break;\n                }\n                end++;\n            }\n            return end;\n        }\n\n        return -1;\n    }\n\n    public static List<String> tokenize(String str) {\n        return tokenize(str, null);\n    }\n\n    public static List<String> tokenize(String str, Map<String, String> vars) {\n        if (isBlank(str)) {\n            return new ArrayList<>();\n        }\n\n        if (vars == null) {\n            vars = Collections.emptyMap();\n        }\n\n        // Split the string with ' and space cleverly.\n        ArrayList<String> parts = new ArrayList<>();\n        int varEnd;\n\n        boolean hasValue = false;\n        StringBuilder current = new StringBuilder(str.length());\n        for (int i = 0; i < str.length(); ) {\n            char c = str.charAt(i);\n            if (c == '\\'') {\n                hasValue = true;\n                int end = str.indexOf(c, i + 1);\n                if (end < 0) {\n                    end = str.length();\n                }\n                current.append(str, i + 1, end);\n                i = end + 1;\n\n            } else if (c == '\"') {\n                hasValue = true;\n                i++;\n                while (i < str.length()) {\n                    c = str.charAt(i++);\n                    if (c == '\"') {\n                        break;\n                    } else if (c == '`' && i < str.length()) {\n                        c = str.charAt(i++);\n                        switch (c) {\n                            case 'a':\n                                c = '\\u0007';\n                                break;\n                            case 'b':\n                                c = '\\b';\n                                break;\n                            case 'f':\n                                c = '\\f';\n                                break;\n                            case 'n':\n                                c = '\\n';\n                                break;\n                            case 'r':\n                                c = '\\r';\n                                break;\n                            case 't':\n                                c = '\\t';\n                                break;\n                            case 'v':\n                                c = '\\u000b';\n                                break;\n                        }\n                        current.append(c);\n                    } else if (c == '$' && (varEnd = findVarEnd(str, i)) >= 0) {\n                        String key = str.substring(i, varEnd);\n                        String value = vars.get(key);\n                        if (value != null) {\n                            current.append(value);\n                        } else {\n                            current.append('$').append(key);\n                        }\n\n                        i = varEnd;\n                    } else {\n                        current.append(c);\n                    }\n                }\n            } else if (c == ' ') {\n                if (hasValue) {\n                    parts.add(current.toString());\n                    current.setLength(0);\n                    hasValue = false;\n                }\n                i++;\n            } else if (c == '$' && (varEnd = findVarEnd(str, i + 1)) >= 0) {\n                hasValue = true;\n                String key = str.substring(i + 1, varEnd);\n                String value = vars.get(key);\n                if (value != null) {\n                    current.append(value);\n                } else {\n                    current.append('$').append(key);\n                }\n\n                i = varEnd;\n            } else {\n                hasValue = true;\n                current.append(c);\n                i++;\n            }\n        }\n        if (hasValue) {\n            parts.add(current.toString());\n        }\n\n        return parts;\n    }\n\n    public static String parseColorEscapes(String original) {\n        if (original.indexOf('\\u00A7') < 0)\n            return original;\n\n        return original.replaceAll(\"\\u00A7[0-9a-fk-or]\", \"\");\n    }\n\n    private static final Pattern COLOR_CODE_PATTERN = Pattern.compile(\"\\u00A7([0-9a-fk-or])\");\n    private static final String FORMAT_CODE = \"format_code\";\n\n    public static List<Pair<String, String>> parseMinecraftColorCodes(String original) {\n        List<Pair<String, String>> pairs = new ArrayList<>();\n        if (isBlank(original)) {\n            return pairs;\n        }\n        Matcher matcher = COLOR_CODE_PATTERN.matcher(original);\n        String currentColor = \"\";\n        int lastIndex = 0;\n\n        while (matcher.find()) {\n            String text = original.substring(lastIndex, matcher.start());\n            if (!text.isEmpty()) {\n                pairs.add(new Pair<>(text, currentColor));\n            }\n\n            char code = matcher.group(1).charAt(0);\n            String newColor = switch (code) {\n                case '0' -> \"black\";\n                case '1' -> \"dark_blue\";\n                case '2' -> \"dark_green\";\n                case '3' -> \"dark_aqua\";\n                case '4' -> \"dark_red\";\n                case '5' -> \"dark_purple\";\n                case '6' -> \"gold\";\n                case '7' -> \"gray\";\n                case '8' -> \"dark_gray\";\n                case '9' -> \"blue\";\n                case 'a' -> \"green\";\n                case 'b' -> \"aqua\";\n                case 'c' -> \"red\";\n                case 'd' -> \"light_purple\";\n                case 'e' -> \"yellow\";\n                case 'f' -> \"white\";\n                case 'k', 'l', 'm', 'n', 'o' -> FORMAT_CODE;\n                case 'r' -> \"\";\n                default -> null;\n            };\n\n            if (newColor != null && !newColor.equals(FORMAT_CODE)) {\n                currentColor = newColor;\n            }\n\n            lastIndex = matcher.end();\n        }\n\n        if (lastIndex < original.length()) {\n            String remainingText = original.substring(lastIndex);\n            pairs.add(new Pair<>(remainingText, currentColor));\n        }\n        return pairs;\n    }\n\n    public static String parseEscapeSequence(String str) {\n        int idx = str.indexOf('\\033');\n        if (idx < 0)\n            return str;\n\n        StringBuilder builder = new StringBuilder(str.length());\n        boolean inEscape = false;\n\n        builder.append(str, 0, idx);\n        for (int i = idx; i < str.length(); i++) {\n            char ch = str.charAt(i);\n            if (ch == '\\033') {\n                inEscape = true;\n            }\n            if (!inEscape) {\n                builder.append(ch);\n            }\n            if (inEscape && ch == 'm') {\n                inEscape = false;\n            }\n        }\n        return builder.toString();\n    }\n\n    public static String escapeXmlAttribute(String str) {\n        return str\n                .replace(\"&\", \"&amp;\")\n                .replace(\"\\\"\", \"&quot;\")\n                .replace(\"<\", \"&lt;\")\n                .replace(\">\", \"&gt;\")\n                .replace(\"'\", \"&apos;\");\n    }\n\n    public static String repeats(char ch, int repeat) {\n        StringBuilder result = new StringBuilder();\n        for (int i = 0; i < repeat; i++) {\n            result.append(ch);\n        }\n        return result.toString();\n    }\n\n    public static String truncate(String str, int limit) {\n        assert limit > 5;\n\n        if (str.length() <= limit) {\n            return str;\n        }\n\n        final int halfLength = (limit - 5) / 2;\n        return str.substring(0, halfLength) + \" ... \" + str.substring(str.length() - halfLength);\n    }\n\n    public static boolean isASCII(String cs) {\n        for (int i = 0; i < cs.length(); i++)\n            if (cs.charAt(i) >= 128)\n                return false;\n        return true;\n    }\n\n    public static boolean isAlphabeticOrNumber(String str) {\n        int length = str.length();\n        for (int i = 0; i < length; i++) {\n            char ch = str.charAt(i);\n            if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z') && !(ch >= 'A' && ch <= 'Z'))\n                return false;\n        }\n        return true;\n    }\n\n    public static class LevCalculator {\n        private int[][] lev;\n\n        public LevCalculator() {\n        }\n\n        public LevCalculator(int length1, int length2) {\n            allocate(length1, length2);\n        }\n\n        private void allocate(int length1, int length2) {\n            length1 += 1;\n            length2 += 1;\n            lev = new int[length1][length2];\n            for (int i = 1; i < length1; i++) {\n                lev[i][0] = i;\n            }\n            int[] cache = lev[0];\n            for (int i = 0; i < length2; i++) {\n                cache[i] = i;\n            }\n        }\n\n        public int getLength1() {\n            return lev.length;\n        }\n\n        public int getLength2() {\n            return lev[0].length;\n        }\n\n        private int min(int a, int b, int c) {\n            return Math.min(a, Math.min(b, c));\n        }\n\n        public int calc(CharSequence a, CharSequence b) {\n            if (lev == null || a.length() >= lev.length || b.length() >= lev[0].length) {\n                allocate(a.length(), b.length());\n            }\n\n            int lengthA = a.length() + 1, lengthB = b.length() + 1;\n\n            for (int i = 1; i < lengthA; i++) {\n                for (int j = 1; j < lengthB; j++) {\n                    lev[i][j] = min(\n                            lev[i][j - 1] + 1, // insert\n                            lev[i - 1][j] + 1, // del\n                            a.charAt(i - 1) == b.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1 // replace\n                    );\n                }\n            }\n\n            return lev[a.length()][b.length()];\n        }\n    }\n\n    /**\n     * Class for computing the longest common subsequence between strings.\n     */\n    public static final class LongestCommonSubsequence {\n        // We reuse dynamic programming storage array here to reduce allocations.\n        private final int[][] f;\n        private final int maxLengthA;\n        private final int maxLengthB;\n\n        public LongestCommonSubsequence(int maxLengthA, int maxLengthB) {\n            this.maxLengthA = maxLengthA;\n            this.maxLengthB = maxLengthB;\n            f = new int[maxLengthA + 1][];\n            for (int i = 0; i <= maxLengthA; i++) {\n                f[i] = new int[maxLengthB + 1];\n            }\n        }\n\n        public int calc(CharSequence a, CharSequence b) {\n            if (a.length() > maxLengthA || b.length() > maxLengthB) {\n                throw new IllegalArgumentException(\"Too large length\");\n            }\n            for (int i = 1; i <= a.length(); i++) {\n                for (int j = 1; j <= b.length(); j++) {\n                    if (a.charAt(i - 1) == b.charAt(j - 1)) {\n                        f[i][j] = 1 + f[i - 1][j - 1];\n                    } else {\n                        f[i][j] = Math.max(f[i - 1][j], f[i][j - 1]);\n                    }\n                }\n            }\n            return f[a.length()][b.length()];\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/ToStringBuilder.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\npublic final class ToStringBuilder {\n\n    private final StringBuilder stringBuilder;\n    private boolean first = true;\n\n    public ToStringBuilder(Object object) {\n        stringBuilder = new StringBuilder(object.getClass().getSimpleName()).append(\" [\");\n    }\n\n    public ToStringBuilder append(String name, Object content) {\n        if (!first)\n            stringBuilder.append(\", \");\n        first = false;\n        stringBuilder.append(name).append('=').append(content);\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return stringBuilder.toString() + \"]\";\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/TypeUtils.java",
    "content": "// Copy from GsonTypes\n\n/*\n * Copyright (C) 2008 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.lang.reflect.Array;\nimport java.lang.reflect.GenericArrayType;\nimport java.lang.reflect.GenericDeclaration;\nimport java.lang.reflect.Modifier;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.lang.reflect.TypeVariable;\nimport java.lang.reflect.WildcardType;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.Objects;\nimport java.util.Properties;\n\n/**\n * Static methods for working with types.\n *\n * @author Bob Lee\n * @author Jesse Wilson\n */\n@SuppressWarnings(\"MemberName\") // legacy class name\npublic final class TypeUtils {\n    static final Type[] EMPTY_TYPE_ARRAY = new Type[]{};\n\n    private TypeUtils() {\n        throw new UnsupportedOperationException();\n    }\n\n    private static void checkArgument(boolean condition) {\n        if (!condition) {\n            throw new IllegalArgumentException();\n        }\n    }\n\n    /**\n     * Returns a new parameterized type, applying {@code typeArguments} to {@code rawType} and\n     * enclosed by {@code ownerType}.\n     *\n     * @return a {@link java.io.Serializable serializable} parameterized type.\n     */\n    public static ParameterizedType newParameterizedTypeWithOwner(\n            Type ownerType, Class<?> rawType, Type... typeArguments) {\n        return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);\n    }\n\n    /**\n     * Returns an array type whose elements are all instances of {@code componentType}.\n     *\n     * @return a {@link java.io.Serializable serializable} generic array type.\n     */\n    public static GenericArrayType arrayOf(Type componentType) {\n        return new GenericArrayTypeImpl(componentType);\n    }\n\n    /**\n     * Returns a type that represents an unknown type that extends {@code bound}. For example, if\n     * {@code bound} is {@code CharSequence.class}, this returns {@code ? extends CharSequence}. If\n     * {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code ?\n     * extends Object}.\n     */\n    public static WildcardType subtypeOf(Type bound) {\n        Type[] upperBounds;\n        if (bound instanceof WildcardType w) {\n            upperBounds = w.getUpperBounds();\n        } else {\n            upperBounds = new Type[]{bound};\n        }\n        return new WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY);\n    }\n\n    /**\n     * Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code\n     * bound} is {@code String.class}, this returns {@code ? super String}.\n     */\n    public static WildcardType supertypeOf(Type bound) {\n        Type[] lowerBounds;\n        if (bound instanceof WildcardType w) {\n            lowerBounds = w.getLowerBounds();\n        } else {\n            lowerBounds = new Type[]{bound};\n        }\n        return new WildcardTypeImpl(new Type[]{Object.class}, lowerBounds);\n    }\n\n    /**\n     * Returns a type that is functionally equal but not necessarily equal according to {@link\n     * Object#equals(Object) Object.equals()}. The returned type is {@link java.io.Serializable}.\n     */\n    public static Type canonicalize(Type type) {\n        if (type instanceof Class<?> c) {\n            return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;\n\n        } else if (type instanceof ParameterizedType p) {\n            return new ParameterizedTypeImpl(\n                    p.getOwnerType(), (Class<?>) p.getRawType(), p.getActualTypeArguments());\n\n        } else if (type instanceof GenericArrayType g) {\n            return new GenericArrayTypeImpl(g.getGenericComponentType());\n\n        } else if (type instanceof WildcardType w) {\n            return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());\n\n        } else {\n            // type is either serializable as-is or unsupported\n            return type;\n        }\n    }\n\n    public static Class<?> getRawType(Type type) {\n        if (type instanceof Class<?> clazz) {\n            // type is a normal class.\n            return clazz;\n        } else if (type instanceof ParameterizedType parameterizedType) {\n\n            // getRawType() returns Type instead of Class; that seems to be an API mistake,\n            // see https://bugs.openjdk.org/browse/JDK-8250659\n            Type rawType = parameterizedType.getRawType();\n            checkArgument(rawType instanceof Class);\n            return (Class<?>) rawType;\n\n        } else if (type instanceof GenericArrayType g) {\n            Type componentType = g.getGenericComponentType();\n            return Array.newInstance(getRawType(componentType), 0).getClass();\n\n        } else if (type instanceof TypeVariable) {\n            // we could use the variable's bounds, but that won't work if there are multiple.\n            // having a raw type that's more general than necessary is okay\n            return Object.class;\n\n        } else if (type instanceof WildcardType w) {\n            Type[] bounds = w.getUpperBounds();\n            // Currently the JLS only permits one bound for wildcards so using first bound is safe\n            assert bounds.length == 1;\n            return getRawType(bounds[0]);\n\n        } else {\n            String className = type == null ? \"null\" : type.getClass().getName();\n            throw new IllegalArgumentException(\n                    \"Expected a Class, ParameterizedType, or GenericArrayType, but <\"\n                            + type\n                            + \"> is of type \"\n                            + className);\n        }\n    }\n\n    private static boolean equal(Object a, Object b) {\n        return Objects.equals(a, b);\n    }\n\n    /**\n     * Returns true if {@code a} and {@code b} are equal.\n     */\n    public static boolean equals(Type a, Type b) {\n        if (a == b) {\n            // also handles (a == null && b == null)\n            return true;\n\n        } else if (a instanceof Class) {\n            // Class already specifies equals().\n            return a.equals(b);\n\n        } else if (a instanceof ParameterizedType pa) {\n            if (!(b instanceof ParameterizedType pb)) {\n                return false;\n            }\n\n            // TODO: save a .clone() call\n            return equal(pa.getOwnerType(), pb.getOwnerType())\n                    && pa.getRawType().equals(pb.getRawType())\n                    && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments());\n\n        } else if (a instanceof GenericArrayType ga) {\n            if (!(b instanceof GenericArrayType gb)) {\n                return false;\n            }\n\n            return equals(ga.getGenericComponentType(), gb.getGenericComponentType());\n\n        } else if (a instanceof WildcardType wa) {\n            if (!(b instanceof WildcardType wb)) {\n                return false;\n            }\n\n            return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds())\n                    && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds());\n\n        } else if (a instanceof TypeVariable<?> va) {\n            if (!(b instanceof TypeVariable<?> vb)) {\n                return false;\n            }\n            return Objects.equals(va.getGenericDeclaration(), vb.getGenericDeclaration())\n                    && va.getName().equals(vb.getName());\n\n        } else {\n            // This isn't a type we support. Could be a generic array type, wildcard type, etc.\n            return false;\n        }\n    }\n\n    public static String typeToString(Type type) {\n        return type instanceof Class<?> clazz ? clazz.getName() : type.toString();\n    }\n\n    /**\n     * Returns the generic supertype for {@code supertype}. For example, given a class {@code\n     * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set<Integer>} and the\n     * result when the supertype is {@code Collection.class} is {@code Collection<Integer>}.\n     */\n    public static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> supertype) {\n        if (supertype == rawType) {\n            return context;\n        }\n\n        // we skip searching through interfaces if unknown is an interface\n        if (supertype.isInterface()) {\n            Class<?>[] interfaces = rawType.getInterfaces();\n            for (int i = 0, length = interfaces.length; i < length; i++) {\n                if (interfaces[i] == supertype) {\n                    return rawType.getGenericInterfaces()[i];\n                } else if (supertype.isAssignableFrom(interfaces[i])) {\n                    return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], supertype);\n                }\n            }\n        }\n\n        // check our supertypes\n        if (!rawType.isInterface()) {\n            while (rawType != Object.class) {\n                Class<?> rawSupertype = rawType.getSuperclass();\n                if (rawSupertype == supertype) {\n                    return rawType.getGenericSuperclass();\n                } else if (supertype.isAssignableFrom(rawSupertype)) {\n                    return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, supertype);\n                }\n                rawType = rawSupertype;\n            }\n        }\n\n        // we can't resolve this further\n        return supertype;\n    }\n\n    /**\n     * Returns the generic form of {@code supertype}. For example, if this is {@code\n     * ArrayList<String>}, this returns {@code Iterable<String>} given the input {@code\n     * Iterable.class}.\n     *\n     * @param supertype a superclass of, or interface implemented by, this.\n     */\n    public static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {\n        if (context instanceof WildcardType w) {\n            // Wildcards are useless for resolving supertypes. As the upper bound has the same raw type,\n            // use it instead\n            Type[] bounds = w.getUpperBounds();\n            // Currently the JLS only permits one bound for wildcards so using first bound is safe\n            assert bounds.length == 1;\n            context = bounds[0];\n        }\n        checkArgument(supertype.isAssignableFrom(contextRawType));\n        return resolve(\n                context, contextRawType, TypeUtils.getGenericSupertype(context, contextRawType, supertype));\n    }\n\n    /**\n     * Returns the component type of this array type.\n     *\n     * @throws ClassCastException if this type is not an array.\n     */\n    public static Type getArrayComponentType(Type array) {\n        return array instanceof GenericArrayType g\n                ? g.getGenericComponentType()\n                : ((Class<?>) array).getComponentType();\n    }\n\n    /**\n     * Returns the element type of this collection type.\n     *\n     * @throws IllegalArgumentException if this type is not a collection.\n     */\n    public static Type getCollectionElementType(Type context, Class<?> contextRawType) {\n        Type collectionType = getSupertype(context, contextRawType, Collection.class);\n\n        if (collectionType instanceof ParameterizedType p) {\n            return p.getActualTypeArguments()[0];\n        }\n        return Object.class;\n    }\n\n    /**\n     * Returns a two element array containing this map's key and value types in positions 0 and 1\n     * respectively.\n     */\n    public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) {\n        /*\n         * Work around a problem with the declaration of java.util.Properties. That\n         * class should extend Hashtable<String, String>, but it's declared to\n         * extend Hashtable<Object, Object>.\n         */\n        if (Properties.class.isAssignableFrom(contextRawType)) {\n            return new Type[]{String.class, String.class};\n        }\n\n        Type mapType = getSupertype(context, contextRawType, Map.class);\n        // TODO: strip wildcards?\n        if (mapType instanceof ParameterizedType mapParameterizedType) {\n            return mapParameterizedType.getActualTypeArguments();\n        }\n        return new Type[]{Object.class, Object.class};\n    }\n\n    public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {\n        return resolve(context, contextRawType, toResolve, new HashMap<>());\n    }\n\n    private static Type resolve(\n            Type context,\n            Class<?> contextRawType,\n            Type toResolve,\n            Map<TypeVariable<?>, Type> visitedTypeVariables) {\n        // this implementation is made a little more complicated in an attempt to avoid object-creation\n        TypeVariable<?> resolving = null;\n        while (true) {\n            if (toResolve instanceof TypeVariable<?> typeVariable) {\n                Type previouslyResolved = visitedTypeVariables.get(typeVariable);\n                if (previouslyResolved != null) {\n                    // cannot reduce due to infinite recursion\n                    return (previouslyResolved == Void.TYPE) ? toResolve : previouslyResolved;\n                }\n\n                // Insert a placeholder to mark the fact that we are in the process of resolving this type\n                visitedTypeVariables.put(typeVariable, Void.TYPE);\n                if (resolving == null) {\n                    resolving = typeVariable;\n                }\n\n                toResolve = resolveTypeVariable(context, contextRawType, typeVariable);\n                if (toResolve == typeVariable) {\n                    break;\n                }\n\n            } else if (toResolve instanceof Class<?> original && original.isArray()) {\n                Type componentType = original.getComponentType();\n                Type newComponentType =\n                        resolve(context, contextRawType, componentType, visitedTypeVariables);\n                toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType);\n                break;\n\n            } else if (toResolve instanceof GenericArrayType original) {\n                Type componentType = original.getGenericComponentType();\n                Type newComponentType =\n                        resolve(context, contextRawType, componentType, visitedTypeVariables);\n                toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType);\n                break;\n\n            } else if (toResolve instanceof ParameterizedType original) {\n                Type ownerType = original.getOwnerType();\n                Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables);\n                boolean ownerChanged = !equal(newOwnerType, ownerType);\n\n                Type[] args = original.getActualTypeArguments();\n                boolean argsChanged = false;\n                for (int t = 0, length = args.length; t < length; t++) {\n                    Type resolvedTypeArgument =\n                            resolve(context, contextRawType, args[t], visitedTypeVariables);\n                    if (!equal(resolvedTypeArgument, args[t])) {\n                        if (!argsChanged) {\n                            args = args.clone();\n                            argsChanged = true;\n                        }\n                        args[t] = resolvedTypeArgument;\n                    }\n                }\n\n                toResolve =\n                        ownerChanged || argsChanged\n                                ? newParameterizedTypeWithOwner(\n                                newOwnerType, (Class<?>) original.getRawType(), args)\n                                : original;\n                break;\n\n            } else if (toResolve instanceof WildcardType original) {\n                Type[] originalLowerBound = original.getLowerBounds();\n                Type[] originalUpperBound = original.getUpperBounds();\n\n                if (originalLowerBound.length == 1) {\n                    Type lowerBound =\n                            resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);\n                    if (lowerBound != originalLowerBound[0]) {\n                        toResolve = supertypeOf(lowerBound);\n                        break;\n                    }\n                } else if (originalUpperBound.length == 1) {\n                    Type upperBound =\n                            resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);\n                    if (upperBound != originalUpperBound[0]) {\n                        toResolve = subtypeOf(upperBound);\n                        break;\n                    }\n                }\n                toResolve = original;\n                break;\n\n            } else {\n                break;\n            }\n        }\n        // ensure that any in-process resolution gets updated with the final result\n        if (resolving != null) {\n            visitedTypeVariables.put(resolving, toResolve);\n        }\n        return toResolve;\n    }\n\n    private static Type resolveTypeVariable(\n            Type context, Class<?> contextRawType, TypeVariable<?> unknown) {\n        Class<?> declaredByRaw = declaringClassOf(unknown);\n\n        // we can't reduce this further\n        if (declaredByRaw == null) {\n            return unknown;\n        }\n\n        Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw);\n        if (declaredBy instanceof ParameterizedType p) {\n            int index = indexOf(declaredByRaw.getTypeParameters(), unknown);\n            return p.getActualTypeArguments()[index];\n        }\n\n        return unknown;\n    }\n\n    private static int indexOf(Object[] array, Object toFind) {\n        for (int i = 0, length = array.length; i < length; i++) {\n            if (toFind.equals(array[i])) {\n                return i;\n            }\n        }\n        throw new NoSuchElementException();\n    }\n\n    /**\n     * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by\n     * a class.\n     */\n    private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) {\n        GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();\n        return genericDeclaration instanceof Class<?> clazz ? clazz : null;\n    }\n\n    static void checkNotPrimitive(Type type) {\n        checkArgument(!(type instanceof Class<?> clazz) || !clazz.isPrimitive());\n    }\n\n    /**\n     * Whether an {@linkplain ParameterizedType#getOwnerType() owner type} must be specified when\n     * constructing a {@link ParameterizedType} for {@code rawType}.\n     *\n     * <p>Note that this method might not require an owner type for all cases where Java reflection\n     * would create parameterized types with owner type.\n     */\n    public static boolean requiresOwnerType(Type rawType) {\n        if (rawType instanceof Class<?> rawTypeAsClass) {\n            return !Modifier.isStatic(rawTypeAsClass.getModifiers())\n                    && rawTypeAsClass.getDeclaringClass() != null;\n        }\n        return false;\n    }\n\n    // Here and below we put @SuppressWarnings(\"serial\") on fields of type `Type`. Recent Java\n    // compilers complain that the declared type is not Serializable. But in this context we go out of\n    // our way to ensure that the Type in question is either Class (which is serializable) or one of\n    // the nested Type implementations here (which are also serializable).\n    private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {\n        private final Type ownerType;\n        private final Type rawType;\n        private final Type[] typeArguments;\n\n        public ParameterizedTypeImpl(Type ownerType, Class<?> rawType, Type... typeArguments) {\n            Objects.requireNonNull(rawType);\n\n            if (ownerType == null && requiresOwnerType(rawType)) {\n                throw new IllegalArgumentException(\"Must specify owner type for \" + rawType);\n            }\n\n            this.ownerType = ownerType == null ? null : canonicalize(ownerType);\n            this.rawType = canonicalize(rawType);\n            this.typeArguments = typeArguments.clone();\n            for (int t = 0, length = this.typeArguments.length; t < length; t++) {\n                Objects.requireNonNull(this.typeArguments[t]);\n                checkNotPrimitive(this.typeArguments[t]);\n                this.typeArguments[t] = canonicalize(this.typeArguments[t]);\n            }\n        }\n\n        @Override\n        public Type @NotNull [] getActualTypeArguments() {\n            return typeArguments.clone();\n        }\n\n        @Override\n        public @NotNull Type getRawType() {\n            return rawType;\n        }\n\n        @Override\n        public Type getOwnerType() {\n            return ownerType;\n        }\n\n        @Override\n        public boolean equals(Object other) {\n            return other instanceof ParameterizedType p && TypeUtils.equals(this, p);\n        }\n\n        private static int hashCodeOrZero(Object o) {\n            return o != null ? o.hashCode() : 0;\n        }\n\n        @Override\n        public int hashCode() {\n            return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType);\n        }\n\n        @Override\n        public String toString() {\n            int length = typeArguments.length;\n            if (length == 0) {\n                return typeToString(rawType);\n            }\n\n            StringBuilder stringBuilder = new StringBuilder(30 * (length + 1));\n            stringBuilder\n                    .append(typeToString(rawType))\n                    .append(\"<\")\n                    .append(typeToString(typeArguments[0]));\n            for (int i = 1; i < length; i++) {\n                stringBuilder.append(\", \").append(typeToString(typeArguments[i]));\n            }\n            return stringBuilder.append(\">\").toString();\n        }\n\n        @Serial\n        private static final long serialVersionUID = 0;\n    }\n\n    private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable {\n        private final Type componentType;\n\n        public GenericArrayTypeImpl(Type componentType) {\n            Objects.requireNonNull(componentType);\n            this.componentType = canonicalize(componentType);\n        }\n\n        @Override\n        public @NotNull Type getGenericComponentType() {\n            return componentType;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            return o instanceof GenericArrayType g && TypeUtils.equals(this, g);\n        }\n\n        @Override\n        public int hashCode() {\n            return componentType.hashCode();\n        }\n\n        @Override\n        public String toString() {\n            return typeToString(componentType) + \"[]\";\n        }\n\n        @Serial\n        private static final long serialVersionUID = 0;\n    }\n\n    /**\n     * The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only\n     * support what the target Java version supports - at most one bound, see also\n     * <a href=\"https://bugs.openjdk.org/browse/JDK-8250660\">JDK-8250660</a>.\n     * If a lower bound is set, the upper bound must be Object.class.\n     */\n    private static final class WildcardTypeImpl implements WildcardType, Serializable {\n        private final Type upperBound;\n        private final Type lowerBound;\n\n        public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {\n            checkArgument(lowerBounds.length <= 1);\n            checkArgument(upperBounds.length == 1);\n\n            if (lowerBounds.length == 1) {\n                Objects.requireNonNull(lowerBounds[0]);\n                checkNotPrimitive(lowerBounds[0]);\n                checkArgument(upperBounds[0] == Object.class);\n                this.lowerBound = canonicalize(lowerBounds[0]);\n                this.upperBound = Object.class;\n\n            } else {\n                Objects.requireNonNull(upperBounds[0]);\n                checkNotPrimitive(upperBounds[0]);\n                this.lowerBound = null;\n                this.upperBound = canonicalize(upperBounds[0]);\n            }\n        }\n\n        @Override\n        public Type @NotNull [] getUpperBounds() {\n            return new Type[]{upperBound};\n        }\n\n        @Override\n        public Type @NotNull [] getLowerBounds() {\n            return lowerBound != null ? new Type[]{lowerBound} : EMPTY_TYPE_ARRAY;\n        }\n\n        @Override\n        public boolean equals(Object other) {\n            return other instanceof WildcardType w && TypeUtils.equals(this, w);\n        }\n\n        @Override\n        public int hashCode() {\n            // this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds());\n            return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode());\n        }\n\n        @Override\n        public String toString() {\n            if (lowerBound != null) {\n                return \"? super \" + typeToString(lowerBound);\n            } else if (upperBound == Object.class) {\n                return \"?\";\n            } else {\n                return \"? extends \" + typeToString(upperBound);\n            }\n        }\n\n        @Serial\n        private static final long serialVersionUID = 0;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalBiConsumer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.function;\n\nimport static java.util.Objects.requireNonNull;\n\npublic interface ExceptionalBiConsumer<T, U, E extends Exception> {\n    void accept(T t, U u) throws E;\n\n    default ExceptionalBiConsumer<T, U, ?> andThen(ExceptionalBiConsumer<? super T, ? super U, ?> after) {\n        requireNonNull(after);\n\n        return (l, r) -> {\n            this.accept(l, r);\n            after.accept(l, r);\n        };\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalBiFunction.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.function;\n\nimport static java.util.Objects.requireNonNull;\n\npublic interface ExceptionalBiFunction<T, U, R, E extends Exception> {\n    R apply(T t, U u) throws E;\n\n    default <V> ExceptionalBiFunction<T, U, V, ?> andThen(ExceptionalFunction<? super R, ? extends V, ?> after) {\n        requireNonNull(after);\n        return (t, u) -> after.apply(apply(t, u));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalConsumer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.function;\n\n/**\n * @author huangyuhui\n */\npublic interface ExceptionalConsumer<T, E extends Exception> {\n    void accept(T t) throws E;\n\n    static <T, E extends Exception> ExceptionalConsumer<T, E> fromRunnable(ExceptionalRunnable<E> runnable) {\n        return new ExceptionalConsumer<T, E>() {\n            @Override\n            public void accept(T o) throws E {\n                runnable.run();\n            }\n\n            @Override\n            public String toString() {\n                return runnable.toString();\n            }\n        };\n    }\n\n    static <T> ExceptionalConsumer<T, ?> empty() {\n        return s -> {\n        };\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalFunction.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.function;\n\n/**\n *\n * @author huangyuhui\n */\npublic interface ExceptionalFunction<T, R, E extends Exception> {\n    R apply(T t) throws E;\n\n    static <T, E extends RuntimeException> ExceptionalFunction<T, T, E> identity() {\n        return t -> t;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalPredicate.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.function;\n\npublic interface ExceptionalPredicate<T, E extends Exception> {\n    boolean test(T t) throws E;\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalRunnable.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.function;\n\nimport java.util.concurrent.Callable;\n\n/**\n *\n * @author huangyuhui\n */\npublic interface ExceptionalRunnable<E extends Exception> {\n\n    void run() throws E;\n\n    default Callable<Void> toCallable() {\n        return () -> {\n            run();\n            return null;\n        };\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalSupplier.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.function;\n\nimport java.util.concurrent.Callable;\n\n/**\n *\n * @author huangyuhui\n */\npublic interface ExceptionalSupplier<R, E extends Exception> {\n    R get() throws E;\n    \n    default Callable<R> toCallable() {\n        return this::get;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/EnumOrdinalDeserializer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.SerializedName;\n\nimport java.lang.reflect.Type;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * A deserializer that supports deserializing strings and **numbers** into enums.\n *\n * @author yushijinhun\n */\npublic final class EnumOrdinalDeserializer<T extends Enum<T>> implements JsonDeserializer<T> {\n\n    private final Map<String, T> mapping = new HashMap<>();\n\n    public EnumOrdinalDeserializer(Class<T> enumClass) {\n        for (T constant : enumClass.getEnumConstants()) {\n            mapping.put(String.valueOf(constant.ordinal()), constant);\n            String name = constant.name();\n            try {\n                SerializedName annotation = enumClass.getField(name).getAnnotation(SerializedName.class);\n                if (annotation != null) {\n                    name = annotation.value();\n                    for (String alternate : annotation.alternate()) {\n                        mapping.put(alternate, constant);\n                    }\n                }\n            } catch (NoSuchFieldException e) {\n                throw new AssertionError(e);\n            }\n            mapping.put(name, constant);\n        }\n    }\n\n    @Override\n    public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n        if (json == null || json.isJsonNull())\n            return null;\n\n        String name = json.getAsString();\n        T value = mapping.get(name);\n        if (value == null)\n            throw new JsonParseException(\"No enum constant with name \" + name);\n        return value;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapter.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.*;\n\nimport java.lang.reflect.Type;\nimport java.time.*;\nimport java.time.format.DateTimeFormatter;\nimport java.time.format.DateTimeFormatterBuilder;\nimport java.time.format.DateTimeParseException;\nimport java.time.format.FormatStyle;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Locale;\n\nimport static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;\n\npublic final class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {\n    public static final InstantTypeAdapter INSTANCE = new InstantTypeAdapter();\n\n    private InstantTypeAdapter() {\n    }\n\n    @Override\n    public JsonElement serialize(Instant t, Type type, JsonSerializationContext jsc) {\n        return new JsonPrimitive(serializeToString(t, ZoneId.systemDefault()));\n    }\n\n    @Override\n    public Instant deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {\n        if (!(json instanceof JsonPrimitive))\n            throw new JsonParseException(\"The instant should be a string value\");\n        else {\n            Instant time = deserializeToInstant(json.getAsString());\n            if (type == Instant.class)\n                return time;\n            else\n                throw new IllegalArgumentException(this.getClass() + \" cannot be deserialized to \" + type);\n        }\n    }\n\n    private static final DateTimeFormatter EN_US_FORMAT = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM)\n            .withLocale(Locale.US)\n            .withZone(ZoneId.systemDefault());\n    private static final DateTimeFormatter ISO_DATE_TIME = new DateTimeFormatterBuilder()\n            .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)\n            .optionalStart().appendOffset(\"+HH:MM\", \"+00:00\").optionalEnd()\n            .optionalStart().appendOffset(\"+HHMM\", \"+0000\").optionalEnd()\n            .optionalStart().appendOffset(\"+HH\", \"Z\").optionalEnd()\n            .optionalStart().appendOffsetId().optionalEnd()\n            .toFormatter();\n\n    public static Instant deserializeToInstant(String string) {\n        try {\n            return ZonedDateTime.parse(string, EN_US_FORMAT).toInstant();\n        } catch (DateTimeParseException ex1) {\n            try {\n                ZonedDateTime zonedDateTime = ZonedDateTime.parse(string, ISO_DATE_TIME);\n                return zonedDateTime.toInstant();\n            } catch (DateTimeParseException e) {\n                try {\n                    LocalDateTime localDateTime = LocalDateTime.parse(string, ISO_LOCAL_DATE_TIME);\n                    return localDateTime.atZone(ZoneId.systemDefault()).toInstant();\n                } catch (DateTimeParseException e2) {\n                    throw new JsonParseException(\"Invalid instant: \" + string, e);\n                }\n            }\n        }\n    }\n\n    public static String serializeToString(Instant instant, ZoneId zone) {\n        return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.ofInstant(instant, zone).truncatedTo(ChronoUnit.SECONDS));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonMap.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * To resolve JsonParseException: duplicate key: null\n * By skipping inserting data with key null\n * @param <K>\n * @param <V>\n */\npublic final class JsonMap<K, V> extends HashMap<K, V> {\n    public JsonMap(int initialCapacity, float loadFactor) {\n        super(initialCapacity, loadFactor);\n    }\n\n    public JsonMap(int initialCapacity) {\n        super(initialCapacity);\n    }\n\n    public JsonMap() {\n        super();\n    }\n\n    public JsonMap(Map<? extends K, ? extends V> m) {\n        super(m);\n    }\n\n    @Override\n    public V put(K key, V value) {\n        if (key == null) return null;\n        return super.put(key, value);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonSerializable.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport java.lang.annotation.*;\n\n/// Marks a class as being serialized/deserialized by Gson.\n/// This annotation is not retained at runtime and only serves as a reminder to developers not to change field names arbitrarily.\n///\n/// TODO: Add this annotation to all classes in the HMCL project that need to be serialized/deserialized.\n///\n/// @author Glavo\n@Documented\n@Retention(RetentionPolicy.SOURCE)\n@Target(ElementType.TYPE)\npublic @interface JsonSerializable {\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonSubtype.java",
    "content": "package org.jackhuang.hmcl.util.gson;\n\npublic @interface JsonSubtype {\n    Class<?> clazz();\n\n    String name();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonType.java",
    "content": "package org.jackhuang.hmcl.util.gson;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface JsonType {\n    String property();\n\n    JsonSubtype[] subtypes();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonTypeAdapterFactory.java",
    "content": "package org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.*;\nimport com.google.gson.internal.Streams;\nimport com.google.gson.reflect.TypeToken;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic final class JsonTypeAdapterFactory implements TypeAdapterFactory {\n\n    public static final JsonTypeAdapterFactory INSTANCE = new JsonTypeAdapterFactory();\n\n    private <T> TypeAdapter<T> createForJsonType(Gson gson, TypeToken<T> type) {\n        Class<? super T> rawType = type.getRawType();\n        JsonType jsonType = rawType.getDeclaredAnnotation(JsonType.class);\n        if (jsonType == null)\n            return null;\n        JsonSubtype[] subtypes = jsonType.subtypes();\n        Map<String, TypeAdapter<?>> labelTypeAdapterMap = new HashMap<>();\n        Map<Class<?>, TypeAdapter<?>> classTypeAdapterMap = new HashMap<>();\n        Map<Class<?>, JsonSubtype> classJsonSubtypeMap = new HashMap<>();\n        for (JsonSubtype subtype : subtypes) {\n            TypeAdapter<?> typeAdapter = gson.getDelegateAdapter(this, TypeToken.get(subtype.clazz()));\n            labelTypeAdapterMap.put(subtype.name(), typeAdapter);\n            classTypeAdapterMap.put(subtype.clazz(), typeAdapter);\n            classJsonSubtypeMap.put(subtype.clazz(), subtype);\n        }\n\n        return new TypeAdapter<T>() {\n            @Override\n            public void write(JsonWriter out, T value) throws IOException {\n                Class<?> type = value.getClass();\n                @SuppressWarnings(\"unchecked\")\n                TypeAdapter<T> delegate = (TypeAdapter<T>) classTypeAdapterMap.get(type);\n                if (delegate == null) {\n                    throw new JsonParseException(\"Cannot serialize \" + type.getName() + \". Please check your @JsonType configuration\");\n                }\n                JsonSubtype subtype = classJsonSubtypeMap.get(type);\n                JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();\n                if (jsonObject.has(jsonType.property())) {\n                    throw new JsonParseException(\"Cannot serialize \" + type.getName() + \". Because it has already defined a field named '\" + jsonType.property() + \"'\");\n                }\n                jsonObject.add(jsonType.property(), new JsonPrimitive(subtype.name()));\n                Streams.write(jsonObject, out);\n            }\n\n            @Override\n            public T read(JsonReader in) {\n                JsonElement jsonElement = Streams.parse(in);\n                JsonElement typeLabelElement = jsonElement.getAsJsonObject().get(jsonType.property());\n                if (typeLabelElement == null) {\n                    throw new JsonParseException(\"Cannot deserialize \" + type + \". Because it does not define a field named '\" + jsonType.property() + \"'\");\n                }\n                String typeLabel = typeLabelElement.getAsString();\n                @SuppressWarnings(\"unchecked\")\n                TypeAdapter<T> delegate = (TypeAdapter<T>) labelTypeAdapterMap.get(typeLabel);\n                if (delegate == null) {\n                    throw new JsonParseException(\"Cannot deserialize \" + type + \" with subtype '\" + typeLabel + \"'\");\n                }\n\n                return delegate.fromJsonTree(jsonElement);\n            }\n        };\n    }\n\n    private <T> TypeAdapter<T> createForJsonSubtype(Gson gson, TypeToken<T> type) {\n        Class<? super T> rawType = type.getRawType();\n        if (rawType.getSuperclass() == null) return null;\n        JsonType jsonType = rawType.getSuperclass().getDeclaredAnnotation(JsonType.class);\n        if (jsonType == null)\n            return null;\n        JsonSubtype jsonSubtype = null;\n        for (JsonSubtype subtype : jsonType.subtypes()) {\n            if (subtype.clazz() == rawType) {\n                jsonSubtype = subtype;\n            }\n        }\n        if (jsonSubtype == null)\n            return null;\n        final JsonSubtype subtype = jsonSubtype;\n\n        TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);\n\n        return new TypeAdapter<T>() {\n            @Override\n            public void write(JsonWriter out, T value) throws IOException {\n                Class<?> type = value.getClass();\n                JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();\n                if (jsonObject.has(jsonType.property())) {\n                    throw new JsonParseException(\"Cannot serialize \" + type.getName() + \". Because it has already defined a field named '\" + jsonType.property() + \"'\");\n                }\n                jsonObject.add(jsonType.property(), new JsonPrimitive(subtype.name()));\n                Streams.write(jsonObject, out);\n            }\n\n            @Override\n            public T read(JsonReader in) throws IOException {\n                return delegate.read(in);\n            }\n        };\n    }\n\n    @Override\n    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {\n        TypeAdapter<T> typeAdapter = createForJsonType(gson, type);\n        if (typeAdapter == null)\n            typeAdapter = createForJsonSubtype(gson, type);\n        return typeAdapter;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonParseException;\nimport com.google.gson.JsonSyntaxException;\nimport com.google.gson.reflect.TypeToken;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\n\n/**\n * @author yushijinhun\n */\n@SuppressWarnings(\"unchecked\")\npublic final class JsonUtils {\n\n    public static final Gson GSON = defaultGsonBuilder().create();\n\n    public static final Gson UGLY_GSON = new GsonBuilder()\n            .registerTypeAdapterFactory(JsonTypeAdapterFactory.INSTANCE)\n            .registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)\n            .registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory.INSTANCE)\n            .create();\n\n    private JsonUtils() {\n    }\n\n    public static <T> TypeToken<List<T>> listTypeOf(Class<T> elementType) {\n        return (TypeToken<List<T>>) TypeToken.getParameterized(List.class, elementType);\n    }\n\n    public static <T> TypeToken<List<T>> listTypeOf(TypeToken<T> elementType) {\n        return (TypeToken<List<T>>) TypeToken.getParameterized(List.class, elementType.getType());\n    }\n\n    public static <K, V> TypeToken<Map<K, V>> mapTypeOf(Class<K> keyType, Class<V> valueType) {\n        return (TypeToken<Map<K, V>>) TypeToken.getParameterized(Map.class, keyType, valueType);\n    }\n\n    public static <K, V> TypeToken<Map<K, V>> mapTypeOf(Class<K> keyType, TypeToken<V> valueType) {\n        return (TypeToken<Map<K, V>>) TypeToken.getParameterized(Map.class, keyType, valueType.getType());\n    }\n\n    public static <T> T fromJsonFile(Path file, Class<T> classOfT) throws IOException {\n        return fromJsonFile(file, TypeToken.get(classOfT));\n    }\n\n    public static <T> T fromJsonFile(Path file, TypeToken<T> type) throws IOException {\n        try (var reader = Files.newBufferedReader(file)) {\n            return GSON.fromJson(reader, type.getType());\n        }\n    }\n\n    public static <T> T fromJsonFully(InputStream json, Class<T> classOfT) throws IOException, JsonParseException {\n        try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {\n            return GSON.fromJson(reader, classOfT);\n        }\n    }\n\n    public static <T> T fromJsonFully(InputStream json, TypeToken<T> type) throws IOException, JsonParseException {\n        try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {\n            return GSON.fromJson(reader, type);\n        }\n    }\n\n    public static <T> T fromNonNullJson(String json, Class<T> classOfT) throws JsonParseException {\n        T parsed = GSON.fromJson(json, classOfT);\n        if (parsed == null)\n            throw new JsonParseException(\"Json object cannot be null.\");\n        return parsed;\n    }\n\n    public static <T> T fromNonNullJson(String json, TypeToken<T> type) throws JsonParseException {\n        T parsed = GSON.fromJson(json, type);\n        if (parsed == null)\n            throw new JsonParseException(\"Json object cannot be null.\");\n        return parsed;\n    }\n\n    public static <T> T fromNonNullJsonFully(InputStream json, Class<T> classOfT) throws IOException, JsonParseException {\n        try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {\n            T parsed = GSON.fromJson(reader, classOfT);\n            if (parsed == null)\n                throw new JsonParseException(\"Json object cannot be null.\");\n            return parsed;\n        }\n    }\n\n    public static <T> T fromNonNullJsonFully(InputStream json, TypeToken<T> type) throws IOException, JsonParseException {\n        try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {\n            T parsed = GSON.fromJson(reader, type);\n            if (parsed == null)\n                throw new JsonParseException(\"Json object cannot be null.\");\n            return parsed;\n        }\n    }\n\n    public static <T> T fromMaybeMalformedJson(String json, Class<T> classOfT) throws JsonParseException {\n        try {\n            return GSON.fromJson(json, classOfT);\n        } catch (JsonSyntaxException e) {\n            return null;\n        }\n    }\n\n    public static <T> T fromMaybeMalformedJson(String json, TypeToken<T> type) throws JsonParseException {\n        try {\n            return GSON.fromJson(json, type);\n        } catch (JsonSyntaxException e) {\n            return null;\n        }\n    }\n\n    public static void writeToJsonFile(Path file, Object value) throws IOException {\n        try (var writer = Files.newBufferedWriter(file)) {\n            GSON.toJson(value, writer);\n        }\n    }\n\n    public static GsonBuilder defaultGsonBuilder() {\n        return new GsonBuilder()\n                .enableComplexMapKeySerialization()\n                .setPrettyPrinting()\n                .registerTypeAdapter(Instant.class, InstantTypeAdapter.INSTANCE)\n                .registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)\n                .registerTypeAdapter(Path.class, PathTypeAdapter.INSTANCE)\n                .registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)\n                .registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory.INSTANCE)\n                .registerTypeAdapterFactory(JsonTypeAdapterFactory.INSTANCE);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/LowerCaseEnumTypeAdapterFactory.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.Gson;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.TypeAdapterFactory;\nimport com.google.gson.reflect.TypeToken;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Locale;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class LowerCaseEnumTypeAdapterFactory implements TypeAdapterFactory {\n\n    public static final LowerCaseEnumTypeAdapterFactory INSTANCE = new LowerCaseEnumTypeAdapterFactory();\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> tt) {\n        Class<? super T> rawType = tt.getRawType();\n        if (!rawType.isEnum())\n            return null;\n\n        HashMap<String, T> lowercaseToConstant = new HashMap<>();\n        for (Object constant : rawType.getEnumConstants())\n            lowercaseToConstant.put(toLowercase(constant), (T) constant);\n\n        return new TypeAdapter<T>() {\n            @Override\n            public void write(JsonWriter writer, T t) throws IOException {\n                if (t == null)\n                    writer.nullValue();\n                else\n                    writer.value(toLowercase(t));\n            }\n\n            @Override\n            public T read(JsonReader reader) throws IOException {\n                if (reader.peek() == JsonToken.NULL) {\n                    reader.nextNull();\n                    return null;\n                }\n                return lowercaseToConstant.get(reader.nextString().toLowerCase(Locale.ROOT));\n            }\n        };\n    }\n\n    private static String toLowercase(Object o) {\n        return o.toString().toLowerCase(Locale.ROOT);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/ObservableSetting.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.SerializedName;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\nimport javafx.beans.property.ListProperty;\nimport javafx.beans.property.MapProperty;\nimport javafx.beans.property.Property;\nimport javafx.beans.property.SetProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.collections.ObservableMap;\nimport javafx.collections.ObservableSet;\nimport org.jackhuang.hmcl.util.TypeUtils;\nimport org.jackhuang.hmcl.util.javafx.DirtyTracker;\nimport org.jackhuang.hmcl.util.javafx.ObservableHelper;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.*;\n\n/// Represents a settings object with multiple [Observable] fields.\n///\n/// All instance fields in this object, unless marked as `transient`, are considered observable fields.\n/// The field types should be subclasses of one of [javafx.beans.property.Property], [javafx.collections.ObservableList], [javafx.collections.ObservableSet], or [javafx.collections.ObservableMap].\n///\n/// This class implements the [Observable] interface.\n/// If any field in a settings object changes, all listeners installed via [#addListener(InvalidationListener)] will be triggered.\n///\n/// For each observable field, this object tracks whether it has been modified. When serializing, fields that have never been modified will not be serialized by default.\n///\n/// All subclasses of this class must call [#register()] once in their constructor.\n///\n/// @author Glavo\npublic abstract class ObservableSetting implements Observable {\n\n    private static final ClassValue<List<? extends ObservableField<?>>> FIELDS = new ClassValue<>() {\n        @Override\n        protected List<? extends ObservableField<?>> computeValue(@NotNull Class<?> type) {\n            if (!ObservableSetting.class.isAssignableFrom(type))\n                throw new AssertionError(\"Type: \" + type);\n\n            try {\n                var allFields = new ArrayList<ObservableField<?>>();\n\n                for (Class<?> current = type;\n                     current != ObservableSetting.class;\n                     current = current.getSuperclass()) {\n                    final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(current, MethodHandles.lookup());\n\n                    Field[] fields = current.getDeclaredFields();\n                    for (Field field : fields) {\n                        int modifiers = field.getModifiers();\n                        if (Modifier.isTransient(modifiers) || Modifier.isStatic(modifiers))\n                            continue;\n\n                        allFields.add(ObservableField.of(lookup, field));\n                    }\n                }\n\n                return allFields;\n            } catch (IllegalAccessException e) {\n                throw new ExceptionInInitializerError(e);\n            }\n        }\n    };\n\n    protected transient final ObservableHelper helper = new ObservableHelper(this);\n    protected transient final Map<String, JsonElement> unknownFields = new HashMap<>();\n    protected transient final DirtyTracker tracker = new DirtyTracker();\n\n    private boolean registered = false;\n\n    protected final void register() {\n        if (registered)\n            return;\n\n        registered = true;\n\n        @SuppressWarnings(\"unchecked\")\n        var fields = (List<ObservableField<ObservableSetting>>) FIELDS.get(this.getClass());\n        for (var field : fields) {\n            Observable observable = field.get(this);\n            tracker.track(observable);\n            observable.addListener(helper);\n        }\n    }\n\n    @Override\n    public void addListener(InvalidationListener listener) {\n        helper.addListener(listener);\n    }\n\n    @Override\n    public void removeListener(InvalidationListener listener) {\n        helper.removeListener(listener);\n    }\n\n    private static sealed abstract class ObservableField<T> {\n\n        public static <T> ObservableField<T> of(MethodHandles.Lookup lookup, Field field) {\n            String name;\n            List<String> alternateNames;\n\n            SerializedName serializedName = field.getAnnotation(SerializedName.class);\n            if (serializedName == null) {\n                name = field.getName();\n                alternateNames = List.of();\n            } else {\n                name = serializedName.value();\n                alternateNames = List.of(serializedName.alternate());\n            }\n\n            VarHandle varHandle;\n            try {\n                varHandle = lookup.unreflectVarHandle(field);\n            } catch (IllegalAccessException e) {\n                throw new IllegalArgumentException(e);\n            }\n\n            if (ObservableList.class.isAssignableFrom(field.getType())) {\n                Type listType = TypeUtils.getSupertype(field.getGenericType(), field.getType(), List.class);\n                if (!(listType instanceof ParameterizedType))\n                    throw new IllegalArgumentException(\"Cannot resolve the list type of \" + field.getName());\n                return new CollectionField<>(name, alternateNames, varHandle, listType, listType);\n            } else if (ObservableSet.class.isAssignableFrom(field.getType())) {\n                Type setType = TypeUtils.getSupertype(field.getGenericType(), field.getType(), Set.class);\n                if (!(setType instanceof ParameterizedType))\n                    throw new IllegalArgumentException(\"Cannot resolve the set type of \" + field.getName());\n\n                ParameterizedType listType = TypeUtils.newParameterizedTypeWithOwner(\n                        null,\n                        List.class,\n                        ((ParameterizedType) setType).getActualTypeArguments()[0]\n                );\n                return new CollectionField<>(name, alternateNames, varHandle, setType, listType);\n            } else if (ObservableMap.class.isAssignableFrom(field.getType())) {\n                Type mapType = TypeUtils.getSupertype(field.getGenericType(), field.getType(), Map.class);\n                if (!(mapType instanceof ParameterizedType))\n                    throw new IllegalArgumentException(\"Cannot resolve the map type of \" + field.getName());\n                return new MapField<>(name, alternateNames, varHandle, mapType);\n            } else if (Property.class.isAssignableFrom(field.getType())) {\n                Type propertyType = TypeUtils.getSupertype(field.getGenericType(), field.getType(), Property.class);\n                if (!(propertyType instanceof ParameterizedType))\n                    throw new IllegalArgumentException(\"Cannot resolve the element type of \" + field.getName());\n                Type elementType = ((ParameterizedType) propertyType).getActualTypeArguments()[0];\n                return new PropertyField<>(name, alternateNames, varHandle, elementType);\n            } else {\n                throw new IllegalArgumentException(\"Field \" + field.getName() + \" is not a property or observable collection\");\n            }\n        }\n\n        protected final String serializedName;\n        protected final List<String> alternateNames;\n        protected final VarHandle varHandle;\n\n        private ObservableField(String serializedName, List<String> alternateNames, VarHandle varHandle) {\n            this.serializedName = serializedName;\n            this.alternateNames = alternateNames;\n            this.varHandle = varHandle;\n        }\n\n        public String getSerializedName() {\n            return serializedName;\n        }\n\n        public List<String> getAlternateNames() {\n            return alternateNames;\n        }\n\n        public Observable get(T value) {\n            return (Observable) varHandle.get(value);\n        }\n\n        public abstract void serialize(JsonObject result, T value, JsonSerializationContext context);\n\n        public abstract void deserialize(T value, JsonElement element, JsonDeserializationContext context);\n\n        private static final class PropertyField<T> extends ObservableField<T> {\n            private final Type elementType;\n\n            PropertyField(String serializedName, List<String> alternate, VarHandle varHandle, Type elementType) {\n                super(serializedName, alternate, varHandle);\n                this.elementType = elementType;\n            }\n\n            @Override\n            public void serialize(JsonObject result, T value, JsonSerializationContext context) {\n                Property<?> property = (Property<?>) get(value);\n\n                if (property instanceof RawPreservingProperty<?> rawPreserving) {\n                    JsonElement rawJson = rawPreserving.getRawJson();\n                    if (rawJson != null) {\n                        result.add(getSerializedName(), rawJson);\n                        return;\n                    }\n                }\n\n                JsonElement serialized = context.serialize(property.getValue(), elementType);\n                if (serialized != null && !serialized.isJsonNull())\n                    result.add(getSerializedName(), serialized);\n            }\n\n            @Override\n            @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n            public void deserialize(T value, JsonElement element, JsonDeserializationContext context) {\n                Property property = (Property) get(value);\n\n                try {\n                    property.setValue(context.deserialize(element, elementType));\n                } catch (Throwable e) {\n                    if (property instanceof RawPreservingProperty<?>) {\n                        ((RawPreservingProperty<?>) property).setRawJson(element);\n                    } else {\n                        throw e;\n                    }\n                }\n            }\n        }\n\n        private static final class CollectionField<T> extends ObservableField<T> {\n            private final Type collectionType;\n\n            /// When deserializing a Set, we first deserialize it into a `List`, then put the elements into the Set.\n            private final Type listType;\n\n            CollectionField(String serializedName, List<String> alternate, VarHandle varHandle,\n                            Type collectionType, Type listType) {\n                super(serializedName, alternate, varHandle);\n                this.collectionType = collectionType;\n                this.listType = listType;\n            }\n\n            @Override\n            public void serialize(JsonObject result, T value, JsonSerializationContext context) {\n                result.add(getSerializedName(), context.serialize(get(value), collectionType));\n            }\n\n            @SuppressWarnings({\"unchecked\"})\n            @Override\n            public void deserialize(T value, JsonElement element, JsonDeserializationContext context) {\n                List<?> deserialized = context.deserialize(element, listType);\n                Object fieldValue = get(value);\n\n                if (fieldValue instanceof ListProperty) {\n                    ((ListProperty<Object>) fieldValue).set(FXCollections.observableList((List<Object>) deserialized));\n                } else if (fieldValue instanceof ObservableList) {\n                    ((ObservableList<Object>) fieldValue).setAll(deserialized);\n                } else if (fieldValue instanceof SetProperty) {\n                    ((SetProperty<Object>) fieldValue).set(FXCollections.observableSet(new HashSet<>(deserialized)));\n                } else if (fieldValue instanceof ObservableSet) {\n                    ObservableSet<Object> set = (ObservableSet<Object>) fieldValue;\n                    set.clear();\n                    set.addAll(deserialized);\n                } else {\n                    throw new JsonParseException(\"Unsupported field type: \" + fieldValue.getClass());\n                }\n            }\n        }\n\n        private static final class MapField<T> extends ObservableField<T> {\n            private final Type mapType;\n\n            MapField(String serializedName, List<String> alternate, VarHandle varHandle, Type mapType) {\n                super(serializedName, alternate, varHandle);\n                this.mapType = mapType;\n            }\n\n            @Override\n            public void serialize(JsonObject result, T value, JsonSerializationContext context) {\n                result.add(getSerializedName(), context.serialize(get(value), mapType));\n            }\n\n            @SuppressWarnings({\"unchecked\"})\n            @Override\n            public void deserialize(T config, JsonElement element, JsonDeserializationContext context) {\n                Map<Object, Object> deserialized = context.deserialize(element, mapType);\n                ObservableMap<Object, Object> map = (ObservableMap<Object, Object>) varHandle.get(config);\n                if (map instanceof MapProperty<?, ?>)\n                    ((MapProperty<Object, Object>) map).set(FXCollections.observableMap(deserialized));\n                else {\n                    map.clear();\n                    map.putAll(deserialized);\n                }\n            }\n        }\n    }\n\n    public static abstract class Adapter<T extends ObservableSetting>\n            implements JsonSerializer<T>, JsonDeserializer<T> {\n\n        protected abstract T createInstance();\n\n        @Override\n        public JsonElement serialize(T setting, Type typeOfSrc, JsonSerializationContext context) {\n            if (setting == null)\n                return JsonNull.INSTANCE;\n\n            @SuppressWarnings(\"unchecked\")\n            var fields = (List<ObservableField<T>>) FIELDS.get(setting.getClass());\n\n            JsonObject result = new JsonObject();\n            for (var field : fields) {\n                Observable observable = field.get(setting);\n                if (setting.tracker.isDirty(observable)) {\n                    field.serialize(result, setting, context);\n                }\n            }\n            setting.unknownFields.forEach(result::add);\n            return result;\n        }\n\n        @Override\n        public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (json == null || json.isJsonNull())\n                return null;\n\n            if (!json.isJsonObject())\n                throw new JsonParseException(\"Config is not an object: \" + json);\n\n            T setting = createInstance();\n            @SuppressWarnings(\"unchecked\")\n            var fields = (List<ObservableField<T>>) FIELDS.get(setting.getClass());\n\n            var values = new LinkedHashMap<>(json.getAsJsonObject().asMap());\n            for (ObservableField<T> field : fields) {\n                JsonElement value = values.remove(field.getSerializedName());\n                if (value == null) {\n                    for (String alternateName : field.getAlternateNames()) {\n                        value = values.remove(alternateName);\n                        if (value != null)\n                            break;\n                    }\n                }\n\n                if (value != null) {\n                    setting.tracker.markDirty(field.get(setting));\n                    field.deserialize(setting, value, context);\n                }\n            }\n\n            setting.unknownFields.putAll(values);\n            return setting;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/PaintAdapter.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.*;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\nimport javafx.scene.paint.*;\n\nimport java.io.IOException;\n\npublic final class PaintAdapter extends TypeAdapter<Paint> {\n\n    @Override\n    public void write(JsonWriter out, Paint value) throws IOException {\n        if (value == null) {\n            out.nullValue();\n            return;\n        }\n\n        if (value instanceof Color) {\n            Color color = (Color) value;\n            int red = (int) Math.round(color.getRed() * 255.);\n            int green = (int) Math.round(color.getGreen() * 255.);\n            int blue = (int) Math.round(color.getBlue() * 255.);\n            int opacity = (int) Math.round(color.getOpacity() * 255.);\n            if (opacity < 255)\n                out.value(String.format(\"#%02x%02x%02x%02x\", red, green, blue, opacity));\n            else\n                out.value(String.format(\"#%02x%02x%02x\", red, green, blue));\n        } else if (value instanceof LinearGradient\n                || value instanceof RadialGradient) {\n            out.value(value.toString());\n        } else {\n            throw new JsonParseException(\"Unsupported Paint type: \" + value.getClass().getName());\n        }\n    }\n\n    @Override\n    public Paint read(JsonReader in) throws IOException {\n        if (in.peek() == JsonToken.NULL) {\n            in.nextNull();\n            return null;\n        }\n        String value = in.nextString();\n        return Paint.valueOf(value);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/PathTypeAdapter.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Path;\n\n/// @author Glavo\npublic final class PathTypeAdapter extends TypeAdapter<Path> {\n\n    public static final PathTypeAdapter INSTANCE = new PathTypeAdapter();\n\n    @Override\n    public Path read(JsonReader in) throws IOException {\n        if (in.peek() == JsonToken.NULL) {\n            in.nextNull();\n            return null;\n        }\n\n        String value = in.nextString();\n        if (File.separatorChar == '\\\\')\n            value = value.replace('/', '\\\\');\n        return Path.of(value);\n    }\n\n    @Override\n    public void write(JsonWriter out, Path path) throws IOException {\n        if (path != null) {\n            if (path.getFileSystem() != FileSystems.getDefault())\n                throw new IOException(\"Unsupported file system: \" + path.getFileSystem());\n\n            String value = path.toString();\n            if (!path.isAbsolute() && File.separatorChar == '\\\\')\n                value = value.replace('\\\\', '/');\n            out.value(value);\n        } else {\n            out.nullValue();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingObjectProperty.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.JsonElement;\nimport javafx.beans.property.SimpleObjectProperty;\nimport org.jetbrains.annotations.Nullable;\n\n/// @author Glavo\n/// @see ObservableSetting\npublic class RawPreservingObjectProperty<T> extends SimpleObjectProperty<T> implements RawPreservingProperty<T> {\n    private JsonElement rawJson;\n\n    public RawPreservingObjectProperty() {\n    }\n\n    public RawPreservingObjectProperty(T initialValue) {\n        super(initialValue);\n    }\n\n    public RawPreservingObjectProperty(Object bean, String name) {\n        super(bean, name);\n    }\n\n    public RawPreservingObjectProperty(Object bean, String name, T initialValue) {\n        super(bean, name, initialValue);\n    }\n\n    @Override\n    public void setRawJson(JsonElement value) {\n        this.rawJson = value;\n    }\n\n    @Override\n    public @Nullable JsonElement getRawJson() {\n        return rawJson;\n    }\n\n    @Override\n    protected void invalidated() {\n        this.rawJson = null;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingProperty.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.JsonElement;\nimport javafx.beans.property.Property;\nimport org.jetbrains.annotations.Nullable;\n\n/// If a Property implementing this interface fails to deserialize a json object into a value, it will store the original JsonElement internally.\n/// If the value does not change at runtime, the original JsonElement will be written back during serialization.\n///\n/// @author Glavo\npublic interface RawPreservingProperty<T> extends Property<T> {\n    void setRawJson(JsonElement value);\n\n    @Nullable JsonElement getRawJson();\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/TolerableValidationException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\n/**\n * This exception gets thrown by implementations of {@link Validation#validate()} if you want to replace\n * the nullable JSON-parsed object which does not satisfy the constraint with null value.\n * @see Validation\n */\npublic final class TolerableValidationException extends Exception {\n\n    public TolerableValidationException() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/UUIDTypeAdapter.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\n\nimport java.io.IOException;\nimport java.util.UUID;\nimport java.util.regex.Pattern;\n\n/**\n *\n * @author huang\n */\npublic final class UUIDTypeAdapter extends TypeAdapter<UUID> {\n\n    public static final UUIDTypeAdapter INSTANCE = new UUIDTypeAdapter();\n\n    @Override\n    public void write(JsonWriter writer, UUID value) throws IOException {\n        writer.value(value == null ? null : fromUUID(value));\n    }\n\n    @Override\n    public UUID read(JsonReader reader) throws IOException {\n        try {\n            return fromString(reader.nextString());\n        } catch (IllegalArgumentException e) {\n            throw new JsonParseException(\"UUID malformed\");\n        }\n    }\n\n    public static String fromUUID(UUID value) {\n        return value.toString().replace(\"-\", \"\");\n    }\n\n    private static final Pattern regex = Pattern.compile(\"(\\\\w{8})(\\\\w{4})(\\\\w{4})(\\\\w{4})(\\\\w{12})\");\n\n    public static UUID fromString(String input) {\n        return UUID.fromString(regex.matcher(input).replaceFirst(\"$1-$2-$3-$4-$5\"));\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/Validation.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.JsonParseException;\n\n/**\n * Check if the json object's fields automatically filled by Gson are in right format.\n *\n * @author huangyuhui\n */\npublic interface Validation {\n\n    /**\n     * 1. Check some non-null fields and;\n     * 2. Check strings and;\n     * 3. Check generic type of lists &lt;T&gt; and maps &lt;K, V&gt; are correct.\n     *\n     * Will be called immediately after initialization.\n     * Throw an exception when values are malformed.\n     *\n     * @throws JsonParseException if fields are filled in wrong format or wrong type.\n     * @throws TolerableValidationException if we want to replace this object with null (i.e. the object does not fulfill the constraints).\n     */\n    void validate() throws JsonParseException, TolerableValidationException;\n\n    static void requireNonNull(Object object, String message) throws JsonParseException {\n        if (object == null)\n            throw new JsonParseException(message);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/ValidationTypeAdapterFactory.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.Gson;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.TypeAdapterFactory;\nimport com.google.gson.reflect.TypeToken;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\n\nimport java.io.IOException;\n\n/**\n *\n * @author huangyuhui\n */\npublic final class ValidationTypeAdapterFactory implements TypeAdapterFactory {\n\n    public static final ValidationTypeAdapterFactory INSTANCE = new ValidationTypeAdapterFactory();\n\n    @Override\n    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> tt) {\n        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, tt);\n        return new TypeAdapter<T>() {\n            @Override\n            public void write(JsonWriter writer, T t) throws IOException {\n                if (t instanceof Validation) {\n                    try {\n                        ((Validation) t).validate();\n                    } catch (TolerableValidationException e) {\n                        delegate.write(writer, null);\n                        return;\n                    }\n                }\n\n                delegate.write(writer, t);\n            }\n\n            @Override\n            public T read(JsonReader reader) throws IOException {\n                T t = delegate.read(reader);\n                if (t instanceof Validation) {\n                    try {\n                        ((Validation) t).validate();\n                    } catch (TolerableValidationException e) {\n                        return null;\n                    }\n                }\n                return t;\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/DefaultResourceBundleControl.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n;\n\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.ResourceBundle;\n\n/// Overrides the default behavior of [ResourceBundle.Control], optimizing the candidate list generation logic.\n///\n/// Compared to the default implementation, [DefaultResourceBundleControl] optimizes the following scenarios:\n///\n/// - If no language is specified (such as [Locale#ROOT]), `en` is used instead.\n/// - For all Chinese locales, if no script is specified, the script (`Hans`/`Hant`/`Latn`) is always inferred based on region and variant.\n/// - For all Chinese locales, `zh-CN` is always added to the candidate list. If `zh-Hans` already exists in the candidate list,\n///   `zh-CN` is inserted before `zh`; otherwise, it is inserted after `zh`.\n/// - For all Traditional Chinese locales, `zh-TW` is always added to the candidate list (before `zh`).\n/// - For all supported ISO 639-3 language code (such as `eng`, `zho`, `lzh`, etc.),\n///  a candidate list with the language code replaced by the ISO 639-1 (Macro)language code is added to the end of the candidate list.\n///\n/// @author Glavo\npublic class DefaultResourceBundleControl extends ResourceBundle.Control {\n\n    public static final DefaultResourceBundleControl INSTANCE = new DefaultResourceBundleControl();\n\n    public DefaultResourceBundleControl() {\n    }\n\n    @Override\n    public List<Locale> getCandidateLocales(String baseName, Locale locale) {\n        return LocaleUtils.getCandidateLocales(locale);\n    }\n\n    @Override\n    public Locale getFallbackLocale(String baseName, Locale locale) {\n        // By default, when only the base bundle is found, it will attempt to fall back to Locale.getDefault() for further lookup.\n        // Since we always use the base bundle as the English resource file, we want to suppress this behavior.\n        return null;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n;\n\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.NativeUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.windows.Kernel32;\nimport org.jackhuang.hmcl.util.platform.windows.WinConstants;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.Unmodifiable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.ZoneId;\nimport java.time.ZonedDateTime;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class LocaleUtils {\n\n    public static final Locale SYSTEM_DEFAULT = Locale.getDefault();\n\n    public static final boolean IS_CHINA_MAINLAND = isChinaMainland();\n\n    private static boolean isChinaMainland() {\n        if (\"Asia/Shanghai\".equals(ZoneId.systemDefault().getId()))\n            return true;\n\n        // Check if the time zone is UTC+8\n        if (ZonedDateTime.now().getOffset().getTotalSeconds() == Duration.ofHours(8).toSeconds()) {\n            if (\"CN\".equals(LocaleUtils.SYSTEM_DEFAULT.getCountry()))\n                return true;\n\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && NativeUtils.USE_JNA) {\n                Kernel32 kernel32 = Kernel32.INSTANCE;\n\n                // https://learn.microsoft.com/windows/win32/intl/table-of-geographical-locations\n                if (kernel32 != null && kernel32.GetUserGeoID(WinConstants.GEOCLASS_NATION) == 45) // China\n                    return true;\n            }\n        }\n\n        return false;\n    }\n\n    public static final Locale LOCALE_ZH_HANS = Locale.forLanguageTag(\"zh-Hans\");\n    public static final Locale LOCALE_ZH_HANT = Locale.forLanguageTag(\"zh-Hant\");\n\n    public static final String DEFAULT_LANGUAGE_KEY = \"default\";\n\n    private static final Map<String, String> PARENT_LANGUAGE = loadCSV(\"sublanguages.csv\");\n    private static final Map<String, String> NORMALIZED_TAG = loadCSV(\"language_aliases.csv\");\n    private static final Map<String, String> DEFAULT_SCRIPT = loadCSV(\"default_script.csv\");\n    private static final Map<String, String> PREFERRED_LANGUAGE = Map.of(\"zh\", \"cmn\");\n    private static final Set<String> RTL_SCRIPTS = Set.of(\"Qabs\", \"Arab\", \"Hebr\");\n    private static final Set<String> CHINESE_TRADITIONAL_REGIONS = Set.of(\"TW\", \"HK\", \"MO\");\n\n    /// Load CSV files located in `/assets/lang/`.\n    /// Each line in these files contains at least two elements.\n    ///\n    /// For example, if a file contains `value0,value1,value2`, the return value will be `{value1=value0, value2=value0}`.\n    private static Map<String, String> loadCSV(String fileName) {\n        InputStream resource = LocaleUtils.class.getResourceAsStream(\"/assets/lang/\" + fileName);\n        if (resource == null) {\n            LOG.warning(\"Can't find file: \" + fileName);\n            return Map.of();\n        }\n\n        HashMap<String, String> result = new HashMap<>();\n        try (resource) {\n            new String(resource.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> {\n                if (line.startsWith(\"#\") || line.isBlank())\n                    return;\n\n                String[] items = line.split(\",\");\n                if (items.length < 2) {\n                    LOG.warning(\"Invalid line in \" + fileName + \": \" + line);\n                    return;\n                }\n\n                String parent = items[0];\n                for (int i = 1; i < items.length; i++) {\n                    result.put(items[i], parent);\n                }\n            });\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to load \" + fileName, e);\n        }\n\n        return Map.copyOf(result);\n    }\n\n    private static Locale getInstance(String language, String script, String region,\n                                      String variant) {\n        Locale.Builder builder = new Locale.Builder();\n        if (!language.isEmpty()) builder.setLanguage(language);\n        if (!script.isEmpty()) builder.setScript(script);\n        if (!region.isEmpty()) builder.setRegion(region);\n        if (!variant.isEmpty()) builder.setVariant(variant);\n        return builder.build();\n    }\n\n    /// Convert a locale to the language key.\n    ///\n    /// The language key is in the format of BCP 47 language tag.\n    /// If the locale is the default locale (language is empty), \"default\" will be returned.\n    public static String toLanguageKey(Locale locale) {\n        return locale.getLanguage().isEmpty()\n                ? DEFAULT_LANGUAGE_KEY\n                : locale.stripExtensions().toLanguageTag();\n    }\n\n    public static boolean isEnglish(Locale locale) {\n        return \"en\".equals(getRootLanguage(locale));\n    }\n\n    public static boolean isChinese(Locale locale) {\n        return \"zh\".equals(getRootLanguage(locale));\n    }\n\n    // ---\n\n    /// Normalize the language code to the code in the IANA Language Subtag Registry.\n    /// Typically, it normalizes ISO 639 alpha-3 codes to ISO 639 alpha-2 codes.\n    public static @NotNull String normalizeLanguage(String language) {\n        return language.isEmpty()\n                ? \"en\"\n                : NORMALIZED_TAG.getOrDefault(language, language);\n    }\n\n    /// If `language` is a sublanguage of a [macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage),\n    /// return the macrolanguage; otherwise, return `null`.\n    public static @Nullable String getParentLanguage(String language) {\n        return PARENT_LANGUAGE.get(language);\n    }\n\n    /// @see #getRootLanguage(String)\n    public static @NotNull String getRootLanguage(Locale locale) {\n        return getRootLanguage(locale.getLanguage());\n    }\n\n    /// - If `language` is a sublanguage of a [macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage),\n    /// return the macrolanguage;\n    /// - If `language` is an ISO 639 alpha-3 language code and there is a corresponding ISO 639 alpha-2 language code, return the ISO 639 alpha-2 code;\n    /// - If `language` is empty, return `en`;\n    /// - Otherwise, return the `language`.\n    public static @NotNull String getRootLanguage(String language) {\n        language = normalizeLanguage(language);\n\n        String parent = getParentLanguage(language);\n        return parent != null ? parent : language;\n    }\n\n    /// If `language` is a macrolanguage, try to map it to the most commonly used individual language.\n    ///\n    /// For example, if `language` is `zh`, this method will return `cmn`.\n    public static @NotNull String getPreferredLanguage(String language) {\n        language = normalizeLanguage(language);\n        return PREFERRED_LANGUAGE.getOrDefault(language, language);\n    }\n\n    /// Get the script of the locale. If the script is empty and the language is Chinese,\n    /// the script will be inferred based on the language, the region and the variant.\n    public static @NotNull String getScript(Locale locale) {\n        if (locale.getScript().isEmpty()) {\n            if (!locale.getVariant().isEmpty()) {\n                String script = DEFAULT_SCRIPT.get(locale.getVariant());\n                if (script != null)\n                    return script;\n            }\n\n            if (\"UD\".equals(locale.getCountry())) {\n                return \"Qabs\";\n            }\n\n            String script = DEFAULT_SCRIPT.get(normalizeLanguage(locale.getLanguage()));\n            if (script != null)\n                return script;\n\n            if (isChinese(locale)) {\n                return CHINESE_TRADITIONAL_REGIONS.contains(locale.getCountry())\n                        ? \"Hant\"\n                        : \"Hans\";\n            }\n\n            return \"\";\n        }\n\n        return locale.getScript();\n    }\n\n    public static @NotNull TextDirection getTextDirection(Locale locale) {\n        return RTL_SCRIPTS.contains(getScript(locale))\n                ? TextDirection.RIGHT_TO_LEFT\n                : TextDirection.LEFT_TO_RIGHT;\n    }\n\n    private static final ConcurrentMap<Locale, List<Locale>> CANDIDATE_LOCALES = new ConcurrentHashMap<>();\n\n    public static @NotNull List<Locale> getCandidateLocales(Locale locale) {\n        return CANDIDATE_LOCALES.computeIfAbsent(locale, LocaleUtils::createCandidateLocaleList);\n    }\n\n    private static List<Locale> createCandidateLocaleList(Locale locale) {\n        String language = getPreferredLanguage(locale.getLanguage());\n        String script = getScript(locale);\n        String region = locale.getCountry();\n        List<String> variants = locale.getVariant().isEmpty()\n                ? List.of()\n                : List.of(locale.getVariant().split(\"[_\\\\-]\"));\n\n        ArrayList<Locale> result = new ArrayList<>();\n        do {\n            addCandidateLocales(result, language, script, region, variants);\n        } while ((language = getParentLanguage(language)) != null);\n\n        result.add(Locale.ROOT);\n        return List.copyOf(result);\n    }\n\n    private static void addCandidateLocales(ArrayList<Locale> list,\n                                            String language,\n                                            String script,\n                                            String region,\n                                            List<String> variants) {\n        if (!variants.isEmpty()) {\n            for (String v : variants) {\n                list.add(getInstance(language, script, region, v));\n            }\n        }\n        if (!region.isEmpty()) {\n            list.add(getInstance(language, script, region, \"\"));\n        }\n        if (!script.isEmpty()) {\n            list.add(getInstance(language, script, \"\", \"\"));\n            if (!variants.isEmpty()) {\n                for (String v : variants) {\n                    list.add(getInstance(language, \"\", region, v));\n                }\n            }\n            if (!region.isEmpty()) {\n                list.add(getInstance(language, \"\", region, \"\"));\n            }\n        }\n\n        list.add(getInstance(language, \"\", \"\", \"\"));\n\n        if (language.equals(\"zh\")) {\n            if (list.contains(LocaleUtils.LOCALE_ZH_HANT) && !list.contains(Locale.TRADITIONAL_CHINESE)) {\n                int chineseIdx = list.indexOf(Locale.CHINESE);\n                if (chineseIdx >= 0)\n                    list.add(chineseIdx, Locale.TRADITIONAL_CHINESE);\n            }\n\n            if (!list.contains(Locale.SIMPLIFIED_CHINESE)) {\n                int chineseIdx = list.indexOf(Locale.CHINESE);\n\n                if (chineseIdx >= 0) {\n                    if (list.contains(LocaleUtils.LOCALE_ZH_HANS))\n                        list.add(chineseIdx, Locale.SIMPLIFIED_CHINESE);\n                    else\n                        list.add(chineseIdx + 1, Locale.SIMPLIFIED_CHINESE);\n                }\n            }\n        }\n    }\n\n    // -------------\n\n    public static <T> @Nullable T getByCandidateLocales(Map<String, T> map, List<Locale> candidateLocales) {\n        for (Locale locale : candidateLocales) {\n            String key = toLanguageKey(locale);\n            if (map.containsKey(key))\n                return map.get(key);\n        }\n        return null;\n    }\n\n    /// Find all localized files in the given directory with the given base name and extension.\n    /// The file name should be in the format of `baseName[_languageTag].ext`.\n    ///\n    /// @return A map of language key to file path.\n    public static @NotNull @Unmodifiable Map<String, Path> findAllLocalizedFiles(Path dir, String baseName, String ext) {\n        if (Files.isDirectory(dir)) {\n            String suffix = \".\" + ext;\n            String defaultName = baseName + suffix;\n            String noDefaultPrefix = baseName + \"_\";\n\n            try (Stream<Path> list = Files.list(dir)) {\n                var result = new LinkedHashMap<String, Path>();\n\n                list.forEach(file -> {\n                    if (Files.isRegularFile(file)) {\n                        String fileName = file.getFileName().toString();\n                        if (fileName.equals(defaultName)) {\n                            result.put(DEFAULT_LANGUAGE_KEY, file);\n                        } else if (fileName.startsWith(noDefaultPrefix) && fileName.endsWith(suffix)) {\n                            String languageKey = fileName.substring(noDefaultPrefix.length(), fileName.length() - suffix.length())\n                                    .replace('_', '-');\n\n                            if (!languageKey.isEmpty())\n                                result.put(languageKey, file);\n                        }\n                    }\n                });\n\n                return result;\n            } catch (IOException e) {\n                LOG.warning(\"Failed to list files in directory \" + dir, e);\n            }\n        }\n\n        return Map.of();\n    }\n\n    /// Find all localized files in the given directory with the given base name and extensions.\n    /// The file name should be in the format of `baseName[_languageTag].ext`.\n    ///\n    /// @return A map of language key to a map of extension to file path.\n    public static @NotNull @Unmodifiable Map<String, Map<String, Path>> findAllLocalizedFiles(Path dir, String baseName, Collection<String> exts) {\n        if (Files.isDirectory(dir)) {\n            try (Stream<Path> list = Files.list(dir)) {\n                var result = new LinkedHashMap<String, Map<String, Path>>();\n\n                list.forEach(file -> {\n                    if (Files.isRegularFile(file)) {\n                        String fileName = file.getFileName().toString();\n                        if (!fileName.startsWith(baseName))\n                            return;\n\n                        String ext = StringUtils.substringAfterLast(fileName, '.');\n                        if (!exts.contains(ext))\n                            return;\n\n                        String languageKey;\n                        int defaultFileNameLength = baseName.length() + ext.length() + 1;\n                        if (fileName.length() == defaultFileNameLength)\n                            languageKey = DEFAULT_LANGUAGE_KEY;\n                        else if (fileName.length() > defaultFileNameLength + 1 && fileName.charAt(baseName.length()) == '_')\n                            languageKey = fileName.substring(baseName.length() + 1, fileName.length() - ext.length() - 1)\n                                    .replace('_', '-');\n                        else\n                            return;\n\n                        result.computeIfAbsent(languageKey, key -> new HashMap<>())\n                                .put(ext, file);\n                    }\n                });\n\n                return result;\n            } catch (IOException e) {\n                LOG.warning(\"Failed to list files in directory \" + dir, e);\n            }\n        }\n\n        return Map.of();\n    }\n\n    private LocaleUtils() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocalizedText.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n;\n\nimport com.google.gson.JsonSyntaxException;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.annotations.JsonAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonToken;\nimport com.google.gson.stream.JsonWriter;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.*;\n\n@JsonAdapter(LocalizedText.Adapter.class)\npublic final class LocalizedText {\n    private final @Nullable String value;\n    private final @Nullable Map<String, String> localizedValues;\n\n    public LocalizedText(String value) {\n        this.value = value;\n        this.localizedValues = null;\n    }\n\n    public LocalizedText(@NotNull Map<String, String> localizedValues) {\n        this.value = null;\n        this.localizedValues = Objects.requireNonNull(localizedValues);\n    }\n\n    public String getText(@NotNull List<Locale> candidates) {\n        if (localizedValues != null) {\n            for (Locale locale : candidates) {\n                String value = localizedValues.get(LocaleUtils.toLanguageKey(locale));\n                if (value != null)\n                    return value;\n            }\n            return null;\n        } else\n            return value;\n    }\n\n    static final class Adapter extends TypeAdapter<LocalizedText> {\n\n        @Override\n        public LocalizedText read(JsonReader jsonReader) throws IOException {\n            JsonToken nextToken = jsonReader.peek();\n            if (nextToken == JsonToken.NULL) {\n                return null;\n            } else if (nextToken == JsonToken.STRING) {\n                return new LocalizedText(jsonReader.nextString());\n            } else if (nextToken == JsonToken.BEGIN_OBJECT) {\n                LinkedHashMap<String, String> localizedValues = new LinkedHashMap<>();\n\n                jsonReader.beginObject();\n                while (jsonReader.hasNext()) {\n                    String name = jsonReader.nextName();\n                    String value = jsonReader.nextString();\n\n                    localizedValues.put(name, value);\n                }\n                jsonReader.endObject();\n\n                return new LocalizedText(localizedValues);\n            } else {\n                throw new JsonSyntaxException(\"Unexpected token \" + nextToken);\n            }\n        }\n\n        @Override\n        public void write(JsonWriter jsonWriter, LocalizedText localizedText) throws IOException {\n            if (localizedText == null) {\n                jsonWriter.nullValue();\n            } else if (localizedText.localizedValues != null) {\n\n                jsonWriter.beginObject();\n                for (var entry : localizedText.localizedValues.entrySet()) {\n                    jsonWriter.name(entry.getKey());\n                    jsonWriter.value(entry.getValue());\n                }\n                jsonWriter.endObject();\n            } else {\n                jsonWriter.value(localizedText.value);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/TextDirection.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n;\n\n/// @author Glavo\npublic enum TextDirection {\n    LEFT_TO_RIGHT,\n    RIGHT_TO_LEFT;\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/CSVTable.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n\npackage org.jackhuang.hmcl.util.io;\n\nimport org.jackhuang.hmcl.util.InfiniteSizeList;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.*;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\n\npublic final class CSVTable {\n    private int columnCount = 0;\n    private int rowCount = 0;\n    private final InfiniteSizeList<InfiniteSizeList<String>> table = new InfiniteSizeList<>();\n\n    public CSVTable() {\n    }\n\n    public int getColumnCount() {\n        return columnCount;\n    }\n\n    public int getRowCount() {\n        return rowCount;\n    }\n\n    public @NotNull String get(int x, int y) {\n        List<String> row = table.get(y);\n        if (row == null) {\n            return \"\";\n        }\n        String value = row.get(x);\n        return value != null ? value : \"\";\n    }\n\n    public void set(int x, int y, String txt) {\n        if (x < 0 || y < 0)\n            throw new IllegalArgumentException(\"x or y must be greater than or equal to 0\");\n\n        InfiniteSizeList<String> row = table.get(y);\n        if (row == null) {\n            row = new InfiniteSizeList<>(x);\n            table.set(y, row);\n        }\n        row.set(x, txt);\n\n        columnCount = Integer.max(columnCount, x + 1);\n        rowCount = Integer.max(rowCount, y + 1);\n    }\n\n    public void write(Path file) throws IOException {\n        try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {\n            write(writer);\n        }\n    }\n\n    public void write(Appendable out) throws IOException {\n        for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {\n            List<String> row = this.table.get(rowIndex);\n            if (row != null) {\n                for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {\n                    String txt = row.get(columnIndex);\n                    if (txt != null) {\n                        if (txt.indexOf('\"') < 0 && txt.indexOf(',') < 0 && txt.indexOf('\\n') < 0 && txt.indexOf('\\r') < 0)\n                            out.append(txt);\n                        else {\n                            out.append('\"');\n                            for (int i = 0; i < txt.length(); i++) {\n                                char c = txt.charAt(i);\n                                switch (c) {\n                                    case '\"':\n                                        out.append(\"\\\\\\\"\");\n                                        break;\n                                    case '\\r':\n                                        out.append(\"\\\\r\");\n                                        break;\n                                    case '\\n':\n                                        out.append(\"\\\\n\");\n                                        break;\n                                    default:\n                                        out.append(c);\n                                        break;\n                                }\n                            }\n                            out.append('\"');\n                        }\n                    }\n\n                    if (columnIndex < columnCount - 1)\n                        out.append(',');\n                }\n            } else {\n                for (int columnIndex = 0; columnIndex < columnCount - 1; columnIndex++) {\n                    out.append(',');\n                }\n            }\n\n            out.append('\\n');\n        }\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder builder = new StringBuilder();\n        try {\n            write(builder);\n        } catch (IOException e) {\n            throw new UncheckedIOException(e);\n        }\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ChecksumMismatchException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport org.jackhuang.hmcl.download.ArtifactMalformedException;\nimport org.jackhuang.hmcl.util.DigestUtils;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\npublic final class ChecksumMismatchException extends ArtifactMalformedException {\n\n    private final String algorithm;\n    private final String expectedChecksum;\n    private final String actualChecksum;\n\n    public ChecksumMismatchException(String algorithm, String expectedChecksum, String actualChecksum) {\n        super(\"Incorrect checksum (\" + algorithm + \"), expected: \" + expectedChecksum + \", actual: \" + actualChecksum);\n        this.algorithm = algorithm;\n        this.expectedChecksum = expectedChecksum;\n        this.actualChecksum = actualChecksum;\n    }\n\n    public String getAlgorithm() {\n        return algorithm;\n    }\n\n    public String getExpectedChecksum() {\n        return expectedChecksum;\n    }\n\n    public String getActualChecksum() {\n        return actualChecksum;\n    }\n\n    public static void verifyChecksum(Path file, String algorithm, String expectedChecksum) throws IOException {\n        String checksum = DigestUtils.digestToString(algorithm, file);\n        if (!checksum.equalsIgnoreCase(expectedChecksum)) {\n            throw new ChecksumMismatchException(algorithm, expectedChecksum, checksum);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/CompressingUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.tree.ZipFileTree;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.charset.*;\nimport java.nio.file.*;\nimport java.nio.file.spi.FileSystemProvider;\nimport java.util.*;\nimport java.util.zip.ZipException;\n\n/**\n * Utilities of compressing\n *\n * @author huangyuhui\n */\npublic final class CompressingUtils {\n\n    private static final FileSystemProvider ZIPFS_PROVIDER = FileSystemProvider.installedProviders().stream()\n            .filter(it -> \"jar\".equalsIgnoreCase(it.getScheme()))\n            .findFirst()\n            .orElse(null);\n\n    private CompressingUtils() {\n    }\n\n    private static CharsetDecoder newCharsetDecoder(Charset charset) {\n        return charset.newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT);\n    }\n\n    public static boolean testEncoding(Path zipFile, Charset encoding) throws IOException {\n        try (ZipArchiveReader zf = openZipFile(zipFile, encoding)) {\n            return testEncoding(zf, encoding);\n        }\n    }\n\n    public static boolean testEncoding(ZipArchiveReader zipFile, Charset encoding) {\n        CharsetDecoder cd = newCharsetDecoder(encoding);\n        CharBuffer cb = CharBuffer.allocate(32);\n\n        for (ZipArchiveEntry entry : zipFile.getEntries()) {\n            if (entry.getGeneralPurposeBit().usesUTF8ForNames()) continue;\n\n            cd.reset();\n            byte[] ba = entry.getRawName();\n            int clen = (int) (ba.length * cd.maxCharsPerByte());\n            if (clen == 0) continue;\n            if (clen <= cb.capacity())\n                cb.clear();\n            else\n                cb = CharBuffer.allocate(clen);\n\n            ByteBuffer bb = ByteBuffer.wrap(ba, 0, ba.length);\n            CoderResult cr = cd.decode(bb, cb, true);\n            if (!cr.isUnderflow()) return false;\n            cr = cd.flush(cb);\n            if (!cr.isUnderflow()) return false;\n        }\n        return true;\n    }\n\n    public static Charset findSuitableEncoding(Path zipFile) throws IOException {\n        try (ZipArchiveReader zf = openZipFile(zipFile, StandardCharsets.UTF_8)) {\n            return findSuitableEncoding(zf);\n        }\n    }\n\n    public static Charset findSuitableEncoding(ZipArchiveReader zipFile) throws IOException {\n        if (testEncoding(zipFile, StandardCharsets.UTF_8)) return StandardCharsets.UTF_8;\n        if (OperatingSystem.NATIVE_CHARSET != StandardCharsets.UTF_8 && testEncoding(zipFile, OperatingSystem.NATIVE_CHARSET))\n            return OperatingSystem.NATIVE_CHARSET;\n\n        String[] candidates = {\n                \"GB18030\",\n                \"Big5\",\n                \"Shift_JIS\",\n                \"EUC-JP\",\n                \"ISO-2022-JP\",\n                \"EUC-KR\",\n                \"ISO-2022-KR\",\n                \"KOI8-R\",\n                \"windows-1251\",\n                \"x-MacCyrillic\",\n                \"IBM855\",\n                \"IBM866\",\n                \"windows-1252\",\n                \"ISO-8859-1\",\n                \"ISO-8859-5\",\n                \"ISO-8859-7\",\n                \"ISO-8859-8\",\n                \"UTF-16LE\", \"UTF-16BE\",\n                \"UTF-32LE\", \"UTF-32BE\"\n        };\n\n        for (String candidate : candidates) {\n            try {\n                Charset charset = Charset.forName(candidate);\n                if (!charset.equals(OperatingSystem.NATIVE_CHARSET) && testEncoding(zipFile, charset)) {\n                    return charset;\n                }\n            } catch (IllegalArgumentException ignored) {\n            }\n        }\n\n        throw new IOException(\"Cannot find suitable encoding for the zip.\");\n    }\n\n    public static ZipFileTree openZipTree(Path zipFile) throws IOException {\n        return new ZipFileTree(openZipFile(zipFile));\n    }\n\n    public static ZipArchiveReader openZipFile(Path zipFile) throws IOException {\n        return openZipFileWithPossibleEncoding(zipFile, StandardCharsets.UTF_8);\n    }\n\n    public static ZipArchiveReader openZipFile(Path zipFile, Charset charset) throws IOException {\n        return new ZipArchiveReader(zipFile, charset);\n    }\n\n    public static ZipArchiveReader openZipFileWithPossibleEncoding(Path zipFile, Charset possibleEncoding) throws IOException {\n        if (possibleEncoding == null)\n            possibleEncoding = StandardCharsets.UTF_8;\n\n        ZipArchiveReader zipReader = new ZipArchiveReader(Files.newByteChannel(zipFile));\n\n        Charset suitableEncoding;\n        try {\n            if (possibleEncoding != StandardCharsets.UTF_8 && CompressingUtils.testEncoding(zipReader, possibleEncoding)) {\n                suitableEncoding = possibleEncoding;\n            } else {\n                suitableEncoding = CompressingUtils.findSuitableEncoding(zipReader);\n                if (suitableEncoding == StandardCharsets.UTF_8)\n                    return zipReader;\n            }\n        } catch (Throwable e) {\n            IOUtils.closeQuietly(zipReader, e);\n            throw e;\n        }\n\n        zipReader.close();\n        return new ZipArchiveReader(Files.newByteChannel(zipFile), suitableEncoding);\n    }\n\n    public static final class Builder {\n        private boolean autoDetectEncoding = false;\n        private Charset encoding = StandardCharsets.UTF_8;\n        private boolean useTempFile = false;\n        private final boolean create;\n        private final Path zip;\n\n        public Builder(Path zip, boolean create) {\n            this.zip = zip;\n            this.create = create;\n        }\n\n        public Builder setAutoDetectEncoding(boolean autoDetectEncoding) {\n            this.autoDetectEncoding = autoDetectEncoding;\n            return this;\n        }\n\n        public Builder setEncoding(Charset encoding) {\n            this.encoding = encoding;\n            return this;\n        }\n\n        public Builder setUseTempFile(boolean useTempFile) {\n            this.useTempFile = useTempFile;\n            return this;\n        }\n\n        public FileSystem build() throws IOException {\n            if (autoDetectEncoding) {\n                if (!testEncoding(zip, encoding)) {\n                    encoding = findSuitableEncoding(zip);\n                }\n            }\n            return createZipFileSystem(zip, create, useTempFile, encoding);\n        }\n    }\n\n    public static Builder readonly(Path zipFile) {\n        return new Builder(zipFile, false);\n    }\n\n    public static Builder writable(Path zipFile) {\n        return new Builder(zipFile, true).setUseTempFile(true);\n    }\n\n    public static FileSystem createReadOnlyZipFileSystem(Path zipFile) throws IOException {\n        return createReadOnlyZipFileSystem(zipFile, null);\n    }\n\n    public static FileSystem createReadOnlyZipFileSystem(Path zipFile, Charset charset) throws IOException {\n        return createZipFileSystem(zipFile, false, false, charset);\n    }\n\n    public static FileSystem createWritableZipFileSystem(Path zipFile) throws IOException {\n        return createWritableZipFileSystem(zipFile, null);\n    }\n\n    public static FileSystem createWritableZipFileSystem(Path zipFile, Charset charset) throws IOException {\n        return createZipFileSystem(zipFile, true, true, charset);\n    }\n\n    public static FileSystem createZipFileSystem(Path zipFile, boolean create, boolean useTempFile, Charset encoding) throws IOException {\n        Map<String, Object> env = new HashMap<>();\n        if (create)\n            env.put(\"create\", \"true\");\n        if (encoding != null)\n            env.put(\"encoding\", encoding.name());\n        if (useTempFile)\n            env.put(\"useTempFile\", true);\n        try {\n            if (ZIPFS_PROVIDER == null)\n                throw new FileSystemNotFoundException(\"Module jdk.zipfs does not exist\");\n\n            return ZIPFS_PROVIDER.newFileSystem(zipFile, env);\n        } catch (UnsupportedOperationException ex) {\n            throw new ZipException(\"Not a zip file\");\n        } catch (FileSystemNotFoundException ex) {\n            throw Lang.apply(new ZipException(\"Java Environment is broken\"), it -> it.initCause(ex));\n        }\n    }\n\n    /**\n     * Read the text content of a file in zip.\n     *\n     * @param zipFile the zip file\n     * @param name    the location of the text in zip file, something like A/B/C/D.txt\n     * @return the plain text content of given file.\n     * @throws IOException if the file is not a valid zip file.\n     */\n    public static String readTextZipEntry(Path zipFile, String name) throws IOException {\n        try (ZipArchiveReader s = new ZipArchiveReader(zipFile)) {\n            return readTextZipEntry(s, name);\n        }\n    }\n\n    /**\n     * Read the text content of a file in zip.\n     *\n     * @param zipFile the zip file\n     * @param name    the location of the text in zip file, something like A/B/C/D.txt\n     * @return the plain text content of given file.\n     * @throws IOException if the file is not a valid zip file.\n     */\n    public static String readTextZipEntry(ZipArchiveReader zipFile, String name) throws IOException {\n        return IOUtils.readFullyAsString(zipFile.getInputStream(zipFile.getEntry(name)));\n    }\n\n    /**\n     * Read the text content of a file in zip.\n     *\n     * @param zipFile the zip file\n     * @param name    the location of the text in zip file, something like A/B/C/D.txt\n     * @return the plain text content of given file.\n     * @throws IOException if the file is not a valid zip file.\n     */\n    public static String readTextZipEntry(Path zipFile, String name, Charset encoding) throws IOException {\n        try (ZipArchiveReader s = openZipFile(zipFile, encoding)) {\n            return IOUtils.readFullyAsString(s.getInputStream(s.getEntry(name)));\n        }\n    }\n\n    /**\n     * Read the text content of a file in zip.\n     *\n     * @param file the zip file\n     * @param name the location of the text in zip file, something like A/B/C/D.txt\n     * @return the plain text content of given file.\n     */\n    public static Optional<String> readTextZipEntryQuietly(Path file, String name, Charset encoding) {\n        try {\n            return Optional.of(readTextZipEntry(file, name, encoding));\n        } catch (IOException | NullPointerException e) {\n            return Optional.empty();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ContentEncoding.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URLConnection;\nimport java.net.http.HttpResponse;\nimport java.util.zip.GZIPInputStream;\n\n/**\n * @author Glavo\n */\npublic enum ContentEncoding {\n    IDENTITY {\n        @Override\n        public InputStream wrap(InputStream inputStream) {\n            return inputStream;\n        }\n    },\n    GZIP {\n        @Override\n        public InputStream wrap(InputStream inputStream) throws IOException {\n            return new GZIPInputStream(inputStream);\n        }\n    };\n\n    public static @NotNull ContentEncoding fromConnection(URLConnection connection) throws IOException {\n        String encoding = connection.getContentEncoding();\n        if (encoding == null || encoding.isEmpty() || \"identity\".equals(encoding)) {\n            return IDENTITY;\n        } else if (\"gzip\".equalsIgnoreCase(encoding)) {\n            return GZIP;\n        } else {\n            throw new IOException(\"Unsupported content encoding: \" + encoding);\n        }\n    }\n\n    public static @NotNull ContentEncoding fromResponse(HttpResponse<?> connection) throws IOException {\n        String encoding = connection.headers().firstValue(\"content-encoding\").orElse(\"\");\n        if (encoding.isEmpty() || \"identity\".equals(encoding)) {\n            return IDENTITY;\n        } else if (\"gzip\".equalsIgnoreCase(encoding)) {\n            return GZIP;\n        } else {\n            throw new IOException(\"Unsupported content encoding: \" + encoding);\n        }\n    }\n\n    public abstract InputStream wrap(InputStream inputStream) throws IOException;\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/DirectoryStructurePrinter.java",
    "content": "package org.jackhuang.hmcl.util.io;\n\nimport java.io.IOException;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.FileVisitor;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.BasicFileAttributes;\n\nfinal class DirectoryStructurePrinter {\n    private DirectoryStructurePrinter() {\n    }\n\n    public static String list(Path path, int maxDepth) throws IOException {\n        StringBuilder output = new StringBuilder(128);\n        list(path, maxDepth, output);\n        output.setLength(output.length() - 1);\n        return output.toString();\n    }\n\n    private static void list(Path path, int maxDepth, StringBuilder output) throws IOException {\n        output.append(\"Filesystem structure of: \").append(path).append('\\n');\n\n        if (!Files.exists(path)) {\n            pushMessage(output, \"nonexistent path\", 1);\n            return;\n        }\n        if (Files.isRegularFile(path)) {\n            pushMessage(output, \"regular file path\", 1);\n            return;\n        }\n        if (Files.isDirectory(path)) {\n            Files.walkFileTree(path, new FileVisitor<Path>() {\n                private boolean isFolderEmpty;\n\n                private int depth = 1;\n\n                @Override\n                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {\n                    isFolderEmpty = true;\n\n                    pushFile(output, dir, depth);\n                    if (depth == maxDepth) {\n                        pushMessage(output, \"too deep\", depth);\n                        return FileVisitResult.SKIP_SUBTREE;\n                    }\n\n                    depth++;\n                    return FileVisitResult.CONTINUE;\n                }\n\n                @Override\n                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {\n                    isFolderEmpty = false;\n\n                    pushFile(output, file, depth);\n                    return FileVisitResult.CONTINUE;\n                }\n\n                @Override\n                public FileVisitResult visitFileFailed(Path file, IOException exc) {\n                    visitFile(file, null);\n\n                    pushMessage(output, exc.toString(), depth);\n                    return FileVisitResult.CONTINUE;\n                }\n\n                @Override\n                public FileVisitResult postVisitDirectory(Path dir, IOException exc) {\n                    if (isFolderEmpty) {\n                        pushMessage(output, \"empty directory\", depth);\n                    }\n\n                    depth--;\n                    return FileVisitResult.CONTINUE;\n                }\n            });\n            return;\n        }\n\n        pushMessage(output, \"unknown file type\", 1);\n    }\n\n    private static void pushFile(StringBuilder output, Path file, int depth) {\n        output.append(\"|\");\n        for (int i = 1; i < depth; i++) {\n            output.append(\"  |\");\n        }\n        output.append(\"-> \").append(FileUtils.getName(file)).append('\\n');\n    }\n\n    private static void pushMessage(StringBuilder output, String message, int depth) {\n        output.append(\"| \");\n        for (int i = 1; i < depth; i++) {\n            output.append(\" | \");\n        }\n        output.append('<').append(message).append(\">\\n\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport com.google.errorprone.annotations.CanIgnoreReturnValue;\nimport org.glavo.chardet.DetectedCharset;\nimport org.glavo.chardet.UniversalDetector;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.function.ExceptionalConsumer;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.*;\nimport java.nio.file.*;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.nio.file.attribute.PosixFileAttributeView;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.time.temporal.ChronoUnit;\nimport java.util.*;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huang\n */\npublic final class FileUtils {\n\n    private FileUtils() {\n    }\n\n    public static @Nullable Path toPath(@Nullable File file) {\n        try {\n            return file != null ? file.toPath() : null;\n        } catch (InvalidPathException e) {\n            LOG.warning(\"Invalid path: \" + file);\n            return null;\n        }\n    }\n\n    public static @Nullable List<Path> toPaths(@Nullable List<File> files) {\n        if (files == null) return null;\n        return files.stream().map(FileUtils::toPath).filter(Objects::nonNull).toList();\n    }\n\n    public static boolean canCreateDirectory(String path) {\n        try {\n            return canCreateDirectory(Paths.get(path));\n        } catch (InvalidPathException e) {\n            return false;\n        }\n    }\n\n    public static boolean canCreateDirectory(Path path) {\n        if (Files.isDirectory(path)) return true;\n        else if (Files.exists(path)) return false;\n        else {\n            Path lastPath = path; // always not exist\n            path = path.getParent();\n            // find existent ancestor\n            while (path != null && !Files.exists(path)) {\n                lastPath = path;\n                path = path.getParent();\n            }\n            if (path == null) return false; // all ancestors are nonexistent\n            if (!Files.isDirectory(path)) return false; // ancestor is file\n            try {\n                Files.createDirectory(lastPath); // check permission\n                Files.delete(lastPath); // safely delete empty directory\n                return true;\n            } catch (IOException e) {\n                return false;\n            }\n        }\n    }\n\n    public static String getNameWithoutExtension(String fileName) {\n        return StringUtils.substringBeforeLast(fileName, '.');\n    }\n\n    public static String getNameWithoutExtension(Path file) {\n        return StringUtils.substringBeforeLast(getName(file), '.');\n    }\n\n    public static String getExtension(String fileName) {\n        return StringUtils.substringAfterLast(fileName, '.');\n    }\n\n    public static String getExtension(Path file) {\n        return StringUtils.substringAfterLast(getName(file), '.');\n    }\n\n    /**\n     * This method is for normalizing ZipPath since Path.normalize of ZipFileSystem does not work properly.\n     */\n    public static String normalizePath(String path) {\n        return StringUtils.addPrefix(StringUtils.removeSuffix(path, \"/\", \"\\\\\"), \"/\");\n    }\n\n    public static String getName(Path path) {\n        Path fileName = path.getFileName();\n        return fileName != null ? fileName.toString() : \"\";\n    }\n\n    public static Path toAbsolute(Path path) {\n        return path.toAbsolutePath().normalize();\n    }\n\n    public static String getAbsolutePath(Path path) {\n        return path.toAbsolutePath().normalize().toString();\n    }\n\n    // https://learn.microsoft.com/biztalk/core/restrictions-when-configuring-the-file-adapter\n    private static final Set<String> INVALID_WINDOWS_RESOURCE_BASE_NAMES = Set.of(\n            \"aux\", \"con\", \"nul\", \"prn\", \"clock$\",\n            \"com1\", \"com2\", \"com3\", \"com4\", \"com5\", \"com6\", \"com7\", \"com8\", \"com9\",\n            \"com¹\", \"com²\", \"com³\",\n            \"lpt1\", \"lpt2\", \"lpt3\", \"lpt4\", \"lpt5\", \"lpt6\", \"lpt7\", \"lpt8\", \"lpt9\",\n            \"lpt¹\", \"lpt²\", \"lpt³\"\n    );\n\n    /// @see #isNameValid(OperatingSystem, String)\n    public static boolean isNameValid(String name) {\n        return isNameValid(OperatingSystem.CURRENT_OS, name);\n    }\n\n    /// Returns true if the given name is a valid file name on the given operating system,\n    /// and `false` otherwise.\n    public static boolean isNameValid(OperatingSystem os, String name) {\n        // empty filename is not allowed\n        if (name.isEmpty())\n            return false;\n        // '.', '..' and '~' have special meaning on all platforms\n        if (name.equals(\".\") || name.equals(\"..\") || name.equals(\"~\"))\n            return false;\n\n        for (int i = 0; i < name.length(); i++) {\n            char ch = name.charAt(i);\n            int codePoint;\n\n            if (Character.isSurrogate(ch)) {\n                if (!Character.isHighSurrogate(ch))\n                    return false;\n\n                if (i == name.length() - 1)\n                    return false;\n\n                char ch2 = name.charAt(++i);\n                if (!Character.isLowSurrogate(ch2))\n                    return false;\n\n                codePoint = Character.toCodePoint(ch, ch2);\n            } else {\n                codePoint = ch;\n            }\n\n            if (!Character.isValidCodePoint(codePoint)\n                    || Character.isISOControl(codePoint)\n                    || codePoint == '/' || codePoint == '\\0'\n                    || codePoint == ':'\n                    // Unicode replacement character\n                    || codePoint == 0xfffd\n                    // Not Unicode character\n                    || codePoint == 0xfffe || codePoint == 0xffff)\n                return false;\n\n            // https://learn.microsoft.com/windows/win32/fileio/naming-a-file\n            if (os == OperatingSystem.WINDOWS &&\n                    (ch == '<' || ch == '>' || ch == '\"' || ch == '\\\\' || ch == '|' || ch == '?' || ch == '*')) {\n                return false;\n            }\n        }\n\n        if (os == OperatingSystem.WINDOWS) { // Windows only\n            char lastChar = name.charAt(name.length() - 1);\n            // filenames ending in dot are not valid\n            if (lastChar == '.')\n                return false;\n            // file names ending with whitespace are truncated (bug 118997)\n            if (Character.isWhitespace(lastChar))\n                return false;\n\n            // on windows, filename suffixes are not relevant to name validity\n            String basename = StringUtils.substringBeforeLast(name, '.');\n            if (INVALID_WINDOWS_RESOURCE_BASE_NAMES.contains(basename.toLowerCase(Locale.ROOT)))\n                return false;\n        }\n\n        return true;\n    }\n\n    /// Safely get the file size. Returns `0` if the file does not exist or the size cannot be obtained.\n    public static long size(Path file) {\n        try {\n            return Files.size(file);\n        } catch (NoSuchFileException ignored) {\n            return 0L;\n        } catch (IOException e) {\n            LOG.warning(\"Failed to get file size of \" + file, e);\n            return 0L;\n        }\n    }\n\n    public static String readTextMaybeNativeEncoding(Path file) throws IOException {\n        byte[] bytes = Files.readAllBytes(file);\n\n        if (OperatingSystem.NATIVE_CHARSET == UTF_8)\n            return new String(bytes, UTF_8);\n\n        UniversalDetector detector = new UniversalDetector();\n        detector.handleData(bytes);\n        detector.dataEnd();\n\n        DetectedCharset detectedCharset = detector.getDetectedCharset();\n        if (detectedCharset != null && detectedCharset.isSupported()\n                && (detectedCharset == DetectedCharset.UTF_8 || detectedCharset == DetectedCharset.US_ASCII))\n            return new String(bytes, UTF_8);\n        else\n            return new String(bytes, OperatingSystem.NATIVE_CHARSET);\n    }\n\n    public static void deleteDirectory(Path directory) throws IOException {\n        if (!Files.exists(directory))\n            return;\n\n        if (!Files.isSymbolicLink(directory))\n            cleanDirectory(directory);\n\n        Files.deleteIfExists(directory);\n    }\n\n    public static boolean deleteDirectoryQuietly(Path directory) {\n        try {\n            deleteDirectory(directory);\n            return true;\n        } catch (IOException e) {\n            return false;\n        }\n    }\n\n    public static void setExecutable(Path path) {\n        PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);\n        if (view != null) {\n            try {\n                Set<PosixFilePermission> oldPermissions = view.readAttributes().permissions();\n                if (oldPermissions.contains(PosixFilePermission.OWNER_EXECUTE))\n                    return;\n\n                EnumSet<PosixFilePermission> permissions = EnumSet.noneOf(PosixFilePermission.class);\n                permissions.addAll(oldPermissions);\n                permissions.add(PosixFilePermission.OWNER_EXECUTE);\n                view.setPermissions(permissions);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to set permissions for \" + path, e);\n            }\n        }\n    }\n\n    /**\n     * Copy directory.\n     * Paths of all files relative to source directory will be the same as the ones relative to destination directory.\n     *\n     * @param src  the source directory.\n     * @param dest the destination directory, which will be created if not existing.\n     * @throws IOException if an I/O error occurs.\n     */\n    public static void copyDirectory(Path src, Path dest) throws IOException {\n        copyDirectory(src, dest, path -> true);\n    }\n\n    public static void copyDirectory(Path src, Path dest, Predicate<String> filePredicate) throws IOException {\n        Files.walkFileTree(src, new SimpleFileVisitor<>() {\n            @Override\n            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n                if (!filePredicate.test(src.relativize(file).toString())) {\n                    return FileVisitResult.SKIP_SUBTREE;\n                }\n\n                Path destFile = dest.resolve(src.relativize(file).toString());\n                Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);\n                return FileVisitResult.CONTINUE;\n            }\n\n            @Override\n            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\n                if (!filePredicate.test(src.relativize(dir).toString())) {\n                    return FileVisitResult.SKIP_SUBTREE;\n                }\n\n                Path destDir = dest.resolve(src.relativize(dir).toString());\n                Files.createDirectories(destDir);\n                return FileVisitResult.CONTINUE;\n            }\n        });\n    }\n\n    public static boolean hasKnownDesktop() {\n        if (!OperatingSystem.CURRENT_OS.isLinuxOrBSD())\n            return true;\n\n        String desktops = System.getenv(\"XDG_CURRENT_DESKTOP\");\n        if (desktops == null) {\n            desktops = System.getenv(\"XDG_SESSION_DESKTOP\");\n        }\n\n        if (desktops == null) {\n            return false;\n        }\n        for (String desktop : desktops.split(\":\")) {\n            switch (desktop.toLowerCase(Locale.ROOT)) {\n                case \"gnome\":\n                case \"xfce\":\n                case \"kde\":\n                case \"mate\":\n                case \"deepin\":\n                case \"x-cinnamon\":\n                    return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Move file to trash.\n     *\n     * @param file the file being moved to trash.\n     * @return false if moveToTrash does not exist, or platform does not support Desktop.Action.MOVE_TO_TRASH\n     */\n    public static boolean moveToTrash(Path file) {\n        if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && hasKnownDesktop()) {\n            if (!Files.exists(file)) {\n                return false;\n            }\n\n            String xdgData = System.getenv(\"XDG_DATA_HOME\");\n\n            Path trashDir;\n            if (StringUtils.isNotBlank(xdgData)) {\n                trashDir = Paths.get(xdgData, \"Trash\");\n            } else {\n                trashDir = Paths.get(System.getProperty(\"user.home\"), \".local/share/Trash\");\n            }\n\n            Path infoDir = trashDir.resolve(\"info\");\n            Path filesDir = trashDir.resolve(\"files\");\n\n            try {\n                Files.createDirectories(infoDir);\n                Files.createDirectories(filesDir);\n\n                String name = getName(file);\n\n                Path infoFile = infoDir.resolve(name + \".trashinfo\");\n                Path targetFile = filesDir.resolve(name);\n\n                int n = 0;\n                while (Files.exists(infoFile) || Files.exists(targetFile)) {\n                    n++;\n                    infoFile = infoDir.resolve(name + \".\" + n + \".trashinfo\");\n                    targetFile = filesDir.resolve(name + \".\" + n);\n                }\n\n                String time = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS));\n                if (Files.isDirectory(file)) {\n                    FileUtils.copyDirectory(file, targetFile);\n                } else {\n                    FileUtils.copyFile(file, targetFile);\n                }\n\n                Files.createDirectories(infoDir);\n                Files.writeString(infoFile, \"[Trash Info]\\nPath=\" + FileUtils.getAbsolutePath(file) + \"\\nDeletionDate=\" + time + \"\\n\");\n                FileUtils.forceDelete(file);\n            } catch (IOException e) {\n                LOG.warning(\"Failed to move \" + file + \" to trash\", e);\n                return false;\n            }\n\n            return true;\n        }\n\n        try {\n            return java.awt.Desktop.getDesktop().moveToTrash(file.toFile());\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    public static void cleanDirectory(Path directory)\n            throws IOException {\n        if (!Files.exists(directory)) {\n            Files.createDirectories(directory);\n            return;\n        }\n\n        if (!Files.isDirectory(directory)) {\n            String message = directory + \" is not a directory\";\n            throw new IllegalArgumentException(message);\n        }\n\n        Files.walkFileTree(directory, new SimpleFileVisitor<>() {\n            @Override\n            public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {\n                Files.delete(file);\n                return FileVisitResult.CONTINUE;\n            }\n\n            @Override\n            public @NotNull FileVisitResult postVisitDirectory(@NotNull Path dir, @Nullable IOException exc) throws IOException {\n                if (!dir.equals(directory)) {\n                    Files.delete(dir);\n                }\n                return FileVisitResult.CONTINUE;\n            }\n        });\n    }\n\n    @CanIgnoreReturnValue\n    public static boolean cleanDirectoryQuietly(Path directory) {\n        try {\n            cleanDirectory(directory);\n            return true;\n        } catch (IOException e) {\n            return false;\n        }\n    }\n\n    public static void forceDelete(Path file)\n            throws IOException {\n        if (Files.isDirectory(file))\n            deleteDirectory(file);\n        else\n            Files.delete(file);\n    }\n\n    public static void copyFile(Path srcFile, Path destFile)\n            throws IOException {\n        Objects.requireNonNull(srcFile, \"Source must not be null\");\n        Objects.requireNonNull(destFile, \"Destination must not be null\");\n        if (!Files.exists(srcFile))\n            throw new FileNotFoundException(\"Source '\" + srcFile + \"' does not exist\");\n        if (Files.isDirectory(srcFile))\n            throw new IOException(\"Source '\" + srcFile + \"' exists but is a directory\");\n        Files.createDirectories(destFile.getParent());\n        if (Files.exists(destFile) && !Files.isWritable(destFile))\n            throw new IOException(\"Destination '\" + destFile + \"' exists but is read-only\");\n\n        Files.copy(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);\n    }\n\n    public static List<Path> listFilesByExtension(Path file, String extension) {\n        try (Stream<Path> list = Files.list(file)) {\n            return list.filter(it -> Files.isRegularFile(it) && extension.equals(getExtension(it)))\n                    .toList();\n        } catch (IOException e) {\n            LOG.warning(\"Failed to list files by extension \" + extension, e);\n            return List.of();\n        }\n    }\n\n    public static Optional<Path> tryGetPath(String first, String... more) {\n        if (first == null) return Optional.empty();\n        try {\n            return Optional.of(Paths.get(first, more));\n        } catch (InvalidPathException e) {\n            return Optional.empty();\n        }\n    }\n\n    public static Path tmpSaveFile(Path file) {\n        return file.toAbsolutePath().resolveSibling(\".\" + file.getFileName().toString() + \".tmp\");\n    }\n\n    public static void saveSafely(Path file, String content) throws IOException {\n        Path tmpFile = tmpSaveFile(file);\n        try (BufferedWriter writer = Files.newBufferedWriter(tmpFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) {\n            writer.write(content);\n        }\n\n        try {\n            if (Files.exists(file) && Files.getAttribute(file, \"dos:hidden\") == Boolean.TRUE) {\n                Files.setAttribute(tmpFile, \"dos:hidden\", true);\n            }\n        } catch (Throwable ignored) {\n        }\n\n        Files.move(tmpFile, file, StandardCopyOption.REPLACE_EXISTING);\n    }\n\n    public static void saveSafely(Path file, ExceptionalConsumer<? super OutputStream, IOException> action) throws IOException {\n        Path tmpFile = tmpSaveFile(file);\n\n        try (OutputStream os = Files.newOutputStream(tmpFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) {\n            action.accept(os);\n        }\n\n        try {\n            if (Files.exists(file) && Files.getAttribute(file, \"dos:hidden\") == Boolean.TRUE) {\n                Files.setAttribute(tmpFile, \"dos:hidden\", true);\n            }\n        } catch (Throwable ignored) {\n        }\n\n        Files.move(tmpFile, file, StandardCopyOption.REPLACE_EXISTING);\n    }\n\n    public static String printFileStructure(Path path, int maxDepth) throws IOException {\n        return DirectoryStructurePrinter.list(path, maxDepth);\n    }\n\n    public static EnumSet<PosixFilePermission> parsePosixFilePermission(int unixMode) {\n        EnumSet<PosixFilePermission> permissions = EnumSet.noneOf(PosixFilePermission.class);\n\n        // Owner permissions\n        if ((unixMode & 0400) != 0) permissions.add(PosixFilePermission.OWNER_READ);\n        if ((unixMode & 0200) != 0) permissions.add(PosixFilePermission.OWNER_WRITE);\n        if ((unixMode & 0100) != 0) permissions.add(PosixFilePermission.OWNER_EXECUTE);\n\n        // Group permissions\n        if ((unixMode & 0040) != 0) permissions.add(PosixFilePermission.GROUP_READ);\n        if ((unixMode & 0020) != 0) permissions.add(PosixFilePermission.GROUP_WRITE);\n        if ((unixMode & 0010) != 0) permissions.add(PosixFilePermission.GROUP_EXECUTE);\n\n        // Others permissions\n        if ((unixMode & 0004) != 0) permissions.add(PosixFilePermission.OTHERS_READ);\n        if ((unixMode & 0002) != 0) permissions.add(PosixFilePermission.OTHERS_WRITE);\n        if ((unixMode & 0001) != 0) permissions.add(PosixFilePermission.OTHERS_EXECUTE);\n\n        return permissions;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpMultipartRequest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\npublic final class HttpMultipartRequest implements Closeable {\n    private static final byte[] ENDL = {'\\r', '\\n'};\n\n    private final String boundary = \"*****\" + System.currentTimeMillis() + \"*****\";\n    private final HttpURLConnection urlConnection;\n    private final ByteArrayOutputStream stream;\n\n    public HttpMultipartRequest(HttpURLConnection urlConnection) throws IOException {\n        this.urlConnection = urlConnection;\n        urlConnection.setDoOutput(true);\n        urlConnection.setUseCaches(false);\n        urlConnection.setRequestProperty(\"Content-Type\", \"multipart/form-data; boundary=\" + boundary);\n\n        stream = new ByteArrayOutputStream();\n    }\n\n    private void addLine(String content) throws IOException {\n        stream.write(content.getBytes(UTF_8));\n        stream.write(ENDL);\n    }\n\n    public HttpMultipartRequest file(String name, String filename, String contentType, InputStream inputStream) throws IOException {\n        addLine(\"--\" + boundary);\n        addLine(String.format(\"Content-Disposition: form-data; name=\\\"%s\\\"; filename=\\\"%s\\\"\", name, filename));\n        addLine(\"Content-Type: \" + contentType);\n        addLine(\"\");\n        inputStream.transferTo(stream);\n        addLine(\"\");\n        return this;\n    }\n\n    public HttpMultipartRequest param(String name, String value) throws IOException {\n        addLine(\"--\" + boundary);\n        addLine(String.format(\"Content-Disposition: form-data; name=\\\"%s\\\"\", name));\n        addLine(\"\");\n        addLine(value);\n        return this;\n    }\n\n    @Override\n    public void close() throws IOException {\n        addLine(\"--\" + boundary + \"--\");\n        urlConnection.setRequestProperty(\"Content-Length\", \"\" + stream.size());\n        try (OutputStream os = urlConnection.getOutputStream()) {\n            stream.writeTo(os);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport com.google.gson.JsonParseException;\nimport com.google.gson.reflect.TypeToken;\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.function.ExceptionalBiConsumer;\nimport org.jackhuang.hmcl.util.function.ExceptionalSupplier;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.Lang.wrap;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.GSON;\nimport static org.jackhuang.hmcl.util.io.NetworkUtils.createHttpConnection;\nimport static org.jackhuang.hmcl.util.io.NetworkUtils.resolveConnection;\n\npublic abstract class HttpRequest {\n    protected final String url;\n    protected final String method;\n    protected final Map<String, String> headers = new HashMap<>();\n    protected ExceptionalBiConsumer<URL, Integer, IOException> responseCodeTester;\n    protected final Set<Integer> toleratedHttpCodes = new HashSet<>();\n    protected int retryTimes = 1;\n    protected boolean ignoreHttpCode;\n\n    private HttpRequest(String url, String method) {\n        this.url = url;\n        this.method = method;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public HttpRequest accept(String contentType) {\n        return header(\"Accept\", contentType);\n    }\n\n    public HttpRequest authorization(String token) {\n        return header(\"Authorization\", token);\n    }\n\n    public HttpRequest authorization(String tokenType, String tokenString) {\n        return authorization(tokenType + \" \" + tokenString);\n    }\n\n    public HttpRequest authorization(Authorization authorization) {\n        return authorization(authorization.getTokenType(), authorization.getAccessToken());\n    }\n\n    public HttpRequest header(String key, String value) {\n        headers.put(key, value);\n        return this;\n    }\n\n    public HttpRequest ignoreHttpCode() {\n        ignoreHttpCode = true;\n        return this;\n    }\n\n    public HttpRequest retry(int retryTimes) {\n        if (retryTimes < 1) {\n            throw new IllegalArgumentException(\"retryTimes >= 1\");\n        }\n        this.retryTimes = retryTimes;\n        return this;\n    }\n\n    public abstract String getString() throws IOException;\n\n    public CompletableFuture<String> getStringAsync() {\n        return CompletableFuture.supplyAsync(wrap(this::getString), Schedulers.io());\n    }\n\n    public <T> T getJson(Class<T> typeOfT) throws IOException, JsonParseException {\n        return JsonUtils.fromNonNullJson(getString(), typeOfT);\n    }\n\n    public <T> T getJson(TypeToken<T> type) throws IOException, JsonParseException {\n        return JsonUtils.fromNonNullJson(getString(), type);\n    }\n\n    public <T> CompletableFuture<T> getJsonAsync(Class<T> typeOfT) {\n        return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, typeOfT));\n    }\n\n    public <T> CompletableFuture<T> getJsonAsync(TypeToken<T> type) {\n        return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, type));\n    }\n\n    public HttpRequest ignoreHttpErrorCode(int code) {\n        toleratedHttpCodes.add(code);\n        return this;\n    }\n\n    public HttpURLConnection createConnection() throws IOException {\n        HttpURLConnection con = createHttpConnection(url);\n        con.setRequestMethod(method);\n        for (Map.Entry<String, String> entry : headers.entrySet()) {\n            con.setRequestProperty(entry.getKey(), entry.getValue());\n        }\n        return con;\n    }\n\n    public static class HttpGetRequest extends HttpRequest {\n        protected HttpGetRequest(String url) {\n            super(url, \"GET\");\n        }\n\n        public String getString() throws IOException {\n            return getStringWithRetry(() -> {\n                HttpURLConnection con = createConnection();\n                con = resolveConnection(con);\n                return IOUtils.readFullyAsString(\"gzip\".equals(con.getContentEncoding()) ? IOUtils.wrapFromGZip(con.getInputStream()) : con.getInputStream());\n            }, retryTimes);\n        }\n    }\n\n    public static final class HttpPostRequest extends HttpRequest {\n        private byte[] bytes;\n\n        private HttpPostRequest(String url) {\n            super(url, \"POST\");\n        }\n\n        public HttpPostRequest contentType(String contentType) {\n            headers.put(\"Content-Type\", contentType);\n            return this;\n        }\n\n        public HttpPostRequest json(Object payload) throws JsonParseException {\n            return string(payload instanceof String ? (String) payload : GSON.toJson(payload), \"application/json\");\n        }\n\n        public HttpPostRequest form(Map<String, String> params) {\n            return string(NetworkUtils.withQuery(\"\", params), \"application/x-www-form-urlencoded\");\n        }\n\n        @SafeVarargs\n        public final HttpPostRequest form(Pair<String, String>... params) {\n            return form(mapOf(params));\n        }\n\n        public HttpPostRequest string(String payload, String contentType) {\n            bytes = payload.getBytes(UTF_8);\n            header(\"Content-Length\", \"\" + bytes.length);\n            contentType(contentType + \"; charset=utf-8\");\n            return this;\n        }\n\n        public String getString() throws IOException {\n            return getStringWithRetry(() -> {\n                HttpURLConnection con = createConnection();\n                con.setDoOutput(true);\n\n                try (OutputStream os = con.getOutputStream()) {\n                    os.write(bytes);\n                }\n\n                URL url = new URL(this.url);\n\n                if (con.getResponseCode() / 100 != 2) {\n                    if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) {\n                        try {\n                            throw new ResponseCodeException(url.toString(), con.getResponseCode(), NetworkUtils.readFullyAsString(con));\n                        } catch (IOException e) {\n                            throw new ResponseCodeException(url.toString(), con.getResponseCode(), e);\n                        }\n                    }\n                }\n\n                return NetworkUtils.readFullyAsString(con);\n            }, retryTimes);\n        }\n    }\n\n    public static HttpGetRequest GET(String url) {\n        return new HttpGetRequest(url);\n    }\n\n    @SafeVarargs\n    public static HttpGetRequest GET(String url, Pair<String, String>... query) {\n        return GET(NetworkUtils.withQuery(url, mapOf(query)));\n    }\n\n    public static HttpPostRequest POST(String url) throws MalformedURLException {\n        return new HttpPostRequest(url);\n    }\n\n    private static String getStringWithRetry(ExceptionalSupplier<String, IOException> supplier, int retryTimes) throws IOException {\n        Throwable exception = null;\n        for (int i = 0; i < retryTimes; i++) {\n            try {\n                return supplier.get();\n            } catch (Throwable e) {\n                exception = e;\n            }\n        }\n        if (exception != null) {\n            if (exception instanceof IOException) {\n                throw (IOException) exception;\n            } else {\n                throw new IOException(exception);\n            }\n        }\n        throw new IOException(\"retry 0\");\n    }\n\n    public interface Authorization {\n        String getTokenType();\n\n        String getAccessToken();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpServer.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport com.google.gson.JsonParseException;\nimport fi.iki.elonen.NanoHTTPD;\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.Lang.mapOf;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic class HttpServer extends NanoHTTPD {\n    private int traceId = 0;\n    protected final List<Route> routes = new ArrayList<>();\n\n    public HttpServer(int port) {\n        super(port);\n    }\n\n    public HttpServer(String hostname, int port) {\n        super(hostname, port);\n    }\n\n    public String getRootUrl() {\n        return \"http://localhost:\" + getListeningPort();\n    }\n\n    protected void addRoute(Method method, Pattern path, ExceptionalFunction<Request, Response, ?> server) {\n        routes.add(new DefaultRoute(method, path, server));\n    }\n\n    protected static Response ok(Object response) {\n        LOG.info(String.format(\"Response %s\", JsonUtils.GSON.toJson(response)));\n        return newFixedLengthResponse(Response.Status.OK, \"text/json\", JsonUtils.GSON.toJson(response));\n    }\n\n    protected static Response notFound() {\n        return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_HTML, \"404 not found\");\n    }\n\n    protected static Response noContent() {\n        return newFixedLengthResponse(Response.Status.NO_CONTENT, MIME_HTML, \"\");\n    }\n\n    protected static Response badRequest() {\n        return newFixedLengthResponse(Response.Status.BAD_REQUEST, MIME_HTML, \"400 bad request\");\n    }\n\n    protected static Response internalError() {\n        return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_HTML, \"500 internal error\");\n    }\n\n    @Override\n    public Response serve(IHTTPSession session) {\n        int currentId = traceId++;\n        LOG.info(String.format(\"[%d] %s --> %s\", currentId, session.getMethod().name(),\n                session.getUri() + Optional.ofNullable(session.getQueryParameterString()).map(s -> \"?\" + s).orElse(\"\")));\n\n        Response response = null;\n        for (Route route : routes) {\n            if (route.method != session.getMethod()) continue;\n\n            Matcher pathMatcher = route.pathPattern.matcher(session.getUri());\n            if (!pathMatcher.find()) continue;\n\n            response = route.serve(new Request(pathMatcher, mapOf(NetworkUtils.parseQuery(session.getQueryParameterString())), session));\n            break;\n        }\n\n        if (response == null) response = notFound();\n        LOG.info(String.format(\"[%d] %s <--\", currentId, response.getStatus()));\n        return response;\n    }\n\n    public static abstract class Route {\n        Method method;\n        Pattern pathPattern;\n\n        public Route(Method method, Pattern pathPattern) {\n            this.method = method;\n            this.pathPattern = pathPattern;\n        }\n\n        public Method getMethod() {\n            return method;\n        }\n\n        public Pattern getPathPattern() {\n            return pathPattern;\n        }\n\n        public abstract Response serve(Request request);\n    }\n\n    public static class DefaultRoute extends Route {\n        private final ExceptionalFunction<Request, Response, ?> server;\n\n        public DefaultRoute(Method method, Pattern pathPattern, ExceptionalFunction<Request, Response, ?> server) {\n            super(method, pathPattern);\n            this.server = server;\n        }\n\n        @Override\n        public Response serve(Request request) {\n            try {\n                return server.apply(request);\n            } catch (JsonParseException e) {\n                return badRequest();\n            } catch (Exception e) {\n                LOG.error(\"Error handling \" + request.getSession().getUri(), e);\n                return internalError();\n            }\n        }\n    }\n\n    public static class Request {\n        Matcher pathVariables;\n        Map<String, String> query;\n        NanoHTTPD.IHTTPSession session;\n\n        public Request(Matcher pathVariables, Map<String, String> query, NanoHTTPD.IHTTPSession session) {\n            this.pathVariables = pathVariables;\n            this.query = query;\n            this.session = session;\n        }\n\n        public Matcher getPathVariables() {\n            return pathVariables;\n        }\n\n        public Map<String, String> getQuery() {\n            return query;\n        }\n\n        public NanoHTTPD.IHTTPSession getSession() {\n            return session;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport org.glavo.chardet.DetectedCharset;\nimport org.glavo.chardet.UniversalDetector;\n\nimport java.io.*;\nimport java.nio.channels.Channels;\nimport java.nio.channels.FileChannel;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.zip.GZIPInputStream;\n\nimport static java.nio.charset.StandardCharsets.*;\nimport static org.jackhuang.hmcl.util.platform.OperatingSystem.NATIVE_CHARSET;\n\n/**\n * This utility class consists of some util methods operating on InputStream/OutputStream.\n *\n * @author huangyuhui\n */\npublic final class IOUtils {\n\n    private IOUtils() {\n    }\n\n    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;\n\n    public static BufferedReader newBufferedReaderMaybeNativeEncoding(Path file) throws IOException {\n        if (NATIVE_CHARSET == UTF_8)\n            return Files.newBufferedReader(file);\n\n        FileChannel channel = FileChannel.open(file);\n        try {\n            long oldPosition = channel.position();\n            DetectedCharset detectedCharset = UniversalDetector.detectCharset(channel);\n            Charset charset = detectedCharset != null && detectedCharset.isSupported()\n                    && (detectedCharset.getCharset() == UTF_8 || detectedCharset.getCharset() == US_ASCII)\n                    ? UTF_8 : NATIVE_CHARSET;\n            channel.position(oldPosition);\n            return new BufferedReader(new InputStreamReader(Channels.newInputStream(channel), charset));\n        } catch (Throwable e) {\n            closeQuietly(channel, e);\n            throw e;\n        }\n    }\n\n    public static byte[] readFully(InputStream stream) throws IOException {\n        try (stream) {\n            return stream.readAllBytes();\n        }\n    }\n\n    public static String readFullyAsString(InputStream stream) throws IOException {\n        return new String(readFully(stream), UTF_8);\n    }\n\n    public static String readFullyAsString(InputStream stream, Charset charset) throws IOException {\n        return new String(readFully(stream), charset);\n    }\n\n    public static void skipNBytes(InputStream input, long n) throws IOException {\n        while (n > 0) {\n            long ns = input.skip(n);\n            if (ns > 0 && ns <= n)\n                n -= ns;\n            else if (ns == 0) {\n                if (input.read() == -1)\n                    throw new EOFException();\n                n--;\n            } else {\n                throw new IOException(\"Unexpected skip bytes. Expected: \" + n + \", Actual: \" + ns);\n            }\n        }\n    }\n\n    public static void copyTo(InputStream src, OutputStream dest, byte[] buf) throws IOException {\n        while (true) {\n            int len = src.read(buf);\n            if (len == -1)\n                break;\n            dest.write(buf, 0, len);\n        }\n    }\n\n    public static InputStream wrapFromGZip(InputStream inputStream) throws IOException {\n        return new GZIPInputStream(inputStream);\n    }\n\n    public static void closeQuietly(AutoCloseable closeable) {\n        try {\n            if (closeable != null)\n                closeable.close();\n        } catch (Throwable ignored) {\n        }\n    }\n\n    public static void closeQuietly(AutoCloseable closeable, Throwable exception) {\n        try {\n            if (closeable != null)\n                closeable.close();\n        } catch (Throwable e) {\n            exception.addSuppressed(e);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/JarUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.FileSystemNotFoundException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.CodeSource;\nimport java.util.Properties;\n\npublic final class JarUtils {\n    private JarUtils() {\n    }\n\n    private static final Path THIS_JAR;\n    private static final Properties properties = new Properties();\n\n    static {\n        Class<?> entryPointClass = null;\n        CodeSource cs = null;\n        try {\n            entryPointClass = Class.forName(\"org.jackhuang.hmcl.EntryPoint\");\n            cs = entryPointClass.getProtectionDomain().getCodeSource();\n        } catch (ClassNotFoundException ignored) {\n        }\n\n        if (cs == null) {\n            THIS_JAR = null;\n        } else {\n            Path path;\n            try {\n                path = Path.of(cs.getLocation().toURI()).toAbsolutePath();\n            } catch (FileSystemNotFoundException | IllegalArgumentException | URISyntaxException e) {\n                path = null;\n            }\n            THIS_JAR = path != null && Files.isRegularFile(path) ? path : null;\n        }\n\n        if (entryPointClass != null) {\n            InputStream input = entryPointClass.getResourceAsStream(\"/assets/hmcl.properties\");\n            if (input != null) {\n                try (var reader = new InputStreamReader(input, StandardCharsets.UTF_8)) {\n                    properties.load(reader);\n                } catch (IOException ignored) {\n                }\n            }\n        }\n    }\n\n    @Nullable\n    public static Path thisJarPath() {\n        return THIS_JAR;\n    }\n\n    public static String getAttribute(String name, String defaultValue) {\n        return properties.getProperty(name, defaultValue);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.*;\nimport java.net.*;\nimport java.nio.charset.Charset;\nimport java.util.*;\nimport java.util.Map.Entry;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.jackhuang.hmcl.util.StringUtils.*;\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author huangyuhui\n */\npublic final class NetworkUtils {\n    public static final String USER_AGENT = System.getProperty(\"http.agent\", \"HMCL\");\n\n    public static final String PARAMETER_SEPARATOR = \"&\";\n    public static final String NAME_VALUE_SEPARATOR = \"=\";\n    public static final int TIME_OUT = 8000;\n\n    private NetworkUtils() {\n    }\n\n    public static boolean isLoopbackAddress(URI uri) {\n        String host = uri.getHost();\n        if (StringUtils.isBlank(host))\n            return false;\n\n        try {\n            InetAddress addr = InetAddress.getByName(host);\n            return addr.isLoopbackAddress();\n        } catch (UnknownHostException e) {\n            return false;\n        }\n    }\n\n    public static boolean isHttpUri(URI uri) {\n        return \"http\".equals(uri.getScheme()) || \"https\".equals(uri.getScheme());\n    }\n\n    public static String addHttpsIfMissing(String url) {\n        if (Pattern.compile(\"^(?<scheme>[a-zA-Z][a-zA-Z0-9+.-]*)://\").matcher(url).find())\n            return url;\n\n        if (url.startsWith(\"//\"))\n            return \"https:\" + url;\n        else\n            return \"https://\" + url;\n    }\n\n    public static String withQuery(String baseUrl, Map<String, String> params) {\n        StringBuilder sb = new StringBuilder(baseUrl);\n        boolean first = true;\n        for (Entry<String, String> param : params.entrySet()) {\n            if (param.getValue() == null)\n                continue;\n            if (first) {\n                if (!baseUrl.isEmpty()) {\n                    sb.append('?');\n                }\n                first = false;\n            } else {\n                sb.append(PARAMETER_SEPARATOR);\n            }\n            sb.append(encodeURL(param.getKey()));\n            sb.append(NAME_VALUE_SEPARATOR);\n            sb.append(encodeURL(param.getValue()));\n        }\n        return sb.toString();\n    }\n\n    public static String withQuery(String baseUrl, List<Pair<String, String>> params) {\n        StringBuilder sb = new StringBuilder(baseUrl);\n        boolean first = true;\n        for (Pair<String, String> param : params) {\n            if (param.getValue() == null)\n                continue;\n            if (first) {\n                if (!baseUrl.isEmpty()) {\n                    sb.append('?');\n                }\n                first = false;\n            } else {\n                sb.append(PARAMETER_SEPARATOR);\n            }\n            sb.append(encodeURL(param.getKey()));\n            sb.append(NAME_VALUE_SEPARATOR);\n            sb.append(encodeURL(param.getValue()));\n        }\n        return sb.toString();\n    }\n\n    public static List<URI> withQuery(List<URI> list, Map<String, String> params) {\n        return list.stream().map(uri -> URI.create(withQuery(uri.toString(), params))).collect(Collectors.toList());\n    }\n\n    public static List<Pair<String, String>> parseQuery(URI uri) {\n        return parseQuery(uri.getRawQuery());\n    }\n\n    public static List<Pair<String, String>> parseQuery(String queryParameterString) {\n        if (queryParameterString == null) return Collections.emptyList();\n\n        List<Pair<String, String>> result = new ArrayList<>();\n\n        try (Scanner scanner = new Scanner(queryParameterString)) {\n            scanner.useDelimiter(\"&\");\n            while (scanner.hasNext()) {\n                String[] nameValue = scanner.next().split(NAME_VALUE_SEPARATOR);\n                if (nameValue.length == 0 || nameValue.length > 2) {\n                    throw new IllegalArgumentException(\"bad query string\");\n                }\n\n                String name = decodeURL(nameValue[0]);\n                String value = nameValue.length == 2 ? decodeURL(nameValue[1]) : null;\n                result.add(pair(name, value));\n            }\n        }\n        return result;\n    }\n\n    public static URI dropQuery(URI u) {\n        if (u.getRawQuery() == null && u.getRawFragment() == null) {\n            return u;\n        }\n\n        try {\n            return new URI(u.getScheme(), u.getUserInfo(), u.getHost(), u.getPort(), u.getPath(), null, null);\n        } catch (URISyntaxException e) {\n            throw new AssertionError(\"Unreachable\", e);\n        }\n    }\n\n    public static URLConnection createConnection(URI uri) throws IOException {\n        URLConnection connection;\n        try {\n            connection = uri.toURL().openConnection();\n        } catch (IllegalArgumentException | MalformedURLException e) {\n            throw new IOException(e);\n        }\n        connection.setConnectTimeout(TIME_OUT);\n        connection.setReadTimeout(TIME_OUT);\n        if (connection instanceof HttpURLConnection httpConnection) {\n            httpConnection.setRequestProperty(\"Accept-Language\", Locale.getDefault().toLanguageTag());\n            httpConnection.setRequestProperty(\"User-Agent\", USER_AGENT);\n            httpConnection.setInstanceFollowRedirects(false);\n        }\n        return connection;\n    }\n\n    public static HttpURLConnection createHttpConnection(String url) throws IOException {\n        return (HttpURLConnection) createConnection(toURI(url));\n    }\n\n    public static HttpURLConnection createHttpConnection(URI url) throws IOException {\n        return (HttpURLConnection) createConnection(url);\n    }\n\n    private static void encodeCodePoint(StringBuilder builder, int codePoint) {\n        builder.append(encodeURL(Character.toString(codePoint)));\n    }\n\n    /**\n     * @param location the url to be URL encoded\n     * @return encoded URL\n     * @see <a href=\n     * \"https://github.com/curl/curl/blob/3f7b1bb89f92c13e69ee51b710ac54f775aab320/lib/transfer.c#L1427-L1461\">Curl</a>\n     */\n    public static String encodeLocation(String location) {\n        int i = 0;\n        boolean left = true;\n        while (i < location.length()) {\n            char ch = location.charAt(i);\n            if (ch == ' '\n                    || ch == '[' || ch == ']'\n                    || ch == '{' || ch == '}'\n                    || ch >= 0x80)\n                break;\n            else if (ch == '?')\n                left = false;\n            i++;\n        }\n\n        if (i == location.length()) {\n            // No need to encode\n            return location;\n        }\n\n        var builder = new StringBuilder(location.length() + 10);\n        builder.append(location, 0, i);\n\n        for (; i < location.length(); i++) {\n            char ch = location.charAt(i);\n            if (ch == ' ') {\n                if (left)\n                    builder.append(\"%20\");\n                else\n                    builder.append('+');\n            } else if (ch == '?') {\n                left = false;\n                builder.append('?');\n            } else if (ch >= 0x80 || (left && (ch == '[' || ch == ']' || ch == '{' || ch == '}'))) {\n                if (Character.isSurrogate(ch)) {\n                    if (Character.isHighSurrogate(ch) && i < location.length() - 1) {\n                        char ch2 = location.charAt(i + 1);\n                        if (Character.isLowSurrogate(ch2)) {\n                            int codePoint = Character.toCodePoint(ch, ch2);\n                            encodeCodePoint(builder, codePoint);\n                            i++;\n                            continue;\n                        }\n                    }\n\n                    // Invalid surrogate pair, encode as U+FFFD (replacement character)\n                    encodeCodePoint(builder, 0xfffd);\n                    continue;\n                }\n\n                encodeCodePoint(builder, ch);\n            } else {\n                builder.append(ch);\n            }\n        }\n\n        return builder.toString();\n    }\n\n    /**\n     * This method is a work-around that aims to solve problem when \"Location\" in\n     * stupid server's response is not encoded.\n     *\n     * @param conn the stupid http connection.\n     * @return manually redirected http connection.\n     * @throws IOException if an I/O error occurs.\n     * @see <a href=\"https://github.com/curl/curl/issues/473\">Issue with libcurl</a>\n     */\n    public static HttpURLConnection resolveConnection(HttpURLConnection conn) throws IOException {\n        final boolean useCache = conn.getUseCaches();\n        int redirect = 0;\n        while (true) {\n            conn.setUseCaches(useCache);\n            conn.setConnectTimeout(TIME_OUT);\n            conn.setReadTimeout(TIME_OUT);\n            conn.setInstanceFollowRedirects(false);\n            Map<String, List<String>> properties = conn.getRequestProperties();\n            String method = conn.getRequestMethod();\n            int code = conn.getResponseCode();\n            if (code >= 300 && code <= 308 && code != 306 && code != 304) {\n                String newURL = conn.getHeaderField(\"Location\");\n                conn.disconnect();\n\n                if (redirect > 20) {\n                    throw new IOException(\"Too much redirects\");\n                }\n\n                HttpURLConnection redirected = (HttpURLConnection) new URL(conn.getURL(), encodeLocation(newURL))\n                        .openConnection();\n                properties\n                        .forEach((key, value) -> value.forEach(element -> redirected.addRequestProperty(key, element)));\n                redirected.setRequestMethod(method);\n                conn = redirected;\n                ++redirect;\n            } else {\n                break;\n            }\n        }\n        return conn;\n    }\n\n    public static String doGet(String uri) throws IOException {\n        return doGet(toURI(uri));\n    }\n\n    public static String doGet(URI uri) throws IOException {\n        URLConnection connection = createConnection(uri);\n        if (connection instanceof HttpURLConnection httpURLConnection) {\n            connection = resolveConnection(httpURLConnection);\n        }\n        return readFullyAsString(connection);\n    }\n\n    public static String doGet(List<URI> uris) throws IOException {\n        List<IOException> exceptions = null;\n        for (URI uri : uris) {\n            try {\n                return doGet(uri);\n            } catch (IOException e) {\n                if (exceptions == null) {\n                    exceptions = new ArrayList<>(1);\n                }\n                exceptions.add(e);\n            }\n        }\n\n        if (exceptions == null) {\n            throw new IOException(\"No candidate URL\");\n        } else if (exceptions.size() == 1) {\n            throw exceptions.get(0);\n        } else {\n            IOException exception = new IOException(\"Failed to doGet\");\n            for (IOException e : exceptions) {\n                exception.addSuppressed(e);\n            }\n            throw exception;\n        }\n    }\n\n    public static String doPost(URI uri, String post) throws IOException {\n        return doPost(uri, post, \"application/x-www-form-urlencoded\");\n    }\n\n    public static String doPost(URI u, Map<String, String> params) throws IOException {\n        StringBuilder sb = new StringBuilder();\n        if (params != null) {\n            for (Map.Entry<String, String> e : params.entrySet())\n                sb.append(e.getKey()).append(\"=\").append(e.getValue()).append(\"&\");\n            sb.deleteCharAt(sb.length() - 1);\n        }\n        return doPost(u, sb.toString());\n    }\n\n    public static String doPost(URI uri, String post, String contentType) throws IOException {\n        byte[] bytes = post.getBytes(UTF_8);\n\n        HttpURLConnection con = createHttpConnection(uri);\n        con.setRequestMethod(\"POST\");\n        con.setDoOutput(true);\n        con.setRequestProperty(\"Content-Type\", contentType + \"; charset=utf-8\");\n        con.setRequestProperty(\"Content-Length\", String.valueOf(bytes.length));\n        try (OutputStream os = con.getOutputStream()) {\n            os.write(bytes);\n        }\n        return readFullyAsString(con);\n    }\n\n    static final Pattern CHARSET_REGEX = Pattern.compile(\"\\\\s*(charset)\\\\s*=\\\\s*['|\\\"]?(?<charset>[^\\\"^';,]+)['|\\\"]?\");\n\n    public static Charset getCharsetFromContentType(String contentType) {\n        if (contentType == null || contentType.isBlank())\n            return UTF_8;\n\n        Matcher matcher = CHARSET_REGEX.matcher(contentType);\n        if (matcher.find()) {\n            String charsetName = matcher.group(\"charset\");\n            try {\n                return Charset.forName(charsetName);\n            } catch (Throwable e) {\n                // Ignore invalid charset\n                LOG.warning(\"Bad charset name: \" + charsetName + \", using UTF-8 instead\", e);\n            }\n        }\n        return UTF_8;\n    }\n\n    public static String readFullyAsString(URLConnection con) throws IOException {\n        try {\n            var contentEncoding = ContentEncoding.fromConnection(con);\n            Charset charset = getCharsetFromContentType(con.getHeaderField(\"Content-Type\"));\n\n            try (InputStream stdout = con.getInputStream()) {\n                return IOUtils.readFullyAsString(contentEncoding.wrap(stdout), charset);\n            } catch (IOException e) {\n                if (con instanceof HttpURLConnection) {\n                    try (InputStream stderr = ((HttpURLConnection) con).getErrorStream()) {\n                        if (stderr == null)\n                            throw e;\n                        return IOUtils.readFullyAsString(contentEncoding.wrap(stderr), charset);\n                    }\n                } else {\n                    throw e;\n                }\n            }\n        } finally {\n            if (con instanceof HttpURLConnection) {\n                ((HttpURLConnection) con).disconnect();\n            }\n        }\n    }\n\n    public static String detectFileName(URI uri) throws IOException {\n        HttpURLConnection conn = resolveConnection(createHttpConnection(uri));\n        int code = conn.getResponseCode();\n        if (code / 100 == 4)\n            throw new FileNotFoundException();\n        if (code / 100 != 2)\n            throw new ResponseCodeException(uri, conn.getResponseCode());\n\n        String disposition = conn.getHeaderField(\"Content-Disposition\");\n        if (disposition == null || !disposition.contains(\"filename=\")) {\n            String u = conn.getURL().toString();\n            return decodeURL(substringAfterLast(u, '/'));\n        } else {\n            return decodeURL(removeSurrounding(substringAfter(disposition, \"filename=\"), \"\\\"\"));\n        }\n    }\n\n    // ==== Shortcut methods for encoding/decoding URLs in UTF-8 ====\n    public static String encodeURL(String toEncode) {\n        return URLEncoder.encode(toEncode, UTF_8);\n    }\n\n    public static String decodeURL(String toDecode) {\n        return URLDecoder.decode(toDecode, UTF_8);\n    }\n\n    /// @throws IllegalArgumentException if the string is not a valid URI\n    public static @NotNull URI toURI(@NotNull String uri) {\n        try {\n            return new URI(encodeLocation(uri));\n        } catch (URISyntaxException e) {\n            // Possibly an Internationalized Domain Name (IDN)\n            return URI.create(uri);\n        }\n    }\n\n    public static @NotNull URI toURI(@NotNull URL url) {\n        return toURI(url.toExternalForm());\n    }\n\n    public static @Nullable URI toURIOrNull(String uri) {\n        if (StringUtils.isNotBlank(uri)) {\n            try {\n                return toURI(uri);\n            } catch (Exception ignored) {\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ResponseCodeException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport java.io.IOException;\nimport java.net.URI;\n\npublic final class ResponseCodeException extends IOException {\n\n    private final String uri;\n    private final int responseCode;\n    private final String data;\n\n    public ResponseCodeException(URI uri, int responseCode) {\n        this(uri.toString(), responseCode);\n    }\n\n    public ResponseCodeException(URI uri, int responseCode, Throwable cause) {\n        this(uri.toString(), responseCode, cause);\n    }\n\n    public ResponseCodeException(URI uri, int responseCode, String data) {\n        this(uri.toString(), responseCode, data);\n    }\n\n    public ResponseCodeException(String uri, int responseCode) {\n        super(\"Unable to request url \" + uri + \", response code: \" + responseCode);\n        this.uri = uri;\n        this.responseCode = responseCode;\n        this.data = null;\n    }\n\n    public ResponseCodeException(String uri, int responseCode, Throwable cause) {\n        super(\"Unable to request url \" + uri + \", response code: \" + responseCode, cause);\n        this.uri = uri;\n        this.responseCode = responseCode;\n        this.data = null;\n    }\n\n    public ResponseCodeException(String uri, int responseCode, String data) {\n        super(\"Unable to request url \" + uri + \", response code: \" + responseCode + \", data: \" + data);\n        this.uri = uri;\n        this.responseCode = responseCode;\n        this.data = data;\n    }\n\n    public String getUri() {\n        return uri;\n    }\n\n    public int getResponseCode() {\n        return responseCode;\n    }\n\n    public String getData() {\n        return data;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Unzipper.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.*;\n\npublic final class Unzipper {\n    private final Path zipFile, dest;\n    private boolean replaceExistentFile = false;\n    private boolean terminateIfSubDirectoryNotExists = false;\n    private String subDirectory = \"/\";\n    private EntryFilter filter;\n    private Charset encoding = StandardCharsets.UTF_8;\n\n    /// Decompress the given zip file to a directory.\n    ///\n    /// @param zipFile the input zip file to be uncompressed\n    /// @param destDir the dest directory to hold uncompressed files\n    public Unzipper(Path zipFile, Path destDir) {\n        this.zipFile = zipFile;\n        this.dest = destDir;\n    }\n\n    /// True if replace the existent files in destination directory,\n    /// otherwise those conflict files will be ignored.\n    public Unzipper setReplaceExistentFile(boolean replaceExistentFile) {\n        this.replaceExistentFile = replaceExistentFile;\n        return this;\n    }\n\n    /// Will be called for every entry in the zip file.\n    /// Callback returns false if you want leave the specific file uncompressed.\n    public Unzipper setFilter(EntryFilter filter) {\n        this.filter = filter;\n        return this;\n    }\n\n    /// Will only uncompress files in the \"subDirectory\", their path will be also affected.\n    ///\n    /// For example, if you set subDirectory to /META-INF, files in /META-INF/ will be\n    /// uncompressed to the destination directory without creating META-INF folder.\n    ///\n    /// Default value: \"/\"\n    public Unzipper setSubDirectory(String subDirectory) {\n        this.subDirectory = FileUtils.normalizePath(subDirectory);\n        return this;\n    }\n\n    public Unzipper setEncoding(Charset encoding) {\n        this.encoding = encoding;\n        return this;\n    }\n\n    public Unzipper setTerminateIfSubDirectoryNotExists() {\n        this.terminateIfSubDirectoryNotExists = true;\n        return this;\n    }\n\n    /// Decompress the given zip file to a directory.\n    ///\n    /// @throws IOException if zip file is malformed or filesystem error.\n    public void unzip() throws IOException {\n        Path destDir = this.dest.toAbsolutePath().normalize();\n        Files.createDirectories(destDir);\n\n        CopyOption[] copyOptions = replaceExistentFile\n                ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING}\n                : new CopyOption[]{};\n\n        long entryCount = 0L;\n        try (ZipArchiveReader reader = CompressingUtils.openZipFileWithPossibleEncoding(zipFile, encoding)) {\n            String pathPrefix = StringUtils.addSuffix(subDirectory, \"/\");\n\n            for (ZipArchiveEntry entry : reader.getEntries()) {\n                String normalizedPath = FileUtils.normalizePath(entry.getName());\n                if (!normalizedPath.startsWith(pathPrefix)) {\n                    continue;\n                }\n\n                String relativePath = normalizedPath.substring(pathPrefix.length());\n                Path destFile = destDir.resolve(relativePath).toAbsolutePath().normalize();\n                if (!destFile.startsWith(destDir)) {\n                    throw new IOException(\"Zip entry is trying to write outside of the destination directory: \" + entry.getName());\n                }\n\n                if (filter != null && !filter.accept(entry, destFile, relativePath)) {\n                    continue;\n                }\n\n                entryCount++;\n\n                if (entry.isDirectory()) {\n                    Files.createDirectories(destFile);\n                } else {\n                    Files.createDirectories(destFile.getParent());\n                    if (entry.isUnixSymlink()) {\n                        String linkTarget = reader.getUnixSymlink(entry);\n                        if (replaceExistentFile)\n                            Files.deleteIfExists(destFile);\n\n                        Path targetPath;\n                        try {\n                            targetPath = Path.of(linkTarget);\n                        } catch (InvalidPathException e) {\n                            throw new IOException(\"Zip entry has an invalid symlink target: \" + entry.getName(), e);\n                        }\n\n                        if (!destFile.getParent().resolve(targetPath).toAbsolutePath().normalize().startsWith(destDir)) {\n                            throw new IOException(\"Zip entry is trying to create a symlink outside of the destination directory: \" + entry.getName());\n                        }\n\n                        try {\n                            Files.createSymbolicLink(destFile, targetPath);\n                        } catch (FileAlreadyExistsException ignored) {\n                        }\n                    } else {\n                        try (InputStream input = reader.getInputStream(entry)) {\n                            Files.copy(input, destFile, copyOptions);\n                        } catch (FileAlreadyExistsException e) {\n                            if (replaceExistentFile)\n                                throw e;\n                        }\n\n                        if (entry.getUnixMode() != 0 && OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) {\n                            Files.setPosixFilePermissions(destFile, FileUtils.parsePosixFilePermission(entry.getUnixMode()));\n                        }\n                    }\n                }\n            }\n\n            if (entryCount == 0 && !\"/\".equals(subDirectory) && !terminateIfSubDirectoryNotExists) {\n                throw new NoSuchFileException(\"Subdirectory \" + subDirectory + \" does not exist in the zip file.\");\n            }\n        }\n    }\n\n    @FunctionalInterface\n    public interface EntryFilter {\n        boolean accept(ZipArchiveEntry zipArchiveEntry, Path destFile, String relativePath) throws IOException;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Zipper.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport org.jackhuang.hmcl.util.function.ExceptionalPredicate;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.*;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.stream.Stream;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipException;\nimport java.util.zip.ZipOutputStream;\n\n/**\n * Non thread-safe\n *\n * @author huangyuhui\n */\npublic final class Zipper implements Closeable {\n\n    private final ZipOutputStream zos;\n    private final byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];\n    private final Set<String> entryNames;\n\n    public Zipper(Path zipFile) throws IOException {\n        this(zipFile, false);\n    }\n\n    public Zipper(Path zipFile, boolean allowDuplicateEntry) throws IOException {\n        this.zos = new ZipOutputStream(Files.newOutputStream(zipFile), StandardCharsets.UTF_8);\n        this.entryNames = allowDuplicateEntry ? new HashSet<>() : null;\n    }\n\n    private static String normalize(String path) {\n        path = path.replace('\\\\', '/');\n        if (path.startsWith(\"/\"))\n            path = path.substring(1);\n        if (path.endsWith(\"/\"))\n            path = path.substring(0, path.length() - 1);\n        return path;\n    }\n\n    private ZipEntry newEntry(String name) throws IOException {\n        if (entryNames == null || name.endsWith(\"/\") || entryNames.add(name))\n            return new ZipEntry(name);\n\n        for (int i = 1; i < 10; i++) {\n            String newName = name + \".\" + i;\n            if (entryNames.add(newName)) {\n                return new ZipEntry(newName);\n            }\n        }\n\n        throw new ZipException(\"duplicate entry: \" + name);\n    }\n\n    private static String resolve(String dir, String file) {\n        if (dir.isEmpty()) return file;\n        if (file.isEmpty()) return dir;\n        return dir + \"/\" + file;\n    }\n\n    @Override\n    public void close() throws IOException {\n        zos.close();\n    }\n\n    /**\n     * Compress all the files in sourceDir\n     *\n     * @param source    the file in basePath to be compressed\n     * @param targetDir the path of the directory in this zip file.\n     */\n    public void putDirectory(Path source, String targetDir) throws IOException {\n        putDirectory(source, targetDir, null);\n    }\n\n    /**\n     * Compress all the files in sourceDir\n     *\n     * @param source    the file in basePath to be compressed\n     * @param targetDir the path of the directory in this zip file.\n     * @param filter    returns false if you do not want that file or directory\n     */\n    public void putDirectory(Path source, String targetDir, ExceptionalPredicate<String, IOException> filter) throws IOException {\n        String root = normalize(targetDir);\n        Files.walkFileTree(source, new SimpleFileVisitor<Path>() {\n            @Override\n            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n                if (\".DS_Store\".equals(file.getFileName().toString())) {\n                    return FileVisitResult.SKIP_SUBTREE;\n                }\n                String relativePath = normalize(source.relativize(file).normalize().toString());\n                if (filter != null && !filter.test(relativePath)) {\n                    return FileVisitResult.SKIP_SUBTREE;\n                }\n                putFile(file, resolve(root, relativePath));\n                return FileVisitResult.CONTINUE;\n            }\n\n            @Override\n            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\n                String relativePath = normalize(source.relativize(dir).normalize().toString());\n                if (filter != null && !filter.test(relativePath)) {\n                    return FileVisitResult.SKIP_SUBTREE;\n                }\n                try {\n                    zos.putNextEntry(new ZipEntry(resolve(root, relativePath) + \"/\"));\n                    zos.closeEntry();\n                } catch (ZipException ignored) {\n                    // Directory already exists\n                }\n                return FileVisitResult.CONTINUE;\n            }\n        });\n    }\n\n    public void putFile(Path file, String path) throws IOException {\n        path = normalize(path);\n\n        BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class);\n\n        ZipEntry entry = newEntry(attrs.isDirectory() ? path + \"/\" : path);\n        entry.setCreationTime(attrs.creationTime());\n        entry.setLastAccessTime(attrs.lastAccessTime());\n        entry.setLastModifiedTime(attrs.lastModifiedTime());\n\n        if (attrs.isDirectory()) {\n            try {\n                zos.putNextEntry(entry);\n                zos.closeEntry();\n            } catch (ZipException ignored) {\n                // Directory already exists\n            }\n        } else {\n            try (InputStream input = Files.newInputStream(file)) {\n                zos.putNextEntry(entry);\n                IOUtils.copyTo(input, zos, buffer);\n                zos.closeEntry();\n            }\n        }\n    }\n\n    public void putStream(InputStream in, String path) throws IOException {\n        zos.putNextEntry(newEntry(normalize(path)));\n        IOUtils.copyTo(in, zos, buffer);\n        zos.closeEntry();\n    }\n\n    public OutputStream putStream(String path) throws IOException {\n        zos.putNextEntry(newEntry(normalize(path)));\n        return new OutputStream() {\n            public void write(int b) throws IOException {\n                zos.write(b);\n            }\n\n            public void write(@NotNull byte[] b) throws IOException {\n                zos.write(b);\n            }\n\n            public void write(@NotNull byte[] b, int off, int len) throws IOException {\n                zos.write(b, off, len);\n            }\n\n            public void flush() throws IOException {\n                zos.flush();\n            }\n\n            public void close() throws IOException {\n                zos.closeEntry();\n            }\n        };\n    }\n\n    public void putLines(Stream<String> lines, String path) throws IOException {\n        zos.putNextEntry(newEntry(normalize(path)));\n\n        try {\n            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(zos));\n            lines.forEachOrdered(line -> {\n                try {\n                    writer.write(line);\n                    writer.write('\\n');\n                } catch (IOException e) {\n                    throw new UncheckedIOException(e);\n                }\n            });\n            writer.flush();\n        } catch (UncheckedIOException e) {\n            throw e.getCause();\n        } finally {\n            zos.closeEntry();\n        }\n    }\n\n    public void putTextFile(String text, String path) throws IOException {\n        putTextFile(text, StandardCharsets.UTF_8, path);\n    }\n\n    public void putTextFile(String text, Charset encoding, String path) throws IOException {\n        zos.putNextEntry(newEntry(normalize(path)));\n        zos.write(text.getBytes(encoding));\n        zos.closeEntry();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/AutomatedToggleGroup.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.Toggle;\nimport javafx.scene.control.ToggleGroup;\n\n/**\n * @author yushijinhun\n */\npublic final class AutomatedToggleGroup extends ToggleGroup {\n\n    private final ObservableList<? extends Toggle> toggles;\n    private final ListChangeListener<Toggle> listListener;\n\n    public AutomatedToggleGroup(ObservableList<? extends Toggle> toggles) {\n        this.toggles = toggles;\n\n        listListener = change -> {\n            while (change.next()) {\n                change.getRemoved().forEach(it -> it.setToggleGroup(null));\n                change.getAddedSubList().forEach(it -> it.setToggleGroup(this));\n            }\n        };\n        toggles.addListener(listListener);\n\n        toggles.forEach(it -> it.setToggleGroup(this));\n    }\n\n    public void disconnect() {\n        toggles.removeListener(listListener);\n        toggles.forEach(it -> it.setToggleGroup(null));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/BindingMapping.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport javafx.application.Platform;\nimport javafx.beans.Observable;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.ObjectBinding;\nimport javafx.beans.value.ObservableValue;\n\nimport java.util.Objects;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.jackhuang.hmcl.util.Lang.handleUncaughtException;\n\n/**\n * @author yushijinhun\n */\npublic abstract class BindingMapping<T, U> extends ObjectBinding<U> {\n\n    public static <T> BindingMapping<?, T> of(ObservableValue<T> property) {\n        if (property instanceof BindingMapping) {\n            return (BindingMapping<?, T>) property;\n        }\n        return new SimpleBinding<>(property);\n    }\n\n    public static <S extends Observable, T> BindingMapping<?, T> of(S watched, Function<S, T> mapper) {\n        return of(Bindings.createObjectBinding(() -> mapper.apply(watched), watched));\n    }\n\n    protected final ObservableValue<? extends T> predecessor;\n\n    public BindingMapping(ObservableValue<? extends T> predecessor) {\n        this.predecessor = requireNonNull(predecessor);\n        bind(predecessor);\n    }\n\n    public <V> BindingMapping<?, V> map(Function<? super U, ? extends V> mapper) {\n        return new MappedBinding<>(this, mapper);\n    }\n\n    public <V> BindingMapping<?, V> flatMap(Function<? super U, ? extends ObservableValue<? extends V>> mapper) {\n        return flatMap(mapper, null);\n    }\n\n    public <V> BindingMapping<?, V> flatMap(Function<? super U, ? extends ObservableValue<? extends V>> mapper, Supplier<? extends V> nullAlternative) {\n        return new FlatMappedBinding<>(map(mapper), nullAlternative);\n    }\n\n    public <V> BindingMapping<?, V> asyncMap(Function<U, CompletableFuture<V>> mapper, V initial) {\n        return new AsyncMappedBinding<>(this, mapper, initial);\n    }\n\n    private static final class SimpleBinding<T> extends BindingMapping<T, T> {\n\n        public SimpleBinding(ObservableValue<T> predecessor) {\n            super(predecessor);\n        }\n\n        @Override\n        protected T computeValue() {\n            return predecessor.getValue();\n        }\n\n        @Override\n        public <V> BindingMapping<?, V> map(Function<? super T, ? extends V> mapper) {\n            return new MappedBinding<>(predecessor, mapper);\n        }\n\n        @Override\n        public <V> BindingMapping<?, V> asyncMap(Function<T, CompletableFuture<V>> mapper, V initial) {\n            return new AsyncMappedBinding<>(predecessor, mapper, initial);\n        }\n    }\n\n    private static final class MappedBinding<T, U> extends BindingMapping<T, U> {\n\n        private final Function<? super T, ? extends U> mapper;\n\n        public MappedBinding(ObservableValue<? extends T> predecessor, Function<? super T, ? extends U> mapper) {\n            super(predecessor);\n            this.mapper = mapper;\n        }\n\n        @Override\n        protected U computeValue() {\n            return mapper.apply(predecessor.getValue());\n        }\n    }\n\n    private static final class FlatMappedBinding<T extends ObservableValue<? extends U>, U> extends BindingMapping<T, U> {\n\n        private final Supplier<? extends U> nullAlternative;\n        private T lastObservable = null;\n\n        public FlatMappedBinding(ObservableValue<? extends T> predecessor, Supplier<? extends U> nullAlternative) {\n            super(predecessor);\n            this.nullAlternative = nullAlternative;\n        }\n\n        @Override\n        protected U computeValue() {\n            T currentObservable = predecessor.getValue();\n            if (currentObservable != lastObservable) {\n                if (lastObservable != null) {\n                    unbind(lastObservable);\n                }\n                if (currentObservable != null) {\n                    bind(currentObservable);\n                }\n                lastObservable = currentObservable;\n            }\n\n            if (currentObservable == null) {\n                if (nullAlternative == null) {\n                    throw new NullPointerException();\n                } else {\n                    return nullAlternative.get();\n                }\n            } else {\n                return currentObservable.getValue();\n            }\n        }\n    }\n\n    private static final class AsyncMappedBinding<T, U> extends BindingMapping<T, U> {\n\n        private final ReentrantLock lock = new ReentrantLock();\n        private boolean initialized = false;\n        private T prev;\n        private U value;\n\n        private final Function<? super T, ? extends CompletableFuture<? extends U>> mapper;\n        private T computingPrev;\n        private boolean computing = false;\n\n        public AsyncMappedBinding(ObservableValue<? extends T> predecessor, Function<? super T, ? extends CompletableFuture<? extends U>> mapper, U initial) {\n            super(predecessor);\n            this.value = initial;\n            this.mapper = mapper;\n        }\n\n        private void tryUpdateValue(T currentPrev) {\n            lock.lock();\n            try {\n                if ((initialized && Objects.equals(prev, currentPrev))\n                        || isComputing(currentPrev)) {\n                    return;\n                }\n                computing = true;\n                computingPrev = currentPrev;\n            } finally {\n                lock.unlock();\n            }\n\n            CompletableFuture<? extends U> task;\n            try {\n                task = requireNonNull(mapper.apply(currentPrev));\n            } catch (Throwable e) {\n                valueUpdateFailed(currentPrev);\n                throw e;\n            }\n\n            task.handle((result, e) -> {\n                if (e == null) {\n                    valueUpdate(currentPrev, result);\n                    Platform.runLater(this::invalidate);\n                } else {\n                    handleUncaughtException(e);\n                    valueUpdateFailed(currentPrev);\n                }\n                return null;\n            });\n        }\n\n        private void valueUpdate(T currentPrev, U computed) {\n            lock.lock();\n            try {\n                if (isComputing(currentPrev)) {\n                    computing = false;\n                    computingPrev = null;\n                    prev = currentPrev;\n                    value = computed;\n                    initialized = true;\n                }\n            } finally {\n                lock.unlock();\n            }\n        }\n\n        private void valueUpdateFailed(T currentPrev) {\n            lock.lock();\n            try {\n                if (isComputing(currentPrev)) {\n                    computing = false;\n                    computingPrev = null;\n                }\n            } finally {\n                lock.unlock();\n            }\n        }\n\n        private boolean isComputing(T prev) {\n            return computing && Objects.equals(prev, computingPrev);\n        }\n\n        @Override\n        protected U computeValue() {\n            tryUpdateValue(predecessor.getValue());\n            return value;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/DirtyTracker.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\nimport javafx.beans.WeakListener;\n\nimport java.lang.ref.WeakReference;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.Set;\n\n/// @author Glavo\npublic final class DirtyTracker {\n\n    private final Set<Observable> dirty = Collections.newSetFromMap(new IdentityHashMap<>());\n    private final Listener listener = new Listener(this);\n\n    public void track(Observable observable) {\n        if (!dirty.contains(observable))\n            observable.addListener(listener);\n    }\n\n    public boolean isDirty(Observable observable) {\n        return dirty.contains(observable);\n    }\n\n    public void markDirty(Observable observable) {\n        observable.removeListener(listener);\n        dirty.add(observable);\n    }\n\n    private static final class Listener implements InvalidationListener, WeakListener {\n\n        private final WeakReference<DirtyTracker> trackerReference;\n\n        public Listener(DirtyTracker trackerReference) {\n            this.trackerReference = new WeakReference<>(trackerReference);\n        }\n\n        @Override\n        public boolean wasGarbageCollected() {\n            return trackerReference.get() == null;\n        }\n\n        @Override\n        public void invalidated(Observable observable) {\n            observable.removeListener(this);\n\n            DirtyTracker tracker = trackerReference.get();\n            if (tracker != null)\n                tracker.markDirty(observable);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ExtendedProperties.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.WeakInvalidationListener;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.Property;\nimport javafx.collections.ObservableList;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport org.jackhuang.hmcl.util.Holder;\n\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\nimport static javafx.beans.binding.Bindings.createBooleanBinding;\nimport static org.jackhuang.hmcl.util.Pair.pair;\n\n/**\n * @author yushijinhun\n */\npublic final class ExtendedProperties {\n\n    private static final String PROP_PREFIX = ExtendedProperties.class.getName();\n\n    // ==== ComboBox ====\n    @SuppressWarnings(\"unchecked\")\n    public static <T> ObjectProperty<T> selectedItemPropertyFor(ComboBox<T> comboBox) {\n        return (ObjectProperty<T>) comboBox.getProperties().computeIfAbsent(\n                PROP_PREFIX + \".comboxBox.selectedItem\",\n                any -> createPropertyForSelectionModel(comboBox, comboBox.selectionModelProperty()));\n    }\n\n    private static <T> ObjectProperty<T> createPropertyForSelectionModel(Object bean, Property<? extends SelectionModel<T>> modelProperty) {\n        return new ReadWriteComposedProperty<>(bean, \"extra.selectedItem\",\n                BindingMapping.of(modelProperty)\n                        .flatMap(SelectionModel::selectedItemProperty),\n                obj -> modelProperty.getValue().select(obj));\n    }\n    // ====\n\n    // ==== Toggle ====\n    @SuppressWarnings(\"unchecked\")\n    public static ObjectProperty<Toggle> selectedTogglePropertyFor(ToggleGroup toggleGroup) {\n        return (ObjectProperty<Toggle>) toggleGroup.getProperties().computeIfAbsent(\n                PROP_PREFIX + \".toggleGroup.selectedToggle\",\n                any -> createPropertyForToggleGroup(toggleGroup));\n    }\n\n    private static ObjectProperty<Toggle> createPropertyForToggleGroup(ToggleGroup toggleGroup) {\n        return new ReadWriteComposedProperty<>(toggleGroup, \"extra.selectedToggle\",\n                toggleGroup.selectedToggleProperty(),\n                toggleGroup::selectToggle);\n    }\n\n    public static <T> ObjectProperty<T> createSelectedItemPropertyFor(ObservableList<? extends Toggle> items, Class<T> userdataType) {\n        return selectedItemPropertyFor(new AutomatedToggleGroup(items), userdataType);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> ObjectProperty<T> selectedItemPropertyFor(ToggleGroup toggleGroup, Class<T> userdataType) {\n        return (ObjectProperty<T>) toggleGroup.getProperties().computeIfAbsent(\n                pair(PROP_PREFIX + \".toggleGroup.selectedItem\", userdataType),\n                any -> createMappedPropertyForToggleGroup(\n                        toggleGroup,\n                        toggle -> toggle == null ? null : userdataType.cast(toggle.getUserData())));\n    }\n\n    private static <T> ObjectProperty<T> createMappedPropertyForToggleGroup(ToggleGroup toggleGroup, Function<Toggle, T> mapper) {\n        ObjectProperty<Toggle> selectedToggle = selectedTogglePropertyFor(toggleGroup);\n        AtomicReference<Optional<T>> pendingItemHolder = new AtomicReference<>();\n\n        Consumer<T> itemSelector = newItem -> {\n            Optional<Toggle> toggleToSelect = toggleGroup.getToggles().stream()\n                    .filter(toggle -> Objects.equals(newItem, mapper.apply(toggle)))\n                    .findFirst();\n            if (toggleToSelect.isPresent()) {\n                pendingItemHolder.set(null);\n                selectedToggle.set(toggleToSelect.get());\n            } else {\n                // We are asked to select an nonexistent item.\n                // However, this item may become available in the future.\n                // So here we store it, and once the associated toggle becomes available, we will update the selection.\n                pendingItemHolder.set(Optional.ofNullable(newItem));\n                selectedToggle.set(null);\n            }\n        };\n\n        ReadWriteComposedProperty<T> property = new ReadWriteComposedProperty<>(toggleGroup, \"extra.selectedItem\",\n                BindingMapping.of(selectedTogglePropertyFor(toggleGroup))\n                        .map(mapper),\n                itemSelector);\n\n        InvalidationListener onTogglesChanged = any -> {\n            Optional<T> pendingItem = pendingItemHolder.get();\n            if (pendingItem != null) {\n                itemSelector.accept(pendingItem.orElse(null));\n            }\n        };\n        toggleGroup.getToggles().addListener(new WeakInvalidationListener(onTogglesChanged));\n        property.addListener(new Holder<>(onTogglesChanged));\n\n        return property;\n    }\n    // ====\n\n    // ==== CheckBox ====\n    @SuppressWarnings(\"unchecked\")\n    public static ObjectProperty<Boolean> reversedSelectedPropertyFor(CheckBox checkbox) {\n        return (ObjectProperty<Boolean>) checkbox.getProperties().computeIfAbsent(\n                PROP_PREFIX + \".checkbox.reservedSelected\",\n                any -> new MappedProperty<Boolean, Boolean>(checkbox, \"ext.reservedSelected\",\n                        checkbox.selectedProperty(), it -> !it, it -> !it));\n    }\n    // ====\n\n    // ==== General ====\n    @SuppressWarnings(\"unchecked\")\n    public static ObjectProperty<Boolean> classPropertyFor(Node node, String cssClass) {\n        return (ObjectProperty<Boolean>) node.getProperties().computeIfAbsent(\n                PROP_PREFIX + \".cssClass.\" + cssClass,\n                any -> {\n                    ObservableList<String> classes = node.getStyleClass();\n                    return new ReadWriteComposedProperty<>(node, \"extra.cssClass.\" + cssClass,\n                            createBooleanBinding(() -> classes.contains(cssClass), classes),\n                            state -> {\n                                if (state) {\n                                    if (!classes.contains(cssClass)) {\n                                        classes.add(cssClass);\n                                    }\n                                } else {\n                                    classes.remove(cssClass);\n                                }\n                            });\n                });\n    }\n    // ====\n\n    private ExtendedProperties() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MappedObservableList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.collections.transformation.TransformationList;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.Function;\n\n/// @author Glavo\npublic final class MappedObservableList<E, F> extends TransformationList<E, F> {\n\n    /// This method creates a mapping of `source`, using `mapper` as the converter.\n    ///\n    /// If an item is added to `source`, `mapper` will be invoked to create a corresponding item, which will also be added to the returned `ObservableList`.\n    /// If an item is removed from `source`, the corresponding item in the returned `ObservableList` will also be removed.\n    /// If `source` is permutated, the returned `ObservableList` will also be permutated in the same way.\n    ///\n    /// The returned `ObservableList` is unmodifiable.\n    public static <T, U> ObservableList<U> create(ObservableList<T> source, Function<T, U> mapper) {\n        return new MappedObservableList<>(source, mapper);\n    }\n\n    private final Function<? super F, ? extends E> mapper;\n    private final List<E> elements;\n\n    public MappedObservableList(@NotNull ObservableList<? extends F> source, @NotNull Function<? super F, ? extends E> mapper) {\n        super(source);\n        this.mapper = mapper;\n        this.elements = new ArrayList<>(source.size());\n        for (F f : source) {\n            elements.add(mapper.apply(f));\n        }\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    protected void sourceChanged(ListChangeListener.Change<? extends F> change) {\n        beginChange();\n        while (change.next()) {\n            int from = change.getFrom();\n            int to = change.getTo();\n\n            if (change.wasPermutated()) {\n                Object[] temp = new Object[to - from];\n                int[] permutations = new int[to - from];\n\n                for (int i = 0; i < temp.length; i++) {\n                    temp[i] = elements.get(from + i);\n                }\n\n                for (int i = from; i < to; i++) {\n                    int n = i - from;\n                    int permutation = change.getPermutation(i);\n                    permutations[n] = permutation;\n                    elements.set(permutation, (E) temp[n]);\n                }\n\n                nextPermutation(from, to, permutations);\n            } else if (change.wasUpdated()) {\n                for (int i = from; i < to; i++) {\n                    elements.set(i, mapper.apply(getSource().get(i)));\n                    nextUpdate(i);\n                }\n            } else {\n                List<E> removed = List.of();\n                if (change.wasRemoved()) {\n                    List<E> subList = elements.subList(from, from + change.getRemovedSize());\n                    removed = new ArrayList<>(subList);\n                    subList.clear();\n                }\n\n                if (change.wasAdded()) {\n                    Object[] temp = new Object[to - from];\n                    List<? extends F> addedSubList = change.getAddedSubList();\n                    for (int i = 0; i < addedSubList.size(); i++) {\n                        temp[i] = mapper.apply(addedSubList.get(i));\n                    }\n                    elements.addAll(from, (List<E>) Arrays.asList(temp));\n                }\n\n                if (change.wasRemoved() && change.wasAdded()) {\n                    nextReplace(from, to, removed);\n                } else if (change.wasRemoved()) {\n                    nextRemove(from, removed);\n                } else if (change.wasAdded()) {\n                    nextAdd(from, to);\n                }\n            }\n        }\n        endChange();\n    }\n\n    @Override\n    public E get(int index) {\n        return elements.get(index);\n    }\n\n    @Override\n    public int size() {\n        return elements.size();\n    }\n\n    @Override\n    public int getSourceIndex(int index) {\n        Objects.checkIndex(index, this.size());\n        return index;\n    }\n\n    @Override\n    public int getViewIndex(int index) {\n        Objects.checkIndex(index, this.size());\n        return index;\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MappedProperty.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.ObjectBinding;\nimport javafx.beans.property.Property;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.value.ObservableValue;\n\nimport java.util.function.Function;\n\n/**\n * @author yushijinhun\n */\npublic final class MappedProperty<T, U> extends SimpleObjectProperty<U> {\n\n    private final Property<T> predecessor;\n    private final Function<U, T> reservedMapper;\n\n    private final ObjectBinding<U> binding;\n\n    public MappedProperty(Property<T> predecessor, Function<T, U> mapper, Function<U, T> reservedMapper) {\n        this(null, \"\", predecessor, mapper, reservedMapper);\n    }\n\n    public MappedProperty(Object bean, String name, Property<T> predecessor, Function<T, U> mapper, Function<U, T> reservedMapper) {\n        super(bean, name);\n        this.predecessor = predecessor;\n        this.reservedMapper = reservedMapper;\n\n        binding = new ObjectBinding<U>() {\n            {\n                bind(predecessor);\n            }\n\n            @Override\n            protected U computeValue() {\n                return mapper.apply(predecessor.getValue());\n            }\n\n            @Override\n            protected void onInvalidating() {\n                MappedProperty.this.fireValueChangedEvent();\n            }\n        };\n    }\n\n    @Override\n    public U get() {\n        return binding.get();\n    }\n\n    @Override\n    public void set(U value) {\n        predecessor.setValue(reservedMapper.apply(value));\n    }\n\n    @Override\n    public void bind(ObservableValue<? extends U> observable) {\n        predecessor.bind(Bindings.createObjectBinding(() -> reservedMapper.apply(observable.getValue()), observable));\n    }\n\n    @Override\n    public void unbind() {\n        predecessor.unbind();\n    }\n\n    @Override\n    public boolean isBound() {\n        return predecessor.isBound();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ObservableCache.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.function.BiConsumer;\n\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\n\nimport javafx.application.Platform;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.ObjectBinding;\n\n/**\n * @author yushijinhun\n */\npublic final class ObservableCache<K, V, E extends Exception> {\n\n    private final ReentrantLock lock = new ReentrantLock();\n    private final ExceptionalFunction<K, V, E> source;\n    private final BiConsumer<K, Throwable> exceptionHandler;\n    private final V fallbackValue;\n    private final Executor executor;\n    private final ObservableHelper observable = new ObservableHelper();\n    private final Map<K, V> cache = new HashMap<>();\n    private final Map<K, CompletableFuture<V>> pendings = new HashMap<>();\n    private final Map<K, Boolean> invalidated = new HashMap<>();\n\n    public ObservableCache(ExceptionalFunction<K, V, E> source, BiConsumer<K, Throwable> exceptionHandler, V fallbackValue, Executor executor) {\n        this.source = source;\n        this.exceptionHandler = exceptionHandler;\n        this.fallbackValue = fallbackValue;\n        this.executor = executor;\n    }\n\n    public Optional<V> getImmediately(K key) {\n        lock.lock();\n        try {\n            return Optional.ofNullable(cache.get(key));\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    public void put(K key, V value) {\n        lock.lock();\n        try {\n            cache.put(key, value);\n            invalidated.remove(key);\n        } finally {\n            lock.unlock();\n        }\n        Platform.runLater(observable::invalidate);\n    }\n\n    private CompletableFuture<V> query(K key, Executor executor) {\n        CompletableFuture<V> future;\n        lock.lock();\n        try {\n            CompletableFuture<V> prev = pendings.get(key);\n            if (prev != null) {\n                return prev;\n            } else {\n                future = new CompletableFuture<>();\n                pendings.put(key, future);\n            }\n        } finally {\n            lock.unlock();\n        }\n\n        executor.execute(() -> {\n            V result;\n            try {\n                result = source.apply(key);\n            } catch (Throwable ex) {\n                lock.lock();\n                try {\n                    pendings.remove(key);\n                } finally {\n                    lock.unlock();\n                }\n                exceptionHandler.accept(key, ex);\n                future.completeExceptionally(ex);\n                return;\n            }\n\n            lock.lock();\n            try {\n                cache.put(key, result);\n                invalidated.remove(key);\n                pendings.remove(key, future);\n            } finally {\n                lock.unlock();\n            }\n            future.complete(result);\n            Platform.runLater(observable::invalidate);\n        });\n\n        return future;\n    }\n\n    public V get(K key) {\n        V cached;\n        lock.lock();\n        try {\n            cached = cache.get(key);\n            if (cached != null && !invalidated.containsKey(key)) {\n                return cached;\n            }\n        } finally {\n            lock.unlock();\n        }\n\n        try {\n            return query(key, Runnable::run).join();\n        } catch (CompletionException | CancellationException ignored) {\n        }\n\n        if (cached == null) {\n            return fallbackValue;\n        } else {\n            return cached;\n        }\n    }\n\n    public V getDirectly(K key) throws E {\n        V result = source.apply(key);\n        put(key, result);\n        return result;\n    }\n\n    public ObjectBinding<V> binding(K key) {\n        return binding(key, false);\n    }\n\n    /**\n     * @param quiet if true, calling get() on the returned binding won't toggle a query\n     */\n    public ObjectBinding<V> binding(K key, boolean quiet) {\n        // This method is thread-safe because ObservableHelper supports concurrent modification\n        return Bindings.createObjectBinding(() -> {\n            V result;\n            boolean refresh;\n\n            lock.lock();\n            try {\n                result = cache.get(key);\n                if (result == null) {\n                    result = fallbackValue;\n                    refresh = true;\n                } else {\n                    refresh = invalidated.containsKey(key);\n                }\n            } finally {\n                lock.unlock();\n            }\n            if (!quiet && refresh) {\n                query(key, executor);\n            }\n            return result;\n        }, observable);\n    }\n\n    public void invalidate(K key) {\n        lock.lock();\n        try {\n            if (cache.containsKey(key)) {\n                invalidated.put(key, Boolean.TRUE);\n            }\n        } finally {\n            lock.unlock();\n        }\n        Platform.runLater(observable::invalidate);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ObservableHelper.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\n\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Helper class for implementing {@link Observable}.\n *\n * @author yushijinhun\n */\npublic final class ObservableHelper implements Observable, InvalidationListener {\n\n    private final List<InvalidationListener> listeners = new CopyOnWriteArrayList<>();\n    private final Observable source;\n\n    public ObservableHelper() {\n        this.source = this;\n    }\n\n    public ObservableHelper(Observable source) {\n        this.source = source;\n    }\n\n    /**\n     * This method can be called from any thread.\n     */\n    @Override\n    public void addListener(InvalidationListener listener) {\n        listeners.add(listener);\n    }\n\n    /**\n     * This method can be called from any thread.\n     */\n    @Override\n    public void removeListener(InvalidationListener listener) {\n        listeners.remove(listener);\n    }\n\n    public void invalidate() {\n        listeners.forEach(it -> it.invalidated(source));\n    }\n\n    @Override\n    public void invalidated(Observable observable) {\n        this.invalidate();\n    }\n\n    public void receiveUpdatesFrom(Observable observable) {\n        observable.removeListener(this); // remove the previously added listener(if any)\n        observable.addListener(this);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ObservableOptionalCache.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport java.util.Optional;\nimport java.util.concurrent.Executor;\nimport java.util.function.BiConsumer;\n\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\n\nimport javafx.beans.binding.ObjectBinding;\n\n/**\n * @author yushijinhun\n */\npublic final class ObservableOptionalCache<K, V, E extends Exception> {\n\n    private final ObservableCache<K, Optional<V>, E> backed;\n\n    public ObservableOptionalCache(ExceptionalFunction<K, Optional<V>, E> source, BiConsumer<K, Throwable> exceptionHandler, Executor executor) {\n        backed = new ObservableCache<>(source, exceptionHandler, Optional.empty(), executor);\n    }\n\n    public Optional<V> getImmediately(K key) {\n        return backed.getImmediately(key).flatMap(it -> it);\n    }\n\n    public void put(K key, V value) {\n        backed.put(key, Optional.of(value));\n    }\n\n    public Optional<V> get(K key) {\n        return backed.get(key);\n    }\n\n    public Optional<V> getDirectly(K key) throws E {\n        return backed.getDirectly(key);\n    }\n\n    public ObjectBinding<Optional<V>> binding(K key) {\n        return backed.binding(key);\n    }\n\n    public ObjectBinding<Optional<V>> binding(K key, boolean quiet) {\n        return backed.binding(key, quiet);\n    }\n\n    public void invalidate(K key) {\n        backed.invalidate(key);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/PropertyUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport java.lang.reflect.Method;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.Observable;\nimport javafx.beans.property.Property;\nimport javafx.beans.value.WritableValue;\nimport javafx.collections.ObservableList;\nimport javafx.collections.ObservableMap;\nimport javafx.collections.ObservableSet;\n\npublic final class PropertyUtils {\n    private PropertyUtils() {\n    }\n\n    public static final class PropertyHandle {\n        public final WritableValue<Object> accessor;\n        public final Observable observable;\n\n        public PropertyHandle(WritableValue<Object> accessor, Observable observable) {\n            this.accessor = accessor;\n            this.observable = observable;\n        }\n    }\n\n    @SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n    public static Map<String, Function<Object, PropertyHandle>> getPropertyHandleFactories(Class<?> type) {\n        Map<String, Method> collectionGetMethods = new LinkedHashMap<>();\n        Map<String, Method> propertyMethods = new LinkedHashMap<>();\n        for (Method method : type.getMethods()) {\n            Class<?> returnType = method.getReturnType();\n            if (method.getParameterCount() == 0\n                    && !returnType.equals(void.class)) {\n                String name = method.getName();\n                if (name.endsWith(\"Property\")) {\n                    String propertyName = name.substring(0, name.length() - \"Property\".length());\n                    if (!propertyName.isEmpty() && Property.class.isAssignableFrom(returnType)) {\n                        propertyMethods.put(propertyName, method);\n                    }\n                } else if (name.startsWith(\"get\")) {\n                    String propertyName = name.substring(\"get\".length());\n                    if (!propertyName.isEmpty() &&\n                            (ObservableList.class.isAssignableFrom(returnType)\n                                    || ObservableSet.class.isAssignableFrom(returnType)\n                                    || ObservableMap.class.isAssignableFrom(returnType))) {\n                        propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);\n                        collectionGetMethods.put(propertyName, method);\n                    }\n                }\n            }\n        }\n        propertyMethods.keySet().forEach(collectionGetMethods::remove);\n\n        Map<String, Function<Object, PropertyHandle>> result = new LinkedHashMap<>();\n        propertyMethods.forEach((propertyName, method) -> {\n            result.put(propertyName, instance -> {\n                Property returnValue;\n                try {\n                    returnValue = (Property<?>) method.invoke(instance);\n                } catch (ReflectiveOperationException e) {\n                    throw new IllegalStateException(e);\n                }\n                return new PropertyHandle(returnValue, returnValue);\n            });\n        });\n\n        collectionGetMethods.forEach((propertyName, method) -> {\n            result.put(propertyName, instance -> {\n                Object returnValue;\n                try {\n                    returnValue = method.invoke(instance);\n                } catch (ReflectiveOperationException e) {\n                    throw new IllegalStateException(e);\n                }\n                WritableValue<Object> accessor;\n                if (returnValue instanceof ObservableList) {\n                    accessor = new WritableValue<Object>() {\n                        @Override\n                        public Object getValue() {\n                            return returnValue;\n                        }\n\n                        @Override\n                        public void setValue(Object value) {\n                            ((ObservableList) returnValue).setAll((List) value);\n                        }\n                    };\n                } else if (returnValue instanceof ObservableSet) {\n                    accessor = new WritableValue<Object>() {\n                        @Override\n                        public Object getValue() {\n                            return returnValue;\n                        }\n\n                        @Override\n                        public void setValue(Object value) {\n                            ObservableSet target = (ObservableSet) returnValue;\n                            target.clear();\n                            target.addAll((Set) value);\n                        }\n                    };\n                } else if (returnValue instanceof ObservableMap) {\n                    accessor = new WritableValue<Object>() {\n                        @Override\n                        public Object getValue() {\n                            return returnValue;\n                        }\n\n                        @Override\n                        public void setValue(Object value) {\n                            ObservableMap target = (ObservableMap) returnValue;\n                            target.clear();\n                            target.putAll((Map) value);\n                        }\n                    };\n                } else {\n                    throw new IllegalStateException();\n                }\n                return new PropertyHandle(accessor, (Observable) returnValue);\n            });\n        });\n        return result;\n    }\n\n    public static void copyProperties(Object from, Object to) {\n        Class<?> type = from.getClass();\n        while (!type.isInstance(to))\n            type = type.getSuperclass();\n\n        getPropertyHandleFactories(type)\n                .forEach((name, factory) -> {\n                    PropertyHandle src = factory.apply(from);\n                    PropertyHandle target = factory.apply(to);\n                    target.accessor.setValue(src.accessor.getValue());\n                });\n    }\n\n    public static void copyProperties(Object from, Object to, Predicate<String> predicate) {\n        Class<?> type = from.getClass();\n        while (!type.isInstance(to))\n            type = type.getSuperclass();\n\n        getPropertyHandleFactories(type)\n                .forEach((name, factory) -> {\n                    if (predicate.test(name)) {\n                        PropertyHandle src = factory.apply(from);\n                        PropertyHandle target = factory.apply(to);\n                        target.accessor.setValue(src.accessor.getValue());\n                    }\n                });\n    }\n\n    public static void attachListener(Object instance, InvalidationListener listener) {\n        getPropertyHandleFactories(instance.getClass())\n                .forEach((name, factory) -> factory.apply(instance).observable.addListener(listener));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReadWriteComposedProperty.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport java.util.function.Consumer;\n\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.beans.value.WeakChangeListener;\n\n/**\n * @author yushijinhun\n */\npublic final class ReadWriteComposedProperty<T> extends SimpleObjectProperty<T> {\n\n    @SuppressWarnings({\"unused\", \"FieldCanBeLocal\"})\n    private final ObservableValue<T> readSource;\n    private final Consumer<T> writeTarget;\n\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private ChangeListener<T> listener;\n\n    public ReadWriteComposedProperty(ObservableValue<T> readSource, Consumer<T> writeTarget) {\n        this(null, \"\", readSource, writeTarget);\n    }\n\n    public ReadWriteComposedProperty(Object bean, String name, ObservableValue<T> readSource, Consumer<T> writeTarget) {\n        super(bean, name);\n        this.readSource = readSource;\n        this.writeTarget = writeTarget;\n\n        this.listener = (observable, oldValue, newValue) -> {\n            if (!isBound()) {\n                set(newValue);\n            }\n        };\n        readSource.addListener(new WeakChangeListener<>(listener));\n        set(readSource.getValue());\n    }\n\n    @Override\n    protected void invalidated() {\n        writeTarget.accept(get());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SafeStringConverter.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\n\nimport javafx.util.StringConverter;\n\n/**\n * @author yushijinhun\n */\npublic final class SafeStringConverter<S extends T, T> extends StringConverter<T> {\n\n    public static SafeStringConverter<Integer, Number> fromInteger() {\n        return new SafeStringConverter<Integer, Number>(Integer::parseInt, NumberFormatException.class)\n                .fallbackTo(0);\n    }\n\n    public static SafeStringConverter<Double, Number> fromDouble() {\n        return new SafeStringConverter<Double, Number>(Double::parseDouble, NumberFormatException.class)\n                .fallbackTo(0.0);\n    }\n\n    public static SafeStringConverter<Double, Number> fromFiniteDouble() {\n        return new SafeStringConverter<Double, Number>(Double::parseDouble, NumberFormatException.class)\n                .restrict(Double::isFinite)\n                .fallbackTo(0.0);\n    }\n\n    private final ExceptionalFunction<String, S, ?> converter;\n    private final Class<?> malformedExceptionClass;\n    private S fallbackValue = null;\n    private final List<Predicate<S>> restrictions = new ArrayList<>();\n\n    public <E extends Exception> SafeStringConverter(ExceptionalFunction<String, S, E> converter, Class<E> malformedExceptionClass) {\n        this.converter = converter;\n        this.malformedExceptionClass = malformedExceptionClass;\n    }\n\n    @Override\n    public String toString(T object) {\n        return object == null ? \"\" : object.toString();\n    }\n\n    @Override\n    public S fromString(String string) {\n        return tryParse(string).orElse(fallbackValue);\n    }\n\n    private Optional<S> tryParse(String string) {\n        if (string == null) {\n            return Optional.empty();\n        }\n\n        S converted;\n        try {\n            converted = converter.apply(string);\n        } catch (Exception e) {\n            if (malformedExceptionClass.isInstance(e)) {\n                return Optional.empty();\n            }\n            if (e instanceof RuntimeException) {\n                throw (RuntimeException) e;\n            } else {\n                throw new RuntimeException(e);\n            }\n        }\n\n        if (!filter(converted)) {\n            return Optional.empty();\n        }\n\n        return Optional.of(converted);\n    }\n\n    private boolean filter(S value) {\n        for (Predicate<S> restriction : restrictions) {\n            if (!restriction.test(value)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public SafeStringConverter<S, T> fallbackTo(S fallbackValue) {\n        this.fallbackValue = fallbackValue;\n        return this;\n    }\n\n    public SafeStringConverter<S, T> restrict(Predicate<S> condition) {\n        this.restrictions.add(condition);\n        return this;\n    }\n\n    public Predicate<String> asPredicate() {\n        return string -> tryParse(string).isPresent();\n    }\n\n    public SafeStringConverter<S, T> asPredicate(Consumer<Predicate<String>> consumer) {\n        consumer.accept(asPredicate());\n        return this;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/logging/CallerFinder.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.logging;\n\nimport java.util.Optional;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\n\n/**\n * @author Glavo\n */\nfinal class CallerFinder {\n    private static final String PACKAGE_PREFIX = CallerFinder.class.getPackageName() + \".\";\n    private static final Predicate<StackWalker.StackFrame> PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX);\n    private static final Function<Stream<StackWalker.StackFrame>, Optional<StackWalker.StackFrame>> FUNCTION = stream -> stream.filter(PREDICATE).findFirst();\n    private static final Function<StackWalker.StackFrame, String> FRAME_MAPPING = frame -> frame.getClassName() + \".\" + frame.getMethodName();\n\n    static String getCaller() {\n        return StackWalker.getInstance().walk(FUNCTION).map(FRAME_MAPPING).orElse(null);\n    }\n\n    private CallerFinder() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/logging/LogEvent.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.logging;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.concurrent.CountDownLatch;\n\n/**\n * @author Glavo\n */\nsealed interface LogEvent {\n    record DoLog(long time,\n                 String caller,\n                 System.Logger.Level level,\n                 String message,\n                 Throwable exception\n    ) implements LogEvent {\n    }\n\n    final class ExportLog implements LogEvent {\n        final CountDownLatch latch = new CountDownLatch(1);\n\n        final OutputStream output;\n        IOException exception;\n\n        ExportLog(OutputStream output) {\n            this.output = output;\n        }\n\n        void await() throws InterruptedException {\n            latch.await();\n        }\n    }\n\n    final class Shutdown implements LogEvent {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/logging/Logger.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.logging;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.tukaani.xz.LZMA2Options;\nimport org.tukaani.xz.XZOutputStream;\n\nimport java.io.*;\nimport java.lang.System.Logger.Level;\nimport java.nio.file.*;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.format.DateTimeFormatter;\nimport java.util.*;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static java.nio.file.StandardOpenOption.CREATE_NEW;\n\n/**\n * @author Glavo\n */\npublic final class Logger {\n    public static final Logger LOG = new Logger();\n\n    private static volatile String[] accessTokens = new String[0];\n\n    public static synchronized void registerAccessToken(String token) {\n        if (token == null || token.length() <= 1)\n            return;\n\n        final String[] oldAccessTokens = accessTokens;\n        final String[] newAccessTokens = Arrays.copyOf(oldAccessTokens, oldAccessTokens.length + 1);\n\n        newAccessTokens[oldAccessTokens.length] = token;\n\n        accessTokens = newAccessTokens;\n    }\n\n    public static String filterForbiddenToken(String message) {\n        for (String token : accessTokens)\n            message = message.replace(token, \"<access token>\");\n        return message;\n    }\n\n    static final String PACKAGE_PREFIX = \"org.jackhuang.hmcl.\";\n    static final String CLASS_NAME = Logger.class.getName();\n\n    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(\"HH:mm:ss\").withZone(ZoneId.systemDefault());\n\n    private final BlockingQueue<LogEvent> queue = new LinkedBlockingQueue<>();\n    private final StringBuilder builder = new StringBuilder(512);\n\n    private Path logFile;\n    private ByteArrayOutputStream rawLogs;\n    private PrintWriter logWriter;\n\n    private Thread loggerThread;\n\n    private boolean shutdown = false;\n\n    private int logRetention = 0;\n\n    public void setLogRetention(int logRetention) {\n        this.logRetention = Math.max(0, logRetention);\n    }\n\n    private String format(LogEvent.DoLog event) {\n        StringBuilder builder = this.builder;\n        builder.setLength(0);\n        builder.append('[');\n        TIME_FORMATTER.formatTo(Instant.ofEpochMilli(event.time()), builder);\n        builder.append(\"] [\");\n\n        if (event.caller() != null && event.caller().startsWith(PACKAGE_PREFIX)) {\n            builder.append(\"@.\").append(event.caller(), PACKAGE_PREFIX.length(), event.caller().length());\n        } else {\n            builder.append(event.caller());\n        }\n\n        builder.append('/')\n                .append(event.level())\n                .append(\"] \")\n                .append(filterForbiddenToken(event.message()));\n        return builder.toString();\n    }\n\n    private void handle(LogEvent event) {\n        if (event instanceof LogEvent.DoLog doLog) {\n            String log = format(doLog);\n            Throwable exception = doLog.exception();\n\n            System.out.println(log);\n            if (exception != null)\n                exception.printStackTrace(System.out);\n\n            logWriter.println(log);\n            if (exception != null)\n                exception.printStackTrace(logWriter);\n        } else if (event instanceof LogEvent.ExportLog exportEvent) {\n            logWriter.flush();\n            try {\n                if (logFile != null) {\n                    Files.copy(logFile, exportEvent.output);\n                } else {\n                    rawLogs.writeTo(exportEvent.output);\n                }\n            } catch (IOException e) {\n                exportEvent.exception = e;\n            } finally {\n                exportEvent.latch.countDown();\n            }\n        } else if (event instanceof LogEvent.Shutdown) {\n            shutdown = true;\n        } else {\n            throw new AssertionError(\"Unknown event: \" + event);\n        }\n    }\n\n    private void onExit() {\n        shutdown();\n        try {\n            loggerThread.join();\n        } catch (InterruptedException ignored) {\n        }\n\n        String caller = CLASS_NAME + \".onExit\";\n\n        if (logRetention > 0 && logFile != null) {\n            var list = findRecentLogFiles(Integer.MAX_VALUE);\n            if (list.size() > logRetention) {\n                for (int i = 0, end = list.size() - logRetention; i < end; i++) {\n                    Path file = list.get(i);\n                    try {\n                        if (!Files.isSameFile(file, logFile)) {\n                            log(Level.INFO, caller, \"Delete old log file \" + file, null);\n                            Files.delete(file);\n                        }\n                    } catch (IOException e) {\n                        log(Level.WARNING, caller, \"Failed to delete log file \" + file, e);\n                    }\n                }\n            }\n        }\n\n        ArrayList<LogEvent> logs = new ArrayList<>();\n        queue.drainTo(logs);\n        for (LogEvent log : logs) {\n            handle(log);\n        }\n\n        if (logFile == null) {\n            return;\n        }\n\n        boolean failed = false;\n        Path xzFile = logFile.resolveSibling(logFile.getFileName() + \".xz\");\n        try (XZOutputStream output = new XZOutputStream(Files.newOutputStream(xzFile), new LZMA2Options())) {\n            logWriter.flush();\n            Files.copy(logFile, output);\n        } catch (IOException e) {\n            failed = true;\n            handle(new LogEvent.DoLog(System.currentTimeMillis(), caller, Level.WARNING, \"Failed to dump log file to xz format\", e));\n        } finally {\n            logWriter.close();\n        }\n\n        if (!failed)\n            try {\n                Files.delete(logFile);\n            } catch (IOException e) {\n                System.err.println(\"An exception occurred while deleting raw log file\");\n                e.printStackTrace(System.err);\n            }\n    }\n\n    public void start(Path logFolder) {\n        if (logFolder != null) {\n            String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH-mm-ss\"));\n            try {\n                Files.createDirectories(logFolder);\n                for (int n = 0; ; n++) {\n                    Path file = logFolder.resolve(time + (n == 0 ? \"\" : \".\" + n) + \".log\").toAbsolutePath().normalize();\n                    try {\n                        logWriter = new PrintWriter(Files.newBufferedWriter(file, UTF_8, CREATE_NEW));\n                        logFile = file;\n                        break;\n                    } catch (FileAlreadyExistsException ignored) {\n                    }\n                }\n            } catch (IOException e) {\n                log(Level.WARNING, CLASS_NAME + \".start\", \"Failed to create log file\", e);\n            }\n        }\n\n        if (logWriter == null) {\n            rawLogs = new ByteArrayOutputStream(256 * 1024);\n            logWriter = new PrintWriter(new OutputStreamWriter(rawLogs, UTF_8));\n        }\n\n        loggerThread = new Thread(() -> {\n            ArrayList<LogEvent> logs = new ArrayList<>();\n            try {\n                while (!shutdown) {\n                    if (queue.drainTo(logs) > 0) {\n                        for (LogEvent log : logs) {\n                            handle(log);\n                        }\n                        logs.clear();\n                    } else {\n                        logWriter.flush();\n                        handle(queue.take());\n                    }\n                }\n\n                while (queue.drainTo(logs) > 0) {\n                    for (LogEvent log : logs) {\n                        handle(log);\n                    }\n                    logs.clear();\n                }\n            } catch (InterruptedException e) {\n                throw new AssertionError(\"This thread cannot be interrupted\", e);\n            }\n        });\n        loggerThread.setName(\"HMCL Logger Thread\");\n        loggerThread.start();\n\n        Thread cleanerThread = new Thread(this::onExit);\n        cleanerThread.setName(\"HMCL Logger Shutdown Hook\");\n        Runtime.getRuntime().addShutdownHook(cleanerThread);\n    }\n\n    public void shutdown() {\n        queue.add(new LogEvent.Shutdown());\n    }\n\n    public Path getLogFile() {\n        return logFile;\n    }\n\n    public @NotNull List<Path> findRecentLogFiles(int n) {\n        if (n <= 0 || logFile == null)\n            return List.of();\n\n        var currentLogFile = LogFile.ofFile(logFile);\n\n        Path logDir = logFile.getParent();\n        if (logDir == null || !Files.isDirectory(logDir))\n            return List.of();\n\n        var logFiles = new ArrayList<LogFile>();\n        try (DirectoryStream<Path> stream = Files.newDirectoryStream(logDir)) {\n            for (Path path : stream) {\n                LogFile item = LogFile.ofFile(path);\n                if (item != null && (currentLogFile == null || item.compareTo(currentLogFile) < 0)) {\n                    logFiles.add(item);\n                }\n            }\n        } catch (IOException e) {\n            log(Level.WARNING, CLASS_NAME + \".findRecentLogFiles\", \"Failed to list log files in \" + logDir, e);\n            return List.of();\n        }\n        logFiles.sort(Comparator.naturalOrder());\n\n        final int resultLength = Math.min(n, logFiles.size());\n        final int offset = logFiles.size() - resultLength;\n\n        var result = new Path[resultLength];\n        for (int i = 0; i < resultLength; i++) {\n            result[i] = logFiles.get(i + offset).file;\n        }\n        return List.of(result);\n    }\n\n    public void exportLogs(OutputStream output) throws IOException {\n        Objects.requireNonNull(output);\n        LogEvent.ExportLog event = new LogEvent.ExportLog(output);\n        try {\n            queue.put(event);\n            event.await();\n        } catch (InterruptedException e) {\n            throw new AssertionError(\"This thread cannot be interrupted\", e);\n        }\n        if (event.exception != null) {\n            throw event.exception;\n        }\n    }\n\n    public String getLogs() {\n        ByteArrayOutputStream output = new ByteArrayOutputStream();\n        try {\n            exportLogs(output);\n            return output.toString(UTF_8);\n        } catch (IOException e) {\n            log(Level.WARNING, CLASS_NAME + \".getLogs\", \"Failed to export logs\", e);\n            return \"\";\n        }\n    }\n\n    private void log(Level level, String caller, String msg, Throwable exception) {\n        queue.add(new LogEvent.DoLog(System.currentTimeMillis(), caller, level, msg, exception));\n    }\n\n    public void log(Level level, String msg) {\n        log(level, CallerFinder.getCaller(), msg, null);\n    }\n\n    public void log(Level level, String msg, Throwable exception) {\n        log(level, CallerFinder.getCaller(), msg, exception);\n    }\n\n    public void error(String msg) {\n        log(Level.ERROR, CallerFinder.getCaller(), msg, null);\n    }\n\n    public void error(String msg, Throwable exception) {\n        log(Level.ERROR, CallerFinder.getCaller(), msg, exception);\n    }\n\n    public void warning(String msg) {\n        log(Level.WARNING, CallerFinder.getCaller(), msg, null);\n    }\n\n    public void warning(String msg, Throwable exception) {\n        log(Level.WARNING, CallerFinder.getCaller(), msg, exception);\n    }\n\n    public void info(String msg) {\n        log(Level.INFO, CallerFinder.getCaller(), msg, null);\n    }\n\n    public void info(String msg, Throwable exception) {\n        log(Level.INFO, CallerFinder.getCaller(), msg, exception);\n    }\n\n    public void debug(String msg) {\n        log(Level.DEBUG, CallerFinder.getCaller(), msg, null);\n    }\n\n    public void debug(String msg, Throwable exception) {\n        log(Level.DEBUG, CallerFinder.getCaller(), msg, exception);\n    }\n\n    public void trace(String msg) {\n        log(Level.TRACE, CallerFinder.getCaller(), msg, null);\n    }\n\n    public void trace(String msg, Throwable exception) {\n        log(Level.TRACE, CallerFinder.getCaller(), msg, exception);\n    }\n\n    private record LogFile(Path file,\n                           int year, int month, int day, int hour, int minute, int second,\n                           int n) implements Comparable<LogFile> {\n        private static final Pattern FILE_NAME_PATTERN = Pattern.compile(\"(?<year>\\\\d{4})-(?<month>\\\\d{2})-(?<day>\\\\d{2})T(?<hour>\\\\d{2})-(?<minute>\\\\d{2})-(?<second>\\\\d{2})(\\\\.(?<n>\\\\d+))?\\\\.log(\\\\.(gz|xz))?\");\n\n        private static @Nullable LogFile ofFile(Path file) {\n            if (!Files.isRegularFile(file))\n                return null;\n\n            Matcher matcher = FILE_NAME_PATTERN.matcher(file.getFileName().toString());\n            if (!matcher.matches())\n                return null;\n\n            int year = Integer.parseInt(matcher.group(\"year\"));\n            int month = Integer.parseInt(matcher.group(\"month\"));\n            int day = Integer.parseInt(matcher.group(\"day\"));\n            int hour = Integer.parseInt(matcher.group(\"hour\"));\n            int minute = Integer.parseInt(matcher.group(\"minute\"));\n            int second = Integer.parseInt(matcher.group(\"second\"));\n            int n = Optional.ofNullable(matcher.group(\"n\")).map(Integer::parseInt).orElse(0);\n\n            return new LogFile(file, year, month, day, hour, minute, second, n);\n        }\n\n        @Override\n        public int compareTo(@NotNull Logger.LogFile that) {\n            if (this.year != that.year) return Integer.compare(this.year, that.year);\n            if (this.month != that.month) return Integer.compare(this.month, that.month);\n            if (this.day != that.day) return Integer.compare(this.day, that.day);\n            if (this.hour != that.hour) return Integer.compare(this.hour, that.hour);\n            if (this.minute != that.minute) return Integer.compare(this.minute, that.minute);\n            if (this.second != that.second) return Integer.compare(this.second, that.second);\n            if (this.n != that.n) return Integer.compare(this.n, that.n);\n            return 0;\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(year, month, day, hour, minute, second, n);\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            return obj instanceof LogFile that && compareTo(that) == 0;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Architecture.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Locale;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.jackhuang.hmcl.util.platform.Bits.BIT_32;\nimport static org.jackhuang.hmcl.util.platform.Bits.BIT_64;\n\npublic enum Architecture {\n    X86(BIT_32, \"x86\"),\n    X86_64(BIT_64, \"x86-64\"),\n    IA32(BIT_32, \"IA-32\"),\n    IA64(BIT_64, \"IA-64\"),\n    SPARC(BIT_32),\n    SPARCV9(BIT_64, \"SPARC V9\"),\n    ARM32(BIT_32),\n    ARM64(BIT_64),\n    MIPS(BIT_32),\n    MIPS64(BIT_64),\n    MIPSEL(BIT_32, \"MIPSel\"),\n    MIPS64EL(BIT_64, \"MIPS64el\"),\n    PPC(BIT_32, \"PowerPC\"),\n    PPC64(BIT_64, \"PowerPC-64\"),\n    PPCLE(BIT_32, \"PowerPC (Little-Endian)\"),\n    PPC64LE(BIT_64, \"PowerPC-64 (Little-Endian)\"),\n    S390(BIT_32),\n    S390X(BIT_64, \"S390x\"),\n    RISCV32(BIT_32, \"RISC-V 32\"),\n    RISCV64(BIT_64, \"RISC-V 64\"),\n    LOONGARCH32(BIT_32, \"LoongArch32\"),\n    LOONGARCH64_OW(BIT_64, \"LoongArch64 (old world)\"),\n    LOONGARCH64(BIT_64, \"LoongArch64\"),\n    UNKNOWN(Bits.UNKNOWN, \"Unknown\");\n\n    private final String checkedName;\n    private final String displayName;\n    private final Bits bits;\n\n    Architecture(Bits bits) {\n        this.checkedName = this.toString().toLowerCase(Locale.ROOT);\n        this.displayName = this.toString();\n        this.bits = bits;\n    }\n\n    Architecture(Bits bits, String displayName) {\n        this.checkedName = this.toString().toLowerCase(Locale.ROOT);\n        this.displayName = displayName;\n        this.bits = bits;\n    }\n\n    public Bits getBits() {\n        return bits;\n    }\n\n    public String getCheckedName() {\n        return checkedName;\n    }\n\n    public String getDisplayName() {\n        return displayName;\n    }\n\n    public boolean isX86() {\n        return this == X86 || this == X86_64;\n    }\n\n    public static final Architecture CURRENT_ARCH;\n    public static final Architecture SYSTEM_ARCH;\n\n    public static Architecture parseArchName(String value) {\n        if (value == null) {\n            return UNKNOWN;\n        }\n        value = value.trim().toLowerCase(Locale.ROOT);\n\n        switch (value) {\n            case \"x8664\":\n            case \"x86-64\":\n            case \"x86_64\":\n            case \"amd64\":\n            case \"ia32e\":\n            case \"em64t\":\n            case \"x64\":\n            case \"intel64\":\n                return X86_64;\n            case \"x8632\":\n            case \"x86-32\":\n            case \"x86_32\":\n            case \"x86\":\n            case \"i86pc\":\n            case \"i386\":\n            case \"i486\":\n            case \"i586\":\n            case \"i686\":\n            case \"ia32\":\n            case \"x32\":\n                return X86;\n            case \"arm64\":\n            case \"aarch64\":\n                return ARM64;\n            case \"arm\":\n            case \"arm32\":\n                return ARM32;\n            case \"mips64\":\n                return MIPS64;\n            case \"mips64el\":\n                return MIPS64EL;\n            case \"mips\":\n            case \"mips32\":\n                return MIPS;\n            case \"mipsel\":\n            case \"mips32el\":\n                return MIPSEL;\n            case \"riscv\":\n            case \"risc-v\":\n            case \"riscv64\":\n                return RISCV64;\n            case \"ia64\":\n            case \"ia64w\":\n            case \"itanium64\":\n                return IA64;\n            case \"ia64n\":\n                return IA32;\n            case \"sparcv9\":\n            case \"sparc64\":\n                return SPARCV9;\n            case \"sparc\":\n            case \"sparc32\":\n                return SPARC;\n            case \"ppc64\":\n            case \"powerpc64\":\n                return \"little\".equals(System.getProperty(\"sun.cpu.endian\")) ? PPC64LE : PPC64;\n            case \"ppc64le\":\n            case \"powerpc64le\":\n                return PPC64LE;\n            case \"ppc\":\n            case \"ppc32\":\n            case \"powerpc\":\n            case \"powerpc32\":\n                return PPC;\n            case \"ppcle\":\n            case \"ppc32le\":\n            case \"powerpcle\":\n            case \"powerpc32le\":\n                return PPCLE;\n            case \"s390\":\n                return S390;\n            case \"s390x\":\n                return S390X;\n            case \"loongarch32\":\n                return LOONGARCH32;\n            case \"loongarch64\": {\n                if (VersionNumber.compare(System.getProperty(\"os.version\"), \"5.19\") < 0)\n                    return LOONGARCH64_OW;\n                return LOONGARCH64;\n            }\n            case \"loongarch64_ow\": {\n                return LOONGARCH64_OW;\n            }\n            default:\n                if (value.startsWith(\"armv7\")) {\n                    return ARM32;\n                }\n                if (value.startsWith(\"armv8\") || value.startsWith(\"armv9\")) {\n                    return ARM64;\n                }\n                return UNKNOWN;\n        }\n    }\n\n    static {\n        CURRENT_ARCH = parseArchName(System.getProperty(\"os.arch\"));\n\n        Architecture sysArch = null;\n        if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {\n            String processorIdentifier = System.getenv(\"PROCESSOR_IDENTIFIER\");\n            if (processorIdentifier != null) {\n                int idx = processorIdentifier.indexOf(' ');\n                if (idx > 0) {\n                    sysArch = parseArchName(processorIdentifier.substring(0, idx));\n                }\n            }\n        } else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {\n            if (CURRENT_ARCH == X86_64) {\n                try {\n                    Process process = Runtime.getRuntime().exec(new String[]{\"/usr/sbin/sysctl\", \"-n\", \"sysctl.proc_translated\"});\n                    if (process.waitFor(3, TimeUnit.SECONDS) && process.exitValue() == 0\n                            && \"1\".equals(IOUtils.readFullyAsString(process.getInputStream(), OperatingSystem.NATIVE_CHARSET).trim())) {\n                        sysArch = ARM64;\n                    }\n                } catch (Throwable e) {\n                    e.printStackTrace(System.err);\n                }\n            }\n        } else {\n            for (String uname : new String[]{\n                    \"/bin/uname\",\n                    \"/usr/bin/uname\"\n            }) {\n                if (Files.exists(Path.of(uname))) {\n                    try {\n                        Process process = Runtime.getRuntime().exec(new String[]{uname, \"-m\"});\n                        if (process.waitFor(3, TimeUnit.SECONDS) && process.exitValue() == 0) {\n                            sysArch = parseArchName(IOUtils.readFullyAsString(process.getInputStream(), OperatingSystem.NATIVE_CHARSET).trim());\n                        }\n                    } catch (Throwable e) {\n                        e.printStackTrace(System.err);\n                    }\n                    break;\n                }\n            }\n        }\n\n        SYSTEM_ARCH = sysArch == null || sysArch == UNKNOWN ? CURRENT_ARCH : sysArch;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Bits.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\n\nimport java.lang.reflect.Type;\n\n/**\n * The platform that indicates which the platform of operating system is, 64-bit or 32-bit.\n * Of course, 128-bit and 16-bit is not supported.\n *\n * @author huangyuhui\n */\n@JsonAdapter(Bits.Serializer.class)\npublic enum Bits {\n    BIT_32(\"32\"),\n    BIT_64(\"64\"),\n    UNKNOWN(\"unknown\");\n\n    private final String bit;\n\n    Bits(String bit) {\n        this.bit = bit;\n    }\n\n    public String getBit() {\n        return bit;\n    }\n\n    /**\n     * The json serializer to {@link Bits}.\n     */\n    public static class Serializer implements JsonSerializer<Bits>, JsonDeserializer<Bits> {\n        @Override\n        public JsonElement serialize(Bits t, Type type, JsonSerializationContext jsc) {\n            if (t == null)\n                return null;\n            else\n                switch (t) {\n                    case BIT_32:\n                        return new JsonPrimitive(0);\n                    case BIT_64:\n                        return new JsonPrimitive(1);\n                    default:\n                        return new JsonPrimitive(-1);\n                }\n        }\n\n        @Override\n        public Bits deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {\n            if (je == null)\n                return null;\n            else\n                switch (je.getAsInt()) {\n                    case 0:\n                        return BIT_32;\n                    case 1:\n                        return BIT_64;\n                    default:\n                        return UNKNOWN;\n                }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/CommandBuilder.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\nimport java.util.*;\nimport java.util.function.Predicate;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class CommandBuilder {\n    private static final Pattern UNSTABLE_OPTION_PATTERN = Pattern.compile(\"-XX:(?<key>[a-zA-Z0-9]+)=(?<value>.*)\");\n    private static final Pattern UNSTABLE_BOOLEAN_OPTION_PATTERN = Pattern.compile(\"-XX:(?<value>[+\\\\-])(?<key>[a-zA-Z0-9]+)\");\n\n    private final OperatingSystem os;\n    private final List<Item> raw = new ArrayList<>();\n\n    public CommandBuilder() {\n        this(OperatingSystem.CURRENT_OS);\n    }\n\n    public CommandBuilder(OperatingSystem os) {\n        this.os = os;\n    }\n\n    private String parse(String s) {\n        if (OperatingSystem.WINDOWS == os) {\n            return toBatchStringLiteral(s);\n        } else {\n            return toShellStringLiteral(s);\n        }\n    }\n\n    /**\n     * Parsing will ignore your manual escaping\n     *\n     * @param arg command\n     * @return this\n     */\n    public CommandBuilder add(String arg) {\n        raw.add(new Item(arg, true));\n        return this;\n    }\n\n    /**\n     * Parsing will ignore your manual escaping\n     *\n     * @param args commands\n     * @return this\n     */\n    public CommandBuilder addAll(String... args) {\n        for (String s : args)\n            raw.add(new Item(s, true));\n        return this;\n    }\n\n    public CommandBuilder addAll(Collection<String> args) {\n        for (String s : args)\n            raw.add(new Item(s, true));\n        return this;\n    }\n\n    public CommandBuilder addWithoutParsing(String... args) {\n        for (String s : args)\n            raw.add(new Item(s, false));\n        return this;\n    }\n\n    public CommandBuilder addAllWithoutParsing(Collection<String> args) {\n        for (String s : args)\n            raw.add(new Item(s, false));\n        return this;\n    }\n\n    public void addAllDefault(Collection<String> args) {\n        addAllDefault(args, true);\n    }\n\n    public void addAllDefaultWithoutParsing(Collection<String> args) {\n        addAllDefault(args, false);\n    }\n\n    private void addAllDefault(Collection<String> args, boolean parse) {\n        loop:\n        for (String arg : args) {\n            if (arg.startsWith(\"-D\")) {\n                int idx = arg.indexOf('=');\n                if (idx >= 0) {\n                    addDefault(arg.substring(0, idx + 1), arg.substring(idx + 1), parse);\n                } else {\n                    String opt = arg + \"=\";\n                    for (Item item : raw) {\n                        if (item.arg.startsWith(opt)) {\n                            LOG.info(\"Default option '\" + arg + \"' is suppressed by '\" + item.arg + \"'\");\n                            continue loop;\n                        } else if (item.arg.equals(arg)) {\n                            continue loop;\n                        }\n                    }\n                    raw.add(new Item(arg, parse));\n                }\n                continue;\n            }\n\n            if (arg.startsWith(\"-XX:\")) {\n                Matcher matcher = UNSTABLE_OPTION_PATTERN.matcher(arg);\n                if (matcher.matches()) {\n                    addUnstableDefault(matcher.group(\"key\"), matcher.group(\"value\"), parse);\n                    continue;\n                }\n\n                matcher = UNSTABLE_BOOLEAN_OPTION_PATTERN.matcher(arg);\n                if (matcher.matches()) {\n                    addUnstableDefault(matcher.group(\"key\"), \"+\".equals(matcher.group(\"value\")), parse);\n                    continue;\n                }\n            }\n\n            if (arg.startsWith(\"-X\")) {\n                String opt = null;\n                String value = null;\n\n                for (String prefix : new String[]{\"-Xmx\", \"-Xms\", \"-Xmn\", \"-Xss\"}) {\n                    if (arg.startsWith(prefix)) {\n                        opt = prefix;\n                        value = arg.substring(prefix.length());\n                        break;\n                    }\n                }\n\n                if (opt != null) {\n                    addDefault(opt, value, parse);\n                    continue;\n                }\n            }\n\n            for (Item item : raw) {\n                if (item.arg.equals(arg)) {\n                    continue loop;\n                }\n            }\n            raw.add(new Item(arg, parse));\n        }\n    }\n\n    public String addDefault(String opt, String value) {\n        return addDefault(opt, value, true);\n    }\n\n    private String addDefault(String opt, String value, boolean parse) {\n        for (Item item : raw) {\n            if (item.arg.startsWith(opt)) {\n                LOG.info(\"Default option '\" + opt + value + \"' is suppressed by '\" + item.arg + \"'\");\n                return item.arg;\n            }\n        }\n        raw.add(new Item(opt + value, parse));\n        return null;\n    }\n\n    public String addUnstableDefault(String opt, boolean value) {\n        return addUnstableDefault(opt, value, true);\n    }\n\n    private String addUnstableDefault(String opt, boolean value, boolean parse) {\n        for (Item item : raw) {\n            final Matcher matcher = UNSTABLE_BOOLEAN_OPTION_PATTERN.matcher(item.arg);\n            if (matcher.matches()) {\n                if (matcher.group(\"key\").equals(opt)) {\n                    return item.arg;\n                }\n            }\n        }\n\n        if (value) {\n            raw.add(new Item(\"-XX:+\" + opt, parse));\n        } else {\n            raw.add(new Item(\"-XX:-\" + opt, parse));\n        }\n        return null;\n    }\n\n    public String addUnstableDefault(String opt, String value) {\n        return addUnstableDefault(opt, value, true);\n    }\n\n    private String addUnstableDefault(String opt, String value, boolean parse) {\n        for (Item item : raw) {\n            final Matcher matcher = UNSTABLE_OPTION_PATTERN.matcher(item.arg);\n            if (matcher.matches()) {\n                if (matcher.group(\"key\").equals(opt)) {\n                    return item.arg;\n                }\n            }\n        }\n\n        raw.add(new Item(\"-XX:\" + opt + \"=\" + value, parse));\n        return null;\n    }\n\n    public boolean removeIf(Predicate<String> pred) {\n        return raw.removeIf(i -> pred.test(i.arg));\n    }\n\n    public boolean noneMatch(Predicate<String> predicate) {\n        return raw.stream().noneMatch(it -> predicate.test(it.arg));\n    }\n\n    @Override\n    public String toString() {\n        return raw.stream().map(i -> i.parse ? parse(i.arg) : i.arg).collect(Collectors.joining(\" \"));\n    }\n\n    public List<String> asList() {\n        return raw.stream().map(i -> i.arg).collect(Collectors.toList());\n    }\n\n    public List<String> asMutableList() {\n        return raw.stream().map(i -> i.arg).collect(Collectors.toCollection(ArrayList::new));\n    }\n\n    private static class Item {\n        final String arg;\n        final boolean parse;\n\n        Item(String arg, boolean parse) {\n            this.arg = arg;\n            this.parse = parse;\n        }\n\n        @Override\n        public String toString() {\n            return parse ? (OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS ? toBatchStringLiteral(arg) : toShellStringLiteral(arg)) : arg;\n        }\n    }\n\n    public static String pwshString(String str) {\n        return \"'\" + str.replace(\"'\", \"''\") + \"'\";\n    }\n\n    public static boolean hasExecutionPolicy() {\n        if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)\n            return true;\n        if (!OperatingSystem.isWindows7OrLater())\n            return false;\n\n        try {\n            String policy = SystemUtils.run(\"powershell.exe\", \"-NoProfile\", \"-Command\", \"Get-ExecutionPolicy\").trim();\n            return \"Unrestricted\".equalsIgnoreCase(policy) || \"RemoteSigned\".equalsIgnoreCase(policy);\n        } catch (Throwable ignored) {\n        }\n        return false;\n    }\n\n    public static boolean setExecutionPolicy() {\n        if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)\n            return true;\n        if (!OperatingSystem.isWindows7OrLater())\n            return false;\n\n        try {\n            SystemUtils.run(\"powershell.exe\", \"-NoProfile\", \"-Command\", \"Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\");\n            return true;\n        } catch (Throwable ignored) {\n            return false;\n        }\n    }\n\n    private static boolean containsEscape(String str, String escapeChars) {\n        for (int i = 0; i < escapeChars.length(); i++) {\n            if (str.indexOf(escapeChars.charAt(i)) >= 0)\n                return true;\n        }\n        return false;\n    }\n\n    private static String escape(String str, char... escapeChars) {\n        for (char ch : escapeChars) {\n            str = str.replace(\"\" + ch, \"\\\\\" + ch);\n        }\n        return str;\n    }\n\n    public static String toBatchStringLiteral(String s) {\n        return containsEscape(s, \" \\t\\\"^&<>|\") ? '\"' + escape(s, '\\\\', '\"') + '\"' : s;\n    }\n\n    public static String toShellStringLiteral(String s) {\n        return containsEscape(s, \" \\t\\\"!#$&'()*,;<=>?[\\\\]^`{|}~\") ? '\"' + escape(s, '\"', '$', '&', '`') + '\"' : s;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\nimport org.jackhuang.hmcl.launch.StreamPump;\nimport org.jackhuang.hmcl.util.Lang;\n\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\n/// The managed process.\n///\n/// @author huangyuhui\n/// <!-- @see org.jackhuang.hmcl.launch.ExitWaiter -->\n/// @see org.jackhuang.hmcl.launch.StreamPump\npublic final class ManagedProcess {\n    private final ReentrantLock lock = new ReentrantLock();\n    private final Process process;\n    private final List<String> commands;\n    private final String classpath;\n    private final Map<String, Object> properties = new HashMap<>();\n    private final List<String> lines = new ArrayList<>();\n    private final List<Thread> relatedThreads = new ArrayList<>();\n\n    public ManagedProcess(ProcessBuilder processBuilder) throws IOException {\n        this.process = processBuilder.start();\n        this.commands = processBuilder.command();\n        this.classpath = null;\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param process  the raw system process that this instance manages.\n     * @param commands the command line of {@code process}.\n     */\n    public ManagedProcess(Process process, List<String> commands) {\n        this.process = process;\n        this.commands = List.copyOf(commands);\n        this.classpath = null;\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param process   the raw system process that this instance manages.\n     * @param commands  the command line of {@code process}.\n     * @param classpath the classpath of java process\n     */\n    public ManagedProcess(Process process, List<String> commands, String classpath) {\n        this.process = process;\n        this.commands = List.copyOf(commands);\n        this.classpath = classpath;\n    }\n\n    /**\n     * The raw system process that this instance manages.\n     *\n     * @return process\n     */\n    public Process getProcess() {\n        return process;\n    }\n\n    /**\n     * The command line.\n     *\n     * @return the list of each part of command line separated by spaces.\n     */\n    public List<String> getCommands() {\n        return commands;\n    }\n\n    /**\n     * The classpath.\n     *\n     * @return classpath\n     */\n    public String getClasspath() {\n        return classpath;\n    }\n\n    /**\n     * To save some information you need.\n     */\n    public Map<String, Object> getProperties() {\n        return properties;\n    }\n\n    /**\n     * The (unmodifiable) standard output/error lines.\n     * If you want to add lines, use {@link #addLine}\n     *\n     * @see #addLine\n     */\n    public List<String> getLines(Predicate<String> lineFilter) {\n        lock.lock();\n        try {\n            if (lineFilter == null)\n                return List.copyOf(lines);\n\n            ArrayList<String> res = new ArrayList<>();\n            for (String line : this.lines) {\n                if (lineFilter.test(line))\n                    res.add(line);\n            }\n            return Collections.unmodifiableList(res);\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    public void addLine(String line) {\n        lock.lock();\n        try {\n            lines.add(line);\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Add related thread.\n     * <p>\n     * If a thread is monitoring this raw process,\n     * you are required to add the instance by this method.\n     */\n    public void addRelatedThread(Thread thread) {\n        lock.lock();\n        try {\n            relatedThreads.add(thread);\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    public void pumpInputStream(Consumer<String> onLogLine) {\n        addRelatedThread(Lang.thread(new StreamPump(process.getInputStream(), onLogLine, OperatingSystem.NATIVE_CHARSET), \"ProcessInputStreamPump\", true));\n    }\n\n    public void pumpErrorStream(Consumer<String> onLogLine) {\n        addRelatedThread(Lang.thread(new StreamPump(process.getErrorStream(), onLogLine, OperatingSystem.NATIVE_CHARSET), \"ProcessErrorStreamPump\", true));\n    }\n\n    /**\n     * True if the managed process is running.\n     */\n    public boolean isRunning() {\n        try {\n            process.exitValue();\n            return false;\n        } catch (IllegalThreadStateException e) {\n            return true;\n        }\n    }\n\n    /**\n     * The exit code of raw process.\n     */\n    public int getExitCode() {\n        return process.exitValue();\n    }\n\n    /**\n     * Destroys the raw process and other related threads that are monitoring this raw process.\n     */\n    public void stop() {\n        process.destroy();\n        destroyRelatedThreads();\n    }\n\n    public void destroyRelatedThreads() {\n        lock.lock();\n        try {\n            relatedThreads.forEach(Thread::interrupt);\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"ManagedProcess[commands=\" + commands + \", isRunning=\" + isRunning() + \"]\";\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/NativeUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\nimport com.sun.jna.Library;\nimport com.sun.jna.Native;\nimport com.sun.jna.Platform;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Collections;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class NativeUtils {\n    public static final boolean USE_JNA = useJNA();\n\n    public static <T extends Library> @Nullable T load(String name, Class<T> interfaceClass) {\n        return load(name, interfaceClass, Collections.emptyMap());\n    }\n\n    public static <T extends Library> @Nullable T load(String name, Class<T> interfaceClass, Map<String, ?> options) {\n        if (USE_JNA) {\n            try {\n                return Native.load(name, interfaceClass, options);\n            } catch (UnsatisfiedLinkError e) {\n                LOG.warning(\"Failed to load native library: \" + name, e);\n            }\n        }\n\n        return null;\n    }\n\n    private static boolean useJNA() {\n        String backend = System.getProperty(\"hmcl.native.backend\");\n        if (backend == null || \"auto\".equalsIgnoreCase(backend)) {\n            try {\n                if (Platform.isWindows()) {\n                    String osVersion = System.getProperty(\"os.version\");\n\n                    // Requires Windows 7 or later (6.1+)\n                    // https://learn.microsoft.com/windows/win32/sysinfo/operating-system-version\n                    if (osVersion == null || osVersion.startsWith(\"5.\") || osVersion.equals(\"6.0\"))\n                        return false;\n\n                    // Currently we only need to use JNA on Windows\n                    Native.getDefaultStringEncoding();\n                    return true;\n                }\n\n                return false;\n            } catch (Throwable ignored) {\n                return false;\n            }\n        } else if (\"jna\".equalsIgnoreCase(backend)) {\n            // Ensure JNA is available\n            Native.getDefaultStringEncoding();\n            return true;\n        } else if (\"none\".equalsIgnoreCase(backend))\n            return false;\n        else\n            throw new Error(\"Unsupported native backend: \" + backend);\n    }\n\n    private NativeUtils() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OSVersion.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Objects;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/// @author Glavo\npublic sealed interface OSVersion {\n    OSVersion.Windows WINDOWS_2000 = new Windows(5, 0);\n    OSVersion.Windows WINDOWS_XP = new Windows(5, 1);\n    OSVersion.Windows WINDOWS_VISTA = new Windows(6, 0);\n    OSVersion.Windows WINDOWS_7 = new Windows(6, 1);\n    OSVersion.Windows WINDOWS_8 = new Windows(6, 2);\n    OSVersion.Windows WINDOWS_8_1 = new Windows(6, 3);\n    OSVersion.Windows WINDOWS_10 = new Windows(10, 0);\n    OSVersion.Windows WINDOWS_11 = new Windows(10, 0, 22000);\n\n    static OSVersion of(OperatingSystem os, String version) {\n        if (Objects.requireNonNull(os) == OperatingSystem.WINDOWS) {\n            return OSVersion.Windows.parse(version);\n        } else {\n            return new Generic(os, VersionNumber.asVersion(version));\n        }\n    }\n\n    @NotNull OperatingSystem getOperatingSystem();\n\n    @NotNull String getVersion();\n\n    /// Returns `true` if the current version and `otherVersion` have the same [operating system][#getOperatingSystem()]\n    /// and the version is not lower than `otherVersion`; otherwise returns `false`.\n    ///\n    /// For example, if you want to check that the system is Windows and the version is at least Windows 7, you can do this:\n    ///\n    /// ```java\n    /// version.isAtLeast(OSVersion.WINDOWS_7)\n    /// ```\n    boolean isAtLeast(@NotNull OSVersion otherVersion);\n\n    record Windows(int major, int minor, int build, int revision,\n                   String version) implements OSVersion, Comparable<Windows> {\n        private static String toVersion(int major, int minor, int build, int revision) {\n            StringBuilder builder = new StringBuilder();\n            builder.append(major).append('.').append(minor);\n            if (build > 0 || revision > 0) {\n                builder.append('.').append(build);\n                if (revision > 0) {\n                    builder.append('.').append(revision);\n                }\n            }\n            return builder.toString();\n        }\n\n        public static OSVersion.Windows parse(String version) {\n            Matcher matcher = Pattern.compile(\"^(?<major>\\\\d+)\\\\.(?<minor>\\\\d+)(\\\\.(?<build>\\\\d+)(\\\\.(?<revision>\\\\d+))?)?\")\n                    .matcher(version);\n            if (matcher.find()) {\n                return new Windows(\n                        Integer.parseInt(matcher.group(\"major\")),\n                        Integer.parseInt(matcher.group(\"minor\")),\n                        matcher.group(\"build\") != null ? Integer.parseInt(matcher.group(\"build\")) : 0,\n                        matcher.group(\"revision\") != null ? Integer.parseInt(matcher.group(\"revision\")) : 0,\n                        version\n                );\n            } else {\n                return new OSVersion.Windows(0, 0, 0, 0, version);\n            }\n        }\n\n        public Windows(int major, int minor) {\n            this(major, minor, 0);\n        }\n\n        public Windows(int major, int minor, int build) {\n            this(major, minor, build, 0);\n        }\n\n        public Windows(int major, int minor, int build, int revision) {\n            this(major, minor, build, revision, toVersion(major, minor, build, revision));\n        }\n\n        @Override\n        public @NotNull OperatingSystem getOperatingSystem() {\n            return OperatingSystem.WINDOWS;\n        }\n\n        @Override\n        public @NotNull String getVersion() {\n            return version;\n        }\n\n        @Override\n        public boolean isAtLeast(@NotNull OSVersion otherVersion) {\n            return this == otherVersion || otherVersion instanceof Windows other && this.compareTo(other) >= 0;\n        }\n\n        @Override\n        public int compareTo(@NotNull OSVersion.Windows that) {\n            if (this.major != that.major)\n                return Integer.compare(this.major, that.major);\n            if (this.minor != that.minor)\n                return Integer.compare(this.minor, that.minor);\n            if (this.build != that.build)\n                return Integer.compare(this.build, that.build);\n            return Integer.compare(this.revision, that.revision);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(major, minor, build, revision);\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            return this == o || o instanceof Windows that\n                    && this.major == that.major\n                    && this.minor == that.minor\n                    && this.build == that.build\n                    && this.revision == that.revision;\n        }\n\n        @Override\n        public @NotNull String toString() {\n            return version;\n        }\n    }\n\n    /// Generic implementation of [OSVersion].\n    ///\n    /// Note: For Windows version numbers, please use [Windows].\n    ///\n    /// @author Glavo\n    record Generic(@NotNull OperatingSystem os, @NotNull VersionNumber version) implements OSVersion {\n        public Generic {\n            Objects.requireNonNull(os);\n            if (os == OperatingSystem.WINDOWS) {\n                throw new IllegalArgumentException(\"Please use the \" + Windows.class.getName());\n            }\n        }\n\n        @Override\n        public @NotNull OperatingSystem getOperatingSystem() {\n            return os;\n        }\n\n        @Override\n        public @NotNull String getVersion() {\n            return version.toString();\n        }\n\n        @Override\n        public boolean isAtLeast(@NotNull OSVersion otherVersion) {\n            return this == otherVersion || otherVersion instanceof Generic that\n                    && this.os == that.os\n                    && this.version.compareTo(that.version) >= 0;\n        }\n\n        @Override\n        public @NotNull String toString() {\n            return getVersion();\n        }\n    }\n}\n\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\nimport org.jackhuang.hmcl.util.KeyValuePairUtils;\nimport org.jackhuang.hmcl.util.platform.windows.Kernel32;\nimport org.jackhuang.hmcl.util.platform.windows.WinReg;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.charset.UnsupportedCharsetException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Represents the operating system.\n *\n * @author huangyuhui\n */\npublic enum OperatingSystem {\n    /**\n     * Microsoft Windows.\n     */\n    WINDOWS(\"windows\"),\n    /**\n     * Linux and Unix like OS, including Solaris.\n     */\n    LINUX(\"linux\"),\n    /**\n     * macOS.\n     */\n    MACOS(\"macos\", \"osx\"),\n    /**\n     * FreeBSD.\n     */\n    FREEBSD(\"freebsd\", \"linux\"),\n    /**\n     * Unknown operating system.\n     */\n    UNKNOWN(\"universal\");\n\n    private final String checkedName;\n    private final String mojangName;\n\n    OperatingSystem(String checkedName) {\n        this.checkedName = checkedName;\n        this.mojangName = checkedName;\n    }\n\n    OperatingSystem(String checkedName, String mojangName) {\n        this.checkedName = checkedName;\n        this.mojangName = mojangName;\n    }\n\n    public String getCheckedName() {\n        return checkedName;\n    }\n\n    public String getMojangName() {\n        return mojangName;\n    }\n\n    public boolean isLinuxOrBSD() {\n        return this == LINUX || this == FREEBSD;\n    }\n\n    public String getJavaExecutable() {\n        return this == WINDOWS ? \"java.exe\" : \"java\";\n    }\n\n    /**\n     * The current operating system.\n     */\n    public static final OperatingSystem CURRENT_OS = parseOSName(System.getProperty(\"os.name\"));\n\n    /**\n     * The system default charset.\n     */\n    public static final Charset NATIVE_CHARSET;\n\n    /**\n     * Windows system build number.\n     * When the version number is not recognized or on another system, the value will be -1.\n     */\n    public static final int SYSTEM_BUILD_NUMBER;\n\n    /**\n     * The name of current operating system.\n     */\n    public static final String SYSTEM_NAME;\n\n    /// The version of current operating system.\n    ///\n    /// If [#CURRENT_OS] is [#WINDOWS], then [#SYSTEM_VERSION] must be an instance of [OSVersion.Windows].\n    public static final OSVersion SYSTEM_VERSION;\n\n    public static final String OS_RELEASE_NAME;\n    public static final String OS_RELEASE_PRETTY_NAME;\n\n    public static final int CODE_PAGE;\n\n    static {\n        String nativeEncoding = System.getProperty(\"native.encoding\");\n        String hmclNativeEncoding = System.getProperty(\"hmcl.native.encoding\");\n        Charset nativeCharset = Charset.defaultCharset();\n\n        try {\n            if (hmclNativeEncoding != null) {\n                nativeCharset = Charset.forName(hmclNativeEncoding);\n            } else {\n                if (nativeEncoding != null && !nativeEncoding.equalsIgnoreCase(nativeCharset.name())) {\n                    nativeCharset = Charset.forName(nativeEncoding);\n                }\n\n                if (nativeCharset == StandardCharsets.UTF_8 || nativeCharset == StandardCharsets.US_ASCII) {\n                    nativeCharset = StandardCharsets.UTF_8;\n                } else if (\"GBK\".equalsIgnoreCase(nativeCharset.name()) || \"GB2312\".equalsIgnoreCase(nativeCharset.name())) {\n                    nativeCharset = Charset.forName(\"GB18030\");\n                }\n            }\n        } catch (UnsupportedCharsetException e) {\n            e.printStackTrace(System.err);\n        }\n        NATIVE_CHARSET = nativeCharset;\n\n        if (CURRENT_OS == WINDOWS) {\n            int codePage = -1;\n            OSVersion.Windows windowsVersion = null;\n\n            Kernel32 kernel32 = Kernel32.INSTANCE;\n            WinReg reg = WinReg.INSTANCE;\n\n            // Get Windows version number\n            if (reg != null) {\n                var baseVersion = OSVersion.Windows.parse(System.getProperty(\"os.version\"));\n                int majorVersion = baseVersion.major();\n                int minorVersion = baseVersion.minor();\n                int buildNumber = baseVersion.build();\n                int revision = baseVersion.revision();\n\n                Object currentBuild = reg.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE,\n                        \"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\", \"CurrentBuild\");\n                if (currentBuild instanceof String currentBuildStr) {\n                    try {\n                        buildNumber = Integer.parseInt(currentBuildStr);\n                    } catch (NumberFormatException e) {\n                        System.err.println(\"Invalid Windows build number: \" + currentBuildStr);\n                    }\n                }\n\n                if (majorVersion >= 10) {\n                    Object ubr = reg.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE,\n                            \"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\", \"UBR\");\n\n                    if (ubr instanceof Integer ubrValue)\n                        revision = ubrValue;\n                }\n\n                windowsVersion = new OSVersion.Windows(majorVersion, minorVersion, buildNumber, revision);\n            }\n\n            if (windowsVersion == null) {\n                try {\n                    Process process = Runtime.getRuntime().exec(new String[]{\"cmd\", \"ver\"});\n                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), NATIVE_CHARSET))) {\n                        Matcher matcher = Pattern.compile(\"(?<version>\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+?)]$\")\n                                .matcher(reader.readLine().trim());\n                        if (matcher.find())\n                            windowsVersion = OSVersion.Windows.parse(matcher.group(\"version\"));\n                    }\n                    process.destroy();\n                } catch (Throwable ignored) {\n                }\n            }\n\n            if (windowsVersion == null)\n                windowsVersion = OSVersion.Windows.parse(System.getProperty(\"os.version\"));\n\n            // Get Code Page\n\n            if (kernel32 != null)\n                codePage = kernel32.GetACP();\n            else {\n                try {\n                    Process process = Runtime.getRuntime().exec(new String[]{\"chcp.com\"});\n                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), NATIVE_CHARSET))) {\n                        Matcher matcher = Pattern.compile(\"(?<cp>[0-9]+)$\")\n                                .matcher(reader.readLine().trim());\n\n                        if (matcher.find())\n                            codePage = Integer.parseInt(matcher.group(\"cp\"));\n                    }\n                    process.destroy();\n                } catch (Throwable ignored) {\n                }\n            }\n\n            String osName = System.getProperty(\"os.name\");\n\n            // Java 17 or earlier recognizes Windows 11 as Windows 10\n            if (osName.equals(\"Windows 10\") && windowsVersion.isAtLeast(OSVersion.WINDOWS_11))\n                osName = \"Windows 11\";\n\n            if (windowsVersion.isAtLeast(OSVersion.WINDOWS_10) && reg != null) {\n                Object displayVersion = reg.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE,\n                        \"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\", \"DisplayVersion\");\n\n                if (displayVersion instanceof String displayVersionStr\n                        && displayVersionStr.matches(\"\\\\d{2}H\\\\d\")) {\n                    osName = osName + \" \" + displayVersionStr;\n                }\n            }\n\n            SYSTEM_NAME = osName;\n            SYSTEM_VERSION = windowsVersion;\n            SYSTEM_BUILD_NUMBER = windowsVersion.build();\n            CODE_PAGE = codePage;\n        } else {\n            SYSTEM_NAME = System.getProperty(\"os.name\");\n            SYSTEM_VERSION = OSVersion.of(CURRENT_OS, System.getProperty(\"os.version\"));\n            SYSTEM_BUILD_NUMBER = -1;\n            CODE_PAGE = -1;\n        }\n\n        Map<String, String> osRelease = Collections.emptyMap();\n        if (CURRENT_OS == LINUX || CURRENT_OS == FREEBSD) {\n            Path osReleaseFile = Paths.get(\"/etc/os-release\");\n            if (Files.exists(osReleaseFile)) {\n                try {\n                    osRelease = KeyValuePairUtils.loadProperties(osReleaseFile);\n                } catch (IOException e) {\n                    e.printStackTrace(System.err);\n                }\n            }\n        }\n        OS_RELEASE_NAME = osRelease.get(\"NAME\");\n        OS_RELEASE_PRETTY_NAME = osRelease.get(\"PRETTY_NAME\");\n    }\n\n    public static OperatingSystem parseOSName(String name) {\n        if (name == null) {\n            return UNKNOWN;\n        }\n\n        name = name.trim().toLowerCase(Locale.ROOT);\n\n        if (name.contains(\"mac\") || name.contains(\"darwin\") || name.contains(\"osx\"))\n            return MACOS;\n        else if (name.contains(\"win\"))\n            return WINDOWS;\n        else if (name.contains(\"solaris\") || name.contains(\"linux\") || name.contains(\"unix\") || name.contains(\"sunos\"))\n            return LINUX;\n        else if (name.equals(\"freebsd\"))\n            return FREEBSD;\n        else\n            return UNKNOWN;\n    }\n\n    public static boolean isWindows7OrLater() {\n        return SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_7);\n    }\n\n    public static Path getWorkingDirectory(String folder) {\n        String home = System.getProperty(\"user.home\", \".\");\n        switch (OperatingSystem.CURRENT_OS) {\n            case LINUX:\n            case FREEBSD:\n                return Paths.get(home, \".\" + folder).toAbsolutePath();\n            case WINDOWS:\n                String appdata = System.getenv(\"APPDATA\");\n                return Paths.get(appdata == null ? home : appdata, \".\" + folder).toAbsolutePath();\n            case MACOS:\n                return Paths.get(home, \"Library\", \"Application Support\", folder).toAbsolutePath();\n            default:\n                return Paths.get(home, folder).toAbsolutePath();\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Platform.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\npublic record Platform(OperatingSystem os, Architecture arch) {\n    public static final Platform UNKNOWN = new Platform(OperatingSystem.UNKNOWN, Architecture.UNKNOWN);\n\n    public static final Platform WINDOWS_X86 = new Platform(OperatingSystem.WINDOWS, Architecture.X86);\n    public static final Platform WINDOWS_X86_64 = new Platform(OperatingSystem.WINDOWS, Architecture.X86_64);\n    public static final Platform WINDOWS_ARM64 = new Platform(OperatingSystem.WINDOWS, Architecture.ARM64);\n\n    public static final Platform LINUX_X86 = new Platform(OperatingSystem.LINUX, Architecture.X86);\n    public static final Platform LINUX_X86_64 = new Platform(OperatingSystem.LINUX, Architecture.X86_64);\n    public static final Platform LINUX_ARM64 = new Platform(OperatingSystem.LINUX, Architecture.ARM64);\n    public static final Platform LINUX_ARM32 = new Platform(OperatingSystem.LINUX, Architecture.ARM32);\n    public static final Platform LINUX_RISCV64 = new Platform(OperatingSystem.LINUX, Architecture.RISCV64);\n    public static final Platform LINUX_LOONGARCH64 = new Platform(OperatingSystem.LINUX, Architecture.LOONGARCH64);\n    public static final Platform LINUX_LOONGARCH64_OW = new Platform(OperatingSystem.LINUX, Architecture.LOONGARCH64_OW);\n    public static final Platform LINUX_MIPS64EL = new Platform(OperatingSystem.LINUX, Architecture.MIPS64EL);\n\n    public static final Platform MACOS_X86_64 = new Platform(OperatingSystem.MACOS, Architecture.X86_64);\n    public static final Platform MACOS_ARM64 = new Platform(OperatingSystem.MACOS, Architecture.ARM64);\n\n    public static final Platform FREEBSD_X86_64 = new Platform(OperatingSystem.FREEBSD, Architecture.X86_64);\n\n    public static final Platform CURRENT_PLATFORM = Platform.getPlatform(OperatingSystem.CURRENT_OS, Architecture.CURRENT_ARCH);\n    public static final Platform SYSTEM_PLATFORM = Platform.getPlatform(OperatingSystem.CURRENT_OS, Architecture.SYSTEM_ARCH);\n\n    public static boolean isCompatibleWithX86Java() {\n        return Architecture.SYSTEM_ARCH.isX86() || SYSTEM_PLATFORM.equals(MACOS_ARM64) || SYSTEM_PLATFORM.equals(WINDOWS_ARM64);\n    }\n\n    public static Platform getPlatform(OperatingSystem os, Architecture arch) {\n        if (os == OperatingSystem.UNKNOWN && arch == Architecture.UNKNOWN) {\n            return UNKNOWN;\n        }\n\n        if (arch == Architecture.X86_64) {\n            switch (os) {\n                case WINDOWS:\n                    return WINDOWS_X86_64;\n                case MACOS:\n                    return MACOS_X86_64;\n                case LINUX:\n                    return LINUX_X86_64;\n            }\n        } else if (arch == Architecture.ARM64) {\n            switch (os) {\n                case WINDOWS:\n                    return WINDOWS_ARM64;\n                case MACOS:\n                    return MACOS_ARM64;\n                case LINUX:\n                    return LINUX_ARM64;\n            }\n        }\n\n        return new Platform(os, arch);\n    }\n\n    public OperatingSystem getOperatingSystem() {\n        return os;\n    }\n\n    public Architecture getArchitecture() {\n        return arch;\n    }\n\n    public Bits getBits() {\n        return arch.getBits();\n    }\n\n    public boolean equals(OperatingSystem os, Architecture arch) {\n        return this.os == os && this.arch == arch;\n    }\n\n    @Override\n    public String toString() {\n        return os.getCheckedName() + \"-\" + arch.getCheckedName();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemInfo.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\nimport org.jackhuang.hmcl.util.DataSizeUnit;\nimport org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;\nimport org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;\nimport org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;\nimport org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;\nimport org.jackhuang.hmcl.util.platform.linux.LinuxHardwareDetector;\nimport org.jackhuang.hmcl.util.platform.macos.MacOSHardwareDetector;\nimport org.jackhuang.hmcl.util.platform.windows.*;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class SystemInfo {\n\n    private static final class Holder {\n        public static final HardwareDetector DETECTOR;\n\n        static {\n            if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)\n                DETECTOR = new WindowsHardwareDetector();\n            else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX)\n                DETECTOR = new LinuxHardwareDetector();\n            else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)\n                DETECTOR = new MacOSHardwareDetector();\n            else\n                DETECTOR = new HardwareDetector();\n        }\n\n        public static final long TOTAL_MEMORY = DETECTOR.getTotalMemorySize();\n        public static final @Nullable CentralProcessor CENTRAL_PROCESSOR = DETECTOR.detectCentralProcessor();\n        public static final @Nullable List<GraphicsCard> GRAPHICS_CARDS = DETECTOR.detectGraphicsCards();\n    }\n\n    public static void initialize() {\n        StringBuilder builder = new StringBuilder(\"System Info:\");\n\n        // CPU\n        CentralProcessor cpu = getCentralProcessor();\n        if (cpu != null)\n            builder.append(\"\\n - CPU: \").append(cpu);\n\n        // Graphics Card\n        List<GraphicsCard> graphicsCards = getGraphicsCards();\n        if (graphicsCards != null) {\n            if (graphicsCards.isEmpty())\n                builder.append(\"\\n - GPU: Not Found\");\n            else if (graphicsCards.size() == 1)\n                builder.append(\"\\n - GPU: \").append(graphicsCards.get(0));\n            else {\n                int index = 1;\n                for (GraphicsCard graphicsCard : graphicsCards) {\n                    builder.append(\"\\n - GPU \").append(index++).append(\": \").append(graphicsCard);\n                }\n            }\n        }\n\n        // Memory\n        long totalMemorySize = getTotalMemorySize();\n        long usedMemorySize = getUsedMemorySize();\n\n        builder.append(\"\\n - Memory: \")\n                .append(DataSizeUnit.format(usedMemorySize))\n                .append(\" / \")\n                .append(DataSizeUnit.format(totalMemorySize));\n\n        if (totalMemorySize > 0 && usedMemorySize > 0)\n            builder.append(\" (\").append((int) (((double) usedMemorySize / totalMemorySize) * 100)).append(\"%)\");\n\n        LOG.info(builder.toString());\n    }\n\n    public static PhysicalMemoryStatus getPhysicalMemoryStatus() {\n        long totalMemorySize = getTotalMemorySize();\n        long freeMemorySize = getFreeMemorySize();\n\n        return totalMemorySize > 0 && freeMemorySize >= 0\n                ? new PhysicalMemoryStatus(totalMemorySize, freeMemorySize)\n                : PhysicalMemoryStatus.INVALID;\n    }\n\n    public static long getTotalMemorySize() {\n        return Holder.TOTAL_MEMORY;\n    }\n\n    public static long getFreeMemorySize() {\n        return Holder.DETECTOR.getFreeMemorySize();\n    }\n\n    public static long getUsedMemorySize() {\n        long totalMemorySize = getTotalMemorySize();\n        if (totalMemorySize <= 0)\n            return 0;\n\n        return Long.max(0, totalMemorySize - getFreeMemorySize());\n    }\n\n    public static @Nullable CentralProcessor getCentralProcessor() {\n        return Holder.CENTRAL_PROCESSOR;\n    }\n\n    public static @Nullable List<GraphicsCard> getGraphicsCards() {\n        return Holder.GRAPHICS_CARDS;\n    }\n\n    private SystemInfo() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.function.ExceptionalFunction;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\npublic final class SystemUtils {\n    private SystemUtils() {\n    }\n\n    public static @Nullable Path which(String command) {\n        String path = System.getenv(\"PATH\");\n        if (path == null)\n            return null;\n\n        try {\n            for (String item : path.split(File.pathSeparator)) {\n                try {\n                    Path program = Paths.get(item, command);\n                    if (Files.isExecutable(program))\n                        return program.toRealPath();\n                } catch (Throwable ignored) {\n                }\n            }\n        } catch (Throwable ignored) {\n        }\n\n        return null;\n    }\n\n    public static int callExternalProcess(String... command) throws IOException, InterruptedException {\n        return callExternalProcess(Arrays.asList(command));\n    }\n\n    public static int callExternalProcess(List<String> command) throws IOException, InterruptedException {\n        return callExternalProcess(new ProcessBuilder(command));\n    }\n\n    public static int callExternalProcess(ProcessBuilder processBuilder) throws IOException, InterruptedException {\n        ManagedProcess managedProcess = new ManagedProcess(processBuilder);\n        managedProcess.pumpInputStream(SystemUtils::onLogLine);\n        managedProcess.pumpErrorStream(SystemUtils::onLogLine);\n        return managedProcess.getProcess().waitFor();\n    }\n\n    private static final Duration DEFAULT_MAX_WAIT_TIME = Duration.ofSeconds(15);\n\n    public static String run(String... command) throws Exception {\n        return run(List.of(command), DEFAULT_MAX_WAIT_TIME);\n    }\n\n    public static String run(List<String> command, Duration maxWaitTime) throws Exception {\n        return run(command, inputStream -> IOUtils.readFullyAsString(inputStream, OperatingSystem.NATIVE_CHARSET), maxWaitTime);\n    }\n\n    public static <T> T run(List<String> command, ExceptionalFunction<InputStream, T, ?> convert) throws Exception {\n        return run(command, convert, DEFAULT_MAX_WAIT_TIME);\n    }\n\n    public static <T> T run(List<String> command, ExceptionalFunction<InputStream, T, ?> convert, Duration maxWaitTime) throws Exception {\n        Process process = new ProcessBuilder(command)\n                .redirectError(ProcessBuilder.Redirect.DISCARD)\n                .start();\n        try {\n            InputStream inputStream = process.getInputStream();\n            CompletableFuture<T> future = CompletableFuture.supplyAsync(\n                    Lang.wrap(() -> convert.apply(inputStream)),\n                    Schedulers.io());\n\n            if (!process.waitFor(maxWaitTime.toMillis(), TimeUnit.MILLISECONDS))\n                throw new TimeoutException();\n\n            if (process.exitValue() != 0)\n                throw new IOException(\"Bad exit code: \" + process.exitValue());\n\n            return future.get();\n        } finally {\n            if (process.isAlive())\n                process.destroy();\n        }\n    }\n\n    public static boolean supportJVMAttachment() {\n        return Thread.currentThread().getContextClassLoader().getResource(\"com/sun/tools/attach/VirtualMachine.class\") != null;\n    }\n\n    public static void onLogLine(String log) {\n        LOG.info(log);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/UnsupportedPlatformException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform;\n\n/**\n * @author Glavo\n */\npublic final class UnsupportedPlatformException extends Exception {\n    public UnsupportedPlatformException() {\n    }\n\n    public UnsupportedPlatformException(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/CentralProcessor.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.hardware;\n\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author Glavo\n */\npublic final class CentralProcessor {\n\n    public static String cleanName(String name) {\n        if (name == null)\n            return null;\n\n        int idx = name.indexOf('@');\n        if (idx > 0)\n            name = name.substring(0, idx);\n\n        name = name.replaceFirst(\" (\\\\d+|Dual|Quad|Six|Eight|Ten)-[Cc]ores?\", \"\");\n        name = name.replaceAll(\" (CPU|FPU|APU|Processor)\", \"\");\n        name = name.replaceAll(\"\\\\((TM|R)\\\\)(?=\\\\s|$)\", \"\");\n\n        if (name.contains(\"Intel\")) {\n            name = name.replaceFirst(\"^(\\\\d+th Gen )?Intel\\\\s+\", \"Intel \");\n            name = name.replace(\"Core(TM)2\", \"Core 2\");\n        } else if (name.contains(\"AMD\")) {\n            name = name.replace(\"(tm)\", \"\");\n            idx = name.indexOf(\" w/ Radeon \"); // Radeon 780M Graphics\n            if (idx < 0)\n                idx = name.indexOf(\" with Radeon \");\n            if (idx < 0)\n                idx = name.indexOf(\" with AMD Radeon \");\n            if (idx > 0)\n                name = name.substring(0, idx);\n        } else if (name.contains(\"Loongson\")) {\n            name = name.replaceFirst(\"^Loongson-3A R\\\\d \\\\((Loongson-[^)]+)\\\\)\", \"$1\");\n        } else if (name.contains(\"Snapdragon\")) {\n            name = StringUtils.normalizeWhitespaces(name);\n\n            if (name.startsWith(\"Snapdragon \")) {\n                Matcher matcher = Pattern.compile(\"Snapdragon X Elite - (?<id>X1E\\\\S+) - Qualcomm Oryon\").matcher(name);\n                if (matcher.matches()) {\n                    name = \"Qualcomm Snapdragon X Elite \" + matcher.group(\"id\");\n                } else if (!name.contains(\"Qualcomm\")) {\n                    name = \"Qualcomm \" + name;\n                }\n            }\n        }\n\n        return StringUtils.normalizeWhitespaces(name);\n    }\n\n    private final String name;\n    private final @Nullable HardwareVendor vendor;\n    private final @Nullable Cores cores;\n\n    private CentralProcessor(String name, @Nullable HardwareVendor vendor, @Nullable Cores cores) {\n        this.name = name;\n        this.vendor = vendor;\n        this.cores = cores;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public @Nullable HardwareVendor getVendor() {\n        return vendor;\n    }\n\n    public @Nullable Cores getCores() {\n        return cores;\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder builder = new StringBuilder(128);\n        if (cores != null && cores.packages > 1)\n            builder.append(cores.packages).append(\" x \");\n\n        builder.append(name);\n\n        if (cores != null) {\n            builder.append(\" (\");\n            builder.append(cores.physical).append(\" Cores\");\n            if (cores.logical > 0 && cores.logical != cores.physical)\n                builder.append(\" / \").append(cores.logical).append(\" Threads\");\n            builder.append(\")\");\n        }\n\n        return builder.toString();\n    }\n\n    public static final class Cores {\n        public final int physical;\n        public final int logical;\n        public final int packages;\n\n        public Cores(int logical) {\n            this(logical, logical, 1);\n        }\n\n        public Cores(int physical, int logical, int packages) {\n            this.physical = physical;\n            this.logical = logical;\n            this.packages = packages;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"Cores[physical=%d, logical=%d, packages=%d]\", physical, logical, packages);\n        }\n    }\n\n    public static final class Builder {\n        private String name;\n        private @Nullable HardwareVendor vendor;\n        private @Nullable Cores cores;\n\n        public CentralProcessor build() {\n            String name = this.name;\n            if (name == null) {\n                if (vendor != null)\n                    name = vendor + \" Processor\";\n                else\n                    name = \"Unknown\";\n            }\n\n            return new CentralProcessor(name, vendor, cores);\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public Builder setName(String name) {\n            this.name = name;\n            return this;\n        }\n\n        public HardwareVendor getVendor() {\n            return vendor;\n        }\n\n        public Builder setVendor(HardwareVendor vendor) {\n            this.vendor = vendor;\n            return this;\n        }\n\n        public Cores getCores() {\n            return cores;\n        }\n\n        public Builder setCores(Cores cores) {\n            this.cores = cores;\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/FastFetchUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.hardware;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\nfinal class FastFetchUtils {\n    private FastFetchUtils() {\n    }\n\n    private static <T> T get(String type, TypeToken<T> resultType) {\n        Path fastfetch = SystemUtils.which(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? \"fastfetch.exe\" : \"fastfetch\");\n        if (fastfetch == null)\n            return null;\n\n        String output;\n        try {\n            output = SystemUtils.run(Arrays.asList(fastfetch.toString(), \"--structure\", type, \"--format\", \"json\"),\n                    inputStream -> IOUtils.readFullyAsString(inputStream, OperatingSystem.NATIVE_CHARSET));\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get result from fastfetch\", e);\n            return null;\n        }\n\n        try {\n            // Sometimes there is some garbage before the output JSON, we should filter it out\n            int idx = output.indexOf('[');\n            String json = idx >= 0 ? output.substring(idx) : output;\n\n            List<Result<T>> list = JsonUtils.GSON.fromJson(json, JsonUtils.listTypeOf(Result.typeOf(resultType)));\n\n            Result<T> result;\n            if (list == null\n                    || list.size() != 1\n                    || (result = list.get(0)) == null\n                    || !type.equalsIgnoreCase(result.type)\n                    || result.result == null) {\n                throw new IOException(\"Illegal output format\");\n            }\n\n            return result.result;\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to parse fastfetch output: \" + output, e);\n            return null;\n        }\n    }\n\n    static @Nullable CentralProcessor detectCentralProcessor() {\n        CPUInfo cpuInfo = get(\"CPU\", TypeToken.get(CPUInfo.class));\n\n        if (cpuInfo == null)\n            return null;\n\n        CentralProcessor.Builder builder = new CentralProcessor.Builder()\n                .setName(cpuInfo.cpu)\n                .setVendor(HardwareVendor.of(cpuInfo.vendor));\n\n        if (cpuInfo.cores != null) {\n            try {\n                String physical = cpuInfo.cores.get(\"physical\");\n                String logical = cpuInfo.cores.get(\"logical\");\n\n                int cores = physical != null ? Integer.parseInt(physical) : 0;\n                int threads = logical != null ? Integer.parseInt(logical) : 0;\n                int packages = Integer.max(cpuInfo.packages, 1);\n\n                if (cores > 0 && threads == 0)\n                    threads = cores;\n                else if (threads > 0 && cores == 0)\n                    cores = threads;\n\n                builder.setCores(new CentralProcessor.Cores(cores, threads, packages));\n            } catch (Throwable ignored) {\n            }\n        }\n\n        return builder.build();\n    }\n\n    static @Nullable List<GraphicsCard> detectGraphicsCards() {\n        List<GPUInfo> gpuInfos = get(\"GPU\", JsonUtils.listTypeOf(GPUInfo.class));\n        if (gpuInfos == null)\n            return null;\n\n        ArrayList<GraphicsCard> result = new ArrayList<>(gpuInfos.size());\n        for (GPUInfo gpuInfo : gpuInfos) {\n            if (gpuInfo == null)\n                continue;\n\n            GraphicsCard.Builder builder = new GraphicsCard.Builder()\n                    .setName(gpuInfo.name)\n                    .setVendor(HardwareVendor.of(gpuInfo.vendor))\n                    .setDriver(gpuInfo.driver);\n\n            if (\"Discrete\".equalsIgnoreCase(gpuInfo.type))\n                builder.setType(GraphicsCard.Type.Discrete);\n            else if (\"Integrated\".equalsIgnoreCase(gpuInfo.type))\n                builder.setType(GraphicsCard.Type.Integrated);\n\n            result.add(builder.build());\n        }\n        return result;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static final class Result<T> {\n        static <T> TypeToken<Result<T>> typeOf(TypeToken<T> type) {\n            return (TypeToken<Result<T>>) TypeToken.getParameterized(Result.class, type.getType());\n        }\n\n        String type;\n        T result;\n    }\n\n    private static final class CPUInfo {\n        String cpu;\n        String vendor;\n        int packages;\n        Map<String, String> cores;\n    }\n\n    private static final class GPUInfo {\n        String name;\n        String vendor;\n        String type;\n        String driver;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCard.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.hardware;\n\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author Glavo\n */\npublic final class GraphicsCard {\n\n    public static String cleanName(String name) {\n        if (name == null)\n            return null;\n\n        name = name.replaceAll(\"\\\\((TM|R)\\\\)(?=\\\\s|$)\", \"\");\n        name = name.replace(\" GPU\", \"\");\n\n        if (name.contains(\"Snapdragon\")) {\n            name = StringUtils.normalizeWhitespaces(name);\n            if (name.startsWith(\"Snapdragon \")) {\n                Matcher matcher = Pattern.compile(\"Snapdragon X Elite - (?<id>X1E\\\\S+) - Qualcomm Adreno\").matcher(name);\n                if (matcher.matches()) {\n                    name = \"Qualcomm Adreno Graphics\";\n                }\n            }\n        }\n\n        return StringUtils.normalizeWhitespaces(name);\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    private final String name;\n    private final @Nullable HardwareVendor vendor;\n    private final @Nullable Type type;\n    private final @Nullable String driver;\n    private final @Nullable String driverVersion;\n\n    private GraphicsCard(String name, @Nullable HardwareVendor vendor, @Nullable Type type, @Nullable String driver, @Nullable String driverVersion) {\n        this.name = Objects.requireNonNull(name);\n        this.vendor = vendor;\n        this.type = type;\n        this.driver = driver;\n        this.driverVersion = driverVersion;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public @Nullable HardwareVendor getVendor() {\n        return vendor;\n    }\n\n    public @Nullable String getDriverVersion() {\n        return driverVersion;\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder builder = new StringBuilder(name);\n\n        if (type != null) {\n            builder.append(\" [\").append(type).append(']');\n        }\n\n        return builder.toString();\n    }\n\n    public enum Type {\n        Integrated,\n        Discrete\n    }\n\n    public static final class Builder {\n        private String name;\n        private HardwareVendor vendor;\n        private Type type;\n        private String driver;\n        private String driverVersion;\n\n        public GraphicsCard build() {\n            String name = this.name;\n            if (name == null) {\n                if (vendor != null)\n                    name = vendor + \" Graphics\";\n                else\n                    name = \"Unknown\";\n            }\n\n            return new GraphicsCard(name, vendor, type, driver, driverVersion);\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public Builder setName(String name) {\n            this.name = name;\n            return this;\n        }\n\n        public HardwareVendor getVendor() {\n            return vendor;\n        }\n\n        public Builder setVendor(HardwareVendor vendor) {\n            this.vendor = vendor;\n            return this;\n        }\n\n        public Type getType() {\n            return type;\n        }\n\n        public Builder setType(Type type) {\n            this.type = type;\n            return this;\n        }\n\n        public String getDriver() {\n            return driver;\n        }\n\n        public Builder setDriver(String driver) {\n            this.driver = driver;\n            return this;\n        }\n\n        public String getDriverVersion() {\n            return driverVersion;\n        }\n\n        public Builder setDriverVersion(String driverVersion) {\n            this.driverVersion = driverVersion;\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/HardwareDetector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.hardware;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.management.ManagementFactory;\nimport java.util.List;\n\n/**\n * @author Glavo\n */\npublic class HardwareDetector {\n    private static final boolean USE_FAST_FETCH = \"true\".equalsIgnoreCase(System.getProperty(\"hmcl.hardware.fastfetch\", \"true\"));\n\n    public @Nullable CentralProcessor detectCentralProcessor() {\n        return USE_FAST_FETCH ? FastFetchUtils.detectCentralProcessor() : null;\n    }\n\n    public @Nullable List<GraphicsCard> detectGraphicsCards() {\n        return USE_FAST_FETCH ? FastFetchUtils.detectGraphicsCards() : null;\n    }\n\n    public long getTotalMemorySize() {\n        try {\n            if (ManagementFactory.getOperatingSystemMXBean() instanceof com.sun.management.OperatingSystemMXBean bean) {\n                return bean.getTotalMemorySize();\n            }\n        } catch (NoClassDefFoundError ignored) {\n        }\n\n        return 0L;\n    }\n\n    public long getFreeMemorySize() {\n        try {\n            if (ManagementFactory.getOperatingSystemMXBean() instanceof com.sun.management.OperatingSystemMXBean bean) {\n                return bean.getFreeMemorySize();\n            }\n        } catch (NoClassDefFoundError ignored) {\n        }\n\n        return 0L;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/HardwareVendor.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n\npackage org.jackhuang.hmcl.util.platform.hardware;\n\nimport org.jetbrains.annotations.Contract;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Locale;\n\npublic final class HardwareVendor {\n    public static final HardwareVendor INTEL = new HardwareVendor(\"Intel\");\n    public static final HardwareVendor NVIDIA = new HardwareVendor(\"NVIDIA\");\n    public static final HardwareVendor AMD = new HardwareVendor(\"AMD\");\n    public static final HardwareVendor APPLE = new HardwareVendor(\"Apple\");\n    public static final HardwareVendor ARM = new HardwareVendor(\"ARM\");\n    public static final HardwareVendor QUALCOMM = new HardwareVendor(\"Qualcomm\");\n    public static final HardwareVendor MTK = new HardwareVendor(\"MTK\");\n    public static final HardwareVendor VMWARE = new HardwareVendor(\"VMware\");\n    public static final HardwareVendor PARALLEL = new HardwareVendor(\"Parallel\");\n    public static final HardwareVendor MICROSOFT = new HardwareVendor(\"Microsoft\");\n    public static final HardwareVendor MOORE_THREADS = new HardwareVendor(\"Moore Threads\");\n    public static final HardwareVendor BROADCOM = new HardwareVendor(\"Broadcom\");\n    public static final HardwareVendor IMG = new HardwareVendor(\"Imagination\");\n    public static final HardwareVendor LOONGSON = new HardwareVendor(\"Loongson\");\n    public static final HardwareVendor JINGJIA_MICRO = new HardwareVendor(\"Jingjia Micro\");\n    public static final HardwareVendor HUAWEI = new HardwareVendor(\"Huawei\");\n    public static final HardwareVendor ZHAOXIN = new HardwareVendor(\"Zhaoxin\");\n    public static final HardwareVendor SAMSUNG = new HardwareVendor(\"Samsung\");\n    public static final HardwareVendor MARVELL = new HardwareVendor(\"Marvell\");\n    public static final HardwareVendor AMPERE = new HardwareVendor(\"Ampere\");\n    public static final HardwareVendor ROCKCHIP = new HardwareVendor(\"Rockchip\");\n\n    // RISC-V\n    public static final HardwareVendor THEAD = new HardwareVendor(\"T-Head\");\n    public static final HardwareVendor STARFIVE = new HardwareVendor(\"StarFive\");\n    public static final HardwareVendor ESWIN = new HardwareVendor(\"ESWIN\");\n    public static final HardwareVendor SPACEMIT = new HardwareVendor(\"SpacemiT\");\n\n    public static @Nullable HardwareVendor getKnown(String name) {\n        if (name == null)\n            return null;\n\n        String lower = name.toLowerCase(Locale.ROOT);\n        if (lower.startsWith(\"intel\") || lower.startsWith(\"genuineintel\")) return INTEL;\n        if (lower.startsWith(\"nvidia\")) return NVIDIA;\n        if (lower.startsWith(\"advanced micro devices\")\n            || lower.startsWith(\"authenticamd\")\n            || (lower.startsWith(\"amd\") && !(lower.length() > 3 && Character.isAlphabetic(lower.charAt(3)))))\n            return AMD;\n        if (lower.equals(\"brcm\") || lower.startsWith(\"broadcom\")) return BROADCOM;\n        if (lower.startsWith(\"mediatek\")) return MTK;\n        if (lower.equals(\"qcom\") || lower.startsWith(\"qualcomm\")) return QUALCOMM;\n        if (lower.startsWith(\"apple\")) return APPLE;\n        if (lower.startsWith(\"microsoft\")) return MICROSOFT;\n        if (lower.startsWith(\"imagination\") || lower.equals(\"img\")) return IMG;\n        if (lower.startsWith(\"loongson\")) return LOONGSON;\n        if (lower.startsWith(\"moore threads\")) return MOORE_THREADS;\n        if (lower.startsWith(\"jingjia\")) return JINGJIA_MICRO;\n        if (lower.startsWith(\"huawei\") || lower.startsWith(\"hisilicon\")) return HUAWEI;\n        if (lower.startsWith(\"zhaoxin\")) return ZHAOXIN;\n        if (lower.startsWith(\"marvell\")) return MARVELL;\n        if (lower.startsWith(\"samsung\")) return SAMSUNG;\n        if (lower.startsWith(\"ampere\")) return AMPERE;\n        if (lower.startsWith(\"rockchip\")) return ROCKCHIP;\n        if (lower.startsWith(\"thead\") || lower.startsWith(\"t-head\")) return THEAD;\n        if (lower.startsWith(\"starfive\")) return STARFIVE;\n        if (lower.startsWith(\"eswin\")) return ESWIN;\n        if (lower.startsWith(\"spacemit\")) return SPACEMIT;\n\n        return null;\n    }\n\n    @Contract(\"null -> null; !null -> !null\")\n    public static HardwareVendor of(String name) {\n        if (name == null)\n            return null;\n\n        HardwareVendor known = getKnown(name);\n        return known != null ? known : new HardwareVendor(name);\n    }\n\n    public static @Nullable HardwareVendor ofPciVendorId(int vendorId) {\n        // https://devicehunt.com/all-pci-vendors\n        switch (vendorId) {\n            case 0x106b:\n                return APPLE;\n            case 0x1002:\n            case 0x1022:\n            case 0x1dd8: // AMD Pensando Systems\n            case 0x1924: // AMD Solarflare\n                return AMD;\n            case 0x8086:\n            case 0x8087:\n            case 0x03e7:\n                return INTEL;\n            case 0x0955:\n            case 0x10de:\n            case 0x12d2:\n                return NVIDIA;\n            case 0x1ed5:\n                return MOORE_THREADS;\n            case 0x168c:\n            case 0x5143:\n                return QUALCOMM;\n            case 0x14c3:\n                return MTK;\n            case 0x15ad:\n                return VMWARE;\n            case 0x1ab8:\n                return PARALLEL;\n            case 0x1414:\n                return MICROSOFT;\n            case 0x182f:\n            case 0x14e4:\n                return BROADCOM;\n            case 0x0014:\n                return LOONGSON;\n            case 0x0731:\n                return JINGJIA_MICRO;\n            case 0x19e5:\n                return HUAWEI;\n            case 0x1d17:\n                return ZHAOXIN;\n            default:\n                return null;\n        }\n    }\n\n    public static @Nullable HardwareVendor ofArmImplementerId(int implementerId) {\n        // https://github.com/util-linux/util-linux/blob/0a21358af3e50fcb13a9bf3702779f11a4739667/sys-utils/lscpu-arm.c#L301\n        switch (implementerId) {\n            case 0x41:\n                return ARM;\n            case 0x42:\n                return BROADCOM;\n            case 0x48:\n                return HUAWEI;\n            case 0x4e:\n                return NVIDIA;\n            case 0x51:\n                return QUALCOMM;\n            case 0x53:\n                return SAMSUNG;\n            case 0x56:\n                return MARVELL;\n            case 0x61:\n                return APPLE;\n            case 0x69:\n                return INTEL;\n            case 0x6D:\n                return MICROSOFT;\n            case 0xc0:\n                return AMPERE;\n            default:\n                return null;\n        }\n    }\n\n    private final String name;\n\n    public HardwareVendor(String name) {\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        return o instanceof HardwareVendor && name.equals(((HardwareVendor) o).name);\n    }\n\n    @Override\n    public int hashCode() {\n        return name.hashCode();\n    }\n\n    @Override\n    public String toString() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/PhysicalMemoryStatus.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.hardware;\n\npublic final class PhysicalMemoryStatus {\n    private final long total;\n    private final long available;\n\n    public PhysicalMemoryStatus(long total, long available) {\n        this.total = total;\n        this.available = available;\n    }\n\n    public long getTotal() {\n        return total;\n    }\n\n    public long getUsed() {\n        return hasAvailable() ? total - available : 0;\n    }\n\n    public long getAvailable() {\n        return available;\n    }\n\n    public boolean hasAvailable() {\n        return available >= 0;\n    }\n\n    public static final PhysicalMemoryStatus INVALID = new PhysicalMemoryStatus(0, -1);\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxCPUDetector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.linux;\n\nimport org.jackhuang.hmcl.util.KeyValuePairUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;\nimport org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.nio.file.DirectoryStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n * @see <a href=\"https://github.com/fastfetch-cli/fastfetch/blob/e13eb05073297aa6f1dc244ab3414e98170a43e1/src/detection/cpu/cpu_linux.c\">cpu_linux.c</a>\n */\nfinal class LinuxCPUDetector {\n\n    private static TreeMap<Integer, Map<String, String>> loadCPUInfo() {\n        try {\n            List<Map<String, String>> list = KeyValuePairUtils.loadList(Paths.get(\"/proc/cpuinfo\"));\n            TreeMap<Integer, Map<String, String>> result = new TreeMap<>();\n            for (Map<String, String> map : list) {\n                String id = map.get(\"processor\");\n                if (id != null) {\n                    result.put(Integer.parseInt(id), map);\n                }\n            }\n            return result;\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to load /proc/cpuinfo\", e);\n            return null;\n        }\n    }\n\n    // https://asahilinux.org/docs/hw/soc/soc-codenames/\n    private static String appleCodeToName(int code) {\n        switch (code) {\n            case 8103:\n                return \"Apple M1\";\n            case 6000:\n                return \"Apple M1 Pro\";\n            case 6001:\n                return \"Apple M1 Max\";\n            case 6002:\n                return \"Apple M1 Ultra\";\n            case 8112:\n                return \"Apple M2\";\n            case 6020:\n                return \"Apple M2 Pro\";\n            case 6021:\n                return \"Apple M2 Max\";\n            case 6022:\n                return \"Apple M2 Ultra\";\n            case 8122:\n                return \"Apple M3\";\n            case 6030:\n                return \"Apple M3 Pro\";\n            case 6031:\n            case 6034:\n                return \"Apple M3 Max\";\n            case 8132:\n                return \"Apple M4\";\n            case 6040:\n                return \"Apple M4 Pro\";\n            case 6041:\n                return \"Apple M4 Max\";\n            default:\n                return null;\n        }\n    }\n\n    private static void detectName(CentralProcessor.Builder builder, TreeMap<Integer, Map<String, String>> cpuInfo) {\n        // assert !cpuInfo.isEmpty();\n        Map<String, String> firstCore = cpuInfo.firstEntry().getValue();\n\n        String modelName = firstCore.get(\"model name\");\n        if (modelName == null)\n            modelName = firstCore.get(\"Model Name\");\n        if (modelName == null)\n            modelName = firstCore.get(\"Model name\");\n        if (modelName == null)\n            modelName = firstCore.get(\"cpu model\");\n\n        if (modelName != null) {\n            builder.setName(CentralProcessor.cleanName(modelName));\n            builder.setVendor(HardwareVendor.of(firstCore.get(\"vendor_id\")));\n\n            if (builder.getVendor() == null && modelName.startsWith(\"Loongson\"))\n                builder.setVendor(HardwareVendor.LOONGSON);\n\n            return;\n        }\n\n        try {\n            Path compatiblePath = Paths.get(\"/proc/device-tree/compatible\");\n            if (Files.isRegularFile(compatiblePath)) {\n                // device-vendor,device-model\\0soc-vendor,soc-model\\0\n                String[] data = Files.readString(compatiblePath).split(\"\\0\");\n\n                for (int i = data.length - 1; i >= 0; i--) {\n                    String device = data[i];\n                    int idx = device.indexOf(',');\n                    if (idx <= 0 || idx >= device.length() - 1)\n                        continue;\n\n                    String vendor = device.substring(0, idx);\n                    String model = device.substring(idx + 1);\n\n                    if (model.startsWith(\"generic-\"))\n                        continue;\n\n                    builder.setVendor(HardwareVendor.getKnown(vendor));\n                    if (builder.getVendor() == null)\n                        builder.setVendor(HardwareVendor.of(StringUtils.capitalizeFirst(vendor)));\n\n                    if (builder.getVendor() == HardwareVendor.APPLE) {\n                        // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/apple\n                        if (model.matches(\"t[0-9]+\"))\n                            builder.setName(appleCodeToName(Integer.parseInt(model.substring(1))));\n\n                        if (builder.getName() == null)\n                            builder.setName(\"Apple Silicon \" + model);\n                    } else if (builder.getVendor() == HardwareVendor.QUALCOMM) {\n                        // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/qcom\n                        if (model.startsWith(\"x\"))\n                            builder.setName(\"Qualcomm Snapdragon X Elite \" + model.toUpperCase(Locale.ROOT));\n                    } else if (builder.getVendor() == HardwareVendor.BROADCOM)\n                        // Raspberry Pi\n                        builder.setName(\"Broadcom \" + model.toUpperCase(Locale.ROOT));\n\n                    if (builder.getName() == null)\n                        builder.setName(builder.getVendor() + \" \" + model.toUpperCase(Locale.ROOT));\n\n                    return;\n                }\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to detect CPU name from /proc/device-tree/compatible\", e);\n        }\n    }\n\n    private static void detectCores(CentralProcessor.Builder builder, TreeMap<Integer, Map<String, String>> cpuInfo) {\n        // assert !cpuInfo.isEmpty();\n\n        int logical = cpuInfo.size();\n\n        try {\n            Map<String, String> firstCore = cpuInfo.firstEntry().getValue();\n            if (firstCore.containsKey(\"cpu cores\") && firstCore.containsKey(\"physical id\")) {\n                TreeMap<Integer, Integer> cpuCores = new TreeMap<>();\n                TreeSet<Integer> physicalIds = new TreeSet<>();\n\n                for (Map<String, String> core : cpuInfo.values()) {\n                    int cores = Integer.parseInt(core.get(\"cpu cores\"));\n                    int physicalId = Integer.parseInt(core.get(\"physical id\"));\n\n                    cpuCores.put(physicalId, cores);\n                    physicalIds.add(physicalId);\n                }\n\n                int physical = 0;\n                for (Integer value : cpuCores.values()) {\n                    physical += value;\n                }\n\n                builder.setCores(new CentralProcessor.Cores(physical, logical, physicalIds.size()));\n                return;\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to detect cores in /proc/cpuinfo\", e);\n        }\n\n        Path cpuDevicesDir = Paths.get(\"/sys/devices/system/cpu\");\n        if (Files.isRegularFile(cpuDevicesDir.resolve(\"cpu0/topology/physical_package_id\"))\n                && Files.isRegularFile(cpuDevicesDir.resolve(\"cpu0/topology/core_cpus_list\"))) {\n            Pattern dirNamePattern = Pattern.compile(\"cpu[0-9]+\");\n            TreeSet<Integer> physicalPackageIds = new TreeSet<>();\n            TreeSet<Integer> physicalCores = new TreeSet<>();\n\n            int physical = 0;\n            try (DirectoryStream<Path> stream = Files.newDirectoryStream(cpuDevicesDir)) {\n                for (Path cpuDir : stream) {\n                    if (!dirNamePattern.matcher(cpuDir.getFileName().toString()).matches() || !Files.isDirectory(cpuDir))\n                        continue;\n\n                    physicalPackageIds.add(Integer.parseInt(Files.readString(cpuDir.resolve(\"topology/physical_package_id\")).trim()));\n\n                    boolean shouldCount = false;\n                    for (String item : Files.readString(cpuDir.resolve(\"topology/core_cpus_list\")).trim().split(\",\")) {\n                        String range = item.trim();\n                        int idx = range.indexOf('-');\n                        if (idx < 0)\n                            shouldCount |= physicalCores.add(Integer.parseInt(range));\n                        else {\n                            int first = Integer.parseInt(range.substring(0, idx));\n                            int last = Integer.parseInt(range.substring(idx + 1));\n\n                            for (int i = first; i <= last; i++) {\n                                shouldCount |= physicalCores.add(i);\n                            }\n                        }\n                    }\n                    if (shouldCount)\n                        physical++;\n                }\n\n                builder.setCores(new CentralProcessor.Cores(physical, logical, physicalPackageIds.size()));\n                return;\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to detect cores in /sys/devices/system/cpu\", e);\n            }\n        }\n\n        builder.setCores(new CentralProcessor.Cores(logical));\n    }\n\n    static @Nullable CentralProcessor detect() {\n        TreeMap<Integer, Map<String, String>> cpuInfo = loadCPUInfo();\n        if (cpuInfo == null || cpuInfo.isEmpty()) return null;\n\n        CentralProcessor.Builder builder = new CentralProcessor.Builder();\n        detectName(builder, cpuInfo);\n        if (builder.getName() == null)\n            return null;\n\n        detectCores(builder, cpuInfo);\n        return builder.build();\n    }\n\n    private LinuxCPUDetector() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxGPUDetector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.linux;\n\nimport org.glavo.pci.ids.PCIIDsDatabase;\nimport org.glavo.pci.ids.model.Device;\nimport org.glavo.pci.ids.model.Vendor;\nimport org.jackhuang.hmcl.util.Lang;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;\nimport org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;\n\nimport java.io.*;\nimport java.lang.ref.SoftReference;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n * @see <a href=\"https://github.com/fastfetch-cli/fastfetch/blob/33205326683fd89ec63204f83549839e2374c286/src/detection/gpu/gpu_linux.c\">gpu_linux.c</a>\n */\nfinal class LinuxGPUDetector {\n    private static volatile SoftReference<PCIIDsDatabase> databaseCache;\n\n    private static final Pattern PCI_MODALIAS_PATTERN =\n            Pattern.compile(\"pci:v(?<vendorId>\\\\p{XDigit}{8})d(?<deviceId>\\\\p{XDigit}{8})sv(?<subVendorId>\\\\p{XDigit}{8})sd(?<subDeviceId>\\\\p{XDigit}{8})bc(?<classId>\\\\p{XDigit}{2})sc(?<subclassId>\\\\p{XDigit}{2})i\\\\p{XDigit}{2}\");\n    private static final Pattern PCI_DEVICE_PATTERN =\n            Pattern.compile(\"(?<pciDomain>\\\\p{XDigit}+):(?<pciBus>\\\\p{XDigit}+):(?<pciDevice>\\\\p{XDigit}+)\\\\.(?<pciFunc>\\\\p{XDigit}+)\");\n    private static final Pattern OF_DEVICE_PATTERN =\n            Pattern.compile(\"of:N(img)?gpuT[^C]*C(?<compatible>.*)\");\n\n    private static PCIIDsDatabase getPCIIDsDatabase() {\n        SoftReference<PCIIDsDatabase> databaseWeakReference = LinuxGPUDetector.databaseCache;\n        PCIIDsDatabase pciIDsDatabase;\n\n        if (databaseCache != null) {\n            pciIDsDatabase = databaseWeakReference.get();\n            if (pciIDsDatabase != null) {\n                return pciIDsDatabase;\n            }\n        }\n\n        for (String path : new String[]{\n                \"/usr/share/misc/pci.ids\",\n                \"/usr/share/hwdata/pci.ids\",\n                \"/usr/local/share/hwdata/pci.ids\"\n        }) {\n            Path p = Paths.get(path);\n            if (Files.isRegularFile(p)) {\n                try {\n                    pciIDsDatabase = PCIIDsDatabase.load(p);\n                    databaseCache = new SoftReference<>(pciIDsDatabase);\n                    return pciIDsDatabase;\n                } catch (IOException e) {\n                    return null;\n                }\n            }\n        }\n\n        return null;\n    }\n\n    private static void detectDriver(GraphicsCard.Builder builder, Path deviceDir) {\n        try {\n            Path driverDir = Files.readSymbolicLink(deviceDir.resolve(\"driver\"));\n            if (driverDir.getNameCount() > 0) {\n\n                String name = driverDir.getName(driverDir.getNameCount() - 1).toString();\n                builder.setDriver(name);\n\n                Path versionFile = deviceDir.resolve(\"driver/module/version\");\n                if (Files.isRegularFile(versionFile)) {\n                    builder.setDriverVersion(Files.readString(versionFile).trim());\n                } else if (\"zx\".equals(name)) {\n                    versionFile = deviceDir.resolve(\"zx_info/driver_version\");\n                    if (Files.isRegularFile(versionFile)) {\n                        builder.setDriverVersion(Files.readString(versionFile).trim());\n                    }\n                }\n            }\n        } catch (IOException ignored) {\n        }\n    }\n\n    private static GraphicsCard detectPCI(Path deviceDir, String modalias) throws IOException {\n        Matcher matcher = PCI_MODALIAS_PATTERN.matcher(modalias);\n        if (!matcher.matches())\n            return null;\n\n        GraphicsCard.Builder builder = GraphicsCard.builder();\n\n        int vendorId = Integer.parseInt(matcher.group(\"vendorId\"), 16);\n        int deviceId = Integer.parseInt(matcher.group(\"deviceId\"), 16);\n        int classId = Integer.parseInt(matcher.group(\"classId\"), 16);\n        int subclassId = Integer.parseInt(matcher.group(\"subclassId\"), 16);\n\n        if (classId != 0x03) // PCI_BASE_CLASS_DISPLAY\n            return null; // Not a GPU device\n\n        Path pciPath = Files.readSymbolicLink(deviceDir);\n        int nameCount = pciPath.getNameCount();\n        if (nameCount == 0)\n            return null;\n\n        matcher = PCI_DEVICE_PATTERN.matcher(pciPath.getName(nameCount - 1).toString());\n        if (!matcher.matches())\n            return null;\n\n        int pciDomain = Integer.parseInt(matcher.group(\"pciDomain\"), 16);\n        int pciBus = Integer.parseInt(matcher.group(\"pciBus\"), 16);\n        int pciDevice = Integer.parseInt(matcher.group(\"pciDevice\"), 16);\n        int pciFunc = Integer.parseInt(matcher.group(\"pciFunc\"), 16);\n\n        builder.setVendor(HardwareVendor.ofPciVendorId(vendorId));\n        detectDriver(builder, deviceDir);\n\n        try {\n            if (builder.getVendor() == HardwareVendor.AMD) {\n                Path hwmon = deviceDir.resolve(\"hwmon\");\n                try (Stream<Path> subDirs = Files.list(hwmon)) {\n                    for (Path subDir : Lang.toIterable(subDirs)) {\n                        if (Files.isDirectory(subDir) && !subDir.getFileName().toString().startsWith(\".\")) {\n                            builder.setType(Files.exists(subDir.resolve(\"in1_input\"))\n                                    ? GraphicsCard.Type.Integrated\n                                    : GraphicsCard.Type.Discrete);\n                            break;\n                        }\n                    }\n                } catch (IOException ignored) {\n                }\n\n                Path revisionFile = deviceDir.resolve(\"revision\");\n                if (Files.isRegularFile(revisionFile)) {\n                    String revisionString = Files.readString(revisionFile).trim();\n                    int revision = Integer.decode(revisionString);\n                    String prefix = String.format(\"%X,\\t%X,\\t\", deviceId, revision);\n                    //noinspection DataFlowIssue\n                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(\n                            LinuxHardwareDetector.class.getResourceAsStream(\"/assets/platform/amdgpu.ids\"), StandardCharsets.UTF_8))) {\n                        String line;\n                        while ((line = reader.readLine()) != null) {\n                            if (line.startsWith(prefix)) {\n                                builder.setName(line.substring(prefix.length()));\n                                break;\n                            }\n                        }\n                    }\n                }\n\n            } else if (builder.getVendor() == HardwareVendor.INTEL) {\n                builder.setType(pciDevice == 20 ? GraphicsCard.Type.Integrated : GraphicsCard.Type.Discrete);\n            }\n        } catch (Throwable ignored) {\n        }\n\n        if (builder.getName() == null || builder.getVendor() == null) {\n            PCIIDsDatabase database = getPCIIDsDatabase();\n            if (database != null) {\n                Vendor vendor = database.findVendor(vendorId);\n                if (vendor != null) {\n                    if (builder.getVendor() == null)\n                        builder.setVendor(HardwareVendor.of(vendor.getName()));\n\n                    if (builder.getName() == null) {\n                        Device device = vendor.getDevices().get(deviceId);\n                        if (device != null) {\n                            matcher = Pattern.compile(\".*\\\\[(?<name>.*)]\").matcher(device.getName());\n                            if (matcher.matches())\n                                builder.setName(GraphicsCard.cleanName(builder.getVendor() + \" \" + matcher.group(\"name\")));\n                            else\n                                builder.setName(builder.getVendor() + \" \" + device.getName());\n                        }\n                    }\n                }\n            }\n        }\n\n        if (builder.getName() == null) {\n            String subclassStr;\n            switch (subclassId) {\n                case 0: // PCI_CLASS_DISPLAY_VGA\n                    subclassStr = \" (VGA compatible)\";\n                    break;\n                case 1: // PCI_CLASS_DISPLAY_XGA\n                    subclassStr = \" (XGA compatible)\";\n                    break;\n                case 2: // PCI_CLASS_DISPLAY_3D\n                    subclassStr = \" (3D)\";\n                    break;\n                default:\n                    subclassStr = \"\";\n            }\n\n            builder.setName(String.format(\"%s Device %04X%s\",\n                    builder.getVendor() != null ? builder.getVendor().toString() : \"Unknown\",\n                    deviceId, subclassStr));\n        }\n\n        if (builder.getType() == null) {\n            if (builder.getVendor() == HardwareVendor.NVIDIA) {\n                if (builder.getName().startsWith(\"GeForce\")\n                        || builder.getName().startsWith(\"Quadro\")\n                        || builder.getName().startsWith(\"Tesla\"))\n                    builder.setType(GraphicsCard.Type.Discrete);\n\n            } else if (builder.getVendor() == HardwareVendor.MOORE_THREADS) {\n                if (builder.getName().startsWith(\"MTT \"))\n                    builder.setType(GraphicsCard.Type.Discrete);\n            }\n        }\n\n        return builder.build();\n    }\n\n    private static GraphicsCard detectOF(Path deviceDir, String modalias) throws IOException {\n        Matcher matcher = OF_DEVICE_PATTERN.matcher(modalias);\n        if (!matcher.matches())\n            return null;\n\n        GraphicsCard.Builder builder = new GraphicsCard.Builder();\n\n        String compatible = matcher.group(\"compatible\");\n        int idx = compatible.indexOf(',');\n        if (idx < 0) {\n            String name = compatible.trim().toUpperCase(Locale.ROOT);\n            if (name.equals(\"IMG-GPU\"))  // Fucking Imagination\n                builder.setVendor(HardwareVendor.IMG);\n            else\n                builder.setName(name);\n        } else {\n            String vendorName = compatible.substring(0, idx).trim();\n            HardwareVendor vendor = HardwareVendor.getKnown(vendorName);\n            if (vendor == null)\n                vendor = new HardwareVendor(StringUtils.capitalizeFirst(vendorName));\n\n            builder.setVendor(vendor);\n\n            String name = compatible.substring(idx + 1).trim().toUpperCase(Locale.ROOT);\n            if (vendor == HardwareVendor.IMG) {\n                if (!name.equals(\"GPU\"))\n                    builder.setName(vendor + \" \" + name);\n            } else\n                builder.setName(vendor + \" \" + name);\n        }\n\n        builder.setType(GraphicsCard.Type.Integrated);\n\n        detectDriver(builder, deviceDir);\n        return builder.build();\n    }\n\n    static List<GraphicsCard> detect() {\n        Path drm = Paths.get(\"/sys/class/drm\");\n        if (!Files.isDirectory(drm))\n            return Collections.emptyList();\n\n        ArrayList<GraphicsCard> cards = new ArrayList<>();\n\n        try (Stream<Path> stream = Files.list(drm)) {\n            for (Path deviceRoot : Lang.toIterable(stream)) {\n                Path dirName = deviceRoot.getFileName();\n                if (dirName == null)\n                    continue;\n\n                String name = dirName.toString();\n                if (!name.startsWith(\"card\") || name.indexOf('-') >= 0)\n                    continue;\n\n                Path deviceDir = deviceRoot.resolve(\"device\");\n                Path modaliasFile = deviceDir.resolve(\"modalias\");\n                if (!Files.isRegularFile(modaliasFile))\n                    continue;\n\n                try {\n                    String modalias = Files.readString(modaliasFile).trim();\n                    GraphicsCard graphicsCard = null;\n                    if (modalias.startsWith(\"pci:\"))\n                        graphicsCard = detectPCI(deviceDir, modalias);\n                    else if (modalias.startsWith(\"of:\"))\n                        graphicsCard = detectOF(deviceDir, modalias);\n\n                    if (graphicsCard != null)\n                        cards.add(graphicsCard);\n                } catch (IOException ignored) {\n                }\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get graphics card info\", e);\n        } finally {\n            databaseCache = null;\n        }\n\n        return Collections.unmodifiableList(cards);\n    }\n\n    private LinuxGPUDetector() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxHardwareDetector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.linux;\n\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;\nimport org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;\nimport org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class LinuxHardwareDetector extends HardwareDetector {\n\n    @Override\n    public @Nullable CentralProcessor detectCentralProcessor() {\n        if (OperatingSystem.CURRENT_OS != OperatingSystem.LINUX)\n            return null;\n        CentralProcessor cpu = LinuxCPUDetector.detect();\n        return cpu != null ? cpu : super.detectCentralProcessor();\n    }\n\n    @Override\n    public List<GraphicsCard> detectGraphicsCards() {\n        if (OperatingSystem.CURRENT_OS != OperatingSystem.LINUX)\n            return null;\n        List<GraphicsCard> cards = LinuxGPUDetector.detect();\n        if (cards == null || cards.isEmpty()) {\n            List<GraphicsCard> fastfetchResults = super.detectGraphicsCards();\n            if (fastfetchResults != null) // Avoid overwriting empty lists with null\n                cards = fastfetchResults;\n        }\n        return cards;\n    }\n\n    private static final Path MEMINFO = Paths.get(\"/proc/meminfo\");\n    private static final Pattern MEMINFO_PATTERN = Pattern.compile(\"^.+:\\\\s*(?<value>\\\\d+)\\\\s*kB?$\");\n\n    private static long parseMemoryInfoLine(final String line) throws IOException {\n        Matcher matcher = MEMINFO_PATTERN.matcher(line);\n        if (!matcher.matches())\n            throw new IOException(\"Unable to parse line in /proc/meminfo: \" + line);\n\n        return Long.parseLong(matcher.group(\"value\")) * 1024;\n    }\n\n    @Override\n    public long getTotalMemorySize() {\n        try (BufferedReader reader = Files.newBufferedReader(MEMINFO)) {\n            String line;\n            while ((line = reader.readLine()) != null) {\n                if (line.startsWith(\"MemTotal:\")) {\n                    long total = parseMemoryInfoLine(line);\n                    if (total <= 0)\n                        throw new IOException(\"Invalid total memory size: \" + line + \" kB\");\n\n                    return total;\n                }\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to parse /proc/meminfo\", e);\n        }\n\n        return super.getTotalMemorySize();\n    }\n\n    @Override\n    public long getFreeMemorySize() {\n        try (BufferedReader reader = Files.newBufferedReader(MEMINFO)) {\n            long free = -1L;\n\n            String line;\n            while ((line = reader.readLine()) != null) {\n                if (line.startsWith(\"MemAvailable:\")) {\n                    long available = parseMemoryInfoLine(line);\n                    if (available < 0)\n                        throw new IOException(\"Invalid available memory size: \" + line + \" kB\");\n                    return available;\n                }\n\n                if (line.startsWith(\"MemFree:\"))\n                    free = parseMemoryInfoLine(line);\n            }\n\n            if (free >= 0)\n                return free;\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to parse /proc/meminfo\", e);\n        }\n\n        return super.getFreeMemorySize();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/macos/MacOSHardwareDetector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.macos;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport org.jackhuang.hmcl.util.KeyValuePairUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.gson.JsonUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;\nimport org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;\nimport org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;\nimport org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class MacOSHardwareDetector extends HardwareDetector {\n\n    @Override\n    public @Nullable CentralProcessor detectCentralProcessor() {\n        if (OperatingSystem.CURRENT_OS != OperatingSystem.MACOS)\n            return null;\n\n        try {\n            Map<String, String> values = SystemUtils.run(Arrays.asList(\"/usr/sbin/sysctl\", \"machdep.cpu\"),\n                    inputStream -> KeyValuePairUtils.loadPairs(\n                            new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET))));\n\n            String brandString = values.get(\"machdep.cpu.brand_string\");\n            String coreCount = values.get(\"machdep.cpu.core_count\");\n            String threadCount = values.get(\"machdep.cpu.thread_count\");\n            String coresPerPackage = values.get(\"machdep.cpu.cores_per_package\");\n\n            CentralProcessor.Builder builder = new CentralProcessor.Builder();\n\n            if (brandString != null) {\n                builder.setName(brandString);\n\n                String lower = brandString.toLowerCase(Locale.ROOT);\n                if (lower.startsWith(\"apple\"))\n                    builder.setVendor(HardwareVendor.APPLE);\n                else if (lower.startsWith(\"intel\"))\n                    builder.setVendor(HardwareVendor.INTEL);\n            } else\n                builder.setName(\"Unknown\");\n\n            if (coreCount != null || threadCount != null) {\n                int cores = coreCount != null ? Integer.parseInt(coreCount) : 0;\n                int threads = threadCount != null ? Integer.parseInt(threadCount) : 0;\n                int coresPerPackageCount = coresPerPackage != null ? Integer.parseInt(coresPerPackage) : 0;\n\n                if (cores > 0 && threads == 0)\n                    threads = cores;\n                else if (threads > 0 && cores == 0)\n                    cores = threads;\n\n                int packages = 1;\n                if (cores > 0 && coresPerPackageCount > 0)\n                    packages = Integer.max(cores / coresPerPackageCount, 1);\n\n                builder.setCores(new CentralProcessor.Cores(cores, threads, packages));\n            } else\n                builder.setCores(new CentralProcessor.Cores(Runtime.getRuntime().availableProcessors()));\n\n            return builder.build();\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get CPU info\", e);\n            return null;\n        }\n    }\n\n    @Override\n    public List<GraphicsCard> detectGraphicsCards() {\n        if (OperatingSystem.CURRENT_OS != OperatingSystem.MACOS)\n            return null;\n\n        String json = null;\n        try {\n            json = SystemUtils.run(\"/usr/sbin/system_profiler\", \"SPDisplaysDataType\", \"-json\");\n\n            JsonObject object = JsonUtils.GSON.fromJson(json, JsonObject.class);\n            JsonArray spDisplaysDataType = object.getAsJsonArray(\"SPDisplaysDataType\");\n            if (spDisplaysDataType != null) {\n                ArrayList<GraphicsCard> cards = new ArrayList<>();\n\n                for (JsonElement element : spDisplaysDataType) {\n                    JsonObject item = element.getAsJsonObject();\n\n                    JsonElement deviceType = item.get(\"sppci_device_type\");\n                    if (deviceType == null || !\"spdisplays_gpu\".equals(deviceType.getAsString()))\n                        continue;\n\n                    JsonElement model = item.getAsJsonPrimitive(\"sppci_model\");\n                    JsonElement vendor = item.getAsJsonPrimitive(\"spdisplays_vendor\");\n                    JsonElement bus = item.getAsJsonPrimitive(\"sppci_bus\");\n\n                    if (model == null)\n                        continue;\n\n                    GraphicsCard.Builder builder = GraphicsCard.builder()\n                            .setName(model.getAsString());\n\n                    if (vendor != null)\n                        builder.setVendor(HardwareVendor.of(StringUtils.removePrefix(vendor.getAsString(), \"sppci_vendor_\")));\n\n                    GraphicsCard.Type type = GraphicsCard.Type.Integrated;\n                    if (bus != null) {\n                        String lower = bus.getAsString().toLowerCase(Locale.ROOT);\n                        if (!lower.contains(\"builtin\") && !lower.contains(\"built_in\") & !lower.contains(\"built-in\"))\n                            type = GraphicsCard.Type.Discrete;\n                    }\n                    builder.setType(type);\n\n                    cards.add(builder.build());\n                }\n\n                return Collections.unmodifiableList(cards);\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get graphics card info\" + (json != null ? \": \" + json : \"\"), e);\n            return Collections.emptyList();\n        }\n\n        return Collections.emptyList();\n    }\n\n    private static final Pattern PAGE_SIZE_PATTERN = Pattern.compile(\"\\\\(page size of (?<size>\\\\d+) bytes\\\\)\");\n\n    @Override\n    public long getFreeMemorySize() {\n        vmStat:\n        try {\n            Map<String, String> stats = SystemUtils.run(List.of(\"/usr/bin/vm_stat\"),\n                    inputStream -> KeyValuePairUtils.loadPairs(\n                            new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET))));\n            String statistics = stats.get(\"Mach Virtual Memory Statistics\");\n\n            long pageSize;\n            long pagesFree;\n            long pagesInactive;\n            long pagesSpeculative;\n\n            if (statistics != null) {\n                Matcher matcher = PAGE_SIZE_PATTERN.matcher(statistics);\n                if (matcher.find()) {\n                    pageSize = Long.parseLong(matcher.group(\"size\"));\n                } else {\n                    break vmStat;\n                }\n            } else {\n                break vmStat;\n            }\n\n            String pagesFreeStr = stats.get(\"Pages free\");\n            if (pagesFreeStr != null && pagesFreeStr.endsWith(\".\")) {\n                pagesFree = Long.parseUnsignedLong(pagesFreeStr, 0, pagesFreeStr.length() - 1, 10);\n            } else {\n                break vmStat;\n            }\n\n            String pagesInactiveStr = stats.get(\"Pages inactive\");\n            if (pagesInactiveStr != null && pagesInactiveStr.endsWith(\".\")) {\n                pagesInactive = Long.parseUnsignedLong(pagesInactiveStr, 0, pagesInactiveStr.length() - 1, 10);\n            } else {\n                break vmStat;\n            }\n\n            String pagesSpeculativeStr = stats.get(\"Pages speculative\");\n            if (pagesSpeculativeStr != null && pagesSpeculativeStr.endsWith(\".\")) {\n                pagesSpeculative = Long.parseUnsignedLong(pagesSpeculativeStr, 0, pagesSpeculativeStr.length() - 1, 10);\n            } else {\n                break vmStat;\n            }\n\n            long available = (pagesFree + pagesSpeculative + pagesInactive) * pageSize;\n            if (available > 0) {\n                return available;\n            }\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to parse vm_stat output\", e);\n        }\n\n        return super.getFreeMemorySize();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/Advapi32.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\nimport com.sun.jna.Pointer;\nimport com.sun.jna.WString;\nimport com.sun.jna.ptr.IntByReference;\nimport com.sun.jna.ptr.PointerByReference;\nimport com.sun.jna.win32.StdCallLibrary;\nimport org.jackhuang.hmcl.util.platform.NativeUtils;\n\n/**\n * @author Glavo\n */\npublic interface Advapi32 extends StdCallLibrary {\n\n    Advapi32 INSTANCE = NativeUtils.USE_JNA && com.sun.jna.Platform.isWindows()\n            ? NativeUtils.load(\"advapi32\", Advapi32.class)\n            : null;\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winreg/nf-winreg-regopenkeyexw\">RegOpenKeyExW function</a>\n     */\n    int RegOpenKeyExW(Pointer hKey, WString lpSubKey, int ulOptions, int samDesired, PointerByReference phkResult);\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winreg/nf-winreg-regclosekey\">RegCloseKey function</a>\n     */\n    int RegCloseKey(Pointer hKey);\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winreg/nf-winreg-regqueryvalueexw\">RegQueryValueExW function</a>\n     */\n    int RegQueryValueExW(Pointer hKey, WString lpValueName, Pointer lpReserved, IntByReference lpType, Pointer lpData, IntByReference lpcbData);\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winreg/nf-winreg-regenumkeyexw\">RegEnumKeyExW function</a>\n     */\n    int RegEnumKeyExW(Pointer hKey, int dwIndex,\n                      Pointer lpName, IntByReference lpcchName,\n                      IntByReference lpReserved,\n                      Pointer lpClass, IntByReference lpcchClass,\n                      Pointer lpftLastWriteTime);\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/Dwmapi.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\nimport com.sun.jna.PointerType;\nimport com.sun.jna.win32.StdCallLibrary;\nimport org.jackhuang.hmcl.util.platform.NativeUtils;\n\n/// @author Glavo\npublic interface Dwmapi extends StdCallLibrary {\n    Dwmapi INSTANCE = NativeUtils.USE_JNA && com.sun.jna.Platform.isWindows()\n            ? NativeUtils.load(\"dwmapi\", Dwmapi.class)\n            : null;\n\n    /// @see <a href=\"https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute\">DwmSetWindowAttribute function</a>\n    int DwmSetWindowAttribute(WinTypes.HANDLE hwnd, int dwAttribute, PointerType pvAttribute, int cbAttribute);\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/Kernel32.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\nimport com.sun.jna.Pointer;\nimport com.sun.jna.ptr.IntByReference;\nimport com.sun.jna.win32.StdCallLibrary;\nimport org.jackhuang.hmcl.util.platform.NativeUtils;\n\n/**\n * @author Glavo\n */\npublic interface Kernel32 extends StdCallLibrary {\n\n    Kernel32 INSTANCE = NativeUtils.USE_JNA && com.sun.jna.Platform.isWindows()\n            ? NativeUtils.load(\"kernel32\", Kernel32.class)\n            : null;\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror\">GetLastError function</a>\n     */\n    int GetLastError();\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-getversionexw\">GetVersionExW function</a>\n     */\n    boolean GetVersionExW(WinTypes.OSVERSIONINFOEXW lpVersionInfo);\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winnls/nf-winnls-getacp\">GetACP function</a>\n     */\n    int GetACP();\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winnls/nf-winnls-getusergeoid\">GetUserGeoID function</a>\n     */\n    int GetUserGeoID(int geoClass);\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex\">GlobalMemoryStatusEx function</a>\n     */\n    boolean GlobalMemoryStatusEx(WinTypes.MEMORYSTATUSEX lpBuffer);\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex\">GetLogicalProcessorInformationEx function</a>\n     */\n    boolean GetLogicalProcessorInformationEx(int relationshipType, Pointer buffer, IntByReference returnedLength);\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinConstants.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\n/**\n * @author Glavo\n */\npublic interface WinConstants {\n\n    // https://learn.microsoft.com/windows/win32/debug/system-error-codes--0-499-\n    int ERROR_SUCCESS = 0;\n    int ERROR_FILE_NOT_FOUND = 2;\n    int ERROR_PATH_NOT_FOUND = 3;\n    int ERROR_ACCESS_DENIED = 5;\n    int ERROR_INVALID_HANDLE = 6;\n    int ERROR_INVALID_DATA = 13;\n    int ERROR_NOT_SAME_DEVICE = 17;\n    int ERROR_NOT_READY = 21;\n    int ERROR_SHARING_VIOLATION = 32;\n    int ERROR_FILE_EXISTS = 80;\n    int ERROR_INVALID_PARAMETER = 87;\n    int ERROR_DISK_FULL = 112;\n    int ERROR_INSUFFICIENT_BUFFER = 122;\n    int ERROR_INVALID_LEVEL = 124;\n    int ERROR_DIR_NOT_ROOT = 144;\n    int ERROR_DIR_NOT_EMPTY = 145;\n    int ERROR_ALREADY_EXISTS = 183;\n    int ERROR_MORE_DATA = 234;\n    int ERROR_NO_MORE_ITEMS = 259;\n    int ERROR_DIRECTORY = 267;\n    int ERROR_NOTIFY_ENUM_DIR = 1022;\n    int ERROR_PRIVILEGE_NOT_HELD = 1314;\n    int ERROR_NONE_MAPPED = 1332;\n    int ERROR_CANT_ACCESS_FILE = 1920;\n    int ERROR_NOT_A_REPARSE_POINT = 4390;\n    int ERROR_INVALID_REPARSE_DATA = 4392;\n\n    // https://learn.microsoft.com/windows/win32/sysinfo/registry-key-security-and-access-rights\n    int KEY_QUERY_VALUE = 0x0001;\n    int KEY_ENUMERATE_SUB_KEYS = 0x0008;\n    int KEY_READ = 0x20019;\n\n    // https://learn.microsoft.com/windows/win32/sysinfo/registry-value-types\n    int REG_NONE = 0;\n    int REG_SZ = 1;\n    int REG_EXPAND_SZ = 2;\n    int REG_BINARY = 3;\n    int REG_DWORD_LITTLE_ENDIAN = 4;\n    int REG_DWORD_BIG_ENDIAN = 5;\n    int REG_LINK = 6;\n    int REG_MULTI_SZ = 7;\n    int REG_RESOURCE_LIST = 8;\n    int REG_FULL_RESOURCE_DESCRIPTOR = 9;\n    int REG_RESOURCE_REQUIREMENTS_LIST = 10;\n    int REG_QWORD_LITTLE_ENDIAN = 11;\n\n    // https://learn.microsoft.com/windows/win32/sysinfo/predefined-keys\n    long HKEY_CLASSES_ROOT = 0x80000000L;\n    long HKEY_CURRENT_USER = 0x80000001L;\n    long HKEY_LOCAL_MACHINE = 0x80000002L;\n    long HKEY_USERS = 0x80000003L;\n    long HKEY_PERFORMANCE_DATA = 0x80000004L;\n    long HKEY_PERFORMANCE_TEXT = 0x80000050L;\n    long HKEY_PERFORMANCE_NLSTEXT = 0x80000060L;\n    long HKEY_CURRENT_CONFIG = 0x80000005L;\n    long HKEY_DYN_DATA = 0x80000006L;\n    long HKEY_CURRENT_USER_LOCAL_SETTINGS = 0x80000007L;\n\n    // https://learn.microsoft.com/windows/win32/api/winnls/ne-winnls-sysgeoclass\n    int GEOCLASS_NATION = 16;\n    int GEOCLASS_REGION = 14;\n    int GEOCLASS_ALL = 0;\n\n    // https://learn.microsoft.com/windows/win32/api/winnt/ne-winnt-logical_processor_relationship\n    int RelationProcessorCore = 0;\n    int RelationNumaNode = 1;\n    int RelationCache = 2;\n    int RelationProcessorPackage = 3;\n    int RelationGroup = 4;\n    int RelationProcessorDie = 5;\n    int RelationNumaNodeEx = 6;\n    int RelationProcessorModule = 7;\n    int RelationAll = 0xffff;\n\n    // https://learn.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute\n    int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\nimport com.sun.jna.Memory;\nimport com.sun.jna.Pointer;\nimport com.sun.jna.WString;\nimport com.sun.jna.ptr.IntByReference;\nimport com.sun.jna.ptr.PointerByReference;\nimport org.jackhuang.hmcl.util.platform.NativeUtils;\n\nimport java.nio.ByteOrder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic abstract class WinReg {\n\n    public static final WinReg INSTANCE = NativeUtils.USE_JNA && Advapi32.INSTANCE != null\n            ? new JNAWinReg(Advapi32.INSTANCE) : null;\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/sysinfo/predefined-keys\">Predefined Keys</a>\n     */\n    public enum HKEY {\n        HKEY_CLASSES_ROOT(0x80000000),\n        HKEY_CURRENT_USER(0x80000001),\n        HKEY_LOCAL_MACHINE(0x80000002),\n        HKEY_USERS(0x80000003),\n        HKEY_PERFORMANCE_DATA(0x80000004),\n        HKEY_PERFORMANCE_TEXT(0x80000050),\n        HKEY_PERFORMANCE_NLSTEXT(0x80000060),\n        HKEY_CURRENT_CONFIG(0x80000005),\n        HKEY_DYN_DATA(0x80000006),\n        HKEY_CURRENT_USER_LOCAL_SETTINGS(0x80000007);\n        private final int value;\n\n        HKEY(int value) {\n            this.value = value;\n        }\n\n        public int getValue() {\n            return value;\n        }\n\n        public Pointer toPointer() {\n            return Pointer.createConstant((long) value);\n        }\n    }\n\n    public abstract boolean exists(HKEY root, String key);\n\n    public abstract Object queryValue(HKEY root, String key, String valueName);\n\n    public abstract List<String> querySubKeyNames(HKEY root, String key);\n\n    public List<String> querySubKeys(HKEY root, String key) {\n        List<String> list = querySubKeyNames(root, key);\n        if (list.isEmpty())\n            return list;\n\n        if (!(list instanceof ArrayList))\n            list = new ArrayList<>(list);\n\n        String prefix = key.endsWith(\"\\\\\") ? key : key + \"\\\\\";\n        list.replaceAll(str -> prefix + str);\n        return list;\n    }\n\n    private static final class JNAWinReg extends WinReg {\n\n        private final Advapi32 advapi32;\n\n        JNAWinReg(Advapi32 advapi32) {\n            this.advapi32 = advapi32;\n        }\n\n        @Override\n        public boolean exists(HKEY root, String key) {\n            PointerByReference phkKey = new PointerByReference();\n            int status = advapi32.RegOpenKeyExW(root.toPointer(), new WString(key), 0, WinConstants.KEY_READ, phkKey);\n\n            if (status == WinConstants.ERROR_SUCCESS) {\n                advapi32.RegCloseKey(phkKey.getValue());\n                return true;\n            } else\n                return false;\n        }\n\n        private static void checkLength(int expected, int actual) {\n            if (expected != actual) {\n                throw new IllegalStateException(\"Expected \" + expected + \" bytes, but got \" + actual);\n            }\n        }\n\n        @Override\n        public Object queryValue(HKEY root, String key, String valueName) {\n            PointerByReference phkKey = new PointerByReference();\n            if (advapi32.RegOpenKeyExW(root.toPointer(), new WString(key), 0, WinConstants.KEY_READ, phkKey) != WinConstants.ERROR_SUCCESS)\n                return null;\n\n            Pointer hkey = phkKey.getValue();\n            try {\n                IntByReference lpType = new IntByReference();\n                IntByReference lpcbData = new IntByReference();\n                int status = advapi32.RegQueryValueExW(hkey, new WString(valueName), null, lpType, null, lpcbData);\n                if (status != WinConstants.ERROR_SUCCESS) {\n                    if (status == WinConstants.ERROR_FILE_NOT_FOUND)\n                        return null;\n                    else\n                        throw new RuntimeException(\"Failed to query value: \" + status);\n                }\n\n                int type = lpType.getValue();\n                int cbData = lpcbData.getValue();\n\n                try (Memory lpData = new Memory(cbData)) {\n                    status = advapi32.RegQueryValueExW(hkey, new WString(valueName), null, null, lpData, lpcbData);\n                    if (status != WinConstants.ERROR_SUCCESS) {\n                        if (status == WinConstants.ERROR_FILE_NOT_FOUND)\n                            return null;\n                        else\n                            throw new RuntimeException(\"Failed to query value: \" + status);\n                    }\n\n                    checkLength(cbData, lpcbData.getValue());\n\n                    switch (type) {\n                        case WinConstants.REG_NONE:\n                        case WinConstants.REG_BINARY:\n                            return lpData.getByteArray(0L, cbData);\n                        case WinConstants.REG_DWORD_LITTLE_ENDIAN:\n                        case WinConstants.REG_DWORD_BIG_ENDIAN: {\n                            checkLength(4, cbData);\n                            int value = lpData.getInt(0L);\n                            ByteOrder expectedOrder = type == WinConstants.REG_DWORD_LITTLE_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;\n                            if (expectedOrder != ByteOrder.nativeOrder())\n                                value = Integer.reverseBytes(value);\n                            return value;\n                        }\n                        case WinConstants.REG_QWORD_LITTLE_ENDIAN: {\n                            checkLength(8, cbData);\n                            long value = lpData.getLong(0L);\n                            if (ByteOrder.nativeOrder() != ByteOrder.LITTLE_ENDIAN)\n                                value = Long.reverseBytes(value);\n                            return value;\n                        }\n                        case WinConstants.REG_SZ:\n                        case WinConstants.REG_EXPAND_SZ:\n                        case WinConstants.REG_LINK: {\n                            if (cbData < 2)\n                                throw new RuntimeException(\"Illegal length: \" + cbData);\n                            if (lpData.getChar(cbData - 2) != '\\0')\n                                throw new RuntimeException(\"The string does not end with \\\\0\");\n\n                            return lpData.getWideString(0L);\n                        }\n                        case WinConstants.REG_MULTI_SZ: {\n                            String str = new String(lpData.getByteArray(0L, cbData), StandardCharsets.UTF_16LE);\n\n                            var result = new ArrayList<String>();\n                            for (int start = 0; start < str.length(); ) {\n                                final int end = str.indexOf('\\0', start);\n                                if (end < 0) {\n                                    result.add(str.substring(start));\n                                    break;\n                                } else if (end == start && end == str.length() - 1) {\n                                    // The terminator of the array\n                                    break;\n                                }\n\n                                result.add(str.substring(start, end));\n                                start = end + 1;\n                            }\n\n                            return result.toArray(new String[0]);\n                        }\n                        default:\n                            throw new RuntimeException(\"Unknown reg type: \" + type);\n                    }\n                }\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to query value\", e);\n            } finally {\n                advapi32.RegCloseKey(hkey);\n            }\n\n            return null;\n        }\n\n        @Override\n        public List<String> querySubKeyNames(HKEY root, String key) {\n            PointerByReference phkKey = new PointerByReference();\n            if (advapi32.RegOpenKeyExW(root.toPointer(), new WString(key), 0, WinConstants.KEY_READ, phkKey) != WinConstants.ERROR_SUCCESS)\n                return Collections.emptyList();\n\n            Pointer hkey = phkKey.getValue();\n            try {\n                ArrayList<String> res = new ArrayList<>();\n                int maxKeyLength = 256;\n                try (Memory lpName = new Memory(maxKeyLength * 2)) {\n                    IntByReference lpcchName = new IntByReference();\n                    int i = 0;\n\n                    while (true) {\n                        lpcchName.setValue(maxKeyLength);\n                        int status = advapi32.RegEnumKeyExW(hkey, i, lpName, lpcchName, null, null, null, null);\n                        if (status == WinConstants.ERROR_SUCCESS) {\n                            res.add(lpName.getWideString(0L));\n                            i++;\n                        } else {\n                            if (status != WinConstants.ERROR_NO_MORE_ITEMS)\n                                LOG.warning(\"Failed to enum key: \" + status);\n                            break;\n                        }\n                    }\n                }\n                return res;\n            } catch (Throwable e) {\n                LOG.warning(\"Failed to query keys\", e);\n            } finally {\n                advapi32.RegCloseKey(hkey);\n            }\n\n            return Collections.emptyList();\n        }\n    }\n\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinTypes.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\nimport com.sun.jna.*;\nimport com.sun.jna.ptr.ByReference;\nimport com.sun.jna.ptr.LongByReference;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * @author Glavo\n */\npublic interface WinTypes {\n\n    /// @see <a href=\"https://learn.microsoft.com/windows/win32/winprog/windows-data-types\">Windows Data Types</a>\n    final class BOOL extends IntegerType {\n\n        public static final int SIZE = 4;\n\n        public BOOL() {\n            this(0);\n        }\n\n        public BOOL(boolean value) {\n            this(value ? 1L : 0L);\n        }\n\n        public BOOL(long value) {\n            super(SIZE, value, false);\n            assert value == 0 || value == 1;\n        }\n\n        public boolean booleanValue() {\n            return this.intValue() > 0;\n        }\n\n        @Override\n        public String toString() {\n            return Boolean.toString(booleanValue());\n        }\n\n    }\n\n    /// @see <a href=\"https://learn.microsoft.com/windows/win32/winprog/windows-data-types\">Windows Data Types</a>\n    final class BOOLByReference extends ByReference {\n\n        public BOOLByReference() {\n            this(new BOOL(0));\n        }\n\n        public BOOLByReference(BOOL value) {\n            super(BOOL.SIZE);\n            setValue(value);\n        }\n\n        public void setValue(BOOL value) {\n            getPointer().setInt(0, value.intValue());\n        }\n\n        public BOOL getValue() {\n            return new BOOL(getPointer().getInt(0));\n        }\n    }\n\n    /// @see <a href=\"https://learn.microsoft.com/windows/win32/winprog/windows-data-types\">Windows Data Types</a>\n    final class HANDLE extends PointerType {\n        public static final long INVALID_VALUE = Native.POINTER_SIZE == 8 ? -1 : 0xFFFFFFFFL;\n\n        public static final HANDLE INVALID = new HANDLE(Pointer.createConstant(INVALID_VALUE));\n\n        private boolean immutable;\n\n        public HANDLE() {\n        }\n\n        public HANDLE(Pointer p) {\n            setPointer(p);\n            immutable = true;\n        }\n\n        @Override\n        public Object fromNative(Object nativeValue, FromNativeContext context) {\n            Object o = super.fromNative(nativeValue, context);\n            if (INVALID.equals(o)) {\n                return INVALID;\n            }\n            return o;\n        }\n\n        @Override\n        public void setPointer(Pointer p) {\n            if (immutable) {\n                throw new UnsupportedOperationException(\"immutable reference\");\n            }\n\n            super.setPointer(p);\n        }\n\n        @Override\n        public String toString() {\n            return String.valueOf(getPointer());\n        }\n    }\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-osversioninfoexw\">OSVERSIONINFOEXW structure</a>\n     */\n    final class OSVERSIONINFOEXW extends Structure {\n        public int dwOSVersionInfoSize;\n        public int dwMajorVersion;\n        public int dwMinorVersion;\n        public int dwBuildNumber;\n        public int dwPlatformId;\n        public char[] szCSDVersion;\n        public short wServicePackMajor;\n        public short wServicePackMinor;\n        public short wSuiteMask;\n        public byte wProductType;\n        public byte wReserved;\n\n        public OSVERSIONINFOEXW() {\n            szCSDVersion = new char[128];\n            dwOSVersionInfoSize = size();\n        }\n\n        @Override\n        protected List<String> getFieldOrder() {\n            return Arrays.asList(\n                    \"dwOSVersionInfoSize\",\n                    \"dwMajorVersion\", \"dwMinorVersion\", \"dwBuildNumber\",\n                    \"dwPlatformId\",\n                    \"szCSDVersion\",\n                    \"wServicePackMajor\", \"wServicePackMinor\",\n                    \"wSuiteMask\", \"wProductType\",\n                    \"wReserved\"\n            );\n        }\n    }\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex\">MEMORYSTATUSEX structure</a>\n     */\n    final class MEMORYSTATUSEX extends Structure {\n        public int dwLength;\n        public int dwMemoryLoad;\n        public long ullTotalPhys;\n        public long ullAvailPhys;\n        public long ullTotalPageFile;\n        public long ullAvailPageFile;\n        public long ullTotalVirtual;\n        public long ullAvailVirtual;\n        public long ullAvailExtendedVirtual;\n\n        public MEMORYSTATUSEX() {\n            dwLength = size();\n        }\n\n        @Override\n        protected List<String> getFieldOrder() {\n            return Arrays.asList(\n                    \"dwLength\", \"dwMemoryLoad\",\n                    \"ullTotalPhys\", \"ullAvailPhys\", \"ullTotalPageFile\", \"ullAvailPageFile\",\n                    \"ullTotalVirtual\", \"ullAvailVirtual\", \"ullAvailExtendedVirtual\");\n        }\n    }\n\n    final class GROUP_AFFINITY extends Structure {\n        public LongByReference mask;\n        public short group;\n        public short[] reserved = new short[3];\n\n        public GROUP_AFFINITY(Pointer memory) {\n            super(memory);\n        }\n\n        public GROUP_AFFINITY() {\n            super();\n        }\n\n        @Override\n        protected List<String> getFieldOrder() {\n            return Arrays.asList(\n                    \"mask\", \"group\", \"reserved\"\n            );\n        }\n    }\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-processor_group_info\">PROCESSOR_GROUP_INFO structure</a>\n     */\n    final class PROCESSOR_GROUP_INFO extends Structure {\n        public byte maximumProcessorCount;\n        public byte activeProcessorCount;\n        public byte[] reserved = new byte[38];\n        public LongByReference activeProcessorMask;\n\n        public PROCESSOR_GROUP_INFO(Pointer memory) {\n            super(memory);\n        }\n\n        public PROCESSOR_GROUP_INFO() {\n            super();\n        }\n\n        @Override\n        protected List<String> getFieldOrder() {\n            return Arrays.asList(\"maximumProcessorCount\", \"activeProcessorCount\", \"reserved\", \"activeProcessorMask\");\n        }\n    }\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-processor_relationship\">PROCESSOR_RELATIONSHIP structure</a>\n     */\n    final class PROCESSOR_RELATIONSHIP extends Structure {\n\n        public byte flags;\n        public byte efficiencyClass;\n        public byte[] reserved = new byte[20];\n        public short groupCount;\n        public GROUP_AFFINITY[] groupMask = new GROUP_AFFINITY[1];\n\n        public PROCESSOR_RELATIONSHIP() {\n        }\n\n        public PROCESSOR_RELATIONSHIP(Pointer memory) {\n            super(memory);\n        }\n\n        @Override\n        protected List<String> getFieldOrder() {\n            return Arrays.asList(\"flags\", \"efficiencyClass\", \"reserved\", \"groupCount\", \"groupMask\");\n        }\n\n        @Override\n        public void read() {\n            readField(\"groupCount\");\n            if (groupCount != groupMask.length) {\n                groupMask = new GROUP_AFFINITY[groupCount];\n            }\n            super.read();\n        }\n    }\n\n    /**\n     * @see <a href=\"https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-group_relationship\">GROUP_RELATIONSHIP structure</a>\n     */\n    final class GROUP_RELATIONSHIP extends Structure {\n        public short maximumGroupCount;\n        public short activeGroupCount;\n        public byte[] reserved = new byte[20];\n        public PROCESSOR_GROUP_INFO[] groupInfo = new PROCESSOR_GROUP_INFO[1];\n\n        public GROUP_RELATIONSHIP() {\n        }\n\n        public GROUP_RELATIONSHIP(Pointer memory) {\n            super(memory);\n        }\n\n        @Override\n        protected List<String> getFieldOrder() {\n            return Arrays.asList(\"maximumGroupCount\", \"activeGroupCount\", \"reserved\", \"groupInfo\");\n        }\n\n        @Override\n        public void read() {\n            readField(\"activeGroupCount\");\n            if (activeGroupCount != groupInfo.length)\n                groupInfo = new PROCESSOR_GROUP_INFO[activeGroupCount];\n            super.read();\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsCPUDetector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\nimport com.sun.jna.Memory;\nimport com.sun.jna.ptr.IntByReference;\nimport org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;\nimport org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * @author Glavo\n */\nfinal class WindowsCPUDetector {\n\n    private static void detectName(CentralProcessor.Builder builder, WinReg reg) {\n        Object name = reg.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE, \"HARDWARE\\\\DESCRIPTION\\\\System\\\\CentralProcessor\\\\0\", \"ProcessorNameString\");\n        Object vendor = reg.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE, \"HARDWARE\\\\DESCRIPTION\\\\System\\\\CentralProcessor\\\\0\", \"VendorIdentifier\");\n\n        if (name instanceof String)\n            builder.setName(CentralProcessor.cleanName((String) name));\n\n        if (vendor instanceof String)\n            builder.setVendor(HardwareVendor.of((String) vendor));\n    }\n\n    private static void detectCores(CentralProcessor.Builder builder, Kernel32 kernel32) {\n        int coresLogical = 0;\n        int coresPhysical = 0;\n        int packages = 0;\n\n        IntByReference length = new IntByReference();\n        if (!kernel32.GetLogicalProcessorInformationEx(WinConstants.RelationAll, null, length) && length.getValue() == 0)\n            throw new AssertionError(\"Failed to get logical processor information length: \" + kernel32.GetLastError());\n\n        try (Memory pProcessorInfo = new Memory(Integer.toUnsignedLong(length.getValue()))) {\n            if (!kernel32.GetLogicalProcessorInformationEx(WinConstants.RelationAll, pProcessorInfo, length))\n                throw new AssertionError(\"Failed to get logical processor information length: \" + kernel32.GetLastError());\n\n            for (long offset = 0L; offset < pProcessorInfo.size(); ) {\n                int relationship = pProcessorInfo.getInt(offset);\n                long size = Integer.toUnsignedLong(pProcessorInfo.getInt(offset + 4L));\n\n                if (relationship == WinConstants.RelationGroup) {\n                    WinTypes.GROUP_RELATIONSHIP groupRelationship = new WinTypes.GROUP_RELATIONSHIP(pProcessorInfo.share(offset + 8L, size - 8L));\n                    groupRelationship.read();\n\n                    int activeGroupCount = Short.toUnsignedInt(groupRelationship.activeGroupCount);\n                    for (int i = 0; i < activeGroupCount; i++) {\n                        coresLogical += Short.toUnsignedInt(groupRelationship.groupInfo[i].maximumProcessorCount);\n                    }\n                } else if (relationship == WinConstants.RelationProcessorCore)\n                    coresPhysical++;\n                else if (relationship == WinConstants.RelationProcessorPackage)\n                    packages++;\n\n                offset += size;\n            }\n        }\n\n        builder.setCores(new CentralProcessor.Cores(coresPhysical, coresLogical, packages));\n    }\n\n    static @Nullable CentralProcessor detect() {\n        WinReg reg = WinReg.INSTANCE;\n        Kernel32 kernel32 = Kernel32.INSTANCE;\n        if (reg == null)\n            return null;\n\n        CentralProcessor.Builder builder = new CentralProcessor.Builder();\n        detectName(builder, reg);\n        if (kernel32 != null)\n            detectCores(builder, kernel32);\n        return builder.build();\n    }\n\n    private WindowsCPUDetector() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsGPUDetector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\nimport org.jackhuang.hmcl.util.KeyValuePairUtils;\nimport org.jackhuang.hmcl.util.StringUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.SystemUtils;\nimport org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;\nimport org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\nfinal class WindowsGPUDetector {\n\n    private static GraphicsCard.Type fromDacType(String adapterDACType) {\n        if (StringUtils.isBlank(adapterDACType)\n                || \"Internal\".equalsIgnoreCase(adapterDACType)\n                || \"InternalDAC\".equalsIgnoreCase(adapterDACType)) {\n            return GraphicsCard.Type.Integrated;\n        } else {\n            return GraphicsCard.Type.Discrete;\n        }\n    }\n\n    private static List<GraphicsCard> detectByCim() {\n        try {\n            String getCimInstance = OperatingSystem.SYSTEM_VERSION.getVersion().startsWith(\"6.1\")\n                    ? \"Get-WmiObject\"\n                    : \"Get-CimInstance\";\n\n            List<Map<String, String>> videoControllers = SystemUtils.run(Arrays.asList(\n                            \"powershell.exe\",\n                            \"-NoProfile\",\n                            \"-Command\",\n                            String.join(\" | \",\n                                    getCimInstance + \" -Class Win32_VideoController\",\n                                    \"Select-Object Name,AdapterCompatibility,DriverVersion,AdapterDACType\",\n                                    \"Format-List\"\n                            )),\n                    inputStream -> KeyValuePairUtils.loadList(new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET))));\n\n            ArrayList<GraphicsCard> cards = new ArrayList<>(videoControllers.size());\n            for (Map<String, String> videoController : videoControllers) {\n                String name = videoController.get(\"Name\");\n                String adapterCompatibility = videoController.get(\"AdapterCompatibility\");\n                String driverVersion = videoController.get(\"DriverVersion\");\n                String adapterDACType = videoController.get(\"AdapterDACType\");\n\n                if (StringUtils.isNotBlank(name)) {\n                    cards.add(GraphicsCard.builder().setName(GraphicsCard.cleanName(name))\n                            .setVendor(HardwareVendor.of(adapterCompatibility))\n                            .setDriverVersion(driverVersion)\n                            .setType(fromDacType(adapterDACType))\n                            .build()\n                    );\n                }\n            }\n\n            return cards;\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get graphics card info\", e);\n            return List.of();\n        }\n    }\n\n    private static @Nullable String regValueToString(Object object) {\n        if (object == null) {\n            return null;\n        } else if (object instanceof String[]) {\n            return String.join(\" \", (String[]) object);\n        } else if (object instanceof byte[]) {\n            return new String((byte[]) object, StandardCharsets.UTF_16LE)\n                    .replace(\"\\0\", \"\");\n        } else {\n            return object.toString();\n        }\n    }\n\n    private static List<GraphicsCard> detectByRegistry(WinReg reg) {\n        final WinReg.HKEY hkey = WinReg.HKEY.HKEY_LOCAL_MACHINE;\n        final String displayDevices = \"SYSTEM\\\\CurrentControlSet\\\\Control\\\\Class\\\\{4d36e968-e325-11ce-bfc1-08002be10318}\\\\\";\n        final Pattern graphicsCardPattern = Pattern.compile(\"\\\\\\\\[0-9]+\\\\\\\\?$\");\n\n        var result = new ArrayList<GraphicsCard>();\n        for (String subkey : reg.querySubKeys(hkey, displayDevices)) {\n            if (!graphicsCardPattern.matcher(subkey).find())\n                continue;\n\n            String name = regValueToString(reg.queryValue(hkey, subkey, \"HardwareInformation.AdapterString\"));\n            String vendor = regValueToString(reg.queryValue(hkey, subkey, \"ProviderName\"));\n            String driverVersion = regValueToString(reg.queryValue(hkey, subkey, \"DriverVersion\"));\n            String dacType = regValueToString(reg.queryValue(hkey, subkey, \"HardwareInformation.DacType\"));\n\n            GraphicsCard.Builder builder = GraphicsCard.builder();\n            if (name != null)\n                builder.setName(GraphicsCard.cleanName(name));\n            if (vendor != null)\n                builder.setVendor(HardwareVendor.of(vendor));\n            if (driverVersion != null)\n                builder.setDriverVersion(driverVersion);\n            if (dacType != null)\n                builder.setType(fromDacType(dacType));\n            result.add(builder.build());\n        }\n        return result;\n    }\n\n    static @Nullable List<GraphicsCard> detect() {\n        try {\n            WinReg reg = WinReg.INSTANCE;\n            if (reg != null) {\n                List<GraphicsCard> res = detectByRegistry(reg);\n                if (!res.isEmpty())\n                    return res;\n            }\n            return detectByCim();\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to get graphics cards\", e);\n            return null;\n        }\n    }\n\n    private WindowsGPUDetector() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsHardwareDetector.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\nimport org.jackhuang.hmcl.util.platform.NativeUtils;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;\nimport org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;\nimport org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic final class WindowsHardwareDetector extends HardwareDetector {\n\n    @Override\n    public @Nullable CentralProcessor detectCentralProcessor() {\n        if (!OperatingSystem.isWindows7OrLater())\n            return null;\n        return WindowsCPUDetector.detect();\n    }\n\n    @Override\n    public List<GraphicsCard> detectGraphicsCards() {\n        if (!OperatingSystem.isWindows7OrLater())\n            return null;\n        return WindowsGPUDetector.detect();\n    }\n\n    @Override\n    public long getTotalMemorySize() {\n        if (NativeUtils.USE_JNA) {\n            Kernel32 kernel32 = Kernel32.INSTANCE;\n            if (kernel32 != null) {\n                WinTypes.MEMORYSTATUSEX status = new WinTypes.MEMORYSTATUSEX();\n                if (kernel32.GlobalMemoryStatusEx(status))\n                    return status.ullTotalPhys;\n                else\n                    LOG.warning(\"Failed to get memory status: \" + kernel32.GetLastError());\n            }\n        }\n\n        return super.getTotalMemorySize();\n    }\n\n    @Override\n    public long getFreeMemorySize() {\n        if (NativeUtils.USE_JNA) {\n            Kernel32 kernel32 = Kernel32.INSTANCE;\n            if (kernel32 != null) {\n                WinTypes.MEMORYSTATUSEX status = new WinTypes.MEMORYSTATUSEX();\n                if (kernel32.GlobalMemoryStatusEx(status))\n                    return status.ullAvailPhys;\n                else\n                    LOG.warning(\"Failed to get memory status: \" + kernel32.GetLastError());\n            }\n        }\n\n        return super.getFreeMemorySize();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/skin/InvalidSkinException.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.skin;\n\npublic class InvalidSkinException extends Exception {\n\n    public InvalidSkinException() {}\n\n    public InvalidSkinException(String message) {\n        super(message);\n    }\n\n    public InvalidSkinException(Throwable cause) {\n        super(cause);\n    }\n\n    public InvalidSkinException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/skin/NormalizedSkin.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.skin;\n\nimport javafx.scene.image.Image;\nimport javafx.scene.image.PixelReader;\nimport javafx.scene.image.PixelWriter;\nimport javafx.scene.image.WritableImage;\n\n/**\n * Describes a Minecraft 1.8+ skin (64x64).\n * Old format skins are converted to the new format.\n *\n * @author yushijinhun\n */\npublic final class NormalizedSkin {\n\n    private static void copyImage(Image src, WritableImage dst, int sx, int sy, int dx, int dy, int w, int h, boolean flipHorizontal) {\n        PixelReader reader = src.getPixelReader();\n        PixelWriter writer = dst.getPixelWriter();\n\n        for (int y = 0; y < h; y++) {\n            for (int x = 0; x < w; x++) {\n                int pixel = reader.getArgb(sx + x, sy + y);\n                writer.setArgb(dx + (flipHorizontal ? w - x - 1 : x), dy + y, pixel);\n            }\n        }\n    }\n\n    private final Image texture;\n    private final WritableImage normalizedTexture;\n    private final int scale;\n    private final boolean oldFormat;\n\n    public NormalizedSkin(Image texture) throws InvalidSkinException {\n        this.texture = texture;\n\n        // check format\n        int w = (int) texture.getWidth();\n        int h = (int) texture.getHeight();\n        if (w % 64 != 0) {\n            throw new InvalidSkinException(\"Invalid size \" + w + \"x\" + h);\n        }\n        if (w == h) {\n            oldFormat = false;\n        } else if (w == h * 2) {\n            oldFormat = true;\n        } else {\n            throw new InvalidSkinException(\"Invalid size \" + w + \"x\" + h);\n        }\n\n        // compute scale\n        scale = w / 64;\n\n        normalizedTexture = new WritableImage(w, w);\n        copyImage(texture, normalizedTexture, 0, 0, 0, 0, w, h, false);\n        if (oldFormat) {\n            convertOldSkin();\n        }\n    }\n\n    private void convertOldSkin() {\n        copyImageRelative(4, 16, 20, 48, 4, 4, true); // Top Leg\n        copyImageRelative(8, 16, 24, 48, 4, 4, true); // Bottom Leg\n        copyImageRelative(0, 20, 24, 52, 4, 12, true); // Outer Leg\n        copyImageRelative(4, 20, 20, 52, 4, 12, true); // Front Leg\n        copyImageRelative(8, 20, 16, 52, 4, 12, true); // Inner Leg\n        copyImageRelative(12, 20, 28, 52, 4, 12, true); // Back Leg\n        copyImageRelative(44, 16, 36, 48, 4, 4, true); // Top Arm\n        copyImageRelative(48, 16, 40, 48, 4, 4, true); // Bottom Arm\n        copyImageRelative(40, 20, 40, 52, 4, 12, true); // Outer Arm\n        copyImageRelative(44, 20, 36, 52, 4, 12, true); // Front Arm\n        copyImageRelative(48, 20, 32, 52, 4, 12, true); // Inner Arm\n        copyImageRelative(52, 20, 44, 52, 4, 12, true); // Back Arm\n    }\n\n    private void copyImageRelative(int sx, int sy, int dx, int dy, int w, int h, boolean flipHorizontal) {\n        copyImage(normalizedTexture, normalizedTexture, sx * scale, sy * scale, dx * scale, dy * scale, w * scale, h * scale, flipHorizontal);\n    }\n\n    public Image getOriginalTexture() {\n        return texture;\n    }\n\n    public Image getNormalizedTexture() {\n        return normalizedTexture;\n    }\n\n    public int getScale() {\n        return scale;\n    }\n\n    public boolean isOldFormat() {\n        return oldFormat;\n    }\n\n    public boolean isSlim() {\n        return (hasTransparencyRelative(50, 16, 2, 4) ||\n                hasTransparencyRelative(54, 20, 2, 12) ||\n                hasTransparencyRelative(42, 48, 2, 4) ||\n                hasTransparencyRelative(46, 52, 2, 12)) ||\n                (isAreaBlackRelative(50, 16, 2, 4) &&\n                        isAreaBlackRelative(54, 20, 2, 12) &&\n                        isAreaBlackRelative(42, 48, 2, 4) &&\n                        isAreaBlackRelative(46, 52, 2, 12));\n    }\n\n    private boolean hasTransparencyRelative(int x0, int y0, int w, int h) {\n        PixelReader reader = normalizedTexture.getPixelReader();\n        x0 *= scale;\n        y0 *= scale;\n        w *= scale;\n        h *= scale;\n        for (int y = 0; y < h; y++) {\n            for (int x = 0; x < w; x++) {\n                int pixel = reader.getArgb(x0 + x, y0 + y);\n                if (pixel >>> 24 != 0xff) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private boolean isAreaBlackRelative(int x0, int y0, int w, int h) {\n        PixelReader reader = normalizedTexture.getPixelReader();\n        x0 *= scale;\n        y0 *= scale;\n        w *= scale;\n        h *= scale;\n        for (int y = 0; y < h; y++) {\n            for (int x = 0; x < w; x++) {\n                int pixel = reader.getArgb(x0 + x, y0 + y);\n                if (pixel != 0xff000000) {\n                    return false;\n                }\n            }\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/ArchiveFileTree.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.tree;\n\nimport kala.compress.archivers.ArchiveEntry;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.UnmodifiableView;\n\nimport java.io.*;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.nio.file.attribute.FileTime;\nimport java.util.*;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/// @author Glavo\npublic abstract class ArchiveFileTree<R, E extends ArchiveEntry> implements Closeable {\n\n    public static ArchiveFileTree<?, ?> open(Path file) throws IOException {\n        Path namePath = file.getFileName();\n        if (namePath == null) {\n            throw new IOException(file + \" is not a valid archive file\");\n        }\n\n        String name = namePath.toString();\n        if (name.endsWith(\".jar\") || name.endsWith(\".zip\")) {\n            return new ZipFileTree(new ZipArchiveReader(file));\n        } else if (name.endsWith(\".tar\") || name.endsWith(\".tar.gz\") || name.endsWith(\".tgz\")) {\n            return TarFileTree.open(file);\n        } else {\n            throw new IOException(file + \" is not a valid archive file\");\n        }\n    }\n\n    protected final R reader;\n    protected final Dir<E> root = new Dir<>(\"\", \"\");\n\n    public ArchiveFileTree(R reader) {\n        this.reader = reader;\n    }\n\n    public R getReader() {\n        return reader;\n    }\n\n    public Dir<E> getRoot() {\n        return root;\n    }\n\n    public @Nullable E getEntry(@NotNull String entryPath) {\n        Dir<E> dir = root;\n        if (entryPath.indexOf('/') < 0) {\n            return dir.getFiles().get(entryPath);\n        } else {\n            String[] path = entryPath.split(\"/\");\n            if (path.length == 0)\n                return root.getEntry();\n\n            for (int i = 0; i < path.length - 1; i++) {\n                String item = path[i];\n                if (item.isEmpty())\n                    continue;\n                dir = dir.getSubDirs().get(item);\n                if (dir == null)\n                    return null;\n            }\n\n            String fileName = path[path.length - 1];\n            return dir.getFiles().get(fileName);\n        }\n    }\n\n    public @Nullable Dir<E> getDirectory(@NotNull String dirPath) {\n        Dir<E> dir = root;\n        if (dirPath.isEmpty()) {\n            return dir;\n        }\n        String[] path = dirPath.split(\"/\");\n        for (String item : path) {\n            if (item.isEmpty())\n                continue;\n            dir = dir.getSubDirs().get(item);\n            if (dir == null)\n                return null;\n        }\n        return dir;\n    }\n\n    protected void addEntry(E entry) throws IOException {\n        String[] path = entry.getName().split(\"/\");\n        List<String> pathList = Arrays.asList(path);\n\n        Dir<E> dir = root;\n\n        for (int i = 0, end = entry.isDirectory() ? path.length : path.length - 1; i < end; i++) {\n            String item = path[i];\n            if (item.equals(\".\"))\n                continue;\n            if (item.equals(\"..\") || item.isEmpty())\n                throw new IOException(\"Invalid entry: \" + entry.getName());\n\n            if (dir.files.containsKey(item)) {\n                throw new IOException(\"A file and a directory have the same name: \" + entry.getName());\n            }\n\n            final int nameEnd = i + 1;\n            dir = dir.subDirs.computeIfAbsent(item, name ->\n                    new Dir<>(name, String.join(\"/\", pathList.subList(0, nameEnd))));\n        }\n\n        if (entry.isDirectory()) {\n            if (dir.entry == null)\n                dir.entry = entry;\n            else if (!dir.entry.isDirectory())\n                throw new IOException(\"A file and a directory have the same name: \" + entry.getName());\n        } else {\n            String fileName = path[path.length - 1];\n\n            if (dir.subDirs.containsKey(fileName)) {\n                throw new IOException(\"A file and a directory have the same name: \" + entry.getName());\n            }\n\n            if (dir.files.containsKey(fileName)) {\n                throw new IOException(\"Duplicate entry: \" + entry.getName());\n            }\n\n            dir.files.put(fileName, entry);\n        }\n    }\n\n    public abstract InputStream getInputStream(E entry) throws IOException;\n\n    public @NotNull InputStream getInputStream(String entryPath) throws IOException {\n        E entry = getEntry(entryPath);\n        if (entry == null)\n            throw new FileNotFoundException(\"Entry not found: \" + entryPath);\n        return getInputStream(entry);\n    }\n\n    public BufferedReader getBufferedReader(@NotNull E entry) throws IOException {\n        return new BufferedReader(new InputStreamReader(getInputStream(entry), StandardCharsets.UTF_8));\n    }\n\n    public @NotNull BufferedReader getBufferedReader(String entryPath) throws IOException {\n        E entry = getEntry(entryPath);\n        if (entry == null)\n            throw new FileNotFoundException(\"Entry not found: \" + entryPath);\n        return getBufferedReader(entry);\n    }\n\n    public byte[] readBinaryEntry(@NotNull E entry) throws IOException {\n        try (InputStream input = getInputStream(entry)) {\n            return input.readAllBytes();\n        }\n    }\n\n    public String readTextEntry(@NotNull String entryPath) throws IOException {\n        E entry = getEntry(entryPath);\n        if (entry == null)\n            throw new FileNotFoundException(\"Entry not found: \" + entryPath);\n        return readTextEntry(entry);\n    }\n\n    public String readTextEntry(@NotNull E entry) throws IOException {\n        return new String(readBinaryEntry(entry), StandardCharsets.UTF_8);\n    }\n\n    protected void copyAttributes(@NotNull E source, @NotNull Path targetFile) throws IOException {\n        FileTime lastModifiedTime = source.getLastModifiedTime();\n        if (lastModifiedTime != null)\n            Files.setLastModifiedTime(targetFile, lastModifiedTime);\n    }\n\n    public void extractTo(@NotNull String entryPath, @NotNull Path targetFile) throws IOException {\n        E entry = getEntry(entryPath);\n        if (entry == null)\n            throw new FileNotFoundException(\"Entry not found: \" + entryPath);\n\n        extractTo(entry, targetFile);\n    }\n\n    public void extractTo(@NotNull E entry, @NotNull Path targetFile) throws IOException {\n        try (InputStream input = getInputStream(entry)) {\n            Files.copy(input, targetFile, StandardCopyOption.REPLACE_EXISTING);\n        }\n        try {\n            copyAttributes(entry, targetFile);\n        } catch (Throwable e) {\n            LOG.warning(\"Failed to copy attributes to \" + targetFile, e);\n        }\n    }\n\n    public abstract boolean isLink(E entry);\n\n    public abstract String getLink(E entry) throws IOException;\n\n    public abstract boolean isExecutable(E entry);\n\n    @Override\n    public abstract void close() throws IOException;\n\n    public static final class Dir<E extends ArchiveEntry> {\n        private final String name;\n        private final String fullName;\n        private E entry;\n\n        final Map<String, Dir<E>> subDirs = new HashMap<>();\n        final Map<String, E> files = new HashMap<>();\n\n        public Dir(String name, String fullName) {\n            this.name = name;\n            this.fullName = fullName;\n        }\n\n        public boolean isRoot() {\n            return name.isEmpty();\n        }\n\n        public @NotNull String getName() {\n            return name;\n        }\n\n        /// Get the normalized full path. Leading `/` and all `.` in the path will be removed.\n        public @NotNull String getFullName() {\n            return fullName;\n        }\n\n        public @Nullable E getEntry() {\n            return entry;\n        }\n\n        public @NotNull @UnmodifiableView Map<String, Dir<E>> getSubDirs() {\n            return subDirs;\n        }\n\n        public @NotNull @UnmodifiableView Map<String, E> getFiles() {\n            return files;\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/TarFileTree.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\n\npackage org.jackhuang.hmcl.util.tree;\n\nimport kala.compress.archivers.tar.TarArchiveEntry;\nimport kala.compress.archivers.tar.TarArchiveReader;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.*;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.nio.file.attribute.BasicFileAttributeView;\nimport java.util.zip.GZIPInputStream;\n\n/**\n * @author Glavo\n */\npublic final class TarFileTree extends ArchiveFileTree<TarArchiveReader, TarArchiveEntry> {\n\n    public static TarFileTree open(Path file) throws IOException {\n        String fileName = file.getFileName().toString();\n\n        if (fileName.endsWith(\".tar.gz\") || fileName.endsWith(\".tgz\")) {\n            Path tempFile = Files.createTempFile(\"hmcl-\", \".tar\");\n            TarArchiveReader tarFile;\n            try (GZIPInputStream input = new GZIPInputStream(Files.newInputStream(file));\n                 OutputStream output = Files.newOutputStream(tempFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)\n            ) {\n                input.transferTo(output);\n                tarFile = new TarArchiveReader(tempFile);\n            } catch (Throwable e) {\n                try {\n                    Files.deleteIfExists(tempFile);\n                } catch (Throwable e2) {\n                    e.addSuppressed(e2);\n                }\n                throw e;\n            }\n\n            return new TarFileTree(tarFile, tempFile);\n        } else {\n            return new TarFileTree(new TarArchiveReader(file), null);\n        }\n    }\n\n    private final Path tempFile;\n    private final Thread shutdownHook;\n\n    public TarFileTree(TarArchiveReader file, Path tempFile) throws IOException {\n        super(file);\n        this.tempFile = tempFile;\n        try {\n            for (TarArchiveEntry entry : file.getEntries()) {\n                addEntry(entry);\n            }\n        } catch (Throwable e) {\n            try {\n                file.close();\n            } catch (Throwable e2) {\n                e.addSuppressed(e2);\n            }\n\n            if (tempFile != null) {\n                try {\n                    Files.deleteIfExists(tempFile);\n                } catch (Throwable e2) {\n                    e.addSuppressed(e2);\n                }\n            }\n\n            throw e;\n        }\n\n        if (tempFile != null) {\n            this.shutdownHook = new Thread(() -> {\n                try {\n                    Files.deleteIfExists(tempFile);\n                } catch (Throwable ignored) {\n                }\n            });\n            Runtime.getRuntime().addShutdownHook(shutdownHook);\n        } else\n            this.shutdownHook = null;\n    }\n\n    @Override\n    protected void copyAttributes(@NotNull TarArchiveEntry source, @NotNull Path targetFile) throws IOException {\n        var fileAttributeView = Files.getFileAttributeView(targetFile, BasicFileAttributeView.class);\n        if (fileAttributeView == null)\n            return;\n\n        fileAttributeView.setTimes(\n                source.getLastModifiedTime(),\n                source.getLastAccessTime(),\n                source.getCreationTime()\n        );\n    }\n\n    @Override\n    public InputStream getInputStream(TarArchiveEntry entry) throws IOException {\n        return reader.getInputStream(entry);\n    }\n\n    @Override\n    public boolean isLink(TarArchiveEntry entry) {\n        return entry.isSymbolicLink();\n    }\n\n    @Override\n    public String getLink(TarArchiveEntry entry) throws IOException {\n        return entry.getLinkName();\n    }\n\n    @Override\n    public boolean isExecutable(TarArchiveEntry entry) {\n        return entry.isFile() && (entry.getMode() & 0b1000000) != 0;\n    }\n\n    @Override\n    public void close() throws IOException {\n        try {\n            reader.close();\n        } finally {\n            if (tempFile != null) {\n                Runtime.getRuntime().removeShutdownHook(shutdownHook);\n                Files.deleteIfExists(tempFile);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/ZipFileTree.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.tree;\n\nimport kala.compress.archivers.zip.ZipArchiveEntry;\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.BasicFileAttributeView;\nimport java.nio.file.attribute.PosixFileAttributeView;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.util.EnumSet;\nimport java.util.Set;\n\n/**\n * @author Glavo\n */\npublic final class ZipFileTree extends ArchiveFileTree<ZipArchiveReader, ZipArchiveEntry> {\n    private final boolean closeReader;\n\n    public ZipFileTree(ZipArchiveReader file) throws IOException {\n        this(file, true);\n    }\n\n    public ZipFileTree(ZipArchiveReader file, boolean closeReader) throws IOException {\n        super(file);\n        this.closeReader = closeReader;\n        try {\n            for (ZipArchiveEntry zipArchiveEntry : file.getEntries()) {\n                addEntry(zipArchiveEntry);\n            }\n        } catch (Throwable e) {\n            if (closeReader)\n                IOUtils.closeQuietly(file, e);\n            throw e;\n        }\n    }\n\n    @Override\n    public void close() throws IOException {\n        if (closeReader)\n            reader.close();\n    }\n\n    @Override\n    @SuppressWarnings(\"OctalInteger\")\n    protected void copyAttributes(@NotNull ZipArchiveEntry source, @NotNull Path targetFile) throws IOException {\n        BasicFileAttributeView targetView = Files.getFileAttributeView(targetFile, PosixFileAttributeView.class);\n\n        // target might not support posix even if source does\n        if (targetView == null)\n            targetView = Files.getFileAttributeView(targetFile, BasicFileAttributeView.class);\n\n        if (targetView == null)\n            return;\n\n        targetView.setTimes(\n                source.getLastModifiedTime(),\n                source.getLastAccessTime(),\n                source.getCreationTime()\n        );\n\n        int unixMode = source.getUnixMode();\n        if (unixMode != 0 && targetView instanceof PosixFileAttributeView posixView) {\n            Set<PosixFilePermission> permissions = EnumSet.noneOf(PosixFilePermission.class);\n\n            // Owner permissions\n            if ((unixMode & 0400) != 0) permissions.add(PosixFilePermission.OWNER_READ);\n            if ((unixMode & 0200) != 0) permissions.add(PosixFilePermission.OWNER_WRITE);\n            if ((unixMode & 0100) != 0) permissions.add(PosixFilePermission.OWNER_EXECUTE);\n\n            // Group permissions\n            if ((unixMode & 0040) != 0) permissions.add(PosixFilePermission.GROUP_READ);\n            if ((unixMode & 0020) != 0) permissions.add(PosixFilePermission.GROUP_WRITE);\n            if ((unixMode & 0010) != 0) permissions.add(PosixFilePermission.GROUP_EXECUTE);\n\n            // Others permissions\n            if ((unixMode & 0004) != 0) permissions.add(PosixFilePermission.OTHERS_READ);\n            if ((unixMode & 0002) != 0) permissions.add(PosixFilePermission.OTHERS_WRITE);\n            if ((unixMode & 0001) != 0) permissions.add(PosixFilePermission.OTHERS_EXECUTE);\n\n            posixView.setPermissions(permissions);\n        }\n    }\n\n    @Override\n    public InputStream getInputStream(ZipArchiveEntry entry) throws IOException {\n        return getReader().getInputStream(entry);\n    }\n\n    @Override\n    public boolean isLink(ZipArchiveEntry entry) {\n        return entry.isUnixSymlink();\n    }\n\n    @Override\n    public String getLink(ZipArchiveEntry entry) throws IOException {\n        return getReader().getUnixSymlink(entry);\n    }\n\n    @Override\n    public boolean isExecutable(ZipArchiveEntry entry) {\n        return !entry.isDirectory() && !entry.isUnixSymlink() && (entry.getUnixMode() & 0b1000000) != 0;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.versioning;\n\nimport org.jackhuang.hmcl.util.ToStringBuilder;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static org.jackhuang.hmcl.util.logging.Logger.LOG;\n\n/**\n * @author Glavo\n */\npublic abstract sealed class GameVersionNumber implements Comparable<GameVersionNumber> {\n\n    public static String[] getDefaultGameVersions() {\n        return Versions.DEFAULT_GAME_VERSIONS;\n    }\n\n    public static GameVersionNumber asGameVersion(String version) {\n        GameVersionNumber versionNumber = Versions.SPECIALS.get(version);\n        if (versionNumber != null)\n            return versionNumber;\n\n        try {\n            if (!version.isEmpty()) {\n                char ch = version.charAt(0);\n                switch (ch) {\n                    case 'r':\n                    case 'a':\n                    case 'b':\n                    case 'c':\n                    case 'i':\n                        return Old.parse(version);\n                }\n\n                if (version.equals(\"0.0\"))\n                    return Release.ZERO;\n\n                if (version.length() >= 6 && version.charAt(2) == 'w')\n                    return LegacySnapshot.parse(version);\n\n                return Release.parse(version);\n            }\n        } catch (Throwable ignore) {\n        }\n\n        return new Special(version, version);\n    }\n\n    public static GameVersionNumber asGameVersion(Optional<String> version) {\n        return version.isPresent() ? asGameVersion(version.get()) : unknown();\n    }\n\n    public static GameVersionNumber unknown() {\n        return Release.ZERO;\n    }\n\n    public static int compare(String version1, String version2) {\n        return asGameVersion(version1).compareTo(asGameVersion(version2));\n    }\n\n    public static VersionRange<GameVersionNumber> between(String minimum, String maximum) {\n        return VersionRange.between(asGameVersion(minimum), asGameVersion(maximum));\n    }\n\n    public static VersionRange<GameVersionNumber> atLeast(String minimum) {\n        return VersionRange.atLeast(asGameVersion(minimum));\n    }\n\n    public static VersionRange<GameVersionNumber> atMost(String maximum) {\n        return VersionRange.atMost(asGameVersion(maximum));\n    }\n\n    final String value;\n    final String normalized;\n\n    GameVersionNumber(String value, String normalized) {\n        this.value = value;\n        this.normalized = normalized;\n    }\n\n    public boolean isAprilFools() {\n        if (this instanceof Special) {\n            String normalizedVersion = this.toNormalizedString();\n            return !normalizedVersion.startsWith(\"1.\") && !normalizedVersion.equals(\"13w12~\")\n                    || normalizedVersion.equals(\"1.RV-Pre1\");\n        }\n\n        if (this instanceof LegacySnapshot snapshot) {\n            return snapshot.intValue == LegacySnapshot.toInt(15, 14, 'a', false);\n        }\n\n        return false;\n    }\n\n    enum Type {\n        PRE_CLASSIC, CLASSIC, INDEV, INFDEV, ALPHA, BETA, NEW\n    }\n\n    abstract Type getType();\n\n    abstract int compareToImpl(@NotNull GameVersionNumber other);\n\n    public int compareTo(@NotNull String other) {\n        return this.compareTo(asGameVersion(other));\n    }\n\n    @Override\n    public int compareTo(@NotNull GameVersionNumber other) {\n        if (this.getType() != other.getType())\n            return Integer.compare(this.getType().ordinal(), other.getType().ordinal());\n\n        return compareToImpl(other);\n    }\n\n    /// @see #isAtLeast(String, String, boolean)\n    public boolean isAtLeast(@NotNull String releaseVersion, @NotNull String snapshotVersion) {\n        return isAtLeast(releaseVersion, snapshotVersion, false);\n    }\n\n    /// When comparing between Release Version and Snapshot Version, it is necessary to load `/assets/game/versions.txt` and perform a lookup, which is less efficient.\n    /// Therefore, when checking whether a version contains a certain feature, you should use this method and provide both the first release version and the exact snapshot version that introduced the feature,\n    /// so that the comparison can be performed quickly without a lookup.\n    ///\n    /// For example, the datapack feature was introduced in Minecraft 1.13, and more specifically in snapshot `17w43a`.\n    /// So you can test whether a game version supports datapacks like this:\n    ///\n    /// ```java\n    /// GameVersionNumber.asVersion(\"...\").isAtLeast(\"1.13\", \"17w43a\");\n    ///```\n    ///\n    /// @param strictReleaseVersion When `strictReleaseVersion` is `false`, `releaseVersion` is considered less than\n    ///                             its corresponding pre/rc versions.\n    public boolean isAtLeast(@NotNull String releaseVersion, @NotNull String snapshotVersion, boolean strictReleaseVersion) {\n        if (this instanceof Release self) {\n            Release other;\n            if (strictReleaseVersion) {\n                other = Release.parse(releaseVersion);\n            } else {\n                other = Release.parseSimple(releaseVersion);\n            }\n\n            return self.compareToRelease(other) >= 0;\n        } else {\n            return this.compareTo(LegacySnapshot.parse(snapshotVersion)) >= 0;\n        }\n    }\n\n    public String toNormalizedString() {\n        return normalized;\n    }\n\n    @Override\n    public String toString() {\n        return value;\n    }\n\n    protected ToStringBuilder buildDebugString() {\n        return new ToStringBuilder(this)\n                .append(\"value\", value)\n                .append(\"normalized\", normalized)\n                .append(\"type\", getType());\n    }\n\n    public final String toDebugString() {\n        return buildDebugString().toString();\n    }\n\n    public static final class Old extends GameVersionNumber {\n        static Old parse(String value) {\n            if (value.isEmpty())\n                throw new IllegalArgumentException(\"Empty old version number\");\n\n            Type type;\n            int prefixLength = 1;\n            switch (value.charAt(0)) {\n                case 'r':\n                    if (!value.startsWith(\"rd-\")) {\n                        throw new IllegalArgumentException(value);\n                    }\n\n                    type = Type.PRE_CLASSIC;\n                    prefixLength = \"rd-\".length();\n                    break;\n                case 'i':\n                    if (value.startsWith(\"inf-\")) {\n                        type = Type.INFDEV;\n                        prefixLength = \"inf-\".length();\n                    } else if (value.startsWith(\"in-\")) {\n                        type = Type.INDEV;\n                        prefixLength = \"in-\".length();\n                    } else {\n                        throw new IllegalArgumentException(value);\n                    }\n                    break;\n                case 'a':\n                    type = Type.ALPHA;\n                    break;\n                case 'b':\n                    type = Type.BETA;\n                    break;\n                case 'c':\n                    type = Type.CLASSIC;\n                    break;\n                default:\n                    throw new IllegalArgumentException(value);\n            }\n\n            if (value.length() < prefixLength + 1 || !Character.isDigit(value.charAt(prefixLength)))\n                throw new IllegalArgumentException(value);\n\n            return new Old(value, type, VersionNumber.asVersion(value.substring(prefixLength)));\n        }\n\n        final Type type;\n        final VersionNumber versionNumber;\n\n        private Old(String value, Type type, VersionNumber versionNumber) {\n            super(value, value);\n            this.type = type;\n            this.versionNumber = versionNumber;\n        }\n\n        @Override\n        Type getType() {\n            return type;\n        }\n\n        @Override\n        int compareToImpl(@NotNull GameVersionNumber other) {\n            return this.versionNumber.compareTo(((Old) other).versionNumber);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(type, versionNumber);\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            return o instanceof Old that\n                    && this.type == that.type\n                    && this.versionNumber.equals(that.versionNumber);\n        }\n    }\n\n    public static final class Release extends GameVersionNumber {\n        private static final int MINIMUM_YEAR_MAJOR_VERSION = 25;\n\n        public enum ReleaseType {\n            UNKNOWN(\"\"),\n            SNAPSHOT(\"-snapshot-\"),\n            PRE_RELEASE(\"-pre\"),\n            RELEASE_CANDIDATE(\"-rc\"),\n            GA(\"\");\n            private final String infix;\n\n            ReleaseType(String infix) {\n                this.infix = infix;\n            }\n        }\n\n        public enum Additional {\n            NONE(\"\"), UNOBFUSCATED(\"_unobfuscated\");\n            private final String suffix;\n\n            Additional(String suffix) {\n                this.suffix = suffix;\n            }\n        }\n\n        static final Release ZERO = new Release(\n                \"0.0\", \"0.0\",\n                0, 0, 0,\n                ReleaseType.UNKNOWN, VersionNumber.ZERO, Additional.NONE\n        );\n\n        private static final Pattern VERSION_PATTERN = Pattern.compile(\"(?<prefix>(?<major>1|[1-9]\\\\d+)\\\\.(?<minor>\\\\d+)(\\\\.(?<patch>[0-9]+))?)(?<suffix>.*)\");\n\n        static Release parse(String value) {\n            Matcher matcher = VERSION_PATTERN.matcher(value);\n            if (!matcher.matches()) {\n                throw new IllegalArgumentException(value);\n            }\n\n            int major = Integer.parseInt(matcher.group(\"major\"));\n            if (major != 1 && major < MINIMUM_YEAR_MAJOR_VERSION)\n                throw new IllegalArgumentException(value);\n\n            int minor = Integer.parseInt(matcher.group(\"minor\"));\n\n            String patchString = matcher.group(\"patch\");\n            int patch = patchString != null ? Integer.parseInt(patchString) : 0;\n\n            String suffix = matcher.group(\"suffix\");\n\n            ReleaseType releaseType;\n            VersionNumber eaVersion;\n            Additional additional = Additional.NONE;\n            boolean needNormalize = false;\n\n            if (suffix.endsWith(\"_unobfuscated\")) {\n                suffix = suffix.substring(0, suffix.length() - \"_unobfuscated\".length());\n                additional = Additional.UNOBFUSCATED;\n            } else if (suffix.endsWith(\" Unobfuscated\")) {\n                needNormalize = true;\n                suffix = suffix.substring(0, suffix.length() - \" Unobfuscated\".length());\n                additional = Additional.UNOBFUSCATED;\n            }\n\n            if (suffix.isEmpty()) {\n                releaseType = ReleaseType.GA;\n                eaVersion = VersionNumber.ZERO;\n            } else if (suffix.startsWith(\"-snapshot-\")) {\n                releaseType = ReleaseType.SNAPSHOT;\n                eaVersion = VersionNumber.asVersion(suffix.substring(\"-snapshot-\".length()));\n            } else if (suffix.startsWith(\" Snapshot \")) {\n                needNormalize = true;\n                releaseType = ReleaseType.SNAPSHOT;\n                eaVersion = VersionNumber.asVersion(suffix.substring(\" Snapshot \".length()));\n            } else if (suffix.startsWith(\"-pre-\")) {\n                needNormalize = true;\n                releaseType = ReleaseType.PRE_RELEASE;\n                eaVersion = VersionNumber.asVersion(suffix.substring(\"-pre-\".length()));\n            } else if (suffix.startsWith(\"-pre\")) {\n                releaseType = ReleaseType.PRE_RELEASE;\n                eaVersion = VersionNumber.asVersion(suffix.substring(\"-pre\".length()));\n            } else if (suffix.startsWith(\" Pre-Release \")) {\n                needNormalize = true;\n                releaseType = ReleaseType.PRE_RELEASE;\n                eaVersion = VersionNumber.asVersion(suffix.substring(\" Pre-Release \".length()));\n            } else if (suffix.startsWith(\" Pre-release \")) {\n                // https://github.com/HMCL-dev/HMCL/issues/5476\n                needNormalize = true;\n                releaseType = ReleaseType.PRE_RELEASE;\n                eaVersion = VersionNumber.asVersion(suffix.substring(\" Pre-release \".length()));\n            } else if (suffix.startsWith(\"-rc-\")) {\n                needNormalize = true;\n                releaseType = ReleaseType.RELEASE_CANDIDATE;\n                eaVersion = VersionNumber.asVersion(suffix.substring(\"-rc-\".length()));\n            } else if (suffix.startsWith(\"-rc\")) {\n                releaseType = ReleaseType.RELEASE_CANDIDATE;\n                eaVersion = VersionNumber.asVersion(suffix.substring(\"-rc\".length()));\n            } else if (suffix.startsWith(\" Release Candidate \")) {\n                needNormalize = true;\n                releaseType = ReleaseType.RELEASE_CANDIDATE;\n                eaVersion = VersionNumber.asVersion(suffix.substring(\" Release Candidate \".length()));\n            } else {\n                throw new IllegalArgumentException(value);\n            }\n\n            String normalized;\n            if (needNormalize) {\n                StringBuilder builder = new StringBuilder(value.length());\n                builder.append(matcher.group(\"prefix\"));\n                if (releaseType != ReleaseType.GA) {\n                    builder.append(releaseType.infix);\n                    builder.append(eaVersion);\n                }\n                builder.append(additional.suffix);\n                normalized = builder.toString();\n            } else {\n                normalized = value;\n            }\n\n            return new Release(value, normalized, major, minor, patch, releaseType, eaVersion, additional);\n        }\n\n        /// Quickly parses a simple format (`[1-9][0-9]+\\.[0-9]+(\\.[0-9]+)?`) release version.\n        /// The returned [#eaType] will be set to [ReleaseType#UNKNOWN], meaning it will be less than all pre/rc and official versions of this version.\n        ///\n        /// @see GameVersionNumber#isAtLeast(String, String)\n        static Release parseSimple(String value) {\n            int majorLength = getNumberLength(value, 0);\n            if (majorLength == 0 || value.length() < majorLength + 2 || value.charAt(majorLength) != '.')\n                throw new IllegalArgumentException(value);\n\n            int major = Integer.parseInt(value, 0, majorLength, 10);\n            if (major != 1 && major < MINIMUM_YEAR_MAJOR_VERSION)\n                throw new IllegalArgumentException(value);\n\n            final int minorOffset = majorLength + 1;\n\n            int minorLength = getNumberLength(value, minorOffset);\n            if (minorLength == 0)\n                throw new IllegalArgumentException(value);\n\n            try {\n                int minor = Integer.parseInt(value, minorOffset, minorOffset + minorLength, 10);\n                int patch = 0;\n\n                if (minorOffset + minorLength < value.length()) {\n                    int patchOffset = minorOffset + minorLength + 1;\n\n                    if (patchOffset >= value.length() || value.charAt(patchOffset - 1) != '.')\n                        throw new IllegalArgumentException(value);\n\n                    patch = Integer.parseInt(value, patchOffset, value.length(), 10);\n                }\n\n                return new Release(value, value, major, minor, patch, ReleaseType.UNKNOWN, VersionNumber.ZERO, Additional.NONE);\n            } catch (NumberFormatException e) {\n                throw new IllegalArgumentException(value);\n            }\n        }\n\n        private static int getNumberLength(String value, int offset) {\n            int current = offset;\n            while (current < value.length()) {\n                char ch = value.charAt(current);\n                if (ch < '0' || ch > '9')\n                    break;\n\n                current++;\n            }\n\n            return current - offset;\n        }\n\n        private final int major;\n        private final int minor;\n        private final int patch;\n\n        private final ReleaseType eaType;\n        private final VersionNumber eaVersion;\n        private final Additional additional;\n\n        Release(String value, String normalized, int major, int minor, int patch, ReleaseType eaType, VersionNumber eaVersion, Additional additional) {\n            super(value, normalized);\n            this.major = major;\n            this.minor = minor;\n            this.patch = patch;\n            this.eaType = eaType;\n            this.eaVersion = eaVersion;\n            this.additional = additional;\n        }\n\n        @Override\n        Type getType() {\n            return Type.NEW;\n        }\n\n        int compareToRelease(Release other) {\n            int c = Integer.compare(this.major, other.major);\n            if (c != 0)\n                return c;\n\n            c = Integer.compare(this.minor, other.minor);\n            if (c != 0)\n                return c;\n\n            c = Integer.compare(this.patch, other.patch);\n            if (c != 0)\n                return c;\n\n            c = this.eaType.compareTo(other.eaType);\n            if (c != 0)\n                return c;\n\n            c = this.eaVersion.compareTo(other.eaVersion);\n            if (c != 0)\n                return c;\n\n            return this.additional.compareTo(other.additional);\n        }\n\n        int compareToSnapshot(LegacySnapshot other) {\n            if (major == 0) {\n                return -1;\n            } else if (major == 1) {\n                int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue);\n                if (idx >= 0)\n                    return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;\n\n                idx = -(idx + 1);\n                if (idx == Versions.SNAPSHOT_INTS.length)\n                    return -1;\n\n                return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;\n            } else {\n                return 1;\n            }\n        }\n\n        @Override\n        int compareToImpl(@NotNull GameVersionNumber other) {\n            if (other instanceof Release release)\n                return compareToRelease(release);\n\n            if (other instanceof LegacySnapshot snapshot)\n                return compareToSnapshot(snapshot);\n\n            if (other instanceof Special special)\n                return -special.compareToReleaseOrSnapshot(this);\n\n            throw new AssertionError(other.getClass());\n        }\n\n        public int getMajor() {\n            return major;\n        }\n\n        public int getMinor() {\n            return minor;\n        }\n\n        public int getPatch() {\n            return patch;\n        }\n\n        public ReleaseType getEaType() {\n            return eaType;\n        }\n\n        public VersionNumber getEaVersion() {\n            return eaVersion;\n        }\n\n        public Additional getAdditional() {\n            return additional;\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(major, minor, patch, eaType, eaVersion, additional);\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            return o instanceof Release that\n                    && this.major == that.major\n                    && this.minor == that.minor\n                    && this.patch == that.patch\n                    && this.eaType == that.eaType\n                    && this.eaVersion.equals(that.eaVersion)\n                    && this.additional == that.additional;\n        }\n\n        @Override\n        protected ToStringBuilder buildDebugString() {\n            return super.buildDebugString()\n                    .append(\"major\", major)\n                    .append(\"minor\", minor)\n                    .append(\"patch\", patch)\n                    .append(\"eaType\", eaType)\n                    .append(\"eaVersion\", eaVersion)\n                    .append(\"additional\", additional);\n        }\n    }\n\n    /// Legacy snapshot version numbers like `25w46a`.\n    public static final class LegacySnapshot extends GameVersionNumber {\n        static LegacySnapshot parse(String value) {\n            if (value.length() < 6 || value.charAt(2) != 'w')\n                throw new IllegalArgumentException(value);\n\n            int prefixLength;\n            boolean unobfuscated;\n            String normalized;\n            if (value.endsWith(\"_unobfuscated\")) {\n                prefixLength = value.length() - \"_unobfuscated\".length();\n                unobfuscated = true;\n                normalized = value;\n            } else if (value.endsWith(\" Unobfuscated\")) {\n                prefixLength = value.length() - \" Unobfuscated\".length();\n                unobfuscated = true;\n                normalized = value.substring(0, prefixLength) + \"_unobfuscated\";\n            } else {\n                prefixLength = value.length();\n                unobfuscated = false;\n                normalized = value;\n            }\n\n            if (prefixLength != 6) {\n                throw new IllegalArgumentException(value);\n            }\n\n            int year;\n            int week;\n            try {\n                year = Integer.parseInt(value, 0, 2, 10);\n                week = Integer.parseInt(value, 3, 5, 10);\n            } catch (NumberFormatException e) {\n                throw new IllegalArgumentException(value);\n            }\n\n            char suffix = value.charAt(5);\n            if (suffix < 'a' || suffix > 'z')\n                throw new IllegalArgumentException(value);\n\n            return new LegacySnapshot(value, normalized, year, week, suffix, unobfuscated);\n        }\n\n        static int toInt(int year, int week, char suffix, boolean unobfuscated) {\n            return (year << 24) | (week << 16) | (suffix << 8) | (unobfuscated ? 1 : 0);\n        }\n\n        final int intValue;\n\n        LegacySnapshot(String value, String normalized, int year, int week, char suffix, boolean unobfuscated) {\n            super(value, normalized);\n            this.intValue = toInt(year, week, suffix, unobfuscated);\n        }\n\n        @Override\n        Type getType() {\n            return Type.NEW;\n        }\n\n        @Override\n        int compareToImpl(@NotNull GameVersionNumber other) {\n            if (other instanceof Release otherRelease)\n                return -otherRelease.compareToSnapshot(this);\n\n            if (other instanceof LegacySnapshot otherSnapshot)\n                return Integer.compare(this.intValue, otherSnapshot.intValue);\n\n            if (other instanceof Special otherSpecial)\n                return -otherSpecial.compareToReleaseOrSnapshot(this);\n\n            throw new AssertionError(other.getClass());\n        }\n\n        public int getYear() {\n            return (intValue >> 24) & 0xff;\n        }\n\n        public int getWeek() {\n            return (intValue >> 16) & 0xff;\n        }\n\n        public char getSuffix() {\n            return (char) ((intValue >> 8) & 0xff);\n        }\n\n        public boolean isUnobfuscated() {\n            return (intValue & 0b00000001) != 0;\n        }\n\n        @Override\n        public int hashCode() {\n            return intValue;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            return o instanceof LegacySnapshot that && this.intValue == that.intValue;\n        }\n\n        @Override\n        protected ToStringBuilder buildDebugString() {\n            return super.buildDebugString()\n                    .append(\"year\", getYear())\n                    .append(\"week\", getWeek())\n                    .append(\"suffix\", getSuffix())\n                    .append(\"unobfuscated\", isUnobfuscated());\n        }\n    }\n\n    public static final class Special extends GameVersionNumber {\n        private VersionNumber versionNumber;\n\n        private GameVersionNumber prev;\n\n        Special(String value, String normalized) {\n            super(value, normalized);\n        }\n\n        @Override\n        Type getType() {\n            return Type.NEW;\n        }\n\n        boolean isUnknown() {\n            return prev == null;\n        }\n\n        VersionNumber asVersionNumber() {\n            if (versionNumber != null)\n                return versionNumber;\n\n            return versionNumber = VersionNumber.asVersion(normalized);\n        }\n\n        GameVersionNumber getPrevNormalVersion() {\n            GameVersionNumber v = prev;\n            while (v instanceof Special special) {\n                v = special.prev;\n            }\n\n            if (v == null) throw new AssertionError(\"version: \" + value);\n\n            return v;\n        }\n\n        int compareToReleaseOrSnapshot(GameVersionNumber other) {\n            if (isUnknown()) {\n                return 1;\n            }\n\n            if (getPrevNormalVersion().compareTo(other) >= 0) {\n                return 1;\n            }\n\n            return -1;\n        }\n\n        int compareToSpecial(Special other) {\n            if (this.isUnknown())\n                return other.isUnknown() ? this.asVersionNumber().compareTo(other.asVersionNumber()) : 1;\n\n            if (other.isUnknown())\n                return -1;\n\n            if (this.normalized.equals(other.normalized))\n                return 0;\n\n            int c = this.getPrevNormalVersion().compareTo(other.getPrevNormalVersion());\n            if (c != 0)\n                return c;\n\n            GameVersionNumber v = prev;\n            while (v instanceof Special special) {\n                if (v == other)\n                    return 1;\n\n                v = special.prev;\n            }\n\n            return -1;\n        }\n\n        @Override\n        int compareToImpl(@NotNull GameVersionNumber o) {\n            if (o instanceof Release || o instanceof LegacySnapshot)\n                return compareToReleaseOrSnapshot(o);\n\n            if (o instanceof Special special)\n                return compareToSpecial(special);\n\n            throw new AssertionError(o.getClass());\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            return o instanceof Special that && this.normalized.equals(that.normalized);\n        }\n\n        @Override\n        public int hashCode() {\n            return normalized.hashCode();\n        }\n    }\n\n    static final class Versions {\n        static final HashMap<String, Special> SPECIALS = new HashMap<>();\n        static final String[] DEFAULT_GAME_VERSIONS;\n\n        static final int[] SNAPSHOT_INTS;\n        static final Release[] SNAPSHOT_PREV;\n\n        static {\n            ArrayDeque<String> defaultGameVersions = new ArrayDeque<>(64);\n\n            List<LegacySnapshot> snapshots = new ArrayList<>(1024);\n            List<Release> snapshotPrev = new ArrayList<>(1024);\n\n            //noinspection DataFlowIssue\n            try (var reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream(\"/assets/game/versions.txt\"), StandardCharsets.US_ASCII))) {\n                Release currentRelease = null;\n                GameVersionNumber prev = null;\n\n                for (String line; (line = reader.readLine()) != null; ) {\n                    if (line.isEmpty())\n                        continue;\n\n                    GameVersionNumber version = GameVersionNumber.asGameVersion(line);\n\n                    if (currentRelease == null)\n                        currentRelease = (Release) version;\n\n                    if (version instanceof LegacySnapshot snapshot) {\n                        snapshots.add(snapshot);\n                        snapshotPrev.add(currentRelease);\n                    } else if (version instanceof Release release) {\n                        currentRelease = release;\n\n                        if (currentRelease.eaType == Release.ReleaseType.GA\n                                && currentRelease.additional == Release.Additional.NONE) {\n                            defaultGameVersions.addFirst(currentRelease.value);\n                        }\n                    } else if (version instanceof Special special) {\n                        special.prev = prev;\n                        SPECIALS.put(special.value, special);\n                    } else\n                        throw new AssertionError(\"version: \" + version);\n\n                    prev = version;\n                }\n            } catch (IOException e) {\n                throw new AssertionError(e);\n            }\n\n            //noinspection DataFlowIssue\n            try (var reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream(\"/assets/game/version-alias.csv\"), StandardCharsets.US_ASCII))) {\n                for (String line; (line = reader.readLine()) != null; ) {\n                    if (line.isEmpty())\n                        continue;\n\n                    String[] parts = line.split(\",\");\n                    if (parts.length < 2) {\n                        LOG.warning(\"Invalid line: \" + line);\n                        continue;\n                    }\n\n                    String normalized = parts[0];\n                    Special normalizedVersion = SPECIALS.get(normalized);\n                    if (normalizedVersion == null) {\n                        LOG.warning(\"Unknown special version: \" + normalized);\n                        continue;\n                    }\n\n                    for (int i = 1; i < parts.length; i++) {\n                        String version = parts[i];\n                        Special versionNumber = new Special(version, normalized);\n                        versionNumber.prev = normalizedVersion.prev;\n                        SPECIALS.put(version, versionNumber);\n                    }\n                }\n            } catch (IOException e) {\n                throw new AssertionError(e);\n            }\n\n            DEFAULT_GAME_VERSIONS = defaultGameVersions.toArray(new String[0]);\n\n            SNAPSHOT_INTS = new int[snapshots.size()];\n            for (int i = 0; i < snapshots.size(); i++) {\n                SNAPSHOT_INTS[i] = snapshots.get(i).intValue;\n            }\n\n            SNAPSHOT_PREV = snapshotPrev.toArray(new Release[SNAPSHOT_INTS.length]);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.versioning;\n\nimport java.math.BigInteger;\nimport java.util.*;\n\n/**\n * Copied from org.apache.maven.artifact.versioning.ComparableVersion\n * Apache License 2.0\n * <p>\n * Maybe we can migrate to org.jenkins-ci:version-number:1.7?\n *\n * @see <a href=\"http://maven.apache.org/pom.html#Version_Order_Specification\">Specification</a>\n */\npublic final class VersionNumber implements Comparable<VersionNumber> {\n\n    public static final VersionNumber ZERO = asVersion(\"0\");\n\n    public static VersionNumber asVersion(String version) {\n        Objects.requireNonNull(version);\n        return new VersionNumber(version);\n    }\n\n    public static int compare(String version1, String version2) {\n        return asVersion(version1).compareTo(asVersion(version2));\n    }\n\n    public static String normalize(String str) {\n        return new VersionNumber(str).getCanonical();\n    }\n\n    public static boolean isIntVersionNumber(String version) {\n        if (version.isEmpty()) {\n            return false;\n        }\n\n        int idx = 0;\n        boolean cont = true;\n        do {\n            int dotIndex = version.indexOf('.', idx);\n            if (dotIndex == idx || dotIndex == version.length() - 1) {\n                return false;\n            }\n\n            int endIndex;\n            if (dotIndex < 0) {\n                cont = false;\n                endIndex = version.length();\n            } else {\n                endIndex = dotIndex;\n            }\n\n            if (endIndex - idx > 9)\n                // Numbers which are larger than 10^10 cannot be stored as integer\n                return false;\n\n            for (int i = idx; i < endIndex; i++) {\n                char ch = version.charAt(i);\n                if (ch < '0' || ch > '9')\n                    return false;\n            }\n\n            idx = endIndex + 1;\n        } while (cont);\n\n        return true;\n    }\n\n    public static VersionRange<VersionNumber> between(String minimum, String maximum) {\n        return VersionRange.between(asVersion(minimum), asVersion(maximum));\n    }\n\n    public static VersionRange<VersionNumber> atLeast(String minimum) {\n        return VersionRange.atLeast(asVersion(minimum));\n    }\n\n    public static VersionRange<VersionNumber> atMost(String maximum) {\n        return VersionRange.atMost(asVersion(maximum));\n    }\n\n    private interface Item {\n        int LONG_ITEM = 0;\n        int BIGINTEGER_ITEM = 1;\n        int STRING_ITEM = 2;\n        int LIST_ITEM = 3;\n\n        int compareTo(Item item);\n\n        int getType();\n\n        boolean isNull();\n\n        void appendTo(StringBuilder buffer);\n    }\n\n    private static final class LongItem implements Item {\n        private final long value;\n\n        public static final LongItem ZERO = new LongItem(0L);\n\n        LongItem(long value) {\n            this.value = value;\n        }\n\n        public int getType() {\n            return LONG_ITEM;\n        }\n\n        public boolean isNull() {\n            return value == 0L;\n        }\n\n        public int compareTo(Item item) {\n            if (item == null) {\n                return value == 0L ? 0 : 1; // 1.0 == 1, 1.1 > 1\n            }\n\n            switch (item.getType()) {\n                case LONG_ITEM:\n                    long itemValue = ((LongItem) item).value;\n                    return Long.compare(value, itemValue);\n                case BIGINTEGER_ITEM:\n                    return -1;\n\n                case STRING_ITEM:\n                    return 1; // 1.1 > 1-sp\n\n                case LIST_ITEM:\n                    return 1; // 1.1 > 1-1\n\n                default:\n                    throw new AssertionError(\"invalid item: \" + item.getClass());\n            }\n        }\n\n        @Override\n        public void appendTo(StringBuilder buffer) {\n            buffer.append(value);\n        }\n\n        public String toString() {\n            return Long.toString(value);\n        }\n    }\n\n    /**\n     * Represents a numeric item in the version item list.\n     */\n    private static final class BigIntegerItem implements Item {\n        private final BigInteger value;\n\n        BigIntegerItem(String str) {\n            this.value = new BigInteger(str);\n        }\n\n        public int getType() {\n            return BIGINTEGER_ITEM;\n        }\n\n        public boolean isNull() {\n            // Never be 0\n            // return BigInteger.ZERO.equals(value);\n            return false;\n        }\n\n        public int compareTo(Item item) {\n            if (item == null) {\n                // return BigInteger.ZERO.equals(value) ? 0 : 1; // 1.0 == 1, 1.1 > 1\n                return 1;\n            }\n\n            switch (item.getType()) {\n                case LONG_ITEM:\n                    return 1;\n                case BIGINTEGER_ITEM:\n                    return value.compareTo(((BigIntegerItem) item).value);\n\n                case STRING_ITEM:\n                    return 1; // 1.1 > 1-sp\n\n                case LIST_ITEM:\n                    return 1; // 1.1 > 1-1\n\n                default:\n                    throw new AssertionError(\"invalid item: \" + item.getClass());\n            }\n        }\n\n        @Override\n        public void appendTo(StringBuilder buffer) {\n            buffer.append(value);\n        }\n\n        public String toString() {\n            return value.toString();\n        }\n    }\n\n    /**\n     * Represents a string in the version item list, usually a qualifier.\n     */\n    private static final class StringItem implements Item {\n        private final String value;\n        private final boolean pre;\n\n        StringItem(String value) {\n            this.value = value;\n\n            String lower = value.trim().toLowerCase(Locale.ROOT);\n            this.pre = lower.startsWith(\"alpha\")\n                    || lower.startsWith(\"beta\")\n                    || lower.startsWith(\"pre\")\n                    || lower.startsWith(\"rc\")\n                    || lower.startsWith(\"experimental\");\n        }\n\n        public int getType() {\n            return STRING_ITEM;\n        }\n\n        public boolean isNull() {\n            return value.isEmpty();\n        }\n\n        public int compareTo(Item item) {\n            if (item == null) {\n                // 1-beta < 1 < 1-string\n                return pre ? -1 : 1;\n            }\n            switch (item.getType()) {\n                case LONG_ITEM:\n                case BIGINTEGER_ITEM:\n                    return -1; // 1.any < 1.1 ?\n\n                case STRING_ITEM:\n                    return value.compareTo(((StringItem) item).value);\n\n                case LIST_ITEM:\n                    return -1; // 1.any < 1-1\n\n                default:\n                    throw new AssertionError(\"invalid item: \" + item.getClass());\n            }\n        }\n\n        @Override\n        public void appendTo(StringBuilder buffer) {\n            buffer.append(value);\n        }\n\n        public String toString() {\n            return value;\n        }\n    }\n\n    /**\n     * Represents a version list item. This class is used both for the global item list and for sub-lists (which start\n     * with '-(number)' in the version specification).\n     */\n    private static final class ListItem extends ArrayList<Item> implements Item {\n        private final Character separator;\n\n        ListItem() {\n            this.separator = null;\n        }\n\n        ListItem(char separator) {\n            this.separator = separator;\n        }\n\n        public int getType() {\n            return LIST_ITEM;\n        }\n\n        public boolean isNull() {\n            return size() == 0;\n        }\n\n        void normalize() {\n            for (int i = size() - 1; i >= 0; i--) {\n                Item lastItem = get(i);\n\n                if (lastItem.isNull()) {\n                    // remove null trailing items: 0, \"\", empty list\n                    remove(i);\n                } else if (!(lastItem instanceof ListItem)) {\n                    break;\n                }\n            }\n        }\n\n        public int compareTo(Item item) {\n            if (item == null) {\n                if (size() == 0) {\n                    return 0; // 1-0 = 1- (normalize) = 1\n                }\n                Item first = get(0);\n                return first.compareTo(null);\n            }\n            switch (item.getType()) {\n                case LONG_ITEM:\n                case BIGINTEGER_ITEM:\n                    return -1; // 1-1 < 1.0.x\n\n                case STRING_ITEM:\n                    return 1; // 1-1 > 1-sp\n\n                case LIST_ITEM:\n                    Iterator<Item> left = iterator();\n                    Iterator<Item> right = ((ListItem) item).iterator();\n\n                    while (left.hasNext() || right.hasNext()) {\n                        Item l = left.hasNext() ? left.next() : null;\n                        Item r = right.hasNext() ? right.next() : null;\n\n                        // if this is shorter, then invert the compare and mul with -1\n                        int result = l == null ? (r == null ? 0 : -1 * r.compareTo(l)) : l.compareTo(r);\n\n                        if (result != 0) {\n                            return result;\n                        }\n                    }\n\n                    return 0;\n\n                default:\n                    throw new AssertionError(\"invalid item: \" + item.getClass());\n            }\n        }\n\n        @Override\n        public void appendTo(StringBuilder buffer) {\n            if (separator != null) {\n                buffer.append((char) separator);\n            }\n\n            final int initLength = buffer.length();\n\n            for (Item item : this) {\n                if (buffer.length() > initLength) {\n                    if (!(item instanceof ListItem))\n                        buffer.append('.');\n                }\n                item.appendTo(buffer);\n            }\n        }\n\n        public String toString() {\n            StringBuilder buffer = new StringBuilder();\n            appendTo(buffer);\n            return buffer.toString();\n        }\n    }\n\n    private static final int MAX_LONGITEM_LENGTH = 18;\n\n    private final String value;\n    private final ListItem items;\n    private final String canonical;\n\n    private VersionNumber(String version) {\n        this.value = version;\n\n        ListItem list = this.items = new ListItem();\n\n        Deque<Item> stack = new ArrayDeque<>();\n        stack.push(list);\n\n        boolean isDigit = false;\n\n        int startIndex = 0;\n\n        for (int i = 0; i < version.length(); i++) {\n            char c = version.charAt(i);\n\n            if (c == '.') {\n                if (i == startIndex) {\n                    list.add(LongItem.ZERO);\n                } else {\n                    list.add(parseItem(version.substring(startIndex, i)));\n                }\n                startIndex = i + 1;\n            } else if (\"!\\\"#$%&'()*+,-/:;<=>?@[\\\\]^_`{|}~\".indexOf(c) != -1) {\n                if (i == startIndex) {\n                    list.add(LongItem.ZERO);\n                } else {\n                    list.add(parseItem(version.substring(startIndex, i)));\n                }\n                startIndex = i + 1;\n\n                list.add(list = new ListItem(c));\n                stack.push(list);\n            } else if (c >= '0' && c <= '9') {\n                if (!isDigit && i > startIndex) {\n                    list.add(parseItem(version.substring(startIndex, i)));\n                    startIndex = i;\n\n                    list.add(list = new ListItem());\n                    stack.push(list);\n                }\n\n                isDigit = true;\n            } else {\n                if (isDigit && i > startIndex) {\n                    list.add(parseItem(version.substring(startIndex, i)));\n                    startIndex = i;\n\n                    list.add(list = new ListItem());\n                    stack.push(list);\n                }\n\n                isDigit = false;\n            }\n        }\n\n        if (version.length() > startIndex) {\n            list.add(parseItem(version.substring(startIndex)));\n        }\n\n        while (!stack.isEmpty()) {\n            list = (ListItem) stack.pop();\n            list.normalize();\n        }\n\n        this.canonical = items.toString();\n    }\n\n    // For simple version\n    private VersionNumber(String version, ListItem items) {\n        this.value = version;\n        this.items = items;\n        this.canonical = version;\n    }\n\n    private static Item parseItem(String buf) {\n        int numberLength = 0;\n        boolean leadingZero = true;\n        for (int i = 0; i < buf.length(); i++) {\n            char ch = buf.charAt(i);\n            if (ch >= '0' && ch <= '9') {\n                if (ch != '0') {\n                    leadingZero = false;\n                }\n\n                if (!leadingZero) {\n                    numberLength++;\n                }\n            } else {\n                return new StringItem(buf);\n            }\n        }\n\n        if (numberLength == 0) {\n            return LongItem.ZERO;\n        } else if (numberLength <= MAX_LONGITEM_LENGTH) {\n            // Numbers which are larger than 10^19 cannot be stored as long\n            return new LongItem(Long.parseLong(buf));\n        } else {\n            return new BigIntegerItem(buf);\n        }\n    }\n\n    public int compareTo(String o) {\n        return compareTo(VersionNumber.asVersion(o));\n    }\n\n    @Override\n    public int compareTo(VersionNumber o) {\n        return items.compareTo(o.items);\n    }\n\n    @Override\n    public String toString() {\n        return value;\n    }\n\n    public String getCanonical() {\n        return canonical;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        return o instanceof VersionNumber && canonical.equals(((VersionNumber) o).canonical);\n    }\n\n    @Override\n    public int hashCode() {\n        return canonical.hashCode();\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionRange.java",
    "content": "package org.jackhuang.hmcl.util.versioning;\n\nimport java.util.Objects;\n\n/**\n * @author Glavo\n */\n@SuppressWarnings(\"unchecked\")\npublic final class VersionRange<T extends Comparable<T>> {\n    private static final VersionRange<?> EMPTY = new VersionRange<>(null, null);\n    private static final VersionRange<?> ALL = new VersionRange<>(null, null);\n\n    public static <T extends Comparable<T>> VersionRange<T> empty() {\n        return (VersionRange<T>) EMPTY;\n    }\n\n    public static <T extends Comparable<T>> VersionRange<T> all() {\n        return (VersionRange<T>) ALL;\n    }\n\n    public static <T extends Comparable<T>> VersionRange<T> between(T minimum, T maximum) {\n        assert minimum.compareTo(maximum) <= 0;\n        return new VersionRange<>(minimum, maximum);\n    }\n\n    public static <T extends Comparable<T>> VersionRange<T> atLeast(T minimum) {\n        assert minimum != null;\n        return new VersionRange<>(minimum, null);\n    }\n\n    public static <T extends Comparable<T>> VersionRange<T> atMost(T maximum) {\n        assert maximum != null;\n        return new VersionRange<>(null, maximum);\n    }\n\n    private final T minimum;\n    private final T maximum;\n\n    private VersionRange(T minimum, T maximum) {\n        this.minimum = minimum;\n        this.maximum = maximum;\n    }\n\n    public T getMinimum() {\n        return minimum;\n    }\n\n    public T getMaximum() {\n        return maximum;\n    }\n\n    public boolean isEmpty() {\n        return this == EMPTY;\n    }\n\n    public boolean isAll() {\n        return !isEmpty() && minimum == null && maximum == null;\n    }\n\n    public boolean contains(T versionNumber) {\n        if (versionNumber == null) return false;\n        if (isEmpty()) return false;\n        if (isAll()) return true;\n\n        return (minimum == null || minimum.compareTo(versionNumber) <= 0) && (maximum == null || maximum.compareTo(versionNumber) >= 0);\n    }\n\n    public boolean isOverlappedBy(final VersionRange<T> that) {\n        if (this.isEmpty() || that.isEmpty())\n            return false;\n\n        if (this.isAll() || that.isAll())\n            return true;\n\n        if (this.minimum == null)\n            return that.minimum == null || that.minimum.compareTo(this.maximum) <= 0;\n\n        if (this.maximum == null)\n            return that.maximum == null || that.maximum.compareTo(this.minimum) >= 0;\n\n        return that.contains(minimum) || that.contains(maximum) || (that.minimum != null && contains(that.minimum));\n    }\n\n    public VersionRange<T> intersectionWith(VersionRange<T> that) {\n        if (this.isAll())\n            return that;\n        if (that.isAll())\n            return this;\n\n        if (!isOverlappedBy(that))\n            return empty();\n\n        T newMinimum;\n        if (this.minimum == null)\n            newMinimum = that.minimum;\n        else if (that.minimum == null)\n            newMinimum = this.minimum;\n        else\n            newMinimum = this.minimum.compareTo(that.minimum) >= 0 ? this.minimum : that.minimum;\n\n        T newMaximum;\n        if (this.maximum == null)\n            newMaximum = that.maximum;\n        else if (that.maximum == null)\n            newMaximum = this.maximum;\n        else\n            newMaximum = this.maximum.compareTo(that.maximum) <= 0 ? this.maximum : that.maximum;\n\n        return new VersionRange<>(newMinimum, newMaximum);\n    }\n\n    @Override\n    public int hashCode() {\n        if (isEmpty())\n            return 1121763849;  // Magic Number\n        if (isAll())\n            return -475303149;  // Magic Number\n\n        return Objects.hash(minimum) ^ Objects.hash(maximum);\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj)\n            return true;\n        if (!(obj instanceof VersionRange))\n            return false;\n\n        VersionRange<T> that = (VersionRange<T>) obj;\n\n        return this.isEmpty() == that.isEmpty() && this.isAll() == that.isAll()\n                && Objects.equals(this.minimum, that.minimum)\n                && Objects.equals(this.maximum, that.maximum);\n    }\n\n    @Override\n    public String toString() {\n        if (isEmpty())\n            return \"EMPTY\";\n\n        if (isAll())\n            return \"ALL\";\n\n        if (minimum == null)\n            return \"At most \" + maximum;\n\n        if (maximum == null)\n            return \"At least \" + minimum;\n\n        return \"[\" + minimum + \"..\" + maximum + \"]\";\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/game/log4j2-1.12-debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"warn\" packages=\"\">\n    <Appenders>\n        <Console name=\"SysOut\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n\" />\n        </Console>\n        <Queue name=\"ServerGuiConsole\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss} %level]: %msg{nolookups}%n\" />\n        </Queue>\n        <RollingRandomAccessFile name=\"File\" fileName=\"logs/latest.log\" filePattern=\"logs/%d{yyyy-MM-dd}-%i.log.gz\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss}] [%t/%level] [%logger]: %msg{nolookups}%n\"/>\n            <Policies>\n                <TimeBasedTriggeringPolicy/>\n                <OnStartupTriggeringPolicy/>\n            </Policies>\n        </RollingRandomAccessFile>\n        <RollingRandomAccessFile name=\"DebugFile\" fileName=\"logs/debug.log\" filePattern=\"logs/debug-%d{yyyy-MM-dd}-%i.log.gz\">\n            <PatternLayout pattern=\"[%d{yyyy-MM-dd HH:mm:ss}] [%t/%level] [%logger]: %msg{nolookups}%n\"/>\n            <Policies>\n                <TimeBasedTriggeringPolicy/>\n                <OnStartupTriggeringPolicy/>\n            </Policies>\n            <DefaultRolloverStrategy max=\"999\" fileIndex=\"min\"/>\n        </RollingRandomAccessFile>\n    </Appenders>\n    <Loggers>\n        <Root level=\"all\">\n            <filters>\n                <MarkerFilter marker=\"NETWORK_PACKETS\" onMatch=\"DENY\" onMismatch=\"NEUTRAL\"/>\n            </filters>\n            <AppenderRef ref=\"SysOut\" level=\"info\"/>\n            <AppenderRef ref=\"ServerGuiConsole\" level=\"info\"/>\n            <AppenderRef ref=\"File\" level=\"info\"/>\n            <AppenderRef ref=\"DebugFile\" level=\"all\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/game/log4j2-1.12.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"SysOut\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n\" />\n        </Console>\n        <Queue name=\"ServerGuiConsole\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss} %level]: %msg{nolookups}%n\" />\n        </Queue>\n        <RollingRandomAccessFile name=\"File\" fileName=\"logs/latest.log\" filePattern=\"logs/%d{yyyy-MM-dd}-%i.log.gz\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n\" />\n            <Policies>\n                <TimeBasedTriggeringPolicy />\n                <OnStartupTriggeringPolicy />\n            </Policies>\n        </RollingRandomAccessFile>\n    </Appenders>\n    <Loggers>\n        <Root level=\"info\">\n            <filters>\n                <MarkerFilter marker=\"NETWORK_PACKETS\" onMatch=\"DENY\" onMismatch=\"NEUTRAL\" />\n            </filters>\n            <AppenderRef ref=\"SysOut\"/>\n            <AppenderRef ref=\"File\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/game/log4j2-1.7-debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"SysOut\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss}] [%t/%level]: %msg%n\" />\n        </Console>\n        <Queue name=\"ServerGuiConsole\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss} %level]: %msg%n\" />\n        </Queue>\n        <RollingRandomAccessFile name=\"File\" fileName=\"logs/latest.log\" filePattern=\"logs/%d{yyyy-MM-dd}-%i.log.gz\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss}] [%t/%level] [%logger]: %msg%n\"/>\n            <Policies>\n                <TimeBasedTriggeringPolicy/>\n                <OnStartupTriggeringPolicy/>\n            </Policies>\n        </RollingRandomAccessFile>\n        <RollingRandomAccessFile name=\"DebugFile\" fileName=\"logs/debug.log\" filePattern=\"logs/debug-%d{yyyy-MM-dd}-%i.log.gz\">\n            <PatternLayout pattern=\"[%d{yyyy-MM-dd HH:mm:ss}] [%t/%level] [%logger]: %msg%n\"/>\n            <Policies>\n                <TimeBasedTriggeringPolicy/>\n                <OnStartupTriggeringPolicy/>\n            </Policies>\n        </RollingRandomAccessFile>\n    </Appenders>\n    <Loggers>\n        <Root level=\"all\">\n            <filters>\n                <MarkerFilter marker=\"NETWORK_PACKETS\" onMatch=\"DENY\" onMismatch=\"NEUTRAL\"/>\n            </filters>\n            <AppenderRef ref=\"SysOut\" level=\"info\"/>\n            <AppenderRef ref=\"ServerGuiConsole\" level=\"info\"/>\n            <AppenderRef ref=\"File\" level=\"info\"/>\n            <AppenderRef ref=\"DebugFile\" level=\"all\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/game/log4j2-1.7.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration status=\"WARN\">\n    <Appenders>\n        <Console name=\"SysOut\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss}] [%t/%level]: %msg%n\" />\n        </Console>\n        <Queue name=\"ServerGuiConsole\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss} %level]: %msg%n\" />\n        </Queue>\n        <RollingRandomAccessFile name=\"File\" fileName=\"logs/latest.log\" filePattern=\"logs/%d{yyyy-MM-dd}-%i.log.gz\">\n            <PatternLayout pattern=\"[%d{HH:mm:ss}] [%t/%level]: %msg%n\" />\n            <Policies>\n                <TimeBasedTriggeringPolicy />\n                <OnStartupTriggeringPolicy />\n            </Policies>\n        </RollingRandomAccessFile>\n    </Appenders>\n    <Loggers>\n        <Root level=\"info\">\n            <filters>\n                <MarkerFilter marker=\"NETWORK_PACKETS\" onMatch=\"DENY\" onMismatch=\"NEUTRAL\" />\n                <RegexFilter regex=\".*\\$\\{[^}]*\\}.*\" onMatch=\"DENY\" onMismatch=\"NEUTRAL\"/>\n            </filters>\n            <AppenderRef ref=\"SysOut\"/>\n            <AppenderRef ref=\"File\"/>\n        </Root>\n    </Loggers>\n</Configuration>\n"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/game/unlisted-versions.json",
    "content": "{\n  \"versions\": [\n    {\n      \"id\": \"1.21.11_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/327be7759157b04495c591dbb721875e341877af/1.21.11_unobfuscated.json\",\n      \"time\": \"2025-12-09T12:43:15+00:00\",\n      \"releaseTime\": \"2025-12-09T12:43:15+00:00\"\n    },\n    {\n      \"id\": \"1.21.11-rc3_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/ce3f7ac6d0e9d23ea4e5f0354b91ff15039d9931/1.21.11-rc3_unobfuscated.json\",\n      \"time\": \"2025-12-08T13:59:34+00:00\",\n      \"releaseTime\": \"2025-12-08T13:59:34+00:00\"\n    },\n    {\n      \"id\": \"1.21.11-rc2_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/9282a3fb154d2a425086c62c11827281308bf93b/1.21.11-rc2_unobfuscated.json\",\n      \"time\": \"2025-12-05T11:57:45+00:00\",\n      \"releaseTime\": \"2025-12-05T11:57:45+00:00\"\n    },\n    {\n      \"id\": \"1.21.11-rc1_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/5d3ee0ef1f0251cf7e073354ca9e085a884a643d/1.21.11-rc1_unobfuscated.json\",\n      \"time\": \"2025-12-04T15:56:55+00:00\",\n      \"releaseTime\": \"2025-12-04T15:56:55+00:00\"\n    },\n    {\n      \"id\": \"1.21.11-pre5_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/1028441ca6d288bbf2103e773196bf524f7260fd/1.21.11-pre5_unobfuscated.json\",\n      \"time\": \"2025-12-03T13:34:06+00:00\",\n      \"releaseTime\": \"2025-12-03T13:34:06+00:00\"\n    },\n    {\n      \"id\": \"1.21.11-pre4_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/410ce37a2506adcfd54ef7d89168cfbe89cac4cb/1.21.11-pre4_unobfuscated.json\",\n      \"time\": \"2025-12-01T13:40:12+00:00\",\n      \"releaseTime\": \"2025-12-01T13:40:12+00:00\"\n    },\n    {\n      \"id\": \"1.21.11-pre3_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/579bf3428f72b5ea04883d202e4831bfdcb2aa8d/1.21.11-pre3_unobfuscated.json\",\n      \"time\": \"2025-11-25T14:14:30+00:00\",\n      \"releaseTime\": \"2025-11-25T14:14:30+00:00\"\n    },\n    {\n      \"id\": \"1.21.11-pre2_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/2955ce0af0512fdfe53ff0740b017344acf6f397/1.21.11-pre2_unobfuscated.json\",\n      \"time\": \"2025-11-21T12:07:21+00:00\",\n      \"releaseTime\": \"2025-11-21T12:07:21+00:00\"\n    },\n    {\n      \"id\": \"1.21.11-pre1_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/9c267f8dda2728bae55201a753cdd07b584709f1/1.21.11-pre1_unobfuscated.json\",\n      \"time\": \"2025-11-19T08:30:46+00:00\",\n      \"releaseTime\": \"2025-11-19T08:30:46+00:00\"\n    },\n    {\n      \"id\": \"25w46a_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/314ade2afeada364047798e163ef8e82427c69e1/25w46a_unobfuscated.json\",\n      \"time\": \"2025-11-11T13:20:54+00:00\",\n      \"releaseTime\": \"2025-11-11T13:20:54+00:00\"\n    },\n    {\n      \"id\": \"25w45a_unobfuscated\",\n      \"type\": \"unobfuscated\",\n      \"url\": \"https://piston-meta.mojang.com/v1/packages/7a3c149f148b6aa5ac3af48c4f701adea7e5b615/25w45a_unobfuscated.json\",\n      \"time\": \"2025-11-04T14:07:08+00:00\",\n      \"releaseTime\": \"2025-11-04T14:07:08+00:00\"\n    },\n    {\n      \"id\": \"1.19_deep_dark_experimental_snapshot-1\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_19_deep_dark_experimental_snapshot-1/1_19_deep_dark_experimental_snapshot-1.json\",\n      \"time\": \"2022-02-17T13:55:59+00:00\",\n      \"releaseTime\": \"2022-02-17T13:55:59+00:00\"\n    },\n    {\n      \"id\": \"1.18_experimental-snapshot-7\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-7/1_18_experimental-snapshot-7.json\",\n      \"time\": \"2021-09-08T12:33:23+00:00\",\n      \"releaseTime\": \"2021-09-08T12:33:23+00:00\"\n    },\n    {\n      \"id\": \"1.18_experimental-snapshot-6\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-6/1_18_experimental-snapshot-6.json\",\n      \"time\": \"2021-09-01T12:53:14+00:00\",\n      \"releaseTime\": \"2021-09-01T12:53:14+00:00\"\n    },\n    {\n      \"id\": \"1.18_experimental-snapshot-5\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-5/1_18_experimental-snapshot-5.json\",\n      \"time\": \"2021-08-25T14:41:57+00:00\",\n      \"releaseTime\": \"2021-08-25T14:41:57+00:00\"\n    },\n    {\n      \"id\": \"1.18_experimental-snapshot-4\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-4/1_18_experimental-snapshot-4.json\",\n      \"time\": \"2021-08-17T16:10:25+00:00\",\n      \"releaseTime\": \"2021-08-17T16:10:25+00:00\"\n    },\n    {\n      \"id\": \"1.18_experimental-snapshot-3\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-3/1_18_experimental-snapshot-3.json\",\n      \"time\": \"2021-08-10T12:42:45+00:00\",\n      \"releaseTime\": \"2021-08-10T12:42:45+00:00\"\n    },\n    {\n      \"id\": \"1.18_experimental-snapshot-2\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-2/1_18_experimental-snapshot-2.json\",\n      \"time\": \"2021-07-20T13:35:08+00:00\",\n      \"releaseTime\": \"2021-07-20T13:35:08+00:00\"\n    },\n    {\n      \"id\": \"1.18_experimental-snapshot-1\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-1/1_18_experimental-snapshot-1.json\",\n      \"time\": \"2021-07-13T12:54:19+00:00\",\n      \"releaseTime\": \"2021-07-13T12:54:19+00:00\"\n    },\n    {\n      \"id\": \"1.16_combat-6\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-6/1_16_combat-6.json\",\n      \"time\": \"2020-08-26T06:24:28+00:00\",\n      \"releaseTime\": \"2020-08-26T06:24:28+00:00\"\n    },\n    {\n      \"id\": \"1.16_combat-5\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-5/1_16_combat-5.json\",\n      \"time\": \"2020-08-21T09:23:13+00:00\",\n      \"releaseTime\": \"2020-08-21T09:23:13+00:00\"\n    },\n    {\n      \"id\": \"1.16_combat-4\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-4/1_16_combat-4.json\",\n      \"time\": \"2020-08-19T11:14:58+00:00\",\n      \"releaseTime\": \"2020-08-19T11:14:58+00:00\"\n    },\n    {\n      \"id\": \"1.16_combat-3\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-3/1_16_combat-3.json\",\n      \"time\": \"2020-08-14T09:02:15+00:00\",\n      \"releaseTime\": \"2020-08-14T09:02:15+00:00\"\n    },\n    {\n      \"id\": \"1.16_combat-2\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-2/1_16_combat-2.json\",\n      \"time\": \"2020-08-13T11:56:30+00:00\",\n      \"releaseTime\": \"2020-08-13T11:56:30+00:00\"\n    },\n    {\n      \"id\": \"1.16_combat-1\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-1/1_16_combat-1.json\",\n      \"time\": \"2020-08-12T14:07:25+00:00\",\n      \"releaseTime\": \"2020-08-12T14:07:25+00:00\"\n    },\n    {\n      \"id\": \"1.16_combat-0\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-0/1_16_combat-0.json\",\n      \"time\": \"2020-08-07T10:44:47+00:00\",\n      \"releaseTime\": \"2020-08-07T10:44:47+00:00\"\n    },\n    {\n      \"id\": \"1.15_combat-6\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_15_combat-6/1_15_combat-6.json\",\n      \"time\": \"2020-01-15T09:46:35+00:00\",\n      \"releaseTime\": \"2020-01-15T09:46:35+00:00\"\n    },\n    {\n      \"id\": \"1.15_combat-1\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_15_combat-1/1_15_combat-1.json\",\n      \"time\": \"2019-11-29T15:41:39+00:00\",\n      \"releaseTime\": \"2019-11-29T15:41:39+00:00\"\n    },\n    {\n      \"id\": \"1.14_combat-3\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_14_combat-3/1_14_combat-3.json\",\n      \"time\": \"2019-10-31T14:31:38+00:00\",\n      \"releaseTime\": \"2019-10-31T14:31:38+00:00\"\n    },\n    {\n      \"id\": \"1.14_combat-0\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_14_combat-0/1_14_combat-0.json\",\n      \"time\": \"2019-08-13T07:33:42+00:00\",\n      \"releaseTime\": \"2019-08-13T07:33:42+00:00\"\n    },\n    {\n      \"id\": \"1.14_combat-212796\",\n      \"type\": \"pending\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_14_combat-212796/1_14_combat-212796.json\",\n      \"time\": \"2019-06-20T13:23:44+00:00\",\n      \"releaseTime\": \"2019-06-20T13:23:44+00:00\"\n    },\n    {\n      \"id\": \"2.0_blue\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/2point0_blue/2point0_blue.json\",\n      \"time\": \"2013-08-06T06:00:02-05:00\",\n      \"releaseTime\": \"2013-03-20T05:00:02-05:00\"\n    },\n    {\n      \"id\": \"2.0_red\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/2point0_red/2point0_red.json\",\n      \"time\": \"2013-08-06T06:00:01-05:00\",\n      \"releaseTime\": \"2013-03-20T05:00:01-05:00\"\n    },\n    {\n      \"id\": \"2.0_purple\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/2point0_purple/2point0_purple.json\",\n      \"time\": \"2013-08-06T06:00:00-05:00\",\n      \"releaseTime\": \"2013-03-20T05:00:00-05:00\"\n    },\n    {\n      \"id\": \"13w12~\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w12-/13w12-.json\",\n      \"time\": \"2013-03-19T00:00:00+00:00\",\n      \"releaseTime\": \"2013-03-19T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w10b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w10b/13w10b.json\",\n      \"time\": \"2013-03-06T00:00:00+00:00\",\n      \"releaseTime\": \"2013-03-06T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w10a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w10a/13w10a.json\",\n      \"time\": \"2013-03-04T00:00:00+00:00\",\n      \"releaseTime\": \"2013-03-04T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w09c\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w09c/13w09c.json\",\n      \"time\": \"2013-03-01T00:00:00+00:00\",\n      \"releaseTime\": \"2013-03-01T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w09b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w09b/13w09b.json\",\n      \"time\": \"2013-02-27T00:00:00+00:00\",\n      \"releaseTime\": \"2013-02-27T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w09a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w09a/13w09a.json\",\n      \"time\": \"2013-02-26T00:00:00+00:00\",\n      \"releaseTime\": \"2013-02-26T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w07a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w07a/13w07a.json\",\n      \"time\": \"2013-02-14T00:00:00+00:00\",\n      \"releaseTime\": \"2013-02-14T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w11a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w11a/13w11a.json\",\n      \"time\": \"2013-02-14T00:00:00+00:00\",\n      \"releaseTime\": \"2013-02-14T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w06a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w06a/13w06a.json\",\n      \"time\": \"2013-02-07T00:00:00+00:00\",\n      \"releaseTime\": \"2013-02-07T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w05b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w05b/13w05b.json\",\n      \"time\": \"2013-02-01T00:00:00+00:00\",\n      \"releaseTime\": \"2013-02-01T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w05a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w05a/13w05a.json\",\n      \"time\": \"2013-01-31T00:00:00+00:00\",\n      \"releaseTime\": \"2013-01-31T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w04a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w04a/13w04a.json\",\n      \"time\": \"2013-01-24T00:00:00+00:00\",\n      \"releaseTime\": \"2013-01-24T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w03a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w03a/13w03a.json\",\n      \"time\": \"2013-01-17T00:00:00+00:00\",\n      \"releaseTime\": \"2013-01-17T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w02b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w02b/13w02b.json\",\n      \"time\": \"2013-01-11T00:00:00+00:00\",\n      \"releaseTime\": \"2013-01-11T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w02a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w02a/13w02a.json\",\n      \"time\": \"2013-01-10T00:00:00+00:00\",\n      \"releaseTime\": \"2013-01-10T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w01b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w01b/13w01b.json\",\n      \"time\": \"2013-01-04T00:00:00+00:00\",\n      \"releaseTime\": \"2013-01-04T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"13w01a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/13w01a/13w01a.json\",\n      \"time\": \"2013-01-03T00:00:00+00:00\",\n      \"releaseTime\": \"2013-01-03T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w50b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w50b/12w50b.json\",\n      \"time\": \"2012-12-14T00:00:00+00:00\",\n      \"releaseTime\": \"2012-12-14T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w50a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w50a/12w50a.json\",\n      \"time\": \"2012-12-13T00:00:00+00:00\",\n      \"releaseTime\": \"2012-12-13T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w49a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w49a/12w49a.json\",\n      \"time\": \"2012-12-07T00:00:00+00:00\",\n      \"releaseTime\": \"2012-12-07T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w42b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w42b/12w42b.json\",\n      \"time\": \"2012-10-18T00:00:00+00:00\",\n      \"releaseTime\": \"2012-10-18T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w42a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w42a/12w42a.json\",\n      \"time\": \"2012-10-17T00:00:00+00:00\",\n      \"releaseTime\": \"2012-10-17T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w41b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w41b/12w41b.json\",\n      \"time\": \"2012-10-12T00:00:00+00:00\",\n      \"releaseTime\": \"2012-10-12T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w41a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w41a/12w41a.json\",\n      \"time\": \"2012-10-11T00:00:00+00:00\",\n      \"releaseTime\": \"2012-10-11T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w40b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w40b/12w40b.json\",\n      \"time\": \"2012-10-05T00:00:00+00:00\",\n      \"releaseTime\": \"2012-10-05T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w40a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w40a/12w40a.json\",\n      \"time\": \"2012-10-04T00:00:00+00:00\",\n      \"releaseTime\": \"2012-10-04T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w39b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w39b/12w39b.json\",\n      \"time\": \"2012-09-28T00:00:00+00:00\",\n      \"releaseTime\": \"2012-09-28T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w39a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w39a/12w39a.json\",\n      \"time\": \"2012-09-27T00:00:00+00:00\",\n      \"releaseTime\": \"2012-09-27T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w38b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w38b/12w38b.json\",\n      \"time\": \"2012-09-21T00:00:00+00:00\",\n      \"releaseTime\": \"2012-09-21T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w38a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w38a/12w38a.json\",\n      \"time\": \"2012-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2012-09-20T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w37a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w37a/12w37a.json\",\n      \"time\": \"2012-09-13T00:00:00+00:00\",\n      \"releaseTime\": \"2012-09-13T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w36a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w36a/12w36a.json\",\n      \"time\": \"2012-09-06T00:00:00+00:00\",\n      \"releaseTime\": \"2012-09-06T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w34b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w34b/12w34b.json\",\n      \"time\": \"2012-08-24T00:00:00+00:00\",\n      \"releaseTime\": \"2012-08-24T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w34a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w34a/12w34a.json\",\n      \"time\": \"2012-08-23T00:00:00+00:00\",\n      \"releaseTime\": \"2012-08-23T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w32a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w32a/12w32a.json\",\n      \"time\": \"2012-08-09T00:00:00+00:00\",\n      \"releaseTime\": \"2012-08-09T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w30d\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w30d/12w30d.json\",\n      \"time\": \"2012-07-25T00:00:00+00:00\",\n      \"releaseTime\": \"2012-07-25T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w30e\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w30e/12w30e.json\",\n      \"time\": \"2012-07-25T00:00:00+00:00\",\n      \"releaseTime\": \"2012-07-25T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w30c\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w30c/12w30c.json\",\n      \"time\": \"2012-07-24T00:00:00+00:00\",\n      \"releaseTime\": \"2012-07-24T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w30a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w30a/12w30a.json\",\n      \"time\": \"2012-07-23T00:00:00+00:00\",\n      \"releaseTime\": \"2012-07-23T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w30b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w30b/12w30b.json\",\n      \"time\": \"2012-07-23T00:00:00+00:00\",\n      \"releaseTime\": \"2012-07-23T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w27a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w27a/12w27a.json\",\n      \"time\": \"2012-07-05T00:00:00+00:00\",\n      \"releaseTime\": \"2012-07-05T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w26a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w26a/12w26a.json\",\n      \"time\": \"2012-06-27T00:00:00+00:00\",\n      \"releaseTime\": \"2012-06-27T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w25a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w25a/12w25a.json\",\n      \"time\": \"2012-06-21T00:00:00+00:00\",\n      \"releaseTime\": \"2012-06-21T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w24a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w24a/12w24a.json\",\n      \"time\": \"2012-06-14T00:00:00+00:00\",\n      \"releaseTime\": \"2012-06-14T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w23a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w23a/12w23a.json\",\n      \"time\": \"2012-06-07T00:00:00+00:00\",\n      \"releaseTime\": \"2012-06-07T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w23b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w23b/12w23b.json\",\n      \"time\": \"2012-06-07T00:00:00+00:00\",\n      \"releaseTime\": \"2012-06-07T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w22a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w22a/12w22a.json\",\n      \"time\": \"2012-05-31T00:00:00+00:00\",\n      \"releaseTime\": \"2012-05-31T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w21b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w21b/12w21b.json\",\n      \"time\": \"2012-05-25T00:00:00+00:00\",\n      \"releaseTime\": \"2012-05-25T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w21a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w21a/12w21a.json\",\n      \"time\": \"2012-05-24T00:00:00+00:00\",\n      \"releaseTime\": \"2012-05-24T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w19a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w19a/12w19a.json\",\n      \"time\": \"2012-05-10T00:00:00+00:00\",\n      \"releaseTime\": \"2012-05-10T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w18a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w18a/12w18a.json\",\n      \"time\": \"2012-05-03T00:00:00+00:00\",\n      \"releaseTime\": \"2012-05-03T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w17a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w17a/12w17a.json\",\n      \"time\": \"2012-04-26T00:00:00+00:00\",\n      \"releaseTime\": \"2012-04-26T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w16a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w16a/12w16a.json\",\n      \"time\": \"2012-04-19T00:00:00+00:00\",\n      \"releaseTime\": \"2012-04-19T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"1.2\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_2/1_2.json\",\n      \"time\": \"2012-02-29T00:00:00+00:00\",\n      \"releaseTime\": \"2012-02-29T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w08a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w08a/12w08a.json\",\n      \"time\": \"2012-02-23T00:00:00+00:00\",\n      \"releaseTime\": \"2012-02-23T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w07a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w07a/12w07a.json\",\n      \"time\": \"2012-02-15T00:00:00+00:00\",\n      \"releaseTime\": \"2012-02-15T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w07b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w07b/12w07b.json\",\n      \"time\": \"2012-02-15T00:00:00+00:00\",\n      \"releaseTime\": \"2012-02-15T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w06a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w06a/12w06a.json\",\n      \"time\": \"2012-02-09T00:00:00+00:00\",\n      \"releaseTime\": \"2012-02-09T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w05b\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w05b/12w05b.json\",\n      \"time\": \"2012-02-03T00:00:00+00:00\",\n      \"releaseTime\": \"2012-02-03T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w05a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w05a/12w05a.json\",\n      \"time\": \"2012-02-02T00:00:00+00:00\",\n      \"releaseTime\": \"2012-02-02T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w04a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w04a/12w04a.json\",\n      \"time\": \"2012-01-26T00:00:00+00:00\",\n      \"releaseTime\": \"2012-01-26T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w03a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w03a/12w03a.json\",\n      \"time\": \"2012-01-19T00:00:00+00:00\",\n      \"releaseTime\": \"2012-01-19T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"12w01a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/12w01a/12w01a.json\",\n      \"time\": \"2012-01-05T00:00:00+00:00\",\n      \"releaseTime\": \"2012-01-05T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"11w50a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/11w50a/11w50a.json\",\n      \"time\": \"2011-12-15T00:00:00+00:00\",\n      \"releaseTime\": \"2011-12-15T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"11w49a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/11w49a/11w49a.json\",\n      \"time\": \"2011-12-08T00:00:00+00:00\",\n      \"releaseTime\": \"2011-12-08T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"11w48a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/11w48a/11w48a.json\",\n      \"time\": \"2011-12-01T00:00:00+00:00\",\n      \"releaseTime\": \"2011-12-01T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"11w47a\",\n      \"type\": \"snapshot\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/11w47a/11w47a.json\",\n      \"time\": \"2011-11-24T00:00:00+00:00\",\n      \"releaseTime\": \"2011-11-24T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"1.0.0-rc2-3\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1.0.0-rc2-3/1.0.0-rc2-3.json\",\n      \"time\": \"2013-08-06T07:33:30-07:00\",\n      \"releaseTime\": \"2011-11-14T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"1.0.0-rc2-2\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1.0.0-rc2-2/1.0.0-rc2-2.json\",\n      \"time\": \"2019-04-21T21:04:24+00:00\",\n      \"releaseTime\": \"2011-11-14T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"1.0.0-rc2-1\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1.0.0-rc2-1/1.0.0-rc2-1.json\",\n      \"time\": \"2019-11-21T22:37:45+00:00\",\n      \"releaseTime\": \"2011-11-13T17:33:00+00:00\"\n    },\n    {\n      \"id\": \"1.0.0-rc1\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1.0.0-rc1/1.0.0-rc1.json\",\n      \"time\": \"2013-08-06T07:33:30-07:00\",\n      \"releaseTime\": \"2011-11-13T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"b1.9-pre6\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre6/b1_9-pre6.json\",\n      \"time\": \"2011-11-11T00:00:00+00:00\",\n      \"releaseTime\": \"2011-11-11T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.9-pre5\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre5/b1_9-pre5.json\",\n      \"time\": \"2011-10-27T00:00:00+00:00\",\n      \"releaseTime\": \"2011-10-27T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.9-pre4\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre4/b1_9-pre4.json\",\n      \"time\": \"2011-10-13T00:00:00+00:00\",\n      \"releaseTime\": \"2011-10-13T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.9-pre3\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre3/b1_9-pre3.json\",\n      \"time\": \"2011-10-06T00:00:00+00:00\",\n      \"releaseTime\": \"2011-10-06T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.9-pre2\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre2/b1_9-pre2.json\",\n      \"time\": \"2011-09-29T00:00:00+00:00\",\n      \"releaseTime\": \"2011-09-29T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.9-pre1\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre1/b1_9-pre1.json\",\n      \"time\": \"2011-09-22T00:00:00+00:00\",\n      \"releaseTime\": \"2011-09-22T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.8-pre2\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_8-pre2/b1_8-pre2.json\",\n      \"time\": \"2011-09-13T00:00:00+00:00\",\n      \"releaseTime\": \"2011-09-13T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.8-pre1-2\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_8-pre1/b1_8-pre1.json\",\n      \"time\": \"2011-09-09T00:00:00+00:00\",\n      \"releaseTime\": \"2011-09-09T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.8-pre1-1\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1.8-pre1-1/b1.8-pre1-1.json\",\n      \"time\": \"2011-09-09T00:00:00+00:00\",\n      \"releaseTime\": \"2011-09-09T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.6-tb3\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1.6-tb3/b1.6-tb3.json\",\n      \"time\": \"2013-08-06T07:33:30-07:00\",\n      \"releaseTime\": \"2011-05-24T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"b1.1-1\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1.1-1/b1.1-1.json\",\n      \"time\": \"2016-02-02T15:37:47+00:00\",\n      \"releaseTime\": \"2010-12-21T22:00:00+00:00\"\n    },\n    {\n      \"id\": \"b1.1-2\",\n      \"type\": \"old_beta\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1.1-2/b1.1-2.json\",\n      \"time\": \"2016-02-02T15:37:47+00:00\",\n      \"releaseTime\": \"2010-12-21T22:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.1.1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.1.1/a1.1.1.json\",\n      \"time\": \"2010-09-18T19:29:56+02:00\",\n      \"releaseTime\": \"2010-09-18T19:29:56+02:00\"\n    },\n    {\n      \"id\": \"a1.1.0-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.1.0-2/a1.1.0-2.json\",\n      \"time\": \"2016-02-02T15:37:47+00:00\",\n      \"releaseTime\": \"2010-09-12T22:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.1.0-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.1.0-1/a1.1.0-1.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-09-10T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.17_03\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.17_03/a1.0.17_03.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-08-23T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.16_02\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.16_02/a1.0.16_02.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-08-13T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.16_01\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.16_01/a1.0.16_01.json\",\n      \"time\": \"2013-08-06T04:00:00-07:00\",\n      \"releaseTime\": \"2010-08-12T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"a1.0.14-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.14-1/a1.0.14-1.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-30T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.14-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.14-2/a1.0.14-2.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-30T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.13_01-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.13_01-1/a1.0.13_01-1.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-29T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.13_01-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.13_01-2/a1.0.13_01-2.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-29T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.13\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.13/a1.0.13.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-28T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.12\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.12/a1.0.12.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-26T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.10\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.10/a1.0.10.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-22T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.9\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.9/a1.0.9.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-21T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.7\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.7/a1.0.7.json\",\n      \"time\": \"2013-08-06T04:00:00-07:00\",\n      \"releaseTime\": \"2010-07-19T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"a1.0.8_01\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.8_01/a1.0.8_01.json\",\n      \"time\": \"2013-08-06T04:00:00-07:00\",\n      \"releaseTime\": \"2010-07-19T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"a1.0.6_01\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.6_01/a1.0.6_01.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-17T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.6_03\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.6_03/a1.0.6_03.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-17T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.6\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.6/a1.0.6.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-16T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.5\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.5/a1.0.5.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-13T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.3\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.3/a1.0.3.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-07T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.2_01\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.2_01/a1.0.2_01.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-06T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.2_02\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.2_02/a1.0.2_02.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-06T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"a1.0.1_01\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/a1.0.1_01/a1.0.1_01.json\",\n      \"time\": \"2017-10-22T05:26:50+00:00\",\n      \"releaseTime\": \"2010-07-03T22:00:00+00:00\"\n    },\n    {\n      \"id\": \"inf-20100630-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100630-1/inf-20100630-1.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-30T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100630-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100630-2/inf-20100630-2.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-30T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100629\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100629/inf-20100629.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-29T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100627\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100627/inf-20100627.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-27T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"inf-20100625-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100625-1/inf-20100625-1.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-25T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100625-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100625-2/inf-20100625-2.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-25T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100624\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100624/inf-20100624.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-24T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100617-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100617-2/inf-20100617-2.json\",\n      \"time\": \"2010-06-17T00:00:00-00:00\",\n      \"releaseTime\": \"2010-06-17T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100617-3\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100617-3/inf-20100617-3.json\",\n      \"time\": \"2010-06-17T00:00:00-00:00\",\n      \"releaseTime\": \"2010-06-17T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100615\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100615/inf-20100615.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-15T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100611\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100611/inf-20100611.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-11T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100608\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100608/inf-20100608.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-08T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"inf-20100607\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100607/inf-20100607.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-06-07T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100420\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100420/inf-20100420.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-04-20T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"inf-20100415\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100415/inf-20100415.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-04-15T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"inf-20100414\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100414/inf-20100414.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-04-14T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100413\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100413/inf-20100413.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-04-13T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100330\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100330/inf-20100330.json\",\n      \"time\": \"2010-03-30T00:00:00-00:00\",\n      \"releaseTime\": \"2010-03-30T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100327\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100327/inf-20100327.json\",\n      \"time\": \"2010-03-27T00:00:00-00:00\",\n      \"releaseTime\": \"2010-03-27T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100325\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100325/inf-20100325.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-03-25T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100321\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100321/inf-20100321.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-03-21T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100320\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100320/inf-20100320.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-03-20T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100316\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100316/inf-20100316.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-03-16T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"inf-20100313\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/inf-20100313/inf-20100313.json\",\n      \"time\": \"2015-04-13T07:33:30-07:00\",\n      \"releaseTime\": \"2010-03-13T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"in-20100223\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100223/in-20100223.json\",\n      \"time\": \"2013-08-06T07:33:30-07:00\",\n      \"releaseTime\": \"2010-02-23T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"in-20100219\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100219/in-20100219.json\",\n      \"time\": \"2017-10-24T00:00:00+00:00\",\n      \"releaseTime\": \"2010-02-19T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"in-20100218\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100218/in-20100218.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-02-18T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"in-20100214-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100214-2/in-20100214-2.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-02-14T00:00:01+00:00\"\n    },\n    {\n      \"id\": \"in-20100214-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100214-1/in-20100214-1.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-02-14T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"in-20100212-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100212-2/in-20100212-2.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-02-12T00:00:01+00:00\"\n    },\n    {\n      \"id\": \"in-20100212-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100212-1/in-20100212-1.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-02-12T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"in-20100207-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100207-2/in-20100207-2.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-02-07T00:00:01+00:00\"\n    },\n    {\n      \"id\": \"in-20100207-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100207-1/in-20100207-1.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-02-07T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"in-20100206-2103\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100206-2103/in-20100206-2103.json\",\n      \"time\": \"2020-10-14T18:51:02+00:00\",\n      \"releaseTime\": \"2010-02-06T21:03:00+00:00\"\n    },\n    {\n      \"id\": \"in-20100203\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100203/in-20100203.json\",\n      \"time\": \"2017-09-20T19:21:48+02:00\",\n      \"releaseTime\": \"2010-02-03T00:30:00+02:00\"\n    },\n    {\n      \"id\": \"in-20100201-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100201-2/in-20100201-2.json\",\n      \"time\": \"2017-09-20T19:21:48+02:00\",\n      \"releaseTime\": \"2010-02-01T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"in-20100201-3\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100201-3/in-20100201-3.json\",\n      \"time\": \"2017-09-20T19:21:48+02:00\",\n      \"releaseTime\": \"2010-02-01T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"in-20100130\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100130/in-20100130.json\",\n      \"time\": \"2013-08-06T07:33:30-07:00\",\n      \"releaseTime\": \"2010-01-30T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"in-20100129\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100129/in-20100129.json\",\n      \"time\": \"2010-01-29T00:00:00-00:00\",\n      \"releaseTime\": \"2010-01-29T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"in-20100128-2304\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100128-2304/in-20100128-2304.json\",\n      \"time\": \"2010-01-28T23:04:00-00:00\",\n      \"releaseTime\": \"2010-01-28T23:04:00-00:00\"\n    },\n    {\n      \"id\": \"in-20100125-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100125-2/in-20100125-2.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-01-25T15:00:01-07:00\"\n    },\n    {\n      \"id\": \"in-20100125-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100125-1/in-20100125-1.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-01-25T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"in-20100111-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100111-1/in-20100111-1.json\",\n      \"time\": \"2017-09-20T00:00:00+00:00\",\n      \"releaseTime\": \"2010-01-11T15:00:00-07:00\"\n    },\n    {\n      \"id\": \"in-20100105\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20100105/in-20100105.json\",\n      \"time\": \"2013-08-06T07:33:30-07:00\",\n      \"releaseTime\": \"2010-01-05T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"in-20091231-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20091231-2/in-20091231-2.json\",\n      \"time\": \"2021-06-26T22:48:05+00:00\",\n      \"releaseTime\": \"2009-12-31T23:55:00+00:00\"\n    },\n    {\n      \"id\": \"in-20091223-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/in-20091223-2/in-20091223-2.json\",\n      \"time\": \"2009-12-23T00:00:00-00:00\",\n      \"releaseTime\": \"2009-12-23T00:00:00-00:00\"\n    },\n    {\n      \"id\": \"c0.30-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.30-2/c0.30-2.json\",\n      \"time\": \"2013-08-06T17:33:30+03:00\",\n      \"releaseTime\": \"2009-11-10T00:00:01+02:00\"\n    },\n    {\n      \"id\": \"c0.30-1\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.30-1/c0.30-1.json\",\n      \"time\": \"2013-08-06T17:33:30+03:00\",\n      \"releaseTime\": \"2009-11-10T00:00:00+02:00\"\n    },\n    {\n      \"id\": \"c0.29_02\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.29_02/c0.29_02.json\",\n      \"time\": \"2017-09-20T03:05:00+01:00\",\n      \"releaseTime\": \"2009-10-30T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"c0.29_01\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.29_01/c0.29_01.json\",\n      \"time\": \"2021-06-26T12:16:50+00:00\",\n      \"releaseTime\": \"2009-10-29T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"c0.28_01\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.28_01/c0.28_01.json\",\n      \"time\": \"2017-09-20T03:19:48+01:00\",\n      \"releaseTime\": \"2009-10-27T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"c0.27_st\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.27_st/c0.27_st.json\",\n      \"time\": \"2017-09-17T16:33:30+02:00\",\n      \"releaseTime\": \"2009-10-24T24:00:00+00:00\"\n    },\n    {\n      \"id\": \"c0.25_05_st\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.25_05_st/c0.25_05_st.json\",\n      \"time\": \"2009-09-03T00:00:00+00:00\",\n      \"releaseTime\": \"2009-09-03T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"c0.24_st_03\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.24_st_03/c0.24_st_03.json\",\n      \"time\": \"2009-09-01T00:00:00+00:00\",\n      \"releaseTime\": \"2009-09-01T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"c0.0.22a_05\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.22a_05/c0.0.22a_05.json\",\n      \"time\": \"2009-06-30T00:00:00+00:00\",\n      \"releaseTime\": \"2009-06-30T00:00:00+00:00\"\n    },\n    {\n      \"id\": \"c0.0.21a\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.21a/c0.0.21a.json\",\n      \"time\": \"2017-09-20T03:12:34+01:00\",\n      \"releaseTime\": \"2009-06-22T23:11:11+01:00\"\n    },\n    {\n      \"id\": \"c0.0.20a_01\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.20a_01/c0.0.20a_01.json\",\n      \"time\": \"2020-10-14T23:51:10+00:00\",\n      \"releaseTime\": \"2009-06-20T23:10:00+00:00\"\n    },\n    {\n      \"id\": \"c0.0.19a_06-2\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.19a_06-2/c0.0.19a_06-2.json\",\n      \"time\": \"2020-10-17T19:47:58-00:00\",\n      \"releaseTime\": \"2009-06-20T01:37:00-00:00\"\n    },\n    {\n      \"id\": \"c0.0.18a_02\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.18a_02/c0.0.18a_02.json\",\n      \"time\": \"2020-10-15T18:45:15-00:00\",\n      \"releaseTime\": \"2009-06-14T10:53:00-00:00\"\n    },\n    {\n      \"id\": \"c0.0.17a\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.17a/c0.0.17a.json\",\n      \"time\": \"2021-04-17T21:49:29-00:00\",\n      \"releaseTime\": \"2009-06-10T20:14:00-00:00\"\n    },\n    {\n      \"id\": \"c0.0.16a_02\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.16a_02/c0.0.16a_02.json\",\n      \"time\": \"2019-10-20T21:42:41+00:00\",\n      \"releaseTime\": \"2009-06-08T12:47:00+00:00\"\n    },\n    {\n      \"id\": \"c0.0.23a_01\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.23a_01/c0.0.23a_01.json\",\n      \"time\": \"2013-08-06T15:33:30+01:00\",\n      \"releaseTime\": \"2009-05-28T23:11:11+01:00\"\n    },\n    {\n      \"id\": \"c0.0.14a_08\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.14a_08/c0.0.14a_08.json\",\n      \"time\": \"2019-10-20T21:41:28+00:00\",\n      \"releaseTime\": \"2009-05-28T22:37:00+00:00\"\n    },\n    {\n      \"id\": \"c0.0.12a_03\",\n      \"type\": \"old_alpha\",\n      \"url\": \"https://zkitefly.github.io/unlisted-versions-of-minecraft/files/c0.0.12a_03/c0.0.12a_03.json\",\n      \"time\": \"2021-04-26T19:18:00-00:00\",\n      \"releaseTime\": \"2009-05-20T00:00:00-00:00\"\n    }\n  ]\n}\n"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/game/version-alias.csv",
    "content": "1.14_combat-212796,1.14.3 - Combat Test\n1.14_combat-0,Combat Test 2\n1.14_combat-3,Combat Test 3\n1.15_combat-1,Combat Test 4\n1.15_combat-6,Combat Test 5\n1.16_combat-0,Combat Test 6\n1.16_combat-1,Combat Test 7\n1.16_combat-2,Combat Test 7b\n1.16_combat-3,Combat Test 7c\n1.16_combat-4,Combat Test 8\n1.16_combat-5,Combat Test 8b\n1.16_combat-6,Combat Test 8c\n1.18_experimental-snapshot-1,1.18 Experimental Snapshot 1,1.18 experimental snapshot 1\n1.18_experimental-snapshot-2,1.18 Experimental Snapshot 2,1.18 experimental snapshot 2\n1.18_experimental-snapshot-3,1.18 Experimental Snapshot 3,1.18 experimental snapshot 3\n1.18_experimental-snapshot-4,1.18 Experimental Snapshot 4,1.18 experimental snapshot 4\n1.18_experimental-snapshot-5,1.18 Experimental Snapshot 5,1.18 experimental snapshot 5\n1.18_experimental-snapshot-6,1.18 Experimental Snapshot 6,1.18 experimental snapshot 6\n1.18_experimental-snapshot-7,1.18 Experimental Snapshot 7,1.18 experimental snapshot 7\n1.19_deep_dark_experimental_snapshot-1,Deep Dark Experimental Snapshot 1\n2.0,2.0_red,2.0_blue,2.0_purple\n20w14infinite,20w14~\n22w13oneBlockAtATime,22w13oneblockatatime"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/game/versions.txt",
    "content": "1.0\n11w47a\n11w48a\n11w49a\n11w50a\n12w01a\n1.1\n12w03a\n12w04a\n12w05a\n12w05b\n12w06a\n12w07a\n12w07b\n12w08a\n1.2\n1.2.1\n1.2.2\n1.2.3\n1.2.4\n1.2.5\n12w15a\n12w16a\n12w17a\n12w18a\n12w19a\n12w21a\n12w21b\n12w22a\n12w23a\n12w23b\n12w24a\n12w25a\n12w26a\n12w27a\n12w30a\n12w30b\n12w30c\n12w30d\n12w30e\n1.3\n1.3.1\n1.3.2\n12w32a\n12w34a\n12w34b\n12w36a\n12w37a\n12w38a\n12w38b\n12w39a\n12w39b\n12w40a\n12w40b\n12w41a\n12w41b\n12w42a\n12w42b\n1.4\n1.4.1\n1.4.2\n1.4.3\n1.4.4\n12w49a\n12w50a\n12w50b\n1.4.5\n1.4.6\n1.4.7\n13w01a\n13w01b\n13w02a\n13w02b\n13w03a\n13w04a\n13w05a\n13w05b\n13w06a\n13w07a\n13w09a\n13w09b\n13w09c\n13w10a\n13w10b\n1.5\n13w11a\n13w12~\n1.5.1\n2.0\n1.5.2\n13w16a\n13w16b\n13w17a\n13w18a\n13w18b\n13w18c\n13w19a\n13w21a\n13w21b\n13w22a\n13w23a\n13w23b\n13w24a\n13w24b\n13w25a\n13w25b\n13w25c\n13w26a\n1.6\n1.6.1\n1.6.2\n1.6.3\n1.6.4\n13w36a\n13w36b\n13w37a\n13w37b\n13w38a\n13w38b\n13w38c\n13w39a\n13w39b\n13w41a\n13w41b\n13w42a\n13w42b\n13w43a\n1.7\n1.7.1\n1.7.2\n13w47a\n13w47b\n13w47c\n13w47d\n13w47e\n13w48a\n13w48b\n13w49a\n1.7.3\n1.7.4\n1.7.5\n1.7.6-pre1\n1.7.6-pre2\n1.7.6\n1.7.7\n1.7.8\n1.7.9\n1.7.10-pre1\n1.7.10-pre2\n1.7.10-pre3\n1.7.10-pre4\n1.7.10\n14w02a\n14w02b\n14w02c\n14w03a\n14w03b\n14w04a\n14w04b\n14w05a\n14w05b\n14w06a\n14w06b\n14w07a\n14w08a\n14w10a\n14w10b\n14w10c\n14w11a\n14w11b\n14w17a\n14w18a\n14w18b\n14w19a\n14w20a\n14w20b\n14w21a\n14w21b\n14w25a\n14w25b\n14w26a\n14w26b\n14w26c\n14w27a\n14w27b\n14w28a\n14w28b\n14w29a\n14w29b\n14w30a\n14w30b\n14w30c\n14w31a\n14w32a\n14w32b\n14w32c\n14w32d\n14w33a\n14w33b\n14w33c\n14w34a\n14w34b\n14w34c\n14w34d\n1.8-pre1\n1.8-pre2\n1.8-pre3\n1.8\n1.8.1-pre1\n1.8.1-pre2\n1.8.1-pre3\n1.8.1-pre4\n1.8.1-pre5\n1.8.1\n1.8.2-pre1\n1.8.2-pre2\n1.8.2-pre3\n1.8.2-pre4\n1.8.2-pre5\n1.8.2-pre6\n1.8.2-pre7\n1.8.2\n1.8.3\n15w14a\n1.8.4\n1.8.5\n1.8.6\n1.8.7\n1.8.8\n1.8.9\n15w31a\n15w31b\n15w31c\n15w32a\n15w32b\n15w32c\n15w33a\n15w33b\n15w33c\n15w34a\n15w34b\n15w34c\n15w34d\n15w35a\n15w35b\n15w35c\n15w35d\n15w35e\n15w36a\n15w36b\n15w36c\n15w36d\n15w37a\n15w38a\n15w38b\n15w39a\n15w39b\n15w39c\n15w40a\n15w40b\n15w41a\n15w41b\n15w42a\n15w43a\n15w43b\n15w43c\n15w44a\n15w44b\n15w45a\n15w46a\n15w47a\n15w47b\n15w47c\n15w49a\n15w49b\n15w50a\n15w51a\n15w51b\n16w02a\n16w03a\n16w04a\n16w05a\n16w05b\n16w06a\n16w07a\n16w07b\n1.9-pre1\n1.9-pre2\n1.9-pre3\n1.9-pre4\n1.9\n1.9.1-pre1\n1.9.1-pre2\n1.9.1-pre3\n1.9.1\n1.9.2\n1.RV-Pre1\n16w14a\n16w15a\n16w15b\n1.9.3-pre1\n1.9.3-pre2\n1.9.3-pre3\n1.9.3\n1.9.4\n16w20a\n16w21a\n16w21b\n1.10-pre1\n1.10-pre2\n1.10\n1.10.1\n1.10.2\n16w32a\n16w32b\n16w33a\n16w35a\n16w36a\n16w38a\n16w39a\n16w39b\n16w39c\n16w40a\n16w41a\n16w42a\n16w43a\n16w44a\n1.11-pre1\n1.11\n16w50a\n1.11.1\n1.11.2\n17w06a\n17w13a\n17w13b\n17w14a\n17w15a\n17w16a\n17w16b\n17w17a\n17w17b\n17w18a\n17w18b\n1.12-pre1\n1.12-pre2\n1.12-pre3\n1.12-pre4\n1.12-pre5\n1.12-pre6\n1.12-pre7\n1.12\n17w31a\n1.12.1-pre1\n1.12.1\n1.12.2-pre1\n1.12.2-pre2\n1.12.2\n17w43a\n17w43b\n17w45a\n17w45b\n17w46a\n17w47a\n17w47b\n17w48a\n17w49a\n17w49b\n17w50a\n18w01a\n18w02a\n18w03a\n18w03b\n18w05a\n18w06a\n18w07a\n18w07b\n18w07c\n18w08a\n18w08b\n18w09a\n18w10a\n18w10b\n18w10c\n18w10d\n18w11a\n18w14a\n18w14b\n18w15a\n18w16a\n18w19a\n18w19b\n18w20a\n18w20b\n18w20c\n18w21a\n18w21b\n18w22a\n18w22b\n18w22c\n1.13-pre1\n1.13-pre2\n1.13-pre3\n1.13-pre4\n1.13-pre5\n1.13-pre6\n1.13-pre7\n1.13-pre8\n1.13-pre9\n1.13-pre10\n1.13\n18w30a\n18w30b\n18w31a\n18w32a\n18w33a\n1.13.1-pre1\n1.13.1-pre2\n1.13.1\n1.13.2-pre1\n1.13.2-pre2\n1.13.2\n18w43a\n18w43b\n18w43c\n18w44a\n18w45a\n18w46a\n18w47a\n18w47b\n18w48a\n18w48b\n18w49a\n18w50a\n19w02a\n19w03a\n19w03b\n19w03c\n19w04a\n19w04b\n19w05a\n19w06a\n19w07a\n19w08a\n19w08b\n19w09a\n19w11a\n19w11b\n19w12a\n19w12b\n19w13a\n19w13b\n3D Shareware v1.34\n19w14a\n19w14b\n1.14-pre1\n1.14-pre2\n1.14-pre3\n1.14-pre4\n1.14-pre5\n1.14\n1.14.1-pre1\n1.14.1-pre2\n1.14.1\n1.14.2-pre1\n1.14.2-pre2\n1.14.2-pre3\n1.14.2-pre4\n1.14.2\n1.14.3-pre1\n1.14.3-pre2\n1.14.3-pre3\n1.14.3-pre4\n1.14_combat-212796\n1.14.3\n1.14.4-pre1\n1.14.4-pre2\n1.14.4-pre3\n1.14.4-pre4\n1.14.4-pre5\n1.14.4-pre6\n1.14.4-pre7\n1.14.4\n1.14_combat-0\n1.14_combat-3\n19w34a\n19w35a\n19w36a\n19w37a\n19w38a\n19w38b\n19w39a\n19w40a\n19w41a\n19w42a\n19w44a\n19w45a\n19w45b\n19w46a\n19w46b\n1.15-pre1\n1.15-pre2\n1.15-pre3\n1.15_combat-1\n1.15-pre4\n1.15-pre5\n1.15-pre6\n1.15-pre7\n1.15\n1.15.1-pre1\n1.15.1\n1.15.2-pre1\n1.15.2-pre2\n1.15_combat-6\n1.15.2\n20w06a\n20w07a\n20w08a\n20w09a\n20w10a\n20w11a\n20w12a\n20w13a\n20w13b\n20w14infinite\n20w14a\n20w15a\n20w16a\n20w17a\n20w18a\n20w19a\n20w20a\n20w20b\n20w21a\n20w22a\n1.16-pre1\n1.16-pre2\n1.16-pre3\n1.16-pre4\n1.16-pre5\n1.16-pre6\n1.16-pre7\n1.16-pre8\n1.16-rc1\n1.16\n1.16.1\n20w27a\n20w28a\n20w29a\n20w30a\n1.16.2-pre1\n1.16.2-pre2\n1.16.2-pre3\n1.16_combat-0\n1.16.2-rc1\n1.16.2-rc2\n1.16.2\n1.16_combat-1\n1.16_combat-2\n1.16_combat-3\n1.16_combat-4\n1.16_combat-5\n1.16_combat-6\n1.16.3-rc1\n1.16.3\n1.16.4-pre1\n1.16.4-pre2\n1.16.4-rc1\n1.16.4\n1.16.5-rc1\n1.16.5\n20w45a\n20w46a\n20w48a\n20w49a\n20w51a\n21w03a\n21w05a\n21w05b\n21w06a\n21w07a\n21w08a\n21w08b\n21w10a\n21w11a\n21w13a\n21w14a\n21w15a\n21w16a\n21w17a\n21w18a\n21w19a\n21w20a\n1.17-pre1\n1.17-pre2\n1.17-pre3\n1.17-pre4\n1.17-pre5\n1.17-rc1\n1.17-rc2\n1.17\n1.17.1-pre1\n1.17.1-pre2\n1.17.1-pre3\n1.17.1-rc1\n1.17.1-rc2\n1.17.1\n1.18_experimental-snapshot-1\n1.18_experimental-snapshot-2\n1.18_experimental-snapshot-3\n1.18_experimental-snapshot-4\n1.18_experimental-snapshot-5\n1.18_experimental-snapshot-6\n1.18_experimental-snapshot-7\n21w37a\n21w38a\n21w39a\n21w40a\n21w41a\n21w42a\n21w43a\n21w44a\n1.18-pre1\n1.18-pre2\n1.18-pre3\n1.18-pre4\n1.18-pre5\n1.18-pre6\n1.18-pre7\n1.18-pre8\n1.18-rc1\n1.18-rc2\n1.18-rc3\n1.18-rc4\n1.18\n1.18.1-pre1\n1.18.1-rc1\n1.18.1-rc2\n1.18.1-rc3\n1.18.1\n22w03a\n22w05a\n22w06a\n22w07a\n1.19_deep_dark_experimental_snapshot-1\n1.18.2-pre1\n1.18.2-pre2\n1.18.2-pre3\n1.18.2-rc1\n1.18.2\n22w13oneBlockAtATime\n22w11a\n22w12a\n22w13a\n22w14a\n22w15a\n22w16a\n22w16b\n22w17a\n22w18a\n22w19a\n1.19-pre1\n1.19-pre2\n1.19-pre3\n1.19-pre4\n1.19-pre5\n1.19-rc1\n1.19-rc2\n1.19\n22w24a\n1.19.1-pre1\n1.19.1-pre2\n1.19.1-pre3\n1.19.1-pre4\n1.19.1-pre5\n1.19.1-pre6\n1.19.1-rc1\n1.19.1-rc2\n1.19.1-rc3\n1.19.1\n1.19.2-rc1\n1.19.2-rc2\n1.19.2\n22w42a\n22w43a\n22w44a\n22w45a\n22w46a\n1.19.3-pre1\n1.19.3-pre2\n1.19.3-pre3\n1.19.3-rc1\n1.19.3-rc2\n1.19.3-rc3\n1.19.3\n23w03a\n23w04a\n23w05a\n23w06a\n23w07a\n1.19.4-pre1\n1.19.4-pre2\n1.19.4-pre3\n1.19.4-pre4\n1.19.4-rc1\n1.19.4-rc2\n1.19.4-rc3\n1.19.4\n23w12a\n23w13a\n23w13a_or_b\n23w14a\n23w16a\n23w17a\n23w18a\n1.20-pre1\n1.20-pre2\n1.20-pre3\n1.20-pre4\n1.20-pre5\n1.20-pre6\n1.20-pre7\n1.20-rc1\n1.20\n1.20.1-rc1\n1.20.1\n23w31a\n23w32a\n23w33a\n23w35a\n1.20.2-pre1\n1.20.2-pre2\n1.20.2-pre3\n1.20.2-pre4\n1.20.2-rc1\n1.20.2-rc2\n1.20.2\n23w40a\n23w41a\n23w42a\n23w43a\n23w43b\n23w44a\n23w45a\n23w46a\n1.20.3-pre1\n1.20.3-pre2\n1.20.3-pre3\n1.20.3-pre4\n1.20.3-rc1\n1.20.3\n1.20.4-rc1\n1.20.4\n23w51a\n23w51b\n24w03a\n24w03b\n24w04a\n24w05a\n24w05b\n24w06a\n24w07a\n24w09a\n24w10a\n24w11a\n24w12a\n24w13a\n24w14potato\n24w14a\n1.20.5-pre1\n1.20.5-pre2\n1.20.5-pre3\n1.20.5-pre4\n1.20.5-rc1\n1.20.5-rc2\n1.20.5-rc3\n1.20.5\n1.20.6-rc1\n1.20.6\n24w18a\n24w19a\n24w19b\n24w20a\n24w21a\n24w21b\n1.21-pre1\n1.21-pre2\n1.21-pre3\n1.21-pre4\n1.21-rc1\n1.21\n1.21.1-rc1\n1.21.1\n24w33a\n24w34a\n24w35a\n24w36a\n24w37a\n24w38a\n24w39a\n24w40a\n1.21.2-pre1\n1.21.2-pre2\n1.21.2-pre3\n1.21.2-pre4\n1.21.2-pre5\n1.21.2-rc1\n1.21.2-rc2\n1.21.2\n1.21.3\n24w44a\n24w45a\n24w46a\n1.21.4-pre1\n1.21.4-pre2\n1.21.4-pre3\n1.21.4-rc1\n1.21.4-rc2\n1.21.4-rc3\n1.21.4\n25w02a\n25w03a\n25w04a\n25w05a\n25w06a\n25w07a\n25w08a\n25w09a\n25w09b\n25w10a\n1.21.5-pre1\n1.21.5-pre2\n1.21.5-pre3\n1.21.5-rc1\n1.21.5-rc2\n1.21.5\n25w14craftmine\n25w15a\n25w16a\n25w17a\n25w18a\n25w19a\n25w20a\n25w21a\n1.21.6-pre1\n1.21.6-pre2\n1.21.6-pre3\n1.21.6-pre4\n1.21.6-rc1\n1.21.6\n1.21.7-rc1\n1.21.7-rc2\n1.21.7\n1.21.8-rc1\n1.21.8\n25w31a\n25w32a\n25w33a\n25w34a\n25w34b\n25w35a\n25w36a\n25w36b\n25w37a\n1.21.9-pre1\n1.21.9-pre2\n1.21.9-pre3\n1.21.9-pre4\n1.21.9-rc1\n1.21.9\n1.21.10-rc1\n1.21.10\n25w41a\n25w42a\n25w43a\n25w44a\n25w45a\n25w45a_unobfuscated\n25w46a\n25w46a_unobfuscated\n1.21.11-pre1\n1.21.11-pre1_unobfuscated\n1.21.11-pre2\n1.21.11-pre2_unobfuscated\n1.21.11-pre3\n1.21.11-pre3_unobfuscated\n1.21.11-pre4\n1.21.11-pre4_unobfuscated\n1.21.11-pre5\n1.21.11-pre5_unobfuscated\n1.21.11-rc1\n1.21.11-rc1_unobfuscated\n1.21.11-rc2\n1.21.11-rc2_unobfuscated\n1.21.11-rc3\n1.21.11-rc3_unobfuscated\n1.21.11\n1.21.11_unobfuscated\n"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/lang/default_script.csv",
    "content": "Arab,ar,fa,ps,ur\nArmn,hy\nBeng,as,bn\nBlis,zbl\nCyrl,ab,be,bg,kk,mk,ru,uk\nDeva,hi,mr,ne,kok,mai\nEthi,am,ti\nGeor,ka\nGrek,el\nGujr,gu\nGuru,pa\nHant,lzh\nHebr,he,yi\nJpan,ja\nKhmr,km\nKnda,kn\nKore,ko\nLaoo,lo\nLatn,af,ay,bs,ca,ch,cs,cy,da,de,en,eo,es,et,eu,fi,fj,fo,fr,fy,ga,gl,gn,gv,hr,ht,hu,id,is,it,kl,la,lb,ln,lt,lv,mg,mh,ms,mt,na,nb,nd,nl,nn,no,nr,ny,om,pl,pt,qu,rm,rn,ro,rw,sg,sk,sl,sm,so,sq,ss,st,sv,sw,tl,tn,to,tr,ts,ve,vi,xh,zu,dsb,frr,frs,gsw,hsb,men,nds,niu,nso,tem,tkl,tmh,tpi,tvl,tailo,pinyin,hepburn,pehoeji,tongyong,wadegile\nMlym,ml\nMymr,my\nNkoo,nqo\nOrya,or\nSinh,si\nTaml,ta\nTelu,te\nThaa,dv\nThai,th\nTibt,dz\n"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/lang/language_aliases.csv",
    "content": "aa,aar\nab,abk\nae,ave\naf,afr\nak,aka\nam,amh\nan,arg\nar,ara\nas,asm\nav,ava\nay,aym\naz,aze\nba,bak\nbe,bel\nbg,bul\nbh,bih\nbi,bis\nbm,bam\nbn,ben\nbo,bod\nbr,bre\nbs,bos\nca,cat\nce,che\nch,cha\nco,cos\ncr,cre\ncs,ces\ncu,chu\ncv,chv\ncy,cym\nda,dan\nde,deu\ndv,div\ndz,dzo\nee,ewe\nel,ell\nen,eng\neo,epo\nes,spa\net,est\neu,eus\nfa,fas\nff,ful\nfi,fin\nfj,fij\nfo,fao\nfr,fra\nfy,fry\nga,gle\ngd,gla\ngl,glg\ngn,grn\ngu,guj\ngv,glv\nha,hau\nhe,heb\nhi,hin\nho,hmo\nhr,hrv\nht,hat\nhu,hun\nhy,hye\nhz,her\nia,ina\nid,ind\nie,ile\nig,ibo\nii,iii\nik,ipk\nin,ind\nio,ido\nis,isl\nit,ita\niu,iku\nja,jpn\nji,yid\njv,jav\nka,kat\nkg,kon\nki,kik\nkj,kua\nkk,kaz\nkl,kal\nkm,khm\nkn,kan\nko,kor\nkr,kau\nks,kas\nku,kur\nkv,kom\nkw,cor\nky,kir\nla,lat\nlb,ltz\nlg,lug\nli,lim\nln,lin\nlo,lao\nlt,lit\nlu,lub\nlv,lav\nmg,mlg\nmh,mah\nmi,mri\nmk,mkd\nml,mal\nmn,mon\nmo,mol\nmr,mar\nms,msa\nmt,mlt\nmy,mya\nna,nau\nnb,nob\nnd,nde\nne,nep\nng,ndo\nnl,nld\nnn,nno\nno,nor\nnr,nbl\nnv,nav\nny,nya\noc,oci\noj,oji\nom,orm\nor,ori\nos,oss\npa,pan\npi,pli\npl,pol\nps,pus\npt,por\nqu,que\nrm,roh\nrn,run\nro,ron\nru,rus\nrw,kin\nsa,san\nsc,srd\nsd,snd\nse,sme\nsg,sag\nsi,sin\nsk,slk\nsl,slv\nsm,smo\nsn,sna\nso,som\nsq,sqi\nsr,srp\nss,ssw\nst,sot\nsu,sun\nsv,swe\nsw,swa\nta,tam\nte,tel\ntg,tgk\nth,tha\nti,tir\ntk,tuk\ntl,tgl\ntn,tsn\nto,ton\ntr,tur\nts,tso\ntt,tat\ntw,twi\nty,tah\nug,uig\nuk,ukr\nur,urd\nuz,uzb\nve,ven\nvi,vie\nvo,vol\nwa,wln\nwo,wol\nxh,xho\nyi,yid\nza,zha\nzh,zho\nzu,zul"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/lang/sublanguages.csv",
    "content": "ak,tw,fat\nar,aao,abh,abv,acm,acq,acw,acx,acy,adf,aeb,aec,afb,apc,apd,arb,arq,ars,ary,arz,auz,avl,ayh,ayl,ayn,ayp,pga,shu,ssh\nay,ayc,ayr\naz,azb,azj\ncr,crj,crk,crl,crm,csw,cwd\net,ekk,vro\nfa,pes,prs\nff,ffm,fub,fuc,fue,fuf,fuh,fui,fuq,fuv\ngn,gnw,gug,gui,gun,nhd\nik,esi,esk\niu,ike,ikt\nkg,kng,kwy,ldi\nkr,kby,knc,krt\nku,ckb,kmr,sdh\nkv,koi,kpv\nlv,ltg,lvs\nmg,bhr,bmm,bzc,msh,plt,skg,tdx,tkg,txy,xmv,xmw\nmn,khk,mvf\nms,id,bjn,btj,bve,bvu,coa,dup,hji,jak,jax,kvb,kvr,kxd,lce,lcf,liw,max,meo,mfa,mfb,min,mqg,msi,mui,orn,ors,pel,pse,tmw,urk,vkk,vkt,xmm,zlm,zmi,zsm\nne,dty,npi\nno,nb,nn\noj,ciw,ojb,ojc,ojg,ojs,ojw,otw\nom,gax,gaz,hae,orc\nor,ory,spv\nps,pbt,pbu,pst\nqu,qub,qud,quf,qug,quh,quk,qul,qup,qur,qus,quw,qux,quy,quz,qva,qvc,qve,qvh,qvi,qvj,qvl,qvm,qvn,qvo,qvp,qvs,qvw,qvz,qwa,qwc,qwh,qws,qxa,qxc,qxh,qxl,qxn,qxo,qxp,qxr,qxt,qxu,qxw\nsa,cls,vsn\nsc,sdc,sdn,src,sro\nsh,bs,hr,sr,cnr\nsq,aae,aat,aln,als\nsw,swc,swh\nuz,uzn,uzs\nyi,ydd,yih\nza,zch,zeh,zgb,zgm,zgn,zhd,zhn,zlj,zln,zlq,zqe,zyb,zyg,zyj,zyn,zzj\nzh,cdo,cjy,cmn,cnp,cpx,csp,czh,czo,gan,hak,hnm,hsn,luh,lzh,mnp,nan,sjc,wuu,yue\nbal,bcc,bgn,bgp\nbik,bcl,bln,bto,cts,fbl,lbl,rbl,ubl\nbnc,ebk,lbk,obk,rbk,vbk\nbua,bxm,bxr,bxu\nchm,mhr,mrj\ndel,umu,unm\nden,scs,xsl\ndin,dib,dik,dip,diw,dks\ndoi,dgo,xnr\ngba,bdt,gbp,gbq,gmm,gso,gya\ngon,esg,gno,wsg\ngrb,gbo,gec,grj,grv,gry\nhai,hax,hdn\nhmn,cqd,hea,hma,hmc,hmd,hme,hmg,hmh,hmi,hmj,hml,hmm,hmp,hmq,hms,hmw,hmy,hmz,hnj,hrm,huj,mmr,muq,mww,sfm\njrb,aju,jye,yhd,yud\nkln,enb,eyo,niq,oki,pko,sgc,spy,tec,tuy\nkok,gom,knn\nkpe,gkp,xpe\nlah,hnd,hno,jat,phr,pnb,skr,xhe\nluy,bxk,ida,lkb,lko,lks,lri,lrm,lsm,lto,lts,lwg,nle,nyd,rag\nman,emk,mku,mlq,mnk,msc,mwk\nmwr,dhd,mtr,mve,rwr,swv,wry\nraj,bgq,gda,gju,hoj,mup,wbr\nrom,rmc,rmf,rml,rmn,rmo,rmw,rmy\nsyr,aii,cld\ntmh,taq,thv,thz,ttq\nzap,zaa,zab,zac,zad,zae,zaf,zai,zam,zao,zaq,zar,zas,zat,zav,zaw,zax,zca,zcd,zoo,zpa,zpb,zpc,zpd,zpe,zpf,zpg,zph,zpi,zpj,zpk,zpl,zpm,zpn,zpo,zpp,zpq,zpr,zps,zpt,zpu,zpv,zpw,zpx,zpy,zpz,zsr,zte,ztg,ztl,ztm,ztn,ztp,ztq,zts,ztt,ztu,ztx,zty\nzza,diq,kiu\n"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/lang/upside_down.txt",
    "content": "aɐ\nbq\ncɔ\ndp\neǝ\nfɟ\ngᵷ\nhɥ\niᴉ\njɾ\nkʞ\nlꞁ\nmɯ\nnu\noo\npd\nqb\nrɹ\nss\ntʇ\nun\nvʌ\nwʍ\nxx\nyʎ\nzz\nAⱯ\nBᗺ\nCƆ\nDᗡ\nEƎ\nFℲ\nG⅁\nHH\nII\nJſ\nKʞ\nLꞀ\nMW\nNN\nOO\nPԀ\nQὉ\nRᴚ\nSS\nT⟘\nU∩\nVΛ\nWM\nXX\nYʎ\nZZ\n00\n1Ɩ\n2ᘔ\n3Ɛ\n4ㄣ\n5ϛ\n69\n7ㄥ\n88\n96\n_‾\n,'\n;⸵\n.˙\n?¿\n!¡\n//\n\\\\\n',\n()\n)(\n[]\n][\n{}\n}{"
  },
  {
    "path": "HMCLCore/src/main/resources/assets/platform/amdgpu.ids",
    "content": "# https://gitlab.freedesktop.org/mesa/libdrm/-/blob/35a21916c8f621b35a67f3c5994280ae59a4981a/data/amdgpu.ids\n#\n# List of AMDGPU IDs\n#\n# Syntax:\n# device_id,\trevision_id,\tproduct_name        <-- single tab after comma\n\n1.0.0\n1114,\tC2,\tAMD Radeon 860M Graphics\n1114,\tC3,\tAMD Radeon 840M Graphics\n1114,\tD2,\tAMD Radeon 860M Graphics\n1114,\tD3,\tAMD Radeon 840M Graphics\n1309,\t00,\tAMD Radeon R7 Graphics\n130A,\t00,\tAMD Radeon R6 Graphics\n130B,\t00,\tAMD Radeon R4 Graphics\n130C,\t00,\tAMD Radeon R7 Graphics\n130D,\t00,\tAMD Radeon R6 Graphics\n130E,\t00,\tAMD Radeon R5 Graphics\n130F,\t00,\tAMD Radeon R7 Graphics\n130F,\tD4,\tAMD Radeon R7 Graphics\n130F,\tD5,\tAMD Radeon R7 Graphics\n130F,\tD6,\tAMD Radeon R7 Graphics\n130F,\tD7,\tAMD Radeon R7 Graphics\n1313,\t00,\tAMD Radeon R7 Graphics\n1313,\tD4,\tAMD Radeon R7 Graphics\n1313,\tD5,\tAMD Radeon R7 Graphics\n1313,\tD6,\tAMD Radeon R7 Graphics\n1315,\t00,\tAMD Radeon R5 Graphics\n1315,\tD4,\tAMD Radeon R5 Graphics\n1315,\tD5,\tAMD Radeon R5 Graphics\n1315,\tD6,\tAMD Radeon R5 Graphics\n1315,\tD7,\tAMD Radeon R5 Graphics\n1316,\t00,\tAMD Radeon R5 Graphics\n1318,\t00,\tAMD Radeon R5 Graphics\n131B,\t00,\tAMD Radeon R4 Graphics\n131C,\t00,\tAMD Radeon R7 Graphics\n131D,\t00,\tAMD Radeon R6 Graphics\n1435,\tAE,\tAMD Custom GPU 0932\n1506,\tC1,\tAMD Radeon 610M\n1506,\tC2,\tAMD Radeon 610M\n1506,\tC3,\tAMD Radeon 610M\n1506,\tC4,\tAMD Radeon 610M\n150E,\tC1,\tAMD Radeon 890M Graphics\n150E,\tC4,\tAMD Radeon 890M Graphics\n150E,\tC5,\tAMD Radeon 890M Graphics\n150E,\tC6,\tAMD Radeon 890M Graphics\n150E,\tD1,\tAMD Radeon 890M Graphics\n150E,\tD2,\tAMD Radeon 890M Graphics\n150E,\tD3,\tAMD Radeon 890M Graphics\n1586,\tC1,\tRadeon 8060S Graphics\n1586,\tC2,\tRadeon 8050S Graphics\n1586,\tC4,\tRadeon 8050S Graphics\n1586,\tD1,\tRadeon 8060S Graphics\n1586,\tD2,\tRadeon 8050S Graphics\n1586,\tD4,\tRadeon 8050S Graphics\n1586,\tD5,\tRadeon 8040S Graphics\n15BF,\t00,\tAMD Radeon 780M Graphics\n15BF,\t01,\tAMD Radeon 760M Graphics\n15BF,\t02,\tAMD Radeon 780M Graphics\n15BF,\t03,\tAMD Radeon 760M Graphics\n15BF,\tC1,\tAMD Radeon 780M Graphics\n15BF,\tC2,\tAMD Radeon 780M Graphics\n15BF,\tC3,\tAMD Radeon 760M Graphics\n15BF,\tC4,\tAMD Radeon 780M Graphics\n15BF,\tC5,\tAMD Radeon 740M Graphics\n15BF,\tC6,\tAMD Radeon 780M Graphics\n15BF,\tC7,\tAMD Radeon 780M Graphics\n15BF,\tC8,\tAMD Radeon 760M Graphics\n15BF,\tC9,\tAMD Radeon 780M Graphics\n15BF,\tCA,\tAMD Radeon 740M Graphics\n15BF,\tCB,\tAMD Radeon 760M Graphics\n15BF,\tCC,\tAMD Radeon 740M Graphics\n15BF,\tCD,\tAMD Radeon 760M Graphics\n15BF,\tCF,\tAMD Radeon 780M Graphics\n15BF,\tD0,\tAMD Radeon 780M Graphics\n15BF,\tD1,\tAMD Radeon 780M Graphics\n15BF,\tD2,\tAMD Radeon 780M Graphics\n15BF,\tD3,\tAMD Radeon 780M Graphics\n15BF,\tD4,\tAMD Radeon 780M Graphics\n15BF,\tD5,\tAMD Radeon 760M Graphics\n15BF,\tD6,\tAMD Radeon 760M Graphics\n15BF,\tD7,\tAMD Radeon 780M Graphics\n15BF,\tD8,\tAMD Radeon 740M Graphics\n15BF,\tD9,\tAMD Radeon 780M Graphics\n15BF,\tDA,\tAMD Radeon 780M Graphics\n15BF,\tDB,\tAMD Radeon 760M Graphics\n15BF,\tDC,\tAMD Radeon 760M Graphics\n15BF,\tDD,\tAMD Radeon 780M Graphics\n15BF,\tDE,\tAMD Radeon 740M Graphics\n15BF,\tDF,\tAMD Radeon 760M Graphics\n15BF,\tF0,\tAMD Radeon 760M Graphics\n15C8,\tC1,\tAMD Radeon 740M Graphics\n15C8,\tC2,\tAMD Radeon 740M Graphics\n15C8,\tC3,\tAMD Radeon 740M Graphics\n15C8,\tC4,\tAMD Radeon 740M Graphics\n15C8,\tD1,\tAMD Radeon 740M Graphics\n15C8,\tD2,\tAMD Radeon 740M Graphics\n15C8,\tD3,\tAMD Radeon 740M Graphics\n15C8,\tD4,\tAMD Radeon 740M Graphics\n15D8,\t00,\tAMD Radeon RX Vega 8 Graphics WS\n15D8,\t91,\tAMD Radeon Vega 3 Graphics\n15D8,\t91,\tAMD Ryzen Embedded R1606G with Radeon Vega Gfx\n15D8,\t92,\tAMD Radeon Vega 3 Graphics\n15D8,\t92,\tAMD Ryzen Embedded R1505G with Radeon Vega Gfx\n15D8,\t93,\tAMD Radeon Vega 1 Graphics\n15D8,\tA1,\tAMD Radeon Vega 10 Graphics\n15D8,\tA2,\tAMD Radeon Vega 8 Graphics\n15D8,\tA3,\tAMD Radeon Vega 6 Graphics\n15D8,\tA4,\tAMD Radeon Vega 3 Graphics\n15D8,\tB1,\tAMD Radeon Vega 10 Graphics\n15D8,\tB2,\tAMD Radeon Vega 8 Graphics\n15D8,\tB3,\tAMD Radeon Vega 6 Graphics\n15D8,\tB4,\tAMD Radeon Vega 3 Graphics\n15D8,\tC1,\tAMD Radeon Vega 10 Graphics\n15D8,\tC2,\tAMD Radeon Vega 8 Graphics\n15D8,\tC3,\tAMD Radeon Vega 6 Graphics\n15D8,\tC4,\tAMD Radeon Vega 3 Graphics\n15D8,\tC5,\tAMD Radeon Vega 3 Graphics\n15D8,\tC8,\tAMD Radeon Vega 11 Graphics\n15D8,\tC9,\tAMD Radeon Vega 8 Graphics\n15D8,\tCA,\tAMD Radeon Vega 11 Graphics\n15D8,\tCB,\tAMD Radeon Vega 8 Graphics\n15D8,\tCC,\tAMD Radeon Vega 3 Graphics\n15D8,\tCE,\tAMD Radeon Vega 3 Graphics\n15D8,\tCF,\tAMD Ryzen Embedded R1305G with Radeon Vega Gfx\n15D8,\tD1,\tAMD Radeon Vega 10 Graphics\n15D8,\tD2,\tAMD Radeon Vega 8 Graphics\n15D8,\tD3,\tAMD Radeon Vega 6 Graphics\n15D8,\tD4,\tAMD Radeon Vega 3 Graphics\n15D8,\tD8,\tAMD Radeon Vega 11 Graphics\n15D8,\tD9,\tAMD Radeon Vega 8 Graphics\n15D8,\tDA,\tAMD Radeon Vega 11 Graphics\n15D8,\tDB,\tAMD Radeon Vega 3 Graphics\n15D8,\tDB,\tAMD Radeon Vega 8 Graphics\n15D8,\tDC,\tAMD Radeon Vega 3 Graphics\n15D8,\tDD,\tAMD Radeon Vega 3 Graphics\n15D8,\tDE,\tAMD Radeon Vega 3 Graphics\n15D8,\tDF,\tAMD Radeon Vega 3 Graphics\n15D8,\tE3,\tAMD Radeon Vega 3 Graphics\n15D8,\tE4,\tAMD Ryzen Embedded R1102G with Radeon Vega Gfx\n15DD,\t81,\tAMD Ryzen Embedded V1807B with Radeon Vega Gfx\n15DD,\t82,\tAMD Ryzen Embedded V1756B with Radeon Vega Gfx\n15DD,\t83,\tAMD Ryzen Embedded V1605B with Radeon Vega Gfx\n15DD,\t84,\tAMD Radeon Vega 6 Graphics\n15DD,\t85,\tAMD Ryzen Embedded V1202B with Radeon Vega Gfx\n15DD,\t86,\tAMD Radeon Vega 11 Graphics\n15DD,\t88,\tAMD Radeon Vega 8 Graphics\n15DD,\tC1,\tAMD Radeon Vega 11 Graphics\n15DD,\tC2,\tAMD Radeon Vega 8 Graphics\n15DD,\tC3,\tAMD Radeon Vega 3 / 10 Graphics\n15DD,\tC4,\tAMD Radeon Vega 8 Graphics\n15DD,\tC5,\tAMD Radeon Vega 3 Graphics\n15DD,\tC6,\tAMD Radeon Vega 11 Graphics\n15DD,\tC8,\tAMD Radeon Vega 8 Graphics\n15DD,\tC9,\tAMD Radeon Vega 11 Graphics\n15DD,\tCA,\tAMD Radeon Vega 8 Graphics\n15DD,\tCB,\tAMD Radeon Vega 3 Graphics\n15DD,\tCC,\tAMD Radeon Vega 6 Graphics\n15DD,\tCE,\tAMD Radeon Vega 3 Graphics\n15DD,\tCF,\tAMD Radeon Vega 3 Graphics\n15DD,\tD0,\tAMD Radeon Vega 10 Graphics\n15DD,\tD1,\tAMD Radeon Vega 8 Graphics\n15DD,\tD3,\tAMD Radeon Vega 11 Graphics\n15DD,\tD5,\tAMD Radeon Vega 8 Graphics\n15DD,\tD6,\tAMD Radeon Vega 11 Graphics\n15DD,\tD7,\tAMD Radeon Vega 8 Graphics\n15DD,\tD8,\tAMD Radeon Vega 3 Graphics\n15DD,\tD9,\tAMD Radeon Vega 6 Graphics\n15DD,\tE1,\tAMD Radeon Vega 3 Graphics\n15DD,\tE2,\tAMD Radeon Vega 3 Graphics\n163F,\tAE,\tAMD Custom GPU 0405\n163F,\tE1,\tAMD Custom GPU 0405\n164E,\tD8,\tAMD Radeon 610M\n164E,\tD9,\tAMD Radeon 610M\n164E,\tDA,\tAMD Radeon 610M\n164E,\tDB,\tAMD Radeon 610M\n164E,\tDC,\tAMD Radeon 610M\n1681,\t06,\tAMD Radeon 680M\n1681,\t07,\tAMD Radeon 660M\n1681,\t0A,\tAMD Radeon 680M\n1681,\t0B,\tAMD Radeon 660M\n1681,\tC7,\tAMD Radeon 680M\n1681,\tC8,\tAMD Radeon 680M\n1681,\tC9,\tAMD Radeon 660M\n1900,\t01,\tAMD Radeon 780M Graphics\n1900,\t02,\tAMD Radeon 760M Graphics\n1900,\t03,\tAMD Radeon 780M Graphics\n1900,\t04,\tAMD Radeon 760M Graphics\n1900,\t05,\tAMD Radeon 780M Graphics\n1900,\t06,\tAMD Radeon 780M Graphics\n1900,\t07,\tAMD Radeon 760M Graphics\n1900,\tB0,\tAMD Radeon 780M Graphics\n1900,\tB1,\tAMD Radeon 780M Graphics\n1900,\tB2,\tAMD Radeon 780M Graphics\n1900,\tB3,\tAMD Radeon 780M Graphics\n1900,\tB4,\tAMD Radeon 780M Graphics\n1900,\tB5,\tAMD Radeon 780M Graphics\n1900,\tB6,\tAMD Radeon 780M Graphics\n1900,\tB7,\tAMD Radeon 760M Graphics\n1900,\tB8,\tAMD Radeon 760M Graphics\n1900,\tB9,\tAMD Radeon 780M Graphics\n1900,\tBA,\tAMD Radeon 780M Graphics\n1900,\tBB,\tAMD Radeon 780M Graphics\n1900,\tC0,\tAMD Radeon 780M Graphics\n1900,\tC1,\tAMD Radeon 760M Graphics\n1900,\tC2,\tAMD Radeon 780M Graphics\n1900,\tC3,\tAMD Radeon 760M Graphics\n1900,\tC4,\tAMD Radeon 780M Graphics\n1900,\tC5,\tAMD Radeon 780M Graphics\n1900,\tC6,\tAMD Radeon 760M Graphics\n1900,\tC7,\tAMD Radeon 780M Graphics\n1900,\tC8,\tAMD Radeon 760M Graphics\n1900,\tC9,\tAMD Radeon 780M Graphics\n1900,\tCA,\tAMD Radeon 760M Graphics\n1900,\tCB,\tAMD Radeon 780M Graphics\n1900,\tCC,\tAMD Radeon 780M Graphics\n1900,\tCD,\tAMD Radeon 760M Graphics\n1900,\tCE,\tAMD Radeon 780M Graphics\n1900,\tCF,\tAMD Radeon 760M Graphics\n1900,\tD0,\tAMD Radeon 780M Graphics\n1900,\tD1,\tAMD Radeon 760M Graphics\n1900,\tD2,\tAMD Radeon 780M Graphics\n1900,\tD3,\tAMD Radeon 760M Graphics\n1900,\tD4,\tAMD Radeon 780M Graphics\n1900,\tD5,\tAMD Radeon 780M Graphics\n1900,\tD6,\tAMD Radeon 760M Graphics\n1900,\tD7,\tAMD Radeon 780M Graphics\n1900,\tD8,\tAMD Radeon 760M Graphics\n1900,\tD9,\tAMD Radeon 780M Graphics\n1900,\tDA,\tAMD Radeon 760M Graphics\n1900,\tDB,\tAMD Radeon 780M Graphics\n1900,\tDC,\tAMD Radeon 780M Graphics\n1900,\tDD,\tAMD Radeon 760M Graphics\n1900,\tDE,\tAMD Radeon 780M Graphics\n1900,\tDF,\tAMD Radeon 760M Graphics\n1900,\tF0,\tAMD Radeon 780M Graphics\n1900,\tF1,\tAMD Radeon 780M Graphics\n1900,\tF2,\tAMD Radeon 780M Graphics\n1901,\tC1,\tAMD Radeon 740M Graphics\n1901,\tC2,\tAMD Radeon 740M Graphics\n1901,\tC3,\tAMD Radeon 740M Graphics\n1901,\tC6,\tAMD Radeon 740M Graphics\n1901,\tC7,\tAMD Radeon 740M Graphics\n1901,\tC8,\tAMD Radeon 740M Graphics\n1901,\tC9,\tAMD Radeon 740M Graphics\n1901,\tCA,\tAMD Radeon 740M Graphics\n1901,\tD1,\tAMD Radeon 740M Graphics\n1901,\tD2,\tAMD Radeon 740M Graphics\n1901,\tD3,\tAMD Radeon 740M Graphics\n1901,\tD4,\tAMD Radeon 740M Graphics\n1901,\tD5,\tAMD Radeon 740M Graphics\n1901,\tD6,\tAMD Radeon 740M Graphics\n1901,\tD7,\tAMD Radeon 740M Graphics\n1901,\tD8,\tAMD Radeon 740M Graphics\n6600,\t00,\tAMD Radeon HD 8600 / 8700M\n6600,\t81,\tAMD Radeon R7 M370\n6601,\t00,\tAMD Radeon HD 8500M / 8700M\n6604,\t00,\tAMD Radeon R7 M265 Series\n6604,\t81,\tAMD Radeon R7 M350\n6605,\t00,\tAMD Radeon R7 M260 Series\n6605,\t81,\tAMD Radeon R7 M340\n6606,\t00,\tAMD Radeon HD 8790M\n6607,\t00,\tAMD Radeon R5 M240\n6608,\t00,\tAMD FirePro W2100\n6610,\t00,\tAMD Radeon R7 200 Series\n6610,\t81,\tAMD Radeon R7 350\n6610,\t83,\tAMD Radeon R5 340\n6610,\t87,\tAMD Radeon R7 200 Series\n6611,\t00,\tAMD Radeon R7 200 Series\n6611,\t87,\tAMD Radeon R7 200 Series\n6613,\t00,\tAMD Radeon R7 200 Series\n6617,\t00,\tAMD Radeon R7 240 Series\n6617,\t87,\tAMD Radeon R7 200 Series\n6617,\tC7,\tAMD Radeon R7 240 Series\n6640,\t00,\tAMD Radeon HD 8950\n6640,\t80,\tAMD Radeon R9 M380\n6646,\t00,\tAMD Radeon R9 M280X\n6646,\t80,\tAMD Radeon R9 M385\n6646,\t80,\tAMD Radeon R9 M470X\n6647,\t00,\tAMD Radeon R9 M200X Series\n6647,\t80,\tAMD Radeon R9 M380\n6649,\t00,\tAMD FirePro W5100\n6658,\t00,\tAMD Radeon R7 200 Series\n665C,\t00,\tAMD Radeon HD 7700 Series\n665D,\t00,\tAMD Radeon R7 200 Series\n665F,\t81,\tAMD Radeon R7 360 Series\n6660,\t00,\tAMD Radeon HD 8600M Series\n6660,\t81,\tAMD Radeon R5 M335\n6660,\t83,\tAMD Radeon R5 M330\n6663,\t00,\tAMD Radeon HD 8500M Series\n6663,\t83,\tAMD Radeon R5 M320\n6664,\t00,\tAMD Radeon R5 M200 Series\n6665,\t00,\tAMD Radeon R5 M230 Series\n6665,\t83,\tAMD Radeon R5 M320\n6665,\tC3,\tAMD Radeon R5 M435\n6666,\t00,\tAMD Radeon R5 M200 Series\n6667,\t00,\tAMD Radeon R5 M200 Series\n666F,\t00,\tAMD Radeon HD 8500M\n66A1,\t02,\tAMD Instinct MI60 / MI50\n66A1,\t06,\tAMD Radeon Pro VII\n66AF,\tC1,\tAMD Radeon VII\n6780,\t00,\tAMD FirePro W9000\n6784,\t00,\tATI FirePro V (FireGL V) Graphics Adapter\n6788,\t00,\tATI FirePro V (FireGL V) Graphics Adapter\n678A,\t00,\tAMD FirePro W8000\n6798,\t00,\tAMD Radeon R9 200 / HD 7900 Series\n6799,\t00,\tAMD Radeon HD 7900 Series\n679A,\t00,\tAMD Radeon HD 7900 Series\n679B,\t00,\tAMD Radeon HD 7900 Series\n679E,\t00,\tAMD Radeon HD 7800 Series\n67A0,\t00,\tAMD Radeon FirePro W9100\n67A1,\t00,\tAMD Radeon FirePro W8100\n67B0,\t00,\tAMD Radeon R9 200 Series\n67B0,\t80,\tAMD Radeon R9 390 Series\n67B1,\t00,\tAMD Radeon R9 200 Series\n67B1,\t80,\tAMD Radeon R9 390 Series\n67B9,\t00,\tAMD Radeon R9 200 Series\n67C0,\t00,\tAMD Radeon Pro WX 7100 Graphics\n67C0,\t80,\tAMD Radeon E9550\n67C2,\t01,\tAMD Radeon Pro V7350x2\n67C2,\t02,\tAMD Radeon Pro V7300X\n67C4,\t00,\tAMD Radeon Pro WX 7100 Graphics\n67C4,\t80,\tAMD Radeon E9560 / E9565 Graphics\n67C7,\t00,\tAMD Radeon Pro WX 5100 Graphics\n67C7,\t80,\tAMD Radeon E9390 Graphics\n67D0,\t01,\tAMD Radeon Pro V7350x2\n67D0,\t02,\tAMD Radeon Pro V7300X\n67DF,\tC0,\tAMD Radeon Pro 580X\n67DF,\tC1,\tAMD Radeon RX 580 Series\n67DF,\tC2,\tAMD Radeon RX 570 Series\n67DF,\tC3,\tAMD Radeon RX 580 Series\n67DF,\tC4,\tAMD Radeon RX 480 Graphics\n67DF,\tC5,\tAMD Radeon RX 470 Graphics\n67DF,\tC6,\tAMD Radeon RX 570 Series\n67DF,\tC7,\tAMD Radeon RX 480 Graphics\n67DF,\tCF,\tAMD Radeon RX 470 Graphics\n67DF,\tD7,\tAMD Radeon RX 470 Graphics\n67DF,\tE0,\tAMD Radeon RX 470 Series\n67DF,\tE1,\tAMD Radeon RX 590 Series\n67DF,\tE3,\tAMD Radeon RX Series\n67DF,\tE7,\tAMD Radeon RX 580 Series\n67DF,\tEB,\tAMD Radeon Pro 580X\n67DF,\tEF,\tAMD Radeon RX 570 Series\n67DF,\tF7,\tAMD Radeon RX P30PH\n67DF,\tFF,\tAMD Radeon RX 470 Series\n67E0,\t00,\tAMD Radeon Pro WX Series\n67E3,\t00,\tAMD Radeon Pro WX 4100\n67E8,\t00,\tAMD Radeon Pro WX Series\n67E8,\t01,\tAMD Radeon Pro WX Series\n67E8,\t80,\tAMD Radeon E9260 Graphics\n67EB,\t00,\tAMD Radeon Pro V5300X\n67EF,\tC0,\tAMD Radeon RX Graphics\n67EF,\tC1,\tAMD Radeon RX 460 Graphics\n67EF,\tC2,\tAMD Radeon Pro Series\n67EF,\tC3,\tAMD Radeon RX Series\n67EF,\tC5,\tAMD Radeon RX 460 Graphics\n67EF,\tC7,\tAMD Radeon RX Graphics\n67EF,\tCF,\tAMD Radeon RX 460 Graphics\n67EF,\tE0,\tAMD Radeon RX 560 Series\n67EF,\tE1,\tAMD Radeon RX Series\n67EF,\tE2,\tAMD Radeon RX 560X\n67EF,\tE3,\tAMD Radeon RX Series\n67EF,\tE5,\tAMD Radeon RX 560 Series\n67EF,\tE7,\tAMD Radeon RX 560 Series\n67EF,\tEF,\tAMD Radeon 550 Series\n67EF,\tFF,\tAMD Radeon RX 460 Graphics\n67FF,\tC0,\tAMD Radeon Pro 465\n67FF,\tC1,\tAMD Radeon RX 560 Series\n67FF,\tCF,\tAMD Radeon RX 560 Series\n67FF,\tEF,\tAMD Radeon RX 560 Series\n67FF,\tFF,\tAMD Radeon RX 550 Series\n6800,\t00,\tAMD Radeon HD 7970M\n6801,\t00,\tAMD Radeon HD 8970M\n6806,\t00,\tAMD Radeon R9 M290X\n6808,\t00,\tAMD FirePro W7000\n6808,\t00,\tATI FirePro V (FireGL V) Graphics Adapter\n6809,\t00,\tATI FirePro W5000\n6810,\t00,\tAMD Radeon R9 200 Series\n6810,\t81,\tAMD Radeon R9 370 Series\n6811,\t00,\tAMD Radeon R9 200 Series\n6811,\t81,\tAMD Radeon R7 370 Series\n6818,\t00,\tAMD Radeon HD 7800 Series\n6819,\t00,\tAMD Radeon HD 7800 Series\n6820,\t00,\tAMD Radeon R9 M275X\n6820,\t81,\tAMD Radeon R9 M375\n6820,\t83,\tAMD Radeon R9 M375X\n6821,\t00,\tAMD Radeon R9 M200X Series\n6821,\t83,\tAMD Radeon R9 M370X\n6821,\t87,\tAMD Radeon R7 M380\n6822,\t00,\tAMD Radeon E8860\n6823,\t00,\tAMD Radeon R9 M200X Series\n6825,\t00,\tAMD Radeon HD 7800M Series\n6826,\t00,\tAMD Radeon HD 7700M Series\n6827,\t00,\tAMD Radeon HD 7800M Series\n6828,\t00,\tAMD FirePro W600\n682B,\t00,\tAMD Radeon HD 8800M Series\n682B,\t87,\tAMD Radeon R9 M360\n682C,\t00,\tAMD FirePro W4100\n682D,\t00,\tAMD Radeon HD 7700M Series\n682F,\t00,\tAMD Radeon HD 7700M Series\n6830,\t00,\tAMD Radeon 7800M Series\n6831,\t00,\tAMD Radeon 7700M Series\n6835,\t00,\tAMD Radeon R7 Series / HD 9000 Series\n6837,\t00,\tAMD Radeon HD 7700 Series\n683D,\t00,\tAMD Radeon HD 7700 Series\n683F,\t00,\tAMD Radeon HD 7700 Series\n684C,\t00,\tATI FirePro V (FireGL V) Graphics Adapter\n6860,\t00,\tAMD Radeon Instinct MI25\n6860,\t01,\tAMD Radeon Instinct MI25\n6860,\t02,\tAMD Radeon Instinct MI25\n6860,\t03,\tAMD Radeon Pro V340\n6860,\t04,\tAMD Radeon Instinct MI25x2\n6860,\t07,\tAMD Radeon Pro V320\n6861,\t00,\tAMD Radeon Pro WX 9100\n6862,\t00,\tAMD Radeon Pro SSG\n6863,\t00,\tAMD Radeon Vega Frontier Edition\n6864,\t03,\tAMD Radeon Pro V340\n6864,\t04,\tAMD Radeon Instinct MI25x2\n6864,\t05,\tAMD Radeon Pro V340\n6868,\t00,\tAMD Radeon Pro WX 8200\n686C,\t00,\tAMD Radeon Instinct MI25 MxGPU\n686C,\t01,\tAMD Radeon Instinct MI25 MxGPU\n686C,\t02,\tAMD Radeon Instinct MI25 MxGPU\n686C,\t03,\tAMD Radeon Pro V340 MxGPU\n686C,\t04,\tAMD Radeon Instinct MI25x2 MxGPU\n686C,\t05,\tAMD Radeon Pro V340L MxGPU\n686C,\t06,\tAMD Radeon Instinct MI25 MxGPU\n687F,\t01,\tAMD Radeon RX Vega\n687F,\tC0,\tAMD Radeon RX Vega\n687F,\tC1,\tAMD Radeon RX Vega\n687F,\tC3,\tAMD Radeon RX Vega\n687F,\tC7,\tAMD Radeon RX Vega\n6900,\t00,\tAMD Radeon R7 M260\n6900,\t81,\tAMD Radeon R7 M360\n6900,\t83,\tAMD Radeon R7 M340\n6900,\tC1,\tAMD Radeon R5 M465 Series\n6900,\tC3,\tAMD Radeon R5 M445 Series\n6900,\tD1,\tAMD Radeon 530 Series\n6900,\tD3,\tAMD Radeon 530 Series\n6901,\t00,\tAMD Radeon R5 M255\n6902,\t00,\tAMD Radeon Series\n6907,\t00,\tAMD Radeon R5 M255\n6907,\t87,\tAMD Radeon R5 M315\n6920,\t00,\tAMD Radeon R9 M395X\n6920,\t01,\tAMD Radeon R9 M390X\n6921,\t00,\tAMD Radeon R9 M390X\n6929,\t00,\tAMD FirePro S7150\n6929,\t01,\tAMD FirePro S7100X\n692B,\t00,\tAMD FirePro W7100\n6938,\t00,\tAMD Radeon R9 200 Series\n6938,\tF0,\tAMD Radeon R9 200 Series\n6938,\tF1,\tAMD Radeon R9 380 Series\n6939,\t00,\tAMD Radeon R9 200 Series\n6939,\tF0,\tAMD Radeon R9 200 Series\n6939,\tF1,\tAMD Radeon R9 380 Series\n694C,\tC0,\tAMD Radeon RX Vega M GH Graphics\n694E,\tC0,\tAMD Radeon RX Vega M GL Graphics\n6980,\t00,\tAMD Radeon Pro WX 3100\n6981,\t00,\tAMD Radeon Pro WX 3200 Series\n6981,\t01,\tAMD Radeon Pro WX 3200 Series\n6981,\t10,\tAMD Radeon Pro WX 3200 Series\n6985,\t00,\tAMD Radeon Pro WX 3100\n6986,\t00,\tAMD Radeon Pro WX 2100\n6987,\t80,\tAMD Embedded Radeon E9171\n6987,\tC0,\tAMD Radeon 550X Series\n6987,\tC1,\tAMD Radeon RX 640\n6987,\tC3,\tAMD Radeon 540X Series\n6987,\tC7,\tAMD Radeon 540\n6995,\t00,\tAMD Radeon Pro WX 2100\n6997,\t00,\tAMD Radeon Pro WX 2100\n699F,\t81,\tAMD Embedded Radeon E9170 Series\n699F,\tC0,\tAMD Radeon 500 Series\n699F,\tC1,\tAMD Radeon 540 Series\n699F,\tC3,\tAMD Radeon 500 Series\n699F,\tC7,\tAMD Radeon RX 550 / 550 Series\n699F,\tC9,\tAMD Radeon 540\n6FDF,\tE7,\tAMD Radeon RX 590 GME\n6FDF,\tEF,\tAMD Radeon RX 580 2048SP\n7300,\tC1,\tAMD FirePro S9300 x2\n7300,\tC8,\tAMD Radeon R9 Fury Series\n7300,\tC9,\tAMD Radeon Pro Duo\n7300,\tCA,\tAMD Radeon R9 Fury Series\n7300,\tCB,\tAMD Radeon R9 Fury Series\n7312,\t00,\tAMD Radeon Pro W5700\n731E,\tC6,\tAMD Radeon RX 5700XTB\n731E,\tC7,\tAMD Radeon RX 5700B\n731F,\tC0,\tAMD Radeon RX 5700 XT 50th Anniversary\n731F,\tC1,\tAMD Radeon RX 5700 XT\n731F,\tC2,\tAMD Radeon RX 5600M\n731F,\tC3,\tAMD Radeon RX 5700M\n731F,\tC4,\tAMD Radeon RX 5700\n731F,\tC5,\tAMD Radeon RX 5700 XT\n731F,\tCA,\tAMD Radeon RX 5600 XT\n731F,\tCB,\tAMD Radeon RX 5600 OEM\n7340,\tC1,\tAMD Radeon RX 5500M\n7340,\tC3,\tAMD Radeon RX 5300M\n7340,\tC5,\tAMD Radeon RX 5500 XT\n7340,\tC7,\tAMD Radeon RX 5500\n7340,\tC9,\tAMD Radeon RX 5500XTB\n7340,\tCF,\tAMD Radeon RX 5300\n7341,\t00,\tAMD Radeon Pro W5500\n7347,\t00,\tAMD Radeon Pro W5500M\n7360,\t41,\tAMD Radeon Pro 5600M\n7360,\tC3,\tAMD Radeon Pro V520\n7362,\tC1,\tAMD Radeon Pro V540\n7362,\tC3,\tAMD Radeon Pro V520\n738C,\t01,\tAMD Instinct MI100\n73A1,\t00,\tAMD Radeon Pro V620\n73A3,\t00,\tAMD Radeon Pro W6800\n73A5,\tC0,\tAMD Radeon RX 6950 XT\n73AE,\t00,\tAMD Radeon Pro V620 MxGPU\n73AF,\tC0,\tAMD Radeon RX 6900 XT\n73BF,\tC0,\tAMD Radeon RX 6900 XT\n73BF,\tC1,\tAMD Radeon RX 6800 XT\n73BF,\tC3,\tAMD Radeon RX 6800\n73DF,\tC0,\tAMD Radeon RX 6750 XT\n73DF,\tC1,\tAMD Radeon RX 6700 XT\n73DF,\tC2,\tAMD Radeon RX 6800M\n73DF,\tC3,\tAMD Radeon RX 6800M\n73DF,\tC5,\tAMD Radeon RX 6700 XT\n73DF,\tCF,\tAMD Radeon RX 6700M\n73DF,\tD5,\tAMD Radeon RX 6750 GRE 12GB\n73DF,\tD7,\tAMD TDC-235\n73DF,\tDF,\tAMD Radeon RX 6700\n73DF,\tE5,\tAMD Radeon RX 6750 GRE 12GB\n73DF,\tFF,\tAMD Radeon RX 6700\n73E0,\t00,\tAMD Radeon RX 6600M\n73E1,\t00,\tAMD Radeon Pro W6600M\n73E3,\t00,\tAMD Radeon Pro W6600\n73EF,\tC0,\tAMD Radeon RX 6800S\n73EF,\tC1,\tAMD Radeon RX 6650 XT\n73EF,\tC2,\tAMD Radeon RX 6700S\n73EF,\tC3,\tAMD Radeon RX 6650M\n73EF,\tC4,\tAMD Radeon RX 6650M XT\n73FF,\tC1,\tAMD Radeon RX 6600 XT\n73FF,\tC3,\tAMD Radeon RX 6600M\n73FF,\tC7,\tAMD Radeon RX 6600\n73FF,\tCB,\tAMD Radeon RX 6600S\n73FF,\tCF,\tAMD Radeon RX 6600 LE\n73FF,\tDF,\tAMD Radeon RX 6750 GRE 10GB\n7408,\t00,\tAMD Instinct MI250X\n740C,\t01,\tAMD Instinct MI250X / MI250\n740F,\t02,\tAMD Instinct MI210\n7421,\t00,\tAMD Radeon Pro W6500M\n7422,\t00,\tAMD Radeon Pro W6400\n7423,\t00,\tAMD Radeon Pro W6300M\n7423,\t01,\tAMD Radeon Pro W6300\n7424,\t00,\tAMD Radeon RX 6300\n743F,\tC1,\tAMD Radeon RX 6500 XT\n743F,\tC3,\tAMD Radeon RX 6500\n743F,\tC3,\tAMD Radeon RX 6500M\n743F,\tC7,\tAMD Radeon RX 6400\n743F,\tC8,\tAMD Radeon RX 6500M\n743F,\tCC,\tAMD Radeon 6550S\n743F,\tCE,\tAMD Radeon RX 6450M\n743F,\tCF,\tAMD Radeon RX 6300M\n743F,\tD3,\tAMD Radeon RX 6550M\n743F,\tD7,\tAMD Radeon RX 6400\n7448,\t00,\tAMD Radeon Pro W7900\n7449,\t00,\tAMD Radeon Pro W7800 48GB\n744A,\t00,\tAMD Radeon Pro W7900 Dual Slot\n744B,\t00,\tAMD Radeon Pro W7900D\n744C,\tC8,\tAMD Radeon RX 7900 XTX\n744C,\tCC,\tAMD Radeon RX 7900 XT\n744C,\tCE,\tAMD Radeon RX 7900 GRE\n744C,\tCF,\tAMD Radeon RX 7900M\n745E,\tCC,\tAMD Radeon Pro W7800\n7460,\t00,\tAMD Radeon Pro V710\n7461,\t00,\tAMD Radeon Pro V710 MxGPU\n7470,\t00,\tAMD Radeon Pro W7700\n747E,\tC8,\tAMD Radeon RX 7800 XT\n747E,\tD8,\tAMD Radeon RX 7800M\n747E,\tDB,\tAMD Radeon RX 7700\n747E,\tFF,\tAMD Radeon RX 7700 XT\n7480,\t00,\tAMD Radeon Pro W7600\n7480,\tC0,\tAMD Radeon RX 7600 XT\n7480,\tC1,\tAMD Radeon RX 7700S\n7480,\tC2,\tAMD Radeon RX 7650 GRE\n7480,\tC3,\tAMD Radeon RX 7600S\n7480,\tC7,\tAMD Radeon RX 7600M XT\n7480,\tCF,\tAMD Radeon RX 7600\n7483,\tCF,\tAMD Radeon RX 7600M\n7489,\t00,\tAMD Radeon Pro W7500\n7499,\t00,\tAMD Radeon Pro W7400\n7499,\tC0,\tAMD Radeon RX 7400\n7499,\tC1,\tAMD Radeon RX 7300\n74A0,\t00,\tAMD Instinct MI300A\n74A1,\t00,\tAMD Instinct MI300X\n74A2,\t00,\tAMD Instinct MI308X\n74A5,\t00,\tAMD Instinct MI325X\n74A8,\t00,\tAMD Instinct MI308X HF\n74A9,\t00,\tAMD Instinct MI300X HF\n74B5,\t00,\tAMD Instinct MI300X VF\n74B6,\t00,\tAMD Instinct MI308X\n74BD,\t00,\tAMD Instinct MI300X HF\n7550,\tC0,\tAMD Radeon RX 9070 XT\n7550,\tC2,\tAMD Radeon RX 9070 GRE\n7550,\tC3,\tAMD Radeon RX 9070\n7551,\tC0,\tAMD Radeon AI PRO R9700\n7590,\tC0,\tAMD Radeon RX 9060 XT\n7590,\tC7,\tAMD Radeon RX 9060\n75A0,\tC0,\tAMD Instinct MI350X\n75A3,\tC0,\tAMD Instinct MI355X\n75B0,\tC0,\tAMD Instinct MI350X VF\n75B3,\tC0,\tAMD Instinct MI355X VF\n9830,\t00,\tAMD Radeon HD 8400 / R3 Series\n9831,\t00,\tAMD Radeon HD 8400E\n9832,\t00,\tAMD Radeon HD 8330\n9833,\t00,\tAMD Radeon HD 8330E\n9834,\t00,\tAMD Radeon HD 8210\n9835,\t00,\tAMD Radeon HD 8210E\n9836,\t00,\tAMD Radeon HD 8200 / R3 Series\n9837,\t00,\tAMD Radeon HD 8280E\n9838,\t00,\tAMD Radeon HD 8200 / R3 series\n9839,\t00,\tAMD Radeon HD 8180\n983D,\t00,\tAMD Radeon HD 8250\n9850,\t00,\tAMD Radeon R3 Graphics\n9850,\t03,\tAMD Radeon R3 Graphics\n9850,\t40,\tAMD Radeon R2 Graphics\n9850,\t45,\tAMD Radeon R3 Graphics\n9851,\t00,\tAMD Radeon R4 Graphics\n9851,\t01,\tAMD Radeon R5E Graphics\n9851,\t05,\tAMD Radeon R5 Graphics\n9851,\t06,\tAMD Radeon R5E Graphics\n9851,\t40,\tAMD Radeon R4 Graphics\n9851,\t45,\tAMD Radeon R5 Graphics\n9852,\t00,\tAMD Radeon R2 Graphics\n9852,\t40,\tAMD Radeon E1 Graphics\n9853,\t00,\tAMD Radeon R2 Graphics\n9853,\t01,\tAMD Radeon R4E Graphics\n9853,\t03,\tAMD Radeon R2 Graphics\n9853,\t05,\tAMD Radeon R1E Graphics\n9853,\t06,\tAMD Radeon R1E Graphics\n9853,\t07,\tAMD Radeon R1E Graphics\n9853,\t08,\tAMD Radeon R1E Graphics\n9853,\t40,\tAMD Radeon R2 Graphics\n9854,\t00,\tAMD Radeon R3 Graphics\n9854,\t01,\tAMD Radeon R3E Graphics\n9854,\t02,\tAMD Radeon R3 Graphics\n9854,\t05,\tAMD Radeon R2 Graphics\n9854,\t06,\tAMD Radeon R4 Graphics\n9854,\t07,\tAMD Radeon R3 Graphics\n9855,\t02,\tAMD Radeon R6 Graphics\n9855,\t05,\tAMD Radeon R4 Graphics\n9856,\t00,\tAMD Radeon R2 Graphics\n9856,\t01,\tAMD Radeon R2E Graphics\n9856,\t02,\tAMD Radeon R2 Graphics\n9856,\t05,\tAMD Radeon R1E Graphics\n9856,\t06,\tAMD Radeon R2 Graphics\n9856,\t07,\tAMD Radeon R1E Graphics\n9856,\t08,\tAMD Radeon R1E Graphics\n9856,\t13,\tAMD Radeon R1E Graphics\n9874,\t81,\tAMD Radeon R6 Graphics\n9874,\t84,\tAMD Radeon R7 Graphics\n9874,\t85,\tAMD Radeon R6 Graphics\n9874,\t87,\tAMD Radeon R5 Graphics\n9874,\t88,\tAMD Radeon R7E Graphics\n9874,\t89,\tAMD Radeon R6E Graphics\n9874,\tC4,\tAMD Radeon R7 Graphics\n9874,\tC5,\tAMD Radeon R6 Graphics\n9874,\tC6,\tAMD Radeon R6 Graphics\n9874,\tC7,\tAMD Radeon R5 Graphics\n9874,\tC8,\tAMD Radeon R7 Graphics\n9874,\tC9,\tAMD Radeon R7 Graphics\n9874,\tCA,\tAMD Radeon R5 Graphics\n9874,\tCB,\tAMD Radeon R5 Graphics\n9874,\tCC,\tAMD Radeon R7 Graphics\n9874,\tCD,\tAMD Radeon R7 Graphics\n9874,\tCE,\tAMD Radeon R5 Graphics\n9874,\tE1,\tAMD Radeon R7 Graphics\n9874,\tE2,\tAMD Radeon R7 Graphics\n9874,\tE3,\tAMD Radeon R7 Graphics\n9874,\tE4,\tAMD Radeon R7 Graphics\n9874,\tE5,\tAMD Radeon R5 Graphics\n9874,\tE6,\tAMD Radeon R5 Graphics\n98E4,\t80,\tAMD Radeon R5E Graphics\n98E4,\t81,\tAMD Radeon R4E Graphics\n98E4,\t83,\tAMD Radeon R2E Graphics\n98E4,\t84,\tAMD Radeon R2E Graphics\n98E4,\t86,\tAMD Radeon R1E Graphics\n98E4,\tC0,\tAMD Radeon R4 Graphics\n98E4,\tC1,\tAMD Radeon R5 Graphics\n98E4,\tC2,\tAMD Radeon R4 Graphics\n98E4,\tC4,\tAMD Radeon R5 Graphics\n98E4,\tC6,\tAMD Radeon R5 Graphics\n98E4,\tC8,\tAMD Radeon R4 Graphics\n98E4,\tC9,\tAMD Radeon R4 Graphics\n98E4,\tCA,\tAMD Radeon R5 Graphics\n98E4,\tD0,\tAMD Radeon R2 Graphics\n98E4,\tD1,\tAMD Radeon R2 Graphics\n98E4,\tD2,\tAMD Radeon R2 Graphics\n98E4,\tD4,\tAMD Radeon R2 Graphics\n98E4,\tD9,\tAMD Radeon R5 Graphics\n98E4,\tDA,\tAMD Radeon R5 Graphics\n98E4,\tDB,\tAMD Radeon R3 Graphics\n98E4,\tE1,\tAMD Radeon R3 Graphics\n98E4,\tE2,\tAMD Radeon R3 Graphics\n98E4,\tE9,\tAMD Radeon R4 Graphics\n98E4,\tEA,\tAMD Radeon R4 Graphics\n98E4,\tEB,\tAMD Radeon R3 Graphics\n98E4,\tEB,\tAMD Radeon R4 Graphics"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/JavaFXLauncher.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\npublic final class JavaFXLauncher {\n\n    private JavaFXLauncher() {\n    }\n\n    private static boolean started = false;\n\n    static {\n        // init JavaFX Toolkit\n        try {\n            javafx.application.Platform.startup(() -> {\n            });\n            started = true;\n        } catch (IllegalStateException e) {\n            started = true;\n        } catch (Throwable e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static void start() {\n    }\n\n    public static boolean isStarted() {\n        return started;\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.game;\n\nimport org.jackhuang.hmcl.util.Log4jLevel;\nimport org.jackhuang.hmcl.util.Pair;\nimport org.jackhuang.hmcl.util.io.IOUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.*;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class CrashReportAnalyzerTest {\n    private String loadLog(String path) throws IOException {\n        List<Pair<String, Log4jLevel>> logs = new ArrayList<>();\n        InputStream is = CrashReportAnalyzerTest.class.getResourceAsStream(path);\n        if (is == null) {\n            throw new IllegalStateException(\"Resource not found: \" + path);\n        }\n        return IOUtils.readFullyAsString(is);\n    }\n\n    private CrashReportAnalyzer.Result findResultByRule(Set<CrashReportAnalyzer.Result> results, CrashReportAnalyzer.Rule rule) {\n        CrashReportAnalyzer.Result r = results.stream().filter(result -> result.getRule() == rule).findFirst().orElse(null);\n        assertNotNull(r);\n        return r;\n    }\n\n    @Test\n    public void jdk9() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/java9.txt\")),\n                CrashReportAnalyzer.Rule.JDK_9);\n    }\n\n    @Test\n    public void jadeForestOptifine() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/jade_forest_optifine.txt\")),\n                CrashReportAnalyzer.Rule.JADE_FOREST_OPTIFINE);\n    }\n\n    @Test\n    public void rtssForestSodium() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/rtss_forest_sodium.txt\")),\n                CrashReportAnalyzer.Rule.RTSS_FOREST_SODIUM);\n    }\n\n    @Test\n    public void jvm32() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/jvm_32bit.txt\")),\n                CrashReportAnalyzer.Rule.JVM_32BIT);\n    }\n\n    @Test\n    public void jvm321() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/jvm_32bit2.txt\")),\n                CrashReportAnalyzer.Rule.JVM_32BIT);\n    }\n\n    @Test\n    public void modResolution() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/mod_resolution.txt\")),\n                CrashReportAnalyzer.Rule.MOD_RESOLUTION);\n        assertEquals((\"Errors were found!\\n\" +\n                        \" - Mod test depends on mod {fabricloader @ [>=0.11.3]}, which is missing!\\n\" +\n                        \" - Mod test depends on mod {fabric @ [*]}, which is missing!\\n\" +\n                        \" - Mod test depends on mod {java @ [>=16]}, which is missing!\\n\").replaceAll(\"\\\\s+\", \"\"),\n                result.getMatcher().group(\"reason\").replaceAll(\"\\\\s+\", \"\"));\n    }\n\n    @Test\n    public void forgemodResolution() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/forgemod_resolution.txt\")),\n                CrashReportAnalyzer.Rule.FORGEMOD_RESOLUTION);\n        assertEquals((\"\\tMod ID: 'vampirism', Requested by: 'werewolves', Expected range: '[1.9.0-beta.1,)', Actual version: '[MISSING]'\\n\").replaceAll(\"\\\\s+\", \"\"),\n                result.getMatcher().group(\"reason\").replaceAll(\"\\\\s+\", \"\"));\n    }\n\n    @Test\n    public void forgeFoundDuplicateMods() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/forge_found_duplicate_mods.txt\")),\n                CrashReportAnalyzer.Rule.FORGE_FOUND_DUPLICATE_MODS);\n        assertEquals((\"\\tMod ID: 'jei' from mod files: REIPluginCompatibilities-forge-12.0.93.jar, jei-1.20.1-forge-15.2.0.27.jar\\n\").replaceAll(\"\\\\s+\", \"\"),\n                result.getMatcher().group(\"reason\").replaceAll(\"\\\\s+\", \"\"));\n    }\n\n    @Test\n    public void modResolutionCollection() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/mod_resolution_collection.txt\")),\n                CrashReportAnalyzer.Rule.MOD_RESOLUTION_COLLECTION);\n        assertEquals(\"tabtps-fabric\", result.getMatcher().group(\"sourcemod\"));\n        assertEquals(\"{fabricloader @ [>=0.11.1]}\", result.getMatcher().group(\"destmod\"));\n    }\n\n    @Test\n    public void forgeEroor() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/forge_error.txt\")),\n                CrashReportAnalyzer.Rule.FORGE_ERROR);\n        assertEquals((\"\\nnet.minecraftforge.fml.common.MissingModsException: Mod pixelmon (Pixelmon) requires [forge@[14.23.5.2860,)]\\n\" +\n                        \"\\tat net.minecraftforge.fml.common.Loader.sortModList(Loader.java:264) ~[Loader.class:?]\\n\" +\n                        \"\\tat net.minecraftforge.fml.common.Loader.loadMods(Loader.java:570) ~[Loader.class:?]\\n\" +\n                        \"\\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:232) [FMLClientHandler.class:?]\\n\" +\n                        \"\\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:467) [bib.class:?]\\n\" +\n                        \"\\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:378) [bib.class:?]\\n\" +\n                        \"\\tat net.minecraft.client.main.Main.main(SourceFile:123) [Main.class:?]\\n\" +\n                        \"\\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_131]\\n\" +\n                        \"\\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_131]\\n\" +\n                        \"\\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_131]\\n\" +\n                        \"\\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_131]\\n\" +\n                        \"\\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135) [launchwrapper-1.12.jar:?]\\n\" +\n                        \"\\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.12.jar:?]\\n\" +\n                        \"\\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_131]\\n\" +\n                        \"\\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_131]\\n\" +\n                        \"\\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_131]\\n\" +\n                        \"\\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_131]\\n\" +\n                        \"\\tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58) [JavaWrapper.jar:?]\\n\" +\n                        \"\\tat oolloo.jlw.Wrapper.main(Wrapper.java:51) [JavaWrapper.jar:?]\").replaceAll(\"\\\\s+\", \"\"),\n                result.getMatcher().group(\"reason\").replaceAll(\"\\\\s+\", \"\"));\n    }\n\n    @Test\n    public void modResolution0() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/mod_resolution0.txt\")),\n                CrashReportAnalyzer.Rule.MOD_RESOLUTION0);\n    }\n\n    @Test\n    public void tooOldJava() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/too_old_java.txt\")),\n                CrashReportAnalyzer.Rule.TOO_OLD_JAVA);\n        assertEquals(\"60\", result.getMatcher().group(\"expected\"));\n    }\n\n    @Test\n    public void tooOldJava1() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/too_old_java.txt\")),\n                CrashReportAnalyzer.Rule.TOO_OLD_JAVA);\n        assertEquals(\"52\", result.getMatcher().group(\"expected\"));\n    }\n\n    @Test\n    public void tooOldJava2() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/too_old_java2.txt\")),\n                CrashReportAnalyzer.Rule.TOO_OLD_JAVA);\n        assertEquals(\"52\", result.getMatcher().group(\"expected\"));\n    }\n\n    @Test\n    public void javaVersionIsTooHigh() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/java_version_is_too_high.txt\")),\n                CrashReportAnalyzer.Rule.JAVA_VERSION_IS_TOO_HIGH);\n    }\n\n    @Test\n    public void securityException() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/security.txt\")),\n                CrashReportAnalyzer.Rule.FILE_CHANGED);\n        assertEquals(\"assets/minecraft/texts/splashes.txt\", result.getMatcher().group(\"file\"));\n    }\n\n    @Test\n    public void noClassDefFoundError1() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/no_class_def_found_error.txt\")),\n                CrashReportAnalyzer.Rule.NO_CLASS_DEF_FOUND_ERROR);\n        assertEquals(\"blk\", result.getMatcher().group(\"class\"));\n    }\n\n    @Test\n    public void noClassDefFoundError2() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/no_class_def_found_error2.txt\")),\n                CrashReportAnalyzer.Rule.NO_CLASS_DEF_FOUND_ERROR);\n        assertEquals(\"cer\", result.getMatcher().group(\"class\"));\n    }\n\n    @Test\n    public void fileAlreadyExists() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/file_already_exists.txt\")),\n                CrashReportAnalyzer.Rule.FILE_ALREADY_EXISTS);\n        assertEquals(\n                \"D:\\\\Games\\\\Minecraft\\\\Minecraft Longtimeusing\\\\.minecraft\\\\versions\\\\1.12.2-forge1.12.2-14.23.5.2775\\\\config\\\\pvpsettings.txt\",\n                result.getMatcher().group(\"file\"));\n    }\n\n    @Test\n    public void loaderExceptionModCrash() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/loader_exception_mod_crash.txt\")),\n                CrashReportAnalyzer.Rule.LOADING_CRASHED_FORGE);\n        assertEquals(\"Better PvP\", result.getMatcher().group(\"name\"));\n        assertEquals(\"xaerobetterpvp\", result.getMatcher().group(\"id\"));\n    }\n\n    @Test\n    public void loaderExceptionModCrash2() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/loader_exception_mod_crash2.txt\")),\n                CrashReportAnalyzer.Rule.LOADING_CRASHED_FORGE);\n        assertEquals(\"Inventory Sort\", result.getMatcher().group(\"name\"));\n        assertEquals(\"invsort\", result.getMatcher().group(\"id\"));\n    }\n\n    @Test\n    public void loaderExceptionModCrash3() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/loader_exception_mod_crash3.txt\")),\n                CrashReportAnalyzer.Rule.LOADING_CRASHED_FORGE);\n        assertEquals(\"SuperOres\", result.getMatcher().group(\"name\"));\n        assertEquals(\"superores\", result.getMatcher().group(\"id\"));\n    }\n\n    @Test\n    public void loaderExceptionModCrash4() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/loader_exception_mod_crash4.txt\")),\n                CrashReportAnalyzer.Rule.LOADING_CRASHED_FORGE);\n        assertEquals(\"Kathairis\", result.getMatcher().group(\"name\"));\n        assertEquals(\"kathairis\", result.getMatcher().group(\"id\"));\n    }\n\n    @Test\n    public void loadingErrorFabric() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/loading_error_fabric.txt\")),\n                CrashReportAnalyzer.Rule.LOADING_CRASHED_FABRIC);\n        assertEquals(\"test\", result.getMatcher().group(\"id\"));\n    }\n\n    @Test\n    public void graphicsDriver() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/graphics_driver.txt\")),\n                CrashReportAnalyzer.Rule.GRAPHICS_DRIVER);\n    }\n\n    @Test\n    public void graphicsDriverJVM() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/graphics_driver.txt\")),\n                CrashReportAnalyzer.Rule.GRAPHICS_DRIVER);\n    }\n\n    @Test\n    public void splashScreen() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/splashscreen.txt\")),\n                CrashReportAnalyzer.Rule.GRAPHICS_DRIVER);\n    }\n\n    @Test\n    public void macosFailedToFindServicePortForDisplay() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/macos_failed_to_find_service_port_for_display.txt\")),\n                CrashReportAnalyzer.Rule.MACOS_FAILED_TO_FIND_SERVICE_PORT_FOR_DISPLAY);\n    }\n\n    @Test\n    public void modName() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/mod_name.txt\")),\n                CrashReportAnalyzer.Rule.MOD_NAME);\n    }\n\n    @Test\n    public void openj9() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/openj9.txt\")),\n                CrashReportAnalyzer.Rule.OPENJ9);\n    }\n\n    @Test\n    public void resolutionTooHigh() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/resourcepack_resolution.txt\")),\n                CrashReportAnalyzer.Rule.RESOLUTION_TOO_HIGH);\n    }\n\n    @Test\n    public void bootstrapFailed() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/bootstrap.txt\")),\n                CrashReportAnalyzer.Rule.BOOTSTRAP_FAILED);\n        assertEquals(\"prefab\", result.getMatcher().group(\"id\"));\n    }\n\n    @Test\n    public void mixinApplyModFailed() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/mixin_apply_mod_failed.txt\")),\n                CrashReportAnalyzer.Rule.MIXIN_APPLY_MOD_FAILED);\n        assertEquals(\"enhancedblockentities\", result.getMatcher().group(\"id\"));\n    }\n\n    @Test\n    public void unsatisfiedLinkError() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/unsatisfied_link_error.txt\")),\n                CrashReportAnalyzer.Rule.UNSATISFIED_LINK_ERROR);\n        assertEquals(\"lwjgl.dll\", result.getMatcher().group(\"name\"));\n    }\n\n    @Test\n    public void outOfMemoryMC() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/out_of_memory.txt\")),\n                CrashReportAnalyzer.Rule.OUT_OF_MEMORY);\n    }\n\n    @Test\n    public void outOfMemoryJVM() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/out_of_memory.txt\")),\n                CrashReportAnalyzer.Rule.OUT_OF_MEMORY);\n    }\n\n    @Test\n    public void outOfMemoryJVM1() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/out_of_memory2.txt\")),\n                CrashReportAnalyzer.Rule.OUT_OF_MEMORY);\n    }\n\n    @Test\n    public void memoryExceeded() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/memory_exceeded.txt\")),\n                CrashReportAnalyzer.Rule.MEMORY_EXCEEDED);\n    }\n\n    @Test\n    public void config() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/config.txt\")),\n                CrashReportAnalyzer.Rule.CONFIG);\n        assertEquals(\"jumbofurnace\", result.getMatcher().group(\"id\"));\n        assertEquals(\"jumbofurnace-server.toml\", result.getMatcher().group(\"file\"));\n    }\n\n    @Test\n    public void fabricWarnings() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/fabric_warnings.txt\")),\n                CrashReportAnalyzer.Rule.FABRIC_WARNINGS);\n        assertEquals((\" - Conflicting versions found for fabric-api-base: used 0.3.0+a02b446313, also found 0.3.0+a02b44633d, 0.3.0+a02b446318\\n\" +\n                        \" - Conflicting versions found for fabric-rendering-data-attachment-v1: used 0.1.5+a02b446313, also found 0.1.5+a02b446318\\n\" +\n                        \" - Conflicting versions found for fabric-rendering-fluids-v1: used 0.1.13+a02b446318, also found 0.1.13+a02b446313\\n\" +\n                        \" - Conflicting versions found for fabric-lifecycle-events-v1: used 1.4.4+a02b44633d, also found 1.4.4+a02b446318\\n\" +\n                        \" - Mod 'Sodium Extra' (sodium-extra) recommends any version of mod reeses-sodium-options, which is missing!\\n\" +\n                        \"\\t - You must install any version of reeses-sodium-options.\\n\" +\n                        \" - Conflicting versions found for fabric-screen-api-v1: used 1.0.4+155f865c18, also found 1.0.4+198a96213d\\n\" +\n                        \" - Conflicting versions found for fabric-key-binding-api-v1: used 1.0.4+a02b446318, also found 1.0.4+a02b44633d\\n\").replaceAll(\"\\\\s+\", \"\"),\n                result.getMatcher().group(\"reason\").replaceAll(\"\\\\s+\", \"\"));\n    }\n\n    @Test\n    public void fabricWarnings1() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/fabric_warnings2.txt\")),\n                CrashReportAnalyzer.Rule.FABRIC_WARNINGS);\n        assertEquals((\"net.fabricmc.loader.impl.FormattedException: Mod resolution encountered an incompatible mod set!\\n\" +\n                        \"A potential solution has been determined:\\n\" +\n                        \"\\t - Install roughlyenoughitems, version 6.0.2 or later.\\n\" +\n                        \"Unmet dependency listing:\\n\" +\n                        \"\\t - Mod 'Roughly Searchable' (roughlysearchable) 2.2.1+1.17.1 requires version 6.0.2 or later of roughlyenoughitems, which is missing!\\n\" +\n                        \"\\tat net.fabricmc.loader.impl.FabricLoaderImpl.load(FabricLoaderImpl.java:190) ~\").replaceAll(\"\\\\s+\", \"\"),\n                result.getMatcher().group(\"reason\").replaceAll(\"\\\\s+\", \"\"));\n    }\n\n    @Test\n    public void fabricWarnings2() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/fabric_warnings3.txt\")),\n                CrashReportAnalyzer.Rule.FABRIC_WARNINGS);\n        assertEquals((\"net.fabricmc.loader.impl.FormattedException: Some of your mods are incompatible with the game or each other!\\n\" +\n                        \"确定了一种可能的解决方法，这样做可能会解决你的问题：\\n\" +\n                        \"\\t - 安装 fabric-api，任意版本。\\n\" +\n                        \"\\t - 安装 sodium，0.5.6 及以上版本。\\n\" +\n                        \"更多信息：\\n\" +\n                        \"\\t - 模组 'Sodium Extra' (sodium-extra) 0.5.4+mc1.20.4-build.116 需要 fabric-api 的 任意版本，但没有安装它！\\n\" +\n                        \"\\t - 模组 'Sodium Extra' (sodium-extra) 0.5.4+mc1.20.4-build.116 需要 sodium 的 0.5.6 及以上版本，但没有安装它！\\n\" +\n                        \"\\tat net.fabricmc.loader.impl.FormattedException.ofLocalized(FormattedException.java:51) ~\").replaceAll(\"\\\\s+\", \"\"),\n                result.getMatcher().group(\"reason\").replaceAll(\"\\\\s+\", \"\"));\n    }\n\n    @Test\n    public void fabricConflicts() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/fabric-mod-conflict.txt\")),\n                CrashReportAnalyzer.Rule.MOD_RESOLUTION_CONFLICT);\n        assertEquals(\"phosphor\", result.getMatcher().group(\"sourcemod\"));\n        assertEquals(\"{starlight @ [*]}\", result.getMatcher().group(\"destmod\"));\n    }\n\n    @Test\n    public void fabricMissing() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/fabric-mod-missing.txt\")),\n                CrashReportAnalyzer.Rule.MOD_RESOLUTION_MISSING);\n        assertEquals(\"pca\", result.getMatcher().group(\"sourcemod\"));\n        assertEquals(\"{fabric @ [>=0.39.2]}\", result.getMatcher().group(\"destmod\"));\n    }\n\n    @Test\n    public void fabric0_12() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/fabric-version-0.12.txt\")),\n                CrashReportAnalyzer.Rule.FABRIC_VERSION_0_12);\n    }\n\n    @Test\n    public void twilightForestOptiFineIncompatible() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/mod/twilightforest_optifine_incompatibility.txt\")),\n                CrashReportAnalyzer.Rule.TWILIGHT_FOREST_OPTIFINE);\n    }\n\n    @Test\n    public void performantOptiFineIncompatible() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/mod/performant_optifine_incompatibility.txt\")),\n                CrashReportAnalyzer.Rule.PERFORMANT_FOREST_OPTIFINE);\n    }\n\n    @Test\n    public void neoforgeForestOptiFineIncompatible() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/mod/neoforgeforest_optifine_incompatibility.txt\")),\n                CrashReportAnalyzer.Rule.NEOFORGE_FOREST_OPTIFINE);\n    }\n\n    @Test\n    public void fabricMissingMinecraft() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/fabric-minecraft.txt\")),\n                CrashReportAnalyzer.Rule.MOD_RESOLUTION_MISSING_MINECRAFT);\n        assertEquals(\"fabric\", result.getMatcher().group(\"mod\"));\n        assertEquals(\"[~1.16.2-alpha.20.28.a]\", result.getMatcher().group(\"version\"));\n    }\n\n    @Test\n    public void optifineRepeatInstallation() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/optifine_repeat_installation.txt\")),\n                CrashReportAnalyzer.Rule.OPTIFINE_REPEAT_INSTALLATION);\n    }\n\n    @Test\n    public void incompleteForgeInstallation() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/incomplete_forge_installation.txt\")),\n                CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);\n    }\n\n    @Test\n    public void incompleteForgeInstallation2() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/incomplete_forge_installation2.txt\")),\n                CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);\n    }\n\n    @Test\n    public void incompleteForgeInstallation3() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/incomplete_forge_installation3.txt\")),\n                CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);\n    }\n\n    @Test\n    public void incompleteForgeInstallation4() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/incomplete_forge_installation4.txt\")),\n                CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);\n    }\n\n    @Test\n    public void incompleteForgeInstallation5() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/incomplete_forge_installation5.txt\")),\n                CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);\n    }\n\n    @Test\n    public void incompleteForgeInstallation6() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/incomplete_forge_installation6.txt\")),\n                CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);\n    }\n\n    @Test\n    public void incompleteForgeInstallation7() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/incomplete_forge_installation7.txt\")),\n                CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);\n    }\n\n    @Test\n    public void forgeRepeatInstallation() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/forge_repeat_installation.txt\")),\n                CrashReportAnalyzer.Rule.FORGE_REPEAT_INSTALLATION);\n    }\n\n    @Test\n    public void forgeRepeatInstallation1() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/forge_repeat_installation2.txt\")),\n                CrashReportAnalyzer.Rule.FORGE_REPEAT_INSTALLATION);\n    }\n\n    @Test\n    public void needJDK11() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/need_jdk11.txt\")),\n                CrashReportAnalyzer.Rule.NEED_JDK11);\n    }\n\n    @Test\n    public void needJDK112() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/need_jdk112.txt\")),\n                CrashReportAnalyzer.Rule.NEED_JDK11);\n    }\n\n    @Test\n    public void needJDK113() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/need_jdk113.txt\")),\n                CrashReportAnalyzer.Rule.NEED_JDK11);\n    }\n\n    @Test\n    public void optifineIsNotCompatibleWithForge() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/optifine_is_not_compatible_with_forge.txt\")),\n                CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);\n    }\n\n    @Test\n    public void optifineIsNotCompatibleWithForge1() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/optifine_is_not_compatible_with_forge2.txt\")),\n                CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);\n    }\n\n    @Test\n    public void optifineIsNotCompatibleWithForge2() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/optifine_is_not_compatible_with_forge3.txt\")),\n                CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);\n    }\n\n    @Test\n    public void optifineIsNotCompatibleWithForge3() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/optifine_is_not_compatible_with_forge4.txt\")),\n                CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);\n    }\n\n    @Test\n    public void optifineIsNotCompatibleWithForge4() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/optifine_is_not_compatible_with_forge5.txt\")),\n                CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);\n    }\n\n    @Test\n    public void optifineIsNotCompatibleWithForge5() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/optifine_is_not_compatible_with_forge6.txt\")),\n                CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);\n    }\n\n    @Test\n    public void shadersMod() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/shaders_mod.txt\")),\n                CrashReportAnalyzer.Rule.SHADERS_MOD);\n    }\n\n    @Test\n    public void installMixinbootstrap() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/logs/install_mixinbootstrap.txt\")),\n                CrashReportAnalyzer.Rule.INSTALL_MIXINBOOTSTRAP);\n    }\n\n    @Test\n    public void nightconfigfixes() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/night_config_fixes.txt\")),\n                CrashReportAnalyzer.Rule.NIGHT_CONFIG_FIXES);\n    }\n\n    @Test\n    public void customNpc() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/mod/customnpc.txt\")),\n                CrashReportAnalyzer.Rule.ENTITY);\n        assertEquals(\"customnpcs.CustomNpc (noppes.npcs.entity.EntityCustomNpc)\",\n                result.getMatcher().group(\"type\"));\n        assertEquals(\"99942.59, 4.00, 100000.98\",\n                result.getMatcher().group(\"location\"));\n\n        assertEquals(\n                new HashSet<>(Arrays.asList(\"npcs\", \"noppes\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/customnpc.txt\")));\n    }\n\n    @Test\n    public void tconstruct() throws IOException {\n        CrashReportAnalyzer.Result result = findResultByRule(\n                CrashReportAnalyzer.analyze(loadLog(\"/crash-report/mod/tconstruct.txt\")),\n                CrashReportAnalyzer.Rule.BLOCK);\n        assertEquals(\"Block{tconstruct:seared_drain}[active=true,facing=north]\",\n                result.getMatcher().group(\"type\"));\n        assertEquals(\"World: (1370,92,-738), Chunk: (at 10,5,14 in 85,-47; contains blocks 1360,0,-752 to 1375,255,-737), Region: (2,-2; contains chunks 64,-64 to 95,-33, blocks 1024,0,-1024 to 1535,255,-513)\",\n                result.getMatcher().group(\"location\"));\n\n        assertEquals(\n                new HashSet<>(Arrays.asList(\"tconstruct\", \"slimeknights\", \"smeltery\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/tconstruct.txt\")));\n    }\n\n    @Test\n    public void bettersprinting() throws IOException {\n        assertEquals(\n                new HashSet<>(Arrays.asList(\"chylex\", \"bettersprinting\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/bettersprinting.txt\")));\n    }\n\n    @Test\n    public void ic2() throws IOException {\n        assertEquals(\n                Collections.singleton(\"ic2\"),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/ic2.txt\")));\n    }\n\n    @Test\n    public void nei() throws IOException {\n        assertEquals(\n                new HashSet<>(Arrays.asList(\"nei\", \"codechicken\", \"guihook\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/nei.txt\")));\n    }\n\n    @Test\n    public void netease() throws IOException {\n        assertEquals(\n                new HashSet<>(Arrays.asList(\"netease\", \"battergaming\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/netease.txt\")));\n    }\n\n    @Test\n    public void flammpfeil() throws IOException {\n        assertEquals(\n                new HashSet<>(Arrays.asList(\"slashblade\", \"flammpfeil\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/flammpfeil.txt\")));\n    }\n\n    @Test\n    public void creativemd() throws IOException {\n        assertEquals(\n                new HashSet<>(Arrays.asList(\"creativemd\", \"itemphysic\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/creativemd.txt\")));\n    }\n\n    @Test\n    public void mapletree() throws IOException {\n        assertEquals(\n                new HashSet<>(Arrays.asList(\"MapleTree\", \"bamboo\", \"uraniummc\", \"ecru\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/mapletree.txt\")));\n    }\n\n    @Test\n    public void thaumcraft() throws IOException {\n        assertEquals(\n                Collections.singleton(\"thaumcraft\"),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/thaumcraft.txt\")));\n    }\n\n    @Test\n    public void shadersmodcore() throws IOException {\n        assertEquals(\n                Collections.singleton(\"shadersmodcore\"),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/shadersmodcore.txt\")));\n    }\n\n    @Test\n    public void twilightforest() throws IOException {\n        assertEquals(\n                Collections.singleton(\"twilightforest\"),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/twilightforest.txt\")));\n    }\n\n    @Test\n    public void optifine() throws IOException {\n        assertEquals(\n                Collections.singleton(\"OptiFine\"),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/twilightforest_optifine_incompatibility.txt\")));\n    }\n\n    @Test\n    public void wizardry() throws IOException {\n        assertEquals(\n                new HashSet<>(Arrays.asList(\"wizardry\", \"electroblob\", \"projectile\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/wizardry.txt\")));\n    }\n\n    @Test\n    public void icycream() throws IOException {\n        assertEquals(\n                new HashSet<>(Collections.singletonList(\"icycream\")),\n                CrashReportAnalyzer.findKeywordsFromCrashReport(loadLog(\"/crash-report/mod/icycream.txt\")));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepositoryTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2021  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.mod.curse;\n\nimport org.jackhuang.hmcl.util.MurmurHash2;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class CurseForgeRemoteModRepositoryTest {\n\n    @Test\n    @Disabled\n    public void testMurmurHash() throws Exception {\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        try (InputStream is = Files.newInputStream(Paths.get(\"C:\\\\Users\\\\huang\\\\Downloads\\\\JustEnoughCalculation-1.16.5-3.8.5.jar\"))) {\n            byte[] buf = new byte[1024];\n            int len;\n            while ((len = is.read(buf, 0, buf.length)) > 0) {\n                for (int i = 0; i < len; i++) {\n                    byte b = buf[i];\n                    if (b != 9 && b != 10 && b != 13 && b != 32) {\n                        baos.write(b);\n                    }\n                }\n            }\n\n        }\n        long hash = Integer.toUnsignedLong(MurmurHash2.hash32(baos.toByteArray(), baos.size(), 1));\n\n        assertEquals(hash, 3333498611L);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/schematic/LitematicFileTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.schematic;\n\nimport javafx.geometry.Point3D;\nimport org.jackhuang.hmcl.game.CrashReportAnalyzerTest;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.file.Paths;\nimport java.time.Instant;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic final class LitematicFileTest {\n    private static LitematicFile load(String name) throws IOException, URISyntaxException {\n        URL resource = CrashReportAnalyzerTest.class.getResource(name);\n        if (resource == null)\n            throw new IOException(\"Resource not found: \" + name);\n        return LitematicFile.load(Paths.get(resource.toURI()));\n    }\n\n    @Test\n    public void test() throws Exception {\n        LitematicFile file = load(\"/schematics/test.litematic\");\n        assertEquals(\"刷石机一桶岩浆下推爆破8.3万每小时\", file.getName());\n        assertEquals(\"hsds\", file.getAuthor());\n        assertEquals(\"\", file.getDescription());\n        assertEquals(Instant.ofEpochMilli(1746443586433L), file.getTimeCreated());\n        assertEquals(Instant.ofEpochMilli(1746443586433L), file.getTimeModified());\n        assertEquals(1334, file.getTotalBlocks());\n        assertEquals(5746, file.getTotalVolume());\n        assertEquals(new Point3D(17, 26, 13), file.getEnclosingSize());\n        assertEquals(1, file.getRegionCount());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/ByteArrayTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.HexFormat;\nimport java.util.function.Consumer;\n\nimport static org.jackhuang.hmcl.util.ByteArray.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @author Glavo\n */\npublic final class ByteArrayTest {\n    private static final byte[] TEST_ARRAY = {\n            (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78,\n            (byte) 0x9a, (byte) 0xbc, (byte) 0xde, (byte) 0xf0,\n            (byte) 0x00\n    };\n\n    @Test\n    public void testGetByte() {\n        assertEquals((byte) 0x12, getByte(TEST_ARRAY, 0));\n        assertEquals((byte) 0x78, getByte(TEST_ARRAY, 3));\n        assertEquals((byte) 0x00, getByte(TEST_ARRAY, 8));\n\n        assertEquals(0x12, getUnsignedByte(TEST_ARRAY, 0));\n        assertEquals(0x78, getUnsignedByte(TEST_ARRAY, 3));\n        assertEquals(0x00, getUnsignedByte(TEST_ARRAY, 8));\n\n        assertThrows(IndexOutOfBoundsException.class, () -> getByte(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getByte(TEST_ARRAY, 9));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedByte(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedByte(TEST_ARRAY, 9));\n    }\n\n    @Test\n    public void testGetShort() {\n        assertEquals((short) 0x3412, getShortLE(TEST_ARRAY, 0));\n        assertEquals((short) 0x5634, getShortLE(TEST_ARRAY, 1));\n        assertEquals((short) 0x00f0, getShortLE(TEST_ARRAY, 7));\n\n        assertEquals(0x3412, getUnsignedShortLE(TEST_ARRAY, 0));\n        assertEquals(0x5634, getUnsignedShortLE(TEST_ARRAY, 1));\n        assertEquals(0x00f0, getUnsignedShortLE(TEST_ARRAY, 7));\n\n        assertEquals((short) 0x1234, getShortBE(TEST_ARRAY, 0));\n        assertEquals((short) 0x3456, getShortBE(TEST_ARRAY, 1));\n        assertEquals((short) 0xf000, getShortBE(TEST_ARRAY, 7));\n\n        assertEquals(0x1234, getUnsignedShortBE(TEST_ARRAY, 0));\n        assertEquals(0x3456, getUnsignedShortBE(TEST_ARRAY, 1));\n        assertEquals(0xf000, getUnsignedShortBE(TEST_ARRAY, 7));\n\n        assertThrows(IndexOutOfBoundsException.class, () -> getShortLE(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getShortLE(TEST_ARRAY, 8));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedShortLE(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedShortLE(TEST_ARRAY, 8));\n        assertThrows(IndexOutOfBoundsException.class, () -> getShortBE(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getShortBE(TEST_ARRAY, 8));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedShortBE(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedShortBE(TEST_ARRAY, 8));\n    }\n\n    @Test\n    public void testGetInt() {\n        assertEquals(0x78563412, getIntLE(TEST_ARRAY, 0));\n        assertEquals(0x9a785634, getIntLE(TEST_ARRAY, 1));\n        assertEquals(0x00f0debc, getIntLE(TEST_ARRAY, 5));\n\n        assertEquals(0x78563412L, getUnsignedIntLE(TEST_ARRAY, 0));\n        assertEquals(0x9a785634L, getUnsignedIntLE(TEST_ARRAY, 1));\n        assertEquals(0x00f0debcL, getUnsignedIntLE(TEST_ARRAY, 5));\n\n        assertEquals(0x12345678, getIntBE(TEST_ARRAY, 0));\n        assertEquals(0x3456789a, getIntBE(TEST_ARRAY, 1));\n        assertEquals(0xbcdef000, getIntBE(TEST_ARRAY, 5));\n\n        assertEquals(0x12345678L, getUnsignedIntBE(TEST_ARRAY, 0));\n        assertEquals(0x3456789aL, getUnsignedIntBE(TEST_ARRAY, 1));\n        assertEquals(0xbcdef000L, getUnsignedIntBE(TEST_ARRAY, 5));\n\n        assertThrows(IndexOutOfBoundsException.class, () -> getIntLE(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getIntLE(TEST_ARRAY, 6));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedIntLE(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedIntLE(TEST_ARRAY, 6));\n        assertThrows(IndexOutOfBoundsException.class, () -> getIntBE(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getIntBE(TEST_ARRAY, 6));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedIntBE(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getUnsignedIntBE(TEST_ARRAY, 6));\n    }\n\n    @Test\n    public void testGetLong() {\n        assertEquals(0xf0debc9a78563412L, getLongLE(TEST_ARRAY, 0));\n        assertEquals(0x00f0debc9a785634L, getLongLE(TEST_ARRAY, 1));\n\n        assertEquals(0x123456789abcdef0L, getLongBE(TEST_ARRAY, 0));\n        assertEquals(0x3456789abcdef000L, getLongBE(TEST_ARRAY, 1));\n\n        assertThrows(IndexOutOfBoundsException.class, () -> getLongLE(TEST_ARRAY, -1));\n        assertThrows(IndexOutOfBoundsException.class, () -> getLongLE(TEST_ARRAY, 2));\n        assertThrows(IndexOutOfBoundsException.class, () -> getLongBE(TEST_ARRAY, 2));\n        assertThrows(IndexOutOfBoundsException.class, () -> getLongBE(TEST_ARRAY, -1));\n    }\n\n    private static byte[] byteArray(String string) {\n        try {\n            return HexFormat.of().parseHex(string);\n        } catch (IllegalArgumentException e) {\n            throw new AssertionError(e);\n        }\n    }\n\n    private static byte[] changedArray(Consumer<byte[]> consumer) {\n        var array = TEST_ARRAY.clone();\n        consumer.accept(array);\n        return array;\n    }\n\n    @Test\n    public void testSetByte() {\n        assertArrayEquals(byteArray(\"ff3456789abcdef000\"),\n                changedArray(array -> setByte(array, 0, (byte) 0xff)));\n        assertArrayEquals(byteArray(\"123456789affdef000\"),\n                changedArray(array -> setByte(array, 5, (byte) 0xff)));\n        assertArrayEquals(byteArray(\"123456789abcdef0ff\"),\n                changedArray(array -> setByte(array, 8, (byte) 0xff)));\n\n        assertArrayEquals(byteArray(\"ff3456789abcdef000\"),\n                changedArray(array -> setUnsignedByte(array, 0, 0xff)));\n        assertArrayEquals(byteArray(\"123456789affdef000\"),\n                changedArray(array -> setUnsignedByte(array, 5, 0xff)));\n        assertArrayEquals(byteArray(\"123456789abcdef0ff\"),\n                changedArray(array -> setUnsignedByte(array, 8, 0xff)));\n\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setByte(array, -1, (byte) 0xff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setByte(array, 9, (byte) 0xff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedByte(array, -1, 0xff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedByte(array, 9, 0xff)));\n    }\n\n    @Test\n    public void testSetShort() {\n        assertArrayEquals(byteArray(\"ffee56789abcdef000\"),\n                changedArray(array -> setShortLE(array, 0, (short) 0xeeff)));\n        assertArrayEquals(byteArray(\"123456789affeef000\"),\n                changedArray(array -> setShortLE(array, 5, (short) 0xeeff)));\n        assertArrayEquals(byteArray(\"123456789abcdeffee\"),\n                changedArray(array -> setShortLE(array, 7, (short) 0xeeff)));\n\n        assertArrayEquals(byteArray(\"ffee56789abcdef000\"),\n                changedArray(array -> setUnsignedShortLE(array, 0, 0xeeff)));\n        assertArrayEquals(byteArray(\"123456789affeef000\"),\n                changedArray(array -> setUnsignedShortLE(array, 5, 0xeeff)));\n        assertArrayEquals(byteArray(\"123456789abcdeffee\"),\n                changedArray(array -> setUnsignedShortLE(array, 7, 0xeeff)));\n\n        assertArrayEquals(byteArray(\"eeff56789abcdef000\"),\n                changedArray(array -> setShortBE(array, 0, (short) 0xeeff)));\n        assertArrayEquals(byteArray(\"123456789aeefff000\"),\n                changedArray(array -> setShortBE(array, 5, (short) 0xeeff)));\n        assertArrayEquals(byteArray(\"123456789abcdeeeff\"),\n                changedArray(array -> setShortBE(array, 7, (short) 0xeeff)));\n\n        assertArrayEquals(byteArray(\"eeff56789abcdef000\"),\n                changedArray(array -> setUnsignedShortBE(array, 0, 0xeeff)));\n        assertArrayEquals(byteArray(\"123456789aeefff000\"),\n                changedArray(array -> setUnsignedShortBE(array, 5, 0xeeff)));\n        assertArrayEquals(byteArray(\"123456789abcdeeeff\"),\n                changedArray(array -> setUnsignedShortBE(array, 7, 0xeeff)));\n\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setShortLE(array, -1, (short) 0xeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setShortLE(array, 8, (short) 0xeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedShortLE(array, -1, 0xeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedShortLE(array, 8, 0xeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setShortBE(array, -1, (short) 0xeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setShortBE(array, 8, (short) 0xeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedShortBE(array, -1, 0xeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedShortBE(array, 8, 0xeeff)));\n    }\n\n    @Test\n    public void testSetInt() {\n        assertArrayEquals(byteArray(\"ffeeddcc9abcdef000\"),\n                changedArray(array -> setIntLE(array, 0, 0xccddeeff)));\n        assertArrayEquals(byteArray(\"12345678ffeeddcc00\"),\n                changedArray(array -> setIntLE(array, 4, 0xccddeeff)));\n        assertArrayEquals(byteArray(\"123456789affeeddcc\"),\n                changedArray(array -> setIntLE(array, 5, 0xccddeeff)));\n\n        assertArrayEquals(byteArray(\"ffeeddcc9abcdef000\"),\n                changedArray(array -> setUnsignedIntLE(array, 0, 0xccddeeffL)));\n        assertArrayEquals(byteArray(\"12345678ffeeddcc00\"),\n                changedArray(array -> setUnsignedIntLE(array, 4, 0xccddeeffL)));\n        assertArrayEquals(byteArray(\"123456789affeeddcc\"),\n                changedArray(array -> setUnsignedIntLE(array, 5, 0xccddeeffL)));\n\n        assertArrayEquals(byteArray(\"ccddeeff9abcdef000\"),\n                changedArray(array -> setIntBE(array, 0, 0xccddeeff)));\n        assertArrayEquals(byteArray(\"12345678ccddeeff00\"),\n                changedArray(array -> setIntBE(array, 4, 0xccddeeff)));\n        assertArrayEquals(byteArray(\"123456789accddeeff\"),\n                changedArray(array -> setIntBE(array, 5, 0xccddeeff)));\n\n        assertArrayEquals(byteArray(\"ccddeeff9abcdef000\"),\n                changedArray(array -> setUnsignedIntBE(array, 0, 0xccddeeffL)));\n        assertArrayEquals(byteArray(\"12345678ccddeeff00\"),\n                changedArray(array -> setUnsignedIntBE(array, 4, 0xccddeeffL)));\n        assertArrayEquals(byteArray(\"123456789accddeeff\"),\n                changedArray(array -> setUnsignedIntBE(array, 5, 0xccddeeffL)));\n\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setIntLE(array, -1, 0xccddeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setIntLE(array, 8, 0xccddeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedIntLE(array, -1, 0xccddeeffL)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedIntLE(array, 8, 0xccddeeffL)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setIntBE(array, -1, 0xccddeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setIntBE(array, 8, 0xccddeeff)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedIntBE(array, -1, 0xccddeeffL)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setUnsignedIntBE(array, 8, 0xccddeeffL)));\n    }\n\n    @Test\n    public void testSetLong() {\n        assertArrayEquals(byteArray(\"ffeeddccbbaa998800\"),\n                changedArray(array -> setLongLE(array, 0, 0x8899aabbccddeeffL)));\n        assertArrayEquals(byteArray(\"12ffeeddccbbaa9988\"),\n                changedArray(array -> setLongLE(array, 1, 0x8899aabbccddeeffL)));\n\n        assertArrayEquals(byteArray(\"8899aabbccddeeff00\"),\n                changedArray(array -> setLongBE(array, 0, 0x8899aabbccddeeffL)));\n        assertArrayEquals(byteArray(\"128899aabbccddeeff\"),\n                changedArray(array -> setLongBE(array, 1, 0x8899aabbccddeeffL)));\n\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setLongLE(array, -1, 0x8899aabbccddeeffL)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setLongLE(array, 2, 0x8899aabbccddeeffL)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setLongBE(array, -1, 0x8899aabbccddeeffL)));\n        assertThrows(IndexOutOfBoundsException.class,\n                () -> changedArray(array -> setLongBE(array, 2, 0x8899aabbccddeeffL)));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/CircularArrayListTest.java",
    "content": "package org.jackhuang.hmcl.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.*;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @author Glavo\n */\npublic class CircularArrayListTest {\n\n    private static void assertEmpty(CircularArrayList<?> list) {\n        assertEquals(0, list.size());\n        assertTrue(list.isEmpty());\n        assertThrows(NoSuchElementException.class, () -> list.getFirst());\n        assertThrows(NoSuchElementException.class, () -> list.getLast());\n        assertThrows(NoSuchElementException.class, () -> list.removeFirst());\n        assertThrows(NoSuchElementException.class, () -> list.removeLast());\n        assertThrows(IndexOutOfBoundsException.class, () -> list.get(0));\n        assertThrows(IndexOutOfBoundsException.class, () -> list.get(10));\n        assertThrows(IndexOutOfBoundsException.class, () -> list.get(-1));\n    }\n\n    private static void assertListEquals(List<?> expected, CircularArrayList<?> actual) {\n        assertIterableEquals(expected, actual);\n        if (expected.isEmpty()) {\n            assertEmpty(actual);\n        } else {\n            assertEquals(expected.get(0), actual.getFirst());\n            assertEquals(expected.get(expected.size() - 1), actual.getLast());\n            assertThrows(IndexOutOfBoundsException.class, () -> actual.get(-1));\n            assertThrows(IndexOutOfBoundsException.class, () -> actual.get(actual.size()));\n        }\n    }\n\n    @Test\n    public void testEmpty() {\n        CircularArrayList<String> list = new CircularArrayList<>();\n        assertEmpty(list);\n    }\n\n    @Test\n    public void testSequential() {\n        Helper<String> helper = new Helper<>();\n\n        helper.addAll(\"str0\", \"str1\", \"str2\");\n        helper.add(\"str3\");\n        helper.add(2, \"str4\");\n        helper.remove(1);\n        helper.remove(0);\n        helper.removeFirst();\n        helper.removeLast();\n        helper.remove(0);\n        assertEmpty(helper.list);\n    }\n\n    @Test\n    public void testSequentialExpansion() {\n        Helper<String> helper = new Helper<>();\n        Random random = new Random(0);\n        for (int i = 0; i < 5; i++) {\n            helper.add(\"str\" + i);\n        }\n\n        for (int i = 5; i < 100; i++) {\n            helper.add(random.nextInt(helper.size()) + 1, \"str\" + i);\n        }\n\n        for (int i = 0; i < 100; i++) {\n            helper.set(random.nextInt(helper.size()), \"new str \" + i);\n        }\n\n        for (int i = 0; i < 20; i++) {\n            helper.remove(random.nextInt(helper.size()));\n        }\n\n        for (int i = 0; i < 20; i++) {\n            helper.removeFirst();\n            helper.removeLast();\n        }\n\n        int remaining = helper.size();\n        for (int i = 0; i < remaining; i++) {\n            helper.removeLast();\n        }\n    }\n\n    @Test\n    public void testLoopback() {\n        Helper<String> helper = new Helper<>();\n\n        helper.addAll(\"str3\", \"str4\", \"str5\");\n        helper.addAll(0, \"str0\", \"str1\", \"str2\");\n        helper.remove(1);\n        helper.remove(4);\n        helper.removeFirst();\n        helper.removeLast();\n        helper.remove(1);\n        helper.remove(0);\n        assertEmpty(helper.list);\n    }\n\n    @Test\n    public void testLoopbackExpansion() {\n        Helper<String> helper = new Helper<>();\n        Random random = new Random(0);\n\n        for (int i = 5; i < 10; i++) {\n            helper.add(\"str\" + i);\n        }\n        for (int i = 4; i >= 0; i--) {\n            helper.add(0, \"str\" + i);\n        }\n\n        for (int i = 10; i < 100; i++) {\n            helper.add(random.nextInt(helper.size() + 1), \"str\" + i);\n        }\n\n        for (int i = 0; i < 100; i++) {\n            helper.set(random.nextInt(helper.size()), \"new str \" + i);\n        }\n\n        for (int i = 0; i < 20; i++) {\n            helper.remove(random.nextInt(helper.size()));\n        }\n\n        for (int i = 0; i < 20; i++) {\n            helper.removeFirst();\n            helper.removeLast();\n        }\n\n        int remaining = helper.size();\n        for (int i = 0; i < remaining; i++) {\n            helper.removeLast();\n        }\n    }\n\n    @Test\n    public void testClear() {\n        CircularArrayList<String> list = new CircularArrayList<>();\n        list.clear();\n        assertEmpty(list);\n\n        for (int i = 0; i < 20; i++) {\n            list.add(\"str\" + i);\n        }\n        list.clear();\n        assertEmpty(list);\n\n        for (int i = 10; i < 20; i++) {\n            list.add(\"str\" + i);\n        }\n        for (int i = 9; i >= 0; i--) {\n            list.addFirst(\"str\" + i);\n        }\n        list.clear();\n        assertEmpty(list);\n    }\n\n    private static final class Helper<E> {\n        final List<E> expected;\n        final CircularArrayList<E> list;\n\n        Helper() {\n            this.expected = new ArrayList<>();\n            this.list = new CircularArrayList<>();\n\n            assertStatus();\n        }\n\n        Helper(List<E> expected, CircularArrayList<E> list) {\n            this.expected = expected;\n            this.list = list;\n\n            assertStatus();\n        }\n\n        void assertStatus() {\n            assertListEquals(expected, list);\n        }\n\n        int size() {\n            return expected.size();\n        }\n\n        void set(int i, E e) {\n            assertEquals(expected.set(i, e), list.set(i, e));\n            assertStatus();\n        }\n\n        void add(E e) {\n            expected.add(e);\n            list.add(e);\n            assertStatus();\n        }\n\n        void add(int i, E e) {\n            expected.add(i, e);\n            list.add(i, e);\n            assertStatus();\n        }\n\n        @SafeVarargs\n        final void addAll(E... values) {\n            Collections.addAll(expected, values);\n            Collections.addAll(list, values);\n            assertStatus();\n        }\n\n        @SafeVarargs\n        final void addAll(int i, E... values) {\n            List<E> valuesList = Arrays.asList(values);\n            assertEquals(expected.addAll(i, valuesList), list.addAll(i, valuesList));\n            assertStatus();\n        }\n\n        void remove(int idx) {\n            assertEquals(expected.remove(idx), list.remove(idx));\n            assertStatus();\n        }\n\n        void removeFirst() {\n            assertEquals(expected.remove(0), list.removeFirst());\n            assertStatus();\n        }\n\n        void removeLast() {\n            assertEquals(expected.remove(expected.size() - 1), list.removeLast());\n            assertStatus();\n        }\n\n        void clear() {\n            expected.clear();\n            list.clear();\n            assertEmpty(list);\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/DataSizeUnitTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic final class DataSizeUnitTest {\n\n    @Test\n    public void testToString() {\n        assertEquals(\"0 bytes\", DataSizeUnit.format(0));\n        assertEquals(\"1 byte\", DataSizeUnit.format(1));\n        assertEquals(\"2 bytes\", DataSizeUnit.format(2));\n        assertEquals(\"100 bytes\", DataSizeUnit.format(100));\n        assertEquals(\"1023 bytes\", DataSizeUnit.format(1023));\n        assertEquals(\"1 KiB\", DataSizeUnit.format(1024));\n        assertEquals(\"1 KiB\", DataSizeUnit.format(1025));\n        assertEquals(\"1.07 KiB\", DataSizeUnit.format(1100));\n        assertEquals(\"4 KiB\", DataSizeUnit.format(4096));\n        assertEquals(\"1 MiB\", DataSizeUnit.format(1024 * 1024));\n        assertEquals(\"1.5 MiB\", DataSizeUnit.format((long) (1.5 * 1024 * 1024)));\n        assertEquals(\"1 GiB\", DataSizeUnit.format(1024 * 1024 * 1024));\n        assertEquals(\"1.5 GiB\", DataSizeUnit.format((long) (1.5 * 1024 * 1024 * 1024)));\n        assertEquals(\"1 TiB\", DataSizeUnit.format(1024L * 1024 * 1024 * 1024));\n        assertEquals(\"1.5 TiB\", DataSizeUnit.format((long) (1.5 * 1024 * 1024 * 1024 * 1024)));\n    }\n\n    @Test\n    public void testConvertFromBytes() {\n        assertEquals(1, DataSizeUnit.KILOBYTES.convertFromBytes(1024L));\n        assertEquals(1.5, DataSizeUnit.KILOBYTES.convertFromBytes((long) (1024. * 1.5)));\n\n        assertEquals(1, DataSizeUnit.MEGABYTES.convertFromBytes(1024L * 1024));\n        assertEquals(1.5, DataSizeUnit.MEGABYTES.convertFromBytes((long) (1024 * 1024 * 1.5)));\n    }\n\n    @Test\n    public void testConvertToBytes() {\n        assertEquals(10., DataSizeUnit.BYTES.convertToBytes(10));\n        assertEquals(10. * 1024, DataSizeUnit.KILOBYTES.convertToBytes(10));\n        assertEquals(10. * 1024 * 1024, DataSizeUnit.MEGABYTES.convertToBytes(10));\n        assertEquals(10. * 1024 * 1024 * 1024, DataSizeUnit.GIGABYTES.convertToBytes(10));\n        assertEquals(10. * 1024 * 1024 * 1024 * 1024, DataSizeUnit.TERABYTES.convertToBytes(10));\n    }\n\n    private DataSizeUnitTest() {\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairUtilsTest.java",
    "content": "package org.jackhuang.hmcl.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * @author Glavo\n */\npublic final class KeyValuePairUtilsTest {\n    @Test\n    public void test() throws IOException {\n        String content = \"#test: key0=value0\\n \\n\" +\n                \"key1=value1\\n\" +\n                \"key2=\\\"value2\\\"\\n\" +\n                \"key3=\\\"\\\\\\\" \\\\n\\\"\\n\";\n\n        Map<String, String> properties = KeyValuePairUtils.loadProperties(new BufferedReader(new StringReader(content)));\n\n        assertEquals(Lang.mapOf(\n                pair(\"key1\", \"value1\"),\n                pair(\"key2\", \"value2\"),\n                pair(\"key3\", \"\\\" \\n\")\n        ), properties);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/OSVersionTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.util.platform.OSVersion;\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.jackhuang.hmcl.util.versioning.VersionNumber;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/// @author Glavo\npublic class OSVersionTest {\n\n    @Test\n    public void testNotAcceptSystem() {\n        assertThrows(IllegalArgumentException.class, () -> new OSVersion.Generic(OperatingSystem.WINDOWS, VersionNumber.ZERO));\n    }\n\n    @Test\n    public void testParse() {\n        assertEquals(OSVersion.WINDOWS_7, OSVersion.Windows.parse(\"6.1\"));\n        assertEquals(OSVersion.WINDOWS_10, OSVersion.Windows.parse(\"10.0\"));\n        assertEquals(new OSVersion.Windows(10, 0, 26120), OSVersion.Windows.parse(\"10.0.26120\"));\n        assertEquals(new OSVersion.Windows(10, 0, 26120), OSVersion.Windows.parse(\"10.0.26120-unknown\"));\n        assertEquals(new OSVersion.Windows(10, 0, 26120, 3964), OSVersion.Windows.parse(\"10.0.26120.3964\"));\n        assertEquals(new OSVersion.Windows(10, 0, 26120, 3964), OSVersion.Windows.parse(\"10.0.26120.3964-unknown\"));\n    }\n\n    @Test\n    public void testIsAtLeast() {\n        assertTrue(OSVersion.WINDOWS_10.isAtLeast(OSVersion.WINDOWS_7));\n        assertTrue(OSVersion.WINDOWS_10.isAtLeast(OSVersion.WINDOWS_10));\n        assertFalse(OSVersion.WINDOWS_10.isAtLeast(OSVersion.WINDOWS_11));\n        assertFalse(OSVersion.WINDOWS_10.isAtLeast(OSVersion.of(OperatingSystem.LINUX, \"4.0\")));\n\n        assertTrue(OSVersion.of(OperatingSystem.LINUX, \"4.0\").isAtLeast(OSVersion.of(OperatingSystem.LINUX, \"4.0\")));\n        assertTrue(OSVersion.of(OperatingSystem.LINUX, \"4.0\").isAtLeast(OSVersion.of(OperatingSystem.LINUX, \"3.0\")));\n        assertFalse(OSVersion.of(OperatingSystem.LINUX, \"4.0\").isAtLeast(OSVersion.of(OperatingSystem.LINUX, \"5.0\")));\n        assertFalse(OSVersion.of(OperatingSystem.LINUX, \"4.0\").isAtLeast(OSVersion.of(OperatingSystem.WINDOWS, \"4.0\")));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/ServerAddressTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n/**\n * @author Glavo\n */\npublic final class ServerAddressTest {\n\n    @Test\n    public void testParse() {\n        assertEquals(new ServerAddress(\"example.com\"), ServerAddress.parse(\"example.com\"));\n        assertEquals(new ServerAddress(\"example.com\", 25565), ServerAddress.parse(\"example.com:25565\"));\n\n        assertEquals(new ServerAddress(\"127.0.0.0\"), ServerAddress.parse(\"127.0.0.0\"));\n        assertEquals(new ServerAddress(\"127.0.0.0\", 0), ServerAddress.parse(\"127.0.0.0:0\"));\n        assertEquals(new ServerAddress(\"127.0.0.0\", 12345), ServerAddress.parse(\"127.0.0.0:12345\"));\n\n        assertEquals(new ServerAddress(\"::1\"), ServerAddress.parse(\"[::1]\"));\n        assertEquals(new ServerAddress(\"::1\", 0), ServerAddress.parse(\"[::1]:0\"));\n        assertEquals(new ServerAddress(\"::1\", 12345), ServerAddress.parse(\"[::1]:12345\"));\n        assertEquals(new ServerAddress(\"2001:db8::1\"), ServerAddress.parse(\"[2001:db8::1]\"));\n        assertEquals(new ServerAddress(\"2001:db8::1\", 0), ServerAddress.parse(\"[2001:db8::1]:0\"));\n        assertEquals(new ServerAddress(\"2001:db8::1\", 12345), ServerAddress.parse(\"[2001:db8::1]:12345\"));\n\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[]]\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[]:0\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[::1]:\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[::1]|\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[::1]|0\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[::1]:a\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[::1]:65536\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[::1]:-1\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[ ]:-1\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"[-]:-1\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"example.com:\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"example.com:a\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"example.com:65536\"));\n        assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse(\"example.com:-1\"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/StringUtilsTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * @author Glavo\n */\npublic final class StringUtilsTest {\n\n    @Test\n    public void testNormalizeWhitespaces() {\n        assertEquals(\"\", StringUtils.normalizeWhitespaces(\"\"));\n        assertEquals(\"\", StringUtils.normalizeWhitespaces(\" \"));\n        assertEquals(\"\", StringUtils.normalizeWhitespaces(\" \\t\"));\n        assertEquals(\"\", StringUtils.normalizeWhitespaces(\" \\t \"));\n\n        assertEquals(\"abc\", StringUtils.normalizeWhitespaces(\"abc\"));\n        assertEquals(\"abc\", StringUtils.normalizeWhitespaces(\" abc\"));\n        assertEquals(\"abc\", StringUtils.normalizeWhitespaces(\"abc \"));\n        assertEquals(\"abc\", StringUtils.normalizeWhitespaces(\" abc \"));\n        assertEquals(\"abc\", StringUtils.normalizeWhitespaces(\" \\tabc \\t\"));\n\n        assertEquals(\"a bc\", StringUtils.normalizeWhitespaces(\"a bc\"));\n        assertEquals(\"a bc\", StringUtils.normalizeWhitespaces(\"a  bc\"));\n        assertEquals(\"a bc\", StringUtils.normalizeWhitespaces(\"a \\tbc\"));\n        assertEquals(\"a bc\", StringUtils.normalizeWhitespaces(\" a \\tbc \"));\n        assertEquals(\"a b c\", StringUtils.normalizeWhitespaces(\" a\\tb c \"));\n        assertEquals(\"a b c\", StringUtils.normalizeWhitespaces(\" a \\t b c \"));\n        assertEquals(\"a b c\", StringUtils.normalizeWhitespaces(\" a \\t b  c \"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util;\n\nimport org.jackhuang.hmcl.task.Schedulers;\nimport org.jackhuang.hmcl.task.Task;\nimport org.jackhuang.hmcl.task.TaskExecutor;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\n\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class TaskTest {\n    /**\n     * TaskExecutor will not catch error and will be thrown to global handler.\n     */\n    @Test\n    public void expectErrorUncaught() {\n        AtomicReference<Throwable> throwable = new AtomicReference<>();\n        Thread.setDefaultUncaughtExceptionHandler((t, e) -> throwable.set(e));\n        assertFalse(Task.composeAsync(() -> Task.allOf(\n                Task.allOf(Task.runAsync(() -> {\n                    throw new Error();\n                }))\n        )).whenComplete(Assertions::assertNull).test());\n\n        assertInstanceOf(Error.class, throwable.get(), \"Error has not been thrown to uncaught exception handler\");\n    }\n\n    /**\n     *\n     */\n    @Test\n    public void testWhenComplete() {\n        boolean result = Task.supplyAsync(() -> {\n            throw new IllegalStateException();\n        }).whenComplete(exception -> {\n            assertInstanceOf(IllegalStateException.class, exception);\n        }).test();\n\n        assertFalse(result, \"Task should fail at this case\");\n    }\n\n    @Test\n    public void testWithCompose() {\n        AtomicBoolean bool = new AtomicBoolean();\n        boolean success = Task.supplyAsync(() -> {\n            throw new IllegalStateException();\n        }).withRunAsync(() -> {\n            bool.set(true);\n        }).test();\n\n        assertTrue(success, \"Task should success because withRunAsync will ignore previous exception\");\n        assertTrue(bool.get(), \"withRunAsync should be executed\");\n    }\n\n    @Test\n    @EnabledIf(\"org.jackhuang.hmcl.JavaFXLauncher#isStarted\")\n    public void testThenAccept() {\n        AtomicBoolean flag = new AtomicBoolean();\n        Object obj = new Object();\n        boolean result = Task.supplyAsync(() -> obj)\n                .thenAcceptAsync(Schedulers.io(), o -> {\n                    flag.set(true);\n                    assertSame(obj, o);\n                })\n                .test();\n\n        assertTrue(result, \"Task does not succeed\");\n        assertTrue(flag.get(), \"ThenAccept has not been executed\");\n    }\n\n    @Test\n    public void testCancellation() throws InterruptedException {\n        AtomicBoolean flag = new AtomicBoolean(false);\n        CountDownLatch latch = new CountDownLatch(1);\n        Task<?> task = Task.runAsync(() -> {\n            latch.countDown();\n            Thread.sleep(200);\n            // default executor cannot interrupt task.\n            flag.getAndSet(true);\n        }).thenRunAsync(() -> {\n            System.out.println(\"No way!\");\n            Thread.sleep(200);\n            fail(\"Cannot reach here\");\n        });\n        TaskExecutor executor = task.executor();\n        Lang.thread(() -> {\n            try {\n                latch.await();\n                System.out.println(\"Main thread start waiting\");\n                Thread.sleep(100);\n                System.out.println(\"Cancel\");\n                executor.cancel();\n            } catch (InterruptedException e) {\n                fail(e);\n            }\n        });\n        assertFalse(executor.test(), \"Task should fail because we have cancelled it\");\n        Thread.sleep(3000);\n\n        assertInstanceOf(CancellationException.class, executor.getException(), \"CancellationException should not be recorded.\");\n        assertInstanceOf(CancellationException.class, task.getException(), \"CancellationException should not be recorded.\");\n        assertTrue(flag.get(), \"Thread.sleep cannot be interrupted\");\n    }\n\n    @Test\n    public void testCompletableFutureCancellation() throws Throwable {\n        AtomicBoolean flag = new AtomicBoolean(false);\n        CountDownLatch latch = new CountDownLatch(1);\n        CompletableFuture<?> task = CompletableFuture.runAsync(() -> {\n            latch.countDown();\n            System.out.println(\"Sleep\");\n            try {\n                Thread.sleep(200);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n            // default executor cannot interrupt task.\n            flag.getAndSet(true);\n            System.out.println(\"End\");\n        }).thenComposeAsync(non -> {\n            System.out.println(\"compose\");\n            return CompletableFuture.allOf(CompletableFuture.runAsync(() -> {\n                System.out.println(\"No way!\");\n                try {\n                    Thread.sleep(200);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n                fail(\"Cannot reach here\");\n            }));\n        });\n        Lang.thread(() -> {\n            try {\n                latch.await();\n                System.out.println(\"Main thread start waiting\");\n                Thread.sleep(100);\n                System.out.println(\"Cancel\");\n                task.cancel(true);\n            } catch (InterruptedException e) {\n                fail(e);\n            }\n        });\n        System.out.println(\"Start\");\n        try {\n            task.get();\n        } catch (CancellationException e) {\n            System.out.println(\"Successfully cancelled\");\n        }\n        //Assert.assertFalse(\"Task should fail because we have cancelled it\", );\n        Thread.sleep(4000);\n        //Assert.assertNull(\"CancellationException should not be recorded.\", executor.getException());\n        //Assert.assertTrue(\"Thread.sleep cannot be interrupted\", flag.get());\n    }\n\n    public void testRejectedExecutionException() {\n        Schedulers.defaultScheduler();\n        Schedulers.shutdown();\n\n        Task<?> task = Task.runAsync(() -> {\n            Thread.sleep(1000);\n        });\n\n        boolean result = task.test();\n\n        assertFalse(result, \"Task should fail since ExecutorService is shut down and RejectedExecutionException should be thrown\");\n        assertInstanceOf(RejectedExecutionException.class, task.getException(), \"RejectedExecutionException should be recorded\");\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/TokenizerTest.java",
    "content": "package org.jackhuang.hmcl.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic final class TokenizerTest {\n\n    @Test\n    public void textTokenizer() {\n        assertEquals(\n                Arrays.asList(\"C:/Program Files/Bellsoft/JDK-11/bin.java.exe\", \"-version\", \"a.b.c\", \"something\", \"else\"),\n                StringUtils.tokenize(\"\\\"C:/Program Files/Bellsoft/JDK-11/bin.java.exe\\\" -version \\\"a.b.c\\\" something else\")\n        );\n        assertEquals(\n                Arrays.asList(\"AnotherText\", \"something\", \"else\"),\n                StringUtils.tokenize(\"\\\"Another\\\"Text something else\")\n        );\n        assertEquals(\n                Arrays.asList(\"Text\", \"without\", \"quote\"),\n                StringUtils.tokenize(\"Text without quote\")\n        );\n        assertEquals(\n                Arrays.asList(\"Text\", \"with\", \"multiple\", \"spaces\"),\n                StringUtils.tokenize(\"Text  with  multiple  spaces\")\n        );\n        assertEquals(\n                Arrays.asList(\"Text\", \"with\", \"empty\", \"part\", \"\"),\n                StringUtils.tokenize(\"Text with empty part ''\")\n        );\n        assertEquals(\n                Arrays.asList(\"headabc\\n`\\\"$end\"),\n                StringUtils.tokenize(\"head\\\"abc`n```\\\"\\\"$end\")\n        );\n\n        String instName = \"1.20.4\";\n        String instDir = \"C:\\\\Program Files (x86)\\\\Minecraft\\\\\";\n\n        Map<String, String> env = new HashMap<>();\n        env.put(\"INST_NAME\", instName);\n        env.put(\"INST_DIR\", instDir);\n        env.put(\"EMPTY\", \"\");\n\n        assertEquals(\n                Arrays.asList(\"cd\", instDir),\n                StringUtils.tokenize(\"cd $INST_DIR\", env)\n        );\n        assertEquals(\n                Arrays.asList(\"Text\", \"with\", \"empty\", \"part\", \"\"),\n                StringUtils.tokenize(\"Text with empty part $EMPTY\", env)\n        );\n        assertEquals(\n                Arrays.asList(\"head\", \"1.20.4\", \"$UNKNOWN\", instDir, \"\", instDir + instName + \"$UNKNOWN\" + instDir + \"$INST_DIR\\n$UNKNOWN $$\"),\n                StringUtils.tokenize(\"head $INST_NAME $UNKNOWN $INST_DIR $EMPTY $INST_DIR$INST_NAME$UNKNOWN\\\"$INST_DIR`$INST_DIR`n$UNKNOWN $EMPTY$\\\"$\", env)\n        );\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapterTest.java",
    "content": "package org.jackhuang.hmcl.util.gson;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.ZoneOffset;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class InstantTypeAdapterTest {\n\n    @Test\n    public void testDeserialize() {\n        assertEquals(\n                LocalDateTime.of(2017, 6, 8, 4, 26, 33)\n                        .atOffset(ZoneOffset.UTC).toInstant(),\n                InstantTypeAdapter.deserializeToInstant(\"2017-06-08T04:26:33+0000\"));\n\n        assertEquals(\n                LocalDateTime.of(2021, 1, 3, 0, 53, 34)\n                        .atOffset(ZoneOffset.UTC).toInstant(),\n                InstantTypeAdapter.deserializeToInstant(\"2021-01-03T00:53:34+00:00\"));\n\n        assertEquals(\n                LocalDateTime.of(2021, 1, 3, 0, 53, 34)\n                        .atZone(ZoneId.systemDefault()).toInstant(),\n                InstantTypeAdapter.deserializeToInstant(\"2021-01-03T00:53:34\"));\n    }\n\n    @Test\n    public void testSerialize() {\n        assertEquals(\n                \"2024-02-13T15:11:06+08:00\",\n                InstantTypeAdapter.serializeToString(Instant.ofEpochMilli(1707808266154L), ZoneOffset.ofHours(8))\n        );\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/JsonUtilsTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.gson;\n\nimport com.google.gson.reflect.TypeToken;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;\nimport static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @author Glavo\n */\npublic class JsonUtilsTest {\n\n    @Test\n    public void testGetTypeToken() {\n        assertEquals(new TypeToken<List<Object>>(){}, listTypeOf(Object.class));\n        assertEquals(new TypeToken<List<String>>(){}, listTypeOf(String.class));\n        assertEquals(new TypeToken<List<Map<String, Integer>>>(){}, listTypeOf(mapTypeOf(String.class, Integer.class)));\n        assertEquals(new TypeToken<List<Map<String, List<Integer>>>>(){}, listTypeOf(mapTypeOf(String.class, listTypeOf(Integer.class))));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.i18n;\n\nimport com.google.common.jimfs.Configuration;\nimport com.google.common.jimfs.Jimfs;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @author Glavo\n */\npublic final class LocaleUtilsTest {\n    private static void assertCandidateLocales(String languageTag, List<String> candidateLocales) {\n        assertEquals(candidateLocales,\n                LocaleUtils.getCandidateLocales(Locale.forLanguageTag(languageTag))\n                        .stream()\n                        .map(Locale::toLanguageTag)\n                        .toList());\n    }\n\n    private static void assertCandidateLocalesEquals(String l1, String l2) {\n        assertEquals(\n                LocaleUtils.getCandidateLocales(Locale.forLanguageTag(l1)),\n                LocaleUtils.getCandidateLocales(Locale.forLanguageTag(l2))\n        );\n    }\n\n    @Test\n    public void testGetCandidateLocales() {\n        // English\n\n        assertCandidateLocales(\"en\", List.of(\"en-Latn\", \"en\", \"und\"));\n        assertCandidateLocales(\"en-US\", List.of(\"en-Latn-US\", \"en-Latn\", \"en-US\", \"en\", \"und\"));\n        assertCandidateLocalesEquals(\"en\", \"eng\");\n        assertCandidateLocalesEquals(\"en-US\", \"eng-US\");\n        assertCandidateLocalesEquals(\"und\", \"en\");\n\n        // Spanish\n\n        assertCandidateLocales(\"es\", List.of(\"es-Latn\", \"es\", \"und\"));\n        assertCandidateLocalesEquals(\"es\", \"spa\");\n\n        // Japanese\n\n        assertCandidateLocales(\"ja\", List.of(\"ja-Jpan\", \"ja\", \"und\"));\n        assertCandidateLocales(\"ja-JP\", List.of(\"ja-Jpan-JP\", \"ja-Jpan\", \"ja-JP\", \"ja\", \"und\"));\n        assertCandidateLocalesEquals(\"ja\", \"jpn\");\n        assertCandidateLocalesEquals(\"ja-JP\", \"jpn-JP\");\n\n        // Russian\n\n        assertCandidateLocales(\"ru\", List.of(\"ru-Cyrl\", \"ru\", \"und\"));\n        assertCandidateLocalesEquals(\"ru\", \"rus\");\n\n        // Ukrainian\n\n        assertCandidateLocales(\"uk\", List.of(\"uk-Cyrl\", \"uk\", \"und\"));\n        assertCandidateLocalesEquals(\"uk\", \"ukr\");\n\n        // Chinese\n\n        assertCandidateLocales(\"zh\", List.of(\"cmn-Hans\", \"cmn\", \"zh-Hans\", \"zh-CN\", \"zh\", \"und\"));\n        assertCandidateLocales(\"zh-CN\", List.of(\"cmn-Hans-CN\", \"cmn-Hans\", \"cmn-CN\", \"cmn\", \"zh-Hans-CN\", \"zh-Hans\", \"zh-CN\", \"zh\", \"und\"));\n        assertCandidateLocales(\"zh-SG\", List.of(\"cmn-Hans-SG\", \"cmn-Hans\", \"cmn-SG\", \"cmn\", \"zh-Hans-SG\", \"zh-Hans\", \"zh-SG\", \"zh-CN\", \"zh\", \"und\"));\n\n        assertCandidateLocales(\"zh-TW\", List.of(\"cmn-Hant-TW\", \"cmn-Hant\", \"cmn-TW\", \"cmn\", \"zh-Hant-TW\", \"zh-Hant\", \"zh-TW\", \"zh\", \"zh-CN\", \"und\"));\n        assertCandidateLocales(\"zh-HK\", List.of(\"cmn-Hant-HK\", \"cmn-Hant\", \"cmn-HK\", \"cmn\", \"zh-Hant-HK\", \"zh-Hant\", \"zh-HK\", \"zh-TW\", \"zh\", \"zh-CN\", \"und\"));\n        assertCandidateLocales(\"zh-Hant-CN\", List.of(\"cmn-Hant-CN\", \"cmn-Hant\", \"cmn-CN\", \"cmn\", \"zh-Hant-CN\", \"zh-Hant\", \"zh-CN\", \"zh-TW\", \"zh\", \"und\"));\n\n        assertCandidateLocales(\"zh-pinyin\", List.of(\"cmn-Latn-pinyin\", \"cmn-Latn\", \"cmn-pinyin\", \"cmn\", \"zh-Latn-pinyin\", \"zh-Latn\", \"zh-pinyin\", \"zh\", \"zh-CN\", \"und\"));\n\n        assertCandidateLocales(\"lzh\", List.of(\"lzh-Hant\", \"lzh\", \"zh-Hant\", \"zh-TW\", \"zh\", \"zh-CN\", \"und\"));\n        assertCandidateLocales(\"yue\", List.of(\"yue-Hans\", \"yue\", \"zh-Hans\", \"zh-CN\", \"zh\", \"und\"));\n\n        assertCandidateLocalesEquals(\"zh\", \"cmn-Hans\");\n        assertCandidateLocalesEquals(\"zh-CN\", \"cmn-Hans-CN\");\n        assertCandidateLocalesEquals(\"zh-SG\", \"cmn-Hans-SG\");\n        assertCandidateLocalesEquals(\"zh-MY\", \"cmn-Hans-MY\");\n        assertCandidateLocalesEquals(\"zh-TW\", \"cmn-Hant-TW\");\n        assertCandidateLocalesEquals(\"zh-HK\", \"cmn-Hant-HK\");\n        assertCandidateLocalesEquals(\"zh-Hans\", \"cmn-Hans\");\n        assertCandidateLocalesEquals(\"zh-Hant\", \"cmn-Hant\");\n        assertCandidateLocalesEquals(\"zh-Hant-CN\", \"cmn-Hant-CN\");\n        assertCandidateLocalesEquals(\"zh-Hant-SG\", \"cmn-Hant-SG\");\n        assertCandidateLocalesEquals(\"zh-Latn\", \"cmn-Latn\");\n        assertCandidateLocalesEquals(\"zh-pinyin\", \"cmn-Latn-pinyin\");\n        assertCandidateLocalesEquals(\"zho\", \"zh\");\n    }\n\n    @Test\n    public void testIsChinese() {\n        assertTrue(LocaleUtils.isChinese(Locale.CHINESE));\n        assertTrue(LocaleUtils.isChinese(Locale.SIMPLIFIED_CHINESE));\n        assertTrue(LocaleUtils.isChinese(Locale.TRADITIONAL_CHINESE));\n        assertTrue(LocaleUtils.isChinese(LocaleUtils.LOCALE_ZH_HANS));\n        assertTrue(LocaleUtils.isChinese(LocaleUtils.LOCALE_ZH_HANT));\n        assertTrue(LocaleUtils.isChinese(Locale.forLanguageTag(\"lzh\")));\n        assertTrue(LocaleUtils.isChinese(Locale.forLanguageTag(\"cmn\")));\n        assertTrue(LocaleUtils.isChinese(Locale.forLanguageTag(\"cmn-Hans\")));\n        assertTrue(LocaleUtils.isChinese(Locale.forLanguageTag(\"yue\")));\n\n        assertFalse(LocaleUtils.isChinese(Locale.ROOT));\n        assertFalse(LocaleUtils.isChinese(Locale.ENGLISH));\n        assertFalse(LocaleUtils.isChinese(Locale.JAPANESE));\n        assertFalse(LocaleUtils.isChinese(Locale.forLanguageTag(\"es\")));\n        assertFalse(LocaleUtils.isChinese(Locale.forLanguageTag(\"ru\")));\n        assertFalse(LocaleUtils.isChinese(Locale.forLanguageTag(\"uk\")));\n    }\n\n    @Test\n    public void testGetScript() {\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.CHINESE));\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh\")));\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh-Hans\")));\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh-Hans-US\")));\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh-SG\")));\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh-MY\")));\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.forLanguageTag(\"cmn\")));\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.forLanguageTag(\"cmn-Hans\")));\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.forLanguageTag(\"cmn-CN\")));\n        assertEquals(\"Hans\", LocaleUtils.getScript(Locale.forLanguageTag(\"lzh-Hans\")));\n\n        assertEquals(\"Hant\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh-Hant\")));\n        assertEquals(\"Hant\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh-TW\")));\n        assertEquals(\"Hant\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh-HK\")));\n        assertEquals(\"Hant\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh-MO\")));\n        assertEquals(\"Hant\", LocaleUtils.getScript(Locale.forLanguageTag(\"cmn-Hant\")));\n        assertEquals(\"Hant\", LocaleUtils.getScript(Locale.forLanguageTag(\"lzh\")));\n        assertEquals(\"Hant\", LocaleUtils.getScript(Locale.forLanguageTag(\"lzh-Hant\")));\n        assertEquals(\"Hant\", LocaleUtils.getScript(Locale.forLanguageTag(\"lzh-CN\")));\n\n        assertEquals(\"Latn\", LocaleUtils.getScript(Locale.forLanguageTag(\"en\")));\n        assertEquals(\"Latn\", LocaleUtils.getScript(Locale.forLanguageTag(\"zh-pinyin\")));\n        assertEquals(\"Latn\", LocaleUtils.getScript(Locale.forLanguageTag(\"ja-hepburn\")));\n    }\n\n    @Test\n    public void testFindAllLocalizedFiles() throws IOException {\n        try (var testFs = Jimfs.newFileSystem(Configuration.unix())) {\n            Path testDir = testFs.getPath(\"/test-dir\");\n            Files.createDirectories(testDir);\n\n            Files.createFile(testDir.resolve(\"meow.json\"));\n            Files.createFile(testDir.resolve(\"meow_zh.json\"));\n            Files.createFile(testDir.resolve(\"meow_zh_CN.json\"));\n            Files.createFile(testDir.resolve(\"meow_zh_Hans.json\"));\n            Files.createFile(testDir.resolve(\"meow_zh_Hans_CN.json\"));\n            Files.createFile(testDir.resolve(\"meow_en.json\"));\n            Files.createFile(testDir.resolve(\"meow_en.toml\"));\n\n            Files.createFile(testDir.resolve(\"meow_.json\"));\n            Files.createFile(testDir.resolve(\"meowmeow.json\"));\n            Files.createFile(testDir.resolve(\"woem.json\"));\n            Files.createFile(testDir.resolve(\"meow.txt\"));\n            Files.createDirectories(testDir.resolve(\"subdir\"));\n            Files.createDirectories(testDir.resolve(\"meow_en_US.json\"));\n\n            Path notExistsDir = testFs.getPath(\"/not-exists\");\n            Path emptyDir = testFs.getPath(\"/empty\");\n            Files.createDirectories(emptyDir);\n\n            assertEquals(Map.of(), LocaleUtils.findAllLocalizedFiles(emptyDir, \"meow\", \"json\"));\n            assertEquals(Map.of(), LocaleUtils.findAllLocalizedFiles(emptyDir, \"meow\", Set.of(\"json\", \"toml\")));\n            assertEquals(Map.of(), LocaleUtils.findAllLocalizedFiles(notExistsDir, \"meow\", \"json\"));\n            assertEquals(Map.of(), LocaleUtils.findAllLocalizedFiles(notExistsDir, \"meow\", Set.of(\"json\", \"toml\")));\n\n            assertEquals(Map.of(\n                            \"default\", testDir.resolve(\"meow.json\"),\n                            \"zh\", testDir.resolve(\"meow_zh.json\"),\n                            \"zh-CN\", testDir.resolve(\"meow_zh_CN.json\"),\n                            \"zh-Hans\", testDir.resolve(\"meow_zh_Hans.json\"),\n                            \"zh-Hans-CN\", testDir.resolve(\"meow_zh_Hans_CN.json\"),\n                            \"en\", testDir.resolve(\"meow_en.json\")\n                    ),\n                    LocaleUtils.findAllLocalizedFiles(testDir, \"meow\", \"json\"));\n            assertEquals(Map.of(\n                            \"default\", Map.of(\"json\", testDir.resolve(\"meow.json\")),\n                            \"zh\", Map.of(\"json\", testDir.resolve(\"meow_zh.json\")),\n                            \"zh-CN\", Map.of(\"json\", testDir.resolve(\"meow_zh_CN.json\")),\n                            \"zh-Hans\", Map.of(\"json\", testDir.resolve(\"meow_zh_Hans.json\")),\n                            \"zh-Hans-CN\", Map.of(\"json\", testDir.resolve(\"meow_zh_Hans_CN.json\")),\n                            \"en\", Map.of(\n                                    \"json\", testDir.resolve(\"meow_en.json\"),\n                                    \"toml\", testDir.resolve(\"meow_en.toml\")\n                            )\n                    ),\n                    LocaleUtils.findAllLocalizedFiles(testDir, \"meow\", Set.of(\"json\", \"toml\")));\n        }\n    }\n\n    @Test\n    public void testNormalizeLanguage() {\n        assertEquals(\"en\", LocaleUtils.normalizeLanguage(\"\"));\n        assertEquals(\"en\", LocaleUtils.normalizeLanguage(\"eng\"));\n        assertEquals(\"es\", LocaleUtils.normalizeLanguage(\"spa\"));\n        assertEquals(\"ja\", LocaleUtils.normalizeLanguage(\"jpn\"));\n        assertEquals(\"ru\", LocaleUtils.normalizeLanguage(\"rus\"));\n        assertEquals(\"uk\", LocaleUtils.normalizeLanguage(\"ukr\"));\n        assertEquals(\"zh\", LocaleUtils.normalizeLanguage(\"zho\"));\n        assertEquals(\"zu\", LocaleUtils.normalizeLanguage(\"zul\"));\n        assertEquals(\"en\", LocaleUtils.normalizeLanguage(\"\"));\n        assertEquals(\"cmn\", LocaleUtils.normalizeLanguage(\"cmn\"));\n    }\n\n    @Test\n    public void testGetParentLanguage() {\n        assertEquals(\"zh\", LocaleUtils.getParentLanguage(\"cmn\"));\n        assertEquals(\"zh\", LocaleUtils.getParentLanguage(\"yue\"));\n        assertEquals(\"zh\", LocaleUtils.getParentLanguage(\"lzh\"));\n\n        assertNull(LocaleUtils.getParentLanguage(\"\"));\n        assertNull(LocaleUtils.getParentLanguage(\"en\"));\n        assertNull(LocaleUtils.getParentLanguage(\"eng\"));\n        assertNull(LocaleUtils.getParentLanguage(\"zh\"));\n        assertNull(LocaleUtils.getParentLanguage(\"zho\"));\n    }\n\n    @Test\n    public void testGetTextDirection() {\n        assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"en\")));\n        assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"en-US\")));\n        assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"zh\")));\n        assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"zh-Hans\")));\n        assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"zh-CN\")));\n\n        assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"en-Qabs\")));\n        assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"ar\")));\n        assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"ara\")));\n        assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"he\")));\n        assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag(\"heb\")));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/io/CSVTableTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * @author Glavo\n */\npublic final class CSVTableTest {\n\n    @Test\n    public void testCreate() {\n        CSVTable table = new CSVTable();\n\n        assertEquals(0, table.getColumnCount());\n        assertEquals(0, table.getRowCount());\n\n        table.set(0, 0, \"test1\");\n        assertEquals(1, table.getColumnCount());\n        assertEquals(1, table.getRowCount());\n\n        table.set(4, 0, \"test2\");\n        assertEquals(5, table.getColumnCount());\n        assertEquals(1, table.getRowCount());\n\n        table.set(2, 1, \"test3\");\n        assertEquals(5, table.getColumnCount());\n        assertEquals(2, table.getRowCount());\n\n        assertEquals(\"test1\", table.get(0, 0));\n        assertEquals(\"\", table.get(1, 0));\n        assertEquals(\"\", table.get(0, 1));\n        assertEquals(\"test2\", table.get(4, 0));\n        assertEquals(\"test3\", table.get(2, 1));\n    }\n\n    @Test\n    public void testToString() {\n        CSVTable table = new CSVTable();\n        table.set(0, 0, \"a\");\n        table.set(1, 0, \"b\");\n        table.set(3, 0, \"c\");\n        table.set(0, 2, \"a,b\");\n        table.set(1, 2, \"c\");\n        table.set(3, 2, \"d\\\"e\\n\");\n\n        assertEquals(\"a,b,,c\\n,,,\\n\\\"a,b\\\",c,,\\\"d\\\\\\\"e\\\\n\\\"\\n\", table.toString());\n\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/io/CompressingUtilsTest.java",
    "content": "package org.jackhuang.hmcl.util.io;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.stream.Stream;\n\nimport static org.jackhuang.hmcl.util.Pair.pair;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * @author Glavo\n */\npublic final class CompressingUtilsTest {\n\n    public static Stream<Arguments> arguments() {\n        return Stream.of(\n                pair(\"utf-8.zip\", StandardCharsets.UTF_8),\n                pair(\"gbk.zip\", Charset.forName(\"GB18030\"))\n        ).map(pair -> {\n            try {\n                return Arguments.of(Paths.get(CompressingUtilsTest.class.getResource(\"/zip/\" + pair.getKey()).toURI()), pair.getValue());\n            } catch (URISyntaxException e) {\n                throw new AssertionError(e);\n            }\n        });\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    public void testFindSuitableEncoding(Path path, Charset charset) throws IOException {\n        assertEquals(charset, CompressingUtils.findSuitableEncoding(path));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/io/FileUtilsTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport org.jackhuang.hmcl.util.platform.OperatingSystem;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/// @author Glavo\npublic class FileUtilsTest {\n\n    @ParameterizedTest\n    @EnumSource(OperatingSystem.class)\n    public void testIsNameValid(OperatingSystem os) {\n        assertTrue(FileUtils.isNameValid(os, \"example\"));\n        assertTrue(FileUtils.isNameValid(os, \"example.zip\"));\n        assertTrue(FileUtils.isNameValid(os, \"example.tar.gz\"));\n        assertTrue(FileUtils.isNameValid(os, \"\\uD83D\\uDE00\"));\n        assertTrue(FileUtils.isNameValid(os, \"a\\uD83D\\uDE00b\"));\n\n        assertFalse(FileUtils.isNameValid(os, \"\"));\n        assertFalse(FileUtils.isNameValid(os, \".\"));\n        assertFalse(FileUtils.isNameValid(os, \"..\"));\n        assertFalse(FileUtils.isNameValid(os, \"exam\\0ple\"));\n        assertFalse(FileUtils.isNameValid(os, \"example/0\"));\n\n        // Test for invalid surrogate pair\n        assertFalse(FileUtils.isNameValid(os, \"\\uD83D\"));\n        assertFalse(FileUtils.isNameValid(os, \"\\uDE00\"));\n        assertFalse(FileUtils.isNameValid(os, \"\\uDE00\\uD83D\"));\n        assertFalse(FileUtils.isNameValid(os, \"\\uD83D\\uD83D\"));\n        assertFalse(FileUtils.isNameValid(os, \"a\\uD83D\"));\n        assertFalse(FileUtils.isNameValid(os, \"a\\uDE00\"));\n        assertFalse(FileUtils.isNameValid(os, \"a\\uDE00\\uD83D\"));\n        assertFalse(FileUtils.isNameValid(os, \"a\\uD83Db\"));\n        assertFalse(FileUtils.isNameValid(os, \"a\\uDE00b\"));\n        assertFalse(FileUtils.isNameValid(os, \"a\\uDE00\\uD83Db\"));\n        assertFalse(FileUtils.isNameValid(os, \"f:oo\"));\n\n        // Platform-specific tests\n        boolean isWindows = os == OperatingSystem.WINDOWS;\n        boolean isNotWindows = !isWindows;\n        assertEquals(isNotWindows, FileUtils.isNameValid(os, \"com1\"));\n        assertEquals(isNotWindows, FileUtils.isNameValid(os, \"com1.txt\"));\n        assertEquals(isNotWindows, FileUtils.isNameValid(os, \"foo.\"));\n        assertEquals(isNotWindows, FileUtils.isNameValid(os, \"foo \"));\n        assertEquals(isNotWindows, FileUtils.isNameValid(os, \"f<oo\"));\n        assertEquals(isNotWindows, FileUtils.isNameValid(os, \"f>oo\"));\n        assertEquals(isNotWindows, FileUtils.isNameValid(os, \"f?oo\"));\n        assertEquals(isNotWindows, FileUtils.isNameValid(os, \"f*oo\"));\n        assertEquals(isNotWindows, FileUtils.isNameValid(os, \"f\\\\oo\"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/io/NetworkUtilsTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.io;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.net.URI;\n\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.jackhuang.hmcl.util.io.NetworkUtils.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @author Glavo\n */\npublic class NetworkUtilsTest {\n\n    @Test\n    public void testIsLoopbackAddress() {\n        assertTrue(isLoopbackAddress(URI.create(\"https://127.0.0.1/test\")));\n        assertTrue(isLoopbackAddress(URI.create(\"https://127.0.0.1:8080/test\")));\n        assertTrue(isLoopbackAddress(URI.create(\"https://localhost/test\")));\n        assertTrue(isLoopbackAddress(URI.create(\"https://localhost:8080/test\")));\n        assertTrue(isLoopbackAddress(URI.create(\"https://[::1]/test\")));\n        assertTrue(isLoopbackAddress(URI.create(\"https://[::1]:8080/test\")));\n\n        assertFalse(isLoopbackAddress(URI.create(\"https://www.example.com/test\")));\n        assertFalse(isLoopbackAddress(URI.create(\"https://www.example.com:8080/test\")));\n    }\n\n    @Test\n    public void testEncodeLocation() {\n        assertEquals(\"https://github.com\", encodeLocation(\"https://github.com\"));\n        assertEquals(\"https://github.com/HMCL-dev/HMCL/commits?author=Glavo\", encodeLocation(\"https://github.com/HMCL-dev/HMCL/commits?author=Glavo\"));\n        assertEquals(\"https://www.example.com/file%20with%20space\", encodeLocation(\"https://www.example.com/file with space\"));\n        assertEquals(\"https://www.example.com/file%20with%20space\", encodeLocation(\"https://www.example.com/file%20with%20space\"));\n        assertEquals(\"https://www.example.com/%5Bfile%5D\", encodeLocation(\"https://www.example.com/[file]\"));\n        assertEquals(\"https://www.example.com/%7Bfile%7D\", encodeLocation(\"https://www.example.com/{file}\"));\n        assertEquals(\"https://www.example.com/%E6%B5%8B%E8%AF%95\", encodeLocation(\"https://www.example.com/测试\"));\n        assertEquals(\"https://www.example.com/%F0%9F%98%87\", encodeLocation(\"https://www.example.com/\\uD83D\\uDE07\"));\n        assertEquals(\"https://www.example.com/test?a=10+20\", encodeLocation(\"https://www.example.com/test?a=10 20\"));\n        assertEquals(\"https://www.example.com/test?a=10+20&b=[30]&c={40}\", encodeLocation(\"https://www.example.com/test?a=10 20&b=[30]&c={40}\"));\n        assertEquals(\"https://www.example.com/%E6%B5%8B%E8%AF%95?a=10+20\", encodeLocation(\"https://www.example.com/测试?a=10 20\"));\n\n        // Invalid surrogate pair\n        assertEquals(\"https://www.example.com/%EF%BF%BD\", encodeLocation(\"https://www.example.com/\\uD83D\"));\n        assertEquals(\"https://www.example.com/%EF%BF%BD\", encodeLocation(\"https://www.example.com/\\uDE07\"));\n        assertEquals(\"https://www.example.com/%EF%BF%BDtest\", encodeLocation(\"https://www.example.com/\\uD83Dtest\"));\n        assertEquals(\"https://www.example.com/%EF%BF%BDtest\", encodeLocation(\"https://www.example.com/\\uDE07test\"));\n    }\n\n    @Test\n    public void testGetEncodingFromUrl() {\n        assertEquals(UTF_8, getCharsetFromContentType(null));\n        assertEquals(UTF_8, getCharsetFromContentType(\"\"));\n        assertEquals(UTF_8, getCharsetFromContentType(\"text/html\"));\n        assertEquals(UTF_8, getCharsetFromContentType(\"text/html; charset=utf-8\"));\n        assertEquals(US_ASCII, getCharsetFromContentType(\"text/html; charset=ascii\"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/javafx/MappedObservableListTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.javafx;\n\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.collections.ObservableListBase;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.*;\nimport java.util.function.Function;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/// @author Glavo\npublic class MappedObservableListTest {\n\n    @Test\n    public void testInitialMapping() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2, 3, 4, 5);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        assertEquals(5, mapped.size());\n        assertEquals(\"Item-1\", mapped.get(0));\n        assertEquals(\"Item-2\", mapped.get(1));\n        assertEquals(\"Item-3\", mapped.get(2));\n        assertEquals(\"Item-4\", mapped.get(3));\n        assertEquals(\"Item-5\", mapped.get(4));\n    }\n\n    @Test\n    public void testAdd() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2, 3);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        source.add(4);\n        assertEquals(4, mapped.size());\n        assertEquals(\"Item-4\", mapped.get(3));\n\n        source.add(1, 10);\n        assertEquals(5, mapped.size());\n        assertEquals(\"Item-10\", mapped.get(1));\n        assertEquals(\"Item-2\", mapped.get(2));\n    }\n\n    @Test\n    public void testRemove() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2, 3, 4, 5);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        source.remove(2);\n        assertEquals(4, mapped.size());\n        assertEquals(\"Item-1\", mapped.get(0));\n        assertEquals(\"Item-2\", mapped.get(1));\n        assertEquals(\"Item-4\", mapped.get(2));\n        assertEquals(\"Item-5\", mapped.get(3));\n\n        source.remove(Integer.valueOf(1));\n        assertEquals(3, mapped.size());\n        assertEquals(\"Item-2\", mapped.get(0));\n        assertEquals(\"Item-4\", mapped.get(1));\n        assertEquals(\"Item-5\", mapped.get(2));\n\n        source.remove(1, 3);\n        assertEquals(1, mapped.size());\n        assertEquals(\"Item-2\", mapped.get(0));\n    }\n\n    @Test\n    public void testSet() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2, 3, 4, 5);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        source.set(2, 10);\n        assertEquals(5, mapped.size());\n\n        // Verify the actual values\n        assertEquals(\"Item-1\", mapped.get(0));\n        assertEquals(\"Item-2\", mapped.get(1));\n        assertEquals(\"Item-10\", mapped.get(2));\n        assertEquals(\"Item-4\", mapped.get(3));\n        assertEquals(\"Item-5\", mapped.get(4));\n    }\n\n    @Test\n    public void testSort() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(5, 3, 1, 4, 2);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        FXCollections.sort(source);\n\n        assertEquals(5, mapped.size());\n        assertEquals(\"Item-1\", mapped.get(0));\n        assertEquals(\"Item-2\", mapped.get(1));\n        assertEquals(\"Item-3\", mapped.get(2));\n        assertEquals(\"Item-4\", mapped.get(3));\n        assertEquals(\"Item-5\", mapped.get(4));\n    }\n\n    @Test\n    public void testClear() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2, 3, 4, 5);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        source.clear();\n\n        assertEquals(0, mapped.size());\n        assertTrue(mapped.isEmpty());\n        assertThrows(IndexOutOfBoundsException.class, () -> mapped.get(0));\n    }\n\n    @Test\n    public void testAddAll() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        source.addAll(3, 4, 5);\n\n        assertEquals(5, mapped.size());\n        assertEquals(\"Item-3\", mapped.get(2));\n        assertEquals(\"Item-4\", mapped.get(3));\n        assertEquals(\"Item-5\", mapped.get(4));\n    }\n\n    @Test\n    public void testRemoveAll() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2, 3, 4, 5);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        source.removeAll(2, 4);\n\n        assertEquals(3, mapped.size());\n        assertEquals(\"Item-1\", mapped.get(0));\n        assertEquals(\"Item-3\", mapped.get(1));\n        assertEquals(\"Item-5\", mapped.get(2));\n    }\n\n    @Test\n    public void testSetAll() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2, 3);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        source.setAll(10, 20, 30, 40);\n\n        assertEquals(4, mapped.size());\n        assertEquals(\"Item-10\", mapped.get(0));\n        assertEquals(\"Item-20\", mapped.get(1));\n        assertEquals(\"Item-30\", mapped.get(2));\n        assertEquals(\"Item-40\", mapped.get(3));\n    }\n\n    @Test\n    public void testGetSourceIndex() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2, 3, 4, 5);\n        MappedObservableList<String, Integer> mapped = new MappedObservableList<>(source, i -> \"Item-\" + i);\n\n        assertEquals(0, mapped.getSourceIndex(0));\n        assertEquals(2, mapped.getSourceIndex(2));\n        assertEquals(4, mapped.getSourceIndex(4));\n\n        assertThrows(IndexOutOfBoundsException.class, () -> mapped.getSourceIndex(-1));\n        assertThrows(IndexOutOfBoundsException.class, () -> mapped.getSourceIndex(5));\n    }\n\n    @Test\n    public void testGetViewIndex() {\n        ObservableList<Integer> source = FXCollections.observableArrayList(1, 2, 3, 4, 5);\n        MappedObservableList<String, Integer> mapped = new MappedObservableList<>(source, i -> \"Item-\" + i);\n\n        assertEquals(0, mapped.getViewIndex(0));\n        assertEquals(2, mapped.getViewIndex(2));\n        assertEquals(4, mapped.getViewIndex(4));\n\n        assertThrows(IndexOutOfBoundsException.class, () -> mapped.getViewIndex(-1));\n        assertThrows(IndexOutOfBoundsException.class, () -> mapped.getViewIndex(5));\n    }\n\n    @Test\n    public void testComplexOperations() {\n        ObservableList<Integer> source = FXCollections.observableArrayList();\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        // Start with empty\n        assertEquals(0, mapped.size());\n\n        // Add some elements\n        source.addAll(1, 2, 3, 4, 5);\n        assertEquals(5, mapped.size());\n\n        // Remove middle element\n        source.remove(2);\n        assertEquals(4, mapped.size());\n        assertEquals(\"Item-4\", mapped.get(2));\n\n        // Sort\n        FXCollections.sort(source, Collections.reverseOrder());\n        assertEquals(\"Item-5\", mapped.get(0));\n        assertEquals(\"Item-4\", mapped.get(1));\n        assertEquals(\"Item-2\", mapped.get(2));\n        assertEquals(\"Item-1\", mapped.get(3));\n\n        // Add at specific position\n        source.add(2, 3);\n        assertEquals(5, mapped.size());\n        assertEquals(\"Item-3\", mapped.get(2));\n    }\n\n    /// Test for [javafx.collections.ListChangeListener.Change#wasUpdated()].\n    @Test\n    public void testUpdate() {\n        class TestUpdateList<T> extends ObservableListBase<T> {\n            private final List<T> backingList;\n\n            @SafeVarargs\n            public TestUpdateList(T... items) {\n                this.backingList = Arrays.asList(items);\n            }\n\n            @Override\n            public int size() {\n                return backingList.size();\n            }\n\n            @Override\n            public T get(int index) {\n                return backingList.get(index);\n            }\n\n            public void updateItem(int beginIndex, int endIndex, Function<T, T> mapper) {\n                Objects.checkFromToIndex(beginIndex, endIndex, size());\n                beginChange();\n                for (int i = beginIndex; i < endIndex; i++) {\n                    backingList.set(i, mapper.apply(backingList.get(i)));\n                    nextUpdate(i);\n                }\n                endChange();\n            }\n        }\n\n        TestUpdateList<Integer> source = new TestUpdateList<>(1, 2, 3, 4, 5);\n        ObservableList<String> mapped = MappedObservableList.create(source, i -> \"Item-\" + i);\n\n        source.updateItem(2, 4, i -> i * 10);\n        assertEquals(5, mapped.size());\n        assertEquals(\"Item-1\", mapped.get(0));\n        assertEquals(\"Item-2\", mapped.get(1));\n        assertEquals(\"Item-30\", mapped.get(2));\n        assertEquals(\"Item-40\", mapped.get(3));\n        assertEquals(\"Item-5\", mapped.get(4));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/JavaRuntimeTest.java",
    "content": "package org.jackhuang.hmcl.util.platform;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.jackhuang.hmcl.java.JavaInfo.parseVersion;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @author Glavo\n */\npublic final class JavaRuntimeTest {\n    @Test\n    public void testParseVersion() {\n        assertEquals(8, parseVersion(\"1.8.0_302\"));\n        assertEquals(8, parseVersion(\"1.8-internal\"));\n        assertEquals(8, parseVersion(\"8.0\"));\n        assertEquals(11, parseVersion(\"11\"));\n        assertEquals(11, parseVersion(\"11.0.12\"));\n        assertEquals(11, parseVersion(\"11-internal\"));\n        assertEquals(11, parseVersion(\"11+abc\"));\n\n        assertEquals(-1, parseVersion(\"abc\"));\n        assertEquals(-1, parseVersion(\"1.\"));\n        assertEquals(-1, parseVersion(\"1.-internal\"));\n        assertEquals(-1, parseVersion(\"\"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/hardware/CentralProcessorTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.hardware;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.jackhuang.hmcl.util.platform.hardware.CentralProcessor.cleanName;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * @author Glavo\n */\npublic final class CentralProcessorTest {\n\n    @Test\n    public void testCleanName() {\n        assertEquals(\"Intel Core i7-12700K\", cleanName(\"12th Gen Intel(R) Core(TM) i7-12700K\"));\n        assertEquals(\"Intel Core Ultra 9 285K\", cleanName(\"Intel(R) Core(TM) Ultra 9 285K\"));\n        assertEquals(\"Intel Xeon Platinum 8380\", cleanName(\"Intel(R) Xeon(R) Platinum 8380 CPU @ 2.30GHz\"));\n        assertEquals(\"Intel Xeon E5-2660 v2\", cleanName(\"Intel(R) Xeon(R) CPU E5-2660 v2 @ 2.20GHz\"));\n        assertEquals(\"Intel Xeon Phi 7250\", cleanName(\"Intel(R) Xeon Phi(TM) CPU 7250 @ 1.40GHz\"));\n        assertEquals(\"Intel Celeron N5105\", cleanName(\"Intel(R) Celeron(R) N5105 @ 2.00GHz\"));\n        assertEquals(\"Intel Pentium Silver J5005\", cleanName(\"Intel(R) Pentium(R) Silver J5005 CPU @ 1.50GHz\"));\n        assertEquals(\"Intel Atom E3940\", cleanName(\"Intel(R) Atom(TM) Processor E3940 @ 1.60GHz\"));\n        assertEquals(\"Intel Core i7 X 990\", cleanName(\"Intel(R) Core(TM) i7 CPU       X 990  @ 3.47GHz\"));\n        assertEquals(\"Intel Core 2 Duo T7500\", cleanName(\"Intel(R) Core(TM)2 Duo CPU     T7500  @ 2.20GHz\"));\n        assertEquals(\"Intel Core 2 Quad Q9500\", cleanName(\"Intel(R) Core(TM)2 Quad CPU    Q9500  @ 2.83GHz\"));\n\n        assertEquals(\"AMD Ryzen 7 7840HS\", cleanName(\"AMD Ryzen 7 7840HS w/ Radeon 780M Graphics\"));\n        assertEquals(\"AMD Ryzen 7 6800U\", cleanName(\"AMD Ryzen 7 6800U with Radeon Graphics\"));\n        assertEquals(\"AMD Ryzen 7 5800X\", cleanName(\"AMD Ryzen 7 5800X 8-Core Processor\"));\n        assertEquals(\"AMD Ryzen 5 2400G\", cleanName(\"AMD Ryzen 5 2400G with Radeon Vega Graphics\"));\n        assertEquals(\"AMD EPYC 7713\", cleanName(\"AMD EPYC 7713 64-Core Processor\"));\n        assertEquals(\"AMD Ryzen Threadripper 3960X\", cleanName(\"AMD Ryzen Threadripper 3960X 24-Core Processor\"));\n        assertEquals(\"AMD Ryzen Threadripper PRO 5995WX\", cleanName(\"AMD Ryzen Threadripper PRO 5995WX 64-Cores\"));\n        assertEquals(\"AMD Ryzen Embedded V2748\", cleanName(\"AMD Ryzen Embedded V2748 with Radeon Graphics\"));\n        assertEquals(\"AMD A8-7410\", cleanName(\"AMD A8-7410 APU with AMD Radeon R5 Graphics\"));\n        assertEquals(\"AMD FX-8350\", cleanName(\"AMD FX(tm)-8350 Eight-Core Processor\"));\n        assertEquals(\"AMD Phenom II X6 1055T\", cleanName(\"AMD Phenom(tm) II X6 1055T Processor\"));\n        assertEquals(\"AMD Athlon 5350\", cleanName(\"AMD Athlon(tm) 5350 APU with Radeon(tm) R3\"));\n\n        assertEquals(\"Qualcomm Snapdragon X Elite X1E78100\", cleanName(\"Snapdragon(R) X Elite - X1E78100 - Qualcomm(R) Oryon(TM) CPU\"));\n        assertEquals(\"Qualcomm Snapdragon 850\", cleanName(\"Snapdragon (TM) 850 @ 2.96 GHz\"));\n\n        assertEquals(\"Hygon C86 7285\", cleanName(\"Hygon C86 7285 32-core Processor\"));\n        assertEquals(\"Hygon C86 3250\", cleanName(\"Hygon C86 3250  8-core Processor\"));\n\n        assertEquals(\"ZHAOXIN KaiXian KX-6640MA\", cleanName(\"ZHAOXIN KaiXian KX-6640MA@2.2+GHz\"));\n        assertEquals(\"ZHAOXIN KaiXian KX-U6780A\", cleanName(\"ZHAOXIN KaiXian KX-U6780A@2.7GHz\"));\n        assertEquals(\"ZHAOXIN KaiSheng KH-40000/16\", cleanName(\"ZHAOXIN KaiSheng KH-40000/16@2.2GHz\"));\n        assertEquals(\"ZHAOXIN KaiSheng KH-37800D\", cleanName(\"ZHAOXIN KaiSheng KH-37800D@2.7GHz\"));\n\n        assertEquals(\"Loongson-3A3000\", cleanName(\"Loongson-3A R3 (Loongson-3A3000) @ 1400MHz\"));\n        assertEquals(\"Loongson-3B4000\", cleanName(\"Loongson-3A R4 (Loongson-3B4000) @ 1800MHz\"));\n        assertEquals(\"Loongson-3A6000\", cleanName(\"Loongson-3A6000\"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCardTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.hardware;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.jackhuang.hmcl.util.platform.hardware.GraphicsCard.cleanName;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * @author Glavo\n */\npublic final class GraphicsCardTest {\n\n    @Test\n    public void testCleanName() {\n        assertEquals(\"Intel UHD Graphics 770\", cleanName(\"Intel(R) UHD Graphics 770\"));\n\n        assertEquals(\"Qualcomm Adreno 630\", cleanName(\"Qualcomm(R) Adreno(TM) 630 GPU\"));\n        assertEquals(\"Qualcomm Adreno Graphics\", cleanName(\"Snapdragon X Elite - X1E78100 - Qualcomm Adreno\"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WinRegTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\nimport com.sun.jna.platform.win32.Advapi32Util;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\n\nimport java.util.Collections;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @author Glavo\n */\n@EnabledIf(\"isEnabled\")\npublic final class WinRegTest {\n    public static boolean isEnabled() {\n        return WinReg.INSTANCE != null;\n    }\n\n    private static final com.sun.jna.platform.win32.WinReg.HKEY ROOT_KEY = com.sun.jna.platform.win32.WinReg.HKEY_CURRENT_USER;\n    private static final String KEY_BASE = \"Software\\\\JavaSoft\\\\Prefs\\\\hmcl\\\\test\";\n    private static String key;\n\n    private static final String[] SUBKEYS = {\n            \"Sub0\", \"Sub1\", \"Sub2\", \"Sub3\"\n    };\n\n    private static final byte[] TEST_DATA = new byte[128];\n    private static final String[] TEST_STRING_ARRAY = {\n            \"str0\", \"str1\", \"str2\"\n    };\n\n    static {\n        for (int i = 0; i < TEST_DATA.length; i++) {\n            TEST_DATA[i] = (byte) i;\n        }\n    }\n\n    @BeforeAll\n    public static void setup() {\n        key = KEY_BASE + \"\\\\\" + UUID.randomUUID();\n        if (!Advapi32Util.registryCreateKey(ROOT_KEY, key))\n            throw new AssertionError(\"Failed to create key\");\n\n        Advapi32Util.registrySetBinaryValue(ROOT_KEY, key, \"BINARY\", TEST_DATA);\n        Advapi32Util.registrySetStringValue(ROOT_KEY, key, \"SZ\", \"Hello World!\");\n        Advapi32Util.registrySetIntValue(ROOT_KEY, key, \"DWORD\", 0xCAFEBABE);\n        Advapi32Util.registrySetLongValue(ROOT_KEY, key, \"QWORD\", 0xCAFEBABEL);\n        Advapi32Util.registrySetStringArray(ROOT_KEY, key, \"REG_MULTI_SZ\", TEST_STRING_ARRAY);\n        Advapi32Util.registrySetStringArray(ROOT_KEY, key, \"REG_MULTI_SZ_EMPTY\", new String[0]);\n\n        for (String subkey : SUBKEYS) {\n            if (!Advapi32Util.registryCreateKey(ROOT_KEY, key, subkey))\n                throw new AssertionError(\"Failed to create key\");\n        }\n    }\n\n    @AfterAll\n    public static void cleanUp() {\n        if (key != null) {\n            if (!Advapi32Util.registryKeyExists(ROOT_KEY, key))\n                return;\n\n            for (String subKey : Advapi32Util.registryGetKeys(ROOT_KEY, key))\n                Advapi32Util.registryDeleteKey(ROOT_KEY, key, subKey);\n\n            Advapi32Util.registryDeleteKey(ROOT_KEY, key);\n        }\n    }\n\n    @Test\n    public void testQueryValue() {\n        WinReg.HKEY hkey = WinReg.HKEY.HKEY_CURRENT_USER;\n        WinReg reg = WinReg.INSTANCE;\n\n        assertArrayEquals(TEST_DATA, (byte[]) reg.queryValue(hkey, key, \"BINARY\"));\n        assertEquals(\"Hello World!\", reg.queryValue(hkey, key, \"SZ\"));\n        assertEquals(0xCAFEBABE, reg.queryValue(hkey, key, \"DWORD\"));\n        assertEquals(0xCAFEBABEL, reg.queryValue(hkey, key, \"QWORD\"));\n        assertArrayEquals(TEST_STRING_ARRAY, (Object[]) reg.queryValue(hkey, key, \"REG_MULTI_SZ\"));\n        assertArrayEquals(new String[0], (Object[]) reg.queryValue(hkey, key, \"REG_MULTI_SZ_EMPTY\"));\n\n        assertNull(reg.queryValue(hkey, key, \"UNKNOWN\"));\n        assertNull(reg.queryValue(hkey, KEY_BASE + \"\\\\\" + \"NOT_EXIST\", \"UNKNOWN\"));\n    }\n\n    @Test\n    public void testQuerySubKeys() {\n        WinReg.HKEY hkey = WinReg.HKEY.HKEY_CURRENT_USER;\n        WinReg reg = WinReg.INSTANCE;\n\n        assertEquals(Stream.of(SUBKEYS).map(it -> key + \"\\\\\" + it).collect(Collectors.toList()),\n                reg.querySubKeys(hkey, key).stream().sorted().collect(Collectors.toList()));\n        for (String subkey : SUBKEYS) {\n            assertEquals(Collections.emptyList(), reg.querySubKeys(hkey, key + \"\\\\\" + subkey));\n        }\n        assertEquals(Collections.emptyList(), reg.querySubKeys(hkey, key + \"\\\\NOT_EXIST\"));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WindowsVersionTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.platform.windows;\n\n/**\n * @author Glavo\n */\npublic final class WindowsVersionTest {\n\n\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/skin/NormalizedSkinTest.java",
    "content": "package org.jackhuang.hmcl.util.skin;\n\nimport javafx.scene.image.Image;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\n\nimport java.nio.file.Paths;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class NormalizedSkinTest {\n    private static NormalizedSkin getSkin(String name, boolean slim) throws InvalidSkinException {\n        String path = Paths.get(String.format(\"../HMCLCore/src/main/resources/assets/img/skin/%s/%s.png\", slim ? \"slim\" : \"wide\", name)).normalize().toAbsolutePath().toUri().toString();\n        return new NormalizedSkin(new Image(path));\n    }\n\n    @Test\n    @EnabledIf(\"org.jackhuang.hmcl.JavaFXLauncher#isStarted\")\n    public void testIsSlim() throws Exception {\n        String[] names = {\"alex\", \"ari\", \"efe\", \"kai\", \"makena\", \"noor\", \"steve\", \"sunny\", \"zuri\"};\n\n        for (String skin : names) {\n            assertTrue(getSkin(skin, true).isSlim());\n            assertFalse(getSkin(skin, false).isSlim());\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/tree/ZipFileTreeTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.tree;\n\nimport kala.compress.archivers.zip.ZipArchiveReader;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @author Glavo\n */\npublic final class ZipFileTreeTest {\n    private static Path getTestFile(String name) {\n        try {\n            return Path.of(ZipFileTreeTest.class.getResource(\"/zip/\" + name).toURI());\n        } catch (URISyntaxException | NullPointerException e) {\n            throw new AssertionError(\"Resource not found: \" + name, e);\n        }\n    }\n\n    @Test\n    public void testClose() throws IOException {\n        Path testFile = getTestFile(\"utf-8.zip\");\n\n        try (var channel = FileChannel.open(testFile, StandardOpenOption.READ)) {\n            var reader = new ZipArchiveReader(channel);\n\n            try (var ignored = new ZipFileTree(reader, false)) {\n            }\n\n            assertTrue(channel.isOpen());\n\n            try (var ignored = new ZipFileTree(reader)) {\n            }\n\n            assertFalse(channel.isOpen());\n        }\n    }\n\n    @Test\n    public void test() throws IOException {\n        Path testFile = getTestFile(\"utf-8.zip\");\n\n        try (var tree = new ZipFileTree(new ZipArchiveReader(testFile))) {\n            var root = tree.getRoot();\n            assertEquals(2, root.getFiles().size());\n            assertEquals(0, root.getSubDirs().size());\n\n            assertEquals(\"test.txt\", root.getFiles().get(\"test.txt\").getName());\n            assertEquals(\"中文.txt\", root.getFiles().get(\"中文.txt\").getName());\n            assertNull(root.getFiles().get(\"other.txt\"));\n        }\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/GameVersionNumberTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.versioning;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.UncheckedIOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.function.Supplier;\n\nimport static org.jackhuang.hmcl.util.versioning.GameVersionNumber.asGameVersion;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * @author Glavo\n */\npublic final class GameVersionNumberTest {\n\n    //region Helpers\n\n    private static List<String> readVersions() {\n        List<String> versions = new ArrayList<>();\n\n        try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream(\"/assets/game/versions.txt\"), StandardCharsets.UTF_8))) {\n            for (String line; (line = reader.readLine()) != null && !line.isEmpty(); ) {\n                versions.add(line);\n            }\n        } catch (IOException e) {\n            throw new UncheckedIOException(e);\n        }\n\n        return versions;\n    }\n\n    private static Supplier<String> errorMessage(GameVersionNumber version1, GameVersionNumber version2) {\n        return () -> \"version1=%s, version2=%s\".formatted(version1.toDebugString(), version2.toDebugString());\n    }\n\n    private static void assertGameVersionEquals(String version) {\n        assertGameVersionEquals(version, version);\n    }\n\n    private static void assertGameVersionEquals(String version1, String version2) {\n        GameVersionNumber gameVersion1 = asGameVersion(version1);\n        GameVersionNumber gameVersion2 = asGameVersion(version2);\n        assertEquals(0, gameVersion1.compareTo(gameVersion2), errorMessage(gameVersion1, gameVersion2));\n        assertEquals(0, gameVersion2.compareTo(gameVersion1), errorMessage(gameVersion1, gameVersion2));\n        assertEquals(gameVersion1, gameVersion2, errorMessage(gameVersion1, gameVersion2));\n        assertEquals(gameVersion2, gameVersion1, errorMessage(gameVersion1, gameVersion2));\n        assertEquals(gameVersion1.hashCode(), gameVersion2.hashCode(), errorMessage(gameVersion1, gameVersion2));\n    }\n\n    private static void assertOrder(String... versions) {\n        var gameVersionNumbers = new GameVersionNumber[versions.length];\n        for (int i = 0; i < versions.length; i++) {\n            gameVersionNumbers[i] = asGameVersion(versions[i]);\n        }\n\n        for (int i = 0; i < versions.length - 1; i++) {\n            GameVersionNumber version1 = gameVersionNumbers[i];\n\n            for (int j = 0; j < i; j++) {\n                GameVersionNumber version2 = gameVersionNumbers[j];\n\n                assertTrue(version1.compareTo(version2) > 0, errorMessage(version1, version2));\n                assertTrue(version2.compareTo(version1) < 0, errorMessage(version1, version2));\n                assertNotEquals(version1, version2, errorMessage(version1, version2));\n                assertNotEquals(version2, version1, errorMessage(version1, version2));\n            }\n\n            assertGameVersionEquals(versions[i]);\n\n            for (int j = i + 1; j < versions.length; j++) {\n                GameVersionNumber version2 = gameVersionNumbers[j];\n\n                assertTrue(version1.compareTo(version2) < 0, errorMessage(version1, version2));\n                assertTrue(version2.compareTo(version1) > 0, errorMessage(version1, version2));\n                assertNotEquals(version1, version2, errorMessage(version1, version2));\n                assertNotEquals(version2, version1, errorMessage(version1, version2));\n            }\n        }\n\n        assertGameVersionEquals(versions[versions.length - 1]);\n    }\n\n    private void assertOldVersion(String oldVersion, GameVersionNumber.Type type, String versionNumber) {\n        GameVersionNumber version = asGameVersion(oldVersion);\n        assertInstanceOf(GameVersionNumber.Old.class, version);\n        GameVersionNumber.Old old = (GameVersionNumber.Old) version;\n        assertSame(type, old.type);\n        assertEquals(VersionNumber.asVersion(versionNumber), old.versionNumber);\n    }\n\n    //endregion Helpers\n\n    private static boolean isAprilFools(String version) {\n        return asGameVersion(version).isAprilFools();\n    }\n\n    @Test\n    public void testIsAprilFools() {\n        assertTrue(isAprilFools(\"15w14a\"));\n        assertTrue(isAprilFools(\"1.RV-Pre1\"));\n        assertTrue(isAprilFools(\"3D Shareware v1.34\"));\n        assertTrue(isAprilFools(\"2.0\"));\n        assertTrue(isAprilFools(\"20w14infinite\"));\n        assertTrue(isAprilFools(\"22w13oneBlockAtATime\"));\n        assertTrue(isAprilFools(\"23w13a_or_b\"));\n        assertTrue(isAprilFools(\"24w14potato\"));\n        assertTrue(isAprilFools(\"25w14craftmine\"));\n\n        assertFalse(isAprilFools(\"1.21.8\"));\n        assertFalse(isAprilFools(\"1.21.8-rc1\"));\n        assertFalse(isAprilFools(\"25w21a\"));\n        assertFalse(isAprilFools(\"13w12~\"));\n        assertFalse(isAprilFools(\"15w14b\"));\n        assertFalse(isAprilFools(\"25w45a_unobfuscated\"));\n    }\n\n    @Test\n    public void testSortVersions() {\n        List<String> versions = readVersions();\n\n        {\n            List<String> copied = new ArrayList<>(versions);\n            copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));\n            assertIterableEquals(versions, copied);\n        }\n\n        {\n            List<String> copied = new ArrayList<>(versions);\n            Collections.reverse(copied);\n            copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));\n            assertIterableEquals(versions, copied);\n        }\n\n        for (int randomSeed = 0; randomSeed < 5; randomSeed++) {\n            List<String> copied = new ArrayList<>(versions);\n            Collections.shuffle(copied, new Random(randomSeed));\n            copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));\n            assertIterableEquals(versions, copied);\n        }\n    }\n\n    @Test\n    public void testParseOld() {\n        assertOldVersion(\"rd-132211\", GameVersionNumber.Type.PRE_CLASSIC, \"132211\");\n        assertOldVersion(\"in-20100223\", GameVersionNumber.Type.INDEV, \"20100223\");\n        assertOldVersion(\"in-20100212-2\", GameVersionNumber.Type.INDEV, \"20100212-2\");\n        assertOldVersion(\"inf-20100618\", GameVersionNumber.Type.INFDEV, \"20100618\");\n        assertOldVersion(\"inf-20100330-1\", GameVersionNumber.Type.INFDEV, \"20100330-1\");\n        assertOldVersion(\"a1.0.6\", GameVersionNumber.Type.ALPHA, \"1.0.6\");\n        assertOldVersion(\"a1.0.8_01\", GameVersionNumber.Type.ALPHA, \"1.0.8_01\");\n        assertOldVersion(\"a1.0.13_01-1\", GameVersionNumber.Type.ALPHA, \"1.0.13_01-1\");\n        assertOldVersion(\"b1.0\", GameVersionNumber.Type.BETA, \"1.0\");\n        assertOldVersion(\"b1.0_01\", GameVersionNumber.Type.BETA, \"1.0_01\");\n        assertOldVersion(\"b1.6-tb3\", GameVersionNumber.Type.BETA, \"1.6-tb3\");\n        assertOldVersion(\"b1.8-pre1-2\", GameVersionNumber.Type.BETA, \"1.8-pre1-2\");\n        assertOldVersion(\"b1.9-pre1\", GameVersionNumber.Type.BETA, \"1.9-pre1\");\n\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"1.21\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"r-132211\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"rd-\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"rd-a\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"i-20100223\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"in-\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"in-a\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"inf-\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(\"inf-a\"));\n    }\n\n    private static void testParseLegacySnapshot(int year, int week, char suffix) {\n        String raw = \"%02dw%02d%s\".formatted(year, week, suffix);\n        var rawVersion = (GameVersionNumber.LegacySnapshot) asGameVersion(raw);\n        assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);\n        assertEquals(raw, rawVersion.toString());\n        assertEquals(raw, rawVersion.toNormalizedString());\n        assertEquals(year, rawVersion.getYear());\n        assertEquals(week, rawVersion.getWeek());\n        assertEquals(suffix, rawVersion.getSuffix());\n        assertFalse(rawVersion.isUnobfuscated());\n\n        var unobfuscated = raw + \"_unobfuscated\";\n        var unobfuscatedVersion = (GameVersionNumber.LegacySnapshot) asGameVersion(unobfuscated);\n        assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);\n        assertEquals(unobfuscated, unobfuscatedVersion.toString());\n        assertEquals(unobfuscated, unobfuscatedVersion.toNormalizedString());\n        assertEquals(year, unobfuscatedVersion.getYear());\n        assertEquals(week, unobfuscatedVersion.getWeek());\n        assertEquals(suffix, unobfuscatedVersion.getSuffix());\n        assertTrue(unobfuscatedVersion.isUnobfuscated());\n\n        var unobfuscated2 = raw + \" Unobfuscated\";\n        var unobfuscatedVersion2 = (GameVersionNumber.LegacySnapshot) asGameVersion(unobfuscated2);\n        assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);\n        assertEquals(unobfuscated2, unobfuscatedVersion2.toString());\n        assertEquals(unobfuscated, unobfuscatedVersion2.toNormalizedString());\n        assertEquals(year, unobfuscatedVersion2.getYear());\n        assertEquals(week, unobfuscatedVersion2.getWeek());\n        assertEquals(suffix, unobfuscatedVersion2.getSuffix());\n        assertTrue(unobfuscatedVersion2.isUnobfuscated());\n    }\n\n    @Test\n    public void testParseNew() {\n        List<String> versions = readVersions();\n        for (String version : versions) {\n            GameVersionNumber gameVersion = asGameVersion(version);\n            assertFalse(gameVersion instanceof GameVersionNumber.Old, \"version=\" + gameVersion.toDebugString());\n        }\n\n        testParseLegacySnapshot(25, 46, 'a');\n\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parse(\"2.1\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse(\"1.0\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse(\"1.100.1\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse(\"aawbba\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse(\"13w12A\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse(\"13w12~\"));\n    }\n\n    private static void assertSimpleReleaseVersion(String simpleReleaseVersion, int major, int minor, int patch) {\n        GameVersionNumber.Release release = GameVersionNumber.Release.parseSimple(simpleReleaseVersion);\n        assertAll(\"Assert Simple Release Version \" + simpleReleaseVersion,\n                () -> assertEquals(major, release.getMajor()),\n                () -> assertEquals(minor, release.getMinor()),\n                () -> assertEquals(patch, release.getPatch()),\n                () -> assertEquals(GameVersionNumber.Release.ReleaseType.UNKNOWN, release.getEaType()),\n                () -> assertEquals(VersionNumber.ZERO, release.getEaVersion())\n        );\n    }\n\n    @Test\n    public void testParseSimpleRelease() {\n        assertSimpleReleaseVersion(\"1.0\", 1, 0, 0);\n        assertSimpleReleaseVersion(\"1.13\", 1, 13, 0);\n        assertSimpleReleaseVersion(\"1.21.8\", 1, 21, 8);\n        assertSimpleReleaseVersion(\"26.1\", 26, 1, 0);\n        assertSimpleReleaseVersion(\"26.1.1\", 26, 1, 1);\n\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"26\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"24.0.0\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"24.0\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"2.0\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"1\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"1..0\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"1.0.\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"1.a\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"1.1a\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"1.0a\"));\n        assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple(\"1.0.0.0\"));\n    }\n\n    @Test\n    public void testCompareRelease() {\n        assertGameVersionEquals(\"0.0\");\n        assertGameVersionEquals(\"1.100\");\n        assertGameVersionEquals(\"1.100.1\");\n        assertGameVersionEquals(\"1.100.1-pre1\");\n        assertGameVersionEquals(\"1.100.1-pre1\", \"1.100.1 Pre-Release 1\");\n\n        assertOrder(\n                \"0.0\",\n                \"1.0\",\n                \"1.99\",\n                \"1.99.1-pre1\",\n                \"1.99.1 Pre-Release 2\",\n                \"1.99.1-rc1\",\n                \"1.99.1\",\n                \"1.100\",\n                \"1.100.1\",\n                \"26.1\"\n        );\n    }\n\n    @Test\n    public void testCompareSnapshot() {\n        assertOrder(\n                \"90w01a\",\n                \"90w01b\",\n                \"90w01e\",\n                \"90w02a\"\n        );\n    }\n\n    @Test\n    public void testCompareMix() {\n        assertOrder(\n                \"rd-132211\",\n                \"rd-161348\",\n                \"rd-20090515\",\n                \"c0.0.11a\",\n                \"c0.0.13a\",\n                \"c0.0.13a_03\",\n                \"c0.30_01c\",\n                \"inf-20100330-1\",\n                \"inf-20100330-2\",\n                \"inf-20100618\",\n                \"a1.0.4\",\n                \"a1.0.8_01\",\n                \"a1.0.10\",\n                \"a1.0.13_01-1\",\n                \"a1.0.17_02\",\n                \"a1.0.17_04\",\n                \"a1.1.0\",\n                \"a1.1.1\",\n                \"b1.0\",\n                \"b1.0_01\",\n                \"b1.1_02\",\n                \"b1.2\",\n                \"b1.8-pre1-2\",\n                \"b1.8.1\",\n                \"0.0\",\n                \"1.0.0-rc1\",\n                \"1.0.0-rc2-1\",\n                \"1.0.0-rc2-2\",\n                \"1.0.0-rc2-3\",\n                \"1.0\",\n                \"11w47a\",\n                \"1.1\",\n                \"1.5.1\",\n                \"2.0\",\n                \"1.5.2\",\n                \"1.9.2\",\n                \"1.RV-Pre1\",\n                \"16w14a\",\n                \"1.9.3-pre1\",\n                \"1.13.2\",\n                \"19w13b\",\n                \"3D Shareware v1.34\",\n                \"19w14a\",\n                \"1.14 Pre-Release 1\",\n                \"1.14\",\n                \"1.15.2\",\n                \"20w06a\",\n                \"20w13b\",\n                \"20w14infinite\",\n                \"20w22a\",\n                \"1.16-pre1\",\n                \"1.16\",\n                \"1.18.2\",\n                \"22w13oneblockatatime\",\n                \"22w11a\",\n                \"1.19-pre1\",\n                \"1.19.4\",\n                \"23w13a\",\n                \"23w13a_or_b\",\n                \"23w14a\",\n                \"1.20\",\n                \"24w13a\",\n                \"24w14potato\",\n                \"24w14a\",\n                \"25w46a\",\n                \"25w46a_unobfuscated\",\n                \"1.21.11-pre1\",\n                \"1.21.11-pre1_unobfuscated\",\n                \"1.21.11-pre2\",\n                \"1.21.11-pre2_unobfuscated\",\n                \"99w99a\",\n                \"26.1-snapshot-1\",\n                \"26.1-snapshot-2\",\n                \"26.1\",\n                \"26.2-snapshot-1\",\n                \"26.2-snapshot-2\",\n                \"26.2\",\n                \"100.0\",\n                \"Unknown\"\n        );\n    }\n\n    @Test\n    public void testCompareUnknown() {\n        assertOrder(\n                \"23w35a\",\n                \"1.20.2-pre1\",\n                \"1.20.2-rc1\",\n                \"1.20.2\",\n                \"23w35b\", // fictional version number\n                \"23w40a\"\n        );\n\n        assertOrder(\n                \"1.20.4\",\n                \"24w04a\",\n                \"1.100\"   // fictional version number\n        );\n\n        assertOrder(\n                \"1.19.4\",\n                \"23w18a\", // fictional version number\n                \"1.19.5\",\n                \"1.20\"\n        );\n\n        assertOrder(\n                \"1.0\",\n                \"10w47a\", // fictional version number\n                \"11w47a\",\n                \"1.1\"\n        );\n    }\n\n    private static void assertNormalized(String normalized, String version) {\n        assertGameVersionEquals(version);\n        assertGameVersionEquals(normalized, version);\n        assertEquals(normalized, asGameVersion(version).toNormalizedString());\n    }\n\n    @Test\n    public void testToNormalizedString() {\n        for (String version : readVersions()) {\n            assertNormalized(version, version);\n        }\n\n        assertNormalized(\"26.1-snapshot-1\", \"26.1 Snapshot 1\");\n        assertNormalized(\"1.21.11-pre3\", \"1.21.11 Pre-Release 3\");\n        assertNormalized(\"1.21.11-pre3_unobfuscated\", \"1.21.11 Pre-Release 3 Unobfuscated\");\n        assertNormalized(\"1.21.11-pre3_unobfuscated\", \"1.21.11-pre3 Unobfuscated\");\n        assertNormalized(\"26.1-pre1\", \"26.1-pre-1\");\n        assertNormalized(\"1.21.11-rc1\", \"1.21.11 Release Candidate 1\");\n        assertNormalized(\"1.21.11-rc1_unobfuscated\", \"1.21.11 Release Candidate 1 Unobfuscated\");\n        assertNormalized(\"26.1-rc1\", \"26.1-rc-1\");\n        assertNormalized(\"1.14_combat-212796\", \"1.14.3 - Combat Test\");\n        assertNormalized(\"1.14_combat-0\", \"Combat Test 2\");\n        assertNormalized(\"1.14_combat-3\", \"Combat Test 3\");\n        assertNormalized(\"1.15_combat-1\", \"Combat Test 4\");\n        assertNormalized(\"1.15_combat-6\", \"Combat Test 5\");\n        assertNormalized(\"1.16_combat-0\", \"Combat Test 6\");\n        assertNormalized(\"1.16_combat-1\", \"Combat Test 7\");\n        assertNormalized(\"1.16_combat-2\", \"Combat Test 7b\");\n        assertNormalized(\"1.16_combat-3\", \"Combat Test 7c\");\n        assertNormalized(\"1.16_combat-4\", \"Combat Test 8\");\n        assertNormalized(\"1.16_combat-5\", \"Combat Test 8b\");\n        assertNormalized(\"1.16_combat-6\", \"Combat Test 8c\");\n        assertNormalized(\"1.16.2-pre1\", \"1.16.2 Pre-release 1\"); // https://github.com/HMCL-dev/HMCL/pull/5476\n        assertNormalized(\"1.18_experimental-snapshot-1\", \"1.18 Experimental Snapshot 1\");\n        assertNormalized(\"1.18_experimental-snapshot-2\", \"1.18 experimental snapshot 2\");\n        assertNormalized(\"1.18_experimental-snapshot-3\", \"1.18 experimental snapshot 3\");\n        assertNormalized(\"1.18_experimental-snapshot-4\", \"1.18 experimental snapshot 4\");\n        assertNormalized(\"1.18_experimental-snapshot-5\", \"1.18 experimental snapshot 5\");\n        assertNormalized(\"1.18_experimental-snapshot-6\", \"1.18 experimental snapshot 6\");\n        assertNormalized(\"1.18_experimental-snapshot-7\", \"1.18 experimental snapshot 7\");\n        assertNormalized(\"1.19_deep_dark_experimental_snapshot-1\", \"Deep Dark Experimental Snapshot 1\");\n        assertNormalized(\"20w14infinite\", \"20w14~\");\n        assertNormalized(\"22w13oneBlockAtATime\", \"22w13oneblockatatime\");\n    }\n\n    @Test\n    public void isAtLeast() {\n        assertTrue(asGameVersion(\"1.13\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertTrue(asGameVersion(\"1.13\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertTrue(asGameVersion(\"1.13.1\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertTrue(asGameVersion(\"1.13.1\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertTrue(asGameVersion(\"1.14\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertTrue(asGameVersion(\"1.14\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertTrue(asGameVersion(\"1.13-rc1\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertTrue(asGameVersion(\"1.13-pre1\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertTrue(asGameVersion(\"17w43a\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertTrue(asGameVersion(\"17w43a\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertTrue(asGameVersion(\"17w43b\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertTrue(asGameVersion(\"17w43b\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertTrue(asGameVersion(\"17w45a\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertTrue(asGameVersion(\"17w45a\").isAtLeast(\"1.13\", \"17w43a\", false));\n\n\n        assertFalse(asGameVersion(\"1.13-rc1\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertFalse(asGameVersion(\"1.13-pre1\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertFalse(asGameVersion(\"17w31a\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertFalse(asGameVersion(\"17w31a\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertFalse(asGameVersion(\"1.12\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertFalse(asGameVersion(\"1.12\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertFalse(asGameVersion(\"1.12.2\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertFalse(asGameVersion(\"1.12.2\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertFalse(asGameVersion(\"1.12.2-pre1\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertFalse(asGameVersion(\"1.12.2-pre1\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertFalse(asGameVersion(\"rd-132211\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertFalse(asGameVersion(\"rd-132211\").isAtLeast(\"1.13\", \"17w43a\", false));\n        assertFalse(asGameVersion(\"a1.0.6\").isAtLeast(\"1.13\", \"17w43a\", true));\n        assertFalse(asGameVersion(\"a1.0.6\").isAtLeast(\"1.13\", \"17w43a\", false));\n\n        assertThrows(IllegalArgumentException.class, () -> asGameVersion(\"1.13\").isAtLeast(\"17w43a\", \"17w43a\", true));\n        assertThrows(IllegalArgumentException.class, () -> asGameVersion(\"1.13\").isAtLeast(\"17w43a\", \"17w43a\", false));\n        assertThrows(IllegalArgumentException.class, () -> asGameVersion(\"17w43a\").isAtLeast(\"1.13\", \"1.13\", true));\n        assertThrows(IllegalArgumentException.class, () -> asGameVersion(\"17w43a\").isAtLeast(\"1.13\", \"1.13\", false));\n        assertThrows(IllegalArgumentException.class, () -> asGameVersion(\"17w43a\").isAtLeast(\"1.13\", \"22w13oneblockatatime\", true));\n        assertThrows(IllegalArgumentException.class, () -> asGameVersion(\"17w43a\").isAtLeast(\"1.13\", \"22w13oneblockatatime\", false));\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/VersionNumberTest.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.util.versioning;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.*;\nimport java.util.function.Supplier;\n\nimport static org.jackhuang.hmcl.util.versioning.VersionNumber.isIntVersionNumber;\nimport static org.jackhuang.hmcl.util.versioning.VersionNumber.normalize;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class VersionNumberTest {\n\n    @Test\n    public void testCanonical() {\n        assertEquals(\"3.2\", normalize(\"3.2.0.0\"));\n        assertEquals(\"3.2-5\", normalize(\"3.2.0.0-5\"));\n        assertEquals(\"3.2\", normalize(\"3.2.0.0-0\"));\n        assertEquals(\"3.2\", normalize(\"3.2--------\"));\n        assertEquals(\"3.2\", normalize(\"3.0002\"));\n        assertEquals(\"1.7.2$%%^@&snapshot-3.1.1\", normalize(\"1.7.2$%%^@&snapshot-3.1.1\"));\n        assertEquals(\"1.99999999999999999999\", normalize(\"1.99999999999999999999\"));\n        assertEquals(\"1.99999999999999999999\", normalize(\"1.0099999999999999999999\"));\n        assertEquals(\"1.99999999999999999999\", normalize(\"1.99999999999999999999.0\"));\n        assertEquals(\"1.99999999999999999999\", normalize(\"1.99999999999999999999--------\"));\n    }\n\n    @Test\n    public void testIsIntVersion() {\n        assertFalse(isIntVersionNumber(\"\"));\n        assertFalse(isIntVersionNumber(\" \"));\n        assertFalse(isIntVersionNumber(\".\"));\n        assertFalse(isIntVersionNumber(\"1.\"));\n        assertFalse(isIntVersionNumber(\".1\"));\n        assertFalse(isIntVersionNumber(\".1.\"));\n        assertFalse(isIntVersionNumber(\"1..8\"));\n        assertFalse(isIntVersionNumber(\"1.8.\"));\n        assertFalse(isIntVersionNumber(\".1.8\"));\n        assertFalse(isIntVersionNumber(\"1.7.10forge1614_FTBInfinity\"));\n        assertFalse(isIntVersionNumber(\"3.2-5\"));\n        assertFalse(isIntVersionNumber(\"1.9999999999\"));\n\n        assertTrue(isIntVersionNumber(\"0\"));\n        assertTrue(isIntVersionNumber(\"1\"));\n        assertTrue(isIntVersionNumber(\"0.1\"));\n        assertTrue(isIntVersionNumber(\"0.1.0\"));\n        assertTrue(isIntVersionNumber(\"1.8\"));\n        assertTrue(isIntVersionNumber(\"1.12.2\"));\n        assertTrue(isIntVersionNumber(\"1.13.1\"));\n        assertTrue(isIntVersionNumber(\"1.999999999\"));\n        assertTrue(isIntVersionNumber(\"999999999.0\"));\n    }\n\n    private static void assertLessThan(String s1, String s2) {\n        Supplier<String> messageSupplier = () -> String.format(\"%s should be less than %s\", s1, s2);\n\n        VersionNumber v1 = VersionNumber.asVersion(s1);\n        VersionNumber v2 = VersionNumber.asVersion(s2);\n\n        assertTrue(v1.compareTo(v2) < 0, messageSupplier);\n        assertTrue(v2.compareTo(v1) > 0, messageSupplier);\n    }\n\n    @Test\n    public void testComparator() {\n        assertLessThan(\"1.7.10forge1614_FTBInfinity\", \"1.12.2\");\n        assertLessThan(\"1.8.0_51\", \"1.8.0.51\");\n        assertLessThan(\"1.8.0_77\", \"1.8.0_151\");\n        assertLessThan(\"1.6.0_22\", \"1.8.0_11\");\n        assertLessThan(\"1.7.0_22\", \"1.7.99\");\n        assertLessThan(\"1.12.2-14.23.4.2739\", \"1.12.2-14.23.5.2760\");\n        assertLessThan(\"1.9\", \"1.99999999999999999999\");\n        assertLessThan(\"1.99999999999999999999\", \"1.199999999999999999999\");\n        assertLessThan(\"1.99999999999999999999\", \"2\");\n        assertLessThan(\"1.99999999999999999999\", \"2.0\");\n        assertLessThan(\"1.0\", \"1.0-zzz\");\n        assertLessThan(\"1.0-beta.1\", \"1.0\");\n        assertLessThan(\"1.0-alpha.1\", \"1.0-beta.1\");\n        assertLessThan(\"3.6.15\", \"3.6.15.289\");\n        assertLessThan(\"3.6.15.289\", \"3.6.16\");\n    }\n\n    @Test\n    public void testSorting() {\n        final Comparator<String> comparator = ((Comparator<String>) VersionNumber::compare).thenComparing(String::compareTo);\n        final List<String> input = List.of(\n                \"0\",\n                \"0.10.0\",\n                \"1.6.4\",\n                \"1.6.4-Forge9.11.1.1345\",\n                \"1.7.10\",\n                \"1.7.10Agrarian_Skies_2\",\n                \"1.7.10-F1614-L\",\n                \"1.7.10-FL1614_04\",\n                \"1.7.10-Forge10.13.4.1614-1.7.10\",\n                \"1.7.10-Forge1614\",\n                \"1.7.10Forge1614_FTBInfinity-2.6.0\",\n                \"1.7.10Forge1614_FTBInfinity-3.0.1\",\n                \"1.7.10-Forge1614.1\",\n                \"1.7.10forge1614_ATlauncher\",\n                \"1.7.10forge1614_FTBInfinity\",\n                \"1.7.10forge1614_FTBInfinity_server\",\n                \"1.7.10forge1614test\",\n                \"1.7.10-1614\",\n                \"1.7.10-1614-test\",\n                \"1.8\",\n                \"1.8-forge1577\",\n                \"1.8.9\",\n                \"1.8.9-forge1902\",\n                \"1.9\",\n                \"1.10-alpha.2\",\n                \"1.10-beta.1\",\n                \"1.10-beta.2\",\n                \"1.10\",\n                \"1.10.2\",\n                \"1.10.2-AOE\",\n                \"1.10.2-AOE-1.1.5\",\n                \"1.10.2-All the Mods\",\n                \"1.10.2-FTB_Beyond\",\n                \"1.10.2-LiteLoader1.10.2\",\n                \"1.10.2-forge2511-AOE-1.1.2\",\n                \"1.10.2-forge2511-ATM-E\",\n                \"1.10.2-forge2511-Age_of_Progression\",\n                \"1.10.2-forge2511_Farming_Valley\",\n                \"1.10.2-forge2511_bxztest\",\n                \"1.10.2-forge2511-simple_life_2\",\n                \"1.10.2-forge2511中文\",\n                \"1.12.2\",\n                \"1.12.2_Modern_Skyblock-3.4.2\",\n                \"1.13.1\",\n                \"1.99999999999999999999\",\n                \"2\",\n                \"2.0\",\n                \"2.1\"\n        );\n\n        List<String> output = new ArrayList<>(input);\n        output.sort(comparator);\n        assertIterableEquals(input, output);\n\n        Collections.shuffle(output, new Random(0));\n        output.sort(comparator);\n        assertIterableEquals(input, output);\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/VersionRangeTest.java",
    "content": "package org.jackhuang.hmcl.util.versioning;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.jackhuang.hmcl.util.versioning.VersionRange.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class VersionRangeTest {\n\n    @Test\n    public void testContains() {\n        VersionRange<VersionNumber> empty = VersionRange.empty();\n        VersionRange<VersionNumber> all = all();\n\n        assertTrue(VersionNumber.between(\"10\", \"20\").contains(VersionNumber.asVersion(\"10\")));\n        assertTrue(VersionNumber.between(\"10\", \"20\").contains(VersionNumber.asVersion(\"15\")));\n        assertTrue(VersionNumber.between(\"10\", \"20\").contains(VersionNumber.asVersion(\"20\")));\n        assertFalse(VersionNumber.between(\"10\", \"20\").contains(VersionNumber.asVersion(\"5\")));\n        assertFalse(VersionNumber.between(\"10\", \"20\").contains(VersionNumber.asVersion(\"25\")));\n\n        assertTrue(VersionNumber.between(\"10\", \"10\").contains(VersionNumber.asVersion(\"10\")));\n        assertFalse(VersionNumber.between(\"10\", \"10\").contains(VersionNumber.asVersion(\"5\")));\n        assertFalse(VersionNumber.between(\"10\", \"10\").contains(VersionNumber.asVersion(\"15\")));\n\n        assertTrue(VersionNumber.atLeast(\"10\").contains(VersionNumber.asVersion(\"10\")));\n        assertTrue(VersionNumber.atLeast(\"10\").contains(VersionNumber.asVersion(\"20\")));\n        assertFalse(VersionNumber.atLeast(\"10\").contains(VersionNumber.asVersion(\"5\")));\n\n        assertTrue(VersionNumber.atMost(\"10\").contains(VersionNumber.asVersion(\"10\")));\n        assertTrue(VersionNumber.atMost(\"10\").contains(VersionNumber.asVersion(\"5\")));\n        assertFalse(VersionNumber.atMost(\"10\").contains(VersionNumber.asVersion(\"20\")));\n\n        assertFalse(empty.contains(VersionNumber.asVersion(\"0\")));\n        assertFalse(empty.contains(VersionNumber.asVersion(\"10\")));\n\n        assertTrue(all.contains(VersionNumber.asVersion(\"0\")));\n        assertTrue(all.contains(VersionNumber.asVersion(\"10\")));\n\n        assertFalse(all.contains(null));\n        assertFalse(empty.contains( null));\n        assertFalse(VersionNumber.between(\"0\", \"10\").contains(null));\n        assertFalse(VersionNumber.atLeast(\"10\").contains(null));\n        assertFalse(VersionNumber.atMost(\"10\").contains(null));\n        assertFalse(all.contains(null));\n        assertFalse(empty.contains(null));\n        assertFalse(VersionNumber.between(\"0\", \"10\").contains(null));\n        assertFalse(VersionNumber.atLeast(\"10\").contains(null));\n        assertFalse(VersionNumber.atMost(\"10\").contains(null));\n    }\n\n    private static void assertIsOverlappedBy(boolean value, VersionRange<VersionNumber> range1, VersionRange<VersionNumber> range2) {\n        assertEquals(value, range1.isOverlappedBy(range2));\n        assertEquals(value, range2.isOverlappedBy(range1));\n    }\n\n    @Test\n    public void testIsOverlappedBy() {\n        assertIsOverlappedBy(true, all(), all());\n        assertIsOverlappedBy(false, all(), empty());\n        assertIsOverlappedBy(false, empty(), empty());\n\n        assertIsOverlappedBy(true, all(), VersionNumber.between(\"10\", \"20\"));\n        assertIsOverlappedBy(true, all(), VersionNumber.atLeast(\"10\"));\n        assertIsOverlappedBy(true, all(), VersionNumber.atMost(\"10\"));\n\n        assertIsOverlappedBy(false, empty(), VersionNumber.between(\"10\", \"20\"));\n        assertIsOverlappedBy(false, empty(), VersionNumber.atLeast(\"10\"));\n        assertIsOverlappedBy(false, empty(), VersionNumber.atMost(\"10\"));\n\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"10\", \"20\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"5\", \"20\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"5\", \"15\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"5\", \"10\"));\n        assertIsOverlappedBy(false, VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"5\", \"5\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"10\", \"30\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"15\", \"30\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"20\", \"30\"));\n        assertIsOverlappedBy(false, VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"21\", \"30\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"5\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"10\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"15\"));\n        assertIsOverlappedBy(true, VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"20\"));\n        assertIsOverlappedBy(false, VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"25\"));\n\n        assertIsOverlappedBy(true, VersionNumber.atLeast(\"10\"), VersionNumber.atLeast(\"10\"));\n        assertIsOverlappedBy(true, VersionNumber.atLeast(\"10\"), VersionNumber.atLeast(\"20\"));\n        assertIsOverlappedBy(true, VersionNumber.atLeast(\"10\"), VersionNumber.atLeast(\"5\"));\n        assertIsOverlappedBy(true, VersionNumber.atLeast(\"10\"), VersionNumber.atMost(\"10\"));\n        assertIsOverlappedBy(true, VersionNumber.atLeast(\"10\"), VersionNumber.atMost(\"20\"));\n        assertIsOverlappedBy(false, VersionNumber.atLeast(\"10\"), VersionNumber.atMost(\"5\"));\n    }\n\n    private static void assertIntersectionWith(VersionRange<VersionNumber> range1, VersionRange<VersionNumber> range2, VersionRange<VersionNumber> result) {\n        assertEquals(result, range1.intersectionWith(range2));\n        assertEquals(result, range2.intersectionWith(range1));\n    }\n\n    @Test\n    public void testIntersectionWith() {\n        assertIntersectionWith(all(), all(), all());\n        assertIntersectionWith(all(), empty(), empty());\n        assertIntersectionWith(all(), VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(all(), VersionNumber.atLeast(\"10\"), VersionNumber.atLeast(\"10\"));\n        assertIntersectionWith(all(), VersionNumber.atMost(\"10\"), VersionNumber.atMost(\"10\"));\n\n        assertIntersectionWith(empty(), empty(), empty());\n        assertIntersectionWith(empty(), VersionNumber.between(\"10\", \"20\"), empty());\n        assertIntersectionWith(empty(), VersionNumber.atLeast(\"10\"), empty());\n        assertIntersectionWith(empty(), VersionNumber.atMost(\"10\"), empty());\n\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"5\", \"20\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"10\", \"25\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"5\", \"25\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"15\", \"20\"), VersionNumber.between(\"15\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"10\", \"15\"), VersionNumber.between(\"10\", \"15\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.between(\"14\", \"16\"), VersionNumber.between(\"14\", \"16\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"5\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"10\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"15\"), VersionNumber.between(\"15\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"20\"), VersionNumber.between(\"20\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atLeast(\"25\"), empty());\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atMost(\"25\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atMost(\"20\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atMost(\"15\"), VersionNumber.between(\"10\", \"15\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atMost(\"10\"), VersionNumber.between(\"10\", \"10\"));\n        assertIntersectionWith(VersionNumber.between(\"10\", \"20\"), VersionNumber.atMost(\"5\"), empty());\n\n        assertIntersectionWith(VersionNumber.atLeast(\"10\"), VersionNumber.atMost(\"10\"), VersionNumber.between(\"10\", \"10\"));\n        assertIntersectionWith(VersionNumber.atLeast(\"10\"), VersionNumber.atMost(\"20\"), VersionNumber.between(\"10\", \"20\"));\n        assertIntersectionWith(VersionNumber.atLeast(\"10\"), VersionNumber.atMost(\"5\"), empty());\n    }\n}\n"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/config.txt",
    "content": "---- Minecraft Crash Report ----\n// Why did you do that?\n\nTime: 11/22/20 1:40 PM\nDescription: Exception in server tick loop\n\nnet.minecraftforge.fml.config.ConfigFileTypeHandler$ConfigLoadingException: Failed loading config file jumbofurnace-server.toml of type SERVER for modid jumbofurnace\n\tat net.minecraftforge.fml.config.ConfigFileTypeHandler.lambda$reader$1(ConfigFileTypeHandler.java:60) ~[?:?] {re:classloading}\n\tat net.minecraftforge.fml.config.ConfigFileTypeHandler$$Lambda$16675/1280589919.apply(Unknown Source) ~[?:?] {}\n\tat net.minecraftforge.fml.config.ConfigTracker.openConfig(ConfigTracker.java:104) ~[?:?] {re:classloading}\n\tat net.minecraftforge.fml.config.ConfigTracker.lambda$loadConfigs$1(ConfigTracker.java:83) ~[?:?] {re:classloading}\n\tat net.minecraftforge.fml.config.ConfigTracker$$Lambda$16674/452455151.accept(Unknown Source) ~[?:?] {}\n\tat java.lang.Iterable.forEach(Iterable.java:75) ~[?:1.8.0_51] {}\n\tat java.util.Collections$SynchronizedCollection.forEach(Collections.java:2062) ~[?:1.8.0_51] {}\n\tat net.minecraftforge.fml.config.ConfigTracker.loadConfigs(ConfigTracker.java:83) ~[?:?] {re:classloading}\n\tat net.minecraftforge.fml.server.ServerLifecycleHooks.handleServerAboutToStart(ServerLifecycleHooks.java:95) ~[?:?] {re:classloading}\n\tat net.minecraft.server.integrated.IntegratedServer.func_71197_b(IntegratedServer.java:59) ~[?:?] {re:classloading,xf:OptiFine:default,pl:runtimedistcleaner:A}\n\tat net.minecraft.server.MinecraftServer.func_240802_v_(MinecraftServer.java:620) [?:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:byg.mixins.json:server.MixinMinecraftServer,pl:mixin:APP:jaopca.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat net.minecraft.server.MinecraftServer.lambda$startServer$0(MinecraftServer.java:232) [?:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:byg.mixins.json:server.MixinMinecraftServer,pl:mixin:APP:jaopca.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat net.minecraft.server.MinecraftServer$$Lambda$25190/1809790537.run(Unknown Source) [?:?] {}\n\tat java.lang.Thread.run(Thread.java:745) [?:1.8.0_51] {}\nCaused by: com.electronwill.nightconfig.core.io.ParsingException: Not enough data available\n\tat com.electronwill.nightconfig.core.io.ParsingException.notEnoughData(ParsingException.java:22) ~[core-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.core.io.ReaderInput.directReadChar(ReaderInput.java:36) ~[core-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.core.io.AbstractInput.readChar(AbstractInput.java:49) ~[core-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.core.io.AbstractInput.readCharsUntil(AbstractInput.java:123) ~[core-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.toml.TableParser.parseKey(TableParser.java:166) ~[toml-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.toml.TableParser.parseDottedKey(TableParser.java:145) ~[toml-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.toml.TableParser.parseNormal(TableParser.java:55) ~[toml-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.toml.TomlParser.parse(TomlParser.java:44) ~[toml-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.toml.TomlParser.parse(TomlParser.java:37) ~[toml-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.core.io.ConfigParser.parse(ConfigParser.java:113) ~[core-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.core.io.ConfigParser.parse(ConfigParser.java:219) ~[core-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.core.io.ConfigParser.parse(ConfigParser.java:202) ~[core-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.core.file.WriteSyncFileConfig.load(WriteSyncFileConfig.java:73) ~[core-3.6.2.jar:?] {}\n\tat com.electronwill.nightconfig.core.file.AutosaveCommentedFileConfig.load(AutosaveCommentedFileConfig.java:85) ~[core-3.6.2.jar:?] {}\n\tat net.minecraftforge.fml.config.ConfigFileTypeHandler.lambda$reader$1(ConfigFileTypeHandler.java:56) ~[?:?] {re:classloading}\n\t... 13 more\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.16.4\n\tMinecraft Version ID: 1.16.4\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_51, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 997125992 bytes (950 MB) / 5671747584 bytes (5409 MB) up to 7259291648 bytes (6923 MB)\n\tCPUs: 12\n\tJVM Flags: 5 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xss1M -Xmx7787m -Xms256m -XX:PermSize=256m\n\tModLauncher: 8.0.6+85+master.325de55\n\tModLauncher launch target: fmlclient\n\tModLauncher naming: srg\n\tModLauncher services:\n\t\t/mixin-0.8.2.jar mixin PLUGINSERVICE\n\t\t/eventbus-3.0.5-service.jar eventbus PLUGINSERVICE\n\t\t/forge-1.16.4-35.1.1.jar object_holder_definalize PLUGINSERVICE\n\t\t/forge-1.16.4-35.1.1.jar runtime_enum_extender PLUGINSERVICE\n\t\t/accesstransformers-2.2.0-shadowed.jar accesstransformer PLUGINSERVICE\n\t\t/forge-1.16.4-35.1.1.jar capability_inject_definalize PLUGINSERVICE\n\t\t/forge-1.16.4-35.1.1.jar runtimedistcleaner PLUGINSERVICE\n\t\t/mixin-0.8.2.jar mixin TRANSFORMATIONSERVICE\n\t\t/OptiFine_1.16.3_HD_U_G5.jar OptiFine TRANSFORMATIONSERVICE\n\t\t/forge-1.16.4-35.1.1.jar fml TRANSFORMATIONSERVICE\n\tFML: 35.1\n\tForge: net.minecraftforge:35.1.1\n\tFML Language Providers:\n\t\tjavafml@35.1\n\t\tminecraft@1\n\t\tkotori_scala@2.13.3-build-5\n\tMod List:\n\t\tftbessentials-1603.1.0.11.jar                     |FTB Essentials                |ftbessentials                 |1603.1.0.11         |DONE      |NOSIGNATURE\n\t\tAdditionalEnchantedMiner-1.16.3-16.1.2.jar        |QuarryPlus                    |quarryplus                    |16.1.2              |DONE      |1a:13:52:63:6f:dc:0c:ad:7f:8a:64:ac:46:58:8a:0c:90:ea:2c:5d:11:ac:4c:d4:62:85:c7:d1:00:fa:9c:76\n\t\tsimplemagnets-1.0.8-mc1.16.4.jar                  |Simple Magnets                |simplemagnets                 |1.0.8               |DONE      |NOSIGNATURE\n\t\tmissingstructurefix-1.0.jar                       |Missing Structure Fix         |missingstructurefix           |1.0                 |DONE      |NOSIGNATURE\n\t\tmcw-windows-1.0.2-mc1.16.4.jar                    |Macaw's Windows               |mcwwindows                    |1.0.2               |DONE      |NOSIGNATURE\n\t\tmodnametooltip_1.16.2-1.15.0.jar                  |Mod Name Tooltip              |modnametooltip                |1.15.0              |DONE      |NOSIGNATURE\n\t\tIronJetpacks-1.16.4-4.1.3.jar                     |Iron Jetpacks                 |ironjetpacks                  |4.1.3               |DONE      |NOSIGNATURE\n\t\tReAuth-1.16-Forge-3.9.3.jar                       |ReAuth                        |reauth                        |3.9.3               |DONE      |3d:06:1e:e5:da:e2:ff:ae:04:00:be:45:5b:ff:fd:70:65:00:67:0b:33:87:a6:5f:af:20:3c:b6:a1:35:ca:7e\n\t\tPowah-1.16.4-2.3.5.jar                            |Powah                         |powah                         |2.3.5               |DONE      |NOSIGNATURE\n\t\trangedpumps-0.8.2.jar                             |Ranged Pumps                  |rangedpumps                   |0.8.2               |DONE      |NOSIGNATURE\n\t\tjumbofurnace-1.16.3-2.1.1.0.jar                   |Jumbo Furnace                 |jumbofurnace                  |1.16.3-2.1.1.0      |DONE      |NOSIGNATURE\n\t\tWither-Skeleton-Tweaks-1.16.3-5.2.1.jar           |Wither Skeleton Tweaks        |wstweaks                      |5.2.1               |DONE      |NOSIGNATURE\n\t\treliquary-1.16.3-1.3.4.1055.jar                   |Reliquary                     |xreliquary                    |1.16.3-1.3.4.1055   |DONE      |NOSIGNATURE\n\t\trandompatches-1.16.2-1.22.1.2.jar                 |RandomPatches                 |randompatches                 |1.16.2-1.22.1.2     |DONE      |92:f6:29:d4:09:89:f5:f5:98:5e:20:34:31:d0:7b:58:22:06:bd:a5:d1:6a:92:6e:ac:3d:8d:18:c5:b2:5b:d7\n\t\tWaterStrainer-1.16.3-10.0.0.jar                   |Water Strainer                |waterstrainer                 |1.16.3-10.0.0       |DONE      |NOSIGNATURE\n\t\tdynviewdist-1.7.jar                               |Dynamic view distance         |dynview                       |1.7                 |DONE      |NOSIGNATURE\n\t\tTMechworks-1.16.3+-2.2.2.jar                      |Tinkers' Mechworks            |tmechworks                    |2.2.2               |DONE      |NOSIGNATURE\n\t\tJustEnoughResources-1.16.4-0.12.0.103.jar         |Just Enough Resources         |jeresources                   |0.12.0.103          |DONE      |NOSIGNATURE\n\t\tconfdespawntimer_1.16.4-1.3.jar                   |Configurable Despawn Timer    |confdespawntimer              |1.3                 |DONE      |NOSIGNATURE\n\t\tshetiphiancore-1.16-3.8.4.jar                     |ShetiPhian-Core               |shetiphiancore                |3.8.4               |DONE      |NOSIGNATURE\n\t\tMysticalAgradditions-1.16.4-4.1.1.jar             |Mystical Agradditions         |mysticalagradditions          |4.1.1               |DONE      |NOSIGNATURE\n\t\temojiful-1.16.3-2.1.1.jar                         |Emojiful                      |emojiful                      |1.16.3-2.1.1        |DONE      |NOSIGNATURE\n\t\trefinedstorage-1.9.9.jar                          |Refined Storage               |refinedstorage                |1.9.9               |DONE      |NOSIGNATURE\n\t\tPackMenu-1.16.3-2.3.0.jar                         |Pack Menu                     |packmenu                      |2.3.0               |DONE      |NOSIGNATURE\n\t\talltheores-0.1.13-1.16.3-34.1.0.jar               |AllTheOres                    |alltheores                    |0.1.13              |DONE      |NOSIGNATURE\n\t\tindustrial-foregoing-1.16.4-3.2.2-daea863.jar     |Industrial Foregoing          |industrialforegoing           |3.2.2               |DONE      |NOSIGNATURE\n\t\tcleancut-mc1.16-2.2-forge.jar                     |Clean Cut                     |cleancut                      |2.2                 |DONE      |NOSIGNATURE\n\t\ttorchmaster-2.3.4-alpha.jar                       |Torchmaster                   |torchmaster                   |2.3.4-alpha         |DONE      |NOSIGNATURE\n\t\trepurposed_structures-1.16.4-2.3.0.jar            |Repurposed Structures         |repurposed_structures         |1.16.4-2.3.0        |DONE      |NOSIGNATURE\n\t\tBiomesOPlenty-1.16.4-13.0.0.420-universal.jar     |Biomes O' Plenty              |biomesoplenty                 |1.16.4-13.0.0.418   |DONE      |NOSIGNATURE\n\t\tmcw-trapdors-1.0.0-mc1.16.4.jar                   |Macaw's Trapdoors             |mcwtrpdoors                   |1.0.0               |DONE      |NOSIGNATURE\n\t\tSilentGear-1.16.3-2.3.5+170.jar                   |Silent Gear                   |silentgear                    |2.3.5+170           |DONE      |NOSIGNATURE\n\t\tportality-1.16.4-3.2.0.jar                        |Portality                     |portality                     |3.2.0               |DONE      |NOSIGNATURE\n\t\tcurios-forge-1.16.4-4.0.2.1.jar                   |Curios API                    |curios                        |1.16.4-4.0.2.1      |DONE      |NOSIGNATURE\n\t\tBotania-1.16.3-409.jar                            |Botania                       |botania                       |1.16.3-409          |DONE      |NOSIGNATURE\n\t\ttanknull-2.1-1.16.2.jar                           |Tank Null                     |tanknull                      |2.1-1.16.2          |DONE      |NOSIGNATURE\n\t\tangelring-1.16.3-1.3.3.jar                        |Angel Ring                    |angelring                     |1.3.3               |DONE      |NOSIGNATURE\n\t\ttombstone-6.0.2.jar                               |Corail Tombstone              |tombstone                     |6.0.2               |DONE      |NOSIGNATURE\n\t\tExtraStorage-1.16.4-1.2.1.jar                     |Extra Storage                 |extrastorage                  |1.2.1               |DONE      |NOSIGNATURE\n\t\tNaturesAura-32.0.jar                              |Nature's Aura                 |naturesaura                   |32.0                |DONE      |NOSIGNATURE\n\t\tconstructionwand-1.16.2-1.6.jar                   |Construction Wand             |constructionwand              |1.16.2-1.6          |DONE      |NOSIGNATURE\n\t\tmcws-roofs-2.0.0-mc1.16.4.jar                     |Macaw's Roofs                 |mcwroofs                      |2.0.0               |DONE      |NOSIGNATURE\n\t\tglobalxp-1.16.3-v1.4.15.jar                       |Global XP                     |globalxp                      |v1.4.15             |DONE      |NOSIGNATURE\n\t\tcloth-config-forge-4.1.1.jar                      |Cloth Config v4 API           |cloth-config                  |4.0                 |DONE      |NOSIGNATURE\n\t\tFastLeafDecay-v25.jar                             |FastLeafDecay                 |fastleafdecay                 |v25                 |DONE      |NOSIGNATURE\n\t\tBetterMineshafts-Forge-1.16.3-1.1.jar             |YUNG's Better Mineshafts      |bettermineshafts              |1.16.3-1.1          |DONE      |NOSIGNATURE\n\t\tClientTweaks_1.16.3-5.2.0.jar                     |Client Tweaks                 |clienttweaks                  |5.2.0               |DONE      |NOSIGNATURE\n\t\tnomowanderer_MC1.16.3_1.0.jar                     |NoMoWanderer                  |nomowanderer                  |1.16.3_1.0          |DONE      |NOSIGNATURE\n\t\twoot-1.16.3-1.0.0.2.jar                           |Woot                          |woot                          |1.16.3-1.0.0.2      |DONE      |NOSIGNATURE\n\t\tbagofyurting-1.16.3-1.1.0.1.jar                   |Bag of Yurting                |bagofyurting                  |1.1.0.1             |DONE      |NOSIGNATURE\n\t\tjei-1.16.4-7.6.0.58.jar                           |Just Enough Items             |jei                           |7.6.0.58            |DONE      |NOSIGNATURE\n\t\tAttributeFix-1.16.4-9.0.2.jar                     |AttributeFix                  |attributefix                  |9.0.2               |DONE      |ea:45:b3:82:b6:9d:50:16:95:e7:2e:34:e1:92:d5:b4:9b:69:90:d3:4f:2e:71:99:b0:be:40:80:27:1f:3e:b0\n\t\tMekanism-1.16.4-10.0.17.444.jar                   |Mekanism                      |mekanism                      |10.0.17             |DONE      |NOSIGNATURE\n\t\tinvtweaks-1.16.4-1.0.1.jar                        |Inventory Tweaks Renewed      |invtweaks                     |1.16.4-1.0.1        |DONE      |NOSIGNATURE\n\t\tshutupexperimentalsettings-1.0.1.jar              |Shutup Experimental Settings! |shutupexperimentalsettings    |1.0.1               |DONE      |NOSIGNATURE\n\t\tlight-overlay-5.5.4.jar                           |Light Overlay Forge           |lightoverlay-forge            |NONE                |DONE      |NOSIGNATURE\n\t\tNaturesCompass-1.16.3-1.8.5.jar                   |Nature's Compass              |naturescompass                |1.16.3-1.8.5        |DONE      |NOSIGNATURE\n\t\tLibX-1.16.3-1.0.1.jar                             |LibX                          |libx                          |1.16.3-1.0.1        |DONE      |NOSIGNATURE\n\t\tphosphophyllite-1.16.3-0.2.1-beta.jar             |Phosphophyllite               |phosphophyllite               |0.2.1               |DONE      |NOSIGNATURE\n\t\tengineerstools-1.16.4-1.1.2-b1.jar                |Engineer's Tools              |engineerstools                |1.1.2-b1            |DONE      |NOSIGNATURE\n\t\tFarmingForBlockheads_1.16.3-7.2.1.jar             |Farming for Blockheads        |farmingforblockheads          |7.2.1               |DONE      |NOSIGNATURE\n\t\tpneumaticcraft-repressurized-1.16.3-2.5.0-66.jar  |PneumaticCraft: Repressurized |pneumaticcraft                |1.16.3-2.5.0-66     |DONE      |NOSIGNATURE\n\t\tpedestals-0.8g.jar                                |Pedestals                     |pedestals                     |0.8g                |DONE      |NOSIGNATURE\n\t\textradisks-1.16.3-1.3.3.jar                       |Extra Disks                   |extradisks                    |1.3.3               |DONE      |NOSIGNATURE\n\t\tImmersivePetroleum-1.16.3-3.0.0.jar               |Immersive Petroleum           |immersivepetroleum            |3.0.0               |DONE      |NOSIGNATURE\n\t\tironchest-1.16.4-11.2.10.jar                      |Iron Chests                   |ironchest                     |1.16.4-11.2.10      |DONE      |NOSIGNATURE\n\t\tMythicBotany-1.16.3-1.1.10.jar                    |MythicBotany                  |mythicbotany                  |1.16.3-1.1.10       |DONE      |NOSIGNATURE\n\t\tforge-1.16.4-35.1.1-client.jar                    |Minecraft                     |minecraft                     |1.16.4              |DONE      |NOSIGNATURE\n\t\ttheoneprobe-1.16-3.0.6.jar                        |The One Probe                 |theoneprobe                   |1.16-3.0.6          |DONE      |NOSIGNATURE\n\t\tMouseTweaks-2.13-mc1.16.2.jar                     |Mouse Tweaks                  |mousetweaks                   |2.13                |DONE      |NOSIGNATURE\n\t\tImmersiveEngineering-1.16.4-4.1.1-128.jar         |Immersive Engineering         |immersiveengineering          |1.16.4-4.1.1-128    |DONE      |44:39:94:cf:1d:8c:be:3c:7f:a9:ee:f4:1e:63:a5:ac:61:f9:c2:87:d5:5b:d9:d6:8c:b5:3e:96:5d:8e:3f:b7\n\t\tuseful_railroads-1.16.4-1.4.6.34.jar              |Useful Railroads              |usefulrailroads               |1.4.6.34            |DONE      |f4:a6:0b:ee:cb:8a:1a:ea:9f:9d:45:91:8f:8b:b3:ae:26:f3:bf:05:86:1d:90:9e:f6:32:2a:1a:ed:1d:ce:b0\n\t\tpamhc2crops-1.16.3-1.0.1.jar                      |Pam's HarvestCraft 2 Crops    |pamhc2crops                   |version             |DONE      |NOSIGNATURE\n\t\tallthemodium-1.1.15-1.16.3-34.1.0.jar             |Allthemodium                  |allthemodium                  |1.1.15              |DONE      |NOSIGNATURE\n\t\tDing-1.16.3-1.2.0.jar                             |Ding                          |ding                          |1.2.0               |DONE      |NOSIGNATURE\n\t\tjeiintegration_1.16.4-6.1.0.10.jar                |JEI Integration               |jeiintegration                |6.1.0.10            |DONE      |NOSIGNATURE\n\t\tMantle-1.16.4-1.6.43.jar                          |Mantle                        |mantle                        |1.6.43              |DONE      |NOSIGNATURE\n\t\tpamhc2foodcore-1.16.3-1.0.0.jar                   |Pam's HarvestCraft 2 Food Core|pamhc2foodcore                |version             |DONE      |NOSIGNATURE\n\t\tftb-backups-2.1.1.6.jar                           |FTB Backups                   |ftbbackups                    |2.1.1.6             |DONE      |NOSIGNATURE\n\t\tpolymorph-forge-1.16.3-0.16.jar                   |Polymorph                     |polymorph                     |1.16.3-0.16         |DONE      |NOSIGNATURE\n\t\tAutoRegLib-1.6-46.jar                             |AutoRegLib                    |autoreglib                    |1.6-46              |DONE      |NOSIGNATURE\n\t\tstructurize-0.13.96-ALPHA-universal.jar           |Structurize                   |structurize                   |0.13.96-ALPHA       |DONE      |NOSIGNATURE\n\t\tPickleTweaks-1.16.4-5.1.4.jar                     |Pickle Tweaks                 |pickletweaks                  |5.1.4               |DONE      |NOSIGNATURE\n\t\tFastFurnace-1.16.3-4.3.0.jar                      |FastFurnace                   |fastfurnace                   |4.3.0               |DONE      |NOSIGNATURE\n\t\tallthetweaks-1.1.7-1.16.3-34.1.10.jar             |All The Tweaks                |allthetweaks                  |1.1.7               |DONE      |NOSIGNATURE\n\t\tbyg-1.1.5.jar                                     |Oh The Biomes You'll Go       |byg                           |1.1.5               |DONE      |NOSIGNATURE\n\t\textremeSoundMuffler-3.0.0-BETA_Forge-1.16.3.jar   |Extreme Sound Muffler         |extremesoundmuffler           |3.0.0-BETA_forge-1.1|DONE      |NOSIGNATURE\n\t\tCosmeticArmorReworked-1.16.4-v1.jar               |CosmeticArmorReworked         |cosmeticarmorreworked         |1.16.4-v1           |DONE      |5e:ed:25:99:e4:44:14:c0:dd:89:c1:a9:4c:10:b5:0d:e4:b1:52:50:45:82:13:d8:d0:32:89:67:56:57:01:53\n\t\tmorered-1.16.3-2.0.1.0.jar                        |More Red                      |morered                       |2.0.1.0             |DONE      |NOSIGNATURE\n\t\txptome-1.16.2-v1.1.6.jar                          |XP Tome                       |xpbook                        |v1.1.6              |DONE      |NOSIGNATURE\n\t\tDefaultOptions_1.16.3-12.2.0.jar                  |Default Options               |defaultoptions                |12.2.0              |DONE      |NOSIGNATURE\n\t\tNetherPortalFix_1.16.3-7.2.1.jar                  |NetherPortalFix               |netherportalfix               |7.2.1               |DONE      |NOSIGNATURE\n\t\taiotbotania-1.16.3-1.4.2.jar                      |AIOT Botania                  |aiotbotania                   |1.4.2               |DONE      |NOSIGNATURE\n\t\tforge-1.16.3-geckolib-2.1.2.jar                   |GeckoLib                      |geckolib                      |2.1.2               |DONE      |NOSIGNATURE\n\t\tars_nouveau-1.16.3-1.5.3.jar                      |Ars Nouveau                   |ars_nouveau                   |1.5.3               |DONE      |NOSIGNATURE\n\t\tmanagear-1.16.3-2.1.3.jar                         |Mana Gear                     |managear                      |1.16.3-2.1.3        |DONE      |NOSIGNATURE\n\t\tKleeSlabs_1.16.3-9.2.0.jar                        |KleeSlabs                     |kleeslabs                     |9.2.0               |DONE      |NOSIGNATURE\n\t\trsgauges-1.16.4-1.2.6-b1.jar                      |Gauges and Switches           |rsgauges                      |1.2.6-b1            |DONE      |NOSIGNATURE\n\t\tCookingForBlockheads_1.16.3-9.2.2.jar             |Cooking for Blockheads        |cookingforblockheads          |9.2.2               |DONE      |NOSIGNATURE\n\t\tControlling-7.0.0.11.jar                          |Controlling                   |controlling                   |7.0.0.11            |DONE      |NOSIGNATURE\n\t\tPlacebo-1.16.3-4.3.3.jar                          |Placebo                       |placebo                       |4.3.3               |DONE      |NOSIGNATURE\n\t\tdankstorage-3.11.jar                              |Dank Storage                  |dankstorage                   |3.11                |DONE      |NOSIGNATURE\n\t\tcitadel-1.5.0.jar                                 |Citadel                       |citadel                       |1.5.0               |DONE      |NOSIGNATURE\n\t\ticeandfire-2.1.1-1.16.3.jar                       |Ice and Fire                  |iceandfire                    |2.0.3-1.16.3        |DONE      |NOSIGNATURE\n\t\trats-7.0.1-1.16.3.jar                             |Rats                          |rats                          |7.0.1               |DONE      |NOSIGNATURE\n\t\tratlantis-1.0.0-1.16.3.jar                        |Rats: Ratlantis               |ratlantis                     |1.0.0-1.16.3        |DONE      |NOSIGNATURE\n\t\tftb-gui-library-1603.1.1.25.jar                   |FTB GUI Library               |ftbguilibrary                 |1603.1.1.25         |DONE      |NOSIGNATURE\n\t\tpotionsmaster-0.1.24-1.16.3-34.1.42.jar           |Potions Master                |potionsmaster                 |0.1.24              |DONE      |NOSIGNATURE\n\t\tculinaryconstruct-forge-1.16.3-4.0.0.1.jar        |Culinary Construct            |culinaryconstruct             |1.16.3-4.0.0.1      |DONE      |NOSIGNATURE\n\t\tBookshelf-1.16.4-9.0.7.jar                        |Bookshelf                     |bookshelf                     |9.0.7               |DONE      |ea:45:b3:82:b6:9d:50:16:95:e7:2e:34:e1:92:d5:b4:9b:69:90:d3:4f:2e:71:99:b0:be:40:80:27:1f:3e:b0\n\t\tDarkUtilities-1.16.4-7.0.3.jar                    |Dark Utilities                |darkutils                     |7.0.3               |DONE      |ea:45:b3:82:b6:9d:50:16:95:e7:2e:34:e1:92:d5:b4:9b:69:90:d3:4f:2e:71:99:b0:be:40:80:27:1f:3e:b0\n\t\tBotanyPots-1.16.4-6.0.5.jar                       |BotanyPots                    |botanypots                    |6.0.5               |DONE      |ea:45:b3:82:b6:9d:50:16:95:e7:2e:34:e1:92:d5:b4:9b:69:90:d3:4f:2e:71:99:b0:be:40:80:27:1f:3e:b0\n\t\tu_team_core-1.16.4-3.1.12.186.jar                 |U Team Core                   |uteamcore                     |3.1.12.186          |DONE      |f4:a6:0b:ee:cb:8a:1a:ea:9f:9d:45:91:8f:8b:b3:ae:26:f3:bf:05:86:1d:90:9e:f6:32:2a:1a:ed:1d:ce:b0\n\t\tbuildinggadgets-3.7.2.jar                         |Building Gadgets              |buildinggadgets               |3.7.2               |DONE      |NOSIGNATURE\n\t\tforge-1.16.4-35.1.1-universal.jar                 |Forge                         |forge                         |35.1.1              |DONE      |22:af:21:d8:19:82:7f:93:94:fe:2b:ac:b7:e4:41:57:68:39:87:b1:a7:5c:c6:44:f9:25:74:21:14:f5:0d:90\n\t\tcofh_core-1.16.3-1.0.4.jar                        |CoFH Core                     |cofh_core                     |1.0.4               |DONE      |NOSIGNATURE\n\t\tthermal-1.16.3-1.0.5.jar                          |Thermal Series                |thermal                       |1.0.5               |DONE      |NOSIGNATURE\n\t\tthermal_innovation-1.16.3-1.0.3.jar               |Thermal Innovation            |thermal_innovation            |1.0.3               |DONE      |NOSIGNATURE\n\t\tthermal_locomotion-1.16.3-1.0.3.jar               |Thermal Locomotion            |thermal_locomotion            |1.0.3               |DONE      |NOSIGNATURE\n\t\tPsi 1.16-88.jar                                   |Psi                           |psi                           |1.16-88             |DONE      |NOSIGNATURE\n\t\tthermal_cultivation-1.16.3-1.0.5.jar              |Thermal Cultivation           |thermal_cultivation           |1.0.5               |DONE      |NOSIGNATURE\n\t\tAppleSkin-mc1.16.2-forge-1.0.14.jar               |AppleSkin                     |appleskin                     |1.0.14              |DONE      |NOSIGNATURE\n\t\tAquaculture-1.16.4-2.1.6.jar                      |Aquaculture 2                 |aquaculture                   |1.16.4-2.1.6        |DONE      |NOSIGNATURE\n\t\tmcw-doors-1.0.1fix-mc1.16.4.jar                   |Macaw's Doors                 |mcwdoors                      |1.0.1               |DONE      |NOSIGNATURE\n\t\tLightingWand-1.16.4-1.7.2.jar                     |Lighting Wand                 |lightingwand                  |1.7.2               |DONE      |NOSIGNATURE\n\t\tMekanismGenerators-1.16.4-10.0.17.444.jar         |Mekanism: Generators          |mekanismgenerators            |10.0.17             |DONE      |NOSIGNATURE\n\t\tXNetGases-1.16.3-2.0.0.jar                        |XNet Gases                    |xnetgases                     |2.0.0               |DONE      |NOSIGNATURE\n\t\tftb-teams-1.0.1.12.jar                            |FTB Teams                     |ftbteams                      |1.0.1.12            |DONE      |NOSIGNATURE\n\t\tabsentbydesign-1.16.3-1.1.1.jar                   |Absent By Design Mod          |absentbydesign                |1.16.3-1.1.1        |DONE      |1f:47:ac:b1:61:82:96:b8:47:19:16:d2:61:81:11:60:3a:06:4b:61:31:56:7d:44:31:1e:0c:6f:22:5b:4c:ed\n\t\tmcw-bridges-1.0.4-mc1.16.4.jar                    |Macaw's Bridges               |mcwbridges                    |1.0.4               |DONE      |NOSIGNATURE\n\t\tuseful_backpacks-1.16.4-1.11.6.86.jar             |Useful Backpacks              |usefulbackpacks               |1.11.6.86           |DONE      |f4:a6:0b:ee:cb:8a:1a:ea:9f:9d:45:91:8f:8b:b3:ae:26:f3:bf:05:86:1d:90:9e:f6:32:2a:1a:ed:1d:ce:b0\n\t\tresourcefulbees-1.16.3-0.5.5b.jar                 |Resourceful Bees              |resourcefulbees               |1.16.3-0.5.5b       |DONE      |NOSIGNATURE\n\t\tentangled-1.2.7-mc1.16.4.jar                      |Entangled                     |entangled                     |1.2.7               |DONE      |NOSIGNATURE\n\t\tendertanks-1.16-1.9.3.jar                         |EnderTanks                    |endertanks                    |1.9.3               |DONE      |NOSIGNATURE\n\t\tcrashutilities-3.5.jar                            |Crash Utilities               |crashutilities                |3.5                 |DONE      |NOSIGNATURE\n\t\tCompressium-1.16.4-1.1.36.jar                     |Compressium                   |compressium                   |1.1.36              |DONE      |NOSIGNATURE\n\t\tMekanismAdditions-1.16.4-10.0.17.444.jar          |Mekanism: Additions           |mekanismadditions             |10.0.17             |DONE      |NOSIGNATURE\n\t\tvalkyrielib-1.16.3-3.0.3.2.jar                    |ValkyrieLib                   |valkyrielib                   |1.16.3-3.0.3.2      |DONE      |NOSIGNATURE\n\t\tLollipop-1.16.4-3.2.2.jar                         |Lollipop                      |lollipop                      |3.2.2               |DONE      |NOSIGNATURE\n\t\tftb-ranks-1604.1.1.9.jar                          |FTB Ranks                     |ftbranks                      |1604.1.1.9          |DONE      |NOSIGNATURE\n\t\tsimplylight-1.16.3-1.0.2.jar                      |Simply Light                  |simplylight                   |1.16.3-1.0.2        |DONE      |NOSIGNATURE\n\t\ttgcropesmod-1.16.2-1.1.0.jar                      |Ropes Mod                     |tgcropesmod                   |1.16.2-1.1.0        |DONE      |NOSIGNATURE\n\t\tatmadditions-1.16.2-1.0.0.jar                     |All The Mods: Additions       |atmadditions                  |1.16.2-1.0.0        |DONE      |NOSIGNATURE\n\t\tSolarFluxReborn-1.16.3-16.2.3.jar                 |Solar Flux Reborn             |solarflux                     |16.2.3              |DONE      |NOSIGNATURE\n\t\tPatchouli-1.16.2-47.jar                           |Patchouli                     |patchouli                     |1.16.2-47           |DONE      |NOSIGNATURE\n\t\tcollective-1.16.4-1.53.jar                        |Collective                    |collective                    |1.53                |DONE      |NOSIGNATURE\n\t\tcamera-1.16.4-1.0.4.jar                           |Camera Mod                    |camera                        |1.16.4-1.0.4        |DONE      |NOSIGNATURE\n\t\tthermal_expansion-1.16.3-1.0.5.jar                |Thermal Expansion             |thermal_expansion             |1.0.5               |DONE      |NOSIGNATURE\n\t\tMysticalCustomization-1.16.4-2.1.1.jar            |Mystical Customization        |mysticalcustomization         |2.1.1               |DONE      |NOSIGNATURE\n\t\televatorid-1.16.4-1.7.8.jar                       |Elevator Mod                  |elevatorid                    |1.16.4-1.7.8        |DONE      |NOSIGNATURE\n\t\tftb-ultimine-1603.1.1.12.jar                      |FTB Ultimine                  |ftbultimine                   |1603.1.1.12         |DONE      |NOSIGNATURE\n\t\tRunelic-1.16.4-6.0.2.jar                          |Runelic                       |runelic                       |6.0.2               |DONE      |ea:45:b3:82:b6:9d:50:16:95:e7:2e:34:e1:92:d5:b4:9b:69:90:d3:4f:2e:71:99:b0:be:40:80:27:1f:3e:b0\n\t\tMekanismTools-1.16.4-10.0.17.444.jar              |Mekanism: Tools               |mekanismtools                 |10.0.17             |DONE      |NOSIGNATURE\n\t\tcc-tweaked-1.16.4-1.94.0.jar                      |CC: Tweaked                   |computercraft                 |1.94.0              |DONE      |NOSIGNATURE\n\t\ttrashcans-1.0.3-mc1.16.4.jar                      |Trash Cans                    |trashcans                     |1.0.3               |DONE      |NOSIGNATURE\n\t\tTrampleStopper-2.4.7+mc-1.16.4.jar                |Trample Stopper               |tramplestopper                |2.4.7+mc-1.16.4     |DONE      |23:8f:95:0f:65:ec:2e:38:99:79:f4:bc:47:8a:0b:df:29:ef:2d:82:66:20:09:20:02:dc:4a:15:97:45:f8:43\n\t\tbwncr-1.16.4-3.9.16.jar                           |Bad Wither No Cookie Reloaded |bwncr                         |1.16.4-3.9.16       |DONE      |NOSIGNATURE\n\t\tCyclic-1.16.3-0.9.2.jar                           |Cyclic                        |cyclic                        |1.16.3-0.9.2        |DONE      |1f:47:ac:b1:61:82:96:b8:47:19:16:d2:61:81:11:60:3a:06:4b:61:31:56:7d:44:31:1e:0c:6f:22:5b:4c:ed\n\t\tBetterAdvancements-1.16.4-0.1.0.103.jar           |Better Advancements           |betteradvancements            |0.1.0.103           |DONE      |NOSIGNATURE\n\t\trhino-1.7.13.8.jar                                |Rhino                         |rhino                         |1.7.13.8            |DONE      |NOSIGNATURE\n\t\tkubejs-1604.3.1.80.jar                            |KubeJS                        |kubejs                        |1604.3.1.80         |DONE      |NOSIGNATURE\n\t\tbiggerreactors-1.16.3-0.2.4-beta.jar              |Bigger Reactors               |biggerreactors                |0.2.4               |DONE      |NOSIGNATURE\n\t\tCucumber-1.16.4-4.1.5.jar                         |Cucumber Library              |cucumber                      |4.1.5               |DONE      |NOSIGNATURE\n\t\tTrashSlot_1.16.3-12.2.1.jar                       |TrashSlot                     |trashslot                     |12.2.1              |DONE      |NOSIGNATURE\n\t\tflatbedrock-1.16.4-1.1.6.jar                      |Flat Bedrock                  |flatbedrock                   |1.16.4-1.1.6        |DONE      |NOSIGNATURE\n\t\titem-filters-2.2.2.18.jar                         |Item Filters                  |itemfilters                   |2.2.2.18            |DONE      |NOSIGNATURE\n\t\telementalcraft-1.16.3-2.4.8.jar                   |ElementalCraft                |elementalcraft                |1.16.3-2.4.8        |DONE      |NOSIGNATURE\n\t\tplatforms-1.16-1.7.7.jar                          |Platforms                     |platforms                     |1.7.7               |DONE      |NOSIGNATURE\n\t\tmetalbarrels-3.3a.jar                             |Metal Barrels                 |metalbarrels                  |3.3a                |DONE      |NOSIGNATURE\n\t\tensorcellation-1.16.3-1.0.3.jar                   |Ensorcellation                |ensorcellation                |1.0.3               |DONE      |NOSIGNATURE\n\t\tWaystones_1.16.3-7.3.1.jar                        |Waystones                     |waystones                     |7.3.1               |DONE      |NOSIGNATURE\n\t\tClumps-6.0.0.12.jar                               |Clumps                        |clumps                        |6.0.0.12            |DONE      |NOSIGNATURE\n\t\tenviromats-1.16.3-2.0.3.2.jar                     |Environmental Materials       |enviromats                    |1.16.3-2.0.3.2      |DONE      |NOSIGNATURE\n\t\tcomforts-forge-1.16.4-4.0.0.2.jar                 |Comforts                      |comforts                      |1.16.4-4.0.0.2      |DONE      |NOSIGNATURE\n\t\tappliedenergistics2-8.1.0.jar                     |Applied Energistics 2         |appliedenergistics2           |8.1.0               |DONE      |95:58:cc:83:9d:a8:fa:4f:e9:f3:54:90:66:61:c8:ae:9c:08:88:11:52:52:df:2d:28:5f:05:d8:28:57:0f:98\n\t\tDimStorage-1.16.4-4.3.0.jar                       |DimStorage                    |dimstorage                    |4.3.0               |DONE      |NOSIGNATURE\n\t\tDungeonCrawl-1.16.3-2.2.1.jar                     |Dungeon Crawl                 |dungeoncrawl                  |2.2.1               |DONE      |NOSIGNATURE\n\t\tBadMobs-1.16.4-8.0.2.jar                          |BadMobs                       |badmobs                       |8.0.2               |DONE      |ea:45:b3:82:b6:9d:50:16:95:e7:2e:34:e1:92:d5:b4:9b:69:90:d3:4f:2e:71:99:b0:be:40:80:27:1f:3e:b0\n\t\tdemagnetize-forge-1.16.2-1.2.2.jar                |Demagnetize                   |demagnetize                   |1.16.2-1.2.2        |DONE      |NOSIGNATURE\n\t\tTravelAnchors-2.1.jar                             |Travel Anchors                |travel_anchors                |2.1                 |DONE      |NOSIGNATURE\n\t\tmcjtylib-1.16-5.0.13.jar                          |McJtyLib                      |mcjtylib                      |1.16-5.0.13         |DONE      |NOSIGNATURE\n\t\trftoolsbase-1.16-2.0.6.jar                        |RFToolsBase                   |rftoolsbase                   |1.16-2.0.6          |DONE      |NOSIGNATURE\n\t\txnet-1.16-3.0.9.jar                               |XNet                          |xnet                          |1.16-3.0.9          |DONE      |NOSIGNATURE\n\t\trftoolspower-1.16-3.0.8.jar                       |RFToolsPower                  |rftoolspower                  |1.16-3.0.8          |DONE      |NOSIGNATURE\n\t\trftoolsbuilder-1.16-3.0.10.jar                    |RFToolsBuilder                |rftoolsbuilder                |1.16-3.0.10         |DONE      |NOSIGNATURE\n\t\trftoolsstorage-1.16-2.0.6.jar                     |RFToolsStorage                |rftoolsstorage                |1.16-2.0.6          |DONE      |NOSIGNATURE\n\t\trftoolscontrol-1.16-4.0.6.jar                     |RFToolsControl                |rftoolscontrol                |1.16-4.0.6          |DONE      |NOSIGNATURE\n\t\trestrictions-1.16-3.0.3.jar                       |Restrictions                  |restrictions                  |1.16-3.0.3          |DONE      |NOSIGNATURE\n\t\tmahoutsukai-1.16.3-v1.25.4.jar                    |Mahou Tsukai                  |mahoutsukai                   |1.16.3-v1.25.4      |DONE      |NOSIGNATURE\n\t\tToast-Control-1.16.3-4.3.0.jar                    |Toast Control                 |toastcontrol                  |4.3.0               |DONE      |NOSIGNATURE\n\t\tmininggadgets-1.7.0.jar                           |Mining Gadgets                |mininggadgets                 |1.7.0               |DONE      |NOSIGNATURE\n\t\tftb-chunks-1603.2.0.43.jar                        |FTB Chunks                    |ftbchunks                     |1603.2.0.43         |DONE      |NOSIGNATURE\n\t\tBloodMagic-1.16.3-3.0.0-3.jar                     |Blood Magic                   |bloodmagic                    |1.16.3-3.0.0-3      |DONE      |NOSIGNATURE\n\t\tscuba-gear-1.16.4-1.0.1.jar                       |Scuba Gear                    |scuba_gear                    |1.0.1               |DONE      |NOSIGNATURE\n\t\tMysticalAgriculture-1.16.4-4.1.3.jar              |Mystical Agriculture          |mysticalagriculture           |4.1.3               |DONE      |NOSIGNATURE\n\t\tCraftingTweaks_1.16.3-12.2.0.jar                  |Crafting Tweaks               |craftingtweaks                |12.2.0              |DONE      |NOSIGNATURE\n\t\trftoolsutility-1.16-3.0.17.jar                    |RFToolsUtility                |rftoolsutility                |1.16-3.0.17         |DONE      |NOSIGNATURE\n\t\tEnchantmentDescriptions-1.16.4-6.0.2.jar          |EnchantmentDescriptions       |enchdesc                      |6.0.2               |DONE      |ea:45:b3:82:b6:9d:50:16:95:e7:2e:34:e1:92:d5:b4:9b:69:90:d3:4f:2e:71:99:b0:be:40:80:27:1f:3e:b0\n\t\tToolBelt-1.16.3-1.15.2.jar                        |Tool Belt                     |toolbelt                      |1.15.2              |DONE      |NOSIGNATURE\n\t\ttitanium-1.16.4-3.2.1.jar                         |Titanium                      |titanium                      |3.2.1               |DONE      |NOSIGNATURE\n\t\tSilentLib-1.16.3-4.9.0+63.jar                     |Silent Lib                    |silentlib                     |4.9.0+63            |DONE      |NOSIGNATURE\n\t\tforbidden_arcanus-1.16.3-1.0.2.jar                |Forbidden & Arcanus           |forbidden_arcanus             |1.16.3-1.0.2        |DONE      |NOSIGNATURE\n\t\tarchers_paradox-1.16.3-1.0.0.jar                  |Archer's Paradox              |archers_paradox               |1.0.0               |DONE      |NOSIGNATURE\n\t\tomgourd-1.16.3-1.0.0.jar                          |Oh My Gourd                   |omgourd                       |1.0.0               |DONE      |NOSIGNATURE\n\t\tQuark-r2.4-275.jar                                |Quark                         |quark                         |r2.4-275            |DONE      |NOSIGNATURE\n\t\tApotheosis-1.16.3-4.4.1.jar                       |Apotheosis                    |apotheosis                    |4.4.1               |DONE      |NOSIGNATURE\n\t\tJAOPCA-1.16.3-3.4.0.0.jar                         |JAOPCA                        |jaopca                        |3.4.0.0             |DONE      |NOSIGNATURE\n\t\tFastWorkbench-1.16.3-4.4.1.jar                    |FastWorkbench                 |fastbench                     |4.4.1               |DONE      |NOSIGNATURE\n\t\tStorageDrawers-1.16.3-8.2.1.jar                   |Storage Drawers               |storagedrawers                |8.2.1               |DONE      |NOSIGNATURE\n\t\tFluxNetworks-1.16.4-6.1.5.10.jar                  |Flux Networks                 |fluxnetworks                  |1.16.4-6.1.5.10     |DONE      |NOSIGNATURE\n\t\tenderchests-1.16-1.7.5.jar                        |EnderChests                   |enderchests                   |1.7.5               |DONE      |NOSIGNATURE\n\t\tminecolonies-0.13.437-RELEASE-universal.jar       |Minecolonies                  |minecolonies                  |0.13.437-RELEASE    |DONE      |NOSIGNATURE\n\t\tengineersdecor-1.16.4-1.1.4-b2.jar                |Engineer's Decor              |engineersdecor                |1.1.4-b2            |DONE      |bf:30:76:97:e4:58:41:61:2a:f4:30:d3:8f:4c:e3:71:1d:14:c4:a1:4e:85:36:e3:1d:aa:2f:cb:22:b0:04:9b\n\t\tsolcarrot-1.16.3-1.9.8.jar                        |Spice of Life: Carrot Edition |solcarrot                     |1.16.3-1.9.8        |DONE      |NOSIGNATURE\n\t\tmodular-routers-1.16.3-7.2.0-39.jar               |Modular Routers               |modularrouters                |1.16.3-7.2.0-39     |DONE      |NOSIGNATURE\n\t\trefinedstorageaddons-0.7.2.jar                    |Refined Storage Addons        |refinedstorageaddons          |0.7.2               |DONE      |NOSIGNATURE\n\t\tchiselsandbits-0.2.2-ALPHA.jar                    |Chisels & bits                |chiselsandbits                |NONE                |DONE      |NOSIGNATURE\n\t\tbalancedenchanting-1.0.jar                        |Balanced Enchanting           |balancedenchanting            |1.0                 |DONE      |NOSIGNATURE\n\tCrash Report UUID: 047b2145-90de-404c-aa7e-0c2d1804683f\n\t[Psi] Active spell: None\n\tPatchouli open book context: n/a\n\tPlayer Count: 0 / 8; []\n\tData Packs: vanilla, mod:quarryplus, mod:simplemagnets (incompatible), mod:missingstructurefix (incompatible), mod:mcwwindows (incompatible), mod:modnametooltip, mod:ironjetpacks, mod:reauth, mod:powah, mod:rangedpumps, mod:jumbofurnace (incompatible), mod:wstweaks (incompatible), mod:xreliquary, mod:randompatches (incompatible), mod:waterstrainer, mod:jeresources, mod:confdespawntimer, mod:shetiphiancore, mod:mysticalagradditions (incompatible), mod:emojiful (incompatible), mod:refinedstorage, mod:packmenu (incompatible), mod:alltheores, mod:industrialforegoing (incompatible), mod:cleancut (incompatible), mod:torchmaster (incompatible), mod:repurposed_structures, mod:biomesoplenty, mod:silentgear, mod:portality (incompatible), mod:curios, mod:botania, mod:tanknull (incompatible), mod:angelring (incompatible), mod:extrastorage, mod:naturesaura (incompatible), mod:constructionwand (incompatible), mod:mcwroofs (incompatible), mod:globalxp, mod:cloth-config (incompatible), mod:fastleafdecay (incompatible), mod:bettermineshafts (incompatible), mod:clienttweaks (incompatible), mod:nomowanderer (incompatible), mod:woot, mod:bagofyurting (incompatible), mod:jei, mod:attributefix, mod:mekanism, mod:invtweaks (incompatible), mod:shutupexperimentalsettings (incompatible), mod:lightoverlay-forge (incompatible), mod:naturescompass (incompatible), mod:libx, mod:phosphophyllite (incompatible), mod:engineerstools, mod:farmingforblockheads (incompatible), mod:pneumaticcraft, mod:pedestals (incompatible), mod:extradisks, mod:ironchest (incompatible), mod:mythicbotany, mod:theoneprobe, mod:mousetweaks, mod:immersiveengineering (incompatible), mod:usefulrailroads, mod:pamhc2crops (incompatible), mod:allthemodium, mod:ding, mod:jeiintegration, mod:pamhc2foodcore (incompatible), mod:ftbbackups (incompatible), mod:polymorph (incompatible), mod:autoreglib (incompatible), mod:structurize, mod:pickletweaks, mod:fastfurnace (incompatible), mod:allthetweaks, mod:kubejs, mod:byg, mod:extremesoundmuffler (incompatible), mod:cosmeticarmorreworked (incompatible), mod:morered (incompatible), mod:xpbook, mod:defaultoptions (incompatible), mod:netherportalfix (incompatible), mod:aiotbotania (incompatible), mod:geckolib (incompatible), mod:ars_nouveau, mod:managear, mod:kleeslabs (incompatible), mod:rsgauges, mod:cookingforblockheads (incompatible), mod:controlling, mod:placebo (incompatible), mod:dankstorage (incompatible), mod:citadel (incompatible), mod:iceandfire (incompatible), mod:rats, mod:ratlantis, mod:ftbguilibrary (incompatible), mod:potionsmaster, mod:culinaryconstruct (incompatible), mod:bookshelf, mod:botanypots, mod:uteamcore, mod:buildinggadgets, mod:forge, mod:cofh_core (incompatible), mod:thermal (incompatible), mod:thermal_innovation (incompatible), mod:thermal_locomotion (incompatible), mod:psi (incompatible), mod:thermal_cultivation (incompatible), mod:appleskin, mod:aquaculture (incompatible), mod:mcwdoors, mod:lightingwand, mod:mekanismgenerators, mod:xnetgases, mod:ftbteams (incompatible), mod:absentbydesign (incompatible), mod:mcwbridges (incompatible), mod:usefulbackpacks, mod:resourcefulbees (incompatible), mod:entangled (incompatible), mod:endertanks, mod:crashutilities (incompatible), mod:compressium (incompatible), mod:mekanismadditions, mod:valkyrielib, mod:lollipop, mod:ftbranks (incompatible), mod:simplylight, mod:tgcropesmod (incompatible), mod:atmadditions (incompatible), mod:solarflux, mod:patchouli (incompatible), mod:collective, mod:camera, mod:thermal_expansion (incompatible), mod:mysticalcustomization (incompatible), mod:elevatorid, mod:ftbultimine (incompatible), mod:runelic, mod:darkutils (incompatible), mod:mekanismtools, mod:computercraft, mod:trashcans (incompatible), mod:tramplestopper (incompatible), mod:bwncr (incompatible), mod:cyclic (incompatible), mod:betteradvancements, mod:biggerreactors (incompatible), mod:cucumber, mod:trashslot (incompatible), mod:flatbedrock (incompatible), mod:itemfilters (incompatible), mod:elementalcraft, mod:platforms, mod:metalbarrels (incompatible), mod:ensorcellation (incompatible), mod:waystones (incompatible), mod:clumps (incompatible), mod:enviromats (incompatible), mod:comforts (incompatible), mod:appliedenergistics2 (incompatible), mod:dimstorage, mod:badmobs (incompatible), mod:demagnetize (incompatible), mod:travel_anchors, mod:mcjtylib, mod:rftoolsbase, mod:xnet, mod:rftoolspower, mod:rftoolsbuilder, mod:rftoolsstorage, mod:rftoolscontrol, mod:restrictions (incompatible), mod:mahoutsukai (incompatible), mod:toastcontrol (incompatible), mod:mininggadgets (incompatible), mod:ftbchunks (incompatible), mod:scuba_gear (incompatible), mod:mysticalagriculture, mod:craftingtweaks (incompatible), mod:rftoolsutility, mod:enchdesc, mod:toolbelt (incompatible), mod:titanium (incompatible), mod:silentlib (incompatible), mod:forbidden_arcanus (incompatible), mod:archers_paradox (incompatible), mod:omgourd (incompatible), mod:quark (incompatible), mod:apotheosis (incompatible), mod:jaopca (incompatible), mod:fastbench (incompatible), mod:storagedrawers (incompatible), mod:fluxnetworks, mod:enderchests, mod:minecolonies (incompatible), mod:engineersdecor, mod:solcarrot (incompatible), mod:modularrouters, mod:refinedstorageaddons, mod:chiselsandbits (incompatible), mod:balancedenchanting (incompatible), inmemory:jaopca (incompatible), mod:ftbessentials, mod:dynview (incompatible), mod:tmechworks (incompatible), mod:mcwtrpdoors (incompatible), mod:tombstone (incompatible), mod:immersivepetroleum (incompatible), mod:mantle (incompatible), mod:rhino, mod:dungeoncrawl, mod:bloodmagic\n\tType: Integrated Server (map_client.txt)\n\tIs Modded: Definitely; Client brand changed to 'forge'\n\tOptiFine Version: OptiFine_1.16.3_HD_U_G5\n\tOptiFine Build: 20201106-202857\n\tShaders: null\n\tOpenGlVersion: null\n\tOpenGlRenderer: null\n\tOpenGlVendor: null\n\tCpuCount: 0"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/debug_crash.txt",
    "content": "---- Minecraft Crash Report ----\n// You should try our sister game, Minceraft!\n\nTime: 6.9.2013 16:07\nDescription: Manually triggered debug crash\n\njava.lang.Throwable\n\tat awb.m(SourceFile:1282)\n\tat awb.V(SourceFile:686)\n\tat awb.e(SourceFile:642)\n\tat net.minecraft.client.main.Main.main(SourceFile:103)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat bfi.a(SourceFile:285)\n\n-- Affected level --\nDetails:\n\tLevel name: MpServer\n\tAll players: 1 total; [bfn['TheJonttuT'/304, l='MpServer', x=165,90, y=65,62, z=150,30]]\n\tChunk stats: MultiplayerChunkCache: 225, 225\n\tLevel seed: 0\n\tLevel generator: ID 00 - default, ver 1. Features enabled: false\n\tLevel generator options:\n\tLevel spawn location: World: (200,64,172), Chunk: (at 8,4,12 in 12,10; contains blocks 192,0,160 to 207,255,175), Region: (0,0; contains chunks 0,0 to 31,31, blocks 0,0,0 to 511,255,511)\n\tLevel time: 185537 game time, 277169 day time\n\tLevel dimension: 0\n\tLevel storage version: 0x00000 - Unknown?\n\tLevel weather: Rain time: 0 (now: false), thunder time: 0 (now: false)\n\tLevel game mode: Game mode: creative (ID 1). Hardcore: false. Cheats: false\n\tForced entities: 72 total; [sd['Lepakko'/137, l='MpServer', x=216,41, y=28,88, z=195,31], un['Zombi'/136, l='MpServer', x=206,49, y=18,08, z=168,41], sd['Lepakko'/139, l='MpServer', x=213,25, y=23,10, z=202,75], ui['Luuranko'/138, l='MpServer', x=223,50, y=26,00, z=199,50], un['Zombi'/141, l='MpServer', x=203,35, y=26,00, z=220,88], un['Zombi'/135, l='MpServer', x=227,22, y=39,00, z=173,31], ui['Luuranko'/134, l='MpServer', x=208,66, y=21,00, z=167,91], tw['Lurkki'/152, l='MpServer', x=236,50, y=52,00, z=171,50], tw['Lurkki'/153, l='MpServer', x=238,09, y=14,00, z=188,47], tw['Lurkki'/154, l='MpServer', x=239,06, y=14,00, z=187,47], un['Zombi'/155, l='MpServer', x=234,50, y=13,00, z=181,50], ui['Luuranko'/156, l='MpServer', x=218,30, y=33,00, z=190,70], uk['Hämähäkki'/157, l='MpServer', x=231,63, y=45,00, z=176,72], sd['Lepakko'/158, l='MpServer', x=232,09, y=48,20, z=205,34], un['Zombi'/159, l='MpServer', x=239,50, y=39,00, z=205,50], sd['Lepakko'/145, l='MpServer', x=214,41, y=47,10, z=225,75], sg['Lehmä'/31, l='MpServer', x=88,38, y=65,00, z=108,47], tw['Lurkki'/150, l='MpServer', x=229,50, y=44,00, z=155,50], sf['Kana'/151, l='MpServer', x=233,47, y=68,00, z=140,66], un['Zombi'/34, l='MpServer', x=93,03, y=41,00, z=139,63], bfn['TheJonttuT'/304, l='MpServer', x=165,90, y=65,62, z=150,30], un['Zombi'/33, l='MpServer', x=90,66, y=42,00, z=139,34], tw['Lurkki'/175, l='MpServer', x=242,50, y=42,00, z=184,50], tw['Lurkki'/172, l='MpServer', x=243,50, y=17,00, z=162,50], sg['Lehmä'/42, l='MpServer', x=109,16, y=72,00, z=175,66], so['Lammas'/161, l='MpServer', x=229,28, y=63,00, z=223,50], sg['Lehmä'/41, l='MpServer', x=109,66, y=71,00, z=160,25], sg['Lehmä'/51, l='MpServer', x=127,63, y=73,00, z=126,50], sg['Lehmä'/50, l='MpServer', x=127,50, y=73,00, z=97,50], tw['Lurkki'/54, l='MpServer', x=120,50, y=17,00, z=216,50], sd['Lepakko'/531, l='MpServer', x=229,47, y=47,57, z=224,56], sg['Lehmä'/53, l='MpServer', x=112,16, y=72,00, z=173,22], sg['Lehmä'/52, l='MpServer', x=119,69, y=72,00, z=171,25], uk['Hämähäkki'/179, l='MpServer', x=241,00, y=22,33, z=202,84], sg['Lehmä'/63, l='MpServer', x=146,50, y=73,00, z=89,38], sg['Lehmä'/62, l='MpServer', x=139,63, y=75,00, z=84,38], sg['Lehmä'/61, l='MpServer', x=131,22, y=73,00, z=93,31], un['Zombi'/68, l='MpServer', x=136,50, y=25,00, z=143,50], sd['Lepakko'/69, l='MpServer', x=137,28, y=24,51, z=137,84], ui['Luuranko'/70, l='MpServer', x=139,31, y=21,00, z=130,38], uk['Hämähäkki'/71, l='MpServer', x=140,50, y=21,00, z=129,59], tw['Lurkki'/64, l='MpServer', x=143,13, y=20,00, z=126,50], sd['Lepakko'/65, l='MpServer', x=145,47, y=20,00, z=124,31], sg['Lehmä'/66, l='MpServer', x=132,47, y=75,00, z=117,50], sg['Lehmä'/67, l='MpServer', x=130,50, y=75,00, z=117,50], un['Zombi'/85, l='MpServer', x=148,50, y=22,00, z=118,50], un['Zombi'/84, l='MpServer', x=150,56, y=22,00, z=122,25], ui['Luuranko'/87, l='MpServer', x=145,53, y=22,00, z=127,13], tw['Lurkki'/86, l='MpServer', x=159,50, y=22,00, z=123,50], sg['Lehmä'/83, l='MpServer', x=149,88, y=71,00, z=103,88], sh['Hevonen'/89, l='MpServer', x=163,91, y=64,00, z=140,16], sd['Lepakko'/88, l='MpServer', x=144,75, y=19,82, z=125,34], tw['Lurkki'/90, l='MpServer', x=152,50, y=14,00, z=172,50], sd['Lepakko'/102, l='MpServer', x=159,56, y=51,00, z=195,47], sr['Kalmari'/100, l='MpServer', x=172,53, y=59,41, z=175,53], sr['Kalmari'/101, l='MpServer', x=167,72, y=62,25, z=184,50], sr['Kalmari'/98, l='MpServer', x=168,47, y=59,00, z=164,47], sr['Kalmari'/99, l='MpServer', x=173,35, y=61,33, z=162,02], sg['Lehmä'/119, l='MpServer', x=196,38, y=67,00, z=172,56], ui['Luuranko'/118, l='MpServer', x=208,34, y=17,00, z=168,25], sr['Kalmari'/117, l='MpServer', x=204,50, y=58,38, z=146,84], sg['Lehmä'/116, l='MpServer', x=201,78, y=63,00, z=159,72], sg['Lehmä'/115, l='MpServer', x=193,94, y=66,00, z=99,06], sg['Lehmä'/114, l='MpServer', x=188,09, y=66,00, z=100,06], sg['Lehmä'/113, l='MpServer', x=201,66, y=64,00, z=96,28], sg['Lehmä'/112, l='MpServer', x=198,22, y=64,00, z=85,22], tw['Lurkki'/125, l='MpServer', x=193,63, y=22,00, z=225,66], ui['Luuranko'/124, l='MpServer', x=201,50, y=26,00, z=218,50], ui['Luuranko'/123, l='MpServer', x=199,56, y=26,00, z=216,94], sd['Lepakko'/122, l='MpServer', x=204,06, y=24,10, z=181,06], sg['Lehmä'/121, l='MpServer', x=197,47, y=66,00, z=166,69], sg['Lehmä'/120, l='MpServer', x=200,97, y=64,00, z=163,97]]\n\tRetry entities: 0 total; []\n\tServer brand: vanilla\n\tServer type: Integrated singleplayer server\nStacktrace:\n\tat bfi.a(SourceFile:285)\n\tat awb.b(SourceFile:1813)\n\tat awb.e(SourceFile:651)\n\tat net.minecraft.client.main.Main.main(SourceFile:103)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 13w36b\n\tOperating System: Windows 7 (x86) version 6.1\n\tJava Version: 1.7.0_25, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) Client VM (mixed mode, sharing), Oracle Corporation\n\tMemory: 103857920 bytes (99 MB) / 248131584 bytes (236 MB) up to 518979584 bytes (494 MB)\n\tJVM Flags: 1 total; -Xmx512M\n\tAABB Pool Size: 7721 (432376 bytes; 0 MB) allocated, 2 (112 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 13, tallocated: 95\n\tLaunched Version: 13w36b\n\tLWJGL: 2.9.0\n\tOpenGL: GeForce 9400M G/PCI/SSE2 GL version 3.3.0, NVIDIA Corporation\n\tIs Modded: Probably not. Jar signature remains and client brand is untouched.\n\tType: Client (map_client.txt)\n\tResource Packs: []\n\tCurrent Language: suomi (FI)\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 3083 (172648 bytes; 0 MB) allocated, 12 (672 bytes; 0 MB) used"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/file_already_exists.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\nContact their authors BEFORE contacting forge\n\n// There are four lights!\n\nTime: 1/6/19 2:12 AM\nDescription: There was a severe problem during mod loading that has caused the game to fail\n\nnet.minecraftforge.fml.common.LoaderExceptionModCrash: Caught exception from Better PvP (xaerobetterpvp)\nCaused by: java.nio.file.FileAlreadyExistsException: D:\\Games\\Minecraft\\Minecraft Longtimeusing\\.minecraft\\versions\\1.12.2-forge1.12.2-14.23.5.2775\\config\\pvpsettings.txt\n\tat sun.nio.fs.WindowsFileCopy.move(Unknown Source)\n\tat sun.nio.fs.WindowsFileSystemProvider.move(Unknown Source)\n\tat java.nio.file.Files.move(Unknown Source)\n\tat xaero.pvp.BetterPVP.preInit(BetterPVP.java:105)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraftforge.fml.common.FMLModContainer.handleModStateEvent(FMLModContainer.java:624)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:87)\n\tat com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:144)\n\tat com.google.common.eventbus.Subscriber$1.run(Subscriber.java:72)\n\tat com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)\n\tat com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:67)\n\tat com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:108)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:212)\n\tat net.minecraftforge.fml.common.LoadController.sendEventToModContainer(LoadController.java:218)\n\tat net.minecraftforge.fml.common.LoadController.propogateStateMessage(LoadController.java:196)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:87)\n\tat com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:144)\n\tat com.google.common.eventbus.Subscriber$1.run(Subscriber.java:72)\n\tat com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)\n\tat com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:67)\n\tat com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:108)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:212)\n\tat net.minecraftforge.fml.common.LoadController.distributeStateMessage(LoadController.java:135)\n\tat net.minecraftforge.fml.common.Loader.preinitializeMods(Loader.java:627)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:252)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:466)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:377)\n\tat net.minecraft.client.main.Main.main(SourceFile:123)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.12.2\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_192, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 163542816 bytes (155 MB) / 419430400 bytes (400 MB) up to 8506048512 bytes (8112 MB)\n\tJVM Flags: 11 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Xmn128m -Xmx8104m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP 9.42 Powered by Forge 14.23.5.2775 Optifine OptiFine_1.12.2_HD_U_E2 8 mods loaded, 8 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\n\t| State | ID             | Version      | Source                                  | Signature                                |\n\t|:----- |:-------------- |:------------ |:--------------------------------------- |:---------------------------------------- |\n\t| UCH   | minecraft      | 1.12.2       | minecraft.jar                           | None                                     |\n\t| UCH   | mcp            | 9.42         | minecraft.jar                           | None                                     |\n\t| UCH   | FML            | 8.0.99.99    | forge-1.12.2-14.23.5.2775.jar           | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| UCH   | forge          | 14.23.5.2775 | forge-1.12.2-14.23.5.2775.jar           | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| UCEE  | xaerobetterpvp | 1.15.9       | BetterPvPFairPlay_1.15.9_Forge_1.12.jar | None                                     |\n\t| UC    | hud            | 1.3.9        | hud-1.3.9-1.12.jar                      | None                                     |\n\t| UC    | quickplay      | 2.0.3        | Quickplay-1.12-2.0.3.jar                | None                                     |\n\t| UC    | level_head     | 5.0          | Sk1er Levelhead (1.12.2)-5.0.jar        | None                                     |\n\n\tLoaded coremods (and transformers):\n\tGL info: ' Vendor: 'Intel' Version: '4.5.0 - Build 25.20.100.6373' Renderer: 'Intel(R) HD Graphics 530'\n\tOptiFine Version: OptiFine_1.12.2_HD_U_E2\n\tOptiFine Build: 20180728-185429\n\tRender Distance Chunks: 8\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.5.0 - Build 25.20.100.6373\n\tOpenGlRenderer: Intel(R) HD Graphics 530\n\tOpenGlVendor: Intel\n\tCpuCount: 4"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/forge_error.txt",
    "content": "[19:28:17] [main/INFO] [LaunchWrapper]: Loading tweak class name net.minecraftforge.fml.common.launcher.FMLTweaker\n[19:28:17] [main/INFO] [LaunchWrapper]: Using primary tweak class name net.minecraftforge.fml.common.launcher.FMLTweaker\n[19:28:17] [main/INFO] [LaunchWrapper]: Calling tweak class net.minecraftforge.fml.common.launcher.FMLTweaker\n[19:28:17] [main/INFO] [FML]: Forge Mod Loader version 14.23.5.2829 for Minecraft 1.12.2 loading\n[19:28:17] [main/INFO] [FML]: Java is Java HotSpot(TM) 64-Bit Server VM, version 1.8.0_131, running on Windows 7:amd64:6.1, installed at C:\\Program Files\\Java\\jdk1.8.0_131\\jre\n[19:28:17] [main/INFO] [FML]: Searching D:\\GAME\\wdsj\\.minecraft\\versions\\1.12.2-Forge_14.23.5.2829\\mods for mods\n[19:28:17] [main/INFO] [LaunchWrapper]: Loading tweak class name net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker\n[19:28:17] [main/INFO] [LaunchWrapper]: Loading tweak class name net.minecraftforge.fml.common.launcher.FMLDeobfTweaker\n[19:28:17] [main/INFO] [LaunchWrapper]: Calling tweak class net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker\n[19:28:17] [main/INFO] [LaunchWrapper]: Calling tweak class net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker\n[19:28:17] [main/INFO] [LaunchWrapper]: Calling tweak class net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper\n[19:28:18] [main/INFO] [FML]: Found valid fingerprint for Minecraft Forge. Certificate fingerprint e3c3d50c7c986df74c645c0ac54639741c90a557\n[19:28:18] [main/INFO] [FML]: Found valid fingerprint for Minecraft. Certificate fingerprint cd99959656f753dc28d863b46769f7f8fbaefcfc\n[19:28:18] [main/INFO] [LaunchWrapper]: Calling tweak class net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper\n[19:28:18] [main/INFO] [LaunchWrapper]: Calling tweak class net.minecraftforge.fml.common.launcher.FMLDeobfTweaker\n[19:28:19] [main/INFO] [LaunchWrapper]: Loading tweak class name net.minecraftforge.fml.common.launcher.TerminalTweaker\n[19:28:19] [main/INFO] [LaunchWrapper]: Calling tweak class net.minecraftforge.fml.common.launcher.TerminalTweaker\n[19:28:19] [main/INFO] [LaunchWrapper]: Launching wrapped minecraft {net.minecraft.client.main.Main}\n[19:28:19] [main/INFO] [STDOUT]: [net.minecraft.client.main.Main:main:55]: Completely ignored arguments: [480, PCL2, ]\n[19:28:20] [Client thread/INFO] [minecraft/Minecraft]: Setting user: kuk\n[19:28:23] [Client thread/WARN] [minecraft/GameSettings]: Skipping bad option: \n[19:28:23] [Client thread/WARN] [minecraft/GameSettings]: Skipping bad option: \n[19:28:23] [Client thread/WARN] [minecraft/GameSettings]: Skipping bad option: \n[19:28:23] [Client thread/WARN] [minecraft/GameSettings]: Skipping bad option: \n[19:28:23] [Client thread/INFO] [minecraft/Minecraft]: LWJGL Version: 2.9.4\n[19:28:24] [Client thread/INFO] [FML]: -- System Details --\nDetails:\n\tMinecraft Version: 1.12.2\n\tOperating System: Windows 7 (amd64) version 6.1\n\tJava Version: 1.8.0_131, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 35712520 bytes (34 MB) / 400556032 bytes (382 MB) up to 11811160064 bytes (11264 MB)\n\tJVM Flags: 6 total; -XX:+UseG1GC -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmn256m -Xmx11264m\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: \n\tLoaded coremods (and transformers): \n\tGL info: ' Vendor: 'Intel' Version: '4.3.0 - Build 10.18.14.5180' Renderer: 'Intel(R) HD Graphics 4600'\n[19:28:24] [Client thread/INFO] [FML]: MinecraftForge v14.23.5.2829 Initialized\n[19:28:24] [Client thread/INFO] [FML]: Starts to replace vanilla recipe ingredients with ore ingredients.\n[19:28:24] [Thread-3/ERROR] [FML]: Splash thread Exception\njava.lang.IllegalStateException: Texture creation: Invalid enum\n\tat net.minecraftforge.fml.client.SplashProgress.checkGLError(SplashProgress.java:937) ~[SplashProgress.class:?]\n\tat net.minecraftforge.fml.client.SplashProgress$Texture.<init>(SplashProgress.java:803) ~[SplashProgress$Texture.class:?]\n\tat net.minecraftforge.fml.client.SplashProgress$Texture.<init>(SplashProgress.java:753) ~[SplashProgress$Texture.class:?]\n\tat net.minecraftforge.fml.client.SplashProgress$2.run(SplashProgress.java:255) ~[SplashProgress$2.class:?]\n\tat java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]\n[19:28:24] [Client thread/INFO] [FML]: Replaced 1036 ore ingredients\n[19:28:24] [Client thread/INFO] [FML]: Searching D:\\GAME\\wdsj\\.minecraft\\versions\\1.12.2-Forge_14.23.5.2829\\mods for mods\n[19:28:25] [Client thread/INFO] [FML]: Forge Mod Loader has identified 6 mods to load\n[19:28:26] [Client thread/FATAL] [FML]: net.minecraftforge.fml.common.MissingModsException: Mod pixelmon (Pixelmon) requires [forge@[14.23.5.2860,)]\n[19:28:26] [Client thread/ERROR] [FML]: An exception was thrown, the game will display an error screen and halt.\nnet.minecraftforge.fml.common.MissingModsException: Mod pixelmon (Pixelmon) requires [forge@[14.23.5.2860,)]\n\tat net.minecraftforge.fml.common.Loader.sortModList(Loader.java:264) ~[Loader.class:?]\n\tat net.minecraftforge.fml.common.Loader.loadMods(Loader.java:570) ~[Loader.class:?]\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:232) [FMLClientHandler.class:?]\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:467) [bib.class:?]\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:378) [bib.class:?]\n\tat net.minecraft.client.main.Main.main(SourceFile:123) [Main.class:?]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_131]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_131]\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_131]\n\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_131]\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135) [launchwrapper-1.12.jar:?]\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.12.jar:?]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_131]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_131]\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_131]\n\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_131]\n\tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58) [JavaWrapper.jar:?]\n\tat oolloo.jlw.Wrapper.main(Wrapper.java:51) [JavaWrapper.jar:?]\n[19:28:26] [Client thread/WARN] [FML]: EventBus 0 shutting down - future events will not be posted.\n[19:28:26] [Client thread/INFO] [minecraft/SimpleReloadableResourceManager]: Reloading ResourceManager: Default, FMLFileResourcePack:Forge Mod Loader, FMLFileResourcePack:Minecraft Forge, FMLFileResourcePack:Pixelmon, FMLFileResourcePack:Pixelmon TCG"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/graphics_driver.txt",
    "content": "---- Minecraft Crash Report ----\n// There are four lights!\n\nTime: 19-8-7 下午7:05\nDescription: Initializing game\n\norg.lwjgl.LWJGLException: Pixel format not accelerated\n\tat org.lwjgl.opengl.WindowsPeerInfo.nChoosePixelFormat(Native Method)\n\tat org.lwjgl.opengl.WindowsPeerInfo.choosePixelFormat(WindowsPeerInfo.java:52)\n\tat org.lwjgl.opengl.WindowsDisplay.createWindow(WindowsDisplay.java:252)\n\tat org.lwjgl.opengl.Display.createWindow(Display.java:306)\n\tat org.lwjgl.opengl.Display.create(Display.java:848)\n\tat org.lwjgl.opengl.Display.create(Display.java:757)\n\tat org.lwjgl.opengl.Display.create(Display.java:739)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:454)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:877)\n\tat net.minecraft.client.main.Main.main(SourceFile:148)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat org.lwjgl.opengl.WindowsPeerInfo.nChoosePixelFormat(Native Method)\n\tat org.lwjgl.opengl.WindowsPeerInfo.choosePixelFormat(WindowsPeerInfo.java:52)\n\tat org.lwjgl.opengl.WindowsDisplay.createWindow(WindowsDisplay.java:252)\n\tat org.lwjgl.opengl.Display.createWindow(Display.java:306)\n\tat org.lwjgl.opengl.Display.create(Display.java:848)\n\tat org.lwjgl.opengl.Display.create(Display.java:757)\n\tat org.lwjgl.opengl.Display.create(Display.java:739)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:454)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:877)\n\tat net.minecraft.client.main.Main.main(SourceFile:148)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_60, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 70760384 bytes (67 MB) / 188170240 bytes (179 MB) up to 487849984 bytes (465 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx478M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML:\n\tLaunched Version: 1.7.10\n\tLWJGL: 2.9.1\n\tOpenGL: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.\n\tGL Caps:\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: []\n\tCurrent Language: ~~ERROR~~ NullPointerException: null\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tAnisotropic Filtering: Off (1)"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/loader_exception_mod_crash.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\nContact their authors BEFORE contacting forge\n\n// There are four lights!\n\nTime: 1/6/19 2:12 AM\nDescription: There was a severe problem during mod loading that has caused the game to fail\n\nnet.minecraftforge.fml.common.LoaderExceptionModCrash: Caught exception from Better PvP (xaerobetterpvp)\nCaused by: java.nio.file.FileAlreadyExistsException: D:\\Games\\Minecraft\\Minecraft Longtimeusing\\.minecraft\\versions\\1.12.2-forge1.12.2-14.23.5.2775\\config\\pvpsettings.txt\n\tat sun.nio.fs.WindowsFileCopy.move(Unknown Source)\n\tat sun.nio.fs.WindowsFileSystemProvider.move(Unknown Source)\n\tat java.nio.file.Files.move(Unknown Source)\n\tat xaero.pvp.BetterPVP.preInit(BetterPVP.java:105)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraftforge.fml.common.FMLModContainer.handleModStateEvent(FMLModContainer.java:624)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:87)\n\tat com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:144)\n\tat com.google.common.eventbus.Subscriber$1.run(Subscriber.java:72)\n\tat com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)\n\tat com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:67)\n\tat com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:108)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:212)\n\tat net.minecraftforge.fml.common.LoadController.sendEventToModContainer(LoadController.java:218)\n\tat net.minecraftforge.fml.common.LoadController.propogateStateMessage(LoadController.java:196)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:87)\n\tat com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:144)\n\tat com.google.common.eventbus.Subscriber$1.run(Subscriber.java:72)\n\tat com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)\n\tat com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:67)\n\tat com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:108)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:212)\n\tat net.minecraftforge.fml.common.LoadController.distributeStateMessage(LoadController.java:135)\n\tat net.minecraftforge.fml.common.Loader.preinitializeMods(Loader.java:627)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:252)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:466)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:377)\n\tat net.minecraft.client.main.Main.main(SourceFile:123)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.12.2\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_192, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 163542816 bytes (155 MB) / 419430400 bytes (400 MB) up to 8506048512 bytes (8112 MB)\n\tJVM Flags: 11 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Xmn128m -Xmx8104m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP 9.42 Powered by Forge 14.23.5.2775 Optifine OptiFine_1.12.2_HD_U_E2 8 mods loaded, 8 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\n\t| State | ID             | Version      | Source                                  | Signature                                |\n\t|:----- |:-------------- |:------------ |:--------------------------------------- |:---------------------------------------- |\n\t| UCH   | minecraft      | 1.12.2       | minecraft.jar                           | None                                     |\n\t| UCH   | mcp            | 9.42         | minecraft.jar                           | None                                     |\n\t| UCH   | FML            | 8.0.99.99    | forge-1.12.2-14.23.5.2775.jar           | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| UCH   | forge          | 14.23.5.2775 | forge-1.12.2-14.23.5.2775.jar           | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| UCEE  | xaerobetterpvp | 1.15.9       | BetterPvPFairPlay_1.15.9_Forge_1.12.jar | None                                     |\n\t| UC    | hud            | 1.3.9        | hud-1.3.9-1.12.jar                      | None                                     |\n\t| UC    | quickplay      | 2.0.3        | Quickplay-1.12-2.0.3.jar                | None                                     |\n\t| UC    | level_head     | 5.0          | Sk1er Levelhead (1.12.2)-5.0.jar        | None                                     |\n\n\tLoaded coremods (and transformers):\n\tGL info: ' Vendor: 'Intel' Version: '4.5.0 - Build 25.20.100.6373' Renderer: 'Intel(R) HD Graphics 530'\n\tOptiFine Version: OptiFine_1.12.2_HD_U_E2\n\tOptiFine Build: 20180728-185429\n\tRender Distance Chunks: 8\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.5.0 - Build 25.20.100.6373\n\tOpenGlRenderer: Intel(R) HD Graphics 530\n\tOpenGlVendor: Intel\n\tCpuCount: 4"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/loader_exception_mod_crash2.txt",
    "content": "---- Minecraft Crash Report ----\n// On the bright side, I bought you a teddy bear!\n\nTime: 3/28/19 9:52 PM\nDescription: There was a severe problem during mod loading that has caused the game to fail\n\nnet.minecraftforge.fml.common.LoaderExceptionModCrash: Caught exception from Inventory Sort (invsort)\nCaused by: java.lang.IllegalAccessError: tried to access field net.minecraft.client.settings.KeyBinding.field_74512_d from class com.netease.invsort.InvTweaksObfuscation\n\tat com.netease.invsort.InvTweaksObfuscation.getKeyBindingForwardKeyCode(InvTweaksObfuscation.java:272)\n\tat com.netease.invsort.InvTweaksHandlerShortcuts.loadShortcuts(InvTweaksHandlerShortcuts.java:60)\n\tat com.netease.invsort.InvTweaksConfigManager.loadConfig(InvTweaksConfigManager.java:177)\n\tat com.netease.invsort.InvTweaksConfigManager.makeSureConfigurationIsLoaded(InvTweaksConfigManager.java:93)\n\tat com.netease.invsort.InvTweaks.<init>(InvTweaks.java:116)\n\tat com.netease.invsort.forge.ClientProxy.init(ClientProxy.java:52)\n\tat com.netease.invsort.forge.InvTweaksMod.init(InvTweaksMod.java:49)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraftforge.fml.common.FMLModContainer.handleModStateEvent(FMLModContainer.java:627)\n\tat sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:91)\n\tat com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:150)\n\tat com.google.common.eventbus.Subscriber$1.run(Subscriber.java:76)\n\tat com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:399)\n\tat com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:71)\n\tat com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:116)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:217)\n\tat net.minecraftforge.fml.common.LoadController.sendEventToModContainer(LoadController.java:218)\n\tat net.minecraftforge.fml.common.LoadController.propogateStateMessage(LoadController.java:196)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:91)\n\tat com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:150)\n\tat com.google.common.eventbus.Subscriber$1.run(Subscriber.java:76)\n\tat com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:399)\n\tat com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:71)\n\tat com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:116)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:217)\n\tat net.minecraftforge.fml.common.LoadController.distributeStateMessage(LoadController.java:135)\n\tat net.minecraftforge.fml.common.Loader.initializeMods(Loader.java:744)\n\tat net.minecraftforge.fml.client.FMLClientHandler.finishMinecraftLoading(FMLClientHandler.java:329)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:534)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:377)\n\tat net.minecraft.client.main.Main.main(SourceFile:123)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:141)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:23)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.12.2\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_60, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 145947240 bytes (139 MB) / 320770048 bytes (305 MB) up to 8576565248 bytes (8179 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx8191M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP 9.42 Powered by Forge 14.23.4.2705 20 mods loaded, 20 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\n\t| State | ID                 | Version      | Source                        | Signature                                |\n\t|:----- |:------------------ |:------------ |:----------------------------- |:---------------------------------------- |\n\t| UCHI  | minecraft          | 1.12.2       | minecraft.jar                 | None                                     |\n\t| UCHI  | mcp                | 9.42         | minecraft.jar                 | None                                     |\n\t| UCHI  | FML                | 8.0.99.99    | forge-1.12.2-14.23.4.2705.jar | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| UCHI  | forge              | 14.23.4.2705 | forge-1.12.2-14.23.4.2705.jar | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| UCHI  | mercurius_updater  | 1.0          | MercuriusUpdater-1.12.2.jar   | None                                     |\n\t| UCHI  | networkmod         | 1.11.2       | 4620273834451558259@3@0.jar   | None                                     |\n\t| UCHI  | antimod            | 2.0          | 4620273834210283309@3@0.jar   | None                                     |\n\t| UCHI  | filtermod          | 1.0          | 4620273834266845199@3@0.jar   | None                                     |\n\t| UCHI  | friendplaymod      | 1.0          | 4620273834395780067@3@0.jar   | None                                     |\n\t| UCHI  | updatecore         | 1.0          | 4620608844487847104@3@0.jar   | None                                     |\n\t| UCHI  | playermanager      | 1.0          | 4620702976810361335@3@0.jar   | None                                     |\n\t| UCHI  | mcbasemod          | 1.0          | 4621632218832071536@3@0.jar   | None                                     |\n\t| UCHI  | opencommand        | 1.0          | 4624104029872303148@3@0.jar   | None                                     |\n\t| UCHI  | fullscreenpopup    | 1.12.2.38000 | 4624104029891423116@3@0.jar   | None                                     |\n\t| UCHI  | departmod          | 1.0          | 4625678897404525717@3@0.jar   | None                                     |\n\t| UCHI  | skinmod            | 1.0          | 4626894585322620077@3@0.jar   | None                                     |\n\t| UCHI  | gotcha             | 1.12         | 77135129230705664@3@15.jar    | None                                     |\n\t| UCHEE | invsort            | 79317-1.8    | 77135129230705664@3@15.jar    | None                                     |\n\t| UCH   | neteasemcassistant | 1.0.1        | 77135129230705664@3@15.jar    | None                                     |\n\t| UCH   | minimap            | 2.1.12       | 77135129230705664@3@15.jar    | None                                     |\n\n\tLoaded coremods (and transformers):"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/loader_exception_mod_crash3.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\n  BedPatch (bedpatch-2.2-1.12.2.jar)\n  AppleCore (AppleCore-mc1.12.2-3.2.0.jar)\n  Do not report to Forge! Remove FoamFixAPI (or replace with FoamFixAPI-Lawful) and try again. (FoamFix-1.12.2-0.9.4-Anarchy.jar)\n  CustomSkinLoader ([万用皮肤补丁]CustomSkinLoader_Forge-14.10a.jar)\n  CXLibraryCore (cxlibrary-1.12.1-1.6.1.jar)\n  LesslagCorePlugin (lesslag-1.0-1.12.2.jar)\n  CTMCorePlugin (CTM-MC1.12.2-0.3.3.22.jar)\n  JechCore (JustEnoughCharacters-1.12.0-3.3.0.jar)\n  Inventory Tweaks Coremod (InventoryTweaks-1.63.jar)\n  ItemPatchingLoader (ItemPhysic+Lite+1.3.7+mc1.12.2.jar)\n  DynamicSurroundingsCore (DynamicSurroundings-core-1.12.2-3.5.4.3.jar)\n  OpenModsCorePlugin (OpenModsLib-1.12.2-0.12.1.jar)\n  LoadingPlugin (ChunkAnimator-MC1.12-1.2.jar)\nContact their authors BEFORE contacting forge\n\n// Surprise! Haha. Well, this is awkward.\n\nTime: 4/9/19 5:07 PM\nDescription: There was a severe problem during mod loading that has caused the game to fail\n\nnet.minecraftforge.fml.common.LoaderExceptionModCrash: Caught exception from SuperOres (superores)\nCaused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0\n\tat java.util.ArrayList.rangeCheck(Unknown Source)\n\tat java.util.ArrayList.get(Unknown Source)\n\tat net.minecraft.util.NonNullList.get(SourceFile:44)\n\tat abused_master.superores.blocks.BlockOreBase.<init>(BlockOreBase.java:66)\n\tat abused_master.superores.registry.ModResources.register(ModResources.java:33)\n\tat abused_master.superores.proxy.CommonProxy.preInit(CommonProxy.java:22)\n\tat abused_master.superores.proxy.ClientProxy.preInit(ClientProxy.java:23)\n\tat abused_master.superores.SuperOres.preinit(SuperOres.java:25)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraftforge.fml.common.FMLModContainer.handleModStateEvent(FMLModContainer.java:624)\n\tat sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:91)\n\tat com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:150)\n\tat com.google.common.eventbus.Subscriber$1.run(Subscriber.java:76)\n\tat com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:399)\n\tat com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:71)\n\tat com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:116)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:217)\n\tat net.minecraftforge.fml.common.LoadController.sendEventToModContainer(LoadController.java:219)\n\tat net.minecraftforge.fml.common.LoadController.propogateStateMessage(LoadController.java:197)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:91)\n\tat com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:150)\n\tat com.google.common.eventbus.Subscriber$1.run(Subscriber.java:76)\n\tat com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:399)\n\tat com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:71)\n\tat com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:116)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:217)\n\tat net.minecraftforge.fml.common.LoadController.distributeStateMessage(LoadController.java:136)\n\tat net.minecraftforge.fml.common.Loader.preinitializeMods(Loader.java:627)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:252)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:467)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:378)\n\tat net.minecraft.client.main.Main.main(SourceFile:123)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.12.2\n\tOperating System: Windows 7 (amd64) version 6.1\n\tJava Version: 1.8.0_101, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 933328144 bytes (890 MB) / 1509949440 bytes (1440 MB) up to 8539602944 bytes (8144 MB)\n\tJVM Flags: 11 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Xmn128m -Xmx8132m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP 9.42 Powered by Forge 14.23.5.2811 Optifine OptiFine_1.12.2_HD_U_E3 87 mods loaded, 87 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\n\t| State | ID                   | Version                  | Source                                             | Signature                                |\n\t|:----- |:-------------------- |:------------------------ |:-------------------------------------------------- |:---------------------------------------- |\n\t| LCH   | minecraft            | 1.12.2                   | minecraft.jar                                      | None                                     |\n\t| LCH   | mcp                  | 9.42                     | minecraft.jar                                      | None                                     |\n\t| LCH   | FML                  | 8.0.99.99                | forge-1.12.2-14.23.5.2811.jar                      | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| LCH   | forge                | 14.23.5.2811             | forge-1.12.2-14.23.5.2811.jar                      | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| LCH   | itemphysic           | 1.4.0                    | minecraft.jar                                      | None                                     |\n\t| LCH   | jecharacters         | 1.12.0-3.3.0             | JustEnoughCharacters-1.12.0-3.3.0.jar              | None                                     |\n\t| LCH   | openmodscore         | 0.12.1                   | minecraft.jar                                      | None                                     |\n\t| LCH   | foamfixcore          | 7.7.4                    | minecraft.jar                                      | None                                     |\n\t| LCH   | lesslag              | 1.0                      | minecraft.jar                                      | None                                     |\n\t| LCH   | dsurroundcore        | 3.5.4.3                  | minecraft.jar                                      | None                                     |\n\t| LCH   | deconstruction       | 3.0.4                    | [MC1.12.2]DeconTable-3.0.4.jar                     | None                                     |\n\t| LCH   | customskinloader     | 14.10a                   | [万用皮肤补丁]CustomSkinLoader_Forge-14.10a.jar          | 52885f395e68f42e9b3b629ba56ecf606f7d4269 |\n\t| LCH   | applecore            | 3.2.0                    | AppleCore-mc1.12.2-3.2.0.jar                       | None                                     |\n\t| LCH   | jei                  | 4.15.0.268               | jei_1.12.2-4.15.0.268.jar                          | None                                     |\n\t| LCH   | appleskin            | 1.0.9                    | AppleSkin-mc1.12-1.0.9.jar                         | None                                     |\n\t| LCH   | ctm                  | MC1.12.2-0.3.3.22        | CTM-MC1.12.2-0.3.3.22.jar                          | None                                     |\n\t| LCH   | appliedenergistics2  | rv6-stable-6             | appliedenergistics2-rv6-stable-6.jar               | dfa4d3ac143316c6f32aa1a1beda1e34d42132e5 |\n\t| LCH   | codechickenlib       | 3.2.2.353                | CodeChickenLib-1.12.2-3.2.2.353-universal.jar      | f1850c39b2516232a2108a7bd84d1cb5df93b261 |\n\t| LCH   | avaritia             | 3.3.0                    | Avaritia-1.12.2-3.3.0.33-universal.jar             | None                                     |\n\t| LCH   | betterbuilderswands  | 0.13.2                   | BetterBuildersWands-1.12.2-0.13.2.271+5997513.jar  | None                                     |\n\t| LCH   | biomesoplenty        | 7.0.1.2419               | BiomesOPlenty-1.12.2-7.0.1.2419-universal.jar      | None                                     |\n\t| LCH   | bookshelf            | 2.3.577                  | Bookshelf-1.12.2-2.3.577.jar                       | d476d1b22b218a10d845928d1665d45fce301b27 |\n\t| LCH   | redstoneflux         | 2.1.0                    | RedstoneFlux-1.12-2.1.0.6-universal.jar            | 8a6abf2cb9e141b866580d369ba6548732eff25f |\n\t| LCH   | brandonscore         | 2.4.9                    | BrandonsCore-1.12.2-2.4.9.195-universal.jar        | None                                     |\n\t| LCH   | chisel               | MC1.12.2-0.2.1.35        | Chisel-MC1.12.2-0.2.1.35.jar                       | None                                     |\n\t| LCH   | chiselsandbits       | 14.31                    | chiselsandbits-14.31.jar                           | None                                     |\n\t| LCH   | chunkanimator        | 1.2                      | ChunkAnimator-MC1.12-1.2.jar                       | None                                     |\n\t| LCH   | cravings             | 1.0.11                   | Cravings-1.12.2-1.0.11.jar                         | d476d1b22b218a10d845928d1665d45fce301b27 |\n\t| LCH   | cxlibrary            | 1.6.1                    | cxlibrary-1.12.1-1.6.1.jar                         | None                                     |\n\t| LCH   | dimensionaledibles   | 1.3                      | DimensionalEdibles-1.12.2-1.3.jar                  | 4ffa87db52cf086d00ecc4853a929367b1c39b5c |\n\t| LCH   | draconicevolution    | 2.3.20                   | Draconic-Evolution-1.12.2-2.3.20.333-universal.jar | None                                     |\n\t| LCH   | elevatorid           | 1.3.8                    | ElevatorMod-1.12.2-1.3.8.jar                       | None                                     |\n\t| LCH   | mantle               | 1.12-1.3.3.49            | Mantle-1.12-1.3.3.49.jar                           | None                                     |\n\t| LCH   | twilightforest       | 3.8.689                  | twilightforest-1.12.2-3.8.689-universal.jar        | None                                     |\n\t| LCH   | tconstruct           | 1.12.2-2.12.0.135        | TConstruct-1.12.2-2.12.0.135.jar                   | None                                     |\n\t| LCH   | extrautils2          | 1.0                      | extrautils2-1.12-1.9.9.jar                         | None                                     |\n\t| LCH   | fastleafdecay        | v14                      | FastLeafDecay-v14.jar                              | None                                     |\n\t| LCH   | foamfix              | 0.9.4-1.12.2             | FoamFix-1.12.2-0.9.4-Anarchy.jar                   | None                                     |\n\t| LCH   | gpick                | 4.2                      | GPickaxe2-1.12.2-4.2.jar                           | None                                     |\n\t| LCH   | growthcraft          | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | growthcraft_apples   | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | growthcraft_bamboo   | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | growthcraft_cellar   | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | growthcraft_bees     | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | growthcraft_fishtrap | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | growthcraft_grapes   | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | growthcraft_hops     | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | growthcraft_milk     | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | growthcraft_rice     | 4.0.4.500                | growthcraft-1.12.2-4.0.4.500.jar                   | None                                     |\n\t| LCH   | harvest              | 1.12-1.2.7-20            | Harvest-1.12-1.2.7-20.jar                          | None                                     |\n\t| LCH   | ichunutil            | 7.2.1                    | iChunUtil-1.12.2-7.2.1.jar                         | 4db5c2bd1b556f252a5b8b54b256d381b2a0a6b8 |\n\t| LCH   | hats                 | 7.0.0                    | Hats-1.12.2-7.0.2.jar                              | None                                     |\n\t| LCH   | i18nmod              | 1.12.2-1.0.8             | i18nupdatemod-1.12.2-1.0.8.jar                     | None                                     |\n\t| LCH   | ic2                  | 2.8.999                  | IC2Classic+1.12-1.5.4.2.jar                        | None                                     |\n\t| LCH   | ic2-classic-spmod    | 0.0.0.0                  | IC2Classic+1.12-1.5.4.2.jar                        | None                                     |\n\t| LCH   | ias                  | 7.0.3                    | InGameAccountSwitcher-Forge-1.12-7.0.3.jar         | None                                     |\n\t| LCH   | inventorytweaks      | 1.63+release.109.220f184 | InventoryTweaks-1.63.jar                           | 55d2cd4f5f0961410bf7b91ef6c6bf00a766dcbe |\n\t| LCH   | earthenbounty        | 2.1.6                    | just_a_few_more_ores-2.1.6.jar                     | None                                     |\n\t| LCH   | justenoughbuttons    | 1.12-1.2                 | justenoughbuttons-1.12.2-1.2.3.jar                 | None                                     |\n\t| LCH   | keepinginventory     | 2.4                      | KeepingInventory-1.12.2-2.4.jar                    | None                                     |\n\t| LCH   | mobends              | 0.24                     | mobends-0.24_for_MC-1.12.jar                       | None                                     |\n\t| LCH   | mod_autofish         | 1.12-1.7                 | mod_autofish_forge-1.12-1.7.jar                    | None                                     |\n\t| LCH   | mousetweaks          | 2.10                     | MouseTweaks-2.10-mc1.12.2.jar                      | None                                     |\n\t| LCH   | multipagechest       | 1.9.1                    | MultiPageChest-1.12-1.9.1.jar                      | None                                     |\n\t| LCH   | naturescompass       | 1.5.1                    | NaturesCompass-1.12.2-1.5.1.jar                    | None                                     |\n\t| LCH   | openmods             | 0.12.1                   | OpenModsLib-1.12.2-0.12.1.jar                      | d2a9a8e8440196e26a268d1f3ddc01b2e9c572a5 |\n\t| LCH   | openblocks           | 1.8                      | OpenBlocks-1.12.2-1.8.jar                          | d2a9a8e8440196e26a268d1f3ddc01b2e9c572a5 |\n\t| LCH   | getalltheseeds       | 1.12a                    | Pam's+Get+all+the+Seeds!+1.12a.jar                 | None                                     |\n\t| LCH   | harvestcraft         | 1.12.2zb                 | Pam's+HarvestCraft+1.12.2zb.jar                    | None                                     |\n\t| LCH   | pamscookables        | 1.1                      | pamscookables-1.1.jar                              | None                                     |\n\t| LCH   | pamsimpleharvest     | 2.0.0                    | pamsimpleharvest-2.0.0.jar                         | None                                     |\n\t| LCH   | pinklysheep          | 4.0.0b8                  | pinklysheep-mc1.12.2-4.0b8.jar                     | None                                     |\n\t| LCH   | projecte             | 1.12.2-PE1.4.0           | ProjectE-1.12.2-PE1.4.0.jar                        | None                                     |\n\t| LCH   | texfix               | 4.0                      | TexFix+V-1.12-4.0.jar                              | None                                     |\n\t| LCH   | tinkersjei           | 1.1                      | tinkersjei-1.1.jar                                 | None                                     |\n\t| LCH   | tinkertoolleveling   | 1.12.2-1.1.0.DEV.b23e769 | TinkerToolLeveling-1.12.2-1.1.0.jar                | None                                     |\n\t| LCH   | tofucraft            | 0.0.2.1                  | TofuCraftReload-0.0.2.1.jar                        | None                                     |\n\t| LCH   | torcherino           | 7.6                      | torcherino-7.6.jar                                 | None                                     |\n\t| LCH   | trashslot            | 8.4.6                    | TrashSlot_1.12.1-8.4.6.jar                         | None                                     |\n\t| LCH   | veinminer            | 0.38.2                   | VeinMiner-1.12-0.38.2.647+b31535a.jar              | None                                     |\n\t| LCH   | veinminermodsupport  | 0.38.2                   | VeinMiner-1.12-0.38.2.647+b31535a.jar              | None                                     |\n\t| LCH   | kiwi                 | 0.5.2.29                 | Kiwi-1.12.2-0.5.2.29.jar                           | None                                     |\n\t| LCH   | cuisine              | 0.5.0-build838           | Cuisine-0.5.0-build838.jar                         | None                                     |\n\t| LCH   | orelib               | 3.5.2.2                  | OreLib-1.12.2-3.5.2.2.jar                          | 7a2128d395ad96ceb9d9030fbd41d035b435753a |\n\t| LCH   | dsurround            | 3.5.4.3                  | DynamicSurroundings-1.12.2-3.5.4.3.jar             | 7a2128d395ad96ceb9d9030fbd41d035b435753a |\n\t| LCH   | coffeework           | @version@                | CoffeeWorkshop_V1.2.5_MC1.12.2.jar                 | None                                     |\n\t| LCE   | superores            | 1.9.0_1.12               | superores-1.9.0-1.12.jar                           | None                                     |\n\n\tLoaded coremods (and transformers):\nBedPatch (bedpatch-2.2-1.12.2.jar)\n  com.mordenkainen.bedpatch.BedPatchASM\nAppleCore (AppleCore-mc1.12.2-3.2.0.jar)\n  squeek.applecore.asm.TransformerModuleHandler\nDo not report to Forge! Remove FoamFixAPI (or replace with FoamFixAPI-Lawful) and try again. (FoamFix-1.12.2-0.9.4-Anarchy.jar)\n  pl.asie.foamfix.coremod.FoamFixTransformer\nCustomSkinLoader ([万用皮肤补丁]CustomSkinLoader_Forge-14.10a.jar)\n  customskinloader.forge.loader.LaunchWrapper\nCXLibraryCore (cxlibrary-1.12.1-1.6.1.jar)\n  cubex2.cxlibrary.CoreModTransformer\nLesslagCorePlugin (lesslag-1.0-1.12.2.jar)\n\nCTMCorePlugin (CTM-MC1.12.2-0.3.3.22.jar)\n  team.chisel.ctm.client.asm.CTMTransformer\nJechCore (JustEnoughCharacters-1.12.0-3.3.0.jar)\n  me.towdium.jecharacters.core.JechClassTransformer\nInventory Tweaks Coremod (InventoryTweaks-1.63.jar)\n  invtweaks.forge.asm.ContainerTransformer\nItemPatchingLoader (ItemPhysic+Lite+1.3.7+mc1.12.2.jar)\n  com.creativemd.itemphysic.ItemTransformer\nDynamicSurroundingsCore (DynamicSurroundings-core-1.12.2-3.5.4.3.jar)\n  org.orecruncher.dsurround.asm.Transformer\nOpenModsCorePlugin (OpenModsLib-1.12.2-0.12.1.jar)\n  openmods.core.OpenModsClassTransformer\nLoadingPlugin (ChunkAnimator-MC1.12-1.2.jar)\n  lumien.chunkanimator.asm.ClassTransformer\n\tGL info: ' Vendor: 'NVIDIA Corporation' Version: '4.6.0 NVIDIA 419.17' Renderer: 'GeForce GTX 1050/PCIe/SSE2'\n\tOpenModsLib class transformers: [llama_null_fix:FINISHED],[horse_base_null_fix:FINISHED],[pre_world_render_hook:FINISHED],[player_render_hook:FINISHED],[horse_null_fix:FINISHED]\n\tAE2 Version: stable rv6-stable-6 for Forge 14.23.5.2768\n\tPulsar/tconstruct loaded Pulses:\n\t\t- TinkerCommons (Enabled/Forced)\n\t\t- TinkerWorld (Enabled/Not Forced)\n\t\t- TinkerTools (Enabled/Not Forced)\n\t\t- TinkerHarvestTools (Enabled/Forced)\n\t\t- TinkerMeleeWeapons (Enabled/Forced)\n\t\t- TinkerRangedWeapons (Enabled/Forced)\n\t\t- TinkerModifiers (Enabled/Forced)\n\t\t- TinkerSmeltery (Enabled/Not Forced)\n\t\t- TinkerGadgets (Enabled/Not Forced)\n\t\t- TinkerOredict (Enabled/Forced)\n\t\t- TinkerIntegration (Enabled/Forced)\n\t\t- TinkerFluids (Enabled/Forced)\n\t\t- TinkerMaterials (Enabled/Forced)\n\t\t- TinkerModelRegister (Enabled/Forced)\n\t\t- chiselIntegration (Enabled/Not Forced)\n\t\t- chiselsandbitsIntegration (Enabled/Not Forced)\n\n\tOptiFine Version: OptiFine_1.12.2_HD_U_E3\n\tOptiFine Build: 20181210-121000\n\tRender Distance Chunks: 8\n\tMipmaps: 0\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.6.0 NVIDIA 419.17\n\tOpenGlRenderer: GeForce GTX 1050/PCIe/SSE2\n\tOpenGlVendor: NVIDIA Corporation\n\tCpuCount: 6"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/loader_exception_mod_crash4.txt",
    "content": "---- Minecraft Crash Report ----\n// Don't do that.\n\nTime: 2019-12-31 11:43:31 CST\nDescription: Initializing game\n\nnet.minecraftforge.fml.common.LoaderExceptionModCrash: Caught exception from Kathairis (kathairis)\nCaused by: java.lang.IllegalArgumentException: Failed to register dimension for id 2, One is already registered\n    at net.minecraftforge.common.DimensionManager.registerDimension(DimensionManager.java:134)\n    at mod.krevik.KCore.dimRegistry(KCore.java:493)\n    at mod.krevik.KCore.preInit(KCore.java:799)\n    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n    at java.lang.reflect.Method.invoke(Unknown Source)\n    at net.minecraftforge.fml.common.FMLModContainer.handleModStateEvent(FMLModContainer.java:637)\n    at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)\n    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n    at java.lang.reflect.Method.invoke(Unknown Source)\n    at com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:91)\n    at com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:150)\n    at com.google.common.eventbus.Subscriber$1.run(Subscriber.java:76)\n    at com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:399)\n    at com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:71)\n    at com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:116)\n    at com.google.common.eventbus.EventBus.post(EventBus.java:217)\n    at net.minecraftforge.fml.common.LoadController.sendEventToModContainer(LoadController.java:219)\n    at net.minecraftforge.fml.common.LoadController.propogateStateMessage(LoadController.java:197)\n    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n    at java.lang.reflect.Method.invoke(Unknown Source)\n    at com.google.common.eventbus.Subscriber.invokeSubscriberMethod(Subscriber.java:91)\n    at com.google.common.eventbus.Subscriber$SynchronizedSubscriber.invokeSubscriberMethod(Subscriber.java:150)\n    at com.google.common.eventbus.Subscriber$1.run(Subscriber.java:76)\n    at com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:399)\n    at com.google.common.eventbus.Subscriber.dispatchEvent(Subscriber.java:71)\n    at com.google.common.eventbus.Dispatcher$PerThreadQueuedDispatcher.dispatch(Dispatcher.java:116)\n    at com.google.common.eventbus.EventBus.post(EventBus.java:217)\n    at net.minecraftforge.fml.common.LoadController.distributeStateMessage(LoadController.java:136)\n    at net.minecraftforge.fml.common.Loader.preinitializeMods(Loader.java:627)\n    at net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:252)\n    at net.minecraft.client.Minecraft.init(Minecraft.java:467)\n    at net.minecraft.client.Minecraft.run(Minecraft.java:3931)\n    at net.minecraft.client.main.Main.main(SourceFile:123)\n    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n    at java.lang.reflect.Method.invoke(Unknown Source)\n    at net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n    at net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\n  Minecraft Version: 1.12.2\n  Operating System: Windows 7 (amd64) version 6.1\n  Java Version: 1.8.0_121, Oracle Corporation\n  Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n  Memory: 2673556512 bytes (2549 MB) / 3674210304 bytes (3504 MB) up to 3674210304 bytes (3504 MB)\n  JVM Flags: 11 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Xmn128m -Xmx3500m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n  IntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n  FML: MCP 9.42 Powered by Forge 14.23.5.2825 22 mods loaded, 22 mods active\n       States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\n       | State | ID                  | Version                  | Source                                            | Signature                                |\n       |:----- |:------------------- |:------------------------ |:------------------------------------------------- |:---------------------------------------- |\n       | LCH   | minecraft           | 1.12.2                   | minecraft.jar                                     | None                                     |\n       | LCH   | mcp                 | 9.42                     | minecraft.jar                                     | None                                     |\n       | LCH   | FML                 | 8.0.99.99                | forge-1.12.2-14.23.5.2825.jar                     | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n       | LCH   | forge               | 14.23.5.2825             | forge-1.12.2-14.23.5.2825.jar                     | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n       | LCH   | foamfixcore         | 7.7.4                    | minecraft.jar                                     | None                                     |\n       | LCH   | damageindicatorsmod | 1.0                      | [1.12.2]血量显示.jar                                  | None                                     |\n       | LCH   | inventorytweaks     | 1.63+release.109.220f184 | [R建整理]InventoryTweaks-1.63.jar                    | 55d2cd4f5f0961410bf7b91ef6c6bf00a766dcbe |\n       | LCH   | foamfix             | 0.10.3-1.12.2            | [内存优化]foamfix-0.10.3-1.12.2.jar                   | None                                     |\n       | LCH   | codechickenlib      | 3.2.2.353                | [前置]CodeChickenLib-1.12.2-3.2.2.353-universal.jar | f1850c39b2516232a2108a7bd84d1cb5df93b261 |\n       | LCH   | recipehandler       | 0.13                     | [合成冲突消除器]NoMoreRecipeConflict-0.13(1.12.2).jar    | None                                     |\n       | LCH   | lucky               | 7.5.0                    | [幸运方块]LuckyBlock_1-12_v7-5-0.jar                  | None                                     |\n       | LCH   | jei                 | 4.15.0.287               | jei_1.12.2-4.15.0.287.jar                         | None                                     |\n       | LCH   | projecte            | 1.12.2-PE1.4.0           | [等价交换]ProjectE-1.12.2-PE1.4.0.jar                 | None                                     |\n       | LCH   | durabilityshow      | 5.0.0                    | [耐久显示]Durability+Show-1.12-5.0.0.jar              | None                                     |\n       | LCH   | oreexcavation       | 1.4.137                  | [连锁挖矿]OreExcavation-1.4.137.jar                   | None                                     |\n       | LCH   | vanillafix          | 1.0.10-SNAPSHOT          | [防止崩溃]VanillaFix-1.0.10-99.jar                    | None                                     |\n       | LCH   | hardcorequesting    | @VERSION@                | HQM-1.12-5.4.0-hotfix1.jar                        | None                                     |\n       | LCH   | journeymap          | 1.12-5.4.9               | journeymap-1.12-5.4.9.jar                         | None                                     |\n       | LCH   | kamenridercraft4th  | 1.3                      | KamenRiderCraft4th+-+1.12.2+-+1.4.jar             | None                                     |\n       | LCE   | kathairis           | 1.12.2-1.6.0-beta        | Kathairis-1.12.2-1.6.0-beta.jar                   | None                                     |\n       | LC    | nei                 | 2.4.2                    | NotEnoughItems-1.12.2-2.4.2.244-universal.jar     | f1850c39b2516232a2108a7bd84d1cb5df93b261 |\n       | LC    | aoa3                | 3.1.2.b                  | 虚无世界3.jar                                         | None                                     |\n  Loaded coremods (and transformers): Do not report to Forge! (If you haven't disabled the FoamFix coremod, try disabling it in the config! Note that this bit of text will still appear.) ([内存优化]foamfix-0.10.3-1.12.2.jar)\n                                        pl.asie.foamfix.coremod.FoamFixTransformer\n                                      VanillaFixLoadingPlugin ([防止崩溃]VanillaFix-1.0.10-99.jar)\n\n                                      Inventory Tweaks Coremod ([R建整理]InventoryTweaks-1.63.jar)\n                                        invtweaks.forge.asm.ContainerTransformer\n  GL info: ' Vendor: 'Intel' Version: '4.0.0 - Build 10.18.10.4425' Renderer: 'Intel(R) HD Graphics'\n  Suspected Mods: Kathairis (kathairis)\n  Launched Version: HMCL 3.2.139\n  LWJGL: 2.9.4\n  OpenGL: Intel(R) HD Graphics GL version 4.0.0 - Build 10.18.10.4425, Intel\n  GL Caps: Using GL 1.3 multitexturing.\n           Using GL 1.3 texture combiners.\n           Using framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\n           Shaders are available because OpenGL 2.1 is supported.\n           VBOs are available because OpenGL 1.5 is supported.\n  Using VBOs: Yes\n  Is Modded: Definitely; Client brand changed to 'fml,forge'\n  Type: Client (map_client.txt)\n  Resource Packs:\n  Current Language: 简体中文 (中国)\n  Profiler Position: N/A (disabled)\n  CPU: 2x Intel(R) Celeron(R) CPU G1620 @ 2.70GHz\n"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/loading_error_fabric.txt",
    "content": "---- Minecraft Crash Report ----\n// You're mean.\n\nTime: 2021/9/21 上午2:06\nDescription: Initializing game\n\njava.lang.RuntimeException: Could not execute entrypoint stage 'main' due to errors, provided by 'test'!\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke0(EntrypointUtils.java:50)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke(EntrypointUtils.java:33)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointClient.start(EntrypointClient.java:33)\n\tat net.minecraft.class_310.<init>(class_310.java:457)\n\tat net.minecraft.client.main.Main.main(Main.java:179)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:567)\n\tat net.fabricmc.loader.game.MinecraftGameProvider.launch(MinecraftGameProvider.java:234)\n\tat net.fabricmc.loader.launch.knot.Knot.launch(Knot.java:153)\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:28)\nCaused by: java.lang.IllegalArgumentException: QAQ!\n\tat net.fabricmc.example.ExampleMod.onInitialize(ExampleMod.java:14)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke0(EntrypointUtils.java:47)\n\t... 11 more\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Render thread\nStacktrace:\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke0(EntrypointUtils.java:50)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke(EntrypointUtils.java:33)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointClient.start(EntrypointClient.java:33)\n\tat net.minecraft.class_310.<init>(class_310.java:457)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.main.Main.main(Main.java:179)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:567)\n\tat net.fabricmc.loader.game.MinecraftGameProvider.launch(MinecraftGameProvider.java:234)\n\tat net.fabricmc.loader.launch.knot.Knot.launch(Knot.java:153)\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:28)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.17.1\n\tMinecraft Version ID: 1.17.1\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 16.0.1, Microsoft\n\tJava VM Version: OpenJDK 64-Bit Server VM (mixed mode, sharing), Microsoft\n\tMemory: 3268182032 bytes (3116 MiB) / 3506438144 bytes (3344 MiB) up to 4294967296 bytes (4096 MiB)\n\tCPUs: 12\n\tProcessor Vendor: GenuineIntel\n\tProcessor Name: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\n\tIdentifier: Intel64 Family 6 Model 158 Stepping 10\n\tMicroarchitecture: Coffee Lake\n\tFrequency (GHz): 2.59\n\tNumber of physical packages: 1\n\tNumber of physical CPUs: 6\n\tNumber of logical CPUs: 12\n\tGraphics card #0 name: NVIDIA GeForce GTX 1660 Ti\n\tGraphics card #0 vendor: NVIDIA (0x10de)\n\tGraphics card #0 VRAM (MB): 4095.00\n\tGraphics card #0 deviceId: 0x2191\n\tGraphics card #0 versionInfo: DriverVersion=27.21.14.6172\n\tMemory slot #0 capacity (MB): 8192.00\n\tMemory slot #0 clockSpeed (GHz): 2.67\n\tMemory slot #0 type: DDR4\n\tMemory slot #1 capacity (MB): 8192.00\n\tMemory slot #1 clockSpeed (GHz): 2.67\n\tMemory slot #1 type: DDR4\n\tVirtual memory max (MB): 53167.24\n\tVirtual memory used (MB): 39480.58\n\tSwap memory total (MB): 36864.00\n\tSwap memory used (MB): 4051.20\n\tJVM Flags: 12 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -Xmn128m -Xmx4096m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tLaunched Version: 1.17.1s\n\tBackend library: LWJGL version 3.2.2 build 10\n\tBackend API: NO CONTEXT\n\tWindow size: <not initialized>\n\tGL Caps: Using framebuffer using OpenGL 3.2\n\tGL debug messages: <disabled>\n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'fabric'\n\tType: Client (map_client.txt)\n\tCPU: <unknown>\n#@!@# Game crashed! Crash report saved to: #@!@# C:\\Users\\huang\\AppData\\Roaming\\.minecraft\\crash-reports\\crash-2021-09-21_02.06.21-client.txt\n"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/bettersprinting.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\n  NeteaseCore (4619774556351054392@3@0.jar)\n  SkinCore (4626894634154779079@3@0.jar)\n  BaseModNeteaseCore (4620273813196076442@3@0.jar)\n  InputFix (4618424574399199550@3@0.jar)\n  RandFTCore (4618421856281952104@2@33.jar)\nContact their authors BEFORE contacting forge\n\n// Hi. I'm Minecraft, and I'm a crashaholic.\n\nTime: 19-1-20 上午10:58\nDescription: Ticking entity\n\njava.lang.IllegalAccessError: tried to access field net.minecraft.client.entity.EntityPlayerSP.field_71156_d from class chylex.bettersprinting.client.player.impl.LivingUpdate\n\tat chylex.bettersprinting.client.player.impl.LivingUpdate.callPreSuper(LivingUpdate.java:18)\n\tat chylex.bettersprinting.client.player.impl.PlayerOverride.func_70636_d(PlayerOverride.java:33)\n\tat net.minecraft.entity.EntityLivingBase.func_70071_h_(EntityLivingBase.java:1614)\n\tat net.minecraft.entity.player.EntityPlayer.func_70071_h_(EntityPlayer.java:283)\n\tat net.minecraft.client.entity.EntityPlayerSP.func_70071_h_(EntityPlayerSP.java:117)\n\tat net.minecraft.world.World.func_72866_a(World.java:1860)\n\tat net.minecraft.world.World.func_72870_g(World.java:1829)\n\tat net.minecraft.world.World.func_72939_s(World.java:1661)\n\tat net.minecraft.client.Minecraft.func_71407_l(Minecraft.java:2082)\n\tat net.minecraft.client.Minecraft.func_71411_J(Minecraft.java:1017)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:351)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat chylex.bettersprinting.client.player.impl.LivingUpdate.callPreSuper(LivingUpdate.java:18)\n\tat chylex.bettersprinting.client.player.impl.PlayerOverride.func_70636_d(PlayerOverride.java:33)\n\tat net.minecraft.entity.EntityLivingBase.func_70071_h_(EntityLivingBase.java:1614)\n\tat net.minecraft.entity.player.EntityPlayer.func_70071_h_(EntityPlayer.java:283)\n\tat net.minecraft.client.entity.EntityPlayerSP.func_70071_h_(EntityPlayerSP.java:117)\n\tat net.minecraft.world.World.func_72866_a(World.java:1860)\n\tat net.minecraft.world.World.func_72870_g(World.java:1829)\n\n-- Entity being ticked --\nDetails:\n\tEntity Type: null (chylex.bettersprinting.client.player.impl.PlayerOverride)\n\tEntity ID: 2833344\n\tEntity Name: 屠仙召唤师1\n\tEntity's Exact location: -25.07, 70.05, 1.57\n\tEntity's Block location: -26.00,70.00,1.00 - World: (-26,70,1), Chunk: (at 6,4,1 in -2,0; contains blocks -32,0,0 to -17,255,15), Region: (-1,0; contains chunks -32,0 to -1,31, blocks -512,0,0 to -1,255,511)\n\tEntity's Momentum: 0.00, 0.00, 0.00\n\tEntity's Rider: ~~ERROR~~ NullPointerException: null\n\tEntity's Vehicle: ~~ERROR~~ NullPointerException: null\nStacktrace:\n\tat net.minecraft.world.World.func_72939_s(World.java:1661)\n\n-- Affected level --\nDetails:\n\tLevel name: MpServer\n\tAll players: 33 total; [PlayerOverride['屠仙召唤师1'/2833344, l='MpServer', x=-25.07, y=70.05, z=1.57], EntityOtherPlayerMP['Mmqlipe'/2832646, l='MpServer', x=-21.94, y=70.00, z=0.13], EntityOtherPlayerMP['Aghikt'/2833156, l='MpServer', x=-23.77, y=70.00, z=0.35], EntityOtherPlayerMP['任凭暴雪纷飞'/2832666, l='MpServer', x=-22.78, y=70.00, z=0.81], EntityOtherPlayerMP['summer_殇恋'/2833248, l='MpServer', x=-25.00, y=70.03, z=1.63], EntityOtherPlayerMP['单身汪carzy'/2831244, l='MpServer', x=-17.53, y=68.00, z=1.41], EntityOtherPlayerMP['BARRYDCY'/2830748, l='MpServer', x=-23.81, y=70.00, z=0.13], EntityOtherPlayerMP['mc小禾禾'/2832081, l='MpServer', x=-2.50, y=67.00, z=7.89], EntityOtherPlayerMP['逗比牛是真的逗比'/2827646, l='MpServer', x=-13.63, y=67.00, z=9.16], EntityOtherPlayerMP['二次元ren'/2830953, l='MpServer', x=-25.41, y=70.00, z=-0.09], EntityOtherPlayerMP['暗影作者'/2818626, l='MpServer', x=-22.78, y=70.00, z=-0.31], EntityOtherPlayerMP['梦晨丶辉'/2833153, l='MpServer', x=-23.16, y=70.03, z=-0.69], EntityOtherPlayerMP['gu_chen'/2833342, l='MpServer', x=-25.16, y=70.03, z=-0.66], EntityOtherPlayerMP['上进豹猫的飞'/2831854, l='MpServer', x=-24.94, y=70.00, z=-0.44], EntityOtherPlayerMP['隔山打牛丨患幻'/2832942, l='MpServer', x=-0.19, y=67.00, z=-0.63], EntityOtherPlayerMP['c4ru4rp9p9'/55, l='MpServer', x=-8.50, y=67.00, z=-4.50], EntityOtherPlayerMP['辣鸡翅'/2830962, l='MpServer', x=-8.18, y=68.00, z=-7.38], EntityOtherPlayerMP['luksm1'/2828598, l='MpServer', x=-7.59, y=68.00, z=-7.97], EntityOtherPlayerMP['放大阿萨姆'/2831633, l='MpServer', x=-5.44, y=67.80, z=-8.03], EntityOtherPlayerMP['rd65d5wf57'/0, l='MpServer', x=3.50, y=67.00, z=-14.50], EntityOtherPlayerMP['n7m0k9a63m'/6, l='MpServer', x=1.50, y=68.00, z=-5.50], EntityOtherPlayerMP['w1rc9838y8'/7, l='MpServer', x=3.50, y=68.00, z=-2.50], EntityOtherPlayerMP['YlNb'/2830955, l='MpServer', x=12.36, y=75.50, z=3.53], EntityOtherPlayerMP['阳光主播秋日'/2829917, l='MpServer', x=9.63, y=67.00, z=1.03], EntityOtherPlayerMP['24vnu41f3t'/1, l='MpServer', x=10.50, y=67.00, z=0.50], EntityOtherPlayerMP['pa63qhv6ql'/2, l='MpServer', x=-0.50, y=68.00, z=9.50], EntityOtherPlayerMP['4o0yglp9p4'/4, l='MpServer', x=3.50, y=68.00, z=3.50], EntityOtherPlayerMP['9i6ei17ml9'/5, l='MpServer', x=1.50, y=68.00, z=6.50], EntityOtherPlayerMP['pater得得520'/2832550, l='MpServer', x=-14.57, y=69.14, z=-22.54], EntityOtherPlayerMP['YoloYap'/2832181, l='MpServer', x=-1.95, y=69.17, z=-22.41], EntityOtherPlayerMP['敲帅的骚鱼鸭丶'/2831850, l='MpServer', x=-8.79, y=69.15, z=-20.27], EntityOtherPlayerMP['九凤最帅'/2831536, l='MpServer', x=-24.59, y=57.23, z=-31.83], EntityOtherPlayerMP['海神修罗唐三'/2820695, l='MpServer', x=-72.50, y=76.72, z=-7.61]]\n\tChunk stats: MultiplayerChunkCache: 45, 45\n\tLevel seed: 0\n\tLevel generator: ID 00 - default, ver 1. Features enabled: false\n\tLevel generator options:\n\tLevel spawn location: -26.00,70.00,1.00 - World: (-26,70,1), Chunk: (at 6,4,1 in -2,0; contains blocks -32,0,0 to -17,255,15), Region: (-1,0; contains chunks -32,0 to -1,31, blocks -512,0,0 to -1,255,511)\n\tLevel time: 236429046 game time, 389000 day time\n\tLevel dimension: 0\n\tLevel storage version: 0x00000 - Unknown?\n\tLevel weather: Rain time: 0 (now: false), thunder time: 0 (now: false)\n\tLevel game mode: Game mode: survival (ID 0). Hardcore: false. Cheats: false\n\tForced entities: 218 total; [EntityOtherPlayerMP['24vnu41f3t'/1, l='MpServer', x=10.50, y=67.00, z=0.50], PlayerOverride['屠仙召唤师1'/2833344, l='MpServer', x=-25.07, y=70.05, z=1.57], EntityOtherPlayerMP['pa63qhv6ql'/2, l='MpServer', x=-0.50, y=68.00, z=9.50], EntityArmorStand['§e§l1,035名玩家'/3, l='MpServer', x=-0.50, y=67.88, z=9.50], EntityOtherPlayerMP['Mmqlipe'/2832646, l='MpServer', x=-21.94, y=70.00, z=0.13], EntityOtherPlayerMP['4o0yglp9p4'/4, l='MpServer', x=3.50, y=68.00, z=3.50], EntityOtherPlayerMP['Aghikt'/2833156, l='MpServer', x=-23.77, y=70.00, z=0.35], EntityOtherPlayerMP['任凭暴雪纷飞'/2832666, l='MpServer', x=-22.78, y=70.00, z=0.81], EntityOtherPlayerMP['9i6ei17ml9'/5, l='MpServer', x=1.50, y=68.00, z=6.50], EntityOtherPlayerMP['n7m0k9a63m'/6, l='MpServer', x=1.50, y=68.00, z=-5.50], EntityOtherPlayerMP['summer_殇恋'/2833248, l='MpServer', x=-25.00, y=70.03, z=1.63], EntityOtherPlayerMP['w1rc9838y8'/7, l='MpServer', x=3.50, y=68.00, z=-2.50], EntityOtherPlayerMP['单身汪carzy'/2831244, l='MpServer', x=-17.53, y=68.00, z=1.41], EntityOtherPlayerMP['BARRYDCY'/2830748, l='MpServer', x=-23.81, y=70.00, z=0.13], EntityArmorStand['§b神秘宝库'/8, l='MpServer', x=-8.50, y=68.38, z=-8.50], EntityArmorStand['§e§l右键点击'/9, l='MpServer', x=-8.50, y=68.09, z=-8.50], EntityVillager['村民'/10, l='MpServer', x=-8.50, y=67.00, z=10.50], EntityArmorStand['§e§l右键点击'/11, l='MpServer', x=-8.50, y=67.16, z=10.50], EntityArmorStand['§b任务达人'/12, l='MpServer', x=-8.50, y=67.47, z=10.50], EntityOtherPlayerMP['逗比牛是真的逗比'/2827646, l='MpServer', x=-13.63, y=67.00, z=9.16], EntityOtherPlayerMP['mc小禾禾'/2832081, l='MpServer', x=-2.50, y=67.00, z=7.89], EntityOtherPlayerMP['暗影作者'/2818626, l='MpServer', x=-22.78, y=70.00, z=-0.31], EntityOtherPlayerMP['二次元ren'/2830953, l='MpServer', x=-25.41, y=70.00, z=-0.09], EntityOtherPlayerMP['gu_chen'/2833342, l='MpServer', x=-25.16, y=70.03, z=-0.66], EntityOtherPlayerMP['梦晨丶辉'/2833153, l='MpServer', x=-23.16, y=70.03, z=-0.69], EntityOtherPlayerMP['隔山打牛丨患幻'/2832942, l='MpServer', x=-0.19, y=67.00, z=-0.63], EntityOtherPlayerMP['上进豹猫的飞'/2831854, l='MpServer', x=-24.94, y=70.00, z=-0.44], EntityOtherPlayerMP['辣鸡翅'/2830962, l='MpServer', x=-8.18, y=68.00, z=-7.38], EntityOtherPlayerMP['c4ru4rp9p9'/55, l='MpServer', x=-8.50, y=67.00, z=-4.50], EntityOtherPlayerMP['luksm1'/2828598, l='MpServer', x=-7.59, y=68.00, z=-7.97], EntityOtherPlayerMP['放大阿萨姆'/2831633, l='MpServer', x=-5.44, y=67.80, z=-8.03], EntityOtherPlayerMP['c4ru4rp9p9'/55, l='MpServer', x=-8.50, y=67.00, z=-4.50], EntityArmorStand['§b礼包使者§r'/58, l='MpServer', x=-8.50, y=67.25, z=-4.50], EntityArmorStand['§e§l右键点击§r'/57, l='MpServer', x=-8.50, y=66.97, z=-4.50], EntityOtherPlayerMP['n7m0k9a63m'/6, l='MpServer', x=1.50, y=68.00, z=-5.50], EntityArmorStand['盔甲架'/2833250, l='MpServer', x=-8.50, y=70.69, z=-8.50], EntityArmorStand['§e1. §cCC直播丶终宇飞i§r§7 [GHOST]§7 - §e1,054'/62, l='MpServer', x=-10.50, y=72.78, z=-28.00], EntityOtherPlayerMP['YlNb'/2830955, l='MpServer', x=12.36, y=75.50, z=3.53], EntityOtherPlayerMP['summer_殇恋'/2833248, l='MpServer', x=-25.00, y=70.03, z=1.63], EntityArmorStand['§b§l起床战争等级'/60, l='MpServer', x=-10.50, y=73.25, z=-28.00], EntityOtherPlayerMP['敲帅的骚鱼鸭丶'/2831850, l='MpServer', x=-8.79, y=69.15, z=-20.27], EntityArmorStand['§e4. §aAing_§r§7 [S]§7 - §e641'/68, l='MpServer', x=-10.50, y=71.69, z=-28.00], EntityArmorStand['§e5. §bCC直播丶贼能混§r§7 [S]§7 - §e625'/70, l='MpServer', x=-10.50, y=71.31, z=-28.00], EntityOtherPlayerMP['上进豹猫的飞'/2831854, l='MpServer', x=-24.94, y=70.00, z=-0.44], EntityArmorStand['§e2. §b奶糕酷§r§7 [DD]§7 - §e707'/64, l='MpServer', x=-10.50, y=72.41, z=-28.00], EntityArmorStand['§e3. §cMinikloon§7 - §e666'/66, l='MpServer', x=-10.50, y=72.03, z=-28.00], EntityOtherPlayerMP['阳光主播秋日'/2829917, l='MpServer', x=9.63, y=67.00, z=1.03], EntityOtherPlayerMP['24vnu41f3t'/1, l='MpServer', x=10.50, y=67.00, z=0.50], EntityArmorStand['§e8. §7慕依起不来床了§r§7 [CEO]§7 - §e578'/76, l='MpServer', x=-10.50, y=70.19, z=-28.00], EntityOtherPlayerMP['pa63qhv6ql'/2, l='MpServer', x=-0.50, y=68.00, z=9.50], EntityOtherPlayerMP['4o0yglp9p4'/4, l='MpServer', x=3.50, y=68.00, z=3.50], EntityArmorStand['§e9. §7CC直播丶小凯吖§r§7 [S]§7 - §e524'/78, l='MpServer', x=-10.50, y=69.81, z=-28.00], EntityOtherPlayerMP['9i6ei17ml9'/5, l='MpServer', x=1.50, y=68.00, z=6.50], EntityOtherPlayerMP['单身汪carzy'/2831244, l='MpServer', x=-17.53, y=68.00, z=1.41], EntityArmorStand['§e6. §7闻雪此生挚爱唯江§r§7 [V8]§7 - §e611'/72, l='MpServer', x=-10.50, y=70.94, z=-28.00], EntityArmorStand['§e7. §7闻雪一生挚爱唯江§r§7 [SW]§7 - §e608'/74, l='MpServer', x=-10.50, y=70.56, z=-28.00], EntityArmorStand['§7所有模式'/84, l='MpServer', x=-5.00, y=73.38, z=-28.00], EntityArmorStand['§e1. §cCC直播丶终宇飞i§r§7 [GHOST]§7 - §e18,937'/86, l='MpServer', x=-5.00, y=72.91, z=-28.00], EntityArmorStand['§e10. §7樱花落雪思丶飞神§r§7 [CEO]§7 - §e521'/80, l='MpServer', x=-10.50, y=69.47, z=-28.00], EntityArmorStand['§b§l生涯胜场'/82, l='MpServer', x=-5.00, y=73.75, z=-28.00], EntityOtherPlayerMP['梦晨丶辉'/2833153, l='MpServer', x=-23.16, y=70.03, z=-0.69], EntityArmorStand['§e4. §7闻雪一生挚爱唯江§r§7 [SW]§7 - §e8,699'/92, l='MpServer', x=-5.00, y=71.81, z=-28.00], EntityOtherPlayerMP['YoloYap'/2832181, l='MpServer', x=-1.95, y=69.17, z=-22.41], EntityOtherPlayerMP['pater得得520'/2832550, l='MpServer', x=-14.57, y=69.14, z=-22.54], EntityArmorStand['§e5. §7CC直播丶小凯吖§r§7 [S]§7 - §e8,390'/94, l='MpServer', x=-5.00, y=71.44, z=-28.00], EntityOtherPlayerMP['Aghikt'/2833156, l='MpServer', x=-23.77, y=70.00, z=0.35], EntityArmorStand['§e2. §bCC直播丶贼能混§r§7 [S]§7 - §e11,714'/88, l='MpServer', x=-5.00, y=72.53, z=-28.00], EntityArmorStand['§e3. §7闻雪此生挚爱唯江§r§7 [V8]§7 - §e10,068'/90, l='MpServer', x=-5.00, y=72.19, z=-28.00], EntityArmorStand['§e9. §7初雨凉笙叹丶星尘§7 - §e7,605'/102, l='MpServer', x=-5.00, y=69.97, z=-28.00], EntityArmorStand['§e8. §a顽皮野蛮长的大丶§r§7 [CEO]§7 - §e7,934'/100, l='MpServer', x=-5.00, y=70.31, z=-28.00], EntityArmorStand['§e7. §b没人疼爱我了§r§7 [S]§7 - §e8,154'/98, l='MpServer', x=-5.00, y=70.69, z=-28.00], EntityOtherPlayerMP['任凭暴雪纷飞'/2832666, l='MpServer', x=-22.78, y=70.00, z=0.81], EntityOtherPlayerMP['敲帅的骚鱼鸭丶'/2831850, l='MpServer', x=-8.79, y=69.15, z=-20.27], EntityArmorStand['§e6. §biLikeOreo§7 - §e8,323'/96, l='MpServer', x=-5.00, y=71.06, z=-28.00], EntityArmorStand['§b§l生涯最终击杀'/110, l='MpServer', x=0.50, y=73.75, z=-28.00], EntityArmorStand['§a§l生涯 §r§7每周 §r§7每日'/108, l='MpServer', x=-5.00, y=68.75, z=-28.00], EntityArmorStand['§6§l点击切换！'/106, l='MpServer', x=-5.00, y=69.13, z=-28.00], EntityArmorStand['§e10. §b笙歌叹离愁丶旺仔§r§7 [URBBR]§7 - §e7,564'/104, l='MpServer', x=-5.00, y=69.59, z=-28.00], EntityArmorStand['§e3. §b奶糕酷§r§7 [DD]§7 - §e31,951'/118, l='MpServer', x=0.50, y=72.19, z=-28.00], EntityArmorStand['§e2. §bCC直播丶贼能混§r§7 [S]§7 - §e38,941'/116, l='MpServer', x=0.50, y=72.53, z=-28.00], EntityArmorStand['§e1. §cCC直播丶终宇飞i§r§7 [GHOST]§7 - §e62,954'/114, l='MpServer', x=0.50, y=72.91, z=-28.00], EntityArmorStand['§7所有模式'/112, l='MpServer', x=0.50, y=73.38, z=-28.00], EntityOtherPlayerMP['BARRYDCY'/2830748, l='MpServer', x=-23.81, y=70.00, z=0.13], EntityArmorStand['§e7. §7闻雪此生挚爱唯江§r§7 [V8]§7 - §e27,126'/126, l='MpServer', x=0.50, y=70.69, z=-28.00], EntityOtherPlayerMP['海神修罗唐三'/2820695, l='MpServer', x=-72.50, y=76.72, z=-7.61], EntityOtherPlayerMP['阳光主播秋日'/2829917, l='MpServer', x=9.63, y=67.00, z=1.03], EntityOtherPlayerMP['Mmqlipe'/2832646, l='MpServer', x=-21.94, y=70.00, z=0.13], EntityArmorStand['§e6. §7初雨凉笙叹丶星尘§7 - §e29,287'/124, l='MpServer', x=0.50, y=71.06, z=-28.00], EntityArmorStand['§e5. §7闻雪一生挚爱唯江§r§7 [SW]§7 - §e29,751'/122, l='MpServer', x=0.50, y=71.44, z=-28.00], EntityArmorStand['§e4. §b奶熊§r§7 [KID]§7 - §e30,021'/120, l='MpServer', x=0.50, y=71.81, z=-28.00], EntityItemFrame['entity.ItemFrame.name'/2833365, l='MpServer', x=-9.50, y=75.50, z=28.97], EntityArmorStand['§a§l生涯 §r§7每周 §r§7每日'/136, l='MpServer', x=0.50, y=68.75, z=-28.00], EntityItemFrame['entity.ItemFrame.name'/2833364, l='MpServer', x=-9.50, y=74.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833367, l='MpServer', x=-8.50, y=71.50, z=28.97], EntityArmorStand['§b§n点击切换！'/138, l='MpServer', x=5.00, y=70.00, z=-26.00], EntityItemFrame['entity.ItemFrame.name'/2833366, l='MpServer', x=-8.50, y=70.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833361, l='MpServer', x=-9.50, y=71.50, z=28.97], EntityArmorStand['§a§l所有模式'/140, l='MpServer', x=5.00, y=69.53, z=-26.00], EntityItemFrame['entity.ItemFrame.name'/2833360, l='MpServer', x=-9.50, y=70.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833363, l='MpServer', x=-9.50, y=73.50, z=28.97], EntityArmorStand['§7单挑'/142, l='MpServer', x=5.00, y=69.16, z=-26.00], EntityItemFrame['entity.ItemFrame.name'/2833362, l='MpServer', x=-9.50, y=72.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833373, l='MpServer', x=-7.50, y=71.50, z=28.97], EntityArmorStand['§e8. §7唯念龙神三里清风§r§7 [DK]§7 - §e26,547'/128, l='MpServer', x=0.50, y=70.31, z=-28.00], EntityItemFrame['entity.ItemFrame.name'/2833372, l='MpServer', x=-7.50, y=70.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833375, l='MpServer', x=-7.50, y=73.50, z=28.97], EntityArmorStand['§e9. §biLikeOreo§7 - §e23,899'/130, l='MpServer', x=0.50, y=69.97, z=-28.00], EntityItemFrame['entity.ItemFrame.name'/2833374, l='MpServer', x=-7.50, y=72.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833369, l='MpServer', x=-8.50, y=73.50, z=28.97], EntityArmorStand['§e10. §a叮当吖§r§7 [W]§7 - §e21,616'/132, l='MpServer', x=0.50, y=69.59, z=-28.00], EntityItemFrame['entity.ItemFrame.name'/2833368, l='MpServer', x=-8.50, y=72.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833371, l='MpServer', x=-8.50, y=75.50, z=28.97], EntityArmorStand['§6§l点击切换！'/134, l='MpServer', x=0.50, y=69.13, z=-28.00], EntityItemFrame['entity.ItemFrame.name'/2833370, l='MpServer', x=-8.50, y=74.50, z=28.97], EntityArmorStand['§f成就：§e14§a/73'/152, l='MpServer', x=3.50, y=68.03, z=-14.50], EntityItemFrame['entity.ItemFrame.name'/2833348, l='MpServer', x=-11.50, y=70.50, z=28.97], EntityArmorStand['§f总胜利场次：§a2'/153, l='MpServer', x=3.50, y=67.69, z=-14.50], EntityItemFrame['entity.ItemFrame.name'/2833349, l='MpServer', x=-11.50, y=71.50, z=28.97], EntityArmorStand['§f当前连胜次数：§a0'/154, l='MpServer', x=3.50, y=67.31, z=-14.50], EntityItemFrame['entity.ItemFrame.name'/2833350, l='MpServer', x=-11.50, y=72.50, z=28.97], EntityArmorStand['§e§l点击打开'/155, l='MpServer', x=10.50, y=67.28, z=0.50], EntityItemFrame['entity.ItemFrame.name'/2833351, l='MpServer', x=-11.50, y=73.50, z=28.97], EntityArmorStand['§b店主'/156, l='MpServer', x=10.50, y=66.91, z=0.50], EntityArmorStand['§f5个礼包！§r'/2833345, l='MpServer', x=-8.50, y=67.56, z=-4.50], EntityArmorStand['§6§l限时梦幻模式'/157, l='MpServer', x=-0.50, y=69.03, z=9.50], EntityArmorStand['§e§l点击开始游戏§r'/158, l='MpServer', x=-0.50, y=68.69, z=9.50], EntityArmorStand['盔甲架'/2833346, l='MpServer', x=-8.50, y=68.69, z=-8.50], EntityArmorStand['§b疾速起床V2§7 [v1.5]'/159, l='MpServer', x=-0.50, y=68.31, z=9.50], EntityArmorStand['§f1个可用!§r'/2833347, l='MpServer', x=-8.50, y=68.69, z=-8.50], EntityArmorStand['§7双人'/144, l='MpServer', x=5.00, y=68.78, z=-26.00], EntityItemFrame['entity.ItemFrame.name'/2833356, l='MpServer', x=-10.50, y=72.50, z=28.97], EntityBat['蝙蝠'/145, l='MpServer', x=-8.50, y=68.35, z=-4.50], EntityItemFrame['entity.ItemFrame.name'/2833357, l='MpServer', x=-10.50, y=73.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833358, l='MpServer', x=-10.50, y=74.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833359, l='MpServer', x=-10.50, y=75.50, z=28.97], EntityArmorStand['§e§l点击查看更多数据'/148, l='MpServer', x=3.50, y=66.91, z=-14.50], EntityItemFrame['entity.ItemFrame.name'/2833352, l='MpServer', x=-11.50, y=74.50, z=28.97], EntityArmorStand['§6§l你的起床战争信息'/149, l='MpServer', x=3.50, y=69.16, z=-14.50], EntityItemFrame['entity.ItemFrame.name'/2833353, l='MpServer', x=-11.50, y=75.50, z=28.97], EntityArmorStand['§f等级：§a§73?'/150, l='MpServer', x=3.50, y=68.78, z=-14.50], EntityItemFrame['entity.ItemFrame.name'/2833354, l='MpServer', x=-10.50, y=70.50, z=28.97], EntityArmorStand['§f进度：§b125§7/§a3.5k'/151, l='MpServer', x=3.50, y=68.41, z=-14.50], EntityItemFrame['entity.ItemFrame.name'/2833355, l='MpServer', x=-10.50, y=71.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833399, l='MpServer', x=-3.50, y=73.50, z=28.97], EntityArmorStand['§e§l323名玩家'/171, l='MpServer', x=3.50, y=67.94, z=-2.50], EntityItemFrame['entity.ItemFrame.name'/2833398, l='MpServer', x=-3.50, y=72.50, z=28.97], EntityArmorStand['§b双人模式§7 [v1.5]'/170, l='MpServer', x=3.50, y=68.31, z=-2.50], EntityItemFrame['entity.ItemFrame.name'/2833397, l='MpServer', x=-3.50, y=71.50, z=28.97], EntityArmorStand['§e§l点击开始游戏§r'/169, l='MpServer', x=3.50, y=68.69, z=-2.50], EntityItemFrame['entity.ItemFrame.name'/2833396, l='MpServer', x=-3.50, y=70.50, z=28.97], EntityArmorStand['§e§l199名玩家'/168, l='MpServer', x=1.50, y=67.94, z=-5.50], EntityItemFrame['entity.ItemFrame.name'/2833395, l='MpServer', x=-4.50, y=75.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833394, l='MpServer', x=-4.50, y=74.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833393, l='MpServer', x=-4.50, y=73.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833392, l='MpServer', x=-4.50, y=72.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833407, l='MpServer', x=-2.50, y=75.50, z=28.97], EntityArmorStand['§e§l点击开始游戏§r'/163, l='MpServer', x=1.50, y=68.69, z=6.50], EntityItemFrame['entity.ItemFrame.name'/2833406, l='MpServer', x=-2.50, y=74.50, z=28.97], EntityArmorStand['§e§l273名玩家'/162, l='MpServer', x=3.50, y=67.94, z=3.50], EntityItemFrame['entity.ItemFrame.name'/2833405, l='MpServer', x=-2.50, y=73.50, z=28.97], EntityArmorStand['§b3v3v3v3模式§7 [v1.5]'/161, l='MpServer', x=3.50, y=68.31, z=3.50], EntityItemFrame['entity.ItemFrame.name'/2833404, l='MpServer', x=-2.50, y=72.50, z=28.97], EntityArmorStand['§e§l点击开始游戏§r'/160, l='MpServer', x=3.50, y=68.69, z=3.50], EntityItemFrame['entity.ItemFrame.name'/2833403, l='MpServer', x=-2.50, y=71.50, z=28.97], EntityArmorStand['§b单挑模式§7 [v1.5]'/167, l='MpServer', x=1.50, y=68.31, z=-5.50], EntityItemFrame['entity.ItemFrame.name'/2833402, l='MpServer', x=-2.50, y=70.50, z=28.97], EntityArmorStand['§e§l点击开始游戏§r'/166, l='MpServer', x=1.50, y=68.69, z=-5.50], EntityItemFrame['entity.ItemFrame.name'/2833401, l='MpServer', x=-3.50, y=75.50, z=28.97], EntityArmorStand['§e§l1,463名玩家'/165, l='MpServer', x=1.50, y=67.94, z=6.50], EntityItemFrame['entity.ItemFrame.name'/2833400, l='MpServer', x=-3.50, y=74.50, z=28.97], EntityArmorStand['§b4v4v4v4模式§7 [v1.5]'/164, l='MpServer', x=1.50, y=68.31, z=6.50], EntityItemFrame['entity.ItemFrame.name'/2833382, l='MpServer', x=-6.50, y=74.50, z=28.97], EntityArmorStand['盔甲架'/187, l='MpServer', x=-4.50, y=84.13, z=30.50], EntityItemFrame['entity.ItemFrame.name'/2833383, l='MpServer', x=-6.50, y=75.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833380, l='MpServer', x=-6.50, y=72.50, z=28.97], EntityArmorStand['盔甲架'/185, l='MpServer', x=-4.50, y=84.50, z=30.50], EntityItemFrame['entity.ItemFrame.name'/2833381, l='MpServer', x=-6.50, y=73.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833378, l='MpServer', x=-6.50, y=70.50, z=28.97], EntityOtherPlayerMP['放大阿萨姆'/2831633, l='MpServer', x=-5.44, y=67.80, z=-8.03], EntityArmorStand['盔甲架'/191, l='MpServer', x=-60.50, y=98.13, z=0.50], EntityItemFrame['entity.ItemFrame.name'/2833379, l='MpServer', x=-6.50, y=71.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833376, l='MpServer', x=-7.50, y=74.50, z=28.97], EntityArmorStand['盔甲架'/189, l='MpServer', x=-60.50, y=98.50, z=0.50], EntityOtherPlayerMP['YoloYap'/2832181, l='MpServer', x=-1.95, y=69.17, z=-22.41], EntityItemFrame['entity.ItemFrame.name'/2833377, l='MpServer', x=-7.50, y=75.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833390, l='MpServer', x=-4.50, y=70.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833391, l='MpServer', x=-4.50, y=71.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833388, l='MpServer', x=-5.50, y=74.50, z=28.97], EntityOtherPlayerMP['九凤最帅'/2831536, l='MpServer', x=-24.59, y=57.23, z=-31.83], EntityItemFrame['entity.ItemFrame.name'/2833389, l='MpServer', x=-5.50, y=75.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833386, l='MpServer', x=-5.50, y=72.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833387, l='MpServer', x=-5.50, y=73.50, z=28.97], EntityArmorStand['盔甲架'/183, l='MpServer', x=-56.50, y=91.13, z=-21.50], EntityItemFrame['entity.ItemFrame.name'/2833384, l='MpServer', x=-5.50, y=70.50, z=28.97], EntityItemFrame['entity.ItemFrame.name'/2833385, l='MpServer', x=-5.50, y=71.50, z=28.97], EntityArmorStand['盔甲架'/181, l='MpServer', x=-56.50, y=91.50, z=-21.50], EntityArmorStand['盔甲架'/196, l='MpServer', x=-24.50, y=69.50, z=-23.50], EntityArmorStand['盔甲架'/198, l='MpServer', x=-24.50, y=69.13, z=-23.50], EntityOtherPlayerMP['海神修罗唐三'/2820695, l='MpServer', x=-72.50, y=76.72, z=-7.61], EntityOtherPlayerMP['gu_chen'/2833342, l='MpServer', x=-25.16, y=70.03, z=-0.66], EntityOtherPlayerMP['暗影作者'/2818626, l='MpServer', x=-22.78, y=70.00, z=-0.31], EntityOtherPlayerMP['九凤最帅'/2831536, l='MpServer', x=-24.59, y=57.23, z=-31.83], EntityBat['蝙蝠'/2253285, l='MpServer', x=-22.72, y=71.78, z=3.66], EntityOtherPlayerMP['mc小禾禾'/2832081, l='MpServer', x=-2.50, y=67.00, z=7.89], EntityOtherPlayerMP['隔山打牛丨患幻'/2832942, l='MpServer', x=-0.19, y=67.00, z=-0.63], EntityOtherPlayerMP['luksm1'/2828598, l='MpServer', x=-7.59, y=68.00, z=-7.97], EntityWither['§6空岛战争§a实验室更新 - §d§l疯狂TNT§a开放中!'/-1234, l='MpServer', x=7.91, y=70.03, z=1.56], EntityZombie['僵尸'/2830960, l='MpServer', x=11.36, y=67.68, z=1.71], EntityOtherPlayerMP['辣鸡翅'/2830962, l='MpServer', x=-8.18, y=68.00, z=-7.38], EntityArmorStand['§bbili277691049'/2830957, l='MpServer', x=11.40, y=66.16, z=2.00], EntityArmorStand['盔甲架'/2830956, l='MpServer', x=11.41, y=67.16, z=2.05], EntityArmorStand['盔甲架'/2830959, l='MpServer', x=11.17, y=67.38, z=1.79], EntityArmorStand['盔甲架'/2830958, l='MpServer', x=11.31, y=67.16, z=1.54], EntityOtherPlayerMP['二次元ren'/2830953, l='MpServer', x=-25.41, y=70.00, z=-0.09], EntityOtherPlayerMP['YlNb'/2830955, l='MpServer', x=12.36, y=75.50, z=3.53], EntityOtherPlayerMP['pater得得520'/2832550, l='MpServer', x=-14.57, y=69.14, z=-22.54], EntityChicken['鸡'/2495109, l='MpServer', x=-14.50, y=71.00, z=-6.50], EntityOtherPlayerMP['逗比牛是真的逗比'/2827646, l='MpServer', x=-13.63, y=67.00, z=9.16]]\n\tRetry entities: 0 total; []\n\tServer brand: BungeeCord (Hypixel) <- vanilla\n\tServer type: Non-integrated multiplayer server\nStacktrace:\n\tat net.minecraft.client.multiplayer.WorldClient.func_72914_a(WorldClient.java:412)\n\tat net.minecraft.client.Minecraft.func_71396_d(Minecraft.java:2529)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:372)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.8.9\n\tOperating System: Windows 7 (amd64) version 6.1\n\tCPU: <unknown>\n\tJava Version: 1.7.0, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 182123040 bytes (173 MB) / 413282304 bytes (394 MB) up to 1345585152 bytes (1283 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx1295M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tIntCache: cache: 0, tcache: 0, allocated: 13, tallocated: 95\n\tFML: MCP 9.19 Powered by Forge 11.15.1.0 Optifine OptiFine_1.8.9_HD_U_H8 19 mods loaded, 19 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJA\tmcp{9.19} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJA\tFML{8.0.99.99} [Forge Mod Loader] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\tForge{11.15.1.0} [Minecraft Forge] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\trandftcoremod{1.8.9} [randftcoremod] (minecraft.jar)\n\tUCHIJA\tInputFix{1.8.x-v2} [InputFix] (minecraft.jar)\n\tUCHIJA\tskincoremod{1.8.9} [skincoremod] (minecraft.jar)\n\tUCHIJA\tneteasecore{1.11.2} [NeteaseCore] (minecraft.jar)\n\tUCHIJA\tbasemodneteasecore{1.8.9} [BaseModNeteaseCore] (minecraft.jar)\n\tUCHIJA\tnetworkmod{1.0} [network rpc Mod] (4620273813222949778@3@0.jar)\n\tUCHIJA\tstoremod{1.0} [storemod] (4618419806295460477@3@0.jar)\n\tUCHIJA\tantimod{2.0} [anti addiction Mod] (4618827437296985101@3@0.jar)\n\tUCHIJA\tfiltermod{1.0} [filtermod] (4619774556351054392@3@0.jar)\n\tUCHIJA\tfriendplaymod{1.0} [Friendplay Mod] (4620273813159696403@3@0.jar)\n\tUCHIJA\tmcbasemod{1.0} [mcbasemod] (4620273813196076442@3@0.jar)\n\tUCHIJA\tupdatecore{1.0} [updatecore] (4620608833856825631@3@0.jar)\n\tUCHIJA\tplayermanager{1.0} [playermanager] (4620702952524438419@3@0.jar)\n\tUCHIJA\tfullscreenpopup{1.8.9.38000} [Fullscreen Popup Mod] (4624103992226684617@3@0.jar)\n\tUCHIJA\tskinmod{1.8.8-15739} [skinmod] (4626894634154779079@3@0.jar)\n\tUCHIJA\tBetterSprinting{1.1.3} [Better Sprinting] (强制疾跑.jar)\n\tLoaded coremods (and transformers):\nNeteaseCore (4619774556351054392@3@0.jar)\n  com.netease.mc.core.NeteaseCoreTransformer\nSkinCore (4626894634154779079@3@0.jar)\n  com.netease.mc.mod.skin.coremod.SkinCoreTransformer\nBaseModNeteaseCore (4620273813196076442@3@0.jar)\n  com.netease.mc.core.base.NeteaseCoreTransformer\nInputFix (4618424574399199550@3@0.jar)\n  lain.mods.inputfix.InputFixTransformer\nRandFTCore (4618421856281952104@2@33.jar)\n  com.netease.mc.mod.randomfont.RandFTCoreTransformer\n\tLaunched Version: 1.8.9\n\tLWJGL: 2.9.4\n\tOpenGL: GeForce 610M/PCIe/SSE2 GL version 4.5.0 NVIDIA 385.28, NVIDIA Corporation\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing GL 1.3 texture combiners.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nShaders are available because OpenGL 2.1 is supported.\nVBOs are available because OpenGL 1.5 is supported.\n\n\tUsing VBOs: No\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: 锟斤拷锟斤拷专锟斤拷锟斤拷锟?zip\n\tCurrent Language: 简体中文 (中国)\n\tProfiler Position: N/A (disabled)\n\tCPU: <unknown>\n\tOptiFine Version: OptiFine_1.8.9_HD_U_H8\n\tRender Distance Chunks: 10\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.5.0 NVIDIA 385.28\n\tOpenGlRenderer: GeForce 610M/PCIe/SSE2\n\tOpenGlVendor: NVIDIA Corporation\n\tCpuCount: 2"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/creativemd.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\n  SkinCore (4626894634154779079@3@0.jar)\n  BaseModNeteaseCore (4620273813196076442@3@0.jar)\n  ItemPatchingLoader (ItemPhysic-Mod-Lite-1.8.9.jar)\n  RandFTCore (4618421856281952104@2@33.jar)\n  Main ([防砍动画]OldAnimationsMod v2.4.2 FORGE MC1.8.9.jar)\n  InputFix (4618424574399199550@3@0.jar)\n  BetterFps ([1.8Fps提升]Ex-BetterFps-1.1.0[laozikaiG].jar)\n  FMLLoadingPlugin ([防砍动画未响应修复]OldAnimationsModUnresponsiveFix-1.0.jar)\n  FMLLoadingPlugin (【鼠标无延迟】1.8nodelay mouse.jar)\n  NeteaseCore (4619774556351054392@3@0.jar)\nContact their authors BEFORE contacting forge\n\n// I bet Cylons wouldn't have this problem.\n\nTime: 19-1-25 下午7:38\nDescription: Rendering entity in world\n\njava.lang.NoSuchMethodError: net.minecraft.client.renderer.entity.RenderEntityItem.shouldSpreadItems()Z\n\tat com.creativemd.itemphysic.physics.ClientPhysic.doRender(ClientPhysic.java:208)\n\tat net.minecraft.client.renderer.entity.RenderEntityItem.func_76986_a(RenderEntityItem.java)\n\tat net.minecraft.client.renderer.entity.RenderEntityItem.func_76986_a(RenderEntityItem.java:17)\n\tat net.minecraft.client.renderer.entity.RenderManager.func_147939_a(RenderManager.java:399)\n\tat net.minecraft.client.renderer.entity.RenderManager.func_147940_a(RenderManager.java:379)\n\tat net.minecraft.client.particle.EntityPickupFX.func_180434_a(SourceFile:53)\n\tat net.minecraft.client.particle.EffectRenderer.func_78872_b(EffectRenderer.java:368)\n\tat net.minecraft.client.renderer.EntityRenderer.func_175068_a(EntityRenderer.java:1825)\n\tat net.minecraft.client.renderer.EntityRenderer.func_78471_a(EntityRenderer.java:1580)\n\tat net.minecraft.client.renderer.EntityRenderer.func_181560_a(EntityRenderer.java:1370)\n\tat net.minecraft.client.Minecraft.func_71411_J(Minecraft.java:1044)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:351)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat com.creativemd.itemphysic.physics.ClientPhysic.doRender(ClientPhysic.java:208)\n\tat net.minecraft.client.renderer.entity.RenderEntityItem.func_76986_a(RenderEntityItem.java)\n\tat net.minecraft.client.renderer.entity.RenderEntityItem.func_76986_a(RenderEntityItem.java:17)\n\n-- Entity being rendered --\nDetails:\n\tEntity Type: Item (net.minecraft.entity.item.EntityItem)\n\tEntity ID: 1231\n\tEntity Name: item.tile.whiteStone\n\tEntity's Exact location: -96.06, 65.00, 32.03\n\tEntity's Block location: -97.00,65.00,32.00 - World: (-97,65,32), Chunk: (at 15,4,0 in -7,2; contains blocks -112,0,32 to -97,255,47), Region: (-1,0; contains chunks -32,0 to -1,31, blocks -512,0,0 to -1,255,511)\n\tEntity's Momentum: -0.00, -0.00, -0.00\n\tEntity's Rider: ~~ERROR~~ NullPointerException: null\n\tEntity's Vehicle: ~~ERROR~~ NullPointerException: null\n\n-- Renderer details --\nDetails:\n\tAssigned renderer: net.minecraft.client.renderer.entity.RenderEntityItem@d0f3c5a\n\tLocation: 3.44,0.81,63.70 - World: (3,0,63), Chunk: (at 3,0,15 in 0,3; contains blocks 0,0,48 to 15,255,63), Region: (0,0; contains chunks 0,0 to 31,31, blocks 0,0,0 to 511,255,511)\n\tRotation: -22.5\n\tDelta: 0.3214321\nStacktrace:\n\tat net.minecraft.client.renderer.entity.RenderManager.func_147939_a(RenderManager.java:399)\n\tat net.minecraft.client.renderer.entity.RenderManager.func_147940_a(RenderManager.java:379)\n\tat net.minecraft.client.particle.EntityPickupFX.func_180434_a(SourceFile:53)\n\tat net.minecraft.client.particle.EffectRenderer.func_78872_b(EffectRenderer.java:368)\n\tat net.minecraft.client.renderer.EntityRenderer.func_175068_a(EntityRenderer.java:1825)\n\tat net.minecraft.client.renderer.EntityRenderer.func_78471_a(EntityRenderer.java:1580)\n\n-- Affected level --\nDetails:\n\tLevel name: MpServer\n\tAll players: 16 total; [EntityPlayerSP['MacAdamKaren'/1881652, l='MpServer', x=-99.13, y=64.50, z=-31.73], EntityOtherPlayerMP['闪电史蒂夫少年'/63, l='MpServer', x=-37.00, y=65.50, z=96.44], EntityOtherPlayerMP['马五德的高尚的画'/112, l='MpServer', x=-99.42, y=76.95, z=-31.57], EntityOtherPlayerMP['Zibey'/351, l='MpServer', x=32.58, y=65.91, z=-90.02], EntityOtherPlayerMP['包皮过长要割包皮'/376, l='MpServer', x=36.89, y=65.00, z=83.52], EntityOtherPlayerMP['方健皓'/383, l='MpServer', x=-94.31, y=66.05, z=32.55], EntityOtherPlayerMP['Bili丶Chara'/384, l='MpServer', x=-92.00, y=65.22, z=32.07], EntityOtherPlayerMP['Stay_岁月'/64, l='MpServer', x=-60.47, y=67.94, z=85.31], EntityOtherPlayerMP['龜薾嗭'/193, l='MpServer', x=32.20, y=64.41, z=-99.65], EntityOtherPlayerMP['情殇桥山丶哈克芬'/58, l='MpServer', x=12.11, y=74.60, z=-85.42], EntityOtherPlayerMP['MC小文文'/60, l='MpServer', x=-81.73, y=65.76, z=-31.64], EntityOtherPlayerMP['f159z039f3'/587, l='MpServer', x=-38.50, y=65.00, z=-95.00], EntityOtherPlayerMP['bij9i36g72'/613, l='MpServer', x=-95.00, y=65.00, z=-38.50], EntityOtherPlayerMP['9u9x011ex0'/660, l='MpServer', x=-25.50, y=65.00, z=-95.00], EntityOtherPlayerMP['Samakud'/374, l='MpServer', x=33.00, y=65.00, z=96.00], EntityOtherPlayerMP['Cs丶诗小仙'/59, l='MpServer', x=-32.00, y=66.00, z=-95.00]]\n\tChunk stats: MultiplayerChunkCache: 441, 441\n\tLevel seed: 0\n\tLevel generator: ID 01 - flat, ver 0. Features enabled: false\n\tLevel generator options:\n\tLevel spawn location: -82.00,4.00,-907.00 - World: (-82,4,-907), Chunk: (at 14,0,5 in -6,-57; contains blocks -96,0,-912 to -81,255,-897), Region: (-1,-2; contains chunks -32,-64 to -1,-33, blocks -512,0,-1024 to -1,255,-513)\n\tLevel time: 166821440 game time, 1 day time\n\tLevel dimension: 0\n\tLevel storage version: 0x00000 - Unknown?\n\tLevel weather: Rain time: 0 (now: false), thunder time: 0 (now: false)\n\tLevel game mode: Game mode: creative (ID 1). Hardcore: false. Cheats: false\n\tForced entities: 178 total; [EntityArmorStand['Armor Stand'/515, l='MpServer', x=0.50, y=81.01, z=33.50], EntityArmorStand['§e等级§cI'/516, l='MpServer', x=-32.50, y=81.88, z=0.50], EntityArmorStand['§2§l绿宝石'/517, l='MpServer', x=-32.50, y=81.50, z=0.50], EntityArmorStand['Armor Stand'/523, l='MpServer', x=-32.50, y=81.00, z=0.50], EntityItem['item.item.emerald'/1035, l='MpServer', x=0.50, y=79.00, z=33.50], EntityOtherPlayerMP['Samakud'/374, l='MpServer', x=33.00, y=65.00, z=96.00], EntityItem['item.item.emerald'/1036, l='MpServer', x=33.50, y=79.00, z=0.50], EntityArmorStand['§e等级§cI'/525, l='MpServer', x=0.50, y=81.88, z=-32.50], EntityArmorStand['§2§l绿宝石'/526, l='MpServer', x=0.50, y=81.50, z=-32.50], EntityArmorStand['§e将在§c25§e秒后产出'/527, l='MpServer', x=0.50, y=81.13, z=-32.50], EntityItem['item.item.emerald'/1041, l='MpServer', x=-32.50, y=79.00, z=0.50], EntityItem['item.item.emerald'/1043, l='MpServer', x=0.50, y=79.00, z=-32.50], EntityArmorStand['Armor Stand'/533, l='MpServer', x=0.50, y=81.00, z=-32.50], EntityArmorStand['§e等级§cI'/534, l='MpServer', x=33.50, y=81.88, z=0.50], EntityArmorStand['§2§l绿宝石'/535, l='MpServer', x=33.50, y=81.50, z=0.50], EntityItem['item.item.diamond'/1047, l='MpServer', x=-75.50, y=62.00, z=-74.50], EntityItem['item.item.diamond'/1049, l='MpServer', x=-74.50, y=62.00, z=76.50], EntityArmorStand['Armor Stand'/541, l='MpServer', x=33.50, y=81.00, z=0.50], EntityArmorStand['§e等级§cI'/542, l='MpServer', x=-75.50, y=64.88, z=-74.50], EntityArmorStand['§b§l钻石'/543, l='MpServer', x=-75.50, y=64.50, z=-74.50], EntityArmorStand['§e将在§c21§e秒后产出'/544, l='MpServer', x=-75.50, y=64.13, z=-74.50], EntityOtherPlayerMP['Zibey'/351, l='MpServer', x=32.58, y=65.91, z=-90.02], EntityArmorStand['Armor Stand'/550, l='MpServer', x=-75.50, y=64.00, z=-74.50], EntityArmorStand['§e等级§cI'/551, l='MpServer', x=-74.50, y=64.88, z=76.50], EntityArmorStand['§b§l钻石'/552, l='MpServer', x=-74.50, y=64.50, z=76.50], EntityArmorStand['Armor Stand'/558, l='MpServer', x=-74.50, y=64.00, z=76.50], EntityOtherPlayerMP['情殇桥山丶哈克芬'/58, l='MpServer', x=12.11, y=74.60, z=-85.42], EntityOtherPlayerMP['Cs丶诗小仙'/59, l='MpServer', x=-32.00, y=66.00, z=-95.00], EntityOtherPlayerMP['MC小文文'/60, l='MpServer', x=-81.73, y=65.76, z=-31.64], EntityOtherPlayerMP['闪电史蒂夫少年'/63, l='MpServer', x=-37.00, y=65.50, z=96.44], EntityOtherPlayerMP['Stay_岁月'/64, l='MpServer', x=-60.47, y=67.94, z=85.31], EntityArmorStand['§e将在§c24§e秒后产出'/580, l='MpServer', x=0.50, y=81.13, z=33.50], EntityOtherPlayerMP['六岁就很乖'/354, l='MpServer', x=-2.50, y=118.00, z=5.22], EntityOtherPlayerMP['f159z039f3'/587, l='MpServer', x=-38.50, y=65.00, z=-95.00], EntityOtherPlayerMP['f159z039f3'/587, l='MpServer', x=-38.50, y=65.00, z=-95.00], EntityBat['Bat'/589, l='MpServer', x=-38.50, y=66.35, z=-95.00], EntityArmorStand['§b组队模式'/590, l='MpServer', x=-38.50, y=65.44, z=-95.00], EntityOtherPlayerMP['1h5kukjp53'/593, l='MpServer', x=96.00, y=65.00, z=39.50], EntityArmorStand['§b升级'/591, l='MpServer', x=-38.50, y=65.16, z=-95.00], EntityArmorStand['§e§l右键点击'/592, l='MpServer', x=-38.50, y=64.91, z=-95.00], EntityOtherPlayerMP['53ij2115e2'/628, l='MpServer', x=96.00, y=65.00, z=-38.50], EntityOtherPlayerMP['a67r8nj697'/634, l='MpServer', x=96.00, y=65.00, z=-25.50], EntityArmorStand['§b组队模式'/600, l='MpServer', x=-25.50, y=65.44, z=96.00], EntityArmorStand['§b升级'/601, l='MpServer', x=-25.50, y=65.16, z=96.00], EntityArmorStand['§e§l右键点击'/602, l='MpServer', x=-25.50, y=64.91, z=96.00], EntityOtherPlayerMP['bij9i36g72'/613, l='MpServer', x=-95.00, y=65.00, z=-38.50], EntityArmorStand['§b道具商店'/605, l='MpServer', x=39.50, y=65.06, z=-95.00], EntityArmorStand['§e§l右键点击'/606, l='MpServer', x=39.50, y=64.81, z=-95.00], EntityArmorStand['§b组队模式'/610, l='MpServer', x=39.50, y=65.44, z=96.00], EntityArmorStand['§b升级'/611, l='MpServer', x=39.50, y=65.16, z=96.00], EntityArmorStand['§e§l右键点击'/612, l='MpServer', x=39.50, y=64.91, z=96.00], EntityOtherPlayerMP['bij9i36g72'/613, l='MpServer', x=-95.00, y=65.00, z=-38.50], EntityBat['Bat'/615, l='MpServer', x=-95.00, y=66.35, z=-38.50], EntityArmorStand['§b道具商店'/616, l='MpServer', x=-95.00, y=65.16, z=-38.50], EntityArmorStand['§e§l右键点击'/617, l='MpServer', x=-95.00, y=64.91, z=-38.50], EntityOtherPlayerMP['情殇桥山丶哈克芬'/58, l='MpServer', x=12.11, y=74.60, z=-85.42], EntityCreeper['Creeper'/619, l='MpServer', x=-95.00, y=65.00, z=26.50], EntityArmorStand['§b道具商店'/620, l='MpServer', x=-95.00, y=65.16, z=26.50], EntityOtherPlayerMP['Stay_岁月'/64, l='MpServer', x=-60.47, y=67.94, z=85.31], EntityArmorStand['§e§l右键点击'/621, l='MpServer', x=-95.00, y=64.91, z=26.50], EntityArmorStand['§e将在§c22§e秒后产出'/622, l='MpServer', x=-74.50, y=64.13, z=76.50], EntityOtherPlayerMP['15s3zxz7nm'/645, l='MpServer', x=96.00, y=65.00, z=26.50], EntityOtherPlayerMP['马五德的高尚的画'/112, l='MpServer', x=-99.42, y=76.95, z=-31.57], EntityOtherPlayerMP['MC小文文'/60, l='MpServer', x=-81.73, y=65.76, z=-31.64], EntityWitch['Witch'/624, l='MpServer', x=-95.00, y=65.00, z=-25.50], EntityArmorStand['§b组队模式'/625, l='MpServer', x=-95.00, y=65.84, z=-25.50], EntityArmorStand['§b升级'/626, l='MpServer', x=-95.00, y=65.56, z=-25.50], EntityArmorStand['§e§l右键点击'/627, l='MpServer', x=-95.00, y=65.31, z=-25.50], EntityItem['item.item.ingotGold'/1139, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityOtherPlayerMP['9u9x011ex0'/660, l='MpServer', x=-25.50, y=65.00, z=-95.00], EntityItem['item.item.ingotIron'/1149, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityArmorStand['§b组队模式'/642, l='MpServer', x=26.50, y=65.34, z=-95.00], EntityArmorStand['§b升级'/643, l='MpServer', x=26.50, y=65.06, z=-95.00], EntityArmorStand['§e§l右键点击'/644, l='MpServer', x=26.50, y=64.81, z=-95.00], EntityItem['item.item.ingotIron'/1159, l='MpServer', x=33.00, y=64.50, z=100.00], EntityOtherPlayerMP['Samakud'/374, l='MpServer', x=1.25, y=120.00, z=2.00], EntityOtherPlayerMP['天瞳煞'/304, l='MpServer', x=75.86, y=62.43, z=-75.04], EntityCreeper['Creeper'/652, l='MpServer', x=-95.00, y=65.00, z=39.50], EntityArmorStand['§b组队模式'/653, l='MpServer', x=-95.00, y=65.44, z=39.50], EntityArmorStand['§b升级'/654, l='MpServer', x=-95.00, y=65.16, z=39.50], EntityArmorStand['§e§l右键点击'/655, l='MpServer', x=-95.00, y=64.91, z=39.50], EntityItem['item.item.ingotIron'/1168, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityArmorStand['§b道具商店'/658, l='MpServer', x=26.50, y=65.16, z=96.00], EntityArmorStand['§e§l右键点击'/659, l='MpServer', x=26.50, y=64.91, z=96.00], EntityOtherPlayerMP['9u9x011ex0'/660, l='MpServer', x=-25.50, y=65.00, z=-95.00], EntityBat['Bat'/662, l='MpServer', x=-25.50, y=66.35, z=-95.00], EntityArmorStand['§b道具商店'/663, l='MpServer', x=-25.50, y=65.16, z=-95.00], EntityArmorStand['§e§l右键点击'/664, l='MpServer', x=-25.50, y=64.91, z=-95.00], EntityOtherPlayerMP['MC小文文'/60, l='MpServer', x=-90.67, y=-30.66, z=28.19], EntityArmorStand['§b道具商店'/667, l='MpServer', x=-38.50, y=65.16, z=96.00], EntityOtherPlayerMP['15s3zxz7nm'/645, l='MpServer', x=96.00, y=65.00, z=26.50], EntityArmorStand['§e§l右键点击'/668, l='MpServer', x=-38.50, y=64.91, z=96.00], EntityArmorStand['§e将在§c25§e秒后产出'/669, l='MpServer', x=33.50, y=81.13, z=0.50], EntityOtherPlayerMP['Cs丶诗小仙'/59, l='MpServer', x=-32.00, y=66.00, z=-95.00], EntityArmorStand['§e将在§c25§e秒后产出'/675, l='MpServer', x=-32.50, y=81.13, z=0.50], EntityPlayerSP['MacAdamKaren'/1881652, l='MpServer', x=-99.13, y=64.50, z=-31.73], EntityItem['item.item.ingotIron'/1189, l='MpServer', x=33.00, y=64.50, z=100.00], EntityOtherPlayerMP['a67r8nj697'/634, l='MpServer', x=96.00, y=65.00, z=-25.50], EntityOtherPlayerMP['大海与黑夜之子'/55, l='MpServer', x=2.31, y=118.00, z=5.38], EntityItem['item.item.diamond'/679, l='MpServer', x=-75.50, y=62.00, z=-74.50], EntityOtherPlayerMP['Morbid丶钟情'/65, l='MpServer', x=86.55, y=65.00, z=37.35], EntityItem['item.item.diamond'/684, l='MpServer', x=-74.50, y=62.00, z=76.50], EntityItem['item.item.ingotIron'/1197, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityOtherPlayerMP['53ij2115e2'/628, l='MpServer', x=96.00, y=65.00, z=-38.50], EntityItem['item.item.ingotIron'/1205, l='MpServer', x=33.00, y=64.50, z=100.00], EntityOtherPlayerMP['情殇桥山丶哈克芬'/58, l='MpServer', x=38.78, y=65.04, z=-78.96], EntityOtherPlayerMP['Cs丶诗小仙'/59, l='MpServer', x=21.90, y=5.26, z=-80.97], EntityOtherPlayerMP['MC小文文'/60, l='MpServer', x=-93.96, y=65.68, z=-30.00], EntityOtherPlayerMP['触手TV小天baby'/62, l='MpServer', x=0.50, y=119.16, z=0.50], EntityOtherPlayerMP['1h5kukjp53'/593, l='MpServer', x=96.00, y=65.00, z=39.50], EntityOtherPlayerMP['龜薾嗭'/193, l='MpServer', x=32.20, y=64.41, z=-99.65], EntityOtherPlayerMP['包皮过长要割包皮'/376, l='MpServer', x=36.89, y=65.00, z=83.52], EntityItem['item.item.ingotIron'/1218, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityOtherPlayerMP['闪电史蒂夫少年'/63, l='MpServer', x=-37.00, y=65.50, z=96.44], EntityOtherPlayerMP['Stay_岁月'/64, l='MpServer', x=-45.78, y=-26.26, z=87.06], EntityItem['item.item.ingotIron'/1222, l='MpServer', x=33.00, y=64.50, z=100.00], EntityOtherPlayerMP['Samakud'/374, l='MpServer', x=51.39, y=65.00, z=78.88], EntityOtherPlayerMP['我是新手别打我哇'/67, l='MpServer', x=-4.49, y=118.67, z=-3.88], EntityItem['item.item.ingotIron'/1233, l='MpServer', x=-32.00, y=64.50, z=-99.00], EntityItem['item.item.ingotIron'/1234, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityItem['item.tile.wood.oak'/1237, l='MpServer', x=-93.13, y=65.00, z=28.53], EntityOtherPlayerMP['Morbid丶钟情'/65, l='MpServer', x=-0.03, y=118.00, z=-7.94], EntityItem['item.item.ingotIron'/1242, l='MpServer', x=33.00, y=64.50, z=100.00], EntityOtherPlayerMP['网意papa快弄我'/66, l='MpServer', x=7.78, y=118.00, z=1.87], EntityItem['item.item.ingotGold'/1253, l='MpServer', x=33.00, y=64.50, z=100.00], EntityItem['item.item.ingotGold'/1256, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityItem['item.item.ingotGold'/1260, l='MpServer', x=-32.00, y=64.59, z=-99.00], EntityItem['item.item.ingotIron'/1261, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityItem['item.item.ingotIron'/1262, l='MpServer', x=-32.00, y=64.50, z=-99.00], EntityItem['item.item.ingotIron'/1277, l='MpServer', x=33.00, y=64.50, z=100.00], EntityOtherPlayerMP['龜薾嗭'/193, l='MpServer', x=11.86, y=3.58, z=-83.90], EntityOtherPlayerMP['Cs丶诗小仙'/59, l='MpServer', x=30.50, y=65.88, z=-84.31], EntityItem['item.item.diamond'/1282, l='MpServer', x=-75.50, y=62.00, z=-74.50], EntityItem['item.item.ingotIron'/1283, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityItem['item.item.diamond'/1287, l='MpServer', x=-74.50, y=62.00, z=76.50], EntityItem['item.item.ingotIron'/1290, l='MpServer', x=-32.00, y=64.50, z=-99.00], EntityOtherPlayerMP['马五德的高尚的画'/112, l='MpServer', x=-99.42, y=76.95, z=-31.57], EntityItem['item.item.ingotIron'/1303, l='MpServer', x=33.00, y=64.50, z=100.00], EntityOtherPlayerMP['方健皓'/383, l='MpServer', x=-94.31, y=66.05, z=32.55], EntityItem['item.item.ingotIron'/1309, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityOtherPlayerMP['Bili丶Chara'/384, l='MpServer', x=-92.00, y=65.22, z=32.07], EntityItem['item.item.ingotIron'/1316, l='MpServer', x=-32.00, y=64.50, z=-99.00], EntityOtherPlayerMP['龜薾嗭'/193, l='MpServer', x=32.20, y=64.41, z=-99.65], EntityItem['item.item.ingotIron'/1321, l='MpServer', x=33.00, y=64.50, z=100.00], EntityItem['item.item.ingotIron'/1325, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityItem['item.item.ingotIron'/1333, l='MpServer', x=-32.00, y=64.50, z=-99.00], EntityItem['item.item.ingotIron'/1337, l='MpServer', x=33.00, y=64.50, z=100.00], EntityItem['item.item.ingotIron'/1342, l='MpServer', x=-32.04, y=64.50, z=99.92], EntityItem['item.item.ingotIron'/1345, l='MpServer', x=-99.00, y=64.50, z=33.00], EntityOtherPlayerMP['天瞳煞'/304, l='MpServer', x=-0.06, y=118.00, z=3.66], EntityOtherPlayerMP['Zibey'/351, l='MpServer', x=32.58, y=65.91, z=-90.02], EntityOtherPlayerMP['f159z039f3'/587, l='MpServer', x=-38.50, y=65.00, z=-95.00], EntityOtherPlayerMP['bij9i36g72'/613, l='MpServer', x=-95.00, y=65.00, z=-38.50], EntityOtherPlayerMP['9u9x011ex0'/660, l='MpServer', x=-25.50, y=65.00, z=-95.00], EntityOtherPlayerMP['Samakud'/374, l='MpServer', x=33.00, y=65.00, z=96.00], EntityOtherPlayerMP['包皮过长要割包皮'/376, l='MpServer', x=36.89, y=65.00, z=83.52], EntityOtherPlayerMP['方健皓'/383, l='MpServer', x=-94.31, y=66.05, z=32.55], EntityOtherPlayerMP['Bili丶Chara'/384, l='MpServer', x=-92.00, y=65.22, z=32.07], EntityItemFrame['entity.ItemFrame.name'/464, l='MpServer', x=-26.50, y=66.50, z=87.97], EntityItemFrame['entity.ItemFrame.name'/465, l='MpServer', x=27.50, y=66.50, z=87.97], EntityItemFrame['entity.ItemFrame.name'/468, l='MpServer', x=27.50, y=66.50, z=-86.97], EntityItemFrame['entity.ItemFrame.name'/469, l='MpServer', x=-26.50, y=66.50, z=-86.97], EntityItemFrame['entity.ItemFrame.name'/470, l='MpServer', x=-86.97, y=66.50, z=-26.50], EntityItemFrame['entity.ItemFrame.name'/471, l='MpServer', x=-86.97, y=66.50, z=27.50], EntityItemFrame['entity.ItemFrame.name'/472, l='MpServer', x=-76.50, y=66.50, z=82.97], EntityArmorStand['§e点击！'/473, l='MpServer', x=-76.50, y=63.75, z=82.84], EntityItemFrame['entity.ItemFrame.name'/478, l='MpServer', x=-81.97, y=66.50, z=-76.50], EntityArmorStand['§e点击！'/479, l='MpServer', x=-81.84, y=63.75, z=-76.50], EntityItemFrame['entity.ItemFrame.name'/480, l='MpServer', x=39.50, y=68.50, z=29.03], EntityArmorStand['§e点击！'/481, l='MpServer', x=39.50, y=65.75, z=29.16], EntityItemFrame['entity.ItemFrame.name'/482, l='MpServer', x=29.03, y=68.50, z=-38.50], EntityArmorStand['§e点击！'/483, l='MpServer', x=29.16, y=65.75, z=-38.50], EntityItemFrame['entity.ItemFrame.name'/484, l='MpServer', x=-38.50, y=68.50, z=-28.03], EntityArmorStand['§e点击！'/485, l='MpServer', x=-38.50, y=65.75, z=-28.16], EntityItemFrame['entity.ItemFrame.name'/486, l='MpServer', x=-28.03, y=68.50, z=39.50], EntityArmorStand['§e点击！'/487, l='MpServer', x=-28.16, y=65.75, z=39.50], EntityArmorStand['§e等级§cI'/508, l='MpServer', x=0.50, y=81.88, z=33.50], EntityArmorStand['§2§l绿宝石'/509, l='MpServer', x=0.50, y=81.50, z=33.50]]\n\tRetry entities: 0 total; []\n\tServer brand: BungeeCord (Hypixel) <- vanilla\n\tServer type: Non-integrated multiplayer server\nStacktrace:\n\tat net.minecraft.client.multiplayer.WorldClient.func_72914_a(WorldClient.java:412)\n\tat net.minecraft.client.Minecraft.func_71396_d(Minecraft.java:2529)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:372)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.8.9\n\tOperating System: Windows 7 (amd64) version 6.1\n\tCPU: <unknown>\n\tJava Version: 1.8.0_60, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 221560016 bytes (211 MB) / 652267520 bytes (622 MB) up to 1215561728 bytes (1159 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx1172M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tIntCache: cache: 0, tcache: 0, allocated: 13, tallocated: 95\n\tFML: MCP 9.19 Powered by Forge 11.15.1.0 Optifine OptiFine_1.8.9_HD_U_H8 35 mods loaded, 35 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJA\tmcp{9.19} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJA\tFML{8.0.99.99} [Forge Mod Loader] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\tForge{11.15.1.0} [Minecraft Forge] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\trandftcoremod{1.8.9} [randftcoremod] (minecraft.jar)\n\tUCHIJA\tInputFix{1.8.x-v2} [InputFix] (minecraft.jar)\n\tUCHIJA\tskincoremod{1.8.9} [skincoremod] (minecraft.jar)\n\tUCHIJA\tbetterfps{1.1.0} [BetterFps] (minecraft.jar)\n\tUCHIJA\toldanimations{2.4.2} [OldAnimationsMod] (minecraft.jar)\n\tUCHIJA\titemphysic{1.3.0} [ItemPhysic] (minecraft.jar)\n\tUCHIJA\tmousedelayfix{1.0} [MouseDelayFix] (minecraft.jar)\n\tUCHIJA\tneteasecore{1.11.2} [NeteaseCore] (minecraft.jar)\n\tUCHIJA\tbasemodneteasecore{1.8.9} [BaseModNeteaseCore] (minecraft.jar)\n\tUCHIJA\tkeystrokesmod{KMV5} [KeystrokesMod] (%5B1.8.9%5D+Keystrokes+Mod+V5.jar)\n\tUCHIJA\tnetworkmod{1.0} [network rpc Mod] (4620273813222949778@3@0.jar)\n\tUCHIJA\tstoremod{1.0} [storemod] (4618419806295460477@3@0.jar)\n\tUCHIJA\tantimod{2.0} [anti addiction Mod] (4618827437296985101@3@0.jar)\n\tUCHIJA\tfiltermod{1.0} [filtermod] (4619774556351054392@3@0.jar)\n\tUCHIJA\tfriendplaymod{1.0} [Friendplay Mod] (4620273813159696403@3@0.jar)\n\tUCHIJA\tmcbasemod{1.0} [mcbasemod] (4620273813196076442@3@0.jar)\n\tUCHIJA\tupdatecore{1.0} [updatecore] (4620608833856825631@3@0.jar)\n\tUCHIJA\tplayermanager{1.0} [playermanager] (4620702952524438419@3@0.jar)\n\tUCHIJA\tfullscreenpopup{1.8.9.38000} [Fullscreen Popup Mod] (4624103992226684617@3@0.jar)\n\tUCHIJA\tskinmod{1.8.8-15739} [skinmod] (4626894634154779079@3@0.jar)\n\tUCHIJA\tmotionblurmod{1.0} [MotionBlurMod] ([1.8.9][动态模糊] MotionBlur-1.0.jar)\n\tUCHIJA\twingsmod{1.2} [Wings Mod] ([翅膀]Wings+Mod-1.2.jar)\n\tUCHIJA\tMouseTweaks{2.6.2} [Mouse Tweaks] ([鼠标手势]MouseTweaks-2.6.2-mc1.8.9 - 副本.jar)\n\tUCHIJA\tTcpNoDelayMod-2.0{1.0} [TcpNoDelayMod-2.0] (ghostmod-bymarisa.jar)\n\tUCHIJA\tPingTag{3.0} [Ping Tag] (Ping Tag Mod-3.0.jar)\n\tUCHIJA\tMemoryCleaner{1.0} [Memory Cleaner] (内存清理.jar)\n\tUCHIJA\torangetogglesprint{1.0} [Orange's Simple ToggleSprint] (强制疾跑【按I开】.jar)\n\tUCHIJA\thitrangemod{1.0} [Hit Range Mod] (攻击范围显示 HitRangeMod 1.0 MC1.8.9.jar)\n\tUCHIJA\tblockoverlay{2.1} [BlockOverlay] (方块边框自定义.jar)\n\tUCHIJA\tXaeroBetterPvP{1.9} [Better PVP Mod] (更好的pvp.jar)\n\tUCHIJA\tkeystroke{1.2} [Ben's Keystrokes] (速搭mod 按v开启[By-苏辰汉化].jar)\n\tUCHIJA\tsharpnessparticles{1.1} [Sharpness Particles] (锋利粒子mod.jar)\n\tLoaded coremods (and transformers):\nSkinCore (4626894634154779079@3@0.jar)\n  com.netease.mc.mod.skin.coremod.SkinCoreTransformer\nBaseModNeteaseCore (4620273813196076442@3@0.jar)\n  com.netease.mc.core.base.NeteaseCoreTransformer\nItemPatchingLoader (ItemPhysic-Mod-Lite-1.8.9.jar)\n  com.creativemd.itemphysic.ItemTransformer\nRandFTCore (4618421856281952104@2@33.jar)\n  com.netease.mc.mod.randomfont.RandFTCoreTransformer\nMain ([防砍动画]OldAnimationsMod v2.4.2 FORGE MC1.8.9.jar)\n  com.spiderfrog.main.ClassTransformer\nInputFix (4618424574399199550@3@0.jar)\n  lain.mods.inputfix.InputFixTransformer\nBetterFps ([1.8Fps提升]Ex-BetterFps-1.1.0[laozikaiG].jar)\n  me.guichaguri.betterfps.BetterFpsTransformer\nFMLLoadingPlugin ([防砍动画未响应修复]OldAnimationsModUnresponsiveFix-1.0.jar)\n  io.github.zekerzhayard.unresponsivefix.ClassTransformer\nFMLLoadingPlugin (【鼠标无延迟】1.8nodelay mouse.jar)\n  io.prplz.mousedelayfix.ClassTransformer\nNeteaseCore (4619774556351054392@3@0.jar)\n  com.netease.mc.core.NeteaseCoreTransformer\n\tGL info: ' Vendor: 'Intel' Version: '4.3.0 - Build 10.18.14.4578' Renderer: 'Intel(R) HD Graphics 4400'\n\tLaunched Version: 1.8.9\n\tLWJGL: 2.9.4\n\tOpenGL: Intel(R) HD Graphics 4400 GL version 4.3.0 - Build 10.18.14.4578, Intel\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing GL 1.3 texture combiners.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nShaders are available because OpenGL 2.1 is supported.\nVBOs are available because OpenGL 1.5 is supported.\n\n\tUsing VBOs: No\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: Chroma音效材质.zip, §c§l药儿§b§lPVP材质包 弯剑柄, 我现在正在用的字体.zip\n\tCurrent Language: English (US)\n\tProfiler Position: N/A (disabled)\n\tCPU: <unknown>\n\tOptiFine Version: OptiFine_1.8.9_HD_U_H8\n\tRender Distance Chunks: 10\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.3.0 - Build 10.18.14.4578\n\tOpenGlRenderer: Intel(R) HD Graphics 4400\n\tOpenGlVendor: Intel\n\tCpuCount: 4"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/customnpc.txt",
    "content": "---- Minecraft Crash Report ----\n// I'm sorry, Dave.\n\nTime: 1/22/19 12:20 AM\nDescription: Ticking entity\n\njava.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(Unknown Source)\n\tat java.util.HashMap$KeyIterator.next(Unknown Source)\n\tat net.minecraft.entity.EntityLivingBase.func_70679_bo(EntityLivingBase.java:591)\n\tat net.minecraft.entity.EntityLivingBase.func_70030_z(EntityLivingBase.java:332)\n\tat net.minecraft.entity.EntityLiving.func_70030_z(EntityLiving.java:164)\n\tat net.minecraft.entity.Entity.func_70071_h_(Entity.java:409)\n\tat net.minecraft.entity.EntityLivingBase.func_70071_h_(EntityLivingBase.java:1848)\n\tat net.minecraft.entity.EntityLiving.func_70071_h_(EntityLiving.java:213)\n\tat noppes.npcs.entity.EntityNPCInterface.func_70071_h_(EntityNPCInterface.java:246)\n\tat noppes.npcs.entity.EntityCustomNpc.func_70071_h_(EntityCustomNpc.java:34)\n\tat net.minecraft.world.World.func_72866_a(World.java:2669)\n\tat net.minecraft.world.WorldServer.func_72866_a(WorldServer.java:811)\n\tat net.minecraft.world.World.func_72870_g(World.java:2613)\n\tat net.minecraft.world.World.func_72939_s(World.java:2414)\n\tat net.minecraft.world.WorldServer.func_72939_s(WorldServer.java:636)\n\tat net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:951)\n\tat net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:458)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:806)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:665)\n\tat java.lang.Thread.run(Unknown Source)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat java.util.HashMap$HashIterator.nextNode(Unknown Source)\n\tat java.util.HashMap$KeyIterator.next(Unknown Source)\n\tat net.minecraft.entity.EntityLivingBase.func_70679_bo(EntityLivingBase.java:591)\n\tat net.minecraft.entity.EntityLivingBase.func_70030_z(EntityLivingBase.java:332)\n\tat net.minecraft.entity.EntityLiving.func_70030_z(EntityLiving.java:164)\n\tat net.minecraft.entity.Entity.func_70071_h_(Entity.java:409)\n\tat net.minecraft.entity.EntityLivingBase.func_70071_h_(EntityLivingBase.java:1848)\n\tat net.minecraft.entity.EntityLiving.func_70071_h_(EntityLiving.java:213)\n\tat noppes.npcs.entity.EntityNPCInterface.func_70071_h_(EntityNPCInterface.java:246)\n\tat noppes.npcs.entity.EntityCustomNpc.func_70071_h_(EntityCustomNpc.java:34)\n\tat net.minecraft.world.World.func_72866_a(World.java:2669)\n\tat net.minecraft.world.WorldServer.func_72866_a(WorldServer.java:811)\n\tat net.minecraft.world.World.func_72870_g(World.java:2613)\n\n-- Entity being ticked --\nDetails:\n\tEntity Type: customnpcs.CustomNpc (noppes.npcs.entity.EntityCustomNpc)\n\tEntity ID: 79\n\tEntity Name: 复仇马魂\n\tEntity's Exact location: 99942.59, 4.00, 100000.98\n\tEntity's Block location: World: (99942,4,100000), Chunk: (at 6,0,0 in 6246,6250; contains blocks 99936,0,100000 to 99951,255,100015), Region: (195,195; contains chunks 6240,6240 to 6271,6271, blocks 99840,0,99840 to 100351,255,100351)\n\tEntity's Momentum: 0.05, -0.08, -0.11\nStacktrace:\n\tat net.minecraft.world.World.func_72939_s(World.java:2414)\n\tat net.minecraft.world.WorldServer.func_72939_s(WorldServer.java:636)\n\n-- Affected level --\nDetails:\n\tLevel name: pvp\n\tAll players: 2 total; [EntityPlayerMP['Hui_Yan'/9, l='pvp', x=99947.38, y=4.00, z=99990.13](Hui_Yan at 99947.3789625726,4.0,99990.13423393313), EntityPlayerMP['chiyan_C'/175, l='pvp', x=99939.82, y=14.04, z=100010.74](chiyan_C at 99939.82052702823,14.041729801880352,100010.73578345944)]\n\tChunk stats: ServerChunkCache: 99 Drop: 0\n\tLevel seed: 2672788333781657985\n\tLevel generator: ID 01 - flat, ver 0. Features enabled: true\n\tLevel generator options:\n\tLevel spawn location: World: (-881,4,-100), Chunk: (at 15,0,12 in -56,-7; contains blocks -896,0,-112 to -881,255,-97), Region: (-2,-1; contains chunks -64,-32 to -33,-1, blocks -1024,0,-512 to -513,255,-1)\n\tLevel time: 227387166 game time, 227583837 day time\n\tLevel dimension: 3\n\tLevel storage version: 0x04ABD - Anvil\n\tLevel weather: Rain time: 75183 (now: false), thunder time: 88592 (now: false)\n\tLevel game mode: Game mode: survival (ID 0). Hardcore: false. Cheats: false\nStacktrace:\n\tat net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:951)\n\tat net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:458)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:806)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:665)\n\tat java.lang.Thread.run(Unknown Source)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tKCauldron Version: cc.uraniummc:Uranium:1710-dev-4-B210 UNOFFICIAL DON'T REPORT THIS CRASH\n\tPlugins: ItemDurability, ILSRepair, PlaceholderAPI, GroupManager, TigerSigns, tpLogin, ConsoleSpamFix, HyperFlyCheck, CloudTrade, WorldProtect, NoSellLoreItemToGlobalShop, NoCmds, BanItem, PointShop, EasyAPI, PreFixGui, VexView, RichAutoMessage, ClickLimit, ILSAddonOrnament, Deadbolt, WorldEdit, FastRespawn, Essentials, EasyCommand, TimingsPatcher, uSkyBlock, CraftGuard, fixILS, VexInfoBar, ProtocolLib, Multiverse-Core, MCserverManager, LevelChat, EssentialsChat, GlobalMarket, iConomy, StarLogin, Vault, SFWSupport, Lores, ScriptBlock, LR-ActionBarMessage, VexKeyBoardHelper, NeverLag, HolographicDisplays, OnlineMoney, PlayerPoints, WorldGuard, ItemLoreStats, ChestCommands, EssentialsProtect, EssentialsAntiBuild, JoinMessage, VexCraftTable, EssentialsSpawn, RPG_Items, ColorMOTD, HamsterAPI, Lottery\n\tDisabled Plugins:\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_101, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 3282678464 bytes (3130 MB) / 6844579840 bytes (6527 MB) up to 27030192128 bytes (25778 MB)\n\tJVM Flags: 4 total; -Xms512m -Xmx29000m -XX:+AggressiveOpts -XX:+UseCompressedOops\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1614 42 mods loaded, 42 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJAAAA\tmcp{9.05} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJAAAA\tFML{7.10.99.99} [Forge Mod Loader] (Server.jar)\n\tUCHIJAAAA\tForge{10.13.4.1614} [Minecraft Forge] (Server.jar)\n\tUCHIJAAAA\tkimagine{0.2} [KImagine] (minecraft.jar)\n\tUCHIJAAAA\tUraniumPlus{${UMP_VER}} [Added title and actionbar support for client and server] (Server.jar)\n\tUCHIJAAAA\tcreativecore{1.3.14} [CreativeCore] (-在线图片加载.jar)\n\tUCHIJAAAA\topframe{2.1} [OnlinePictureFrame] (-在线图片加载前置.jar)\n\tUCHIJAAAA\tBlock3DPixelMc{1.7.x} [PokemonGo-Block] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tMod_bag{1.7.x} [PokemonGo-Bag] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tpixelmcrop{1.7.10} [PokemonGo-Crop] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tMod_Wing{1.7.x} [PokemonGo-Wing] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tshopy{1.7.x} [PokemonGo-Shop] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tbikesystem{1.0} [bikesystem] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tBANK-ONEPIECE BLOCK3D{Takakung} [Takakung BLOCK3D] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tchillingmoneysystem{1.0} [ChillingMoneySystem] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tPIXEL-STATION{Takakung} [PIXEL-STATION] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tPIXEL-STATION 3D{Takakung} [PIXEL-STATION 3D] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tshopplayers{1.7.10} [CubeMMOShop] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tGrimoireOfGaia{1.0.0} [Grimoire of Gaia 3] ([魔典盖亚III]1.7.10-1.2.7.jar)\n\tUCHIJAAAA\tChestTransporter{2.0.6} [Chest Transporter] (ChestTransporter-1.7.10-2.0.6.jar)\n\tUCHIJAAAA\tcustommc{1.0v} [custommc] (CustomMc.jar)\n\tUCHIJAAAA\tgxozb{0.0.0.1} [Minecreft Not Enough Items] (gxozb-1.0(3).jar)\n\tUCHIJAAAA\tlycanitesmobs{1.9.0e - MC 1.7.10} [Lycanites Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tarcticmobs{1.9.0e - MC 1.7.10} [Lycanites Arctic Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tdemonmobs{1.9.0e - MC 1.7.10} [Lycanites Demon Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tdesertmobs{1.9.0e - MC 1.7.10} [Lycanites Desert Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tforestmobs{1.9.0e - MC 1.7.10} [Lycanites Forest Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tfreshwatermobs{1.9.0e - MC 1.7.10} [Lycanites Freshwater Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tinfernomobs{1.9.0e - MC 1.7.10} [Lycanites Inferno Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tjunglemobs{1.9.0e - MC 1.7.10} [Lycanites Jungle Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tmountainmobs{1.9.0e - MC 1.7.10} [Lycanites Mountain Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tplainsmobs{1.9.0e - MC 1.7.10} [Lycanites Plains Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tsaltwatermobs{1.9.0e - MC 1.7.10} [Lycanites Saltwater Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tswampmobs{1.9.0e - MC 1.7.10} [Lycanites Swamp Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\twizardry{1.1.4} [Electroblob's Wizardry] (mofa.jar)\n\tUCHIJAAAA\tNBTEdit{1.7.2.2} [In-game NBTEdit] (NBTEdit_1.7.10.jar)\n\tUCHIJAAAA\tpozo{4.0.0} [pozo] (pozo全贴图版.jar)\n\tUCHIJAAAA\tYoHern{2.1.0} [YoHern] (古衍秘制.jar)\n\tUCHIJAAAA\tarmourersWorkshop{1.7.10-0.45.0} [Armourer's Workshop] (时装工坊-1.7.10-0.45.0.jar)\n\tUCHIJAAAA\tplushieWrapper{0.0.0} [Plushie Wrapper] (时装工坊-1.7.10-0.45.0.jar)\n\tUCHIJAAAA\tnewnpc{1.0.0} [NewNpc] (炽焰改-贴图Mod1.7.10.jar)\n\tUCHIJAAAA\tcustomnpcs{1.7.10d} [CustomNpcs] (自定义NPC(1).jar)\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tPlayer Count: 2 / 888; [EntityPlayerMP['Hui_Yan'/9, l='pvp', x=99947.38, y=4.00, z=99990.13](Hui_Yan at 99947.3789625726,4.0,99990.13423393313), EntityPlayerMP['chiyan_C'/175, l='pvp', x=99939.82, y=14.04, z=100010.74](chiyan_C at 99939.82052702823,14.041729801880352,100010.73578345944)]\n\tIs Modded: Definitely; Server brand changed to 'uranium,kcauldron,cauldron,craftbukkit,mcpc,fml,forge'\n\tType: Dedicated Server (map_server.txt)"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/customskinloader.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\n  FMLLoadingPlugin (【内存溢出修复】MemoryFix-0.3.jar)\n  ForgePlugin (【超战皮肤显示】CustomSkinLoader.jar)\n  SkinCore (4626894634154779079@3@0.jar)\n  Main (【防砍模组】OldAnimationsMod v2.4 FORGE MC1.8.9.jar)\n  BaseModNeteaseCore (4620273813196076442@3@0.jar)\n  RandFTCore (4618421856281952104@2@33.jar)\n  InputFix (4618424574399199550@3@0.jar)\n  FMLLoadingPlugin ([防砍动画未响应修复]OldAnimationsModUnresponsiveFix-1.0.jar)\n  NeteaseCore (4619774556351054392@3@0.jar)\nContact their authors BEFORE contacting forge\n\n// Uh... Did I do that?\n\nTime: 19-3-16 下午12:46\nDescription: Rendering entity in world\n\njava.lang.OutOfMemoryError: unable to create new native thread\n\tat java.lang.Thread.start0(Native Method)\n\tat java.lang.Thread.start(Unknown Source)\n\tat customskinloader.profile.DynamicSkullManager.getTexture(DynamicSkullManager.java:160)\n\tat customskinloader.CustomSkinLoader.loadProfileFromCache(CustomSkinLoader.java:145)\n\tat customskinloader.fake.FakeSkinManager.loadSkinFromCache(FakeSkinManager.java:74)\n\tat net.minecraft.client.resources.SkinManager.func_152788_a(SourceFile)\n\tat net.minecraft.client.renderer.tileentity.TileEntitySkullRenderer.func_180543_a(SourceFile:76)\n\tat net.minecraft.client.renderer.entity.layers.LayerCustomHead.func_177141_a(SourceFile:90)\n\tat net.minecraft.client.renderer.entity.RendererLivingEntity.func_177093_a(RendererLivingEntity.java:481)\n\tat net.minecraft.client.renderer.entity.RendererLivingEntity.func_76986_a(RendererLivingEntity.java:186)\n\tat net.minecraft.client.renderer.entity.RenderPlayer.func_76986_a(RenderPlayer.java:63)\n\tat net.minecraft.client.renderer.entity.RenderPlayer.func_76986_a(RenderPlayer.java:23)\n\tat com.orangemarshall.enhancements.custom.CustomHitboxRenderManager.func_147939_a(CustomHitboxRenderManager.java:66)\n\tat net.minecraft.client.renderer.entity.RenderManager.func_147936_a(RenderManager.java:356)\n\tat net.minecraft.client.renderer.entity.RenderManager.func_147937_a(RenderManager.java:323)\n\tat net.minecraft.client.renderer.RenderGlobal.func_180446_a(RenderGlobal.java:834)\n\tat net.minecraft.client.renderer.EntityRenderer.func_175068_a(EntityRenderer.java:1751)\n\tat net.minecraft.client.renderer.EntityRenderer.func_78471_a(EntityRenderer.java:1580)\n\tat net.minecraft.client.renderer.EntityRenderer.func_181560_a(EntityRenderer.java:1370)\n\tat net.minecraft.client.Minecraft.func_71411_J(Minecraft.java:1044)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:351)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat java.lang.Thread.start0(Native Method)\n\tat java.lang.Thread.start(Unknown Source)\n\tat customskinloader.profile.DynamicSkullManager.getTexture(DynamicSkullManager.java:160)\n\tat customskinloader.CustomSkinLoader.loadProfileFromCache(CustomSkinLoader.java:145)\n\tat customskinloader.fake.FakeSkinManager.loadSkinFromCache(FakeSkinManager.java:74)\n\tat net.minecraft.client.resources.SkinManager.func_152788_a(SourceFile)\n\tat net.minecraft.client.renderer.tileentity.TileEntitySkullRenderer.func_180543_a(SourceFile:76)\n\tat net.minecraft.client.renderer.entity.layers.LayerCustomHead.func_177141_a(SourceFile:90)\n\tat net.minecraft.client.renderer.entity.RendererLivingEntity.func_177093_a(RendererLivingEntity.java:481)\n\tat net.minecraft.client.renderer.entity.RendererLivingEntity.func_76986_a(RendererLivingEntity.java:186)\n\tat net.minecraft.client.renderer.entity.RenderPlayer.func_76986_a(RenderPlayer.java:63)\n\tat net.minecraft.client.renderer.entity.RenderPlayer.func_76986_a(RenderPlayer.java:23)\n\n-- Entity being rendered --\nDetails:\n\tEntity Type: null (net.minecraft.client.entity.EntityOtherPlayerMP)\n\tEntity ID: 573304\n\tEntity Name: 唯一的毁灭i\n\tEntity's Exact location: 18.09, 51.97, 13.13\n\tEntity's Block location: 18.00,51.00,13.00 - World: (18,51,13), Chunk: (at 2,3,13 in 1,0; contains blocks 16,0,0 to 31,255,15), Region: (0,0; contains chunks 0,0 to 31,31, blocks 0,0,0 to 511,255,511)\n\tEntity's Momentum: 0.00, 0.00, 0.00\n\tEntity's Rider: ~~ERROR~~ NullPointerException: null\n\tEntity's Vehicle: ~~ERROR~~ NullPointerException: null\n\n-- Renderer details --\nDetails:\n\tAssigned renderer: net.minecraft.client.renderer.entity.RenderPlayer@359ec\n\tLocation: 20.47,-2.03,12.04 - World: (20,-3,12), Chunk: (at 4,-1,12 in 1,0; contains blocks 16,0,0 to 31,255,15), Region: (0,0; contains chunks 0,0 to 31,31, blocks 0,0,0 to 511,255,511)\n\tRotation: 124.75678\n\tDelta: 0.8521968\nStacktrace:\n\tat com.orangemarshall.enhancements.custom.CustomHitboxRenderManager.func_147939_a(CustomHitboxRenderManager.java:66)\n\tat net.minecraft.client.renderer.entity.RenderManager.func_147936_a(RenderManager.java:356)\n\tat net.minecraft.client.renderer.entity.RenderManager.func_147937_a(RenderManager.java:323)\n\tat net.minecraft.client.renderer.RenderGlobal.func_180446_a(RenderGlobal.java:834)\n\tat net.minecraft.client.renderer.EntityRenderer.func_175068_a(EntityRenderer.java:1751)\n\tat net.minecraft.client.renderer.EntityRenderer.func_78471_a(EntityRenderer.java:1580)\n\n-- Affected level --\nDetails:\n\tLevel name: MpServer\n\tAll players: 13 total; [EntityPlayerSP['Rucec的弟弟'/88680, l='MpServer', x=-2.37, y=54.00, z=1.09], EntityOtherPlayerMP['核桃人'/573171, l='MpServer', x=1.59, y=53.00, z=5.19], EntityOtherPlayerMP['LonelyChest'/574415, l='MpServer', x=14.13, y=51.00, z=7.69], EntityOtherPlayerMP['苦恼的雾a'/573985, l='MpServer', x=-18.72, y=62.00, z=8.28], EntityOtherPlayerMP['Sking是你爹爹'/574963, l='MpServer', x=-1.94, y=54.00, z=-0.88], EntityOtherPlayerMP['yRicky'/574993, l='MpServer', x=-1.25, y=54.00, z=-0.41], EntityOtherPlayerMP['s9w8yqgs10'/123, l='MpServer', x=14.50, y=51.00, z=-6.50], EntityOtherPlayerMP['迁葬性格如此丶'/575017, l='MpServer', x=7.69, y=54.72, z=-1.31], EntityOtherPlayerMP['over100'/575031, l='MpServer', x=3.50, y=53.50, z=-0.59], EntityOtherPlayerMP['逍遥九天丿蛋蛋L'/575046, l='MpServer', x=9.45, y=54.00, z=-1.08], EntityOtherPlayerMP['花心大萝卜吖'/575056, l='MpServer', x=15.97, y=54.00, z=-0.14], EntityOtherPlayerMP['唯一的毁灭i'/573304, l='MpServer', x=18.09, y=51.97, z=13.13], EntityOtherPlayerMP['Heartbreak_'/575165, l='MpServer', x=-0.72, y=54.00, z=-0.22]]\n\tChunk stats: MultiplayerChunkCache: 15, 15\n\tLevel seed: 0\n\tLevel generator: ID 01 - flat, ver 0. Features enabled: false\n\tLevel generator options:\n\tLevel spawn location: -3.00,54.00,1.00 - World: (-3,54,1), Chunk: (at 13,3,1 in -1,0; contains blocks -16,0,0 to -1,255,15), Region: (-1,0; contains chunks -32,0 to -1,31, blocks -512,0,0 to -1,255,511)\n\tLevel time: 1243847886 game time, 361000 day time\n\tLevel dimension: 0\n\tLevel storage version: 0x00000 - Unknown?\n\tLevel weather: Rain time: 0 (now: false), thunder time: 0 (now: false)\n\tLevel game mode: Game mode: survival (ID 0). Hardcore: false. Cheats: false\n\tForced entities: 83 total; [EntityBat['Bat'/129, l='MpServer', x=26.50, y=51.00, z=6.50], EntityArmorStand['Armor Stand'/3, l='MpServer', x=14.16, y=51.00, z=-7.66], EntityOtherPlayerMP['唯一的毁灭i'/573304, l='MpServer', x=18.09, y=51.97, z=13.13], EntityArmorStand['Armor Stand'/136, l='MpServer', x=28.50, y=52.69, z=0.50], EntityArmorStand['Armor Stand'/137, l='MpServer', x=28.50, y=52.31, z=0.50], EntityArmorStand['Armor Stand'/138, l='MpServer', x=28.50, y=51.94, z=0.50], EntityArmorStand['Armor Stand'/139, l='MpServer', x=29.50, y=52.69, z=-2.50], EntityArmorStand['Armor Stand'/140, l='MpServer', x=29.50, y=52.31, z=-2.50], EntityArmorStand['Armor Stand'/141, l='MpServer', x=29.50, y=51.94, z=-2.50], EntityArmorStand['Armor Stand'/142, l='MpServer', x=29.50, y=53.03, z=3.50], EntityArmorStand['Armor Stand'/143, l='MpServer', x=29.50, y=52.69, z=3.50], EntityOtherPlayerMP['Heartbreak_'/575165, l='MpServer', x=-0.72, y=54.00, z=-0.22], EntityArmorStand['Armor Stand'/144, l='MpServer', x=29.50, y=52.31, z=3.50], EntityArmorStand['Armor Stand'/145, l='MpServer', x=29.50, y=51.94, z=3.50], EntityArmorStand['§e§l点击开始游戏'/146, l='MpServer', x=26.50, y=51.66, z=6.50], EntityArmorStand['§6§l决斗游戏§bMegaWalls'/147, l='MpServer', x=26.50, y=51.28, z=6.50], EntityArmorStand['§e§l44名玩家'/148, l='MpServer', x=26.50, y=50.91, z=6.50], EntityBat['Bat'/149, l='MpServer', x=14.50, y=52.35, z=-6.50], EntityOtherPlayerMP['yRicky'/574993, l='MpServer', x=-1.25, y=54.00, z=-0.41], EntityArmorStand['Armor Stand'/574994, l='MpServer', x=-0.15, y=52.59, z=-1.46], EntityArmorStand['§2Blt§4SB'/574995, l='MpServer', x=-0.15, y=53.25, z=-1.46], EntityZombie['Zombie'/574996, l='MpServer', x=-1.00, y=54.00, z=-1.44], EntityOtherPlayerMP['迁葬性格如此丶'/575017, l='MpServer', x=7.69, y=54.72, z=-1.31], EntityOtherPlayerMP['苦恼的雾a'/573985, l='MpServer', x=-18.72, y=62.00, z=8.28], EntityArmorStand['§c4个礼包！§r'/575164, l='MpServer', x=14.50, y=51.56, z=-6.50], EntityOtherPlayerMP['Heartbreak_'/575165, l='MpServer', x=-0.72, y=54.00, z=-0.22], EntityArmorStand['Armor Stand'/575166, l='MpServer', x=5.50, y=51.00, z=-4.50], EntityZombie['Zombie'/575167, l='MpServer', x=5.50, y=51.00, z=-4.50], EntityArmorStand['Armor Stand'/564915, l='MpServer', x=28.50, y=53.00, z=0.50], EntityPlayerSP['Rucec的弟弟'/88680, l='MpServer', x=-2.37, y=54.00, z=1.09], EntityOtherPlayerMP['over100'/575031, l='MpServer', x=3.50, y=53.50, z=-0.59], EntityOtherPlayerMP['LonelyChest'/574415, l='MpServer', x=14.13, y=51.00, z=7.69], EntityArmorStand['Armor Stand'/575168, l='MpServer', x=14.50, y=51.56, z=-6.50], EntityOtherPlayerMP['逍遥九天丿蛋蛋L'/575046, l='MpServer', x=9.45, y=54.00, z=-1.08], EntitySlime['§8Lv§7001 §r史莱姆（巨）'/575047, l='MpServer', x=-2.88, y=56.68, z=5.95], EntityArmorStand['Armor Stand'/575064, l='MpServer', x=-1.55, y=51.03, z=9.15], EntityWither['§6空岛战争§a实验室更新 - §d§l疯狂TNT§a开放中!'/-1234, l='MpServer', x=30.63, y=54.00, z=1.06], EntityArmorStand['Armor Stand'/575065, l='MpServer', x=-1.55, y=51.44, z=9.24], EntityZombie['Zombie'/575066, l='MpServer', x=-1.59, y=51.50, z=8.03], EntityOtherPlayerMP['核桃人'/573171, l='MpServer', x=1.59, y=53.00, z=5.19], EntityOtherPlayerMP['花心大萝卜吖'/575056, l='MpServer', x=15.97, y=54.00, z=-0.14], EntityArmorStand['§28KDDDD'/575057, l='MpServer', x=-1.61, y=50.44, z=8.13], EntityArmorStand['Armor Stand'/575058, l='MpServer', x=-1.65, y=50.53, z=8.33], EntityArmorStand['Armor Stand'/575059, l='MpServer', x=-1.71, y=50.19, z=8.83], EntityArmorStand['Armor Stand'/575060, l='MpServer', x=-1.74, y=50.53, z=9.15], EntityOtherPlayerMP['LonelyChest'/574415, l='MpServer', x=14.13, y=51.00, z=7.69], EntityArmorStand['Armor Stand'/575061, l='MpServer', x=-1.83, y=51.03, z=8.30], EntityArmorStand['Armor Stand'/575062, l='MpServer', x=-1.93, y=51.03, z=9.09], EntityOtherPlayerMP['Sking是你爹爹'/574963, l='MpServer', x=-1.94, y=54.00, z=-0.88], EntityArmorStand['Armor Stand'/575063, l='MpServer', x=-1.46, y=51.03, z=8.34], EntityOtherPlayerMP['苦恼的雾a'/573985, l='MpServer', x=-18.72, y=62.00, z=8.28], EntityOtherPlayerMP['yRicky'/574993, l='MpServer', x=-1.25, y=54.00, z=-0.41], EntityOtherPlayerMP['s9w8yqgs10'/123, l='MpServer', x=14.50, y=51.00, z=-6.50], EntityOtherPlayerMP['迁葬性格如此丶'/575017, l='MpServer', x=7.69, y=54.72, z=-1.31], EntityOtherPlayerMP['逍遥九天丿蛋蛋L'/575046, l='MpServer', x=9.45, y=54.00, z=-1.08], EntityOtherPlayerMP['over100'/575031, l='MpServer', x=3.50, y=53.50, z=-0.59], EntityOtherPlayerMP['花心大萝卜吖'/575056, l='MpServer', x=15.97, y=54.00, z=-0.14], EntityArmorStand['Armor Stand'/574968, l='MpServer', x=-1.74, y=53.53, z=1.09], EntityOtherPlayerMP['唯一的毁灭i'/573304, l='MpServer', x=18.09, y=51.97, z=13.13], EntityArmorStand['Armor Stand'/574969, l='MpServer', x=-1.74, y=53.53, z=1.90], EntityArmorStand['Armor Stand'/574970, l='MpServer', x=-1.34, y=53.53, z=1.09], EntityArmorStand['Armor Stand'/574971, l='MpServer', x=-1.33, y=53.53, z=1.89], EntityArmorStand['Armor Stand'/574972, l='MpServer', x=-1.33, y=53.94, z=1.99], EntityVillager['Villager'/116, l='MpServer', x=14.50, y=51.00, z=7.50], EntityZombie['Zombie'/574973, l='MpServer', x=-1.56, y=54.00, z=0.84], EntityArmorStand['§e§l右键点击'/117, l='MpServer', x=14.50, y=51.16, z=7.50], EntityArmorStand['§b任务达人'/118, l='MpServer', x=14.50, y=51.47, z=7.50], EntityArmorStand['§b神秘宝库'/119, l='MpServer', x=21.50, y=52.38, z=-7.50], EntityArmorStand['§e§l右键点击'/120, l='MpServer', x=21.50, y=52.09, z=-7.50], EntityArmorStand['§b神秘宝库'/121, l='MpServer', x=21.50, y=52.38, z=8.50], EntityArmorStand['§e§l右键点击'/122, l='MpServer', x=21.50, y=52.09, z=8.50], EntityOtherPlayerMP['核桃人'/573171, l='MpServer', x=1.59, y=53.00, z=5.19], EntityOtherPlayerMP['Sking是你爹爹'/574963, l='MpServer', x=-1.94, y=54.00, z=-0.88], EntityOtherPlayerMP['s9w8yqgs10'/123, l='MpServer', x=14.50, y=51.00, z=-6.50], EntityArmorStand['§5Awakening'/574964, l='MpServer', x=-1.55, y=52.94, z=0.90], EntityArmorStand['Armor Stand'/573172, l='MpServer', x=0.53, y=52.09, z=4.03], EntityArmorStand['Armor Stand'/574965, l='MpServer', x=-1.55, y=53.03, z=1.09], EntityArmorStand['§d§lThreadripper'/573173, l='MpServer', x=0.53, y=52.75, z=4.03], EntityArmorStand['§e§l右键点击§r'/125, l='MpServer', x=14.50, y=50.97, z=-6.50], EntityArmorStand['Armor Stand'/574966, l='MpServer', x=-1.54, y=52.69, z=1.59], EntityZombie['Zombie'/573174, l='MpServer', x=0.47, y=53.50, z=3.53], EntityArmorStand['§b礼包使者§r'/126, l='MpServer', x=14.50, y=51.25, z=-6.50], EntityArmorStand['Armor Stand'/574967, l='MpServer', x=-1.53, y=53.03, z=1.92]]\n\tRetry entities: 0 total; []\n\tServer brand: BungeeCord (Hypixel) <- vanilla\n\tServer type: Non-integrated multiplayer server\nStacktrace:\n\tat net.minecraft.client.multiplayer.WorldClient.func_72914_a(WorldClient.java:412)\n\tat net.minecraft.client.Minecraft.func_71396_d(Minecraft.java:2529)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:372)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.8.9\n\tOperating System: Windows 7 (x86) version 6.1\n\tCPU: <unknown>\n\tJava Version: 1.8.0_101, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) Client VM (mixed mode), Oracle Corporation\n\tMemory: 188731064 bytes (179 MB) / 667463680 bytes (636 MB) up to 1060372480 bytes (1011 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx1024M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tIntCache: cache: 0, tcache: 0, allocated: 13, tallocated: 95\n\tFML: MCP 9.19 Powered by Forge 11.15.1.0 Optifine OptiFine_1.8.9_HD_U_H8 29 mods loaded, 29 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJA\tmcp{9.19} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJA\tFML{8.0.99.99} [Forge Mod Loader] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\tForge{11.15.1.0} [Minecraft Forge] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\trandftcoremod{1.8.9} [randftcoremod] (minecraft.jar)\n\tUCHIJA\tInputFix{1.8.x-v2} [InputFix] (minecraft.jar)\n\tUCHIJA\tskincoremod{1.8.9} [skincoremod] (minecraft.jar)\n\tUCHIJA\toldanimations{2.4} [OldAnimationsMod] (minecraft.jar)\n\tUCHIJA\tneteasecore{1.11.2} [NeteaseCore] (minecraft.jar)\n\tUCHIJA\tbasemodneteasecore{1.8.9} [BaseModNeteaseCore] (minecraft.jar)\n\tUCHIJA\tnetworkmod{1.0} [network rpc Mod] (4620273813222949778@3@0.jar)\n\tUCHIJA\tstoremod{1.0} [storemod] (4618419806295460477@3@0.jar)\n\tUCHIJA\tantimod{2.0} [anti addiction Mod] (4618827437296985101@3@0.jar)\n\tUCHIJA\tfiltermod{1.0} [filtermod] (4619774556351054392@3@0.jar)\n\tUCHIJA\tfriendplaymod{1.0} [Friendplay Mod] (4620273813159696403@3@0.jar)\n\tUCHIJA\tmcbasemod{1.0} [mcbasemod] (4620273813196076442@3@0.jar)\n\tUCHIJA\tupdatecore{1.0} [updatecore] (4620608833856825631@3@0.jar)\n\tUCHIJA\tplayermanager{1.0} [playermanager] (4620702952524438419@3@0.jar)\n\tUCHIJA\tfullscreenpopup{1.8.9.38000} [Fullscreen Popup Mod] (4624103992226684617@3@0.jar)\n\tUCHIJA\tskinmod{1.8.8-15739} [skinmod] (4626894634154779079@3@0.jar)\n\tUCHIJA\tpotioneffects{1.0} [potioneffects] ([1.8.9] PotionEffects.jar)\n\tUCHIJA\tmwautokill{1.0} [mwautokill] ([1.8.9]MWAutoKill V1.1.jar)\n\tUCHIJA\tsidebarmod{1.01} [SidebarMod] ([右侧信息]SidebarMod-1.01.jar)\n\tUCHIJA\tPingTag{3.0} [Ping Tag] ([延迟显示] PingTag Mod-3.0.jar)\n\tUCHIJA\ttogglesprint{1.0} [togglesprint] ([强制疾跑] ToggleSprint.jar)\n\tUCHIJA\tdynamicfov{1.0} [Dynamic FOV] (dynamicfov-1.0.jar)\n\tUCHIJA\tmemoryfix{0.3} [MemoryFix] (【内存溢出修复】MemoryFix-0.3.jar)\n\tUCHIJA\tenhancements{7.7} [Vanilla Enhancements] (【原版增强】Vanilla Enhancements-7.7.jar)\n\tUCHIJA\tcustomskinloader{14.7} [CustomSkinLoader] (【超战皮肤显示】CustomSkinLoader.jar)\n\tUCHIJA\tDJTastyMod{1.0} [DJTastyMod] (披风模组.jar)\n\tLoaded coremods (and transformers):\nFMLLoadingPlugin (【内存溢出修复】MemoryFix-0.3.jar)\n  io.prplz.memoryfix.ClassTransformer\nForgePlugin (【超战皮肤显示】CustomSkinLoader.jar)\n  customskinloader.forge.TransformerManager\nSkinCore (4626894634154779079@3@0.jar)\n  com.netease.mc.mod.skin.coremod.SkinCoreTransformer\nMain (【防砍模组】OldAnimationsMod v2.4 FORGE MC1.8.9.jar)\n  com.spiderfrog.main.ClassTransformer\nBaseModNeteaseCore (4620273813196076442@3@0.jar)\n  com.netease.mc.core.base.NeteaseCoreTransformer\nRandFTCore (4618421856281952104@2@33.jar)\n  com.netease.mc.mod.randomfont.RandFTCoreTransformer\nInputFix (4618424574399199550@3@0.jar)\n  lain.mods.inputfix.InputFixTransformer\nFMLLoadingPlugin ([防砍动画未响应修复]OldAnimationsModUnresponsiveFix-1.0.jar)\n  io.github.zekerzhayard.unresponsivefix.ClassTransformer\nNeteaseCore (4619774556351054392@3@0.jar)\n  com.netease.mc.core.NeteaseCoreTransformer\n\tLaunched Version: 1.8.9\n\tLWJGL: 2.9.4\n\tOpenGL: AMD Radeon HD 7560D GL version 4.4.13283 Compatibility Profile Context 14.501.1003.0, ATI Technologies Inc.\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing GL 1.3 texture combiners.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nShaders are available because OpenGL 2.1 is supported.\nVBOs are available because OpenGL 1.5 is supported.\n\n\tUsing VBOs: No\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: !Dynamic Duo\n\tCurrent Language: English (US)\n\tProfiler Position: N/A (disabled)\n\tCPU: <unknown>\n\tOptiFine Version: OptiFine_1.8.9_HD_U_H8\n\tRender Distance Chunks: 5\n\tMipmaps: 0\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.4.13283 Compatibility Profile Context 14.501.1003.0\n\tOpenGlRenderer: AMD Radeon HD 7560D\n\tOpenGlVendor: ATI Technologies Inc.\n\tCpuCount: 4"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/flammpfeil.txt",
    "content": "---- Minecraft Crash Report ----\n// Hey, that tickles! Hehehe!\n\nTime: 1/29/19 10:43 PM\nDescription: Ticking entity\n\njava.lang.NullPointerException: Ticking entity\n\tat mods.flammpfeil.slashblade.entity.EntitySakuraEndManager.func_70071_h_(EntitySakuraEndManager.java:123)\n\tat net.minecraft.world.World.func_72866_a(World.java:2740)\n\tat net.minecraft.world.WorldServer.func_72866_a(WorldServer.java:877)\n\tat net.minecraft.world.World.func_72870_g(World.java:2678)\n\tat net.minecraft.world.World.func_72939_s(World.java:2480)\n\tat net.minecraft.world.WorldServer.func_72939_s(WorldServer.java:673)\n\tat net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:986)\n\tat net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:432)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:841)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:693)\n\tat java.lang.Thread.run(Unknown Source)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat mods.flammpfeil.slashblade.entity.EntitySakuraEndManager.func_70071_h_(EntitySakuraEndManager.java:123)\n\tat net.minecraft.world.World.func_72866_a(World.java:2740)\n\tat net.minecraft.world.WorldServer.func_72866_a(WorldServer.java:877)\n\tat net.minecraft.world.World.func_72870_g(World.java:2678)\n\n-- Entity being ticked --\nDetails:\n\tEntity Type: flammpfeil.slashblade.SakuraEndManager (mods.flammpfeil.slashblade.entity.EntitySakuraEndManager)\n\tEntity ID: 9479014\n\tEntity Name: entity.flammpfeil.slashblade.SakuraEndManager.name\n\tEntity's Exact location: 643.38, 63.81, -79.59\n\tEntity's Block location: World: (643,63,-80), Chunk: (at 3,3,0 in 40,-5; contains blocks 640,0,-80 to 655,255,-65), Region: (1,-1; contains chunks 32,-32 to 63,-1, blocks 512,0,-512 to 1023,255,-1)\n\tEntity's Momentum: 0.00, 0.00, 0.00\nStacktrace:\n\tat net.minecraft.world.World.func_72939_s(World.java:2480)\n\tat net.minecraft.world.WorldServer.func_72939_s(WorldServer.java:673)\n\n-- Affected level --\nDetails:\n\tLevel name: world\n\tAll players: 11 total; [EntityPlayerMP['DBY'/469469, l='world', x=-4118.00, y=65.00, z=3859.00](DBY at -4118.0,65.0,3859.0), EntityPlayerMP['DBY'/781369, l='world', x=-4126.23, y=64.00, z=3823.59](DBY at -4126.227272121851,64.0,3823.5863172392415), EntityPlayerMP['DBY'/781369, l='world', x=-4126.23, y=64.00, z=3823.59](DBY at -4126.227272121851,64.0,3823.5863172392415), EntityPlayerMP['DBY'/781369, l='world', x=-4137.00, y=64.00, z=3806.00](DBY at -4137.0,64.0,3806.0), EntityPlayerMP['FFF_fuvk'/1538930, l='DIM-37', x=-3032.80, y=92.00, z=3253.17](FFF_fuvk at -3032.795149331054,92.0,3253.1699346065175), EntityPlayerMP['Ah_Fish'/9366620, l='world', x=2014.60, y=63.00, z=2104.45](Ah_Fish at 2014.5971571796383,63.0,2104.4533554409213), EntityPlayerMP['Saoooo'/9289124, l='world', x=643.38, y=63.00, z=-79.59](Saoooo at 643.3807204488786,63.0,-79.5876382983504), EntityPlayerMP['lalala'/9286296, l='world', x=645.87, y=63.00, z=-79.24](lalala at 645.8682427602682,63.0,-79.24332023905657), EntityPlayerMP['a_warm_day'/9395133, l='world', x=2010.60, y=72.00, z=2251.29](a_warm_day at 2010.5973072675706,72.0,2251.2870820180015), EntityPlayerMP['FFF_fuvk'/9426805, l='world', x=-4106.50, y=62.00, z=3874.65](FFF_fuvk at -4106.504930738181,62.0,3874.6547187729243), EntityPlayerMP['666'/9453107, l='world', x=2001.60, y=72.00, z=2240.70](666 at 2001.6005663678816,72.0,2240.699999988079)]\n\tChunk stats: ServerChunkCache: 2256 Drop: 0\n\tLevel seed: 8436081996934112249\n\tLevel generator: ID 00 - default, ver 1. Features enabled: true\n\tLevel generator options:\n\tLevel spawn location: World: (637,63,-91), Chunk: (at 13,3,5 in 39,-6; contains blocks 624,0,-96 to 639,255,-81), Region: (1,-1; contains chunks 32,-32 to 63,-1, blocks 512,0,-512 to 1023,255,-1)\n\tLevel time: 15766734 game time, 16267409 day time\n\tLevel dimension: 0\n\tLevel storage version: 0x04ABD - Anvil\n\tLevel weather: Rain time: 27839 (now: false), thunder time: 22680 (now: false)\n\tLevel game mode: Game mode: survival (ID 0). Hardcore: false. Cheats: false\nStacktrace:\n\tat net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:986)\n\tat net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:432)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:841)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:693)\n\tat java.lang.Thread.run(Unknown Source)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tThermos Version: cyberdynecc:Thermos:1.7.10-1614.57\n\tPlugins: CoreProtect, RedstoneClockPreventer, WorldEdit, Essentials, GroupManager, WorldBorder, Lockette, ProtocolLib, AutoSaveWorld, NoSpawnChunks, EssentialsProtect, RandomPlacer, NoExplode, BasicTrails, EssentialsChat, EssentialsAntiBuild, BanItem, iConomy, PTweaks, MOTDColor, Vault, RichAutoMessage, SFWSupport, VIP, EssentialsSpawn, PlotMe, NeverLag, QuickShop, Multiverse-Core, ColoredTags, Residence, AncientGates, ChestShop, AcademyBukkit, AuthMe\n\tDisabled Plugins:\n\tOperating System: Windows 7 (amd64) version 6.1\n\tJava Version: 1.8.0_201, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 2167226872 bytes (2066 MB) / 10615259136 bytes (10123 MB) up to 10615259136 bytes (10123 MB)\n\tJVM Flags: 4 total; -Xms4096M -Xmx10240M -XX:+AggressiveOpts -XX:+UseCompressedOops\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 13, tallocated: 95\n\tFML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1614 47 mods loaded, 47 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJAAAA\tmcp{9.05} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJAAAA\tFML{7.10.99.99} [Forge Mod Loader] (Thermos-1.7.10-1614-57-server.jar)\n\tUCHIJAAAA\tForge{10.13.4.1614} [Minecraft Forge] (Thermos-1.7.10-1614-57-server.jar)\n\tUCHIJAAAA\tkimagine{0.2} [KImagine] (minecraft.jar)\n\tUCHIJAAAA\tLambdaLib|Core{1.2.3} [LambdaLib|Core] (minecraft.jar)\n\tUCHIJAAAA\tThaumicTinkerer-preloader{0.1} [Thaumic Tinkerer Core] (minecraft.jar)\n\tUCHIJAAAA\tappliedenergistics2-core{rv3-beta-10} [Applied Energistics 2 Core] (minecraft.jar)\n\tUCHIJAAAA\tLambdaLib{1.2.3} [LambdaLib] ([AC]超能力前置.jar)\n\tUCHIJAAAA\tacademy-craft{1.0.7} [Academy Craft] ([AC]超能力1.0.7.jar)\n\tUCHIJAAAA\tIC2{2.2.653-experimental} [IndustrialCraft 2] ([ic]工业2.jar)\n\tUCHIJAAAA\tGraviSuite{1.7.10-2.0.3} [Graviation Suite] ([ic]重力装甲.jar)\n\tUCHIJAAAA\tAdvancedSolarPanel{1.7.10-3.5.1} [Advanced Solar Panels] ([ic]高级太阳能.jar)\n\tUCHIJAAAA\tBaubles{1.0.1.10} [Baubles] (Baubles-1.7.10-1.0.1.10.jar)\n\tUCHIJAAAA\tThaumcraft{4.2.3.5} [Thaumcraft] ([TC4]神秘时代.jar)\n\tUCHIJAAAA\tBotania{r1.8-249} [Botania] ([植物魔法].jar)\n\tUCHIJAAAA\tappliedenergistics2{rv3-beta-10} [Applied Energistics 2] (应用能源2.jar)\n\tUCHIJAAAA\tThaumicTinkerer{unspecified} [Thaumic Tinkerer] ([神秘工匠]ThaumicTinkerer-2.5-1.7.10-164.jar)\n\tUCHIJAAAA\tAWWayofTime{v1.3.3} [Blood Magic: Alchemical Wizardry] ([血魔法].jar)\n\tUCHIJAAAA\tForbiddenMagic{1.7.10-0.575} [Forbidden Magic] ([TC4]禁忌魔法.jar)\n\tUCHIJAAAA\twitchery{0.24.1} [Witchery] ([巫术].jar)\n\tUCHIJAAAA\tdakimakuramod{1.7.10-1.2} [Dakimakura Mod] ([抱枕]dakimakuramod-1.7.10-1.2.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade{mc1.7.10-r87} [SlashBlade] ([拔刀剑主版本]SlashBlade-mc1.7.10-r87.jar)\n\tUCHIJAAAA\tPseudo{1.0.0} [Pseudo] ([拔刀剑]伪刃SlashBlade-PseudoEdge-mc1.7.10-r11.jar)\n\tUCHIJAAAA\tflammpfeil.nihil{mc1.7.x-r8} [Nihil] ([拔刀剑]似蛭1.7.x-r8.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.terra{mc1.7.10-r1} [SlashBlade-Terra] ([拔刀剑]大地之刃SlashBlade-Terra-mc1.7.10-r1.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.zephyr{1.7.2 r1.2} [SlashBladeZephyr] ([拔刀剑]风雷太刀r1.2.2.jar)\n\tUCHIJAAAA\tmod_ecru_MapleTree{1.1.33m} [MapleTree] ([新版枫树1.1.33].jar)\n\tUCHIJAAAA\teplus{3.0.2-d} [Enchanting Plus] ([更好的附魔].jar)\n\tUCHIJAAAA\tMSM3{3.0.0d} [More Swords 3] ([武器].jar)\n\tUCHIJAAAA\tTaintedMagic{1.1.6.3} [Tainted Magic] ([污秽魔法].jar)\n\tUCHIJAAAA\tguielevator{1.5} [Elrol's GUI Elevator] ([电梯].jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.kamuy{mc1.7.10-r6} [Kamuy] ([神剑]-Kamuy-r6.jar)\n\tUCHIJAAAA\tDummyCore{2.0.1710.0.A} [DummyCore] ([神秘基础学前置].jar)\n\tUCHIJAAAA\tthaumicbases{1.3.1710.2} [Thaumic Bases] ([神秘基础学].jar)\n\tUCHIJAAAA\tBambooMod{Minecraft1.7.10 var2.6.8.2} [BambooMod] ([竹].jar)\n\tUCHIJAAAA\tlevels{r2.3.0} [Levels] ([等级系统].jar)\n\tUCHIJAAAA\tAutomagy{0.28.2} [Automagy] ([自动化魔法].jar)\n\tUCHIJAAAA\tcustomnpcs{1.7.10d} [CustomNpcs] ([自定义NPC].jar)\n\tUCHIJAAAA\tExtraBotany{r1.0-21} [ExtraBotany] ([额外植物].jar)\n\tUCHIJAAAA\tmagicalcrops{1.7.2 - 0.1 ALPHA} [Magical Crops] ([魔法作物].jar)\n\tUCHIJAAAA\ttransformers{0.6.3} [Transformers Mod] (变形金刚.jar)\n\tUCHIJAAAA\tBiblioCraft{1.11.5} [BiblioCraft] (展示架.jar)\n\tUCHIJAAAA\tarmourersWorkshop{1.7.10-0.48.3} [Armourer's Workshop] (时装工坊0.48.jar)\n\tUCHIJAAAA\tEMT{1.2.2} [Electro-Magic Tools] (电力魔法工具.jar)\n\tUCHIJAAAA\tPTRModelLib{1.0.0} [PTRModelLib] (装饰工艺.jar)\n\tUCHIJAAAA\tprops{2.4.2} [Decocraft] (装饰工艺.jar)\n\tUCHIJAAAA\tFoodCraft{1.2.0} [FoodCraft(FoodCraft)] (食物工艺.jar)\n\tAE2 Version: beta rv3-beta-10 for Forge 10.13.4.1448\n\t[DummyCore]: 'Special case ASM modification mods: ''Note, that this mods might not be involved in the crash in ANY WAY!''DummyCore just prints some known mods for a lot of ASM modifications''DummyCore'\n\tAE2 Integration: IC2:ON, RotaryCraft:OFF, RC:OFF, BuildCraftCore:OFF, BuildCraftTransport:OFF, BuildCraftBuilder:OFF, RF:ON, RFItem:ON, MFR:OFF, DSU:OFF, FZ:OFF, FMP:OFF, RB:OFF, CLApi:OFF, Waila:OFF, Mekanism:OFF, ImmibisMicroblocks:OFF, BetterStorage:OFF, OpenComputers:OFF, PneumaticCraft:OFF\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tPlayer Count: 7 / 50; [EntityPlayerMP['lalala'/9286296, l='world', x=645.87, y=63.00, z=-79.24](lalala at 645.8682427602682,63.0,-79.24332023905657), EntityPlayerMP['Saoooo'/9289124, l='world', x=643.38, y=63.00, z=-79.59](Saoooo at 643.3807204488786,63.0,-79.5876382983504), EntityPlayerMP['Ah_Fish'/9366620, l='world', x=2014.60, y=63.00, z=2104.45](Ah_Fish at 2014.5971571796383,63.0,2104.4533554409213), EntityPlayerMP['a_warm_day'/9395133, l='world', x=2010.60, y=72.00, z=2251.29](a_warm_day at 2010.5973072675706,72.0,2251.2870820180015), EntityPlayerMP['FFF_fuvk'/9426805, l='world', x=-4106.50, y=62.00, z=3874.65](FFF_fuvk at -4106.504930738181,62.0,3874.6547187729243), EntityPlayerMP['666'/9453107, l='world', x=2001.60, y=72.00, z=2240.70](666 at 2001.6005663678816,72.0,2240.699999988079), EntityPlayerMP['Guide'/9465741, l='plotworld', x=-83.40, y=59.00, z=-83.12](Guide at -83.39891777874837,59.0,-83.11642267352487)]\n\tIs Modded: Definitely; Server brand changed to 'thermos,cauldron,craftbukkit,mcpc,kcauldron,fml,forge'\n\tType: Dedicated Server (map_server.txt)"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/ic2.txt",
    "content": "---- Minecraft Crash Report ----\n// But it works on my machine.\n\nTime: 3/22/19 4:31 PM\nDescription: Ticking block entity\n\njava.lang.NullPointerException: Ticking block entity\n\tat ic2.core.util.StackUtil.transfer(StackUtil.java:152)\n\tat ic2.core.item.ItemUpgradeModule.onTick(ItemUpgradeModule.java:425)\n\tat ic2.core.block.machine.tileentity.TileEntityStandardMachine.func_145845_h(TileEntityStandardMachine.java:155)\n\tat ic2.core.block.machine.tileentity.TileEntityOreWashing.func_145845_h(TileEntityOreWashing.java:76)\n\tat net.minecraft.world.World.func_72939_s(World.java:2583)\n\tat net.minecraft.world.WorldServer.func_72939_s(WorldServer.java:673)\n\tat net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:986)\n\tat net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:432)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:841)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:693)\n\tat java.lang.Thread.run(Thread.java:748)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat ic2.core.util.StackUtil.transfer(StackUtil.java:152)\n\tat ic2.core.item.ItemUpgradeModule.onTick(ItemUpgradeModule.java:425)\n\tat ic2.core.block.machine.tileentity.TileEntityStandardMachine.func_145845_h(TileEntityStandardMachine.java:155)\n\tat ic2.core.block.machine.tileentity.TileEntityOreWashing.func_145845_h(TileEntityOreWashing.java:76)\n\n-- Block entity being ticked --\nDetails:\n\tName: Ore Washing Plant // ic2.core.block.machine.tileentity.TileEntityOreWashing\n\tBlock type: ID #648 (blockMachine2 // ic2.core.block.machine.BlockMachine2)\n\tBlock data value: 5 / 0x5 / 0b0101\n\tBlock location: World: (24,59,-172), Chunk: (at 8,3,4 in 1,-11; contains blocks 16,0,-176 to 31,255,-161), Region: (0,-1; contains chunks 0,-32 to 31,-1, blocks 0,0,-512 to 511,255,-1)\n\tActual block type: ID #648 (blockMachine2 // ic2.core.block.machine.BlockMachine2)\n\tActual block data value: 5 / 0x5 / 0b0101\nStacktrace:\n\tat net.minecraft.world.World.func_72939_s(World.java:2583)\n\tat net.minecraft.world.WorldServer.func_72939_s(WorldServer.java:673)\n\n-- Affected level --\nDetails:\n\tLevel name: plotworld\n\tAll players: 3 total; [EntityPlayerMP['long_chalk'/785, l='plotworld', x=-159.52, y=65.00, z=25.54](long_chalk at -159.5206542362536,65.0,25.541882416932502), EntityPlayerMP['YAODYW_D'/1133, l='plotworld', x=138.71, y=72.00, z=-7.45](YAODYW_D at 138.71408242393503,72.0,-7.450665362305571), EntityPlayerMP['a312797261'/953, l='plotworld', x=12.01, y=65.00, z=-176.91](a312797261 at 12.012590248563681,65.0,-176.91436786348493)]\n\tChunk stats: ServerChunkCache: 268 Drop: 0\n\tLevel seed: -8894455803401860845\n\tLevel generator: ID 00 - default, ver 1. Features enabled: true\n\tLevel generator options:\n\tLevel spawn location: World: (3,66,3), Chunk: (at 3,4,3 in 0,0; contains blocks 0,0,0 to 15,255,15), Region: (0,0; contains chunks 0,0 to 31,31, blocks 0,0,0 to 511,255,511)\n\tLevel time: 55397029 game time, 4816859 day time\n\tLevel dimension: 3\n\tLevel storage version: 0x04ABD - Anvil\n\tLevel weather: Rain time: 9301 (now: true), thunder time: 14371 (now: false)\n\tLevel game mode: Game mode: survival (ID 0). Hardcore: false. Cheats: false\nStacktrace:\n\tat net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:986)\n\tat net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:432)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:841)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:693)\n\tat java.lang.Thread.run(Thread.java:748)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tThermos Version: cyberdynecc:Thermos:1.7.10-1614.58\n\tPlugins: CoreProtect, WorldEdit, CheckMaliciousLaodMap, Essentials, Restart, GroupManager, TeleportationTools, ColorSigns, FixArcaneWorkbench, GeneralDataCoreV3.1, LaggRemover, ProtocolLib, AutoSaveWorld, EssentialsProtect, DAutoMessage, EssentialsChat, EssentialsAntiBuild, BanItem, MOTDColor, Vault, EssentialsSpawn, ScriptBlock, FakePlayersOnline, FastLogin, PlotMe, NeverLag, LWC, QuickShop, Residence, PlotMe-DefaultGenerator, RPG_Items, Multiverse-Core\n\tDisabled Plugins:\n\tOperating System: Linux (amd64) version 4.4.110-1.el6.elrepo.x86_64\n\tJava Version: 1.8.0_192, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 115150240 bytes (109 MB) / 734003200 bytes (700 MB) up to 734003200 bytes (700 MB)\n\tJVM Flags: 6 total; -XX:+UseFastAccessorMethods -XX:+UseParallelGC -XX:ParallelGCThreads=2 -XX:MaxDirectMemorySize=99M -XX:MaxMetaspaceSize=170M -Xmx755M\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 13, tallocated: 95\n\tFML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1614 36 mods loaded, 36 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJAAAA\tmcp{9.05} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJAAAA\tFML{7.10.99.99} [Forge Mod Loader] (Thermos-1.7.10-1614.jar)\n\tUCHIJAAAA\tForge{10.13.4.1614} [Minecraft Forge] (Thermos-1.7.10-1614.jar)\n\tUCHIJAAAA\tkimagine{0.2} [KImagine] (minecraft.jar)\n\tUCHIJAAAA\tappliedenergistics2-core{rv2-stable-10} [AppliedEnergistics2 Core] (minecraft.jar)\n\tUCHIJAAAA\tuniskinmod{1.2-dev4} [Universal Skin Mod] (minecraft.jar)\n\tUCHIJAAAA\tThaumicTinkerer-preloader{0.1} [Thaumic Tinkerer Core] (minecraft.jar)\n\tUCHIJAAAA\t<CoFH ASM>{000} [CoFH ASM] (minecraft.jar)\n\tUCHIJAAAA\tCoFHCore{1.7.10R3.1.4} [CoFH Core] (2.jar)\n\tUCHIJAAAA\tBaubles{1.0.1.10} [Baubles] (Baubles-1.7.10-1.0.1.10.jar)\n\tUCHIJAAAA\tThermalFoundation{1.7.10R1.2.6} [Thermal Foundation] (1.jar)\n\tUCHIJAAAA\tThermalDynamics{1.7.10R1.2.1} [Thermal Dynamics] (3.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade{mc1.7.10-r87} [SlashBlade] (【拔刀剑1710】R87.jar)\n\tUCHIJAAAA\tYamazakura{1.0.0} [Yamazakura] ([山樱]SlashBlade-Yamazakura-mc1.7-r4.jar)\n\tUCHIJAAAA\tThaumcraft{4.2.3.5} [Thaumcraft] (神秘时代4.jar)\n\tUCHIJAAAA\tBotania{r1.8-249} [Botania] ([植物魔法]Botania r1.8-249.jar)\n\tUCHIJAAAA\tIC2{2.2.756-experimental} [IndustrialCraft 2] (工业2.jar)\n\tUCHIJAAAA\tappliedenergistics2{rv2-stable-10} [Applied Energistics 2] (AE.jar)\n\tUCHIJAAAA\tThaumicTinkerer{unspecified} [Thaumic Tinkerer] (神秘工匠.jar)\n\tUCHIJAAAA\tAWWayofTime{v1.3.3} [Blood Magic: Alchemical Wizardry] (血魔法 v1.3.3-17 (1.7.10).jar)\n\tUCHIJAAAA\tForbiddenMagic{1.7.10-0.574} [Forbidden Magic] ([神秘附属-禁忌魔法]Forbidden Magic-1.7.10-0.574.jar)\n\tUCHIJAAAA\tExtraBotany{r1.0-21} [ExtraBotany] ([额外植物学]ExtraBotany-1.7.10-r1.0-21.jar)\n\tUCHIJAAAA\tcustomnpcs{1.7.10d} [CustomNpcs] (NPC.jar)\n\tUCHIJAAAA\tMantle{1.7.10-0.3.2.jenkins191} [Mantle] (匠魂前置.jar)\n\tUCHIJAAAA\tImmersiveEngineering{0.7.7} [Immersive Engineering] (沉浸.jar)\n\tUCHIJAAAA\tThermalExpansion{1.7.10R4.1.5} [Thermal Expansion] (热力膨胀.jar)\n\tUCHIJAAAA\tTConstruct{1.7.10-1.8.8.build988} [Tinkers' Construct] (匠魂.jar)\n\tUCHIJAAAA\tBambooMod{Minecraft@MC_VERSION@ var@VERSION@} [BambooMod] (和风.jar)\n\tUCHIJAAAA\tflammpfeil.nihil{mc1.7.x-r6} [Nihil] (拔刀剑 - [似蛭].jar)\n\tUCHIJAAAA\tbalumg{1.0.0} [balumg] (拔刀剑 - [巴鲁蒙格].jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.exsa{mc1.7.10-r6} [SlashBlade-ExSa] (拔刀剑 - [更多Sa].jar)\n\tUCHIJAAAA\tIronChest{6.0.62.742} [Iron Chest] (更多箱子.jar)\n\tUCHIJAAAA\tTaintedMagic{r7.6} [Tainted Magic] (污秽魔法.jar)\n\tUCHIJAAAA\tGraviSuite{1.7.10-2.0.3} [Graviation Suite] (重力.jar)\n\tUCHIJAAAA\tAdvancedSolarPanel{1.7.10-3.5.1} [Advanced Solar Panels] (高级太阳能.jar)\n\tUCHIJAAAA\tIguanaTweaksTConstruct{1.7.10-2.1.6.163} [Iguana Tinker Tweaks] (%5B匠魂拓展%5DIguanaTinkerTweaks-1.7.10-2.1.6.jar)\n\tCoFHCore: -[1.7.10]3.1.4-329\n\tThermalFoundation: -[1.7.10]1.2.6-118\n\tThermalDynamics: -[1.7.10]1.2.1-172\n\tAE2 Version: stable rv2-stable-10 for Forge 10.13.2.1291\n\tMantle Environment: DO NOT REPORT THIS CRASH! Unsupported mods in environment: bukkit\n\tThermalExpansion: -[1.7.10]4.1.5-248\n\tTConstruct Environment: Environment healthy.\n\tAE2 Integration: IC2:ON, RotaryCraft:OFF, RC:OFF, BC:OFF, RF:ON, RFItem:ON, MFR:OFF, DSU:ON, FZ:OFF, FMP:OFF, RB:OFF, CLApi:OFF, Waila:OFF, Mekanism:OFF, ImmibisMicroblocks:OFF, BetterStorage:OFF\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tPlayer Count: 5 / 40; [EntityPlayerMP['long_chalk'/785, l='plotworld', x=-159.52, y=65.00, z=25.54](long_chalk at -159.5206542362536,65.0,25.541882416932502), EntityPlayerMP['a312797261'/953, l='plotworld', x=12.01, y=65.00, z=-176.91](a312797261 at 12.012590248563681,65.0,-176.91436786348493), EntityPlayerMP['zzn_T'/752, l='DIM-1', x=2108.46, y=57.00, z=-97.03](zzn_T at 2108.4571112024496,57.0,-97.02775279909504), EntityPlayerMP['YAODYW_D'/1133, l='plotworld', x=138.71, y=72.00, z=-7.45](YAODYW_D at 138.71408242393503,72.0,-7.450665362305571), EntityPlayerMP['Toccle'/5646, l='world', x=-206.69, y=48.00, z=425.30](Toccle at -206.6917225142045,48.0,425.30000001192093)]\n\tIs Modded: Definitely; Server brand changed to 'thermos,cauldron,craftbukkit,mcpc,kcauldron,fml,forge'\n\tType: Dedicated Server (map_server.txt)"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/icycream.txt",
    "content": "---- Minecraft Crash Report ----\n// Why did you do that?\n\nTime: 8/17/20 8:14 PM\nDescription: Ticking player\n\njava.lang.NullPointerException: Ticking player\n\tat icycream.common.item.ItemIceCream.getEffectFromNBT(ItemIceCream.java:43) ~[?:1.0] {re:classloading}\n\tat icycream.common.item.ItemIceCream.func_77654_b(ItemIceCream.java:107) ~[?:1.0] {re:classloading}\n\tat net.minecraft.item.ItemStack.func_77950_b(ItemStack.java:201) ~[?:?] {re:classloading}\n\tat net.minecraft.entity.LivingEntity.func_71036_o(LivingEntity.java:2656) ~[?:?] {re:classloading}\n\tat net.minecraft.entity.player.ServerPlayerEntity.func_71036_o(ServerPlayerEntity.java:954) ~[?:?] {re:classloading,pl:accesstransformer:B}\n\tat net.minecraft.entity.LivingEntity.func_184608_ct(LivingEntity.java:2537) ~[?:?] {re:classloading}\n\tat net.minecraft.entity.LivingEntity.func_70071_h_(LivingEntity.java:2025) ~[?:?] {re:classloading}\n\tat net.minecraft.entity.player.PlayerEntity.func_70071_h_(PlayerEntity.java:233) ~[?:?] {re:classloading,pl:accesstransformer:B,xf:fml:nickname:get_display_name}\n\tat net.minecraft.entity.player.ServerPlayerEntity.func_71127_g(ServerPlayerEntity.java:361) ~[?:?] {re:classloading,pl:accesstransformer:B}\n\tat net.minecraft.network.play.ServerPlayNetHandler.func_73660_a(ServerPlayNetHandler.java:183) ~[?:?] {re:classloading}\n\tat net.minecraft.network.NetworkManager.func_74428_b(NetworkManager.java:228) ~[?:?] {re:classloading}\n\tat net.minecraft.network.NetworkSystem.func_151269_c(NetworkSystem.java:135) ~[?:?] {re:classloading}\n\tat net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:866) ~[?:?] {re:classloading,pl:accesstransformer:B}\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:784) ~[?:?] {re:classloading,pl:accesstransformer:B}\n\tat net.minecraft.server.integrated.IntegratedServer.func_71217_p(IntegratedServer.java:114) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:637) [?:?] {re:classloading,pl:accesstransformer:B}\n\tat java.lang.Thread.run(Thread.java:748) [?:1.8.0_261] {}\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Server thread\nStacktrace:\n\tat icycream.common.item.ItemIceCream.getEffectFromNBT(ItemIceCream.java:43)\n\tat icycream.common.item.ItemIceCream.func_77654_b(ItemIceCream.java:107)\n\tat net.minecraft.item.ItemStack.func_77950_b(ItemStack.java:201)\n\tat net.minecraft.entity.LivingEntity.func_71036_o(LivingEntity.java:2656)\n\tat net.minecraft.entity.player.ServerPlayerEntity.func_71036_o(ServerPlayerEntity.java:954)\n\tat net.minecraft.entity.LivingEntity.func_184608_ct(LivingEntity.java:2537)\n\tat net.minecraft.entity.LivingEntity.func_70071_h_(LivingEntity.java:2025)\n\tat net.minecraft.entity.player.PlayerEntity.func_70071_h_(PlayerEntity.java:233)\n\n-- Player being ticked --\nDetails:\n\tEntity Type: minecraft:player (net.minecraft.entity.player.ServerPlayerEntity)\n\tEntity ID: 209\n\tEntity Name: ZekerZhayard\n\tEntity's Exact location: -44.56, 77.16, -39.57\n\tEntity's Block location: World: (-45,77,-40), Chunk: (at 3,4,8 in -3,-3; contains blocks -48,0,-48 to -33,255,-33), Region: (-1,-1; contains chunks -32,-32 to -1,-1, blocks -512,0,-512 to -1,255,-1)\n\tEntity's Momentum: 0.00, 0.00, 0.00\n\tEntity's Passengers: []\n\tEntity's Vehicle: ~~ERROR~~ NullPointerException: null\nStacktrace:\n\tat net.minecraft.entity.player.ServerPlayerEntity.func_71127_g(ServerPlayerEntity.java:361)\n\tat net.minecraft.network.play.ServerPlayNetHandler.func_73660_a(ServerPlayNetHandler.java:183)\n\tat net.minecraft.network.NetworkManager.func_74428_b(NetworkManager.java:228)\n\n-- Ticking connection --\nDetails:\n\tConnection: net.minecraft.network.NetworkManager@2525d68a\nStacktrace:\n\tat net.minecraft.network.NetworkSystem.func_151269_c(NetworkSystem.java:135)\n\tat net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:866)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:784)\n\tat net.minecraft.server.integrated.IntegratedServer.func_71217_p(IntegratedServer.java:114)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:637)\n\tat java.lang.Thread.run(Thread.java:748)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.15.2\n\tMinecraft Version ID: 1.15.2\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_261, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 2333111144 bytes (2225 MB) / 4294967296 bytes (4096 MB) up to 4294967296 bytes (4096 MB)\n\tCPUs: 4\n\tJVM Flags: 11 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xms512m -Xmx4096m\n\tForge Mods:\n\t\t| ID                     | Name                                             | Version              | Source                                        | Status |\n\t\t| ---------------------- | ------------------------------------------------ | -------------------- | --------------------------------------------- | ------ |\n\t\t| minecraft              | Minecraft                                        | 1.15.2               | forge-1.15.2-31.2.0-client.jar                | DONE   |\n\t\t| unexpectedteleport     | Unexpected Teleport                              | NONE                 | unexpectedteleport-1.0.0.jar                  | DONE   |\n\t\t| simple_permission      | Simple Permission                                | 0.1.1                | SimplePermission-Forge-1.15-0.1.1.jar         | DONE   |\n\t\t| icycream               | Icy Cream                                        | 1.0                  | icycream-1.0.jar                              | DONE   |\n\t\t| vida                   | Vida                                             | 0.0.1                | vida-1.0.jar                                  | DONE   |\n\t\t| instantbubblestudio    | Instant Bubble Studio                            | 1.0.0                | instantbubblestudio-1.0.0.jar                 | DONE   |\n\t\t| customskinloader       | CustomSkinLoader                                 | 14.13-SNAPSHOT-170   | CustomSkinLoader_Forge-14.13-SNAPSHOT-170.jar | DONE   |\n\t\t| hamburger              | Hamburger Of LaoBa                               | 1.0                  | hamburger-1.0.jar                             | DONE   |\n\t\t| jei                    | Just Enough Items                                | 6.0.2.12             | jei-1.15.2-6.0.2.12.jar                       | DONE   |\n\t\t| tea                    | Example Mod                                      | 1.0                  | potato-like-mod-forge-1.0.0.jar               | DONE   |\n\t\t| rpgmaths               | RPG Maths                                        | 1.0.0-snapshot       | rpgmaths-1.0.0-snapshot.jar                   | DONE   |\n\t\t| heavenearthring        | Heaven Earth Ring                                | Bata 0.1.3           | heavenearthring-Bata 0.1.3.jar                | DONE   |\n\t\t| vacation_diary         | 度假日记                                             | 1.0                  | vacation_diary-1.0.jar                        | DONE   |\n\t\t| chineseworkshop        | ChineseWorkshop                                  | 2.4.5                | ChineseWorkshop-1.15.2-2.4.5.jar              | DONE   |\n\t\t| nickname               | Nickname                                         | 0.2.5                | Nickname-Forge-1.15-0.2.5.jar                 | DONE   |\n\t\t| curios                 | Curios API                                       | FORGE-1.15.2-2.0.2.4 | curios-FORGE-1.15.2-2.0.2.4.jar               | DONE   |\n\t\t| patchouli              | Patchouli                                        | 1.15.2-1.2-35        | Patchouli-1.15.2-1.2-35.jar                   | DONE   |\n\t\t| waystones              | Waystones                                        | 6.0.2                | Waystones_1.15.2-6.0.2.jar                    | DONE   |\n\t\t| hnkinvention           | Hoshtimber&Kadokawa Wonderful Invention          | 1.15.2-Beta-1.0.0    | hnkinvention-1.15.2-Beta-1.0.0.jar            | DONE   |\n\t\t| arcaneart              | Arcane Art                                       | 0.0.1                | arcaneart-0.0.1.jar                           | DONE   |\n\t\t| storagedrawers         | Storage Drawers                                  | 1.15.2-7.0.1         | StorageDrawers-1.15.2-7.0.2.jar               | DONE   |\n\t\t| morecrashinfo          | MoreCrashInfo                                    | 1.0.4                | MoreCrashInfo-1.0.4.jar                       | DONE   |\n\t\t| suuuuuuuper_herbal_tea | Suuuuuuuper herbal tea                           | 1.15.2_Alpha_V1      | Suuuuuuuper_Herbal_Tea-1.15.2_Alpha_V1.jar    | DONE   |\n\t\t| abouttea               | AboutTea                                         | 1.0                  | AboutTea-1.0.jar                              | DONE   |\n\t\t| throwable              | Throwable                                        | 1.0                  | throwable-1.0-SNAPSHOT.jar                    | DONE   |\n\t\t| kaleido                | 看过来！关于在酷暑难耐的炎炎夏日，一边品味下午茶，一边惬意地装饰自己的空间，这样的模组你喜欢吗？ | 1.0.1                | Kaleido-1.15.2-1.0.1.jar                      | DONE   |\n\t\t| kiwi                   | Kiwi                                             | 2.8.3                | Kiwi-1.15.2-2.8.3.jar                         | DONE   |\n\t\t| crockpot               | Crock Pot                                        | 0.1.0                | CrockPot-1.15.2-0.1.0.jar                     | DONE   |\n\t\t| dprm                   | Datapack Recipe Maker                            | 1.2                  | DatapackRecipeMaker-1.15.2-1.2.jar            | DONE   |\n\t\t| watersprayer           | Water Sprayer                                    | 1.0.0                | WaterSprayer-1.0.0.jar                        | DONE   |\n\t\t| honkaiimpact           | HonkaiImpact3                                    | 1.0                  | HonkaiImpact-1.0.jar                          | DONE   |\n\t\t| tea_sorcerer           | Tea Sorcerer                                     | 1.0                  | tea_sorcerer-1.0.jar                          | DONE   |\n\t\t| bananacraft            | Banana Craft                                     | 1.0                  | bananacraft-1.0.jar                           | DONE   |\n\t\t| afterthedrizzle        | After the Drizzle                                | 0.2.21-TC-1.15.2     | AfterTheDrizzle-0.2.21-TC-1.15.2.jar          | DONE   |\n\t\t| sinocraft              | Sino Craft                                       | 1.15.2-1.0.0         | sinocraft-1.15.2-1.0.0.jar                    | DONE   |\n\t\t| crafttweaker           | CraftTweaker                                     | 6.0.0.24             | CraftTweaker-1.15.2-6.0.0.24.jar              | DONE   |\n\t\t| chromeball             | Chrome Ball                                      | 0.2.2                | ChromeBall-Forge-1.15-0.2.2.jar               | DONE   |\n\t\t| forge                  | Forge                                            | 31.2.0               | forge-1.15.2-31.2.0-universal.jar             | DONE   |\n\t\t| slide_show             | Slide Show                                       | 0.1.0                | SlideShow-Forge-1.15-0.1.0.jar                | DONE   |\n\t\t| examplemod             | Example Mod                                      | 1.0                  | Out-s-teacon--1.0.jar                         | DONE   |\n\tForge CoreMods:\n\t\t| ID               | Name                      | Source                       | Status |\n\t\t| ---------------- | ------------------------- | ---------------------------- | ------ |\n\t\t| customskinloader | transformers              | transformers.js              | Loaded |\n\t\t| nickname         | nickname_hooks            | hooks.js                     | Loaded |\n\t\t| patchouli        | patchouli_on_advancement  | on_advancement.js            | Loaded |\n\t\t| morecrashinfo    | crashtransformers         | crashtransformers.js         | Loaded |\n\t\t| afterthedrizzle  | atd_celestial_angle       | celestial-angle.js           | Loaded |\n\t\t| forge            | fieldtomethodtransformers | fieldtomethodtransformers.js | Loaded |\n\tModLauncher: 5.1.0+69+master.79f13f7\n\tModLauncher launch target: fmlclient\n\tModLauncher naming: srg\n\tModLauncher services:\n\t\t/eventbus-2.2.0-service.jar eventbus PLUGINSERVICE\n\t\t/forge-1.15.2-31.2.0-launcher.jar object_holder_definalize PLUGINSERVICE\n\t\t/forge-1.15.2-31.2.0-launcher.jar runtime_enum_extender PLUGINSERVICE\n\t\t/accesstransformers-2.1.1-shadowed.jar accesstransformer PLUGINSERVICE\n\t\t/forge-1.15.2-31.2.0-launcher.jar capability_inject_definalize PLUGINSERVICE\n\t\t/forge-1.15.2-31.2.0-launcher.jar runtimedistcleaner PLUGINSERVICE\n\t\t/forge-1.15.2-31.2.0-launcher.jar fml TRANSFORMATIONSERVICE\n\tFML: 31.2\n\tForge: net.minecraftforge:31.2.0\n\tFML Language Providers:\n\t\tjavafml@31.2\n\t\tminecraft@1\n\t\tkotlinforforge@1.3.1\n\t\tscorge@3.0.6\n\tMod List:\n\t\tforge-1.15.2-31.2.0-client.jar Minecraft {minecraft@1.15.2 DONE}\n\t\tunexpectedteleport-1.0.0.jar Unexpected Teleport {unexpectedteleport@NONE DONE}\n\t\tSimplePermission-Forge-1.15-0.1.1.jar Simple Permission {simple_permission@0.1.1 DONE}\n\t\ticycream-1.0.jar Icy Cream {icycream@1.0 DONE}\n\t\tvida-1.0.jar Vida {vida@0.0.1 DONE}\n\t\tinstantbubblestudio-1.0.0.jar Instant Bubble Studio {instantbubblestudio@1.0.0 DONE}\n\t\tCustomSkinLoader_Forge-14.13-SNAPSHOT-170.jar CustomSkinLoader {customskinloader@14.13-SNAPSHOT-170 DONE}\n\t\thamburger-1.0.jar Hamburger Of LaoBa {hamburger@1.0 DONE}\n\t\tjei-1.15.2-6.0.2.12.jar Just Enough Items {jei@6.0.2.12 DONE}\n\t\tpotato-like-mod-forge-1.0.0.jar Example Mod {tea@1.0 DONE}\n\t\trpgmaths-1.0.0-snapshot.jar RPG Maths {rpgmaths@1.0.0-snapshot DONE}\n\t\theavenearthring-Bata 0.1.3.jar Heaven Earth Ring {heavenearthring@Bata 0.1.3 DONE}\n\t\tvacation_diary-1.0.jar 度假日记 {vacation_diary@1.0 DONE}\n\t\tChineseWorkshop-1.15.2-2.4.5.jar ChineseWorkshop {chineseworkshop@2.4.5 DONE}\n\t\tNickname-Forge-1.15-0.2.5.jar Nickname {nickname@0.2.5 DONE}\n\t\tcurios-FORGE-1.15.2-2.0.2.4.jar Curios API {curios@FORGE-1.15.2-2.0.2.4 DONE}\n\t\tPatchouli-1.15.2-1.2-35.jar Patchouli {patchouli@1.15.2-1.2-35 DONE}\n\t\tWaystones_1.15.2-6.0.2.jar Waystones {waystones@6.0.2 DONE}\n\t\thnkinvention-1.15.2-Beta-1.0.0.jar Hoshtimber&Kadokawa Wonderful Invention {hnkinvention@1.15.2-Beta-1.0.0 DONE}\n\t\tarcaneart-0.0.1.jar Arcane Art {arcaneart@0.0.1 DONE}\n\t\tStorageDrawers-1.15.2-7.0.2.jar Storage Drawers {storagedrawers@1.15.2-7.0.1 DONE}\n\t\tMoreCrashInfo-1.0.4.jar MoreCrashInfo {morecrashinfo@1.0.4 DONE}\n\t\tSuuuuuuuper_Herbal_Tea-1.15.2_Alpha_V1.jar Suuuuuuuper herbal tea {suuuuuuuper_herbal_tea@1.15.2_Alpha_V1 DONE}\n\t\tAboutTea-1.0.jar AboutTea {abouttea@1.0 DONE}\n\t\tthrowable-1.0-SNAPSHOT.jar Throwable {throwable@1.0 DONE}\n\t\tKaleido-1.15.2-1.0.1.jar 看过来！关于在酷暑难耐的炎炎夏日，一边品味下午茶，一边惬意地装饰自己的空间，这样的模组你喜欢吗？ {kaleido@1.0.1 DONE}\n\t\tKiwi-1.15.2-2.8.3.jar Kiwi {kiwi@2.8.3 DONE}\n\t\tCrockPot-1.15.2-0.1.0.jar Crock Pot {crockpot@0.1.0 DONE}\n\t\tDatapackRecipeMaker-1.15.2-1.2.jar Datapack Recipe Maker {dprm@1.2 DONE}\n\t\tWaterSprayer-1.0.0.jar Water Sprayer {watersprayer@1.0.0 DONE}\n\t\tHonkaiImpact-1.0.jar HonkaiImpact3 {honkaiimpact@1.0 DONE}\n\t\ttea_sorcerer-1.0.jar Tea Sorcerer {tea_sorcerer@1.0 DONE}\n\t\tbananacraft-1.0.jar Banana Craft {bananacraft@1.0 DONE}\n\t\tAfterTheDrizzle-0.2.21-TC-1.15.2.jar After the Drizzle {afterthedrizzle@0.2.21-TC-1.15.2 DONE}\n\t\tsinocraft-1.15.2-1.0.0.jar Sino Craft {sinocraft@1.15.2-1.0.0 DONE}\n\t\tCraftTweaker-1.15.2-6.0.0.24.jar CraftTweaker {crafttweaker@6.0.0.24 DONE}\n\t\tChromeBall-Forge-1.15-0.2.2.jar Chrome Ball {chromeball@0.2.2 DONE}\n\t\tforge-1.15.2-31.2.0-universal.jar Forge {forge@31.2.0 DONE}\n\t\tSlideShow-Forge-1.15-0.1.0.jar Slide Show {slide_show@0.1.0 DONE}\n\t\tOut-s-teacon--1.0.jar Example Mod {examplemod@1.0 DONE}\n\tKiwi Modules:\n\t\tchineseworkshop:blocks\n\t\tchineseworkshop:debug_stick\n\t\tchineseworkshop:decorations\n\t\tchineseworkshop:materials\n\t\tchineseworkshop:retexture\n\t\tkaleido:carpentry\n\t\tkaleido:kaleido\n\t\tkiwi:contributors\n\t\tkiwi:data\n\tPatchouli open book context: n/a\n\tPlayer Count: 1 / 8; [ServerPlayerEntity['ZekerZhayard'/209, l='Test', x=-44.56, y=77.16, z=-39.57]]\n\tData Packs: mod:customskinloader (incompatible), mod:morecrashinfo (incompatible), vanilla, mod:unexpectedteleport, mod:simple_permission, mod:icycream, mod:vida, mod:instantbubblestudio, mod:hamburger, mod:jei (incompatible), mod:tea, mod:rpgmaths, mod:heavenearthring, mod:vacation_diary, mod:chineseworkshop, mod:nickname, mod:curios (incompatible), mod:patchouli (incompatible), mod:waystones (incompatible), mod:hnkinvention, mod:arcaneart, mod:storagedrawers (incompatible), mod:suuuuuuuper_herbal_tea, mod:abouttea, mod:throwable (incompatible), mod:kaleido, mod:kiwi, mod:crockpot, mod:dprm, mod:watersprayer, mod:honkaiimpact, mod:tea_sorcerer, mod:bananacraft, mod:afterthedrizzle (incompatible), mod:sinocraft, mod:crafttweaker (incompatible), mod:chromeball, mod:forge (incompatible), mod:slide_show, mod:examplemod\n\tType: Integrated Server (map_client.txt)\n\tIs Modded: Definitely; Client brand changed to 'forge'"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/mapletree.txt",
    "content": "---- Minecraft Crash Report ----\n// Oh - I know what I did wrong!\n\nTime: 19-1-22 下午11:44\nDescription: Exception getting block type in world\n\njava.lang.ArrayIndexOutOfBoundsException: 2\n\tat ecru.MapleTree.gen.ecru_WorldGenBigMapleTree.generateLeaves(ecru_WorldGenBigMapleTree.java:359)\n\tat ecru.MapleTree.gen.ecru_WorldGenBigMapleTree.generate(ecru_WorldGenBigMapleTree.java:657)\n\tat ecru.MapleTree.gen.ecru_WorldGenerate.generate(ecru_WorldGenerate.java:239)\n\tat cpw.mods.fml.common.registry.GameRegistry.generateWorld(GameRegistry.java:137)\n\tat net.minecraft.world.gen.ChunkProviderServer.func_73153_a(ChunkProviderServer.java:447)\n\tat net.minecraft.world.chunk.Chunk.func_76624_a(Chunk.java:1188)\n\tat net.minecraft.world.gen.ChunkProviderServer.originalLoadChunk(ChunkProviderServer.java:300)\n\tat net.minecraft.world.gen.ChunkProviderServer.loadChunk(ChunkProviderServer.java:205)\n\tat net.minecraft.world.gen.ChunkProviderServer.func_73158_c(ChunkProviderServer.java:167)\n\tat net.minecraft.world.gen.ChunkProviderServer.func_73154_d(ChunkProviderServer.java:312)\n\tat net.minecraft.world.World.func_72964_e(World.java:735)\n\tat net.minecraft.world.World.func_147439_a(World.java:661)\n\tat ruby.bamboo.worldgen.WorldGenBamboo.func_76484_a(WorldGenBamboo.java:22)\n\tat ruby.bamboo.worldgen.GeneraterHandler.generateBambooshoot(GeneraterHandler.java:78)\n\tat ruby.bamboo.worldgen.GeneraterHandler.generate(GeneraterHandler.java:37)\n\tat cpw.mods.fml.common.registry.GameRegistry.generateWorld(GameRegistry.java:137)\n\tat net.minecraft.world.gen.ChunkProviderServer.func_73153_a(ChunkProviderServer.java:447)\n\tat net.minecraft.world.chunk.Chunk.func_76624_a(Chunk.java:1188)\n\tat net.minecraft.world.gen.ChunkProviderServer.originalLoadChunk(ChunkProviderServer.java:300)\n\tat cc.uraniummc.ChunkGenerator.internalGenerate(ChunkGenerator.java:71)\n\tat cc.uraniummc.ChunkGenerator.chunkGeneratorCycle(ChunkGenerator.java:50)\n\tat cpw.mods.fml.common.FMLCommonHandler.onPostServerTick(FMLCommonHandler.java:252)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:859)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:665)\n\tat java.lang.Thread.run(Unknown Source)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat ecru.MapleTree.gen.ecru_WorldGenBigMapleTree.generateLeaves(ecru_WorldGenBigMapleTree.java:359)\n\tat ecru.MapleTree.gen.ecru_WorldGenBigMapleTree.generate(ecru_WorldGenBigMapleTree.java:657)\n\tat ecru.MapleTree.gen.ecru_WorldGenerate.generate(ecru_WorldGenerate.java:239)\n\tat cpw.mods.fml.common.registry.GameRegistry.generateWorld(GameRegistry.java:137)\n\tat net.minecraft.world.gen.ChunkProviderServer.func_73153_a(ChunkProviderServer.java:447)\n\tat net.minecraft.world.chunk.Chunk.func_76624_a(Chunk.java:1188)\n\tat net.minecraft.world.gen.ChunkProviderServer.originalLoadChunk(ChunkProviderServer.java:300)\n\tat net.minecraft.world.gen.ChunkProviderServer.loadChunk(ChunkProviderServer.java:205)\n\tat net.minecraft.world.gen.ChunkProviderServer.func_73158_c(ChunkProviderServer.java:167)\n\tat net.minecraft.world.gen.ChunkProviderServer.func_73154_d(ChunkProviderServer.java:312)\n\tat net.minecraft.world.World.func_72964_e(World.java:735)\n\n-- Requested block coordinates --\nDetails:\n\tFound chunk: true\n\tLocation: World: (-241,96,-13169), Chunk: (at 15,6,15 in -16,-824; contains blocks -256,0,-13184 to -241,255,-13169), Region: (-1,-26; contains chunks -32,-832 to -1,-801, blocks -512,0,-13312 to -1,255,-12801)\nStacktrace:\n\tat net.minecraft.world.World.func_147439_a(World.java:661)\n\tat ruby.bamboo.worldgen.WorldGenBamboo.func_76484_a(WorldGenBamboo.java:22)\n\tat ruby.bamboo.worldgen.GeneraterHandler.generateBambooshoot(GeneraterHandler.java:78)\n\tat ruby.bamboo.worldgen.GeneraterHandler.generate(GeneraterHandler.java:37)\n\tat cpw.mods.fml.common.registry.GameRegistry.generateWorld(GameRegistry.java:137)\n\tat net.minecraft.world.gen.ChunkProviderServer.func_73153_a(ChunkProviderServer.java:447)\n\tat net.minecraft.world.chunk.Chunk.func_76624_a(Chunk.java:1188)\n\tat net.minecraft.world.gen.ChunkProviderServer.originalLoadChunk(ChunkProviderServer.java:300)\n\tat cc.uraniummc.ChunkGenerator.internalGenerate(ChunkGenerator.java:71)\n\tat cc.uraniummc.ChunkGenerator.chunkGeneratorCycle(ChunkGenerator.java:50)\n\tat cpw.mods.fml.common.FMLCommonHandler.onPostServerTick(FMLCommonHandler.java:252)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:859)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:665)\n\tat java.lang.Thread.run(Unknown Source)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tKCauldron Version: cc.uraniummc:Uranium:1710-dev-4-BUnkonwn UNOFFICIAL DON'T REPORT THIS CRASH\n\tPlugins: SlashFortifier, GroupManager, BetterLottery2, BanWorld, AsyncKeepAlive, TpLogin, DeluxeTags, ItemTime, AutoRestart, Yum, BanItem, CommandsItem, BBSToper, ProtectItemFrame, CoreProtect, WorldEdit, ColorChat, NOWorldCommand, NoRain, OpenInv, Essentials, TimingsPatcher, ColorSigns, WorldBorder, MessageAnnouncer, IronElevators, ProtocolLib, AntiDrop, Notbuild, EasyKits, GaiaLimit, EssentialsChat, DAutoMessage, LevelChat, CommandCode, UltimateGuild, Vault, SFWSupport, bugfix, Lores, NeverLag, AntiSpamBot, Trading, LWC, OnlineMoney, PlotSquared, QuickShop, PlayerPoints, ChestCommands, EssentialsProtect, EssentialsAntiBuild, RandomLocation, EssentialsSpawn, MoreSounds, MCLib, ColorMOTD, Multiverse-Core, InfoBoardReborn, LWCAutoLock, Residence, GlobalMarket, AuthMe, HolographicDisplays\n\tDisabled Plugins:\n\tOperating System: Windows NT (unknown) (amd64) version 10.0\n\tJava Version: 1.8.0_102, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 5172277384 bytes (4932 MB) / 12771524608 bytes (12179 MB) up to 17064394752 bytes (16273 MB)\n\tJVM Flags: 5 total; -Xincgc -Xms12G -Xms12G -XX:+AggressiveOpts -XX:+UseCompressedOops\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 15, tallocated: 95\n\tFML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1614 91 mods loaded, 91 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJAAAA\tmcp{9.05} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJAAAA\tFML{7.10.99.99} [Forge Mod Loader] (Uranium-1.7.10服务端核心[修复GuiCloseEvent].jar)\n\tUCHIJAAAA\tForge{10.13.4.1614} [Minecraft Forge] (Uranium-1.7.10服务端核心[修复GuiCloseEvent].jar)\n\tUCHIJAAAA\tkimagine{0.2} [KImagine] (minecraft.jar)\n\tUCHIJAAAA\tUraniumPlus{${UMP_VER}} [Added title and actionbar support for client and server] (Uranium-1.7.10服务端核心[修复GuiCloseEvent].jar)\n\tUCHIJAAAA\tThE-core{1.0.0.1} [Thaumic Energistics Core] (minecraft.jar)\n\tUCHIJAAAA\tThaumicTinkerer-preloader{0.1} [Thaumic Tinkerer Core] (minecraft.jar)\n\tUCHIJAAAA\tappliedenergistics2-core{rv3-beta-16} [Applied Energistics 2 Core] (minecraft.jar)\n\tUCHIJAAAA\tChocoPatcher{1.2} [Choco Patcher] (minecraft.jar)\n\tUCHIJAAAA\t<CoFH ASM>{000} [CoFH ASM] (minecraft.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade{mc1.7.10-r87} [SlashBlade] ([拔刀剑]SlashBlade-mc1.7.10-r87-icere.jar)\n\tUCHIJAAAA\tPseudo{1.0.0} [Pseudo] (-伪刃.jar)\n\tUCHIJAAAA\tcreativecore{1.3.14} [CreativeCore] (-在线图片加载.jar)\n\tUCHIJAAAA\topframe{2.1} [OnlinePictureFrame] (-在线图片加载前置.jar)\n\tUCHIJAAAA\thorn_plenty{1.0.0} [Horn 'o' Plenty] (-无限食物.jar)\n\tUCHIJAAAA\tCoFHCore{1.7.10R3.1.4} [CoFH Core] (-热力前置.jar)\n\tUCHIJAAAA\tBaubles{1.0.1.10} [Baubles] (Baubles-1.7.10-1.0.1.10.jar)\n\tUCHIJAAAA\tThermalFoundation{1.7.10R1.2.6} [Thermal Foundation] (-热力基础.jar)\n\tUCHIJAAAA\tThermalExpansion{1.7.10R4.1.5} [Thermal Expansion] (-热力膨胀.jar)\n\tUCHIJAAAA\tThermalDynamics{1.7.10R1.2.1} [Thermal Dynamics] (-热动力学.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.exsa{mc1.7.10-r6} [SlashBlade-ExSa] (-狂暴SA.jar)\n\tUCHIJAAAA\tIC2{2.2.828-experimental} [IndustrialCraft 2] ([工业时代2]industrialcraft-2-2.2.828-experimental.jar)\n\tUCHIJAAAA\tThaumcraft{4.2.3.5} [Thaumcraft] (【神秘时代4修复版2】M-Thaumcraft-1.7.10-4.2.3.5-SaraFix-a1.jar)\n\tUCHIJAAAA\tEMT{1.2.2} [Electro-Magic Tools] (-电子魔法工具.jar)\n\tUCHIJAAAA\tflammpfeil.darkraven{mc1.7.2-r2} [DarkRaven] (拔刀剑-暗鸦-r2-1.7.10.jar)\n\tUCHIJAAAA\tlastsmith{1.0.0} [The Last Smith] (-结晶.jar)\n\tUCHIJAAAA\tgvc{0.6.1} [Gliby's Voice Chat Mod] (-语音聊天.jar)\n\tUCHIJAAAA\tMineTweaker3{3.0.10} [MineTweaker 3] (-魔改GUI.jar)\n\tUCHIJAAAA\tmodtweaker2{0.9.6} [Mod Tweaker 2] (-魔改前置.jar)\n\tUCHIJAAAA\tMTRM{1.0} [MineTweakerRecipeMaker] (-魔改物品.jar)\n\tUCHIJAAAA\tpozo{4.0.0} [pozo] (2333333.jar)\n\tUCHIJAAAA\tBambooMod{Minecraft@MC_VERSION@ var@VERSION@} [BambooMod] ([和风]Bamboo-2.6.8.5.jar)\n\tUCHIJAAAA\tNoIC2Destruction{1.0.0} [No IC2 Destruction] ([工业防爆]NoIC2Destruction-1.0.0-1.7.10.jar)\n\tUCHIJAAAA\tBuildCraft|Core{7.1.23} [BuildCraft] ([建筑_核心]buildcraft-7.1.23-core.jar)\n\tUCHIJAAAA\tBuildCraft|Transport{7.1.23} [BC Transport] ([建筑_运输]buildcraft-7.1.23-transport.jar)\n\tUCHIJAAAA\tBuildCraft|Compat{7.1.7} [BuildCraft Compat] ([建筑_兼容]buildcraft-compat-7.1.7.jar)\n\tUCHIJAAAA\tBuildCraft|Factory{7.1.23} [BC Factory] ([建筑_工厂]buildcraft-7.1.23-factory.jar)\n\tUCHIJAAAA\tBuildCraft|Energy{7.1.23} [BC Energy] ([建筑_能量]buildcraft-7.1.23-energy.jar)\n\tUCHIJAAAA\tBuildCraft|Silicon{7.1.23} [BC Silicon] ([建筑_芯片]buildcraft-7.1.23-silicon.jar)\n\tUCHIJAAAA\tcookingbook{1.0.134} [Cooking for Blockheads] ([懒人厨房]cookingbook-mc1.7.10-1.0.134.jar)\n\tUCHIJAAAA\tflammpfeil.nihil{mc1.7.x-r8} [Nihil] ([拔刀剑_似蛭]Nihil-mc1.7.x-r8.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.blademaster{mc1.7.2-r1} [BladeMaster] ([拔刀剑_剑圣之刃]BladeMaster-mc1.7.x-r1.2.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.kirisaya{r1} [SlashBlade-Kirisaya] ([拔刀剑_无神]Kirisaya-r1.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.kamuy{mc1.7.10-r6} [Kamuy] ([拔刀剑_神剑]SlashBlade-Kamuy-mc1.7.10-r6.jar)\n\tUCHIJAAAA\tflammpfeil.fluorescentbar{mc1.7.2-r3} [fluorescentbar] ([拔刀剑_荧光]FluorescentBar-mc1.7.x-r3.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.wanderer{mc1.7.2-r1} [SlashBladeWanderer] ([拔刀剑_风来剑]Wanderer-mc1.7.x-r1.jar)\n\tUCHIJAAAA\tIronChest{6.0.62.742} [Iron Chest] ([更多箱子]ironchest-1.7.10-6.0.62.742-universal.jar)\n\tUCHIJAAAA\tmod_ecru_MapleTree{1.1.32c} [MapleTree] ([枫树]MapleTree+Forge.jar)\n\tUCHIJAAAA\tForgeMultipart{1.1.2.331} [Forge Multipart] (ForgeMultipart-1.7.10-1.1.2.331-universal.jar)\n\tUCHIJAAAA\tappliedenergistics2{rv3-beta-16} [Applied Energistics 2] (应用能源.jar)\n\tUCHIJAAAA\tThaumicTinkerer{unspecified} [Thaumic Tinkerer] (【神秘工匠 】ThaumicTinkerer-2.5-1.7.10-164.jar)\n\tUCHIJAAAA\tTaintedMagic{1.1.6.3} [Tainted Magic] ([污秽魔法]TaintedMagic-1.1.6.3.jar)\n\tUCHIJAAAA\tharvestcraft{1.7.10j} [Pam's HarvestCraft] ([潘马斯丰收工艺]Pams+HarvestCraft+1.7.10Lb.jar)\n\tUCHIJAAAA\tminers{v3.1.1} [Miner's Heaven] ([矿工天堂]汉化v3.1.1.jar)\n\tUCHIJAAAA\ttcinventoryscan{1.0.11} [TC Inventory Scanning] ([神秘时代库存扫描]tcinventoryscan-mc1.7.10-1.0.11.jar)\n\tUCHIJAAAA\tTCBotaniaExoflame{1.0} [TCBotaniaExoflame] ([神秘时代植物魔法冶炼火]TCBotaniaExoflame-1.7.10-1.2.jar)\n\tUCHIJAAAA\tthaumicenergistics{1.1.2.0} [Thaumic Energistics] ([神秘能源]thaumicenergistics-1.1.2.0.jar)\n\tUCHIJAAAA\tAntiCheat3{AntiCheat 3 Mod} [AntiCheat 3 Mod] ([辅助]防盗号安全卫士.jar)\n\tUCHIJAAAA\tAnti_Anti_AntiCheat{2.0} [Anti_Anti_AntiCheat] ([辅助]防盗号安全卫士2.jar)\n\tUCHIJAAAA\tMekanism{9.1.1} [Mekanism] ([通用机械]Mekanism-1.7.10-9.1.1.1031.jar)\n\tUCHIJAAAA\tMekanismTools{9.1.1} [MekanismTools] ([通用机械前置]MekanismTools-1.7.10-9.1.1.1031.jar)\n\tUCHIJAAAA\tMekanismGenerators{9.1.1} [MekanismGenerators] ([通用机械发电机]MekanismGenerators-1.7.10-9.1.1.1031.jar)\n\tUCHIJAAAA\tGraviSuite{1.7.10-2.0.3} [Graviation Suite] ([重力装甲]GraviSuite-1.7.10-2.0.3.jar)\n\tUCHIJAAAA\tFoodCraft{1.2.0} [FoodCraft(FoodCraft)] ([食物工艺]-FoodCraft-16.8.9-1.2.1.jar)\n\tUCHIJAAAA\tAdvancedSolarPanel{1.7.10-3.5.1} [Advanced Solar Panels] ([高级太阳能]AdvancedSolarPanel-1.7.10-3.5.1-icere.jar)\n\tUCHIJAAAA\tcustomnpcs{1.7.10d} [CustomNpcs] (CustomNPCs_1.7.10 .jar)\n\tUCHIJAAAA\tallweapon{1.0} [allweapon] (【附属：万物皆刃】.jar)\n\tUCHIJAAAA\tNegoreRouse{1.0.0} [NegoreRouse] (SlashBlade-NegoreRouse-r2vL.jar)\n\tUCHIJAAAA\tBotania{r1.8-249} [Botania] (【植物魔法】Botania r1.8-249.jar)\n\tUCHIJAAAA\tForbiddenMagic{1.7.10-0.57} [Forbidden Magic] (【禁忌魔法 】Forbidden+Magic-1.7.10-0.57.jar)\n\tUCHIJAAAA\tExtraBotany{r1.0-21} [ExtraBotany] (【额外魔法学】ExtraBotany-1.7.10-r1.0-21.jar)\n\tUCHIJAAAA\tAvaritia{1.1} [Avaritia] (修复Avaritia-1.1.jar)\n\tUCHIJAAAA\tTorcherino{2.2s} [Torcherino] (加速火把Torcherino-TehNut-1.7.10-2.2.jar)\n\tUCHIJAAAA\tMantle{1.7.10-0.3.2.jenkins184} [Mantle] (匠魂手册Mantle-1.7.10-0.3.2.jar)\n\tUCHIJAAAA\tarmourersWorkshop{1.7.10-0.48.1} [Armourer's Workshop] (时装工坊-1.7.10-0.48.1.jar)\n\tUCHIJAAAA\tTConstruct{1.7.10-1.8.7.build979} [Tinkers' Construct] (匠魂本体TConstruct-1.7.10-1.8.7.jar)\n\tUCHIJAAAA\tTiCTooltips{1.2.3} [TiC Tooltips] (匠魂工具TiCTooltips-mc1.7.10-1.2.3.jar)\n\tUCHIJAAAA\tflammpfeil.frostwolf{mc1.7.2-r1} [FrostWolf] (拔刀剑-冰狼之刃-r1-1.7.10.jar)\n\tUCHIJAAAA\tsaligia{1.0.0} [PROJECT_SALIGIA] (拔刀剑-原罪PROJECT_saligia_2.1.0.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.terra{mc1.7.10-r1} [SlashBlade-Terra] (拔刀剑-大地之刃.jar)\n\tUCHIJAAAA\tbalumg{1.0.0} [balumg] (拔刀剑-巴鲁蒙格-r1.0-1-1.7.10.jar)\n\tUCHIJAAAA\tfoxex{1.1.2} [FoxBlade Extra] (拔刀剑-狐月刀FoxExtra v1.1.2(MC1710).jar)\n\tUCHIJAAAA\tslashblade.yukari{mc1.7.10-r2} [Slashblade-yukari] (拔刀剑-节月刀.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.anvilenchant{mc1.7.10-r1} [Slashblade-AnvilEnchant] (拔刀剑-铁砧附魔-r1-1.7.10.jar)\n\tUCHIJAAAA\tflammpfeil.kevinwalker{mc1.7.10} [kevinwalker] (炫彩之刃0.3v.jar)\n\tUCHIJAAAA\tGrimoireOfGaia{1.0.0} [Grimoire of Gaia 3] (盖亚魔典3-1.0.2.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.splight{r5} [SlashBlade-SPLight] (龙一文字SlashBlade-SPLight-r5.jar)\n\tUCHIJAAAA\tbspkrsCore{6.16} [bspkrsCore] ([1.7.10]bspkrsCore-universal-6.16.jar)\n\tUCHIJAAAA\tMcMultipart{1.1.2.331} [Minecraft Multipart Plugin] (ForgeMultipart-1.7.10-1.1.2.331-universal.jar)\n\tUCHIJAAAA\tflammpfeil.slashblade.slasharts{1.7.10 r1} [SlashBlade-SlashArts] (拔刀剑-刀之艺术SlashBlade-SlashArts-mc1.7.10-r2.jar)\n\tUCHIJAAAA\tForgeMicroblock{1.1.2.331} [Forge Microblocks] (ForgeMultipart-1.7.10-1.1.2.331-universal.jar)\n\tCoFHCore: -[1.7.10]3.1.4-329\n\tThermalFoundation: -[1.7.10]1.2.6-118\n\tThermalExpansion: -[1.7.10]4.1.5-248\n\tThermalDynamics: -[1.7.10]1.2.1-172\n\tAE2 Version: beta rv3-beta-16 for Forge 10.13.4.1448\n\tMantle Environment: DO NOT REPORT THIS CRASH! Unsupported mods in environment: bukkit\n\tTConstruct Environment: Environment healthy.\n\tAE2 Integration: IC2:ON, RotaryCraft:OFF, RC:OFF, BuildCraftCore:ON, BuildCraftTransport:ON, BuildCraftBuilder:OFF, RF:ON, RFItem:ON, MFR:OFF, DSU:ON, FZ:OFF, FMP:ON, RB:OFF, CLApi:OFF, Waila:OFF, Mekanism:ON, ImmibisMicroblocks:OFF, BetterStorage:OFF, OpenComputers:OFF, PneumaticCraft:OFF\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tPlayer Count: 23 / 120; [EntityPlayerMP['YeXin_'/58460, l='world', x=745.79, y=4.00, z=17.48](YeXin_ at 745.7856314711456,4.0,17.481752868749858), EntityPlayerMP['PresLEdit'/1711092, l='sc', x=214.70, y=70.00, z=245.14](PresLEdit at 214.69999998807907,70.0,245.1370726272644), EntityPlayerMP['Jinxinlei'/746830, l='sc', x=122.52, y=63.00, z=-174.70](Jinxinlei at 122.51683827589015,63.0,-174.69999998807907), EntityPlayerMP['NicoNico'/694599, l='plotworld', x=-653.70, y=45.00, z=-202.70](NicoNico at -653.6999999880791,45.0,-202.69999998807907), EntityPlayerMP['Mere_odo'/2671836, l='plotworld', x=-35.93, y=59.00, z=1409.41](Mere_odo at -35.931202757813224,59.0,1409.4115590543638), EntityPlayerMP['ADWASDWAA'/2970188, l='plotworld', x=-705.32, y=65.50, z=-424.56](ADWASDWAA at -705.3209889764047,65.5,-424.5644169773898), EntityPlayerMP['mojjhhh'/2550203, l='plotworld', x=-1024.10, y=63.00, z=-211.30](mojjhhh at -1024.098944208268,63.0,-211.30000001192093), EntityPlayerMP['honglantu'/2309613, l='plotworld', x=-2522.83, y=78.00, z=-1480.85](honglantu at -2522.829559546585,78.0,-1480.8501401729739), EntityPlayerMP['X_Kang'/4228282, l='plotworld', x=57.98, y=65.00, z=1060.58](X_Kang at 57.98043650520994,65.0,1060.583496813775), EntityPlayerMP['NiE4'/1717, l='plotworld', x=504.35, y=65.00, z=796.11](NiE4 at 504.3476405493866,65.0,796.1071896245816), EntityPlayerMP['32364596621'/161954, l='plotworld', x=-1255.40, y=65.00, z=1186.68](32364596621 at -1255.3957378976766,65.0,1186.6815602956085), EntityPlayerMP['momo'/4476571, l='plotworld', x=-683.68, y=68.11, z=-421.34](momo at -683.6843243124188,68.11032685842268,-421.3376121935361), EntityPlayerMP['SG25'/725218, l='zy', x=-1860.59, y=43.00, z=-210.52](SG25 at -1860.5945566574449,43.0,-210.51502152724186), EntityPlayerMP['tangmaosheng'/3942144, l='sc', x=217.08, y=69.88, z=247.30](tangmaosheng at 217.0826576787733,69.875,247.30000001192093), EntityPlayerMP['Durure_'/1407, l='plotworld', x=372.15, y=63.00, z=-121.71](Durure_ at 372.1489700463021,63.0,-121.71456996828749), EntityPlayerMP['MC_JG'/4142396, l='sc', x=219.35, y=70.00, z=244.53](MC_JG at 219.3476121409894,70.0,244.5277430282933), EntityPlayerMP['yumi'/4032374, l='sc', x=-284.72, y=86.63, z=-13090.62](yumi at -284.7201237878742,86.62929637844661,-13090.615578558567), EntityPlayerMP['ji_guang'/3344055, l='plotworld', x=371.94, y=63.00, z=-120.26](ji_guang at 371.9424788133897,63.0,-120.25669820054522), EntityPlayerMP['Kinght'/3064275, l='plotworld', x=334.05, y=65.00, z=-139.75](Kinght at 334.05008831784,65.0,-139.75316377587154), EntityPlayerMP['qinshi233'/4472290, l='plotworld', x=-1262.91, y=64.00, z=1190.06](qinshi233 at -1262.910117432677,64.0,1190.0611003716083), EntityPlayerMP['ZhangLuo_'/4558282, l='plotworld', x=-2118.91, y=77.92, z=-1247.99](ZhangLuo_ at -2118.9146514973577,77.91636799395752,-1247.993375045845), EntityPlayerMP['Dearm'/4603750, l='world', x=747.54, y=4.00, z=16.55](Dearm at 747.5437527152422,4.0,16.548873658238385), EntityPlayerMP['FengYe'/4605518, l='world', x=746.10, y=4.00, z=16.49](FengYe at 746.1002914455179,4.0,16.490857509587194)]\n\tIs Modded: Definitely; Server brand changed to 'uranium,kcauldron,cauldron,craftbukkit,mcpc,fml,forge'\n\tType: Dedicated Server (map_server.txt)"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/nei.txt",
    "content": "---- Minecraft Crash Report ----\n// Sorry :(\n\nTime: 19-1-24 下午8:06\nDescription: Rendering screen\n\njava.lang.NullPointerException: Rendering screen\n\tat codechicken.nei.LayoutManager.renderSlotOverlay(LayoutManager.java:719)\n\tat codechicken.nei.guihook.GuiContainerManager.renderSlotOverlay(GuiContainerManager.java:432)\n\tat net.minecraft.client.gui.inventory.GuiContainer.func_146977_a(GuiContainer.java:270)\n\tat net.minecraft.client.gui.inventory.GuiContainer.func_73863_a(GuiContainer.java:99)\n\tat net.minecraft.client.renderer.InventoryEffectRenderer.func_73863_a(InventoryEffectRenderer.java:38)\n\tat net.minecraft.client.gui.inventory.GuiInventory.func_73863_a(SourceFile:47)\n\tat net.minecraft.client.renderer.EntityRenderer.func_78480_b(EntityRenderer.java:1455)\n\tat net.minecraft.client.Minecraft.func_71411_J(Minecraft.java:1001)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:898)\n\tat net.minecraft.client.main.Main.main(SourceFile:148)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat org.jackhuang.hellominecraft.launcher.Launcher.main(Launcher.java:127)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat codechicken.nei.LayoutManager.renderSlotOverlay(LayoutManager.java:719)\n\tat codechicken.nei.guihook.GuiContainerManager.renderSlotOverlay(GuiContainerManager.java:432)\n\tat net.minecraft.client.gui.inventory.GuiContainer.func_146977_a(GuiContainer.java:270)\n\tat net.minecraft.client.gui.inventory.GuiContainer.func_73863_a(GuiContainer.java:99)\n\tat net.minecraft.client.renderer.InventoryEffectRenderer.func_73863_a(InventoryEffectRenderer.java:38)\n\tat net.minecraft.client.gui.inventory.GuiInventory.func_73863_a(SourceFile:47)\n\tat net.minecraft.client.renderer.EntityRenderer.func_78480_b(EntityRenderer.java:1455)\n\n-- Screen render details --\nDetails:\n\tScreen name: net.minecraft.client.gui.inventory.GuiInventory\n\tMouse location: Scaled: (341, 176). Absolute: (683, 353)\n\tScreen size: Scaled: (683, 353). Absolute: (1366, 706). Scale factor of 2\n\n-- Affected level --\nDetails:\n\tLevel name: MpServer\n\tAll players: 2 total; [EntityClientPlayerMP['2333'/204, l='MpServer', x=298.07, y=105.62, z=-304.37], EntityOtherPlayerMP['233'/61, l='MpServer', x=300.31, y=100.00, z=-295.31]]\n\tChunk stats: MultiplayerChunkCache: 49, 58\n\tLevel seed: 0\n\tLevel generator: ID 01 - flat, ver 0. Features enabled: false\n\tLevel generator options:\n\tLevel spawn location: World: (300,101,-300), Chunk: (at 12,6,4 in 18,-19; contains blocks 288,0,-304 to 303,255,-289), Region: (0,-1; contains chunks 0,-32 to 31,-1, blocks 0,0,-512 to 511,255,-1)\n\tLevel time: 427708 game time, 277031 day time\n\tLevel dimension: 0\n\tLevel storage version: 0x00000 - Unknown?\n\tLevel weather: Rain time: 0 (now: false), thunder time: 0 (now: false)\n\tLevel game mode: Game mode: survival (ID 0). Hardcore: false. Cheats: false\n\tForced entities: 32 total; [EntityItem['item.item.apple'/198, l='MpServer', x=295.63, y=100.13, z=-296.59], EntityFireworkRocket['entity.FireworksRocketEntity.name'/2055, l='MpServer', x=295.17, y=125.36, z=-302.73], EntityItem['item.item.apple'/200, l='MpServer', x=297.28, y=102.13, z=-300.69], EntityOtherPlayerMP['233'/61, l='MpServer', x=300.31, y=100.00, z=-295.31], EntityClientPlayerMP['2333'/204, l='MpServer', x=298.07, y=105.62, z=-304.37], EntityHat['未知'/145, l='MpServer', x=298.07, y=105.62, z=-304.37], EntityHat['未知'/146, l='MpServer', x=300.31, y=100.00, z=-295.31], EntityTrail['未知'/148, l='MpServer', x=298.07, y=105.62, z=-304.37], EntityTrail['未知'/149, l='MpServer', x=300.31, y=100.00, z=-295.31], EntityItem['item.item.string'/24, l='MpServer', x=300.22, y=96.88, z=-304.84], EntityItem['item.exnihilo.leaves.infested.spruce'/25, l='MpServer', x=300.96, y=96.88, z=-304.87], EntityItem['item.item.string'/26, l='MpServer', x=302.13, y=96.88, z=-304.87], EntityItem['item.tile.gravel'/27, l='MpServer', x=294.32, y=88.75, z=-289.18], EntityItem['item.tile.gravel'/28, l='MpServer', x=300.28, y=90.50, z=-288.32], EntityItem['item.tile.stoneSlab.cobble'/29, l='MpServer', x=295.13, y=87.69, z=-302.74], EntityFireworkRocket['entity.FireworksRocketEntity.name'/2909, l='MpServer', x=295.51, y=108.66, z=-300.50], EntityItem['item.exnihilo.stone'/30, l='MpServer', x=297.81, y=98.88, z=-288.87], EntityFireworkRocket['entity.FireworksRocketEntity.name'/2910, l='MpServer', x=302.50, y=109.66, z=-311.46], EntityItem['item.exnihilo.leaves.infested.oak'/31, l='MpServer', x=298.34, y=104.13, z=-299.94], EntityItem['item.item.apple'/32, l='MpServer', x=296.47, y=102.13, z=-300.38], EntityItem['item.item.apple'/33, l='MpServer', x=296.69, y=100.13, z=-298.19], EntityItem['item.item.apple'/34, l='MpServer', x=294.53, y=100.13, z=-300.19], EntityItem['item.item.apple'/35, l='MpServer', x=294.81, y=100.13, z=-297.03], EntityItem['item.item.apple'/36, l='MpServer', x=293.25, y=100.13, z=-297.41], EntityItem['item.item.apple'/37, l='MpServer', x=295.91, y=100.13, z=-299.69], EntityItem['item.item.apple'/38, l='MpServer', x=294.19, y=100.13, z=-298.25], EntityItem['item.tile.stonebrick'/39, l='MpServer', x=295.78, y=83.47, z=-284.28], EntityFireworkRocket['entity.FireworksRocketEntity.name'/2919, l='MpServer', x=293.51, y=108.97, z=-308.48], EntityItem['item.tile.gravel'/40, l='MpServer', x=300.35, y=91.00, z=-287.85], EntityFireworkRocket['entity.FireworksRocketEntity.name'/2925, l='MpServer', x=298.50, y=108.64, z=-304.50], EntityFireworkRocket['entity.FireworksRocketEntity.name'/2932, l='MpServer', x=298.51, y=108.55, z=-308.50], EntityOtherPlayerMP['233'/61, l='MpServer', x=300.31, y=100.00, z=-295.31]]\n\tRetry entities: 0 total; []\n\tServer brand: fml,forge\n\tServer type: Non-integrated multiplayer server\nStacktrace:\n\tat net.minecraft.client.Minecraft.func_71396_d(Minecraft.java:2444)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:919)\n\tat net.minecraft.client.main.Main.main(SourceFile:148)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat org.jackhuang.hellominecraft.launcher.Launcher.main(Launcher.java:127)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tOperating System: Windows 7 (amd64) version 6.1\n\tJava Version: 1.8.0_161, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 489768504 bytes (467 MB) / 849276928 bytes (809 MB) up to 1035206656 bytes (987 MB)\n\tJVM Flags: 7 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Xmn128m -Xmx1000m\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1558 Optifine OptiFine_1.7.10_HD_U_E7 107 mods loaded, 107 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJA\tmcp{9.05} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJA\tFML{7.10.99.99} [Forge Mod Loader] (forge-1.7.10-10.13.4.1558-1.7.10.jar)\n\tUCHIJA\tForge{10.13.4.1558} [Minecraft Forge] (forge-1.7.10-10.13.4.1558-1.7.10.jar)\n\tUCHIJA\tInputFix{1.7.x-v2} [InputFix] (minecraft.jar)\n\tUCHIJA\tappliedenergistics2-core{rv2-stable-1} [AppliedEnergistics2 Core] (minecraft.jar)\n\tUCHIJA\tbettersleepingcore{1.0} [Better Sleeping Core] (minecraft.jar)\n\tUCHIJA\tbambootransformer{1.0.0} [BambooTransformer] (minecraft.jar)\n\tUCHIJA\tCodeChickenCore{1.0.7.47} [CodeChicken Core] (minecraft.jar)\n\tUCHIJA\tOldModelLoader{1.0} [OldModelLoader] (minecraft.jar)\n\tUCHIJA\tNotEnoughItems{1.0.5.120} [Not Enough Items] (NotEnoughItems-1.7.10-1.0.5.120-universal.jar)\n\tUCHIJA\titemphysic{0.8.3} [ItemPhysic] (minecraft.jar)\n\tUCHIJA\tOpenModsCore{0.9.1} [OpenModsCore] (minecraft.jar)\n\tUCHIJA\t<CoFH ASM>{000} [CoFH ASM] (minecraft.jar)\n\tUCHIJA\tFastCraft{1.25} [FastCraft] (【快速工艺】fastcraft-1.25.jar)\n\tUCHIJA\tstefinusguns{0.5.2} [New Stefinus Guns] ([3D枪械]Stefinus Guns-0.5.2.jar)\n\tUCHIJA\tgokiStats{1.0.0} [gokiStats] ([RPG被动战技]Core-gokiStats-1.0 cn_via_CI010.jar)\n\tUCHIJA\tinventorytweaks{1.59-dev-152-cf6e263} [Inventory Tweaks] ([R键整理]InventoryTweaks-1.59-dev-152.jar)\n\tUCHIJA\tpunt{1.7.10-10.13.0-14.0} [Punt] ([一帮小船]Small Boats Mod.jar)\n\tUCHIJA\twhitehall{1.7.10-10.13.0-14.0} [Whitehall] ([一帮小船]Small Boats Mod.jar)\n\tUCHIJA\thoy{1.7.10-10.13.0-14.0} [Hoy] ([一帮小船]Small Boats Mod.jar)\n\tUCHIJA\tsmallboats{1.7.10-10.13.0-14.0} [SmallBoats (base)] ([一帮小船]Small Boats Mod.jar)\n\tUCHIJA\tHardcoreQuesting{4.4.4} [Hardcore Questing Mode] ([任务手册]HQM-The Journey-4.4.4.jar)\n\tUCHIJA\tcookingbook{1.0.140} [Cooking for Blockheads] ([傻瓜烹饪]cookingbook-mc1.7.10-1.0.140.jar)\n\tUCHIJA\tMantle{1.7.10-0.3.2.jenkins191} [Mantle] ([匠魂手册]Mantle-1.7.10-0.3.2b.jar)\n\tUCHIJA\tCoFHCore{1.7.10R3.1.3} [CoFH Core] ([热力前置]CoFHCore-[1.7.10]3.1.3-327.jar)\n\tUCHIJA\tThermalFoundation{1.7.10R1.2.5} [Thermal Foundation] ([热力本体]ThermalFoundation-[1.7.10]1.2.5-115.jar)\n\tUCHIJA\tThermalExpansion{1.7.10R4.1.4} [Thermal Expansion] ([热力机器]ThermalExpansion-[1.7.10]4.1.4-247.jar)\n\tUCHIJA\tWaila{1.5.10} [Waila] (Waila-1.5.10_1.7.10.jar)\n\tUCHIJA\tTConstruct{1.7.10-1.8.8.build991} [Tinkers' Construct] ([匠魂本体]TConstruct-1.7.10-1.8.8.build991.jar)\n\tUCHIJA\tTiCTooltips{1.2.3} [TiC Tooltips] ([匠魂工具]TiCTooltips-mc1.7.10-1.2.3.jar)\n\tUCHIJA\texnihilo{1.38-49} [Ex Nihilo] ([无中生有]Ex-Nihilo.jar)\n\tUCHIJA\texcompressum{1.1.143} [Ex Compressum] ([压缩工具]excompressum-mc1.7.10-1.1.143.jar)\n\tUCHIJA\tMTRM{1.0} [MineTweakerRecipeMaker] ([可视化魔改器]MineTweakerRecipeMaker-1.7.10-1.1.0.11[ZH_CN].jar)\n\tUCHIJA\tlibsandstone{1.0.0} [libsandstone] (LibSandstone-1.0.0.jar)\n\tUCHIJA\txreliquary{1.2} [Reliquary] ([圣遗物]Reliquary-1.2.257.jar)\n\tUCHIJA\tSolarFlux{1.7.10-0.3} [Solar Flux] ([太阳能板]SolarFlux-1.7.10-0.3.jar)\n\tUCHIJA\tyarr_cutemobmodels{1.0.8} [Cute Mob Models] ([娘化怪物]yarrcutemobmodels-1.0.8-1.7.10.jar)\n\tUCHIJA\tUnicodeFontFixer{1.1.12-mc1.7.10} [UnicodeFontFixer] ([字体修复]UnicodeFontFixer-1.1.12-mc1.7.10.jar)\n\tUCHIJA\tExtraUtilities{1.2.9} [Extra Utilities] ([实用设备]extrautilities-1.2.9.jar)\n\tUCHIJA\tBiblioCraft{1.10.5} [BiblioCraft] ([展示架]BiblioCraft[v1.10.6][MC1.7.10].jar)\n\tUCHIJA\tiChunUtil{4.0.0} [iChunUtil] ([重要前置]iChunUtil-4.0.0.jar)\n\tUCHIJA\tHats{4.0.0} [Hats] ([帽子]Hats-4.0.0.jar)\n\tUCHIJA\tHatStand{4.0.0} [HatStand] ([帽子架]HatStand-4.0.0.jar)\n\tUCHIJA\tappliedenergistics2{rv2-stable-1} [Applied Energistics 2] ([应用能源]appliedenergistics2-rv2-stable-1.jar)\n\tUCHIJA\tendercore{1.7.10-0.2.0.36_beta} [EnderCore] ([末影前置]EnderCore-1.7.10-0.2.0.36_beta.jar)\n\tUCHIJA\tEnderIO{1.7.10-2.3.0.428_beta} [Ender IO] ([末影接口]EnderIO-1.7.10-2.3.0.428_beta.jar)\n\tUCHIJA\topenmodularturrets{2.1.4-183} [Open Modular Turrets] ([开放炮塔]OpenModularTurrets-1.7.10-2.1.4-183.jar)\n\tUCHIJA\tOpenMods{0.9.1} [OpenMods] ([开源前置]OpenModsLib-1.7.10-0.9.1.jar)\n\tUCHIJA\tOpenBlocks{1.5.1} [OpenBlocks] ([开源方块]OpenBlocks-1.7.10-1.5.1.jar)\n\tUCHIJA\tmobends{0.20.1} [Mo' Bends] ([弯曲动作]MoBends-0.20.1 for MC 1.7.10.jar)\n\tUCHIJA\tSimpleAchievements{MC1.7.10-1.1.0-21} [Simple Achievements] ([成就指南]SimpleAchievements-MC1.7.10-1.1.0-21.jar)\n\tUCHIJA\tCalendarGui{1.0.1} [Calendar Gui] ([日历]Sub-CalendarGui-1.0.1.7.jar)\n\tUCHIJA\texastrisrebirth{MC1.7.10-1.01-45} [Ex Astris Rebirth] ([星辉生万物]Ex-Astris-Rebirth-MC1.7.10-1.01-45.jar)\n\tUCHIJA\tIronChest{6.0.62.742} [Iron Chest] ([更多箱子]Iron-Chests-Mod-1.7.10.jar)\n\tUCHIJA\tBetterAchievements{0.1.0} [Better Achievements] ([更好成就]BetterAchievements-1.7.10-0.1.0.jar)\n\tUCHIJA\tJABBA{1.2.1} [JABBA] ([更好的桶]Jabba-1.2.1a_1.7.10.jar)\n\tUCHIJA\tbettersleeping{1.7.10-0.4.13} [Better Sleeping] ([更好睡眠]BetterSleeping-1.7.10-0.4-CN.jar)\n\tUCHIJA\tCarpentersBlocks{3.3.8} [Carpenter's Blocks] ([木匠方块]Carpenter's Blocks v3.3.8 - MC 1.7.10.jar)\n\tUCHIJA\tEnderZoo{1.7.10-1.0.15.32} [Ender Zoo] ([末影动物园]EnderZoo-1.7.10-1.0.15.32.jar)\n\tUCHIJA\tAgriCraft{1.7.10-1.5.0} [AgriCraft] ([杂交工艺]AgriCraft-1.7.10-1.5.0.jar)\n\tUCHIJA\tHardcoreDarkness{1.7} [Hardcore Darkness] ([深邃黑暗]HardcoreDarkness-MC1.7.10-1.7.jar)\n\tUCHIJA\tHopperDuctMod{1.3.2} [Hopper Ducts] ([漏斗管道]hopperductmod-1.7.10-1.3.2.jar)\n\tUCHIJA\tharvestcraft{1.7.10g} [Pam's HarvestCraft] ([潘马斯的丰收]Pam's HarvestCraft 1.7.10h.jar)\n\tUCHIJA\tThermalDynamics{1.7.10R1.2.0} [Thermal Dynamics] ([热力管道]ThermalDynamics-[1.7.10]1.2.0-171.jar)\n\tUCHIJA\tAppleCore{1.1.0} [AppleCore] ([苹果核]Dep-AppleCore-1.1.0.jar)\n\tUCHIJA\tSpiceOfLife{1.2.3} [The Spice of Life] ([生活调味料]Core-SpiceOfLife-1.2.3.jar)\n\tUCHIJA\tCustomMainMenu{1.7} [Custom Main Menu] ([界面修改]CustomMainMenu-MC1.7.10-1.7.jar)\n\tUCHIJA\tRealisticTorches{1.6.0} [Realistic Torches] ([真实火把]RealisticTorches-1.7.10-1.6.0.jar)\n\tUCHIJA\tbspkrsCore{6.16} [bspkrsCore] ([1.7.10]bspkrsCore-universal-6.16.jar)\n\tUCHIJA\tTreecapitator{1.7.2} [Treecapitator] ([砍树]Treecapitator-universal-2.0.3.jar)\n\tUCHIJA\tBambooMod{Minecraft1.7.10 var2.6.8.3} [BambooMod] ([竹]Bamboo-2.6.8.3.jar)\n\tUCHIJA\tInfernalMobs{1.6.6} [Infernal Mobs] ([精英怪物]InfernalMobs-1.7.10.jar)\n\tUCHIJA\ttorchLevers{1.4.2} [Torch Levers] ([红石机关]TorchLevers-V1.4.2-MC1.7.10.jar)\n\tUCHIJA\ttreeGrowingSimulator{0.0.3} [Tree Growing Simulator 2014] ([蹲防生长]TreeGrowingSimulator2014-MC1.7.10-0.0.3-22.jar)\n\tUCHIJA\tredgear_core{2.2.2} [Red Gear Core] ([酿药前置]RedGearCore-1.7.10-2.2.2.jar)\n\tUCHIJA\tredgear_brewcraft{1.3.3} [Brewcraft] ([酿药修改]Brewcraft-1.7.10-1.3.3.jar)\n\tUCHIJA\tMineTweaker3{3.0.9B} [MineTweaker 3] ([魔改助手]MineTweaker3-1.7.10-3.0.9C.jar)\n\tUCHIJA\tmodtweaker2{0.9.6} [Mod Tweaker 2] ([魔改基友]ModTweaker2-0.9.6.jar)\n\tUCHIJA\tmagicalcrops{4.0.0_PUBLIC_BETA_4b} [Magical Crops: Core] ([魔法作物]magicalcrops-4.0.0_PUBLIC_BETA_5[ITERCN].jar)\n\tUCHIJA\tFTBL{1.0.18.2} [FTBLib] (FTBLib-1.7.10-1.0.18.3.jar)\n\tUCHIJA\tFTBU{1.0.18.2} [FTBUtilities] (FTBUtilities-1.7.10-1.0.18.3.jar)\n\tUCHIJA\tGrowthcraft{1.7.10-2.3.1} [Growthcraft] (growthcraft-1.7.10-2.3.1-complete.jar)\n\tUCHIJA\tGrowthcraft|Cellar{1.7.10-2.3.1} [Growthcraft Cellar] (growthcraft-1.7.10-2.3.1-complete.jar)\n\tUCHIJA\tGrowthcraft|Apples{1.7.10-2.3.1} [Growthcraft Apples] (growthcraft-1.7.10-2.3.1-complete.jar)\n\tUCHIJA\tGrowthcraft|Bamboo{1.7.10-2.3.1} [Growthcraft Bamboo] (growthcraft-1.7.10-2.3.1-complete.jar)\n\tUCHIJA\tGrowthcraft|Bees{1.7.10-2.3.1} [Growthcraft Bees] (growthcraft-1.7.10-2.3.1-complete.jar)\n\tUCHIJA\tGrowthcraft|Fishtrap{1.7.10-2.3.1} [Growthcraft Fishtrap] (growthcraft-1.7.10-2.3.1-complete.jar)\n\tUCHIJA\tGrowthcraft|Grapes{1.7.10-2.3.1} [Growthcraft Grapes] (growthcraft-1.7.10-2.3.1-complete.jar)\n\tUCHIJA\tGrowthcraft|Hops{1.7.10-2.3.1} [Growthcraft Hops] (growthcraft-1.7.10-2.3.1-complete.jar)\n\tUCHIJA\tGrowthcraft|Rice{1.7.10-2.3.1} [Growthcraft Rice] (growthcraft-1.7.10-2.3.1-complete.jar)\n\tUCHIJA\tlmmx{1.0} [lmmx] (littleMaidMobX-1.7.x_0.1.1.jar)\n\tUCHIJA\tMMMLibX{1.7.x-srg-1} [MMMLibX] (littleMaidMobX-1.7.x_0.1.1.jar)\n\tUCHIJA\tzabuton{1.0} [zabuton] (littleMaidMobX-1.7.x_0.1.1.jar)\n\tUCHIJA\tNEIAddons{1.12.3.11} [NEI Addons] (neiaddons-mc1710-1.12.3.11.jar)\n\tUCHIJA\tNEIAddons|Botany{1.12.3.11} [NEI Addons: Botany] (neiaddons-mc1710-1.12.3.11.jar)\n\tUCHIJA\tNEIAddons|Forestry{1.12.3.11} [NEI Addons: Forestry] (neiaddons-mc1710-1.12.3.11.jar)\n\tUCHIJA\tNEIAddons|CraftingTables{1.12.3.11} [NEI Addons: Crafting Tables] (neiaddons-mc1710-1.12.3.11.jar)\n\tUCHIJA\tNEIAddons|ExNihilo{1.12.3.11} [NEI Addons: Ex Nihilo] (neiaddons-mc1710-1.12.3.11.jar)\n\tUCHIJA\tWailaHarvestability{1.1.0} [Waila Harvestability] (WailaHarvestability-mc1.7.x-1.1.0.jar)\n\tUCHIJA\tuncraftingTable{1.4.7} [Uncrafting Table] (【[分解工作台]UncraftingTable-1.4.7.jar)\n\tUCHIJA\tTorcherino{2.2s} [Torcherino] (【[加速火把]Torcherino-1.7.10-2.2s.jar)\n\tUCHIJA\tautopackager{1.5.7} [AutoPackager] (【[自动打包]autopackager-1.5.7.jar)\n\tUCHIJA\tcraftguide{1.7.1.0} [CraftGuide] (【G键合成表】CraftGuide-1.7.1.0-forge[1.7.10].jar)\n\tUCHIJA\tlootbags{2.0.16} [Loot Bags] (【战利品】LootBags-1.7.10-2.0.16.jar)\n\tUCHIJA\tjourneymap{5.1.4p2} [JourneyMap] (【旅行地图journeymap-1.7.10-5.1.4p2-unlimited.jar)\n\tUCHIJA\tbetterbuilderswands{0.6.0} [Better Builder's Wands] (【更好的建筑魔杖】BetterBuildersWands-0.6.0-1.7.10(Chopper汉化).jar)\n\tUCHIJA\tIguanaTweaksTConstruct{1.7.10-2.1.5.140} [Iguana Tinker Tweaks] ([匠魂附属]IguanaTinkerTweaks-1.7.10-2.1.5.jar)\n\tGL info: ' Vendor: 'Intel' Version: '3.1.0 - Build 9.17.10.4229' Renderer: 'Intel(R) HD Graphics 3000'\n\tOpenModsLib class transformers: [stencil_patches:FINISHED],[movement_callback:FINISHED],[map_gen_fix:ENABLED],[gl_capabilities_hook:FINISHED],[player_render_hook:FINISHED]\n\tClass transformer null safety: found misbehaving transformers: me.guichaguri.betterfps.transformers.MathTransformer(me.guichaguri.betterfps.transformers.MathTransformer@4ab2b9cb) returned non-null result: 0,me.guichaguri.betterfps.transformers.EventTransformer(me.guichaguri.betterfps.transformers.EventTransformer@2d223296) returned non-null result: 0\n\tMantle Environment: DO NOT REPORT THIS CRASH! Unsupported mods in environment: optifine\n\tCoFHCore: -[1.7.10]3.1.3-327\n\tThermalFoundation: -[1.7.10]1.2.5-115\n\tThermalExpansion: -[1.7.10]4.1.4-247\n\tTConstruct Environment: Environment healthy.\n\tAE2 Version: stable rv2-stable-1 for Forge 10.13.2.1291\n\tThermalDynamics: -[1.7.10]1.2.0-171\n\tEnderIO: Found the following problem(s) with your installation:\n                  * Optifine is installed. This is NOT supported.\n                  * The RF API that is being used (1.7.10R1.3.1 from <unknown>) differes from that that is reported as being loaded (1.7.10R1.0.13 from [末影接口]EnderIO-1.7.10-2.3.0.428_beta.jar).\n                    It is a supported version, but that difference may lead to problems.\n                  * Our API got loaded from [末影接口]EnderIO-1.7.10-2.3.0.428_beta.jar. That's unexpected.\n                  * Our API got loaded from [末影接口]EnderIO-1.7.10-2.3.0.428_beta.jar. That's unexpected.\n                  * Our API got loaded from [末影接口]EnderIO-1.7.10-2.3.0.428_beta.jar. That's unexpected.\n                  * Our API got loaded from [末影接口]EnderIO-1.7.10-2.3.0.428_beta.jar. That's unexpected.\n                 This may have caused the error. Try reproducing the crash WITHOUT this/these mod(s) before reporting it.\n\tStencil buffer state: Function set: GL30, pool: forge, bits: 8\n\tAE2 Integration: IC2:OFF, RotaryCraft:OFF, RC:OFF, BC:OFF, RF:ON, RFItem:ON, MFR:OFF, DSU:ON, FZ:OFF, FMP:OFF, RB:OFF, CLApi:OFF, Waila:ON, InvTweaks:ON, NEI:ON, CraftGuide:ON, Mekanism:OFF, ImmibisMicroblocks:OFF, BetterStorage:OFF\n\tLaunched Version: HMCL 2.4.0.32\n\tLWJGL: 2.9.1\n\tOpenGL: Intel(R) HD Graphics 3000 GL version 3.1.0 - Build 9.17.10.4229, Intel\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nAnisotropic filtering is supported and maximum anisotropy is 16.\nShaders are available because OpenGL 2.1 is supported.\n\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: [Custom Sky - 2本.zip]\n\tCurrent Language: 简体中文 (中国)\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tAnisotropic Filtering: Off (1)\n\tOptiFine Version: OptiFine_1.7.10_HD_U_E7\n\tRender Distance Chunks: 3\n\tMipmaps: 0\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 3.1.0 - Build 9.17.10.4229\n\tOpenGlRenderer: Intel(R) HD Graphics 3000\n\tOpenGlVendor: Intel\n\tCpuCount: 4"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/neoforgeforest_optifine_incompatibility.txt",
    "content": "[14:42:15] [main/INFO]: ModLauncher running: args [--username, Zkitefly, --version, 1.20.4-of, --gameDir, /home/zkitefly/.minecraft, --assetsDir, /home/zkitefly/.minecraft/assets, --assetIndex, 12, --uuid, 4453d157b2b8488c8be610fe8d34ef8c, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, msa, --versionType, HMCL (PR Collection) 3.5.unofficial-eb0b16b (PR Collection), --width, 854, --height, 480, --fml.neoForgeVersion, 20.4.223, --fml.fmlVersion, 2.0.17, --fml.mcVersion, 1.20.4, --fml.neoFormVersion, 20231207.154220, --launchTarget, forgeclient]\n[14:42:15] [main/INFO]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.10 by BellSoft; OS Linux arch amd64 version 5.10.0-amd64-desktop\n[14:42:15] [main/INFO]: Loading ImmediateWindowProvider fmlearlywindow\n[14:42:15] [main/INFO]: Trying GL version 4.6\n[14:42:15] [main/INFO]: Requested GL version 4.6 got version 4.6\n[14:42:15] [main/INFO]: OptiFineTransformationService.onLoad\n[14:42:15] [main/INFO]: OptiFine ZIP file URL: union:/home/zkitefly/.minecraft/mods/preview_OptiFine_1.20.4_HD_U_I8_pre4.jar%23167!/\n[14:42:15] [main/ERROR]: Error loading OptiFine ZIP file: union:/home/zkitefly/.minecraft/mods/preview_OptiFine_1.20.4_HD_U_I8_pre4.jar%23167!/\njava.nio.file.NoSuchFileException: /home/zkitefly/.minecraft/mods/preview_OptiFine_1.20.4_HD_U_I8_pre4.jar#167\n\tat sun.nio.fs.UnixException.translateToIOException(UnixException.java:92) ~[?:?]\n\tat sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:106) ~[?:?]\n\tat sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111) ~[?:?]\n\tat sun.nio.fs.UnixFileAttributeViews$Basic.readAttributes(UnixFileAttributeViews.java:55) ~[?:?]\n\tat sun.nio.fs.UnixFileSystemProvider.readAttributes(UnixFileSystemProvider.java:148) ~[?:?]\n\tat sun.nio.fs.LinuxFileSystemProvider.readAttributes(LinuxFileSystemProvider.java:99) ~[?:?]\n\tat java.nio.file.Files.readAttributes(Files.java:1851) ~[?:?]\n\tat java.util.zip.ZipFile$Source.get(ZipFile.java:1428) ~[?:?]\n\tat java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:718) ~[?:?]\n\tat java.util.zip.ZipFile.<init>(ZipFile.java:252) ~[?:?]\n\tat java.util.zip.ZipFile.<init>(ZipFile.java:181) ~[?:?]\n\tat java.util.zip.ZipFile.<init>(ZipFile.java:195) ~[?:?]\n\tat optifine.OptiFineTransformationService.onLoad(OptiFineTransformationService.java:109) ~[?:?]\n\tat cpw.mods.modlauncher.TransformationServiceDecorator.onLoad(TransformationServiceDecorator.java:53) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.lambda$loadTransformationServices$11(TransformationServicesHandler.java:116) ~[modlauncher-10.0.9.jar:?]\n\tat java.util.HashMap$Values.forEach(HashMap.java:1065) ~[?:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.loadTransformationServices(TransformationServicesHandler.java:116) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:48) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:88) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:78) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?]\n[14:42:15] [main/ERROR]: Service failed to load OptiFine\ncpw.mods.modlauncher.api.IncompatibleEnvironmentException: Error loading OptiFine ZIP file: union:/home/zkitefly/.minecraft/mods/preview_OptiFine_1.20.4_HD_U_I8_pre4.jar%23167!/\n\tat optifine.OptiFineTransformationService.onLoad(OptiFineTransformationService.java:122) ~[?:?]\n\tat cpw.mods.modlauncher.TransformationServiceDecorator.onLoad(TransformationServiceDecorator.java:53) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.lambda$loadTransformationServices$11(TransformationServicesHandler.java:116) ~[modlauncher-10.0.9.jar:?]\n\tat java.util.HashMap$Values.forEach(HashMap.java:1065) ~[?:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.loadTransformationServices(TransformationServicesHandler.java:116) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:48) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:88) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:78) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?]\n[14:42:15] [main/ERROR]: Found 1 services that failed to load : [OptiFine]\nException in thread \"main\" cpw.mods.modlauncher.InvalidLauncherSetupException: Invalid Services found OptiFine\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.TransformationServicesHandler.validateTransformationServices(TransformationServicesHandler.java:110)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:49)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.run(Launcher.java:88)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.main(Launcher.java:78)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141)\n[HMCL ProcessListener] Minecraft exit with code 1(0x1), type is APPLICATION_ERROR.\n"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/netease.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\n  SkinCore (4626894634154779079@3@0.jar)\n  BaseModNeteaseCore (4620273813196076442@3@0.jar)\n  Main ([防砍动画]OldAnimationsMod v2.4.2 FORGE MC1.8.9.jar)\n  InputFix (4618424574399199550@3@0.jar)\n  FMLLoadingPlugin ([防砍动画未响应修复]OldAnimationsModUnresponsiveFix-1.0.jar)\n  NeteaseCore (4619774556351054392@3@0.jar)\nContact their authors BEFORE contacting forge\n\n// But it works on my machine.\n\nTime: 19-1-26 下午2:11\nDescription: Unexpected error\n\njava.lang.NullPointerException: Unexpected error\n\tat com.netease.mc.mod.battergaming.iIIIiiIIIi.run(r:73)\n\tat com.netease.mc.mod.battergaming.iiiiiiIIIi.ALLATORIxDEMO(j:271)\n\tat com.netease.mc.mod.battergaming.iiiiiiIIIi.ALLATORIxDEMO(j:182)\n\tat net.minecraftforge.fml.common.eventhandler.ASMEventHandler_83_iiiiiiIIIi_ALLATORIxDEMO_ClientTickEvent.invoke(.dynamic)\n\tat net.minecraftforge.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:55)\n\tat net.minecraftforge.fml.common.eventhandler.EventBus.post(EventBus.java:140)\n\tat net.minecraftforge.fml.common.FMLCommonHandler.onPreClientTick(FMLCommonHandler.java:331)\n\tat net.minecraft.client.Minecraft.func_71407_l(Minecraft.java:1617)\n\tat net.minecraft.client.Minecraft.func_71411_J(Minecraft.java:1017)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:351)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.8.9\n\tOperating System: Windows 7 (x86) version 6.1\n\tCPU: <unknown>\n\tJava Version: 1.8.0_101, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) Client VM (mixed mode), Oracle Corporation\n\tMemory: 190658720 bytes (181 MB) / 443887616 bytes (423 MB) up to 747896832 bytes (713 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx726M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP 9.19 Powered by Forge 11.15.1.0 Optifine OptiFine_1.8.9_HD_U_I7 22 mods loaded, 22 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJA\tmcp{9.19} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJA\tFML{8.0.99.99} [Forge Mod Loader] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\tForge{11.15.1.0} [Minecraft Forge] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\tInputFix{1.8.x-v2} [InputFix] (minecraft.jar)\n\tUCHIJA\tskincoremod{1.8.9} [skincoremod] (minecraft.jar)\n\tUCHIJA\toldanimations{2.4.2} [OldAnimationsMod] (minecraft.jar)\n\tUCHIJA\tneteasecore{1.11.2} [NeteaseCore] (minecraft.jar)\n\tUCHIJA\tbasemodneteasecore{1.8.9} [BaseModNeteaseCore] (minecraft.jar)\n\tUCHIJA\tnetworkmod{1.0} [network rpc Mod] (4620273813222949778@3@0.jar)\n\tUCHIJA\tstoremod{1.0} [storemod] (4618419806295460477@3@0.jar)\n\tUCHIJA\tantimod{2.0} [anti addiction Mod] (4618827437296985101@3@0.jar)\n\tUCHIJA\tfiltermod{1.0} [filtermod] (4619774556351054392@3@0.jar)\n\tUCHIJA\tfriendplaymod{1.0} [Friendplay Mod] (4620273813159696403@3@0.jar)\n\tUCHIJA\tmcbasemod{1.0} [mcbasemod] (4620273813196076442@3@0.jar)\n\tUCHIJA\tupdatecore{1.0} [updatecore] (4620608833856825631@3@0.jar)\n\tUCHIJA\tplayermanager{1.0} [playermanager] (4620702952524438419@3@0.jar)\n\tUCHIJA\tfullscreenpopup{1.8.9.38000} [Fullscreen Popup Mod] (4624103992226684617@3@0.jar)\n\tUCHIJA\tskinmod{1.8.8-15739} [skinmod] (4626894634154779079@3@0.jar)\n\tUCHIJA\tbattergaming{0.0.1} [battergaming] (4628181791522237000@2@8.jar)\n\tUCHIJA\tcosmeticwings{@VERSION@} [Cosmetic Wings] (4628181791563285768@2@8.jar)\n\tUCHIJA\tenhancements{7.7} [Vanilla Enhancements] ([1.8.9][原版增强] Vanilla Enhancements-7.7.jar)\n\tUCHIJA\tTcpNoDelayMod-2.0{1.0} [TcpNoDelayMod-2.0] (modid-1.0.jar)\n\tLoaded coremods (and transformers):\nSkinCore (4626894634154779079@3@0.jar)\n  com.netease.mc.mod.skin.coremod.SkinCoreTransformer\nBaseModNeteaseCore (4620273813196076442@3@0.jar)\n  com.netease.mc.core.base.NeteaseCoreTransformer\nMain ([防砍动画]OldAnimationsMod v2.4.2 FORGE MC1.8.9.jar)\n  com.spiderfrog.main.ClassTransformer\nInputFix (4618424574399199550@3@0.jar)\n  lain.mods.inputfix.InputFixTransformer\nFMLLoadingPlugin ([防砍动画未响应修复]OldAnimationsModUnresponsiveFix-1.0.jar)\n  io.github.zekerzhayard.unresponsivefix.ClassTransformer\nNeteaseCore (4619774556351054392@3@0.jar)\n  com.netease.mc.core.NeteaseCoreTransformer\n\tLaunched Version: 1.8.9\n\tLWJGL: 2.9.4\n\tOpenGL: Intel(R) HD Graphics 3000 GL version 3.1.0 - Build 9.17.10.4229, Intel\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing GL 1.3 texture combiners.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nShaders are available because OpenGL 2.1 is supported.\nVBOs are available because OpenGL 1.5 is supported.\n\n\tUsing VBOs: No\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: !      锟斤拷bBlue Moon 锟斤拷432x.zip\n\tCurrent Language: 简体中文 (中国)\n\tProfiler Position: N/A (disabled)\n\tCPU: <unknown>\n\tOptiFine Version: OptiFine_1.8.9_HD_U_I7\n\tRender Distance Chunks: 8\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 3.1.0 - Build 9.17.10.4229\n\tOpenGlRenderer: Intel(R) HD Graphics 3000\n\tOpenGlVendor: Intel\n\tCpuCount: 4"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/performant_optifine_incompatibility.txt",
    "content": "2023-06-03 23:02:04,125 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND\n[23:02:04] [main/INFO]: ModLauncher running: args [--username, Xianxianzzz, --version, Minecraft 1.16.5 - 寮傜晫鐢熸椿骞绘兂, --gameDir, C:\\Users\\********\\Desktop\\寮傜晫鐢熸椿骞绘兂-Isekai-Life's-Fantasy v1.3\\.minecraft, --assetsDir, C:\\Users\\********\\Desktop\\寮傜晫鐢熸椿骞绘兂-Isekai-Life's-Fantasy v1.3\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 0ca3ecd984a34f60840dd218ee1e4c71, --accessToken, 鉂勨潉鉂勨潉鉂勨潉鉂勨潉, --userType, mojang, --versionType, HMCL 3.5.4, --width, 1280, --height, 720, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.39, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550]\n[23:02:04] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 1.8.0_333 by Oracle Corporation\n[23:02:04] [main/INFO]: OptiFineTransformationService.onLoad\n[23:02:04] [main/INFO]: OptiFine ZIP file: C:\\Users\\********\\Desktop\\寮傜晫鐢熸椿骞绘兂-Isekai-Life's-Fantasy v1.3\\.minecraft\\mods\\OptiFine_1.16.5_HD_U_G8.jar\n[23:02:04] [main/INFO]: Target.PRE_CLASS is available\n[23:02:04] [main/INFO]: Added Lets Encrypt root certificates as additional trust\n[23:02:04] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/C:/Users/********/Desktop/%e5%bc%82%e7%95%8c%e7%94%9f%e6%b4%bb%e5%b9%bb%e6%83%b3-Isekai-Life's-Fantasy%20v1.3/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\n[23:02:04] [main/INFO]: OptiFineTransformationService.initialize\n[23:02:06] [main/INFO]: [org.antlr.v4.runtime.ConsoleErrorListener:syntaxError:38]: line 1:0 token recognition error at: '~'\n[23:02:06] [main/INFO]: OptiFineTransformationService.transformers\n[23:02:06] [main/INFO]: Targets: 311\nInit ItemPhysic coremods ...\nInit CreativeCore coremods ...\n[23:02:08] [main/INFO]: additionalClassesLocator: [optifine., net.optifine.]\n[23:02:08] [main/ERROR]: Mixin config drippyloadingscreen.mixin.json does not specify \"minVersion\" property\n[23:02:08] [main/INFO]: Successfully loaded Mixin Connector [tictim.paraglider.MixinConnector]\n[23:02:08] [main/INFO]: Successfully loaded Mixin Connector [icyllis.modernui.core.MixinConnector]\n[23:02:08] [main/INFO]: Successfully loaded Mixin Connector [vazkii.patchouli.common.MixinConnector]\n[23:02:08] [main/INFO]: Successfully loaded Mixin Connector [com.fuzs.easymagic.mixin.MixinConnector]\n[23:02:08] [main/INFO]: Successfully loaded Mixin Connector [com.performant.coremod.mixin.Connector]\n[23:02:08] [main/INFO]: Launching target 'fmlclient' with arguments [--version, Minecraft 1.16.5 - 寮傜晫鐢熸椿骞绘兂, --gameDir, C:\\Users\\********\\Desktop\\寮傜晫鐢熸椿骞绘兂-Isekai-Life's-Fantasy v1.3\\.minecraft, --assetsDir, C:\\Users\\********\\Desktop\\寮傜晫鐢熸椿骞绘兂-Isekai-Life's-Fantasy v1.3\\.minecraft\\assets, --uuid, 0ca3ecd984a34f60840dd218ee1e4c71, --username, Xianxianzzz, --assetIndex, 1.16, --accessToken, 鉂勨潉鉂勨潉鉂勨潉鉂勨潉, --userType, mojang, --versionType, HMCL 3.5.4, --width, 1280, --height, 720]\n[23:02:09] [main/WARN]: Reference map 'nethers_delight.refmap.json' for nethers_delight.mixins.json could not be read. If this is a development environment you can ignore this message\n[23:02:09] [main/INFO]: Performant configs loaded\n[23:02:09] [main/INFO]: Patching LivingEntity#attackEntityFrom\n[23:02:09] [main/INFO]: Transforming class: net.minecraft.entity.Entity\n[23:02:09] [main/INFO]: Transforming class: net.minecraft.entity.Entity\n[23:02:10] [main/INFO]: start\n[23:02:10] [main/INFO]: find block call\n[23:02:10] [main/INFO]: Patching PlayerContainer#<init>\n[23:02:10] [main/INFO]: Patching PlayerContainer#onCraftMatrixChanged\n[23:02:10] [main/INFO]: Patching PlayerContainer#transferStackInSlot\n[23:02:10] [main/INFO]: Transforming class: net.minecraft.tileentity.TileEntity\n[Serene Seasons Transformer]: Transforming func_176224_k in net/minecraft/block/CauldronBlock\n[Serene Seasons Transformer]: Patched 1 calls\n[23:02:10] [main/INFO]: Patching LivingEntity#attackEntityFrom\n[23:02:10] [main/INFO]: Transforming method: setupTerrain net.minecraft.client.renderer.WorldRenderer\n[23:02:10] [main/INFO]: Transforming method: updateCameraAndRender net.minecraft.client.renderer.WorldRenderer\n[Serene Seasons Transformer]: Transforming func_228438_a_ in net/minecraft/client/renderer/WorldRenderer\n[Serene Seasons Transformer]: Patched 1 calls\n[Serene Seasons Transformer]: Transforming func_228436_a_ in net/minecraft/client/renderer/WorldRenderer\n[Serene Seasons Transformer]: Patched 1 calls\n[23:02:10] [main/INFO]: Patching ModelBakery#<init>\n[Serene Seasons Transformer]: Transforming func_201854_a in net/minecraft/world/biome/Biome\n[Serene Seasons Transformer]: Patched 1 calls\n[Serene Seasons Transformer]: Transforming func_201850_b in net/minecraft/world/biome/Biome\n[Serene Seasons Transformer]: Patched 1 calls\n[23:02:11] [main/WARN]: Error loading class: biomesoplenty/common/world/BOPBiomeProvider (java.lang.ClassNotFoundException: null)\n[23:02:11] [main/WARN]: Error loading class: xiroc/dungeoncrawl/dungeon/Dungeon (java.lang.ClassNotFoundException: null)\n[23:02:11] [main/INFO]: Transforming class: net.minecraft.tileentity.TileEntity\n[23:02:11] [main/INFO]: Patching ItemStack#onItemUse\n[23:02:11] [main/INFO]: start\n[23:02:11] [main/INFO]: find block call\n[23:02:12] [main/INFO]: Not enabling mixin forcom.performant.coremod.mixin.forge.BasicEventHooksMixin as config disables it.\n[23:02:12] [main/INFO]: Not enabling mixin forcom.performant.coremod.mixin.world.WorldMixin as config disables it.\n[23:02:12] [main/INFO]: Not enabling mixin forcom.performant.coremod.mixin.entity.LivingEntityUpdateEventMixin as config disables it.\n[23:02:13] [main/INFO]: Transforming class: net.minecraft.entity.Entity\n[23:02:13] [main/INFO]: Patching BlockModelShapes#getModelLocation\n[23:02:13] [main/INFO]: Patching PlayerContainer#<init>\n[23:02:13] [main/INFO]: Patching PlayerContainer#onCraftMatrixChanged\n[23:02:13] [main/INFO]: Patching PlayerContainer#transferStackInSlot\n[23:02:13] [main/INFO]: Patching LivingEntity#attackEntityFrom\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:39)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.run(Launcher.java:82)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: Caused by: java.lang.reflect.InvocationTargetException\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.lang.reflect.Method.invoke(Unknown Source)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.FMLClientLaunchProvider.lambda$launchService$0(FMLClientLaunchProvider.java:37)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37)\n[23:02:13] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \t... 4 more\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: Caused by: org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:392)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:250)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.service.modlauncher.MixinTransformationHandler.processClass(MixinTransformationHandler.java:131)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.launch.MixinLaunchPluginLegacy.processClass(MixinLaunchPluginLegacy.java:131)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.serviceapi.ILaunchPluginService.processClassWithFlags(ILaunchPluginService.java:154)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.LaunchPluginHandler.offerClassNodeToPlugins(LaunchPluginHandler.java:85)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.ClassTransformer.transform(ClassTransformer.java:120)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.TransformingClassLoader$DelegatedClassLoader.findClass(TransformingClassLoader.java:265)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.TransformingClassLoader.loadClass(TransformingClassLoader.java:136)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.TransformingClassLoader.loadClass(TransformingClassLoader.java:98)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat java.lang.ClassLoader.loadClass(Unknown Source)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat java.lang.ClassLoader.defineClass1(Native Method)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat java.lang.ClassLoader.defineClass(Unknown Source)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.TransformingClassLoader.loadClass(TransformingClassLoader.java:138)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.TransformingClassLoader.loadClass(TransformingClassLoader.java:98)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat java.lang.ClassLoader.loadClass(Unknown Source)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat java.lang.ClassLoader.defineClass1(Native Method)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat java.lang.ClassLoader.defineClass(Unknown Source)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.TransformingClassLoader.loadClass(TransformingClassLoader.java:138)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat cpw.mods.modlauncher.TransformingClassLoader.loadClass(TransformingClassLoader.java:98)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat java.lang.ClassLoader.loadClass(Unknown Source)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat net.optifine.reflect.Reflector.<clinit>(Reflector.java:282)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat net.minecraft.crash.CrashReport.func_71504_g(CrashReport.java:101)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat net.minecraft.crash.CrashReport.<init>(CrashReport.java:54)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat net.minecraft.crash.CrashReport.func_230188_h_(CrashReport.java:425)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat net.minecraft.client.main.Main.main(Main.java:122)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \t... 10 more\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: Caused by: org.spongepowered.asm.mixin.injection.throwables.InjectionError: Critical injection failure: Redirector OnisOnLadder(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/LivingEntity;)Z in performant.mixins.json:entity.LivingEntityMixin failed injection check, (0/1) succeeded. Scanned 1 target(s). Using refmap performant.refmap.json\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.injection.struct.InjectionInfo.postInject(InjectionInfo.java:468)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.applyInjections(MixinTargetContext.java:1362)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyInjections(MixinApplicatorStandard.java:1051)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:400)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:325)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:383)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:365)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363)\n[23:02:13] [main/INFO]: [java.lang.Throwable:printStackTrace:-1]: \t... 35 more\nException in thread \"main\""
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/shadersmodcore.txt",
    "content": "---- Minecraft Crash Report ----\n// But it works on my machine.\n\nTime: 1/30/19 2:37 AM\nDescription: Stitching texture atlas\n\njava.lang.NullPointerException: Stitching texture atlas\n\tat shadersmodcore.client.ShadersTex.readImage(ShadersTex.java:464)\n\tat shadersmodcore.client.ShadersTex.readImageAndMipmaps(ShadersTex.java:439)\n\tat shadersmodcore.client.ShadersTex.uploadTexSubForLoadAtlas(ShadersTex.java:419)\n\tat net.minecraft.client.renderer.texture.TextureMap.func_110571_b(TextureMap.java:381)\n\tat net.minecraft.client.renderer.texture.TextureMap.func_110551_a(TextureMap.java:136)\n\tat net.minecraft.client.renderer.texture.TextureManager.func_110579_a(TextureManager.java:94)\n\tat net.minecraft.client.renderer.texture.TextureManager.func_110580_a(TextureManager.java:76)\n\tat net.minecraft.client.renderer.texture.TextureManager.func_130088_a(TextureManager.java:63)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:545)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:878)\n\tat net.minecraft.client.main.Main.main(SourceFile:148)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat shadersmodcore.client.ShadersTex.readImage(ShadersTex.java:464)\n\tat shadersmodcore.client.ShadersTex.readImageAndMipmaps(ShadersTex.java:439)\n\tat shadersmodcore.client.ShadersTex.uploadTexSubForLoadAtlas(ShadersTex.java:419)\n\n-- Texture being stitched together --\nDetails:\n\tAtlas path: textures/blocks\n\tSprite: TextureAtlasSprite{name='missingno', frameCount=1, rotated=false, x=0, y=0, height=16, width=16, u0=6.25E-4, u1=0.999375, v0=6.25E-4, v1=0.999375}\nStacktrace:\n\tat net.minecraft.client.renderer.texture.TextureMap.func_110571_b(TextureMap.java:381)\n\tat net.minecraft.client.renderer.texture.TextureMap.func_110551_a(TextureMap.java:136)\n\n-- Resource location being registered --\nDetails:\n\tResource location: minecraft:textures/atlas/blocks.png\n\tTexture object class: net.minecraft.client.renderer.texture.TextureMap\nStacktrace:\n\tat net.minecraft.client.renderer.texture.TextureManager.func_110579_a(TextureManager.java:94)\n\tat net.minecraft.client.renderer.texture.TextureManager.func_110580_a(TextureManager.java:76)\n\tat net.minecraft.client.renderer.texture.TextureManager.func_130088_a(TextureManager.java:63)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:545)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:878)\n\tat net.minecraft.client.main.Main.main(SourceFile:148)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tOperating System: Windows 8.1 (amd64) version 6.3\n\tJava Version: 1.8.0_51, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 488780776 bytes (466 MB) / 1676148736 bytes (1598 MB) up to 3817865216 bytes (3641 MB)\n\tJVM Flags: 2 total; -Xmx4096m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1558 Optifine OptiFine_1.7.10_HD_U_D3 43 mods loaded, 43 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCH\tmcp{9.05} [Minecraft Coder Pack] (minecraft.jar)\n\tUCH\tFML{7.10.99.99} [Forge Mod Loader] (forge-1.7.10-10.13.4.1558-1.7.10.jar)\n\tUCH\tForge{10.13.4.1558} [Minecraft Forge] (forge-1.7.10-10.13.4.1558-1.7.10.jar)\n\tUCH\tUraniumPlus{1.0} [Added title and actionbar support for client and server] ([motd]UraniumPlus.jar)\n\tUCH\tCodeChickenCore{1.0.4.35} [CodeChicken Core] (minecraft.jar)\n\tUCH\tNotEnoughItems{1.0.4.83} [Not Enough Items] ([NEI]NotEnoughItems-1.0.4.83-universal.jar)\n\tUCH\tbetterfps{1.0.0} [BetterFps] (minecraft.jar)\n\tUCH\tuniskinmod{1.2-dev4} [Universal Skin Mod] (minecraft.jar)\n\tUCH\tIC2{2.2.828-experimental} [IndustrialCraft 2] ([工业2]industrialcraft.jar)\n\tUCH\tBuildCraft|Core{7.1.22} [BuildCraft] ([建筑管道]buildcraft-7.1.22.jar)\n\tUCH\tBuildCraft|Transport{7.1.22} [BC Transport] ([建筑管道]buildcraft-7.1.22.jar)\n\tUCH\tBuildCraft|Factory{7.1.22} [BC Factory] ([建筑管道]buildcraft-7.1.22.jar)\n\tUCH\tBuildCraft|Silicon{7.1.22} [BC Silicon] ([建筑管道]buildcraft-7.1.22.jar)\n\tUCH\tBuildCraft|Energy{7.1.22} [BC Energy] ([建筑管道]buildcraft-7.1.22.jar)\n\tUCH\tBuildCraft|Builders{7.1.22} [BC Builders] ([建筑管道]buildcraft-7.1.22.jar)\n\tUCH\tTwilightForest{2.3.7} [The Twilight Forest] ([暮色]twilightforest-1.7.10-2.3.7.jar)\n\tUCH\tgregtech{MC1710} [GregTech] ([GT5]格雷科技5-gregtech-5.09.31.jar)\n\tUCH\tWaila{1.5.8} [Waila] ([NEI扩展]Waila-1.5.9_1.7.10.jar)\n\tUCH\tcustomnpcs{1.7.10d} [CustomNpcs] ([NPC]CustomNPCs_1.7.10d.jar)\n\tUCH\tReiMinimap{1.7.10} [Rei's Minimap] ([小地图]LittleMap.jar)\n\tUCH\tflammpfeil.slashblade{mc1.7.10-r87} [SlashBlade] ([拔刀]SlashBlade.jar)\n\tUCH\tmrblade{1.2.7} [Technology Revolution Blade Extra] ([工业拔刀附属]TRblade技术革新 v1.2.7(MC1710).jar)\n\tUCH\tBuildCraft|Robotics{7.1.22} [BC Robotics] ([建筑管道]buildcraft-7.1.22.jar)\n\tUCH\tsaligia{1.0.0} [PROJECT_SALIGIA] ([拔刀附属]七宗罪PROJECT_saligia_2.1.0.jar)\n\tUCH\tflammpfeil.nihil{mc1.7.x-r8} [Nihil] ([拔刀附属]似蛭1.7.x-r8.jar)\n\tUCH\tYamazakura{1.0.0} [Yamazakura] ([拔刀附属]山樱SlashBlade-Yamazakura-mc1.7-r4.jar)\n\tUCH\tflammpfeil.slashblade.kirisaya{r1} [SlashBlade-Kirisaya] ([拔刀附属]无神Kirisaya-r1.jar)\n\tUCH\tfoxex{1.1.2} [FoxBlade Extra] ([拔刀附属]狐月刀FoxExtrav1.1.2(MC1710).jar)\n\tUCH\tflammpfeil.slashblade.kamuy{mc1.7.10-r6} [Kamuy] ([拔刀附属]神剑.jar)\n\tUCH\tflammpfeil.fluorescentbar{mc1.7.2-r3} [fluorescentbar] ([拔刀附属]荧光FluorescentBar-mc1.7.2-r3.jar)\n\tUCH\tDamageIndicatorsMod{3.2.0} [Damage Indicators] ([显血]DamageIndicatorsMod.jar)\n\tUCH\tIronChest{6.0.62.742} [Iron Chest] ([更多箱子]ironchest-1.7.10-6.0.62.742-universal.jar)\n\tUCH\tLotsOfFood{1.7.10} [Lots of Food] ([更多食物]Lots+of+Food.jar)\n\tUCH\tmusic{1.3} [Music] ([点歌]music-1.3 1.7.10.jar)\n\tUCH\tBambooMod{Minecraft@MC_VERSION@ var@VERSION@} [BambooMod] ([竹]Bamboo.jar)\n\tUCH\tAdvancedSolarPanel{1.7.10-3.5.1} [Advanced Solar Panels] ([高级太阳能]AdvancedSolarPanel.jar)\n\tUCH\tsupersolarpanel{1.7.10-1.1.1-release} [Super Solar Panel] ([超级太阳能]SSP-1.1.2-1.7.10工业时代腐竹汉化.jar)\n\tUCH\tGraviSuite{1.7.10-2.0.3} [Graviation Suite] ([重力装甲]GraviSuite.jar)\n\tUCH\tFoodCraft{1.2.0} [FoodCraft(FoodCraft)] ([食物工艺]-FoodCraft-16.8.9-1.2.1.jar)\n\tUCH\tDragonMounts{r39} [Dragon Mounts] ([龙骑士]dragonmount_r38.jar)\n\tUCH\tAFSU{1.2.3a-Freeza} [AFSU Mod] (AFSU-1.2.3a-Freeza.jar)\n\tUCH\tinventorytweaks{1.59-dev-152-cf6e263} [Inventory Tweaks] (R键整理.jar)\n\tUCH\tgvc{0.6.1} [Gliby's Voice Chat Mod] (语音聊天 GlibysVC 1.7.10 0.6.2a CN.jar)\n\tGL info: ' Vendor: 'NVIDIA Corporation' Version: '4.6.0 NVIDIA 390.65' Renderer: 'GeForce GT 610/PCIe/SSE2'\n\tLaunched Version: 1.7.10-Forge10.13.4.1558-1.7.10\n\tLWJGL: 2.9.1\n\tOpenGL: GeForce GT 610/PCIe/SSE2 GL version 4.6.0 NVIDIA 390.65, NVIDIA Corporation\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nAnisotropic filtering is supported and maximum anisotropy is 16.\nShaders are available because OpenGL 2.1 is supported.\n\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: []\n\tCurrent Language: 简体中文 (中国)\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tAnisotropic Filtering: Off (1)\n\tOptiFine Version: OptiFine_1.7.10_HD_U_D3\n\tRender Distance Chunks: 12\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tOpenGlVersion: 4.6.0 NVIDIA 390.65\n\tOpenGlRenderer: GeForce GT 610/PCIe/SSE2\n\tOpenGlVendor: NVIDIA Corporation\n\tCpuCount: 4"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/tconstruct.txt",
    "content": "---- Minecraft Crash Report ----\n// Uh... Did I do that?\n\nTime: 21-1-24 下午5:10\nDescription: Tesselating block model\n\njava.lang.NullPointerException: Tesselating block model\n\tat slimeknights.tconstruct.smeltery.SmelteryClientEvents.lambda$blockColors$4(SmelteryClientEvents.java:134) ~[tconstruct:1.16.5-3.0.1.48] {re:classloading,pl:eventbus:A}\n\tat net.minecraft.client.renderer.color.BlockColors.func_228054_a_(BlockColors.java:92) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.BlockModelRenderer.func_228800_a_(BlockModelRenderer.java:132) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.BlockModelRenderer.func_228798_a_(BlockModelRenderer.java:220) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.BlockModelRenderer.renderModelFlat(BlockModelRenderer.java:103) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraftforge.client.model.pipeline.ForgeBlockModelRenderer.renderModelFlat(ForgeBlockModelRenderer.java:69) ~[forge:?] {re:classloading}\n\tat net.minecraft.client.renderer.BlockModelRenderer.renderModel(BlockModelRenderer.java:51) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.BlockRendererDispatcher.renderModel(BlockRendererDispatcher.java:63) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask.func_228940_a_(ChunkRenderDispatcher.java:527) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask.func_225618_a_(ChunkRenderDispatcher.java:449) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender.func_228936_k_(ChunkRenderDispatcher.java:383) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.chunk.ChunkRenderDispatcher.func_228902_a_(ChunkRenderDispatcher.java:164) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.WorldRenderer.func_228437_a_(WorldRenderer.java:847) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.WorldRenderer.func_228426_a_(WorldRenderer.java:936) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.GameRenderer.func_228378_a_(GameRenderer.java:607) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.GameRenderer.func_195458_a(GameRenderer.java:425) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.func_195542_b(Minecraft.java:976) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:607) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.main(Main.java:184) ~[1.16.5.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_202] {}\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_202] {}\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_202] {}\n\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_202] {}\n\tat net.minecraftforge.fml.loading.FMLClientLaunchProvider.lambda$launchService$0(FMLClientLaunchProvider.java:51) ~[forge-1.16.5-36.0.1.jar:36.0] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:82) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66) [modlauncher-8.0.9.jar:?] {}\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Render thread\nStacktrace:\n\tat slimeknights.tconstruct.smeltery.SmelteryClientEvents.lambda$blockColors$4(SmelteryClientEvents.java:134) ~[tconstruct:1.16.5-3.0.1.48] {re:classloading,pl:eventbus:A}\n\tat net.minecraft.client.renderer.color.BlockColors.func_228054_a_(BlockColors.java:92) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.BlockModelRenderer.func_228800_a_(BlockModelRenderer.java:132) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.BlockModelRenderer.func_228798_a_(BlockModelRenderer.java:220) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.BlockModelRenderer.renderModelFlat(BlockModelRenderer.java:103) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraftforge.client.model.pipeline.ForgeBlockModelRenderer.renderModelFlat(ForgeBlockModelRenderer.java:69) ~[forge:?] {re:classloading}\n-- Block model being tesselated --\nDetails:\n\tBlock: Block{tconstruct:seared_drain}[active=true,facing=north]\n\tBlock location: World: (1370,92,-738), Chunk: (at 10,5,14 in 85,-47; contains blocks 1360,0,-752 to 1375,255,-737), Region: (2,-2; contains chunks 64,-64 to 95,-33, blocks 1024,0,-1024 to 1535,255,-513)\n\tUsing AO: false\nStacktrace:\n\tat net.minecraft.client.renderer.BlockModelRenderer.renderModel(BlockModelRenderer.java:51) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\n\n-- Block being tesselated --\nDetails:\n\tBlock: Block{tconstruct:seared_drain}[active=true,facing=north]\n\tBlock location: World: (1370,92,-738), Chunk: (at 10,5,14 in 85,-47; contains blocks 1360,0,-752 to 1375,255,-737), Region: (2,-2; contains chunks 64,-64 to 95,-33, blocks 1024,0,-1024 to 1535,255,-513)\nStacktrace:\n\tat net.minecraft.client.renderer.BlockRendererDispatcher.renderModel(BlockRendererDispatcher.java:63) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask.func_228940_a_(ChunkRenderDispatcher.java:527) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask.func_225618_a_(ChunkRenderDispatcher.java:449) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender.func_228936_k_(ChunkRenderDispatcher.java:383) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.chunk.ChunkRenderDispatcher.func_228902_a_(ChunkRenderDispatcher.java:164) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.WorldRenderer.func_228437_a_(WorldRenderer.java:847) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.WorldRenderer.func_228426_a_(WorldRenderer.java:936) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.renderer.GameRenderer.func_228378_a_(GameRenderer.java:607) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\n\n-- Affected level --\nDetails:\n\tAll players: 1 total; [ClientPlayerEntity['Light'/15217, l='ClientLevel', x=1389.50, y=91.00, z=-733.34]]\n\tChunk stats: Client Chunk Cache: 529, 342\n\tLevel dimension: minecraft:overworld\n\tLevel spawn location: World: (1382,4,-758), Chunk: (at 6,0,10 in 86,-48; contains blocks 1376,0,-768 to 1391,255,-753), Region: (2,-2; contains chunks 64,-64 to 95,-33, blocks 1024,0,-1024 to 1535,255,-513)\n\tLevel time: 1567868 game time, 513106 day time\n\tServer brand: forge\n\tServer type: Non-integrated multiplayer server\nStacktrace:\n\tat net.minecraft.client.world.ClientWorld.func_72914_a(ClientWorld.java:447) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.func_71396_d(Minecraft.java:2024) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:623) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.main(Main.java:184) ~[1.16.5.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_202] {}\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_202] {}\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_202] {}\n\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_202] {}\n\tat net.minecraftforge.fml.loading.FMLClientLaunchProvider.lambda$launchService$0(FMLClientLaunchProvider.java:51) ~[forge-1.16.5-36.0.1.jar:36.0] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:82) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66) [modlauncher-8.0.9.jar:?] {}\n\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.16.5\n\tMinecraft Version ID: 1.16.5\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_202, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 1363596192 bytes (1300 MB) / 2147483648 bytes (2048 MB) up to 2147483648 bytes (2048 MB)\n\tCPUs: 4\n\tJVM Flags: 11 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Xmn128m -Xmx2048m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tModLauncher: 8.0.9+86+master.3cf110c\n\tModLauncher launch target: fmlclient\n\tModLauncher naming: srg\n\tModLauncher services:\n\t\t/mixin-0.8.2.jar mixin PLUGINSERVICE\n\t\t/eventbus-4.0.0.jar eventbus PLUGINSERVICE\n\t\t/forge-1.16.5-36.0.1.jar object_holder_definalize PLUGINSERVICE\n\t\t/forge-1.16.5-36.0.1.jar runtime_enum_extender PLUGINSERVICE\n\t\t/accesstransformers-3.0.1.jar accesstransformer PLUGINSERVICE\n\t\t/forge-1.16.5-36.0.1.jar capability_inject_definalize PLUGINSERVICE\n\t\t/forge-1.16.5-36.0.1.jar runtimedistcleaner PLUGINSERVICE\n\t\t/mixin-0.8.2.jar mixin TRANSFORMATIONSERVICE\n\t\t/forge-1.16.5-36.0.1.jar fml TRANSFORMATIONSERVICE\n\tFML: 36.0\n\tForge: net.minecraftforge:36.0.1\n\tFML Language Providers:\n\t\tjavafml@36.0\n\t\tminecraft@1\n\tMod List:\n\t\tforge-1.16.5-36.0.1-client.jar                    |Minecraft                     |minecraft                     |1.16.5              |DONE      |NOSIGNATURE\n\t\tTConstruct-1.16.5-3.0.1.48.jar                    |Tinkers' Construct            |tconstruct                    |3.0.1.48            |DONE      |NOSIGNATURE\n\t\tRoughlyEnoughItems-Forge-5.8.11+20210123-1121.jar |Roughly Enough Items          |roughlyenoughitems            |5.8.11+20210123-1121|DONE      |NOSIGNATURE\n\t\tforge-1.16.5-36.0.1-universal.jar                 |Forge                         |forge                         |36.0.1              |DONE      |22:af:21:d8:19:82:7f:93:94:fe:2b:ac:b7:e4:41:57:68:39:87:b1:a7:5c:c6:44:f9:25:74:21:14:f5:0d:90\n\t\texnihilosequentia-1.16-2.0.1.0.jar                |Ex Nihilo: Sequentia          |exnihilosequentia             |1.16-2.0.1.0        |DONE      |NOSIGNATURE\n\t\tMantle-1.16.5-1.6.74.jar                          |Mantle                        |mantle                        |1.6.74              |DONE      |NOSIGNATURE\n\t\tcloth-config-forge-4.1.3.jar                      |Cloth Config v4 API           |cloth-config                  |4.1.3               |DONE      |NOSIGNATURE\n\tCrash Report UUID: 3375ccb6-b708-402f-9f12-d1e8d4fbf854\n\tLaunched Version: HMCL 3.3.180\n\tBackend library: LWJGL version 3.2.2 build 10\n\tBackend API: Intel(R) HD Graphics 4000 GL version 4.0.0 - Build 10.18.10.4252, Intel\n\tGL Caps: Using framebuffer using OpenGL 3.0\n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'forge'\n\tType: Client (map_client.txt)\n\tGraphics mode: fast\n\tResource Packs:\n\tCurrent Language: 简体中文 (中国)\n\tCPU: 4x Intel(R) Core(TM) i3-3110M CPU @ 2.40GHz"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/thaumcraft.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\n  CreativePatchingLoader ({扶人前置}CreativeCore_v1.9.45_mc1.12.2.jar)\n  BetterFoliageLoader ({树叶}BetterFoliage-MC1.12-2.2.0.jar)\n  ForgelinPlugin ({生命上限前置}Forgelin-1.8.2.jar)\n  PhosphorFMLLoadingPlugin (phosphor-1.12.2-0.1.6+build31.jar)\n  DynamicSurroundingsCore (DynamicSurroundings-core-1.12.2-3.5.4.3.jar)\nContact their authors BEFORE contacting forge\n\n// I feel sad now :(\n\nTime: 6/17/19 11:30 AM\nDescription: Initializing game\n\njava.lang.RuntimeException: Invalid id 4096 - maximum id range exceeded.\n\tat net.minecraftforge.registries.ForgeRegistry.add(ForgeRegistry.java:296)\n\tat net.minecraftforge.registries.ForgeRegistry.add(ForgeRegistry.java:282)\n\tat net.minecraftforge.registries.ForgeRegistry.register(ForgeRegistry.java:115)\n\tat thaumcraft.common.config.ConfigBlocks.registerBlock(ConfigBlocks.java:508)\n\tat thaumcraft.common.config.ConfigBlocks.registerBlock(ConfigBlocks.java:503)\n\tat thaumcraft.common.config.ConfigBlocks.initBlocks(ConfigBlocks.java:362)\n\tat thaumcraft.Registrar.registerBlocks(Registrar.java:48)\n\tat net.minecraftforge.fml.common.eventhandler.ASMEventHandler_148_Registrar_registerBlocks_Register.invoke(.dynamic)\n\tat net.minecraftforge.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:90)\n\tat net.minecraftforge.fml.common.eventhandler.EventBus$1.invoke(EventBus.java:144)\n\tat net.minecraftforge.fml.common.eventhandler.EventBus.post(EventBus.java:182)\n\tat net.minecraftforge.registries.GameData.fireRegistryEvents(GameData.java:775)\n\tat net.minecraftforge.fml.common.Loader.preinitializeMods(Loader.java:628)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:252)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:466)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:377)\n\tat net.minecraft.client.main.Main.main(SourceFile:123)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Client thread\nStacktrace:\n\tat net.minecraftforge.registries.ForgeRegistry.add(ForgeRegistry.java:296)\n\tat net.minecraftforge.registries.ForgeRegistry.add(ForgeRegistry.java:282)\n\tat net.minecraftforge.registries.ForgeRegistry.register(ForgeRegistry.java:115)\n\tat thaumcraft.common.config.ConfigBlocks.registerBlock(ConfigBlocks.java:508)\n\tat thaumcraft.common.config.ConfigBlocks.registerBlock(ConfigBlocks.java:503)\n\tat thaumcraft.common.config.ConfigBlocks.initBlocks(ConfigBlocks.java:362)\n\tat thaumcraft.Registrar.registerBlocks(Registrar.java:48)\n\tat net.minecraftforge.fml.common.eventhandler.ASMEventHandler_148_Registrar_registerBlocks_Register.invoke(.dynamic)\n\tat net.minecraftforge.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:90)\n\tat net.minecraftforge.fml.common.eventhandler.EventBus$1.invoke(EventBus.java:144)\n\tat net.minecraftforge.fml.common.eventhandler.EventBus.post(EventBus.java:182)\n\tat net.minecraftforge.registries.GameData.fireRegistryEvents(GameData.java:775)\n\tat net.minecraftforge.fml.common.Loader.preinitializeMods(Loader.java:628)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:252)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:466)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:377)\n\tat net.minecraft.client.main.Main.main(SourceFile:123)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.12.2\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_172, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 145002968 bytes (138 MB) / 687865856 bytes (656 MB) up to 3221225472 bytes (3072 MB)\n\tJVM Flags: 11 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Xmn128m -Xmx3072m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP 9.42 Powered by Forge 14.23.5.2768 Optifine OptiFine_1.12.2_HD_U_E4_pre4 39 mods loaded, 39 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\n\t| State | ID                | Version      | Source                                                   | Signature                                |\n\t|:----- |:----------------- |:------------ |:-------------------------------------------------------- |:---------------------------------------- |\n\t| UCH   | minecraft         | 1.12.2       | minecraft.jar                                            | None                                     |\n\t| UCH   | mcp               | 9.42         | minecraft.jar                                            | None                                     |\n\t| UCH   | FML               | 8.0.99.99    | forge-1.12.2-14.23.5.2768.jar                            | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| UCH   | forge             | 14.23.5.2768 | forge-1.12.2-14.23.5.2768.jar                            | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n\t| UCH   | creativecoredummy | 1.0.0        | minecraft.jar                                            | None                                     |\n\t| UCH   | dsurroundcore     | 3.5.4.3      | minecraft.jar                                            | None                                     |\n\t| UCH   | reforged          | 0.7.5        | [更多武器]Reforged-Mod-1.12.jar                              | None                                     |\n\t| UCH   | mocreatures       | 12.0.5       | [更多生物]DrZharks MoCreatures Mod-12.0.5.jar                | None                                     |\n\t| UCH   | customspawner     | 3.11.4       | [更多生物前置]CustomMobSpawner-3.11.4.jar                      | None                                     |\n\t| UCH   | orbis-lib         | 0.2.0        | orbis-lib-1.12.2-0.2.0+build42.jar                       | db341c083b1b8ce9160a769b569ef6737b3f4cdf |\n\t| UCH   | aether            | 0.2.0        | aether_ii-1.12.2-0.2.0+build42-universal.jar             | db341c083b1b8ce9160a769b569ef6737b3f4cdf |\n\t| UCH   | jei               | 4.15.0.268   | {JEI}jei_1.12.2-4.15.0.268.jar                           | None                                     |\n\t| UCH   | jeresources       | 0.8.9.48     | {JEI附属}JustEnoughResources-1.12.2-0.8.9.48.jar           | None                                     |\n\t| UCH   | wawla             | 2.5.273      | {信息显示}Wawla-1.12.2-2.5.273.jar                           | d476d1b22b218a10d845928d1665d45fce301b27 |\n\t| UCH   | craftstudioapi    | 1.0.0        | {动物前置}CraftStudioAPI-universal-1.0.1.95-mc1.12-alpha.jar | None                                     |\n\t| UCH   | baubles           | 1.5.2        | {神秘前置}Baubles-1.12-1.5.2.jar                             | None                                     |\n\t| UCH   | thaumcraft        | 6.1.BETA26   | {神秘}Thaumcraft-1.12.2-6.1.BETA26.jar                     | None                                     |\n\t| UCH   | twilightforest    | 3.8.689      | {暮色}twilightforest-1.12.2-3.8.689-universal.jar          | None                                     |\n\t| UCH   | animania          | 1.6.2        | {动物}animania-1.12.2-1.6.2.jar                            | None                                     |\n\t| UCH   | xaerominimap      | 1.17.1       | {小地图}Xaeros_Minimap_1.17.1_Forge_1.12.jar                | None                                     |\n\t| UCH   | playerrevive      | 1.0          | {扶人}PlayerRevive_v1.2.26_mc1.12.2.jar                    | None                                     |\n\t| UCH   | creativecore      | 1.9.9        | {扶人前置}CreativeCore_v1.9.45_mc1.12.2.jar                  | None                                     |\n\t| UCH   | fbp               | 2.4.1        | {方块破坏}FancyBlockParticles-1.12.x-2.4.1.jar               | None                                     |\n\t| UCH   | forgelin          | 1.8.2        | {生命上限前置}Forgelin-1.8.2.jar                               | None                                     |\n\t| UCH   | betterfoliage     | 2.2.0        | {树叶}BetterFoliage-MC1.12-2.2.0.jar                       | None                                     |\n\t| UCH   | waila             | 1.8.26       | {消息显示}Hwyla-1.8.26-B41_1.12.2.jar                        | None                                     |\n\t| UCH   | abyssalcraft      | 1.9.6        | {深渊}AbyssalCraft-1.12.2-1.9.6.jar                        | 220f10d3a93b3ff5fbaa7434cc629d863d6751b9 |\n\t| UCH   | shadowmc          | 3.8.0        | {生命上线前置}ShadowMC-1.12-3.8.0.jar                          | None                                     |\n\t| UCH   | leveluphp         | 1.12.2-1.4.0 | {生命上限}leveluphp-1.12.2-1.4.0.jar                         | None                                     |\n\t| UCH   | dmp               | 1.12.0       | {真实世界}dmp-1.12.0.jar                                     | None                                     |\n\t| UCH   | pmp               | 1.12.1       | {真实世界}pmp-1.12.1.jar                                     | None                                     |\n\t| UCH   | realworld         | 1.18         | {真实世界}realworld-1.18.jar                                 | None                                     |\n\t| UCH   | durabilityshow    | 5.0.0        | {耐久显示}Durability+Show-1.12-5.0.0.jar                     | None                                     |\n\t| UCH   | powerinventory    | 2.3.1        | {背包扩大}OverpoweredInventory-1.12-2.3.1.jar                | None                                     |\n\t| UCH   | neat              | 1.4-17       | {血条}Neat+1.4-17.jar                                      | None                                     |\n\t| UCH   | phosphor-lighting | 1.12.2-0.1.6 | phosphor-1.12.2-0.1.6+build31.jar                        | f0387d288626cc2d937daa504e74af570c52a2f1 |\n\t| UCH   | orelib            | 3.5.2.2      | {环境前置}OreLib-1.12.2-3.5.2.2.jar                          | 7a2128d395ad96ceb9d9030fbd41d035b435753a |\n\t| UCH   | dsurround         | 3.5.4.3      | {环境}DynamicSurroundings-1.12.2-3.5.4.3.jar               | 7a2128d395ad96ceb9d9030fbd41d035b435753a |\n\t| UCH   | dshuds            | 3.5.4.0      | {环境附属}DynamicSurroundingsHuds-1.12.2-3.5.4.0BETA.jar     | 7a2128d395ad96ceb9d9030fbd41d035b435753a |\n\n\tLoaded coremods (and transformers):\nCreativePatchingLoader ({扶人前置}CreativeCore_v1.9.45_mc1.12.2.jar)\n\nBetterFoliageLoader ({树叶}BetterFoliage-MC1.12-2.2.0.jar)\n  mods.betterfoliage.loader.BetterFoliageTransformer\nForgelinPlugin ({生命上限前置}Forgelin-1.8.2.jar)\n\nPhosphorFMLLoadingPlugin (phosphor-1.12.2-0.1.6+build31.jar)\n\nDynamicSurroundingsCore (DynamicSurroundings-core-1.12.2-3.5.4.3.jar)\n  org.orecruncher.dsurround.asm.Transformer\n\tGL info: ' Vendor: 'NVIDIA Corporation' Version: '4.5.0 NVIDIA 376.53' Renderer: 'GeForce GTX 960/PCIe/SSE2'\n\tLaunched Version: HMCL 3.1.77\n\tLWJGL: 2.9.4\n\tOpenGL: GeForce GTX 960/PCIe/SSE2 GL version 4.5.0 NVIDIA 376.53, NVIDIA Corporation\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing GL 1.3 texture combiners.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nShaders are available because OpenGL 2.1 is supported.\nVBOs are available because OpenGL 1.5 is supported.\n\n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs:\n\tCurrent Language: 简体中文 (中国)\n\tProfiler Position: N/A (disabled)\n\tCPU: 8x Intel(R) Xeon(R) CPU E3-1231 v3 @ 3.40GHz\n\tOptiFine Version: OptiFine_1.12.2_HD_U_E4_pre4\n\tOptiFine Build: 20190410-123819\n\tRender Distance Chunks: 12\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.5.0 NVIDIA 376.53\n\tOpenGlRenderer: GeForce GTX 960/PCIe/SSE2\n\tOpenGlVendor: NVIDIA Corporation\n\tCpuCount: 8"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/twilightforest.txt",
    "content": "---- Minecraft Crash Report ----\n// Hey, that tickles! Hehehe!\n\nTime: 3/10/19 8:14 PM\nDescription: Exception in server tick loop\n\njava.lang.NullPointerException: Exception in server tick loop\n\tat net.minecraft.world.chunk.storage.ExtendedBlockStorage.func_76657_c(ExtendedBlockStorage.java:145)\n\tat net.minecraft.world.chunk.Chunk.func_76603_b(Chunk.java:332)\n\tat net.minecraft.world.chunk.Chunk.func_150807_a(Chunk.java:682)\n\tat net.minecraft.world.World.func_147465_d(World.java:812)\n\tat net.minecraft.world.gen.feature.WorldGenerator.func_150516_a(SourceFile:35)\n\tat twilightforest.world.TFTreeGenerator.setBlockAndMetadata(TFTreeGenerator.java:116)\n\tat twilightforest.world.TFTreeGenerator.putLeafBlock(TFTreeGenerator.java:253)\n\tat twilightforest.world.TFTreeGenerator.makeLeafCircle(TFTreeGenerator.java:167)\n\tat twilightforest.world.TFGenCanopyTree.buildBranch(TFGenCanopyTree.java:139)\n\tat twilightforest.world.TFGenCanopyTree.func_76484_a(TFGenCanopyTree.java:69)\n\tat twilightforest.biomes.TFBiomeDecorator.func_150513_a(TFBiomeDecorator.java:173)\n\tat net.minecraft.world.biome.BiomeDecorator.func_150512_a(BiomeDecorator.java:114)\n\tat twilightforest.biomes.TFBiomeDecorator.func_150512_a(TFBiomeDecorator.java:139)\n\tat net.minecraft.world.biome.BiomeGenBase.func_76728_a(BiomeGenBase.java:339)\n\tat twilightforest.biomes.TFBiomeFireflyForest.func_76728_a(TFBiomeFireflyForest.java:93)\n\tat twilightforest.world.ChunkProviderTwilightForest.func_73153_a(ChunkProviderTwilightForest.java:977)\n\tat net.minecraft.world.gen.ChunkProviderServer.func_73153_a(ChunkProviderServer.java:419)\n\tat net.minecraft.world.chunk.Chunk.func_76624_a(Chunk.java:1187)\n\tat net.minecraft.world.gen.ChunkProviderServer.originalLoadChunk(ChunkProviderServer.java:303)\n\tat kcauldron.ChunkGenerator.internalGenerate(ChunkGenerator.java:71)\n\tat kcauldron.ChunkGenerator.chunkGeneratorCycle(ChunkGenerator.java:50)\n\tat cpw.mods.fml.common.FMLCommonHandler.onPostServerTick(FMLCommonHandler.java:252)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:862)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:669)\n\tat java.lang.Thread.run(Thread.java:748)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tKCauldron Version: pw.prok:KCauldron:1.7.10-1614.201 Official\n\tPlugins: Intensify3, ItemDurability, PlaceholderAPI, GroupManager, Block-That-Command, BetterLottery2, AsyncKeepAlive, WorldCommandBlock, Dantiao, ItemTime, GuoItemLoreCommand, NoInfiniteItem, BanItem, Yum, AT, LWCField, AntiCraft, TimeLock, FrogsPluginLib, CoreProtect, WorldEdit, ChunkFixer, Essentials, Promotion, AgarthaLib, CrackShot, LaggRemover, NoBonemealCheat, PoorChunkGenerator, NoSpawnChunks, AntiRedStone, AnitEnch, EssentialsChat, iConomy, CommandCode, TreeAssist, Anti-Xray, CustomJoinItems, Vault, Lores, PlotSquared, LWC, QuickShop, PlayerPoints, MultiWorld, EasyKitsRel, ChestCommands, EssentialsProtect, EssentialsAntiBuild, CustomArrow, RandomLocation, BossShop, EssentialsSpawn, Multiverse-Core, InfoBoardReborn, Residence, CrazyAuctions, AuthMe, Scavenger, SignShop, LockettePro, ProtocolLib, NeverLag, HolographicDisplays, RPG_Items, ColorMOTD, NameTags, SQLibrary\n\tDisabled Plugins:\n\tOperating System: Linux (amd64) version 2.6.32-754.el6.x86_64\n\tJava Version: 1.8.0_202, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 4647461320 bytes (4432 MB) / 6141509632 bytes (5857 MB) up to 6141509632 bytes (5857 MB)\n\tJVM Flags: 2 total; -Xmx6000M -Xms6000M\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 37, allocated: 0, tallocated: 0\n\tFML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1614 29 mods loaded, 29 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJAAAA\tmcp{9.05} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJAAAA\tFML{7.10.99.99} [Forge Mod Loader] (KCauldron-1.7.10-1614.201.jar)\n\tUCHIJAAAA\tForge{10.13.4.1614} [Minecraft Forge] (KCauldron-1.7.10-1614.201.jar)\n\tUCHIJAAAA\tkimagine{0.2} [KImagine] (minecraft.jar)\n\tUCHIJAAAA\tercclasstransform{1.0} [ERCClassTransform] (minecraft.jar)\n\tUCHIJAAAA\tmod_ThreadedLighting{1.7.10-1.0} [Threaded Lighting] (minecraft.jar)\n\tUCHIJAAAA\tgilded-games-util{1.7.10-1.2} [Gilded Games Utility] ([前置]-games-util-1.7.10-1.9.jar)\n\tUCHIJAAAA\tswords{1.0} [Mo' Swords Mod] (1.7.10 更多剑2汉化.jar)\n\tUCHIJAAAA\tcustomnpcs{1.7.10d} [CustomNpcs] ([NPC]CustomNPCs_1.7.10d(19jun17).jar)\n\tUCHIJAAAA\tpozo{4.0.0} [pozo] ([RPG贴图版]pozo.jar)\n\tUCHIJAAAA\taether{1.7.10-1.6} [Aether II] ([以太2]aether-1.7.10-1.6.jar)\n\tUCHIJAAAA\tUnsheathe{1.7.6} [Unsheathe] ([利刃出鞘]Sword+Unsheathe-1.7.10-1.7.2-v2.jar)\n\tUCHIJAAAA\teasc{2.0.0} [EPicSword] ([史诗的剑]EPicSword-2.0.0.jar)\n\tUCHIJAAAA\tTwilightForest{2.3.7} [The Twilight Forest] ([暮色]twilightforest-1.7.10-2.3.7.jar)\n\tUCHIJAAAA\tMoreBows2{1.2} [More Bows 2] ([更多弓2MOD]More Bows 2[1.7.10].jar)\n\tUCHIJAAAA\tMSM3{3.0.0a} [More Swords 3] ([更多的剑]MSM-SNAP-3.0.0b-For-MC-1.7.10.jar)\n\tUCHIJAAAA\tIronChest{6.0.62.742} [Iron Chest] ([更多箱子]ironchest-1.7.10-6.0.62.742-universal.jar)\n\tUCHIJAAAA\tbrk{1.1} [Bedrocker] ([更多装备][1.7.10]VanillaLife-1.0.jar)\n\tUCHIJAAAA\tmod_ecru_MapleTree{1.1.33k} [MapleTree] ([枫树]1.7.10 MapleTree Forge v1.1.33k.jar)\n\tUCHIJAAAA\tabyssalcraft{1.9.1.3} [AbyssalCraft] ([深渊国度]AbyssalCraft-1.7.10-1.9.1.3-FINAL.jar)\n\tUCHIJAAAA\tDP_SimpleFlight{0.8} [Simple Flight] ([简单飞行]MoresFers.jar)\n\tUCHIJAAAA\tcandycraftmod{Beta 1.3} [CandyCraft] ([糖果世界]CandyCraft-1.3.jar)\n\tUCHIJAAAA\tPTRModelLib{1.0.0} [PTRModelLib] ([装饰品MOD]Decocraft-2.3.6.1_1.7.10.jar)\n\tUCHIJAAAA\tprops{2.3.6.1} [Decocraft] ([装饰品MOD]Decocraft-2.3.6.1_1.7.10.jar)\n\tUCHIJAAAA\tMovingWorld{1.7.10-1.8.1} [Moving World] ([达芬奇的船]movingworld-1.7.10-1.8.1.jar)\n\tUCHIJAAAA\tArchimedesShipsPlus{1.7.10-1.8.1} [Archimedes' Ships Plus] ([达芬奇的船]archimedesshipsplus-1.7.10-1.8.1.jar)\n\tUCHIJAAAA\terc{1.41} [Ex Roller Coaster] ([过山车]Ex-RollerCoaster_ver141-EN.jar)\n\tUCHIJAAAA\tMantle{1.7.10-0.3.2.jenkins184} [Mantle] (匠魂手册Mantle-1.7.10-0.3.2.jar)\n\tUCHIJAAAA\tTConstruct{1.7.10-1.8.7.build979} [Tinkers' Construct] (匠魂本体TConstruct-1.7.10-1.8.7.jar)\n\tMantle Environment: DO NOT REPORT THIS CRASH! Unsupported mods in environment: bukkit\n\tTConstruct Environment: Environment healthy.\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tPlayer Count: 4 / 60; [EntityPlayerMP['HeiDaDa123'/3159, l='DIM7', x=-891.70, y=72.18, z=87.28](HeiDaDa123 at -891.6952613619229,72.17500004804162,87.27907399088143), EntityPlayerMP['sqsqsq'/72, l='DIM7', x=-7522.10, y=35.75, z=-1582.04](sqsqsq at -7522.099572113595,35.7531999805212,-1582.0376475244655), EntityPlayerMP['ccwccw'/797, l='DIM7', x=-1479.99, y=31.00, z=1291.34](ccwccw at -1479.9931065943993,31.0,1291.3350309952402), EntityPlayerMP['xuanmoliluo'/1490, l='world', x=-2010.10, y=65.20, z=-1950.03](xuanmoliluo at -2010.1033977670575,65.20214707782915,-1950.0279298470477)]\n\tIs Modded: Definitely; Server brand changed to 'kcauldron,cauldron,craftbukkit,mcpc,fml,forge'\n\tType: Dedicated Server (map_server.txt)"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/twilightforest_optifine_incompatibility.txt",
    "content": "---- Minecraft Crash Report ----\n// Everything's going to plan. No, really, that was supposed to happen.\n\nTime: 2021/10/18 下午9:15\nDescription: Unexpected error\n\njava.lang.IllegalArgumentException: (256, 0) outside of image bounds (256, 8192)\n\tat net.minecraft.client.renderer.texture.NativeImage.func_195709_a(NativeImage.java:261) ~[?:?] {re:computing_frames,xf:OptiFine:default,re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureAtlasSprite$InterpolationData.func_229259_a_(TextureAtlasSprite.java:687) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureAtlasSprite$InterpolationData.func_229257_a_(TextureAtlasSprite.java:672) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureAtlasSprite$InterpolationData.access$800(TextureAtlasSprite.java:623) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureAtlasSprite.func_94219_l(TextureAtlasSprite.java:532) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.AtlasTexture.func_94248_c(AtlasTexture.java:522) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.AtlasTexture.func_110550_d(AtlasTexture.java:635) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureManager.func_110550_d(TextureManager.java:214) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.Minecraft.func_71407_l(Minecraft.java:1431) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.func_195542_b(Minecraft.java:953) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:607) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.main(Main.java:184) ~[1.16.5.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] {}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?] {}\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] {}\n\tat java.lang.reflect.Method.invoke(Method.java:568) ~[?:?] {}\n\tat net.minecraftforge.fml.loading.FMLClientLaunchProvider.lambda$launchService$0(FMLClientLaunchProvider.java:51) ~[forge-1.16.5-36.2.8.jar:36.2] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:82) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66) [modlauncher-8.0.9.jar:?] {}\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Render thread\nStacktrace:\n\tat net.minecraft.client.renderer.texture.NativeImage.func_195709_a(NativeImage.java:261) ~[?:?] {re:computing_frames,xf:OptiFine:default,re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureAtlasSprite$InterpolationData.func_229259_a_(TextureAtlasSprite.java:687) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureAtlasSprite$InterpolationData.func_229257_a_(TextureAtlasSprite.java:672) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureAtlasSprite$InterpolationData.access$800(TextureAtlasSprite.java:623) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureAtlasSprite.func_94219_l(TextureAtlasSprite.java:532) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.AtlasTexture.func_94248_c(AtlasTexture.java:522) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.AtlasTexture.func_110550_d(AtlasTexture.java:635) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.texture.TextureManager.func_110550_d(TextureManager.java:214) ~[?:?] {re:classloading,xf:OptiFine:default}\n-- Affected level --\nDetails:\n\tAll players: 1 total; [ClientPlayerEntity['Grumm'/237, l='ClientLevel', x=-1.50, y=64.00, z=51.50]]\n\tChunk stats: Client Chunk Cache: 441, 289\n\tLevel dimension: minecraft:overworld\n\tLevel spawn location: World: (-11,63,44), Chunk: (at 5,3,12 in -1,2; contains blocks -16,0,32 to -1,255,47), Region: (-1,0; contains chunks -32,0 to -1,31, blocks -512,0,0 to -1,255,511)\n\tLevel time: 83 game time, 83 day time\n\tServer brand: forge\n\tServer type: Integrated singleplayer server\nStacktrace:\n\tat net.minecraft.client.world.ClientWorld.func_72914_a(ClientWorld.java:617) ~[?:?] {re:classloading,pl:accesstransformer:B,xf:OptiFine:default}\n\tat net.minecraft.client.Minecraft.func_71396_d(Minecraft.java:2029) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:628) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.main(Main.java:184) ~[1.16.5.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] {}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?] {}\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] {}\n\tat java.lang.reflect.Method.invoke(Method.java:568) ~[?:?] {}\n\tat net.minecraftforge.fml.loading.FMLClientLaunchProvider.lambda$launchService$0(FMLClientLaunchProvider.java:51) ~[forge-1.16.5-36.2.8.jar:36.2] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:82) [modlauncher-8.0.9.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66) [modlauncher-8.0.9.jar:?] {}\n\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.16.5\n\tMinecraft Version ID: 1.16.5\n\tOperating System: Windows 7 (amd64) version 6.1\n\tJava Version: 17, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode, sharing), Oracle Corporation\n\tMemory: 1812234600 bytes (1728 MB) / 2634022912 bytes (2512 MB) up to 3003121664 bytes (2864 MB)\n\tCPUs: 4\n\tJVM Flags: 13 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -Xmn128m -Xmx2860m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -XX:+IgnoreUnrecognizedVMOptions\n\tModLauncher: 8.0.9+86+master.3cf110c\n\tModLauncher launch target: fmlclient\n\tModLauncher naming: srg\n\tModLauncher services:\n\t\t/mixin-0.8.4.jar mixin PLUGINSERVICE\n\t\t/eventbus-4.0.0.jar eventbus PLUGINSERVICE\n\t\t/forge-1.16.5-36.2.8.jar object_holder_definalize PLUGINSERVICE\n\t\t/forge-1.16.5-36.2.8.jar runtime_enum_extender PLUGINSERVICE\n\t\t/forge-1.16.5-36.2.8.jar capability_inject_definalize PLUGINSERVICE\n\t\t/accesstransformers-3.0.1.jar accesstransformer PLUGINSERVICE\n\t\t/forge-1.16.5-36.2.8.jar runtimedistcleaner PLUGINSERVICE\n\t\t/mixin-0.8.4.jar mixin TRANSFORMATIONSERVICE\n\t\t/OptiFine-1.16.5_HD_U_G8.jar OptiFine TRANSFORMATIONSERVICE\n\t\t/forge-1.16.5-36.2.8.jar fml TRANSFORMATIONSERVICE\n\tFML: 36.2\n\tForge: net.minecraftforge:36.2.8\n\tFML Language Providers:\n\t\tjavafml@36.2\n\t\tminecraft@1\n\tMod List:\n\t\tforge-1.16.5-36.2.8-client.jar                    |Minecraft                     |minecraft                     |1.16.5              |DONE      |Manifest: NOSIGNATURE\n\t\tforge-1.16.5-36.2.8-universal.jar                 |Forge                         |forge                         |36.2.8              |DONE      |Manifest: 22:af:21:d8:19:82:7f:93:94:fe:2b:ac:b7:e4:41:57:68:39:87:b1:a7:5c:c6:44:f9:25:74:21:14:f5:0d:90\n\t\ttwilightforest-1.16.5-4.0.546-universal.jar       |The Twilight Forest           |twilightforest                |NONE                |DONE      |Manifest: NOSIGNATURE\n\t\tPatchouli-1.16.4-53.2.jar                         |Patchouli                     |patchouli                     |1.16.4-53.2         |DONE      |Manifest: NOSIGNATURE\n\t\tCTM-MC1.16.1-1.1.2.6.jar                          |ConnectedTexturesMod          |ctm                           |MC1.16.1-1.1.2.6    |DONE      |Manifest: NOSIGNATURE\n\tCrash Report UUID: 7d82c941-656a-4506-a362-3426ed4c15f8\n\tPatchouli open book context: n/a\n\tLaunched Version: 1.16.5\n\tBackend library: LWJGL version 3.2.2 build 10\n\tBackend API: Intel(R) UHD Graphics 610 GL version 4.4.0 - Build 21.20.16.5164, Intel\n\tGL Caps: Using framebuffer using OpenGL 3.0\n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'forge'\n\tType: Client (map_client.txt)\n\tGraphics mode: fancy\n\tResource Packs: mod_resources, vanilla, programer_art, twilightforest:classic_textures, file/mcwzh-meme.f36647c.zip\n\tCurrent Language: 梗体中文 (天朝)\n\tCPU: 4x Intel(R) Pentium(R) Gold G5420 CPU @ 3.80GHz\n\tOptiFine Version: OptiFine_1.16.5_HD_U_G8\n\tOptiFine Build: 20210515-161946\n\tRender Distance Chunks: 8\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: (internal)\n\tOpenGlVersion: 4.4.0 - Build 21.20.16.5164\n\tOpenGlRenderer: Intel(R) UHD Graphics 610\n\tOpenGlVendor: Intel\n\tCpuCount: 4"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod/wizardry.txt",
    "content": "---- Minecraft Crash Report ----\n// Ouch. That hurt :(\n\nTime: 1/22/19 5:35 AM\nDescription: Exception in server tick loop\n\nio.netty.handler.codec.EncoderException: java.lang.NullPointerException\n\tat io.netty.handler.codec.MessageToMessageEncoder.write(MessageToMessageEncoder.java:107)\n\tat io.netty.handler.codec.MessageToMessageCodec.write(MessageToMessageCodec.java:116)\n\tat io.netty.channel.DefaultChannelHandlerContext.invokeWrite(DefaultChannelHandlerContext.java:644)\n\tat io.netty.channel.DefaultChannelHandlerContext.write(DefaultChannelHandlerContext.java:698)\n\tat io.netty.channel.DefaultChannelHandlerContext.write(DefaultChannelHandlerContext.java:637)\n\tat io.netty.channel.DefaultChannelHandlerContext.write(DefaultChannelHandlerContext.java:626)\n\tat io.netty.channel.DefaultChannelPipeline.write(DefaultChannelPipeline.java:878)\n\tat io.netty.channel.AbstractChannel.write(AbstractChannel.java:229)\n\tat io.netty.channel.embedded.EmbeddedChannel.writeOutbound(EmbeddedChannel.java:195)\n\tat cpw.mods.fml.common.network.FMLEmbeddedChannel.generatePacketFrom(FMLEmbeddedChannel.java:48)\n\tat cpw.mods.fml.common.network.internal.FMLNetworkHandler.getEntitySpawningPacket(FMLNetworkHandler.java:158)\n\tat net.minecraft.entity.EntityTrackerEntry.func_151260_c(EntityTrackerEntry.java:570)\n\tat net.minecraft.entity.EntityTrackerEntry.func_73117_b(EntityTrackerEntry.java:433)\n\tat net.minecraft.entity.EntityTracker.func_72788_a(EntityTracker.java:296)\n\tat net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:975)\n\tat net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:458)\n\tat net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:806)\n\tat net.minecraft.server.MinecraftServer.run(MinecraftServer.java:665)\n\tat java.lang.Thread.run(Unknown Source)\nCaused by: java.lang.NullPointerException\n\tat electroblob.wizardry.entity.projectile.EntitySparkBomb.writeSpawnData(EntitySparkBomb.java:123)\n\tat cpw.mods.fml.common.network.internal.FMLMessage$EntitySpawnMessage.toBytes(FMLMessage.java:230)\n\tat cpw.mods.fml.common.network.internal.FMLRuntimeCodec.encodeInto(FMLRuntimeCodec.java:22)\n\tat cpw.mods.fml.common.network.internal.FMLRuntimeCodec.encodeInto(FMLRuntimeCodec.java:11)\n\tat cpw.mods.fml.common.network.FMLIndexedMessageToMessageCodec.encode(FMLIndexedMessageToMessageCodec.java:51)\n\tat io.netty.handler.codec.MessageToMessageCodec$1.encode(MessageToMessageCodec.java:67)\n\tat io.netty.handler.codec.MessageToMessageEncoder.write(MessageToMessageEncoder.java:89)\n\t... 18 more\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tKCauldron Version: cc.uraniummc:Uranium:1710-dev-4-B210 UNOFFICIAL DON'T REPORT THIS CRASH\n\tPlugins: ItemDurability, ILSRepair, PlaceholderAPI, GroupManager, TigerSigns, tpLogin, ConsoleSpamFix, HyperFlyCheck, CloudTrade, WorldProtect, NoSellLoreItemToGlobalShop, NoCmds, BanItem, PointShop, EasyAPI, PreFixGui, VexView, RichAutoMessage, ClickLimit, ILSAddonOrnament, Deadbolt, WorldEdit, FastRespawn, Essentials, EasyCommand, TimingsPatcher, uSkyBlock, CraftGuard, fixILS, VexInfoBar, ProtocolLib, Multiverse-Core, MCserverManager, LevelChat, EssentialsChat, GlobalMarket, iConomy, StarLogin, Vault, SFWSupport, Lores, ScriptBlock, LR-ActionBarMessage, VexKeyBoardHelper, NeverLag, HolographicDisplays, OnlineMoney, PlayerPoints, WorldGuard, ItemLoreStats, ChestCommands, EssentialsProtect, EssentialsAntiBuild, JoinMessage, VexCraftTable, EssentialsSpawn, RPG_Items, ColorMOTD, HamsterAPI, Lottery\n\tDisabled Plugins:\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_101, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 1365178232 bytes (1301 MB) / 2848456704 bytes (2716 MB) up to 27030192128 bytes (25778 MB)\n\tJVM Flags: 4 total; -Xms512m -Xmx29000m -XX:+AggressiveOpts -XX:+UseCompressedOops\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1614 42 mods loaded, 42 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJAAAA\tmcp{9.05} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJAAAA\tFML{7.10.99.99} [Forge Mod Loader] (Server.jar)\n\tUCHIJAAAA\tForge{10.13.4.1614} [Minecraft Forge] (Server.jar)\n\tUCHIJAAAA\tkimagine{0.2} [KImagine] (minecraft.jar)\n\tUCHIJAAAA\tUraniumPlus{${UMP_VER}} [Added title and actionbar support for client and server] (Server.jar)\n\tUCHIJAAAA\tcreativecore{1.3.14} [CreativeCore] (-在线图片加载.jar)\n\tUCHIJAAAA\topframe{2.1} [OnlinePictureFrame] (-在线图片加载前置.jar)\n\tUCHIJAAAA\tBlock3DPixelMc{1.7.x} [PokemonGo-Block] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tMod_bag{1.7.x} [PokemonGo-Bag] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tpixelmcrop{1.7.10} [PokemonGo-Crop] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tMod_Wing{1.7.x} [PokemonGo-Wing] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tshopy{1.7.x} [PokemonGo-Shop] ([更多装备道具]pokemongoitem.jar)\n\tUCHIJAAAA\tbikesystem{1.0} [bikesystem] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tBANK-ONEPIECE BLOCK3D{Takakung} [Takakung BLOCK3D] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tchillingmoneysystem{1.0} [ChillingMoneySystem] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tPIXEL-STATION{Takakung} [PIXEL-STATION] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tPIXEL-STATION 3D{Takakung} [PIXEL-STATION 3D] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tshopplayers{1.7.10} [CubeMMOShop] ([更多装备道具]WtfWhateveritems.jar)\n\tUCHIJAAAA\tGrimoireOfGaia{1.0.0} [Grimoire of Gaia 3] ([魔典盖亚III]1.7.10-1.2.7.jar)\n\tUCHIJAAAA\tChestTransporter{2.0.6} [Chest Transporter] (ChestTransporter-1.7.10-2.0.6.jar)\n\tUCHIJAAAA\tcustommc{1.0v} [custommc] (CustomMc.jar)\n\tUCHIJAAAA\tgxozb{0.0.0.1} [Minecreft Not Enough Items] (gxozb-1.0(3).jar)\n\tUCHIJAAAA\tlycanitesmobs{1.9.0e - MC 1.7.10} [Lycanites Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tarcticmobs{1.9.0e - MC 1.7.10} [Lycanites Arctic Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tdemonmobs{1.9.0e - MC 1.7.10} [Lycanites Demon Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tdesertmobs{1.9.0e - MC 1.7.10} [Lycanites Desert Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tforestmobs{1.9.0e - MC 1.7.10} [Lycanites Forest Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tfreshwatermobs{1.9.0e - MC 1.7.10} [Lycanites Freshwater Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tinfernomobs{1.9.0e - MC 1.7.10} [Lycanites Inferno Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tjunglemobs{1.9.0e - MC 1.7.10} [Lycanites Jungle Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tmountainmobs{1.9.0e - MC 1.7.10} [Lycanites Mountain Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tplainsmobs{1.9.0e - MC 1.7.10} [Lycanites Plains Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tsaltwatermobs{1.9.0e - MC 1.7.10} [Lycanites Saltwater Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\tswampmobs{1.9.0e - MC 1.7.10} [Lycanites Swamp Mobs] (Mcmap.cc-[恐怖生物汉化版][1.7.10].jar)\n\tUCHIJAAAA\twizardry{1.1.4} [Electroblob's Wizardry] (mofa.jar)\n\tUCHIJAAAA\tNBTEdit{1.7.2.2} [In-game NBTEdit] (NBTEdit_1.7.10.jar)\n\tUCHIJAAAA\tpozo{4.0.0} [pozo] (pozo全贴图版.jar)\n\tUCHIJAAAA\tYoHern{2.1.0} [YoHern] (古衍秘制.jar)\n\tUCHIJAAAA\tarmourersWorkshop{1.7.10-0.45.0} [Armourer's Workshop] (时装工坊-1.7.10-0.45.0.jar)\n\tUCHIJAAAA\tplushieWrapper{0.0.0} [Plushie Wrapper] (时装工坊-1.7.10-0.45.0.jar)\n\tUCHIJAAAA\tnewnpc{1.0.0} [NewNpc] (炽焰改-贴图Mod1.7.10.jar)\n\tUCHIJAAAA\tcustomnpcs{1.7.10d} [CustomNpcs] (自定义NPC(1).jar)\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tPlayer Count: 2 / 888; [EntityPlayerMP['chiyan_C'/9, l='px', x=-330.90, y=57.92, z=1112.15](chiyan_C at -330.8975918600241,57.92458305075876,1112.1469986025677), EntityPlayerMP['Surii'/56, l='px', x=-299.23, y=57.02, z=1120.27](Surii at -299.2297200833873,57.015555072702206,1120.274395758027)]\n\tIs Modded: Definitely; Server brand changed to 'uranium,kcauldron,cauldron,craftbukkit,mcpc,fml,forge'\n\tType: Dedicated Server (map_server.txt)"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/mod_resolution0.txt",
    "content": "---- Minecraft Crash Report ----\n// Surprise! Haha. Well, this is awkward.\n\nTime: 2022/6/7 下午8:17\nDescription: Mod loading error has occurred\n\njava.lang.Exception: Mod Loading has failed\n        at net.minecraftforge.fml.CrashReportExtender.dumpModLoadingCrashReport(CrashReportExtender.java:86) ~[forge:?] {re:classloading}\n        at net.minecraftforge.fml.server.ServerModLoader.load(ServerModLoader.java:51) ~[forge:?] {re:classloading}\n        at net.minecraft.server.Main.main(Main.java:145) ~[?:?] {re:classloading}\n        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] {}\n        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?] {}\n        at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] {}\n        at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?] {}\n        at net.minecraftforge.fml.loading.FMLServerLaunchProvider.lambda$launchService$0(FMLServerLaunchProvider.java:51) ~[mohist-1.16.5-1021-server.jar:36.2] {}\n        at cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) ~[modlauncher-8.1.3.jar:?] {}\n        at cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54) ~[modlauncher-8.1.3.jar:?] {}\n        at cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72) ~[modlauncher-8.1.3.jar:?] {}\n        at cpw.mods.modlauncher.Launcher.run(Launcher.java:82) ~[modlauncher-8.1.3.jar:?] {}\n        at cpw.mods.modlauncher.Launcher.main(Launcher.java:66) ~[modlauncher-8.1.3.jar:?] {}\n        at net.minecraftforge.server.ServerMain$Runner.runLauncher(ServerMain.java:119) ~[mohist-1.16.5-1021-server.jar:?] {}\n        at net.minecraftforge.server.ServerMain$Runner.access$100(ServerMain.java:116) ~[mohist-1.16.5-1021-server.jar:?] {}\n        at net.minecraftforge.server.ServerMain.main(ServerMain.java:100) ~[mohist-1.16.5-1021-server.jar:?] {re:classloading,re:classloading}\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: main\nStacktrace:\n        at net.minecraftforge.fml.CrashReportExtender.lambda$dumpModLoadingCrashReport$7(CrashReportExtender.java:89) ~[forge:?] {re:classloading}\n-- MOD iceandfire --\nDetails:\n        Mod File: iceandfire-2.1.9-1.16.5.jar\n        Failure message: Mod iceandfire requires citadel 1.8.1 or above\n                Currently, citadel is not installed\n        Mod Version: 2.1.9-1.16.5\n        Mod Issue URL: https://github.com/Alex-the-666/ice-and-fire/issues\n        Exception message: MISSING EXCEPTION MESSAGE\nStacktrace:\n        at net.minecraftforge.fml.CrashReportExtender.lambda$dumpModLoadingCrashReport$7(CrashReportExtender.java:89) ~[forge:?] {re:classloading}\n        at java.util.ArrayList.forEach(ArrayList.java:1511) ~[?:?] {}\n        at net.minecraftforge.fml.CrashReportExtender.dumpModLoadingCrashReport(CrashReportExtender.java:87) ~[forge:?] {re:classloading}\n        at net.minecraftforge.fml.server.ServerModLoader.load(ServerModLoader.java:51) ~[forge:?] {re:classloading}\n        at net.minecraft.server.Main.main(Main.java:145) ~[?:?] {re:classloading}\n        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] {}\n        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?] {}\n        at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] {}\n        at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?] {}\n        at net.minecraftforge.fml.loading.FMLServerLaunchProvider.lambda$launchService$0(FMLServerLaunchProvider.java:51) ~[mohist-1.16.5-1021-server.jar:36.2] {}\n        at cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) ~[modlauncher-8.1.3.jar:?] {}\n        at cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54) ~[modlauncher-8.1.3.jar:?] {}\n        at cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72) ~[modlauncher-8.1.3.jar:?] {}\n        at cpw.mods.modlauncher.Launcher.run(Launcher.java:82) ~[modlauncher-8.1.3.jar:?] {}\n        at cpw.mods.modlauncher.Launcher.main(Launcher.java:66) ~[modlauncher-8.1.3.jar:?] {}\n        at net.minecraftforge.server.ServerMain$Runner.runLauncher(ServerMain.java:119) ~[mohist-1.16.5-1021-server.jar:?] {}\n        at net.minecraftforge.server.ServerMain$Runner.access$100(ServerMain.java:116) ~[mohist-1.16.5-1021-server.jar:?] {}\n        at net.minecraftforge.server.ServerMain.main(ServerMain.java:100) ~[mohist-1.16.5-1021-server.jar:?] {re:classloading,re:classloading,re:classloading}\n\n\n-- System Details --\nDetails:\n        Minecraft Version: 1.16.5\n        Minecraft Version ID: 1.16.5\n        Mohist Version: 1.16.5-1021\n        Operating System: Windows 10 (amd64) version 10.0\n        Java Version: 17.0.1, Oracle Corporation\n        Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode, sharing), Oracle Corporation\n        Memory: 596423192 bytes (568 MB) / 1233125376 bytes (1176 MB) up to 8589934592 bytes (8192 MB)\n        CPUs: 8\n        JVM Flags: 1 total; -Xmx8192M\n        ModLauncher: 8.1.3+8.1.3+main-8.1.x.c94d18ec\n        ModLauncher启动目标: fmlserver\n        ModLauncher命名: srg\n        ModLauncher服务:\n                /mixin-0.8.4.jar mixin PLUGINSERVICE\n                /eventbus-4.0.0.jar eventbus PLUGINSERVICE\n                /mohist-1.16.5-1021-server.jar object_holder_definalize PLUGINSERVICE\n                /mohist-1.16.5-1021-server.jar runtime_enum_extender PLUGINSERVICE\n                /mohist-1.16.5-1021-server.jar capability_inject_definalize PLUGINSERVICE\n                /accesstransformers-3.0.1.jar accesstransformer PLUGINSERVICE\n                /mohist-1.16.5-1021-server.jar runtimedistcleaner PLUGINSERVICE\n                /mixin-0.8.4.jar mixin TRANSFORMATIONSERVICE\n                /mohist-1.16.5-1021-server.jar fml TRANSFORMATIONSERVICE\n        FML: 36.2\n        Forge: com.mohistmc:36.2.39\n        FML语言提供:\n                javafml@36.2\n                minecraft@1\n        Mod List:\n                mohist-1.16.5-1021-server.jar                     |Minecraft                     |minecraft                     |1.16.5              |NONE      |Manifest: NOSIGNATURE\n                mohist-1.16.5-1021-universal.jar                  |Forge                         |forge                         |36.2.39             |NONE      |Manifest: NOSIGNATURE\n        CraftBukkit Information:\n   Running:\n   Failed to handle CraftCrashReport: craftbukkit not runs"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/need_jdk11.txt",
    "content": "2023-06-15 15:54:15,412 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND\n[15:54:15] [main/INFO]: ModLauncher running: args [--username, MarkHairo, --version, XPlus OptiReady based on Minecraft 1.16.5 (Forge), --gameDir, C:\\Users\\********\\Desktop\\HMCL-3.5.4.exe\\.minecraft\\versions\\XPlus OptiReady based on Minecraft 1.16.5 (Forge), --assetsDir, C:\\Users\\********\\Desktop\\HMCL-3.5.4.exe\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 49077709e6763dd4bde7a33aaf06043e, --accessToken, 鉂勨潉鉂勨潉鉂勨潉鉂勨潉, --userType, legacy, --versionType, HMCL 3.5.4, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.34, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550]\n[15:54:15] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 17.0.7 by Microsoft\n[15:54:15] [main/INFO]: Added Lets Encrypt root certificates as additional trust\n[15:54:15] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/C:/Users/********/Desktop/HMCL-3.5.4.exe/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\nInit ItemPhysicLite coremods ...\nInit CreativeCore coremods ...\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: java.lang.ExceptionInInitializerError\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat org.openjdk.nashorn.internal.runtime.Context.compile(Context.java:1509)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat org.openjdk.nashorn.internal.runtime.Context.compileScript(Context.java:1449)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat org.openjdk.nashorn.internal.runtime.Context.compileScript(Context.java:759)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat org.openjdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:528)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat org.openjdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:517)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat org.openjdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:395)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat org.openjdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:146)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat java.scripting/javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:247)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat net.minecraftforge.coremod.CoreMod.initialize(CoreMod.java:40)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat net.minecraftforge.coremod.CoreModEngine.initialize(CoreModEngine.java:75)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat java.base/java.util.ArrayList.forEach(ArrayList.java:1511)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat net.minecraftforge.coremod.CoreModEngine.initializeCoreMods(CoreModEngine.java:69)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat net.minecraftforge.coremod.CoreModProvider.getCoreModTransformers(CoreModProvider.java:17)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat net.minecraftforge.fml.loading.FMLServiceProvider.transformers(FMLServiceProvider.java:156)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat cpw.mods.modlauncher.TransformationServiceDecorator.gatherTransformers(TransformationServiceDecorator.java:74)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat cpw.mods.modlauncher.TransformationServicesHandler.lambda$initialiseServiceTransformers$6(TransformationServicesHandler.java:101)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat java.base/java.util.HashMap$Values.forEach(HashMap.java:1065)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat cpw.mods.modlauncher.TransformationServicesHandler.initialiseServiceTransformers(TransformationServicesHandler.java:101)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:64)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat cpw.mods.modlauncher.Launcher.run(Launcher.java:76)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: Caused by: java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.lambda$getDefineAnonymousClass$0(Context.java:335)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat java.base/java.security.AccessController.doPrivileged(AccessController.java:318)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.getDefineAnonymousClass(Context.java:327)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.<clinit>(Context.java:317)\n[15:54:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \t... 21 more\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: Caused by: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \tat java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:976)\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \tat java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1117)\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \tat java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:3649)\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \tat java.base/java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:2680)\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \tat org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.lambda$getDefineAnonymousClass$0(Context.java:329)\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \t... 24 more\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: Caused by: java.lang.NoSuchMethodError: 'java.lang.Class sun.misc.Unsafe.defineAnonymousClass(java.lang.Class, byte[], java.lang.Object[])'\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \tat java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method)\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \tat java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1085)\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \tat java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1114)\n[15:54:18] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \t... 27 more\nException in thread \"main\""
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/need_jdk112.txt",
    "content": "2023-06-11 12:15:14,674 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND\n[12:15:14] [main/INFO]: ModLauncher running: args [--username, 520, --version, 榫欎箣鍐掗櫓v3.3a, --gameDir, D:\\versions\\榫欎箣鍐掗櫓v3.3a, --assetsDir, D:\\assets, --assetIndex, 1.16, --uuid, 80a1da362ce83d64be4612a92ee7248e, --accessToken, 鉂勨潉鉂勨潉鉂勨潉鉂勨潉, --userType, legacy, --versionType, HMCL 3.5.4, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.35, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550, --fullscreen]\n[12:15:14] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 1.8.0_371 by Oracle Corporation\n[12:15:15] [main/INFO]: Added Lets Encrypt root certificates as additional trust\n[12:15:15] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/D:/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\n[12:15:16] [main/INFO]: [org.antlr.v4.runtime.ConsoleErrorListener:syntaxError:38]: line 13:0 token recognition error at: '`'\n[12:15:16] [main/INFO]: [org.antlr.v4.runtime.ConsoleErrorListener:syntaxError:38]: line 2:141 token recognition error at: ';'\nInit CreativeCore coremods ...\n[12:15:18] [main/ERROR]: Mixin config modernworldcreation.mixin.json does not specify \"minVersion\" property\n[12:15:18] [main/INFO]: Successfully loaded Mixin Connector [hellfirepvp.astralsorcery.MixinConnector]\n[12:15:18] [main/INFO]: Successfully loaded Mixin Connector [tictim.paraglider.MixinConnector]\n[12:15:18] [main/INFO]: Successfully loaded Mixin Connector [vazkii.patchouli.common.MixinConnector]\n[12:15:18] [main/INFO]: Successfully loaded Mixin Connector [com.integral.enigmaticlegacy.MixinConnector]\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: java.lang.UnsupportedClassVersionError: icyllis/modernui/forge/MixinConnector 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 52.0\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.lang.ClassLoader.defineClass1(Native Method)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.lang.ClassLoader.defineClass(Unknown Source)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformingClassLoader.loadClass(TransformingClassLoader.java:138)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformingClassLoader.loadClass(TransformingClassLoader.java:98)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.lang.ClassLoader.loadClass(Unknown Source)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.lang.Class.forName0(Native Method)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.lang.Class.forName(Unknown Source)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.service.modlauncher.ModLauncherClassProvider.findClass(ModLauncherClassProvider.java:67)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinConnectorManager.loadConnectors(MixinConnectorManager.java:70)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinConnectorManager.inject(MixinConnectorManager.java:59)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinPlatformManager.inject(MixinPlatformManager.java:196)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.MixinBootstrap.inject(MixinBootstrap.java:202)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.MixinLaunchPluginLegacy.initializeLaunch(MixinLaunchPluginLegacy.java:201)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.MixinLaunchPluginLegacy.initializeLaunch(MixinLaunchPluginLegacy.java:195)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchPluginHandler.lambda$announceLaunch$9(LaunchPluginHandler.java:97)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.util.HashMap.forEach(Unknown Source)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchPluginHandler.announceLaunch(LaunchPluginHandler.java:97)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:52)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.run(Launcher.java:82)\n[12:15:18] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)\nException in thread \"main\""
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/need_jdk113.txt",
    "content": "2023-08-30 18:52:46,932 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND\n[18:52:47] [main/INFO]: ModLauncher running: args [--username, zq118, --version, 龙之冒险v3.4, --gameDir, C:\\Users\\张启\\Desktop\\.minecraft\\versions\\龙之冒险v3.4, --assetsDir, C:\\Users\\张启\\Desktop\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 17469630b6544c149f3fb407190c1794, --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, HMCL 3.5.5, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.35, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550]\n[18:52:47] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 1.8.0_341 by Oracle Corporation\n[18:52:47] [main/INFO]: Added Lets Encrypt root certificates as additional trust\n[18:52:47] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/C:/Users/%e5%bc%a0%e5%90%af/Desktop/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\n[18:52:50] [main/INFO]: [org.antlr.v4.runtime.ConsoleErrorListener:syntaxError:38]: line 2:141 token recognition error at: ';'\n[18:52:50] [main/INFO]: [org.antlr.v4.runtime.ConsoleErrorListener:syntaxError:38]: line 13:0 token recognition error at: '`'\nInit CreativeCore coremods ...\n[18:52:51] [main/ERROR]: Mixin config modernworldcreation.mixin.json does not specify \"minVersion\" property\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: org.spongepowered.asm.launch.MixinInitialisationError: Error initialising mixin config opotato.mixins.json\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.mixin.transformer.Config.create(Config.java:153)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.mixin.Mixins.createConfiguration(Mixins.java:100)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.mixin.Mixins.addConfiguration(Mixins.java:87)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinPlatformManager.addConfig(MixinPlatformManager.java:262)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinPlatformAgentDefault.prepare(MixinPlatformAgentDefault.java:46)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinContainer.prepare(MixinContainer.java:122)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinPlatformManager.createContainerFor(MixinPlatformManager.java:144)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinPlatformManager.addContainer(MixinPlatformManager.java:134)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinPlatformManager.addNestedContainers(MixinPlatformManager.java:152)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinPlatformManager.scanForContainers(MixinPlatformManager.java:213)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.platform.MixinPlatformManager.inject(MixinPlatformManager.java:186)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.MixinBootstrap.inject(MixinBootstrap.java:202)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.MixinLaunchPluginLegacy.initializeLaunch(MixinLaunchPluginLegacy.java:201)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.launch.MixinLaunchPluginLegacy.initializeLaunch(MixinLaunchPluginLegacy.java:195)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchPluginHandler.lambda$announceLaunch$9(LaunchPluginHandler.java:97)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.util.HashMap.forEach(Unknown Source)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchPluginHandler.announceLaunch(LaunchPluginHandler.java:97)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:52)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.run(Launcher.java:82)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: Caused by: java.lang.IllegalArgumentException: The requested compatibility level JAVA_11 could not be set. Level is not supported by the active JRE or ASM version (Java 1.8, ASM 9.1 (ASM10_EXPERIMENTAL))\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.mixin.MixinEnvironment.setCompatibilityLevel(MixinEnvironment.java:1570)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinConfig.initCompatibilityLevel(MixinConfig.java:555)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinConfig.postInit(MixinConfig.java:500)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinConfig.onLoad(MixinConfig.java:428)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.mixin.transformer.MixinConfig.create(MixinConfig.java:1285)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.spongepowered.asm.mixin.transformer.Config.create(Config.java:148)\n[18:52:51] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \t... 20 more\nException in thread \"main\" "
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/night_config_fixes.txt",
    "content": "Java HotSpot(TM) 64-Bit Server VM warning: Option --illegal-access is deprecated and will be removed in a future release.\n[22:55:37] [main/INFO]: ModLauncher running: args [--username, fmtt1, --version, 1.17.1, --gameDir, F:\\新我的世界, --assetsDir, F:\\新我的世界\\assets, --assetIndex, 1.17, --uuid, 118884b8837d3e2eaf1d48eb68883f95, --accessToken, ❄❄❄❄❄❄❄❄, --userType, legacy, --versionType, HMCL 3.5.4, --width, 854, --height, 480, --launchTarget, forgeclient, --fml.forgeVersion, 37.1.1, --fml.mcVersion, 1.17.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210706.113038]\n[22:55:37] [main/INFO]: ModLauncher 9.0.7+91+master.8569cdf starting: java version 16.0.1 by Oracle Corporation\n[22:55:37] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=union:/F:/新我的世界/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar%2319! Service=ModLauncher Env=CLIENT\n[22:55:37] [main/INFO]: Found mod file jei-1.17.1-8.3.1.1002.jar of type MOD with locator {mods folder locator at F:\\新我的世界\\mods}\n[22:55:37] [main/INFO]: Found mod file journeymap-1.17.1-5.8.0.jar of type MOD with locator {mods folder locator at F:\\新我的世界\\mods}\n[22:55:37] [main/INFO]: Found mod file fmlcore-1.17.1-37.1.1.jar of type LIBRARY with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@18245eb0\n[22:55:37] [main/INFO]: Found mod file javafmllanguage-1.17.1-37.1.1.jar of type LANGPROVIDER with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@18245eb0\n[22:55:37] [main/INFO]: Found mod file mclanguage-1.17.1-37.1.1.jar of type LANGPROVIDER with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@18245eb0\n[22:55:37] [main/INFO]: Found mod file client-1.17.1-20210706.113038-srg.jar of type MOD with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@18245eb0\n[22:55:37] [main/INFO]: Found mod file forge-1.17.1-37.1.1-universal.jar of type MOD with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@18245eb0\n2023-04-26 22:55:37,463 main WARN Error parsing URI F:\\新我的世界\\versions\\1.17.1\\log4j2.xml\n[22:55:38] [main/INFO]: Launching target 'forgeclient' with arguments [--version, 1.17.1, --gameDir, F:\\新我的世界, --assetsDir, F:\\新我的世界\\assets, --uuid, 118884b8837d3e2eaf1d48eb68883f95, --username, fmtt1, --assetIndex, 1.17, --accessToken, ❄❄❄❄❄❄❄❄, --userType, legacy, --versionType, HMCL 3.5.4, --width, 854, --height, 480]\n2023-04-26 22:55:38,406 main WARN Error parsing URI F:\\新我的世界\\versions\\1.17.1\\log4j2.xml\nSLF4J: No SLF4J providers were found.\nSLF4J: Defaulting to no-operation (NOP) logger implementation\nSLF4J: See http://www.slf4j.org/codes.html#noProviders for further details.\n############ Journeymap Coremod Potion Effects Renderer: Injected ############\n[22:55:43] [Render thread/WARN]: Assets URL 'union:/F:/新我的世界/libraries/net/minecraft/client/1.17.1-20210706.113038/client-1.17.1-20210706.113038-srg.jar%2355!/assets/.mcassetsroot' uses unexpected schema\n[22:55:43] [Render thread/WARN]: Assets URL 'union:/F:/新我的世界/libraries/net/minecraft/client/1.17.1-20210706.113038/client-1.17.1-20210706.113038-srg.jar%2355!/data/.mcassetsroot' uses unexpected schema\n[22:55:43] [Render thread/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD'\n[22:55:44] [Render thread/ERROR]: Failed to verify authentication\ncom.mojang.authlib.exceptions.InvalidCredentialsException: Status: 401\n\tat com.mojang.authlib.exceptions.MinecraftClientHttpException.toAuthenticationException(MinecraftClientHttpException.java:56) ~[authlib-2.3.31.jar%2338!:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilSocialInteractionsService.checkPrivileges(YggdrasilSocialInteractionsService.java:112) ~[authlib-2.3.31.jar%2338!:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilSocialInteractionsService.<init>(YggdrasilSocialInteractionsService.java:42) ~[authlib-2.3.31.jar%2338!:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService.createSocialInteractionsService(YggdrasilAuthenticationService.java:151) ~[authlib-2.3.31.jar%2338!:?]\n\tat net.minecraft.client.Minecraft.m_91130_(Minecraft.java:601) ~[client-1.17.1-20210706.113038-srg.jar%2355!:?]\n\tat net.minecraft.client.Minecraft.<init>(Minecraft.java:404) ~[client-1.17.1-20210706.113038-srg.jar%2355!:?]\n\tat net.minecraft.client.main.Main.main(Main.java:151) ~[client-1.17.1-20210706.113038-srg.jar%2355!:?]\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[?:?]\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]\n\tat java.lang.reflect.Method.invoke(Method.java:567) ~[?:?]\n\tat net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:45) ~[fmlloader-1.17.1-37.1.1.jar%2323!:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) [modlauncher-9.0.7.jar%235!:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) [modlauncher-9.0.7.jar%235!:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) [modlauncher-9.0.7.jar%235!:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:106) [modlauncher-9.0.7.jar%235!:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:77) [modlauncher-9.0.7.jar%235!:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) [modlauncher-9.0.7.jar%235!:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) [modlauncher-9.0.7.jar%235!:?]\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:90) [bootstraplauncher-0.1.17.jar:?]\nCaused by: com.mojang.authlib.exceptions.MinecraftClientHttpException: Status: 401\n\tat com.mojang.authlib.minecraft.client.MinecraftClient.readInputStream(MinecraftClient.java:79) ~[authlib-2.3.31.jar%2338!:?]\n\tat com.mojang.authlib.minecraft.client.MinecraftClient.get(MinecraftClient.java:47) ~[authlib-2.3.31.jar%2338!:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilSocialInteractionsService.checkPrivileges(YggdrasilSocialInteractionsService.java:104) ~[authlib-2.3.31.jar%2338!:?]\n\t... 18 more\n[22:55:44] [Render thread/INFO]: Setting user: fmtt1\n[22:55:44] [Render thread/INFO]: Backend library: LWJGL version 3.2.2 SNAPSHOT\n[22:55:45] [modloading-worker-2/INFO]: Forge mod loading, version 37.1.1, for MC 1.17.1 with MCP 20210706.113038\n[22:55:45] [modloading-worker-2/INFO]: MinecraftForge v37.1.1 Initialized\n[22:55:47] [Render thread/INFO]: Narrator library for x64 successfully loaded\n[22:55:47] [Render thread/INFO]: Reloading ResourceManager: Default, Mod Resources\n[22:55:47] [Worker-Main-10/INFO]: No plugins for JourneyMap API discovered.\n[22:55:47] [Forge Version Check/INFO]: [forge] Starting version check at https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json\n[22:55:47] [Render thread/INFO]: Initializing Packet Registries\n[22:55:48] [Render thread/INFO]: Journeymap Initializing\n[22:55:48] [Render thread/INFO]: JourneyMap log initialized.\n[22:55:48] [Render thread/INFO]: initialize ENTER\n[22:55:48] [Render thread/INFO]: [ClientAPI] built with JourneyMap API 1.8-SNAPSHOT\n[22:55:48] [Render thread/INFO]: Initializing plugins with Client API: journeymap.client.api.impl.ClientAPI\n[22:55:48] [Render thread/WARN]: core (Initialized) Bad configField entry during updateFrom(): optionsManagerViewed=null\n[22:55:48] [Render thread/WARN]: core (Initialized) Bad configField entry during updateFrom(): splashViewed=null\n[22:55:48] [Forge Version Check/INFO]: [forge] Found status: UP_TO_DATE Current: 37.1.1 Target: null\n[22:55:48] [Forge Version Check/INFO]: [journeymap] Starting version check at https://forge.curseupdate.com/32274/journeymap\n[22:55:48] [Render thread/INFO]: initialize EXIT, elapsed count 1 avg 508.87ms\n[22:55:49] [Forge Version Check/INFO]: [journeymap] Found status: UP_TO_DATE Current: 5.8.0 Target: null\n[22:56:00] [Render thread/INFO]: Journeymap PostInit\n[22:56:01] [Render thread/INFO]: Preloaded theme textures: 73\n[22:56:01] [Render thread/INFO]: postInitialize EXIT, elapsed count 1 avg 356.59ms\n[22:56:02] [Render thread/INFO]: OpenAL initialized.\n[22:56:02] [Render thread/INFO]: Sound engine started\n[22:56:02] [Render thread/INFO]: Created: 1024x512x4 minecraft:textures/atlas/blocks.png-atlas\n[22:56:02] [Render thread/INFO]: Created: 256x128x4 minecraft:textures/atlas/signs.png-atlas\n[22:56:02] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/banner_patterns.png-atlas\n[22:56:02] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/shield_patterns.png-atlas\n[22:56:02] [Render thread/INFO]: Created: 256x256x4 minecraft:textures/atlas/chest.png-atlas\n[22:56:02] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas\n[22:56:02] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas\n[22:56:03] [Render thread/INFO]: Created: 256x256x0 minecraft:textures/atlas/particles.png-atlas\n[22:56:03] [Render thread/INFO]: Created: 256x256x0 minecraft:textures/atlas/paintings.png-atlas\n[22:56:03] [Render thread/INFO]: Created: 128x128x0 minecraft:textures/atlas/mob_effects.png-atlas\n[22:56:03] [Render thread/INFO]: Created: 256x128x0 jei:textures/atlas/gui.png-atlas\n[22:56:04] [Realms Notification Availability checker #1/INFO]: Could not authorize you against Realms server: Invalid session id\n[22:56:19] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[22:56:19] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]\n[22:56:19] [Render thread/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[22:56:19] [Render thread/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[22:56:19] [Render thread/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[22:56:19] [Render thread/WARN]: Ambiguity between arguments [waypoint, create, name, dimension, location, color, announce] and [waypoint, create, name, dimension, location, color, player] with inputs: [true, false]\n[22:56:19] [Render thread/WARN]: Ambiguity between arguments [waypoint, delete, name, announce] and [waypoint, delete, name, player] with inputs: [true, false]\n[22:56:19] [Render thread/INFO]: Reloading ResourceManager: Default, forge-1.17.1-37.1.1-universal.jar, journeymap-1.17.1-5.8.0.jar, jei-1.17.1-8.3.1.1002.jar\n[22:56:22] [Render thread/INFO]: Loaded 7 recipes\n[22:56:24] [Render thread/INFO]: Loaded 1137 advancements\n[22:56:52] [pool-6-thread-1/INFO]: World optimizaton finished after 27426 ms\n[22:56:58] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[22:56:58] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]\n[22:56:58] [Render thread/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[22:56:58] [Render thread/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[22:56:58] [Render thread/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[22:56:58] [Render thread/WARN]: Ambiguity between arguments [waypoint, create, name, dimension, location, color, announce] and [waypoint, create, name, dimension, location, color, player] with inputs: [true, false]\n[22:56:58] [Render thread/WARN]: Ambiguity between arguments [waypoint, delete, name, announce] and [waypoint, delete, name, player] with inputs: [true, false]\n[22:56:58] [Render thread/INFO]: Reloading ResourceManager: Default, forge-1.17.1-37.1.1-universal.jar, journeymap-1.17.1-5.8.0.jar, jei-1.17.1-8.3.1.1002.jar\n[22:57:01] [Render thread/INFO]: Loaded 7 recipes\n[22:57:02] [Render thread/INFO]: Loaded 1137 advancements\n[22:57:02] [Render thread/INFO]: Injecting existing registry data into this CLIENT instance\n[22:57:03] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[22:57:03] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]\n[22:57:03] [Render thread/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[22:57:03] [Render thread/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[22:57:03] [Render thread/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[22:57:03] [Render thread/WARN]: Ambiguity between arguments [waypoint, create, name, dimension, location, color, announce] and [waypoint, create, name, dimension, location, color, player] with inputs: [true, false]\n[22:57:03] [Render thread/WARN]: Ambiguity between arguments [waypoint, delete, name, announce] and [waypoint, delete, name, player] with inputs: [true, false]\n[22:57:03] [Render thread/INFO]: Reloading ResourceManager: Default, forge-1.17.1-37.1.1-universal.jar, journeymap-1.17.1-5.8.0.jar, jei-1.17.1-8.3.1.1002.jar\n[22:57:05] [Render thread/INFO]: Loaded 7 recipes\n[22:57:07] [Render thread/INFO]: Loaded 1137 advancements\n[22:57:07] [Render thread/INFO]: Injecting existing registry data into this CLIENT instance\n[22:57:08] [Render thread/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD'\n[22:57:08] [Server thread/INFO]: Starting integrated minecraft server version 1.17.1\n[22:57:08] [Server thread/INFO]: Generating keypair\n[22:57:08] [Server thread/ERROR]: Encountered an unexpected exception\nnet.minecraftforge.fml.config.ConfigFileTypeHandler$ConfigLoadingException: Failed loading config file journeymap-server.toml of type SERVER for modid journeymap\n\tat net.minecraftforge.fml.config.ConfigFileTypeHandler.lambda$reader$1(ConfigFileTypeHandler.java:61) ~[fmlcore-1.17.1-37.1.1.jar%2356!:?]\n\tat net.minecraftforge.fml.config.ConfigTracker.openConfig(ConfigTracker.java:74) ~[fmlcore-1.17.1-37.1.1.jar%2356!:?]\n\tat net.minecraftforge.fml.config.ConfigTracker.lambda$loadConfigs$1(ConfigTracker.java:64) ~[fmlcore-1.17.1-37.1.1.jar%2356!:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) ~[?:?]\n\tat java.util.Collections$SynchronizedCollection.forEach(Collections.java:2093) ~[?:?]\n\tat net.minecraftforge.fml.config.ConfigTracker.loadConfigs(ConfigTracker.java:64) ~[fmlcore-1.17.1-37.1.1.jar%2356!:?]\n\tat net.minecraftforge.fmllegacy.server.ServerLifecycleHooks.handleServerAboutToStart(ServerLifecycleHooks.java:95) ~[forge-1.17.1-37.1.1-universal.jar%2359!:?]\n\tat net.minecraft.client.server.IntegratedServer.m_7038_(IntegratedServer.java:64) ~[client-1.17.1-20210706.113038-srg.jar%2355!:?]\n\tat net.minecraft.server.MinecraftServer.m_130011_(MinecraftServer.java:660) ~[client-1.17.1-20210706.113038-srg.jar%2355!:?]\n\tat net.minecraft.server.MinecraftServer.m_177918_(MinecraftServer.java:258) ~[client-1.17.1-20210706.113038-srg.jar%2355!:?]\n\tat java.lang.Thread.run(Thread.java:831) [?:?]\nCaused by: com.electronwill.nightconfig.core.io.ParsingException: Not enough data available\n\tat com.electronwill.nightconfig.core.io.ParsingException.notEnoughData(ParsingException.java:22) ~[core-3.6.3.jar%237!:?]\n\tat com.electronwill.nightconfig.core.io.ReaderInput.directReadChar(ReaderInput.java:36) ~[core-3.6.3.jar%237!:?]\n\tat com.electronwill.nightconfig.core.io.AbstractInput.readChar(AbstractInput.java:49) ~[core-3.6.3.jar%237!:?]\n\tat com.electronwill.nightconfig.core.io.AbstractInput.readCharsUntil(AbstractInput.java:123) ~[core-3.6.3.jar%237!:?]\n\tat com.electronwill.nightconfig.toml.TableParser.parseKey(TableParser.java:166) ~[toml-3.6.3.jar%238!:?]\n\tat com.electronwill.nightconfig.toml.TableParser.parseDottedKey(TableParser.java:145) ~[toml-3.6.3.jar%238!:?]\n\tat com.electronwill.nightconfig.toml.TableParser.parseNormal(TableParser.java:55) ~[toml-3.6.3.jar%238!:?]\n\tat com.electronwill.nightconfig.toml.TomlParser.parse(TomlParser.java:44) ~[toml-3.6.3.jar%238!:?]\n\tat com.electronwill.nightconfig.toml.TomlParser.parse(TomlParser.java:37) ~[toml-3.6.3.jar%238!:?]\n\tat com.electronwill.nightconfig.core.io.ConfigParser.parse(ConfigParser.java:113) ~[core-3.6.3.jar%237!:?]\n\tat com.electronwill.nightconfig.core.io.ConfigParser.parse(ConfigParser.java:219) ~[core-3.6.3.jar%237!:?]\n\tat com.electronwill.nightconfig.core.io.ConfigParser.parse(ConfigParser.java:202) ~[core-3.6.3.jar%237!:?]\n\tat com.electronwill.nightconfig.core.file.WriteSyncFileConfig.load(WriteSyncFileConfig.java:73) ~[core-3.6.3.jar%237!:?]\n\tat com.electronwill.nightconfig.core.file.AutosaveCommentedFileConfig.load(AutosaveCommentedFileConfig.java:85) ~[core-3.6.3.jar%237!:?]\n\tat net.minecraftforge.fml.config.ConfigFileTypeHandler.lambda$reader$1(ConfigFileTypeHandler.java:57) ~[fmlcore-1.17.1-37.1.1.jar%2356!:?]\n\t... 10 more"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/no_class_def_found_error.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\n  LogfileConservationMod_Core ((1.7.10-1.12.2) LogfileConservationMod-4.1.zip)\n  BaseModNeteaseCore (4621632218832071536@3@0.jar)\n  SkinCore (4626894585322620077@3@0.jar)\n  DepartCore (4625678897404525717@3@0.jar)\n  NeteaseCore (4620273834266845199@3@0.jar)\nContact their authors BEFORE contacting forge\n\n// My bad.\n\nTime: 1/5/19 9:04 PM\nDescription: Initializing game\n\njava.lang.NoClassDefFoundError: blk\n\tat net.minecraftforge.fml.common.Loader.sortModList(Loader.java:264)\n\tat net.minecraftforge.fml.common.Loader.loadMods(Loader.java:570)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:225)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:466)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:377)\n\tat net.minecraft.client.main.Main.main(SourceFile:123)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:141)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:23)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Client thread\nStacktrace:\n\tat net.minecraftforge.fml.common.Loader.sortModList(Loader.java:264)\n\tat net.minecraftforge.fml.common.Loader.loadMods(Loader.java:570)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:225)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:466)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:377)\n\tat net.minecraft.client.main.Main.main(SourceFile:123)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:141)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:23)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.12.2\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_192, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 157611280 bytes (150 MB) / 314400768 bytes (299 MB) up to 8484290560 bytes (8091 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx8104M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: ~~ERROR~~ IncompatibleClassChangeError: null\n\tLoaded coremods (and transformers): ~~ERROR~~ IncompatibleClassChangeError: null\n\tLaunched Version: 1.12.2\n\tLWJGL: 2.9.4\n\tOpenGL: GeForce GTX 960M/PCIe/SSE2 GL version 4.6.0 NVIDIA 417.35, NVIDIA Corporation\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing GL 1.3 texture combiners.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nShaders are available because OpenGL 2.1 is supported.\nVBOs are available because OpenGL 1.5 is supported.\n\n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: 4621521193099187385@4@19.zip\n\tCurrent Language: 简体中文 (中国)\n\tProfiler Position: N/A (disabled)\n\tCPU: <unknown>"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/no_class_def_found_error2.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\n  LogfileConservationMod_Core ((1.12.2) LogfileConservationMod-4.3.jar)\nContact their authors BEFORE contacting forge\n\n// Don't do that.\n\nTime: 19-1-6 下午10:26\nDescription: Initializing game\n\njava.lang.NoClassDefFoundError: cer\n\tat java.lang.Class.getDeclaredFields0(Native Method)\n\tat java.lang.Class.privateGetDeclaredFields(Unknown Source)\n\tat java.lang.Class.getField0(Unknown Source)\n\tat java.lang.Class.getField(Unknown Source)\n\tat net.minecraftforge.fml.client.FMLClientHandler.detectOptifine(FMLClientHandler.java:277)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:191)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:417)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:329)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\nCaused by: java.lang.ClassNotFoundException: cer\n\tat net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:191)\n\tat java.lang.ClassLoader.loadClass(Unknown Source)\n\tat java.lang.ClassLoader.loadClass(Unknown Source)\n\t... 15 more\nCaused by: java.lang.NullPointerException\n\tat net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:182)\n\t... 17 more\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat java.lang.Class.getDeclaredFields0(Native Method)\n\tat java.lang.Class.privateGetDeclaredFields(Unknown Source)\n\tat java.lang.Class.getField0(Unknown Source)\n\tat java.lang.Class.getField(Unknown Source)\n\tat net.minecraftforge.fml.client.FMLClientHandler.detectOptifine(FMLClientHandler.java:277)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:191)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:417)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:329)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.8.9\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_192, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 31185480 bytes (29 MB) / 285212672 bytes (272 MB) up to 2147483648 bytes (2048 MB)\n\tJVM Flags: 11 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Xmn128m -Xmx2048m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML:\n\tLoaded coremods (and transformers):\nLogfileConservationMod_Core ((1.12.2) LogfileConservationMod-4.3.jar)\n\n\tLaunched Version: 1.8.9\n\tLWJGL: 2.9.4\n\tOpenGL: GeForce GTX 960M/PCIe/SSE2 GL version 4.6.0 NVIDIA 417.35, NVIDIA Corporation\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing GL 1.3 texture combiners.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nShaders are available because OpenGL 2.1 is supported.\nVBOs are available because OpenGL 1.5 is supported.\n\n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs:\n\tCurrent Language: ~~ERROR~~ NullPointerException: null\n\tProfiler Position: N/A (disabled)\n\tCPU: 4x Intel(R) Core(TM) i5-6300HQ CPU @ 2.30GHz"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/out_of_memory.txt",
    "content": "---- Minecraft Crash Report ----\n// Hey, that tickles! Hehehe!\n\nTime: 2021-02-11 18:14:23 CST\nDescription: Initializing game\n\njava.lang.OutOfMemoryError: Java heap space\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\n  Minecraft Version: 1.12.2\n  Operating System: Windows 10 (amd64) version 10.0\n  Java Version: 1.8.0_271, Oracle Corporation\n  Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n  Memory: 128162664 bytes (122 MB) / 4278190080 bytes (4080 MB) up to 4278190080 bytes (4080 MB)\n  JVM Flags: 11 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Xmn128m -Xmx4069m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n  IntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n  FML: MCP 9.42 Powered by Forge 14.23.5.2855 Optifine OptiFine_1.12.2_HD_U_G5_pre1 248 mods loaded, 248 mods active\n       States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\n       | State | ID                                | Version                  | Source                                                       | Signature                                |\n       |:----- |:--------------------------------- |:------------------------ |:------------------------------------------------------------ |:---------------------------------------- |\n       | LCH   | minecraft                         | 1.12.2                   | minecraft.jar                                                | None                                     |\n       | LCH   | mcp                               | 9.42                     | minecraft.jar                                                | None                                     |\n       | LCH   | FML                               | 8.0.99.99                | forge-1.12.2-14.23.5.2855.jar                                | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n       | LCH   | forge                             | 14.23.5.2855             | forge-1.12.2-14.23.5.2855.jar                                | e3c3d50c7c986df74c645c0ac54639741c90a557 |\n       | LCH   | ColorUtility                      | 1.0.4                    | minecraft.jar                                                | None                                     |\n       | LCH   | creativecoredummy                 | 1.0.0                    | minecraft.jar                                                | None                                     |\n       | LCH   | jecharacters                      | 1.12.0-3.4.8             | 通用拼音搜索JustEnoughCharacters-1.12.0-3.4.8.jar                  | None                                     |\n       | LCH   | openmodscore                      | 0.12.2                   | minecraft.jar                                                | None                                     |\n       | LCH   | foamfixcore                       | 7.7.4                    | minecraft.jar                                                | None                                     |\n       | LCH   | opencomputers|core                | 1.7.5.192                | minecraft.jar                                                | None                                     |\n       | LCH   | clothesline-hooks                 | 1.12.2-0.0.1.2           | minecraft.jar                                                | None                                     |\n       | LCH   | acbl                              | 1.0.6.1                  | [ACBL]刺客信条：方块传奇acbl-1.0.6.1.jar                              | None                                     |\n       | LCH   | ctm                               | MC1.12.2-1.0.2.31        | CTM-MC1.12.2-1.0.2.31.jar                                    | None                                     |\n       | LCH   | appliedenergistics2               | rv6-stable-7             | appliedenergistics2-rv6-stable-7.jar                         | dfa4d3ac143316c6f32aa1a1beda1e34d42132e5 |\n       | LCH   | ae2fc                             | 1.0.9                    | [ae2fc]ae2fc-1.12.2-1.0.9.jar                                | None                                     |\n       | LCH   | bdlib                             | 1.14.3.12                | bdlib-1.14.3.12-mc1.12.2.jar                                 | None                                     |\n       | LCH   | ae2stuff                          | 0.7.0.4                  | [AE2加强]ae2stuff-0.7.0.4-mc1.12.2.jar                         | None                                     |\n       | LCH   | baubles                           | 1.5.2                    | Baubles-1.12-1.5.2.jar                                       | None                                     |\n       | LCH   | statues                           | 0.8.9.2                  | 雕像statues-1.12.X-0.8.9.2.jar                                 | None                                     |\n       | LCH   | crafttweaker                      | 4.1.20                   | 魔改器CraftTweaker2-1.12-4.1.20.614.jar                         | None                                     |\n       | LCH   | endertweaker                      | 1.2.1                    | eio魔改EnderTweaker-1.12.2-1.2.1.jar                           | None                                     |\n       | LCH   | forgelin                          | 1.8.4                    | Forgelin-1.8.4.jar                                           | None                                     |\n       | LCH   | alib                              | 1.0.12                   | 炼金化学前置alib-1.0.12.jar                                        | None                                     |\n       | LCH   | alchemistry                       | 1.12.2-42                | 炼金化学alchemistry-1.12.2-42.jar                                | None                                     |\n       | LCH   | jei                               | 4.15.0.291               | jei_1.12.2-4.15.0.291.jar                                    | None                                     |\n       | LCH   | redstoneflux                      | 2.1.1                    | RedstoneFlux-1.12-2.1.1.1-universal.jar                      | None                                     |\n       | LCH   | cofhcore                          | 4.6.6                    | CoFHCore-1.12.2-4.6.6.1-universal.jar                        | None                                     |\n       | LCH   | cofhworld                         | 1.4.0                    | CoFHWorld-1.12.2-1.4.0.1-universal.jar                       | None                                     |\n       | LCH   | thermalfoundation                 | 2.6.7                    | ThermalFoundation-1.12.2-2.6.7.1-universal.jar               | None                                     |\n       | LCH   | twilightforest                    | 3.10.1013                | twilightforest-1.12.2-3.10.1013-universal.jar                | None                                     |\n       | LCH   | engineerstools                    | 1.0.5                    | 工程师的工具engineerstools-1.12.2-1.0.5.jar                        | None                                     |\n       | LCH   | engineersdecor                    | 1.1.2                    | 工程师的装饰engineersdecor-1.12.2-1.1.2.jar                        | ed58ed655893ced6280650866985abcae2bf7559 |\n       | LCH   | immersiveengineering              | 0.12-92                  | ImmersiveEngineering-0.12-92.jar                             | 4cb49fcde3b43048c9889e0a3d083225da926334 |\n       | LCH   | alternatingflux                   | 0.12.2-2                 | [AF]交变磁通alternatingflux-0.12.2-2.jar                         | None                                     |\n       | LCH   | appliedintegrations               | 8.0.16.7                 | [AI]应用集成AppliedIntegrations-1.12.2-e2884e6f.jar              | None                                     |\n       | LCH   | mcmultipart                       | 2.5.3                    | MCMultiPart-2.5.3.jar                                        | None                                     |\n       | LCH   | computercraft                     | 1.86.2                   | cc-tweaked-1.12.2-1.86.2.jar                                 | None                                     |\n       | LCH   | mekanism                          | 1.12.2-9.8.3.390         | Mekanism-1.12.2-9.8.3.390.jar                                | None                                     |\n       | LCH   | sonarcore                         | 5.0.19                   | sonarcore-1.12.2-5.0.19-20.jar                               | None                                     |\n       | LCH   | calculator                        | 5.0.12                   | [CC]运算工艺calculator-1.12.2-5.0.12-15.jar                      | None                                     |\n       | LCH   | waila                             | 1.8.26                   | [高亮显示]Hwyla-1.8.26-B41_1.12.2.jar                            | None                                     |\n       | LCH   | extracells                        | 2.6.5                    | [EC2更多存储单元]ExtraCells-1.12.2-2.6.5.jar                       | None                                     |\n       | LCH   | floricraft                        | 4.4.3                    | [FC]花卉工艺Floricraft-1.12.2-4.4.3.jar                          | None                                     |\n       | LCH   | journeymap                        | 1.12.2-5.7.1             | [JM流行地图]journeymap-1.12.2-5.7.1.jar                          | None                                     |\n       | LCH   | legendera                         | 1.1.8                    | [LE]传说纪元TheLegendEra-1.1.8.jar                               | None                                     |\n       | LCH   | mm_lib                            | 2.2.0                    | [MMLib]妖怪之山通用库MMLib-2.2.0.jar                                | None                                     |\n       | LCH   | inventorytweaks                   | 1.63+release.109.220f184 | [R键整理]InventoryTweaks-1.63.jar                               | 55d2cd4f5f0961410bf7b91ef6c6bf00a766dcbe |\n       | LCH   | sakura                            | 1.0.5-1.12.2             | Sakura-1.0.5-1.12.2.jar                                      | None                                     |\n       | LCH   | flammpfeil.slashblade             | mc1.12-r32               | SlashBlade-mc1.12-r32.jar                                    | None                                     |\n       | LCH   | slashblade_addon                  | 1.5.0                    | [SJAP]拔刀剑日系附属包SJAP-1.5.0.jar                                 | None                                     |\n       | LCH   | negorerouse                       | r1                       | [尼格洛茨·诸神帝裔]NegoreRouse-r1.1.4-mc1.12.2.jar                   | None                                     |\n       | LCH   | i18nmod                           | 1.12.2-1.0.8             | [自动汉化]i18nupdatemod-1.12.2-1.0.8.jar                         | None                                     |\n       | LCH   | wailaharvestability               | 1.1.12                   | [高亮显示前置]WailaHarvestability-mc1.12-1.1.12.jar                | None                                     |\n       | LCH   | spatialservermod                  | 1.3                      | ae2空间修复spatialservermod-1.3.jar                              | None                                     |\n       | LCH   | jee                               | 1.0.8                    | ae修复JustEnoughEnergistics-1.12.2-1.0.8.jar                   | None                                     |\n       | LCH   | infinitylib                       | 1.12.2-1.12.0            | infinitylib-1.12.0.jar                                       | None                                     |\n       | LCH   | agricraft                         | 2.12.0-1.12.0-a6         | AgriCraft-2.12.0-1.12.0-a6.jar                               | None                                     |\n       | LCH   | extrautils2                       | 1.0                      | extrautils2-1.12-1.9.9.jar                                   | None                                     |\n       | LCH   | flyringbaublemod                  | 0.3.1_1.12-d4e654e       | angelRingToBauble-1.12-0.3.1.50+d4e654e.jar                  | None                                     |\n       | LCH   | aroma1997core                     | 2.0.0.2                  | Aroma1997Core-1.12.2-2.0.0.2.jar                             | dfbfe4c473253d8c5652417689848f650b2cbe32 |\n       | LCH   | aroma1997sdimension               | 2.0.0.2                  | Aroma1997s-Dimensional-World-1.12.2-2.0.0.2.jar              | dfbfe4c473253d8c5652417689848f650b2cbe32 |\n       | LCH   | codechickenlib                    | 3.2.3.358                | CodeChickenLib-1.12.2-3.2.3.358-universal.jar                | f1850c39b2516232a2108a7bd84d1cb5df93b261 |\n       | LCH   | avaritia                          | 3.3.0                    | Avaritia-1.12.2-3.3.0.33-universal.jar                       | None                                     |\n       | LCH   | endercore                         | 1.12.2-0.5.76            | EnderCore-1.12.2-0.5.76.jar                                  | None                                     |\n       | LCH   | brandonscore                      | 2.4.18                   | BrandonsCore-1.12.2-2.4.18.210-universal.jar                 | None                                     |\n       | LCH   | draconicevolution                 | 2.3.25                   | Draconic-Evolution-1.12.2-2.3.25.351-universal.jar           | None                                     |\n       | LCH   | thermalexpansion                  | 5.5.7                    | ThermalExpansion-1.12.2-5.5.7.1-universal.jar                | None                                     |\n       | LCH   | enderio                           | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | avaritiaio                        | @VERSION@                | avaritiaio-1.4.jar                                           | None                                     |\n       | LCH   | botania                           | r1.10-363                | Botania+r1.10-363.jar                                        | None                                     |\n       | LCH   | avaritiatweaks                    | 1.12.2-1.1               | AvaritiaTweaks-1.12.2-1.1.jar                                | 4ffa87db52cf086d00ecc4853a929367b1c39b5c |\n       | LCH   | betternether                      | 0.1.8.6                  | betternether-0.1.8.6.jar                                     | None                                     |\n       | LCH   | betterrecords                     | unspecified              | BetterRecords-1.12.2-1.6.2.jar                               | None                                     |\n       | LCH   | bitcoin                           | ${version}               | bitcoin-1.12.2-1.1.0.jar                                     | None                                     |\n       | LCH   | hammercore                        | 2.0.6.14                 | HammerLib-1.12.2-2.0.6.14.jar                                | 9f5e2a811a8332a842b34f6967b7db0ac4f24856 |\n       | LCH   | botanicadds                       | 12.2.5                   | BotanicAdditions-1.12.2-12.2.5.jar                           | 9f5e2a811a8332a842b34f6967b7db0ac4f24856 |\n       | LCH   | cameraobscura                     | 0.0.1                    | CameraObscura-1.0.3.jar                                      | None                                     |\n       | LCH   | cctweaked                         | 1.86.2                   | cc-tweaked-1.12.2-1.86.2.jar                                 | None                                     |\n       | LCH   | cd4017be_lib                      | 6.5.1                    | CD4017BE_lib-1.12.2-6.5.1.jar                                | None                                     |\n       | LCH   | chickenchunks                     | 2.4.2.74                 | ChickenChunks-1.12.2-2.4.2.74-universal.jar                  | f1850c39b2516232a2108a7bd84d1cb5df93b261 |\n       | LCH   | chineseworkshop                   | 1.2.6                    | ChineseWorkshop-1.12.2_1.2.6.jar                             | None                                     |\n       | LCH   | chinjufumod                       | 3.2.2                    | ChinjufuMod[1.12.2]3.2.2.jar                                 | None                                     |\n       | LCH   | chiselsandbits                    | 14.33                    | chiselsandbits-14.33.jar                                     | None                                     |\n       | LCH   | clockworkphase                    | 1.0g                     | ClockworkPhase-1.12.2-1.0g.jar                               | None                                     |\n       | LCH   | compactvoidminers                 | 1.11                     | CompactVoidMiners-R1.11.jar                                  | None                                     |\n       | LCH   | creativecore                      | 1.10.0                   | CreativeCore_v1.10.44_mc1.12.2.jar                           | None                                     |\n       | LCH   | cucumber                          | 1.1.3                    | Cucumber-1.12.2-1.1.3.jar                                    | None                                     |\n       | LCH   | denseneutroncollectors            | @VERSION@                | denseneutroncollectors-1.1.jar                               | None                                     |\n       | LCH   | draconicadditions                 | 1.11.0                   | Draconic-Additions-1.12.2-1.11.0.31-universal.jar            | None                                     |\n       | LCH   | earthworks                        | 1.3.4.3                  | Earthworks-1.12-1.3.6.jar                                    | None                                     |\n       | LCH   | enderioconduits                   | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | enderioendergy                    | 5.2.66                   | eio扩展EnderIO-endergy-1.12.2-5.2.66.jar                       | None                                     |\n       | LCH   | enderiointegrationtic             | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | enderiobase                       | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | enderioconduitsappliedenergistics | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | opencomputers                     | 1.7.5.192                | OpenComputers-MC1.12.2-1.7.5.192.jar                         | None                                     |\n       | LCH   | enderioconduitsopencomputers      | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | enderioconduitsrefinedstorage     | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | enderiointegrationforestry        | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | enderiointegrationticlate         | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | enderioinvpanel                   | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | enderiomachines                   | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | enderiopowertools                 | 5.2.66                   | EnderIO-1.12.2-5.2.66.jar                                    | None                                     |\n       | LCH   | engineersdoors                    | 0.9.1                    | engineers_doors-1.12.2-0.9.1.jar                             | None                                     |\n       | LCH   | valkyrielib                       | 1.12.2-2.0.20.1          | valkyrielib-1.12.2-2.0.20.1.jar                              | None                                     |\n       | LCH   | environmentaltech                 | 1.12.2-2.0.20.1          | environmentaltech-1.12.2-2.0.20.1.jar                        | None                                     |\n       | LCH   | etlunar                           | 1.12.2-2.0.20.1          | etlunar-1.12.2-2.0.20.1.jar                                  | None                                     |\n       | LCH   | mtlib                             | 3.0.6                    | 磁场工艺前置MTLib-3.0.6.jar                                        | None                                     |\n       | LCH   | extrabotany                       | 58                       | ExtraBotany-r1.1-58r.jar                                     | None                                     |\n       | LCH   | extracpus                         | 1.1.0                    | ExtraCPUsextracpus-1.12.2-1.1.0.jar                          | None                                     |\n       | LCH   | farseek                           | 2.5                      | Farseek-1.12-2.5.jar                                         | None                                     |\n       | LCH   | fluxnetworks                      | 4.0.14                   | fluxnetworks-1.12.2-4.0.14-3通量网络1.jar                        | None                                     |\n       | LCH   | foamfix                           | 0.10.10-1.12.2           | foamfix-0.10.10-1.12.2.jar                                   | None                                     |\n       | LCH   | forgeendertech                    | 1.12.2-4.5.2.0           | ForgeEndertech-1.12.2-4.5.2.0-build.0459.jar                 | None                                     |\n       | LCH   | forgemultipartcbe                 | 2.6.2.83                 | ForgeMultipart-1.12.2-2.6.2.83-universal.jar                 | f1850c39b2516232a2108a7bd84d1cb5df93b261 |\n       | LCH   | microblockcbe                     | 2.6.2.83                 | ForgeMultipart-1.12.2-2.6.2.83-universal.jar                 | None                                     |\n       | LCH   | minecraftmultipartcbe             | 2.6.2.83                 | ForgeMultipart-1.12.2-2.6.2.83-universal.jar                 | None                                     |\n       | LCH   | fpsreducer                        | mc1.12.2-1.12            | FpsReducer-mc1.12.2-1.12.jar                                 | None                                     |\n       | LCH   | ftbbackups                        | 1.1.0.1                  | FTBBackups-1.1.0.1.jar                                       | None                                     |\n       | LCH   | furenikusroads                    | 1.1.10                   | Fureniku的路Fureniku's+Roads-1.1.10.jar                        | None                                     |\n       | LCH   | advgenerators                     | 0.9.20.12                | generators-0.9.20.12-mc1.12.2.jar                            | None                                     |\n       | LCH   | igwmod                            | 1.4.4-15                 | IGW-Mod-1.12.2-1.4.4-15-universal.jar                        | None                                     |\n       | LCH   | industrialexpansion               | 1.2.6                    | IndustrialExpansion-1.2.6.jar                                | None                                     |\n       | LCH   | infraredstone                     | 1.2.1                    | InfraRedstone-1.2.1.jar                                      | None                                     |\n       | LCH   | jeivillagers                      | 1.0                      | jeivillagers-1.12-1.0.2.jar                                  | None                                     |\n       | LCH   | jeiintegration                    | 1.6.0                    | JEI扩展jeiintegration_1.12.2-1.6.0.jar                         | None                                     |\n       | LCH   | jeresources                       | 0.9.2.60                 | JEI扩展JustEnoughResources-1.12.2-0.9.2.60.jar                 | None                                     |\n       | LCH   | jetif                             | 1.5.2                    | Just Enough Throwing In Fluids (JETIF)jetif-1.12.2-1.5.2.jar | None                                     |\n       | LCH   | jeid                              | 1.0.3-55                 | JustEnoughIDs-1.0.3-55.jar                                   | None                                     |\n       | LCH   | zerocore                          | 1.12.2-0.1.2.8           | 大反应前置zerocore-1.12.2-0.1.2.8.jar                             | None                                     |\n       | LCH   | bigreactors                       | 1.12.2-0.4.5.67          | 大型反应ExtremeReactors-1.12.2-0.4.5.67.jar                      | None                                     |\n       | LCH   | justenoughreactors                | 1.1.3.61                 | JustEnoughReactors-1.12.2-1.1.3.61.jar                       | 2238d4a92d81ab407741a2fdb741cebddfeacba6 |\n       | LCH   | libnine                           | 1.1.6                    | libnine-1.12.2-1.1.6.jar                                     | None                                     |\n       | LCH   | threng                            | 1.1.13                   | lazy-ae2-1.12.2-1.1.13.jar                                   | None                                     |\n       | LCH   | lightningcraft                    | 2.9.7_1                  | lightningcraft-2.9.7_1.jar                                   | None                                     |\n       | LCH   | lootcapacitortooltips             | 1.3                      | lootcapacitortooltips-1.3.jar                                | None                                     |\n       | LCH   | mcwroofs                          | 1.0.2                    | Macaw的屋顶mcw-roofs-1.0.2-mc1.12.2.jar                         | None                                     |\n       | LCH   | mcwwindows                        | 1.0                      | Macaw的窗户mcw-windows-1.0.0-mc1.12.2.jar                       | None                                     |\n       | LCH   | malisiscore                       | 1.12.2-6.5.1-SNAPSHOT    | malisiscore-1.12.2-6.5.1.jar                                 | None                                     |\n       | LCH   | mcjtylib_ng                       | 3.5.4                    | mcjtylib-1.12-3.5.4.jar                                      | None                                     |\n       | LCH   | mcwbridges                        | 1.0.4                    | mcw-bridges-1.0.4-mc1.12.2.jar                               | None                                     |\n       | LCH   | mekanismtools                     | 1.12.2-9.8.3.390         | MekanismTools-1.12.2-9.8.3.390.jar                           | None                                     |\n       | LCH   | mekores                           | 2.0.13                   | mekores-2.0.13.jar                                           | None                                     |\n       | LCH   | morpheus                          | 1.12.2-3.5.106           | Morpheus-1.12.2-3.5.106.jar                                  | None                                     |\n       | LCH   | mrtjpcore                         | 2.1.4.43                 | MrTJPCore-1.12.2-2.1.4.43-universal.jar                      | None                                     |\n       | LCH   | projectintelligence               | 1.0.9                    | ProjectIntelligence-1.12.2-1.0.9.28-universal.jar            | None                                     |\n       | LCH   | nei                               | 2.4.2                    | NotEnoughItems-1.12.2-2.4.2.244-universal.jar                | f1850c39b2516232a2108a7bd84d1cb5df93b261 |\n       | LCH   | omlib                             | 3.1.4-249                | omlib-1.12.2-3.1.4-249.jar                                   | None                                     |\n       | LCH   | openmods                          | 0.12.2                   | OpenModsLib-1.12.2-0.12.2.jar                                | d2a9a8e8440196e26a268d1f3ddc01b2e9c572a5 |\n       | LCH   | openblocks                        | 1.8.1                    | OpenBlocks-1.12.2-1.8.1.jar                                  | d2a9a8e8440196e26a268d1f3ddc01b2e9c572a5 |\n       | LCH   | openfm                            | 0.1.0.19                 | OpenFM-1.12.2-0.1.0.19.jar                                   | None                                     |\n       | LCH   | patchouli                         | 1.0-20                   | Patchouli-1.0-20.jar                                         | None                                     |\n       | LCH   | placebo                           | 1.6.0                    | Placebo-1.12.2-1.6.0.jar                                     | None                                     |\n       | LCH   | pneumaticcraft                    | 1.12.2-0.11.14-395       | pneumaticcraft-repressurized-1.12.2-0.11.14-395.jar          | None                                     |\n       | LCH   | projectred-core                   | 4.9.4.120                | ProjectRed-1.12.2-4.9.4.120-Base.jar                         | None                                     |\n       | LCH   | projectred-compat                 | 1.0                      | ProjectRed-1.12.2-4.9.4.120-compat.jar                       | None                                     |\n       | LCH   | projectred-integration            | 4.9.4.120                | ProjectRed-1.12.2-4.9.4.120-integration.jar                  | None                                     |\n       | LCH   | projectred-transmission           | 4.9.4.120                | ProjectRed-1.12.2-4.9.4.120-integration.jar                  | None                                     |\n       | LCH   | projectred-fabrication            | 4.9.4.120                | ProjectRed-1.12.2-4.9.4.120-fabrication.jar                  | None                                     |\n       | LCH   | projectred-illumination           | 4.9.4.120                | ProjectRed-1.12.2-4.9.4.120-lighting.jar                     | None                                     |\n       | LCH   | projectred-expansion              | 4.9.4.120                | ProjectRed-1.12.2-4.9.4.120-mechanical.jar                   | None                                     |\n       | LCH   | projectred-relocation             | 4.9.4.120                | ProjectRed-1.12.2-4.9.4.120-mechanical.jar                   | None                                     |\n       | LCH   | projectred-transportation         | 4.9.4.120                | ProjectRed-1.12.2-4.9.4.120-mechanical.jar                   | None                                     |\n       | LCH   | projectred-exploration            | 4.9.4.120                | ProjectRed-1.12.2-4.9.4.120-world.jar                        | None                                     |\n       | LCH   | ptrmodellib                       | 1.0.4                    | PTRLib-1.0.4.jar                                             | None                                     |\n       | LCH   | rare-ice                          | ${version}               | rare-ice-0.1.0.jar                                           | None                                     |\n       | LCH   | rcroads                           | 0.3.0                    | RC+Roads+0.3.0.jar                                           | None                                     |\n       | LCH   | redstonearsenal                   | 2.6.4                    | RedstoneArsenal-1.12.2-2.6.4.1-universal.jar                 | None                                     |\n       | LCH   | redstonerepository                | 1.3.2                    | RedstoneRepository-1.12.2-1.4.0-dev-universal.jar            | None                                     |\n       | LCH   | rftools                           | 7.73                     | rftools-1.12-7.73.jar                                        | None                                     |\n       | LCH   | rftoolscontrol                    | 2.0.2                    | rftoolsctrl-1.12-2.0.2.jar                                   | None                                     |\n       | LCH   | rftoolsdim                        | 5.71                     | rftoolsdim-1.12-5.71.jar                                     | None                                     |\n       | LCH   | sgcraft                           | 2.0.3                    | SGCraft-2.0.5.jar                                            | None                                     |\n       | LCH   | skewers                           | 1.1                      | Skewers-1.1.jar                                              | None                                     |\n       | LCH   | soundphysics                      | 1.0.10-1                 | Sound-Physics-1.12.2-1.0.10-1.jar                            | None                                     |\n       | LCH   | streams                           | 0.4.8                    | Streams-1.12-0.4.8.jar                                       | None                                     |\n       | LCH   | tammodized                        | 0.15.6                   | TamModized-1.12.2-0.15.6.jar                                 | None                                     |\n       | LCH   | teaandbiscuits                    | 1.4                      | TeaAndBiscuits-1.12.2-1.4.jar                                | None                                     |\n       | LCH   | teastory                          | 3.3.3-B32.404-1.12.2     | TeaStory-3.3.3-B32.404-1.12.2.jar                            | None                                     |\n       | LCH   | theeightfabledblades              | 1.0.0                    | TheEightFabledBlades+V2.0.01.jar                             | None                                     |\n       | LCH   | thermalcultivation                | 0.3.6                    | ThermalCultivation-1.12.2-0.3.6.1-universal.jar              | None                                     |\n       | LCH   | thermaldynamics                   | 2.5.6                    | ThermalDynamics-1.12.2-2.5.6.1-universal.jar                 | None                                     |\n       | LCH   | thermalinnovation                 | 0.3.6                    | ThermalInnovation-1.12.2-0.3.6.1-universal.jar               | None                                     |\n       | LCH   | thermallogistics                  | 0.2-29                   | thermallogistics-0.2-29.jar                                  | None                                     |\n       | LCH   | lastsmith                         | V1.2.6.5-MC1.12.2        | TLS-V1.2.6.5-MC1.12.2.jar                                    | None                                     |\n       | LCH   | universalmodifiers                | 1.12.2-1.0.16.1          | valkyrielib-1.12.2-2.0.20.1.jar                              | None                                     |\n       | LCH   | vanillafix                        | 1.0.10-150               | VanillaFix-1.0.10-150.jar                                    | None                                     |\n       | LCH   | xnet                              | 1.8.2                    | xnet-1.12-1.8.2.jar                                          | None                                     |\n       | LCH   | bettermineshafts                  | 1.12.2-2.2               | YUNG的矿井优化BetterMineshaftsForge-1.12.2-2.2.jar                | None                                     |\n       | LCH   | rustic                            | 1.1.7                    | 乡村rustic-1.1.7.jar                                           | None                                     |\n       | LCH   | portality                         | 1.0-SNAPSHOT             | 传送portality-1.12.2-1.2.3-15.jar                              | None                                     |\n       | LCH   | swordcraftonline                  | 1.0.1                    | 动漫神域SwordCraftOnline 1.12.2 - 汉化.jar                         | None                                     |\n       | LCH   | animalcrops                       | 1.12.2-0.2.0             | 动物作物AnimalCrops-1.12.2-0.2.0.jar                             | None                                     |\n       | LCH   | compactmachines3                  | 3.0.18                   | 压缩空间compactmachines3-1.12.2-3.0.18-b278.jar                  | None                                     |\n       | LCH   | atomicscience                     | 1.12.2-3.0.6.19          | 原子科学Atomic-Science-1.12.2-3.0.6b19.jar                       | None                                     |\n       | LCH   | mtrm                              | 1.2.2.30                 | 可视化魔改MineTweakerRecipeMaker-1.12.2-1.2.2.30.jar              | None                                     |\n       | LCH   | recipehandler                     | 0.13                     | 合成冲突消除NoMoreRecipeConflict-0.13(1.12.2).jar                  | None                                     |\n       | LCH   | compacter                         | 1.3.0.3                  | 合成器compacter-1.3.0.3-mc1.12.2.jar                            | None                                     |\n       | LCH   | extendedcrafting                  | 1.5.6                    | 合成扩展修复ExtendedCrafting-1.12.2-1.5.6 (2)(1).jar               | None                                     |\n       | LCH   | packagedauto                      | 1.12.2-1.0.3.13          | 封包合成PackagedAuto-1.12.2-1.0.3.14.jar                         | None                                     |\n       | LCH   | packagedexcrafting                | 1.12.2-1.0.1.1           | 合成器PackagedExCrafting-1.12.2-1.0.1.2.jar                     | None                                     |\n       | LCH   | coffeework                        | 1.2.9                    | 咖啡工艺coffeework-1.2.9.jar                                     | None                                     |\n       | LCH   | fat_cat                           | 0.0.5                    | 大资本家FatCat-0.0.5.jar                                         | None                                     |\n       | LCH   | absentbydesign                    | 1.12.2-1.0.4             | 好东西absentbydesign-1.12.2-1.0.4.jar                           | 1bc8f8dbe770187a854cef35dad0ff40ba441bbe |\n       | LCH   | coroutil                          | 1.12.1-1.2.37            | 孔明灯，天灯前置coroutil-1.12.1-1.2.37.jar                           | None                                     |\n       | LCH   | sky_lanterns                      | 1.12.1-1.0.1             | 孔明灯，天灯skylanterns-1.12.1-1.0.1.jar                           | None                                     |\n       | LCH   | extendedrenderer                  | v1.0                     | 孔明灯，天灯前置coroutil-1.12.1-1.2.37.jar                           | None                                     |\n       | LCH   | configmod                         | v1.0                     | 孔明灯，天灯前置coroutil-1.12.1-1.2.37.jar                           | None                                     |\n       | LCH   | tp                                | 3.2.34                   | 微型自动化tinyprogressions-1.12.2-3.3.34-Release.jar              | None                                     |\n       | LCH   | grapplemod                        | 1.12.2-v11.1             | 抓钩grapplemod-v11.1-1.12.2.jar                                | None                                     |\n       | LCH   | xcustomizedblade                  | 1.60                     | 拔刀mod编辑XCustomizedBlade.TinyCore.1.12.2.VER1.60.jar          | None                                     |\n       | LCH   | moarsigns                         | 6.0.0.11                 | 更多牌子MoarSigns-1.12.2-6.0.0.11.jar                            | 5a57c7d1420cf4c58f53ea2a298ebef8215ede63 |\n       | LCH   | betterbedrockgen                  | 6.0.2                    | 更好的基岩BetterBedrockGenerator-1.12-6.1.1.jar                   | None                                     |\n       | LCH   | stygian                           | 1.0.4                    | 末地，生物群系扩展stygian-1.0.4.jar                                   | None                                     |\n       | LCH   | regexfilters                      | 1.3                      | 正则表达式过滤器regexfilters-1.3.jar                                 | None                                     |\n       | LCH   | immersivetech                     | 1.3.10                   | 沉浸科技immersivetech-1.12-1.3.10.jar                            | None                                     |\n       | LCH   | immersivecables                   | 1.3.2                    | 沉浸线缆ImmersiveCables-1.12.2-1.3.2.jar                         | None                                     |\n       | LCH   | immersiveposts                    | 0.2.1                    | 沉浸网络ImmersivePosts-0.2.1.jar                                 | 0ba8738eadcf158e7fe1452255a73a022fb15feb |\n       | LCH   | kiwi                              | 0.5.3.32                 | Kiwi-1.12.2-0.5.3.32.jar                                     | None                                     |\n       | LCH   | cuisine                           | 0.5.21-build920          | Cuisine-0.5.21-build920.jar                                  | None                                     |\n       | LCH   | clochecall                        | 1.1.2                    | 沉浸附属ClocheCall-1.1.2.jar                                     | None                                     |\n       | LCH   | norecipebook                      | 1.2.1                    | 没有配方书noRecipeBook_v1.2.2formc1.12.2.jar                      | None                                     |\n       | LCH   | demagnetize                       | 1.12.2-1.1.2             | 消磁demagnetize-1.12.2-1.1.2.jar                               | None                                     |\n       | LCH   | woot                              | 1.12.2-1.4.11            | 生物工厂woot-1.12.2-1.4.11.jar                                   | None                                     |\n       | LCH   | bonsaitrees                       | 1.1.4                    | 盆栽bonsaitrees-1.1.4-b170.jar                                 | None                                     |\n       | LCH   | magneticraft                      | 2.7.0                    | 磁场工艺Magneticraft_1.12-2.8.3-dev.jar                          | None                                     |\n       | LCH   | modelloader                       | 1.1.7                    | 磁场工艺前置modelloader-1.1.7.jar                                  | None                                     |\n       | LCH   | mystcraft                         | 0.13.7.06                | 神秘岛mystcraft-1.12.2-0.13.7.06.jar                            | None                                     |\n       | LCH   | glassential                       | 1.1.0                    | 精致玻璃glassential-1.12.2-1.1.0.jar                             | None                                     |\n       | LCH   | ropebridge                        | 2.0.5                    | 索桥ropebridge-1.12-2.0.7.jar                                  | None                                     |\n       | LCH   | rs_ctr                            | 0.3.1                    | 红石控制RedstoneControl-1.12.2-0.3.1.3.jar                       | None                                     |\n       | LCH   | rsgauges                          | 1.2.5                    | 红石测量与开关rsgauges-1.12.2-1.2.5.jar                             | ed58ed655893ced6280650866985abcae2bf7559 |\n       | LCH   | ambientsounds                     | 3.0                      | 自然音效3,可能只能客户端AmbientSounds_v3.0.18_mc1.12.2.jar              | None                                     |\n       | LCH   | itorch                            | 1.2.1                    | 色火把itorch-1.2.1.jar                                          | None                                     |\n       | LCH   | communism                         | 1.1                      | 苏维埃工坊communism-1.1.2.jar                                     | None                                     |\n       | LCH   | soviet                            | 0.5                      | 苏维埃风格装饰Soviet+Era+0.5.jar                                    | None                                     |\n       | LCH   | voidcraft                         | 0.26.10                  | 虚空工艺-1.12.2.jar                                              | None                                     |\n       | LCH   | signpost                          | 1.08.3                   | 路标signpost-1.12.2-1.08.3.jar                                 | None                                     |\n       | LCH   | controlling                       | 3.0.10                   | 键位冲突显示Controlling-3.0.10.jar                                 | None                                     |\n       | LCH   | winterwonderland                  | 1.2.2                    | 霏雪寄语之地WinterWonderLand-1.2.2.jar                             | None                                     |\n       | LCH   | mukaimods                         | 0.2.3                    | 音游mukaimods-0.3.8.jar                                        | None                                     |\n       | LCH   | ctgui                             | 1.0.0                    | 魔改器CraftTweaker2-1.12-4.1.20.614.jar                         | None                                     |\n       | LCH   | crafttweakerjei                   | 2.0.3                    | 魔改器CraftTweaker2-1.12-4.1.20.614.jar                         | None                                     |\n       | LCH   | rtg                               | 6.1.0.0-snapshot.1       | RTG-1.12.2-6.1.0.0-snapshot.1.jar                            | None                                     |\n       | LCH   | rf-capability-adapter             | 1.1.1                    | [MECA]ME功能适配器capabilityadapter-1.1.1.jar                     | None                                     |\n       | LCH   | clothesline                       | 1.12.2-0.0.2.2           | 晾衣绳clothesline-1.12.2-0.0.3.0-bundle.jar                     | 3bae2d07b93a5971335cb2de15230c19c103db32 |\n       | LCH   | snowrealmagic                     | 0.3.3                    | 雪真实的魔法SnowRealMagic-0.3.3.jar                                | None                                     |\n       | LCH   | adchimneys                        | 1.12.2-3.5.12.1          | AdChimneys-1.12.2-3.5.12.1-build.0460.jar                    | None                                     |\n  Loaded coremods (and transformers): EngineersDoorsLoadingPlugin (engineers_doors-1.12.2-0.9.1.jar)\n                                        nihiltres.engineersdoors.common.asm.EngineersDoorsClassTransformer\n                                      FcCoreMod ([ae2fc]ae2fc-1.12.2-1.0.9.jar)\n                                        xyz.phanta.ae2fc.coremod.FcClassTransformer\n                                      TransformerLoader (OpenComputers-MC1.12.2-1.7.5.192.jar)\n                                        li.cil.oc.common.asm.ClassTransformer\n                                      clothesline-hooks (clothesline-hooks-1.12.2-0.0.1.2.jar)\n                                        com.jamieswhiteshirt.clothesline.hooks.plugin.ClassTransformer\n                                      MekanismCoremod (Mekanism-1.12.2-9.8.3.390.jar)\n                                        mekanism.coremod.KeybindingMigrationHelper\n                                      CorePlugin (ForgeEndertech-1.12.2-4.5.2.0-build.0459.jar)\n\n                                      IELoadingPlugin (ImmersiveEngineering-core-0.12-92.jar)\n                                        blusunrize.immersiveengineering.common.asm.IEClassTransformer\n                                      CoreModLoader (Sound-Physics-1.12.2-1.0.10-1.jar)\n                                        com.sonicether.soundphysics.CoreModInjector\n                                      EnderCorePlugin (EnderCore-1.12.2-0.5.76-core.jar)\n                                        com.enderio.core.common.transform.EnderCoreTransformer\n                                        com.enderio.core.common.transform.SimpleMixinPatcher\n                                      JustEnoughIDs Extension Plugin (JustEnoughIDs-1.0.3-55.jar)\n                                        org.dimdev.jeid.JEIDTransformer\n                                      MalisisCorePlugin (malisiscore-1.12.2-6.5.1.jar)\n\n                                      CoreMod (Aroma1997Core-1.12.2-2.0.0.2.jar)\n\n                                      ColorUtilityCorePlugin (ColorUtility-universal-1.0.4.jar)\n                                        com.Axeryok.ColorUtility.ColorUtilityTransformer\n                                      FarseekCoreMod (Farseek-1.12-2.5.jar)\n                                        farseek.core.FarseekClassTransformer\n                                      OFMDepLoader (OpenFM-1.12.2-0.1.0.19.jar)\n\n                                      ForgelinPlugin (Forgelin-1.8.4.jar)\n\n                                      JechCore (通用拼音搜索JustEnoughCharacters-1.12.0-3.4.8.jar)\n                                        me.towdium.jecharacters.core.JechClassTransformer\n                                      OpenModsCorePlugin (OpenModsLib-1.12.2-0.12.2.jar)\n                                        openmods.core.OpenModsClassTransformer\n                                      HCASM (HammerLib-1.12.2-2.0.6.14.jar)\n                                        com.zeitheron.hammercore.asm.HammerCoreTransformer\n                                      CTMCorePlugin (CTM-MC1.12.2-1.0.2.31.jar)\n                                        team.chisel.ctm.client.asm.CTMTransformer\n                                      VanillaFixLoadingPlugin (VanillaFix-1.0.10-150.jar)\n\n                                      CreativePatchingLoader (CreativeCore_v1.10.44_mc1.12.2.jar)\n\n                                      Do not report to Forge! (If you haven't disabled the FoamFix coremod, try disabling it in the config! Note that this bit of text will still appear.) (foamfix-0.10.10-1.12.2.jar)\n                                        pl.asie.foamfix.coremod.FoamFixTransformer\n                                      LoadingPlugin (AdChimneys-1.12.2-3.5.12.1-build.0460.jar)\n                                        com.endertech.minecraft.mods.adchimneys.world.WorldData$BlockRandomTick\n                                      Inventory Tweaks Coremod ([R键整理]InventoryTweaks-1.63.jar)\n                                        invtweaks.forge.asm.ContainerTransformer\n  GL info: ' Vendor: 'NVIDIA Corporation' Version: '4.6.0 NVIDIA 456.71' Renderer: 'GeForce GTX 750 Ti/PCIe/SSE2'\n  OpenModsLib class transformers: [llama_null_fix:FINISHED],[horse_base_null_fix:FINISHED],[pre_world_render_hook:FINISHED],[player_render_hook:FINISHED],[horse_null_fix:FINISHED]\n  AE2 Version: stable rv6-stable-7 for Forge 14.23.5.2768\n  Ender IO: Found the following problem(s) with your installation (That does NOT mean that Ender IO caused the crash or was involved in it in any way. We add this information to help finding common problems, not as an invitation to post any crash you encounter to Ender IO's issue tracker. Always check the stack trace above to see which mod is most likely failing.):\n                              * Optifine is installed. This is NOT supported.\n                             This may (look up the meaning of 'may' in the dictionary if you're not sure what it means) have caused the error. Try reproducing the crash WITHOUT this/these mod(s) before reporting it.\n            Authlib is : /C:/Users/lenovo/Desktop/233/.minecraft/libraries/com/mojang/authlib/1.5.25/authlib-1.5.25.jar\n\n            !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n            !!!You are looking at the diagnostics information, not at the crash.       !!!\n            !!!Scroll up until you see the line with '---- Minecraft Crash Report ----'!!!\n            !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n  HammerCore Debug Information: Dependent Mods:\n                                    -Botanic Additions (botanicadds) @12.2.5\n  List of loaded APIs: * AgriCraftAPI (1.0) from AgriCraft-2.12.0-1.12.0-a6.jar\n                       * appliedenergistics2|API (rv6) from appliedenergistics2-rv6-stable-7.jar\n                       * Baubles|API (1.4.0.2) from Baubles-1.12-1.5.2.jar\n                       * bigreactors|API (4.0.1) from 大型反应ExtremeReactors-1.12.2-0.4.5.67.jar\n                       * BotaniaAPI (93) from Botania+r1.10-363.jar\n                       * calculatorapi (1.9.4 - 1.0) from [CC]运算工艺calculator-1.12.2-5.0.12-15.jar\n                       * ChiselsAndBitsAPI (14.25.0) from chiselsandbits-14.33.jar\n                       * cofhapi (2.5.0) from CoFHCore-1.12.2-4.6.6.1-universal.jar\n                       * ComputerCraft|API (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|FileSystem (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|Lua (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|Media (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|Network (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|Network|Wired (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|Peripheral (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|Permissions (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|Redstone (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|Turtle (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * ComputerCraft|API|Turtle|Event (1.86.2) from cc-tweaked-1.12.2-1.86.2.jar\n                       * CoroAI|dynamicdifficulty (1.0) from 孔明灯，天灯前置coroutil-1.12.1-1.2.37.jar\n                       * CSLib|API (1.0.1) from PTRLib-1.0.4.jar\n                       * ctm-api (0.1.0) from CTM-MC1.12.2-1.0.2.31.jar\n                       * ctm-api-events (0.1.0) from CTM-MC1.12.2-1.0.2.31.jar\n                       * ctm-api-models (0.1.0) from CTM-MC1.12.2-1.0.2.31.jar\n                       * ctm-api-textures (0.1.0) from CTM-MC1.12.2-1.0.2.31.jar\n                       * ctm-api-utils (0.1.0) from CTM-MC1.12.2-1.0.2.31.jar\n                       * DraconicEvolution|API (1.3) from Draconic-Evolution-1.12.2-2.3.25.351-universal.jar\n                       * enderioapi (4.0.0) from EnderIO-1.12.2-5.2.66.jar\n                       * enderioapi|addon (4.0.0) from EnderIO-1.12.2-5.2.66.jar\n                       * enderioapi|capacitor (4.0.0) from EnderIO-1.12.2-5.2.66.jar\n                       * enderioapi|conduits (4.0.0) from EnderIO-1.12.2-5.2.66.jar\n                       * enderioapi|farm (4.0.0) from EnderIO-1.12.2-5.2.66.jar\n                       * enderioapi|redstone (4.0.0) from EnderIO-1.12.2-5.2.66.jar\n                       * enderioapi|teleport (4.0.0) from EnderIO-1.12.2-5.2.66.jar\n                       * enderioapi|tools (4.0.0) from EnderIO-1.12.2-5.2.66.jar\n                       * enderioapi|upgrades (4.0.0) from EnderIO-1.12.2-5.2.66.jar\n                       * ForgeEndertechAPI (1.0) from ForgeEndertech-1.12.2-4.5.2.0-build.0459.jar\n                       * ImmersiveEngineering|API (1.0) from ImmersiveEngineering-0.12-92.jar\n                       * ImmersiveEngineering|ImmersiveFluxAPI (1.0) from ImmersiveEngineering-0.12-92.jar\n                       * jeresources|API (0.9.2.60) from JEI扩展JustEnoughResources-1.12.2-0.9.2.60.jar\n                       * journeymap|client-api (1.4) from [JM流行地图]journeymap-1.12.2-5.7.1.jar\n                       * journeymap|client-api-display (1.4) from [JM流行地图]journeymap-1.12.2-5.7.1.jar\n                       * journeymap|client-api-event (1.4) from [JM流行地图]journeymap-1.12.2-5.7.1.jar\n                       * journeymap|client-api-model (1.4) from [JM流行地图]journeymap-1.12.2-5.7.1.jar\n                       * journeymap|client-api-util (1.4) from [JM流行地图]journeymap-1.12.2-5.7.1.jar\n                       * JustEnoughItemsAPI (4.13.0) from jei_1.12.2-4.15.0.291.jar\n                       * lightningcraftAPI (2.9.0) from lightningcraft-2.9.7_1.jar\n                       * MagneticraftAPI (1.0.0) from 磁场工艺Magneticraft_1.12-2.8.3-dev.jar\n                       * MekanismAPI|core (9.8.1) from Mekanism-1.12.2-9.8.3.390-api.jar\n                       * MekanismAPI|energy (9.8.1) from Mekanism-1.12.2-9.8.3.390.jar\n                       * MekanismAPI|gas (9.8.1) from Mekanism-1.12.2-9.8.3.390-api.jar\n                       * MekanismAPI|infuse (9.8.1) from Mekanism-1.12.2-9.8.3.390-api.jar\n                       * MekanismAPI|laser (9.8.1) from Mekanism-1.12.2-9.8.3.390.jar\n                       * MekanismAPI|transmitter (9.8.1) from Mekanism-1.12.2-9.8.3.390.jar\n                       * MekanismAPI|util (9.0.0) from Mekanism-1.12.2-9.8.3.390-api.jar\n                       * MoarSigns|API (1.3) from 更多牌子MoarSigns-1.12.2-6.0.0.11.jar\n                       * Model-Loader (1.1.0) from 磁场工艺前置modelloader-1.1.7.jar\n                       * Model-Loader-Vectors (1.0.0) from 磁场工艺前置modelloader-1.1.7.jar\n                       * Mystcraft|API (0.2) from 神秘岛mystcraft-1.12.2-0.13.7.06.jar\n                       * openblocks|api (1.2) from OpenBlocks-1.12.2-1.8.1.jar\n                       * opencomputersapi|component (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|core (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|driver (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|driver|item (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|event (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|filesystem (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|internal (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|machine (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|manual (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|network (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * opencomputersapi|prefab (7.0.0-alpha) from OpenComputers-MC1.12.2-1.7.5.192.jar\n                       * PatchouliAPI (6) from Patchouli-1.0-20.jar\n                       * PneumaticCraftApi (1.1) from pneumaticcraft-repressurized-1.12.2-0.11.14-395.jar\n                       * projectred|api (2.1) from ProjectRed-1.12.2-4.9.4.120-Base.jar\n                       * redstonefluxapi (2.1.1) from RedstoneFlux-1.12-2.1.1.1-universal.jar\n                       * rtgapi (1.0.0) from RTG-1.12.2-6.1.0.0-snapshot.1.jar\n                       * sonarapi (1.0.1) from sonarcore-1.12.2-5.0.19-20.jar\n                       * TeaStoryAPI (3.3.3-B32.404-1.12.2) from TeaStory-3.3.3-B32.404-1.12.2.jar\n                       * valkyrielib.api (1.12.2-2.0.10a) from valkyrielib-1.12.2-2.0.20.1.jar\n                       * WailaAPI (1.3) from [高亮显示]Hwyla-1.8.26-B41_1.12.2.jar\n                       * zerocore|API|multiblock (1.10.2-0.0.2) from 大反应前置zerocore-1.12.2-0.1.2.8.jar\n                       * zerocore|API|multiblock|rectangular (1.10.2-0.0.2) from 大反应前置zerocore-1.12.2-0.1.2.8.jar\n                       * zerocore|API|multiblock|tier (1.10.2-0.0.2) from 大反应前置zerocore-1.12.2-0.1.2.8.jar\n                       * zerocore|API|multiblock|validation (1.10.2-0.0.2) from 大反应前置zerocore-1.12.2-0.1.2.8.jar\n  Suspected Mods: Unknown\n  Launched Version: HMCL 3.3.181\n  LWJGL: 2.9.4\n  OpenGL: GeForce GTX 750 Ti/PCIe/SSE2 GL version 4.6.0 NVIDIA 456.71, NVIDIA Corporation\n  GL Caps: Using GL 1.3 multitexturing.\n           Using GL 1.3 texture combiners.\n           Using framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\n           Shaders are available because OpenGL 2.1 is supported.\n           VBOs are available because OpenGL 1.5 is supported.\n  Using VBOs: Yes\n  Is Modded: Definitely; Client brand changed to 'fml,forge'\n  Type: Client (map_client.txt)\n  Resource Packs: Minecraft-Mod-Language-Modpack.zip\n  Current Language: 简体中文 (中国)\n  Profiler Position: N/A (disabled)\n  CPU: 8x Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz\n"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/processing_of_javaagent_failed.txt",
    "content": "Exception in thread \"main\" java.lang.ClassNotFoundException: org.glavo.log4j.patch.agent.Log4jAgent\r\n\tat java.net.URLClassLoader.findClass(Unknown Source)\r\n\tat java.lang.ClassLoader.loadClass(Unknown Source)\r\nFATAL ERROR in native method: processing of -javaagent failed\r\n\tat sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)\r\n\tat java.lang.ClassLoader.loadClass(Unknown Source)\r\n\tat sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)\r\n\tat sun.instrument.InstrumentationImpl.loadClassAndCallPremain(Unknown Source)\r\n"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/resourcepack_resolution.txt",
    "content": "---- Minecraft Crash Report ----\n// Why is it breaking :(\n\nTime: 05/09/13 15:56\nDescription: Registering texture\n\nbkr: Unable to fit: waterlily - size: 128x128 - Maybe try a lowerresolution texturepack?\n\tat bko.c(SourceFile:63)\n\tat bks.b(SourceFile:87)\n\tat bks.a(SourceFile:54)\n\tat bku.a(SourceFile:69)\n\tat bku.a(SourceFile:133)\n\tat bmf.c(SourceFile:99)\n\tat bmf.a(SourceFile:87)\n\tat avx.b(SourceFile:418)\n\tat bcl.a(SourceFile:146)\n\tat ayr.a(SourceFile:70)\n\tat bcl.a(SourceFile:153)\n\tat ayr.d(SourceFile:133)\n\tat ayr.m(SourceFile:109)\n\tat avx.m(SourceFile:1202)\n\tat avx.V(SourceFile:686)\n\tat avx.e(SourceFile:642)\n\tat net.minecraft.client.main.Main.main(SourceFile:103)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat bko.c(SourceFile:63)\n\tat bks.b(SourceFile:87)\n\tat bks.a(SourceFile:54)\n\n-- Resource location being registered --\nDetails:\n\tResource location: minecraft:textures/atlas/blocks.png\n\tTexture object class: bks\nStacktrace:\n\tat bku.a(SourceFile:69)\n\tat bku.a(SourceFile:133)\n\tat bmf.c(SourceFile:99)\n\tat bmf.a(SourceFile:87)\n\tat avx.b(SourceFile:418)\n\tat bcl.a(SourceFile:146)\n\tat ayr.a(SourceFile:70)\n\tat bcl.a(SourceFile:153)\n\tat ayr.d(SourceFile:133)\n\tat ayr.m(SourceFile:109)\n\n-- Affected screen --\nDetails:\n\tScreen name: bcl\nStacktrace:\n\tat avx.m(SourceFile:1202)\n\tat avx.V(SourceFile:686)\n\tat avx.e(SourceFile:642)\n\tat net.minecraft.client.main.Main.main(SourceFile:103)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 13w36a\n\tOperating System: Windows Vista (x86) version 6.0\n\tJava Version: 1.7.0_25, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) Client VM (mixed mode), Oracle Corporation\n\tMemory: 73035976 bytes (69 MB) / 171958272 bytes (163 MB) up to 1037959168 bytes (989 MB)\n\tJVM Flags: 2 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx1G\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tSuspicious classes: [com.ibm.icu.text.ArabicShapingException], [gnu.trove.TIntCollection], [gnu.trove.impl.HashFunctions, PrimeFinder], [gnu.trove.impl.hash.THash, TPrimitiveHash, TIntIntHash], [gnu.trove.iterator.TIterator, TAdvancingIterator, TIntIntIterator], [gnu.trove.map.TIntIntMap], [gnu.trove.map.hash.TIntIntHashMap], [gnu.trove.procedure.TIntIntProcedure], [gnu.trove.set.TIntSet]\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tLaunched Version: 13w36a\n\tLWJGL: 2.9.0\n\tOpenGL: Intel 965/963 Graphics Media Accelerator GL version 1.5.0 - Build 7.14.10.1437, Intel\n\tIs Modded: Probably not. Jar signature remains and client brand is untouched.\n\tType: Client (map_client.txt)\n\tResource Packs: [Herobrine-Craft-Pre-26.zip, Sphax PureBDcraft 128x MC16.zip]\n\tCurrent Language: English (US)\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: ~~ERROR~~ NullPointerException: null"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/rtss_forest_sodium.txt",
    "content": "---- Minecraft Crash Report ----\n// My bad.\n\nTime: 2024-02-05 22:59:34\nDescription: Initializing game\n\njava.lang.RuntimeException: RivaTuner Statistics Server (RTSS) is not compatible with Sodium, see here for more details: https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible\r\n\tat me.jellysquid.mods.sodium.client.compatibility.checks.ModuleScanner.checkModules(ModuleScanner.java:36)\r\n\tat net.minecraft.class_1041.handler$cda000$sodium$postWindowCreated(class_1041.java:2572)\r\n\tat net.minecraft.class_1041.<init>(class_1041.java:109)\r\n\tat net.minecraft.class_3682.method_16038(class_3682.java:21)\r\n\tat net.minecraft.class_310.<init>(class_310.java:515)\r\n\tat net.minecraft.client.main.Main.main(Main.java:223)\r\n\tat net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:470)\r\n\tat net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74)\r\n\tat net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23)\r\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Render thread\nStacktrace:\n\tat me.jellysquid.mods.sodium.client.compatibility.checks.ModuleScanner.checkModules(ModuleScanner.java:36)\n\tat net.minecraft.class_1041.handler$cda000$sodium$postWindowCreated(class_1041.java:2572)\n\tat net.minecraft.class_1041.<init>(class_1041.java:109)\n\tat net.minecraft.class_3682.method_16038(class_3682.java:21)\n\tat net.minecraft.class_310.<init>(class_310.java:515)\n\n-- Initialization --\nDetails:\n\tModules: \n\t\tADVAPI32.dll:楂樼骇 Windows 32 鍩烘湰 API:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tCOMCTL32.dll:鐢ㄦ埛浣撻獙鎺т欢搴�6.10 (WinBuild.160101.0800):Microsoft Corporation\n\t\tCRYPT32.dll:鍔犲瘑 API32:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tCRYPTBASE.dll:Base cryptographic API DLL:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\tCRYPTSP.dll:Cryptographic Service Provider API:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\tCoreMessaging.dll:Microsoft CoreMessaging Dll:10.0.19041.2193:Microsoft Corporation\n\t\tCoreUIComponents.dll:Microsoft Core UI Components Dll:10.0.19041.546:Microsoft Corporation\n\t\tDBGHELP.DLL:Windows Image Helper:10.0.19041.867 (WinBuild.160101.0800):Microsoft Corporation\n\t\tDEVOBJ.dll:Device Information Set DLL:10.0.19041.1620 (WinBuild.160101.0800):Microsoft Corporation\n\t\tDNSAPI.dll:DNS 瀹㈡埛绔� API DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tGDI32.dll:GDI Client DLL:10.0.19041.2130 (WinBuild.160101.0800):Microsoft Corporation\n\t\tGLU32.dll:OpenGL 瀹炵敤宸ュ叿搴� DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tIMM32.DLL:Multi-User Windows IMM32 API Client DLL:10.0.19041.2193 (WinBuild.160101.0800):Microsoft Corporation\n\t\tIPHLPAPI.DLL:IP 甯姪绋嬪簭 API:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tKERNEL32.DLL:Windows NT 鍩烘湰 API 瀹㈡埛绔� DLL:10.0.19041.1741 (WinBuild.160101.0800):Microsoft Corporation\n\t\tKERNELBASE.dll:Windows NT 鍩烘湰 API 瀹㈡埛绔� DLL:10.0.19041.1741 (WinBuild.160101.0800):Microsoft Corporation\n\t\tMSCTF.dll:MSCTF 鏈嶅姟鍣� DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tNLAapi.dll:Network Location Awareness 2:10.0.19041.2193 (WinBuild.160101.0800):Microsoft Corporation\n\t\tNSI.dll:NSI User-mode interface DLL:10.0.19041.610 (WinBuild.160101.0800):Microsoft Corporation\n\t\tNTASN1.dll:Microsoft ASN.1 API:10.0.19041.1 (WinBuild.160101.0800):Microsoft Corporation\n\t\tOle32.dll:鐢ㄤ簬 Windows 鐨� Microsoft OLE:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tOleAut32.dll:OLEAUT32.DLL:10.0.19041.985 (WinBuild.160101.0800):Microsoft Corporation\n\t\tPOWRPROF.dll:鐢垫簮閰嶇疆鏂囦欢甯姪绋嬪簭 DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tPROPSYS.dll:Microsoft 灞炴�х郴缁�7.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tPSAPI.DLL:Process Status Helper:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\tPdh.dll:Windows 鎬ц兘鏁版嵁鍔╂墜 DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tRPCRT4.dll:杩滅▼杩囩▼璋冪敤杩愯鏃�10.0.19041.1 (WinBuild.160101.0800):Microsoft Corporation\n\t\tRTSSHooks64.dll\n\t\tSETUPAPI.dll:Windows 瀹夎绋嬪簭 API:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tSHCORE.dll:SHCORE:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tSHELL32.dll:Windows Shell 鍏敤 DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tUMPDC.dll\n\t\tUSER32.dll:澶氱敤鎴� Windows 鐢ㄦ埛 API 瀹㈡埛绔� DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tUSERENV.dll:Userenv:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tVCRUNTIME140.dll:Microsoft庐 C Runtime Library:14.29.30139.0 built by: vcwrkspc:Microsoft Corporation\n\t\tVERSION.dll:Version Checking and File Installation Libraries:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\tWINHTTP.dll:Windows HTTP 鏈嶅姟:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tWINMM.dll:MCI API DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tWINSTA.dll:Winstation Library:10.0.19041.2075 (WinBuild.160101.0800):Microsoft Corporation\n\t\tWINTRUST.dll:Microsoft Trust Verification APIs:10.0.19041.2193 (WinBuild.160101.0800):Microsoft Corporation\n\t\tWS2_32.dll:Windows Socket 2.0 32 浣� DLL:10.0.19041.1081 (WinBuild.160101.0800):Microsoft Corporation\n\t\tWSOCK32.dll:Windows Socket 32-Bit DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tWTSAPI32.dll:Windows Remote Desktop Session Host Server SDK APIs:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\tWldp.dll:Windows 閿佸畾绛栫暐:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tamsi.dll:Anti-Malware Scan Interface:10.0.19041.2075 (WinBuild.160101.0800):Microsoft Corporation\n\t\tbcrypt.dll:Windows 鍔犲瘑鍩哄厓搴�10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tbcryptPrimitives.dll:Windows Cryptographic Primitives Library:10.0.19041.1415 (WinBuild.160101.0800):Microsoft Corporation\n\t\tcfgmgr32.dll:Configuration Manager DLL:10.0.19041.1620 (WinBuild.160101.0800):Microsoft Corporation\n\t\tclbcatq.dll:COM+ Configuration Catalog:2001.12.10941.16384 (WinBuild.160101.0800):Microsoft Corporation\n\t\tcombase.dll:鐢ㄤ簬 Windows 鐨� Microsoft COM:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tcryptnet.dll:Crypto Network Related API:10.0.19041.906 (WinBuild.160101.0800):Microsoft Corporation\n\t\tdbgcore.DLL:Windows Core Debugging Helpers:10.0.19041.789 (WinBuild.160101.0800):Microsoft Corporation\n\t\tdhcpcsvc.DLL:DHCP 瀹㈡埛绔湇鍔�10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tdhcpcsvc6.DLL:DHCPv6 瀹㈡埛绔�10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tdinput8.dll:Microsoft DirectInput:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tdrvstore.dll:Driver Store API:10.0.19041.1949 (WinBuild.160101.0800):Microsoft Corporation\n\t\tdwmapi.dll:Microsoft 妗岄潰绐楀彛绠＄悊鍣� API:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tdxcore.dll:DXCore:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\textnet.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tfwpuclnt.dll:FWP/IPsec 鐢ㄦ埛妯″紡 API:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tgdi32full.dll:GDI Client DLL:10.0.19041.2130 (WinBuild.160101.0800):Microsoft Corporation\n\t\tglfw.dll:GLFW 3.4.0 DLL:3.4.0:GLFW\n\t\tiertutil.dll:Internet Explorer 鐨勮繍琛屾椂瀹炵敤绋嬪簭:11.00.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tinputhost.dll:InputHost:10.0.19041.1741 (WinBuild.160101.0800):Microsoft Corporation\n\t\tjava.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tjava.exe:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tjemalloc.dll\n\t\tjimage.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tjli.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tjna17843680146815234223.dll:JNA native library:6.1.6:Java(TM) Native Access (JNA)\n\t\tjsvml.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tjvm.dll:OpenJDK 64-Bit server VM:17.0.10.0:Microsoft\n\t\tkernel.appcore.dll:AppModel API Host:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\tlwjgl.dll\n\t\tlwjgl_opengl.dll\n\t\tmanagement.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tmanagement_ext.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tmsasn1.dll:ASN.1 Runtime APIs:10.0.19041.2251 (WinBuild.160101.0800):Microsoft Corporation\n\t\tmsvcp140.dll:Microsoft庐 C Runtime Library:14.29.30139.0 built by: vcwrkspc:Microsoft Corporation\n\t\tmsvcp_win.dll:Microsoft庐 C Runtime Library:10.0.19041.789 (WinBuild.160101.0800):Microsoft Corporation\n\t\tmsvcrt.dll:Windows NT CRT DLL:7.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\tmswsock.dll:Microsoft Windows Sockets 2.0 鏈嶅姟鎻愪緵绋嬪簭:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tnapinsp.dll:鐢靛瓙閭欢鍛藉悕濉厖鎻愪緵绋嬪簭:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tncrypt.dll:Windows NCrypt 璺敱鍣�10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tnet.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tnetutils.dll:Net Win32 API Helpers DLL:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\tnio.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\tntdll.dll:NT 灞� DLL:10.0.19041.1741 (WinBuild.160101.0800):Microsoft Corporation\n\t\tntmarta.dll:Windows NT MARTA 鎻愪緵绋嬪簭:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tnvoglv64.dll:NVIDIA Compatible OpenGL ICD:31.0.15.3179:NVIDIA Corporation\n\t\tnvspcap64.dll:NVIDIA Game Proxy:3.27.0.120:NVIDIA Corporation\n\t\topengl32.dll:OpenGL Client DLL:10.0.19041.2193 (WinBuild.160101.0800):Microsoft Corporation\n\t\tperfos.dll:Windows 绯荤粺鎬ц兘瀵硅薄 DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tpnrpnsp.dll:PNRP 鍛藉悕绌洪棿鎻愪緵绋嬪簭:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tprofapi.dll:User Profile Basic API:10.0.19041.844 (WinBuild.160101.0800):Microsoft Corporation\n\t\trasadhlp.dll:Remote Access AutoDial Helper:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\trsaenh.dll:Microsoft Enhanced Cryptographic Provider:10.0.19041.1 (WinBuild.160101.0800):Microsoft Corporation\n\t\tsechost.dll:Host for SCM/SDDL/LSA Lookup APIs:10.0.19041.1 (WinBuild.160101.0800):Microsoft Corporation\n\t\tshlwapi.dll:澶栧３绠�鏄撳疄鐢ㄥ伐鍏峰簱:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tsrvcli.dll:Server Service Client DLL:10.0.19041.1645 (WinBuild.160101.0800):Microsoft Corporation\n\t\tsunmscapi.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\ttextinputframework.dll:\"TextInputFramework.DYNLINK\":10.0.19041.2075 (WinBuild.160101.0800):Microsoft Corporation\n\t\tucrtbase.dll:Microsoft庐 C Runtime Library:10.0.19041.789 (WinBuild.160101.0800):Microsoft Corporation\n\t\turlmon.dll:Win32 鐨� OLE32 鎵╁睍:11.00.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tuxtheme.dll:Microsoft UxTheme 搴�10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tvcruntime140_1.dll:Microsoft庐 C Runtime Library:14.29.30139.0 built by: vcwrkspc:Microsoft Corporation\n\t\tverify.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\n\t\twin32u.dll:Win32u:10.0.19041.2251 (WinBuild.160101.0800):Microsoft Corporation\n\t\twindows.storage.dll:Microsoft WinRT Storage API:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\twinrnr.dll:LDAP RnR Provider DLL:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\twintypes.dll:Windows 鍩烘湰绫诲瀷 DLL:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\twshbth.dll:Windows Sockets Helper DLL:10.0.19041.546 (WinBuild.160101.0800):Microsoft Corporation\n\t\twshunix.dll:AF_UNIX Winsock2 Helper DLL:10.0.19041.1 (WinBuild.160101.0800):Microsoft Corporation\n\t\txinput1_4.dll:Microsoft 鍏叡鎺у埗鍣� API:10.0.19041.3203 (WinBuild.160101.0800):Microsoft Corporation\n\t\tzip.dll:OpenJDK Platform binary:17.0.10.0:Microsoft\nStacktrace:\n\tat net.minecraft.client.main.Main.main(Main.java:223)\n\tat net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:470)\n\tat net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74)\n\tat net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.20.4\n\tMinecraft Version ID: 1.20.4\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 17.0.10, Microsoft\n\tJava VM Version: OpenJDK 64-Bit Server VM (mixed mode, sharing), Microsoft\n\tMemory: 376107072 bytes (358 MiB) / 805306368 bytes (768 MiB) up to 7650410496 bytes (7296 MiB)\n\tCPUs: 8\n\tProcessor Vendor: GenuineIntel\n\tProcessor Name: Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz\n\tIdentifier: Intel64 Family 6 Model 60 Stepping 3\n\tMicroarchitecture: Haswell (Client)\n\tFrequency (GHz): 3.29\n\tNumber of physical packages: 1\n\tNumber of physical CPUs: 4\n\tNumber of logical CPUs: 8\n\tGraphics card #0 name: NVIDIA GeForce GTX 960\n\tGraphics card #0 vendor: NVIDIA (0x10de)\n\tGraphics card #0 VRAM (MB): 4095.00\n\tGraphics card #0 deviceId: 0x1401\n\tGraphics card #0 versionInfo: DriverVersion=31.0.15.3179\n\tMemory slot #0 capacity (MB): 8192.00\n\tMemory slot #0 clockSpeed (GHz): 1.60\n\tMemory slot #0 type: DDR3\n\tMemory slot #1 capacity (MB): 8192.00\n\tMemory slot #1 clockSpeed (GHz): 1.60\n\tMemory slot #1 type: DDR3\n\tVirtual memory max (MB): 23006.26\n\tVirtual memory used (MB): 10964.91\n\tSwap memory total (MB): 6656.00\n\tSwap memory used (MB): 29.00\n\tJVM Flags: 11 total; -Xmx7275m -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tFabric Mods: \n\t\tanimatica: Animatica 0.6+1.20\n\t\tbobby: Bobby 5.0.3\n\t\t\tcom_typesafe_config: config 1.4.2\n\t\t\tio_leangen_geantyref_geantyref: geantyref 1.3.13\n\t\t\torg_spongepowered_configurate-core: configurate-core 4.1.2\n\t\t\torg_spongepowered_configurate-hocon: configurate-hocon 4.1.2\n\t\tcapes: Capes 1.5.3+1.20.2\n\t\tcloth-config: Cloth Config v13 13.0.121\n\t\t\tcloth-basic-math: cloth-basic-math 0.6.1\n\t\tcontinuity: Continuity 3.0.0-beta.4+1.20.2\n\t\tdynamic_fps: Dynamic FPS 3.3.3\n\t\tenhancedblockentities: Enhanced Block Entities 0.9.1+1.20.2\n\t\t\tadvanced_runtime_resource_pack: Runtime Resource Pack 0.8.0\n\t\t\tspruceui: SpruceUI 5.0.3+1.20.2\n\t\tentity_texture_features: Entity Texture Features 5.1.2\n\t\t\torg_apache_httpcomponents_httpmime: httpmime 4.5.10\n\t\tentityculling: EntityCulling 1.6.3.1\n\t\tfabric-api: Fabric API 0.94.0+1.20.4\n\t\t\tfabric-api-base: Fabric API Base 0.4.36+78d798af4f\n\t\t\tfabric-api-lookup-api-v1: Fabric API Lookup API (v1) 1.6.47+82b1bb3e4f\n\t\t\tfabric-biome-api-v1: Fabric Biome API (v1) 13.0.16+78d798af4f\n\t\t\tfabric-block-api-v1: Fabric Block API (v1) 1.0.15+78d798af4f\n\t\t\tfabric-block-view-api-v2: Fabric BlockView API (v2) 1.0.4+78d798af4f\n\t\t\tfabric-blockrenderlayer-v1: Fabric BlockRenderLayer Registration (v1) 1.1.46+78d798af4f\n\t\t\tfabric-client-tags-api-v1: Fabric Client Tags 1.1.7+78d798af4f\n\t\t\tfabric-command-api-v1: Fabric Command API (v1) 1.2.41+f71b366f4f\n\t\t\tfabric-command-api-v2: Fabric Command API (v2) 2.2.20+78d798af4f\n\t\t\tfabric-commands-v0: Fabric Commands (v0) 0.2.58+df3654b34f\n\t\t\tfabric-containers-v0: Fabric Containers (v0) 0.1.84+df3654b34f\n\t\t\tfabric-content-registries-v0: Fabric Content Registries (v0) 5.0.10+78d798af4f\n\t\t\tfabric-convention-tags-v1: Fabric Convention Tags 1.5.10+78d798af4f\n\t\t\tfabric-crash-report-info-v1: Fabric Crash Report Info (v1) 0.2.23+78d798af4f\n\t\t\tfabric-data-generation-api-v1: Fabric Data Generation API (v1) 13.1.20+78d798af4f\n\t\t\tfabric-dimensions-v1: Fabric Dimensions API (v1) 2.1.61+78d798af4f\n\t\t\tfabric-entity-events-v1: Fabric Entity Events (v1) 1.6.0+44c0f8c64f\n\t\t\tfabric-events-interaction-v0: Fabric Events Interaction (v0) 0.7.1+389931eb4f\n\t\t\tfabric-events-lifecycle-v0: Fabric Events Lifecycle (v0) 0.2.72+df3654b34f\n\t\t\tfabric-game-rule-api-v1: Fabric Game Rule API (v1) 1.0.46+78d798af4f\n\t\t\tfabric-item-api-v1: Fabric Item API (v1) 2.2.0+d6f2b0844f\n\t\t\tfabric-item-group-api-v1: Fabric Item Group API (v1) 4.0.21+78d798af4f\n\t\t\tfabric-key-binding-api-v1: Fabric Key Binding API (v1) 1.0.41+78d798af4f\n\t\t\tfabric-keybindings-v0: Fabric Key Bindings (v0) 0.2.39+df3654b34f\n\t\t\tfabric-lifecycle-events-v1: Fabric Lifecycle Events (v1) 2.2.30+78d798af4f\n\t\t\tfabric-loot-api-v2: Fabric Loot API (v2) 2.1.5+78d798af4f\n\t\t\tfabric-message-api-v1: Fabric Message API (v1) 6.0.5+78d798af4f\n\t\t\tfabric-mining-level-api-v1: Fabric Mining Level API (v1) 2.1.60+78d798af4f\n\t\t\tfabric-model-loading-api-v1: Fabric Model Loading API (v1) 1.0.8+78d798af4f\n\t\t\tfabric-models-v0: Fabric Models (v0) 0.4.7+9386d8a74f\n\t\t\tfabric-networking-api-v1: Fabric Networking API (v1) 3.1.5+b7e146354f\n\t\t\tfabric-object-builder-api-v1: Fabric Object Builder API (v1) 13.0.9+06274a474f\n\t\t\tfabric-particles-v1: Fabric Particles (v1) 1.1.7+78d798af4f\n\t\t\tfabric-recipe-api-v1: Fabric Recipe API (v1) 2.0.18+78d798af4f\n\t\t\tfabric-registry-sync-v0: Fabric Registry Sync (v0) 4.0.16+78d798af4f\n\t\t\tfabric-renderer-api-v1: Fabric Renderer API (v1) 3.2.4+78d798af4f\n\t\t\tfabric-renderer-indigo: Fabric Renderer - Indigo 1.5.4+78d798af4f\n\t\t\tfabric-renderer-registries-v1: Fabric Renderer Registries (v1) 3.2.51+df3654b34f\n\t\t\tfabric-rendering-data-attachment-v1: Fabric Rendering Data Attachment (v1) 0.3.42+73761d2e4f\n\t\t\tfabric-rendering-fluids-v1: Fabric Rendering Fluids (v1) 3.0.33+78d798af4f\n\t\t\tfabric-rendering-v0: Fabric Rendering (v0) 1.1.54+df3654b34f\n\t\t\tfabric-rendering-v1: Fabric Rendering (v1) 3.0.13+78d798af4f\n\t\t\tfabric-resource-conditions-api-v1: Fabric Resource Conditions API (v1) 2.3.14+78d798af4f\n\t\t\tfabric-resource-loader-v0: Fabric Resource Loader (v0) 0.11.16+78d798af4f\n\t\t\tfabric-screen-api-v1: Fabric Screen API (v1) 2.0.17+78d798af4f\n\t\t\tfabric-screen-handler-api-v1: Fabric Screen Handler API (v1) 1.3.53+78d798af4f\n\t\t\tfabric-sound-api-v1: Fabric Sound API (v1) 1.0.17+78d798af4f\n\t\t\tfabric-transfer-api-v1: Fabric Transfer API (v1) 4.0.8+e84342304f\n\t\t\tfabric-transitive-access-wideners-v1: Fabric Transitive Access Wideners (v1) 5.0.14+78d798af4f\n\t\tfabric-language-kotlin: Fabric Language Kotlin 1.10.17+kotlin.1.9.22\n\t\t\torg_jetbrains_kotlin_kotlin-reflect: kotlin-reflect 1.9.22\n\t\t\torg_jetbrains_kotlin_kotlin-stdlib: kotlin-stdlib 1.9.22\n\t\t\torg_jetbrains_kotlin_kotlin-stdlib-jdk7: kotlin-stdlib-jdk7 1.9.22\n\t\t\torg_jetbrains_kotlin_kotlin-stdlib-jdk8: kotlin-stdlib-jdk8 1.9.22\n\t\t\torg_jetbrains_kotlinx_atomicfu-jvm: atomicfu-jvm 0.23.1\n\t\t\torg_jetbrains_kotlinx_kotlinx-coroutines-core-jvm: kotlinx-coroutines-core-jvm 1.7.3\n\t\t\torg_jetbrains_kotlinx_kotlinx-coroutines-jdk8: kotlinx-coroutines-jdk8 1.7.3\n\t\t\torg_jetbrains_kotlinx_kotlinx-datetime-jvm: kotlinx-datetime-jvm 0.5.0\n\t\t\torg_jetbrains_kotlinx_kotlinx-serialization-cbor-jvm: kotlinx-serialization-cbor-jvm 1.6.2\n\t\t\torg_jetbrains_kotlinx_kotlinx-serialization-core-jvm: kotlinx-serialization-core-jvm 1.6.2\n\t\t\torg_jetbrains_kotlinx_kotlinx-serialization-json-jvm: kotlinx-serialization-json-jvm 1.6.2\n\t\tfabricloader: Fabric Loader 0.15.5\n\t\t\tmixinextras: MixinExtras 0.3.2\n\t\tfabricskyboxes: FabricSkyBoxes 0.7.3+mc1.20.2\n\t\tfsb-interop: FabricSkyBoxes Interop 1.3.6+mc1.20.2-build.52\n\t\tgammautils: Gamma Utils 1.7.18\n\t\timmediatelyfast: ImmediatelyFast 1.2.8+1.20.4\n\t\t\tnet_lenni0451_reflect: Reflect 1.3.1\n\t\tindium: Indium 1.0.28+mc1.20.4\n\t\tiris: Iris 1.6.14\n\t\t\tio_github_douira_glsl-transformer: glsl-transformer 2.0.0-pre13\n\t\t\torg_anarres_jcpp: jcpp 1.4.14\n\t\t\torg_antlr_antlr4-runtime: antlr4-runtime 4.11.1\n\t\tjava: OpenJDK 64-Bit Server VM 17\n\t\tminecraft: Minecraft 1.20.4\n\t\tmodernfix: ModernFix 5.12.1+mc1.20.4\n\t\tmodmenu: Mod Menu 9.0.0\n\t\tmoreculling: More Culling 1.20.4-0.21.0\n\t\t\tconditional-mixin: conditional mixin 0.3.2\n\t\t\tmixinsquared: MixinSquared 0.1.1\n\t\tno_fog: No Fog 1.3.5+1.16.5-1.20.4\n\t\toptigui: OptiGUI 2.1.7\n\t\t\toptiglue: OptiGlue 2.1.7-mc.1.19.3\n\t\t\torg_apache_commons_commons-text: commons-text 1.10.0\n\t\t\torg_ini4j_ini4j: ini4j 0.5.4\n\t\tpuzzle: Puzzle 1.6.1+1.20.4\n\t\t\tmidnightlib: MidnightLib 1.5.2\n\t\t\tpuzzle-base: Puzzle Base 1.6.1+1.20.4\n\t\t\tpuzzle-gui: Puzzle GUI 1.6.1+1.20.4\n\t\t\tpuzzle-models: Puzzle Models 1.6.1+1.20.4\n\t\t\tpuzzle-splashscreen: Puzzle Splash Screen 1.6.1+1.20.4\n\t\treeses-sodium-options: Reese's Sodium Options 1.7.0+mc1.20.2-build.97\n\t\tsodium: Sodium 0.5.5\n\t\tsodium-extra: Sodium Extra 0.5.3+mc1.20.2-build.114\n\t\t\tcaffeineconfig: CaffeineConfig 1.3.0+1.17\n\t\t\tcrowdin-translate: CrowdinTranslate 1.4+1.19.3\n\t\tyet_another_config_lib_v3: YetAnotherConfigLib 3.3.1+1.20.4\n\t\t\tcom_twelvemonkeys_common_common-image: common-image 3.10.0\n\t\t\tcom_twelvemonkeys_common_common-io: common-io 3.10.0\n\t\t\tcom_twelvemonkeys_common_common-lang: common-lang 3.10.0\n\t\t\tcom_twelvemonkeys_imageio_imageio-core: imageio-core 3.10.0\n\t\t\tcom_twelvemonkeys_imageio_imageio-metadata: imageio-metadata 3.10.0\n\t\t\tcom_twelvemonkeys_imageio_imageio-webp: imageio-webp 3.10.0\n\t\t\torg_quiltmc_parsers_gson: gson 0.2.1\n\t\t\torg_quiltmc_parsers_json: json 0.2.1\n\t\tzoomify: Zoomify 2.12.0\n\t\t\tcom_akuleshov7_ktoml-core-jvm: ktoml-core-jvm 0.5.0\n\t\t\tdev_isxander_settxi_settxi-core: settxi-core 2.10.6\n\t\t\tdev_isxander_settxi_settxi-kotlinx-serialization: settxi-kotlinx-serialization 2.10.6\n\tLaunched Version: NotiFine\n\tLauncher name: HMCL\n\tBackend library: LWJGL version 3.3.2-snapshot\n\tBackend API: Unknown\n\tWindow size: <not initialized>\n\tGL Caps: Using framebuffer using OpenGL 3.2\n\tGL debug messages: <disabled>\n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'fabric'\n\tUniverse: 404\n\tType: Client (map_client.txt)\n\tLocale: zh_CN\n\tCPU: <unknown>"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/security.txt",
    "content": "---- Minecraft Crash Report ----\n// Everything's going to plan. No, really, that was supposed to happen.\n\nTime: 15-10-30 上午11:23\nDescription: Initializing game\n\njava.lang.SecurityException: SHA1 digest error for assets/minecraft/texts/splashes.txt\n\tat sun.security.util.ManifestEntryVerifier.verify(Unknown Source)\n\tat java.util.jar.JarVerifier.processEntry(Unknown Source)\n\tat java.util.jar.JarVerifier.update(Unknown Source)\n\tat java.util.jar.JarVerifier$VerifierStream.read(Unknown Source)\n\tat java.io.FilterInputStream.read(Unknown Source)\n\tat sun.nio.cs.StreamDecoder.readBytes(Unknown Source)\n\tat sun.nio.cs.StreamDecoder.implRead(Unknown Source)\n\tat sun.nio.cs.StreamDecoder.read(Unknown Source)\n\tat java.io.InputStreamReader.read(Unknown Source)\n\tat java.io.BufferedReader.fill(Unknown Source)\n\tat java.io.BufferedReader.readLine(Unknown Source)\n\tat java.io.BufferedReader.readLine(Unknown Source)\n\tat net.minecraft.client.gui.GuiMainMenu.<init>(GuiMainMenu.java:89)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:519)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:808)\n\tat net.minecraft.client.main.Main.main(SourceFile:101)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:131)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:27)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat sun.security.util.ManifestEntryVerifier.verify(Unknown Source)\n\tat java.util.jar.JarVerifier.processEntry(Unknown Source)\n\tat java.util.jar.JarVerifier.update(Unknown Source)\n\tat java.util.jar.JarVerifier$VerifierStream.read(Unknown Source)\n\tat java.io.FilterInputStream.read(Unknown Source)\n\tat sun.nio.cs.StreamDecoder.readBytes(Unknown Source)\n\tat sun.nio.cs.StreamDecoder.implRead(Unknown Source)\n\tat sun.nio.cs.StreamDecoder.read(Unknown Source)\n\tat java.io.InputStreamReader.read(Unknown Source)\n\tat java.io.BufferedReader.fill(Unknown Source)\n\tat java.io.BufferedReader.readLine(Unknown Source)\n\tat java.io.BufferedReader.readLine(Unknown Source)\n\tat net.minecraft.client.gui.GuiMainMenu.<init>(GuiMainMenu.java:89)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:519)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:808)\n\tat net.minecraft.client.main.Main.main(SourceFile:101)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:131)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:27)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.6.4\n\tOperating System: Windows 7 (x86) version 6.1\n\tJava Version: 1.7.0_79, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) Client VM (mixed mode), Oracle Corporation\n\tMemory: 49779456 bytes (47 MB) / 227700736 bytes (217 MB) up to 1037959168 bytes (989 MB)\n\tJVM Flags: 1 total; -Xmx1024m\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tSuspicious classes: FML and Forge are installed\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP v8.11 FML v6.4.49.965 Minecraft Forge 9.11.1.965 Optifine OptiFine_1.6.4_HD_U_D1[hukk汉化] 21 mods loaded, 21 mods active\n\tmcp{8.09} [Minecraft Coder Pack] (minecraft.jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tFML{6.4.49.965} [Forge Mod Loader] (minecraftforge-9.11.1.965.jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tForge{9.11.1.965} [Minecraft Forge] (minecraftforge-9.11.1.965.jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tTooManyItems{1.6.4[hukk汉化]} [TooManyItems] (minecraft.jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tInputFix{1.6.x-v3} [InputFix] (minecraft.jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tyarr_cutemobmodels{1.6.4} [Cute Mob Models] ([画面][怪物娘化].zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tBetterGrassAndLeavesMod{1.6.4.D[无节操西瓜君汉化]} [更好的草和树叶] ([画面][更好的草和树叶].jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tcraftguide{1.5.2} [CraftGuide] ([辅助][G键合成表]CraftGuide.zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tmod_ReiMinimap{v3.4_01 [1.6.2]} [mod_ReiMinimap] ([辅助][Rei小地图].zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tmod_ChatBubbles{3.1.4.4.0} [MamiyaOtaru's Chat Bubbles] ([辅助][聊天气泡].zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tDamageIndicatorsMod{2.9.1.1} [Damage Indicators] ([辅助][血量显示]Damagelndicators.zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tmod_MouseTweaks{2.3.4 (for Minecraft 1.6.4)} [Mouse Tweaks] ([辅助][鼠标手势]MouseTweaks2.3.4.zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tCarpentersBlocks{v1.91} [Carpenter's Blocks] (fangkuai.zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tFlansMod{4.1.1} [Flans Mod] (Flsns.jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tThirstMod{1.7.6} [Thirst Mod] (heshui.zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tcustomnpcs{1.6.4} [CustomNpcs] (Npcs.zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tweaponmod{1.6.2 v1.13.6} [Balkon's WeaponMod] (wuqi.zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tBackpack{1.12.13} [Backpack] (阿宅背包~.zip) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tForgeMultipart{1.0.0.219} [Forge Multipart] (ForgeMultipart-universal-1.6.4-1.0.0.219.jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tMcMultipart{1.0.0.219} [Minecraft Multipart Plugin] (ForgeMultipart-universal-1.6.4-1.0.0.219.jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tForgeMicroblock{1.0.0.219} [Forge Microblocks] (ForgeMultipart-universal-1.6.4-1.0.0.219.jar) Unloaded->Constructed->Pre-initialized->Initialized->Post-initialized->Available\n\tLaunched Version: 1.6.4\n\tLWJGL: 2.9.0\n\tOpenGL: AMD 760G GL version 3.3.10243 Compatibility Profile Context, ATI Technologies Inc.\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Pack: 隔离区材质包.zip\n\tCurrent Language: 简体中文 (中国)\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: ~~ERROR~~ NullPointerException: null"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/splashscreen.txt",
    "content": "---- Minecraft Crash Report ----\n\nWARNING: coremods are present:\n  SkinCore (4626894634154779079@3@0.jar)\n  BaseModNeteaseCore (4620273813196076442@3@0.jar)\n  RandFTCore (4618421856281952104@2@33.jar)\n  InputFix (4618424574399199550@3@0.jar)\n  NeteaseCore (4619774556351054392@3@0.jar)\nContact their authors BEFORE contacting forge\n\n// This is a token for 1 free hug. Redeem at your nearest Mojangsta: [~~HUG~~]\n\nTime: 19-1-25 下午9:23\nDescription: Initializing game\n\nSplashProgress has detected a error loading Minecraft.\nThis can sometimes be caused by bad video drivers.\nWe have automatically disabeled the new Splash Screen in config/splash.properties.\nTry reloading minecraft before reporting any errors.\nnet.minecraftforge.fml.client.SplashProgress$5: org.lwjgl.LWJGLException: Could not make context current\n\tat net.minecraftforge.fml.client.SplashProgress.disableSplash(SplashProgress.java:591)\n\tat net.minecraftforge.fml.client.SplashProgress.finish(SplashProgress.java:583)\n\tat net.minecraftforge.fml.client.FMLClientHandler.onInitializationComplete(FMLClientHandler.java:408)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:514)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:331)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\nCaused by: org.lwjgl.LWJGLException: Could not make context current\n\tat org.lwjgl.opengl.WindowsContextImplementation.nMakeCurrent(Native Method)\n\tat org.lwjgl.opengl.WindowsContextImplementation.makeCurrent(WindowsContextImplementation.java:94)\n\tat org.lwjgl.opengl.ContextGL.makeCurrent(ContextGL.java:194)\n\tat org.lwjgl.opengl.DrawableGL.makeCurrent(DrawableGL.java:110)\n\tat net.minecraftforge.fml.client.SplashProgress.finish(SplashProgress.java:575)\n\t... 10 more\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat net.minecraftforge.fml.client.SplashProgress.disableSplash(SplashProgress.java:591)\n\tat net.minecraftforge.fml.client.SplashProgress.finish(SplashProgress.java:583)\n\tat net.minecraftforge.fml.client.FMLClientHandler.onInitializationComplete(FMLClientHandler.java:408)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:514)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:331)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.8.9\n\tOperating System: Windows 7 (amd64) version 6.1\n\tCPU: <unknown>\n\tJava Version: 1.8.0_60, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 260467040 bytes (248 MB) / 422846464 bytes (403 MB) up to 1637089280 bytes (1561 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx1574M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP 9.19 Powered by Forge 11.15.1.0 Optifine OptiFine_1.8.9_HD_U_H8 31 mods loaded, 31 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUCHIJA\tmcp{9.19} [Minecraft Coder Pack] (minecraft.jar)\n\tUCHIJA\tFML{8.0.99.99} [Forge Mod Loader] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\tForge{11.15.1.0} [Minecraft Forge] (forge-1.8.9-11.15.1.0.jar)\n\tUCHIJA\trandftcoremod{1.8.9} [randftcoremod] (minecraft.jar)\n\tUCHIJA\tInputFix{1.8.x-v2} [InputFix] (minecraft.jar)\n\tUCHIJA\tskincoremod{1.8.9} [skincoremod] (minecraft.jar)\n\tUCHIJA\tneteasecore{1.11.2} [NeteaseCore] (minecraft.jar)\n\tUCHIJA\tbasemodneteasecore{1.8.9} [BaseModNeteaseCore] (minecraft.jar)\n\tUCHIJA\tkeystrokesmod{KMV5} [KeystrokesMod] (%5B1.8.9%5D+Keystrokes+Mod+V5.jar)\n\tUCHIJA\tnetworkmod{1.0} [network rpc Mod] (4620273813222949778@3@0.jar)\n\tUCHIJA\tstoremod{1.0} [storemod] (4618419806295460477@3@0.jar)\n\tUCHIJA\tantimod{2.0} [anti addiction Mod] (4618827437296985101@3@0.jar)\n\tUCHIJA\tfiltermod{1.0} [filtermod] (4619774556351054392@3@0.jar)\n\tUCHIJA\tfriendplaymod{1.0} [Friendplay Mod] (4620273813159696403@3@0.jar)\n\tUCHIJA\tmcbasemod{1.0} [mcbasemod] (4620273813196076442@3@0.jar)\n\tUCHIJA\tupdatecore{1.0} [updatecore] (4620608833856825631@3@0.jar)\n\tUCHIJA\tplayermanager{1.0} [playermanager] (4620702952524438419@3@0.jar)\n\tUCHIJA\tfullscreenpopup{1.8.9.38000} [Fullscreen Popup Mod] (4624103992226684617@3@0.jar)\n\tUCHIJA\tskinmod{1.8.8-15739} [skinmod] (4626894634154779079@3@0.jar)\n\tUCHIJA\twingsmod{1.2} [Wings Mod] ([翅膀]Wings+Mod-1.2.jar)\n\tUCHIJA\toldanimations{2.3} [OldAnimationsMod] ([防砍动画]OldAnimationsMod v2.3.1 FORGE MC1.8.8.jar)\n\tUCHIJA\tMouseTweaks{2.6.2} [Mouse Tweaks] ([鼠标手势]MouseTweaks-2.6.2-mc1.8.9 - 副本.jar)\n\tUCHIJA\tTcpNoDelayMod-2.0{1.0} [TcpNoDelayMod-2.0] (ghostmod-bymarisa.jar)\n\tUCHIJA\tPingTag{3.0} [Ping Tag] (Ping Tag Mod-3.0.jar)\n\tUCHIJA\tMemoryCleaner{1.0} [Memory Cleaner] (内存清理.jar)\n\tUCHIJA\torangetogglesprint{1.0} [Orange's Simple ToggleSprint] (强制疾跑【按I开】.jar)\n\tUCHIJA\thitrangemod{1.0} [Hit Range Mod] (攻击范围显示 HitRangeMod 1.0 MC1.8.9.jar)\n\tUCHIJA\tblockoverlay{2.1} [BlockOverlay] (方块边框自定义.jar)\n\tUCHIJA\tXaeroBetterPvP{1.9} [Better PVP Mod] (更好的pvp.jar)\n\tUCHIJA\tsharpnessparticles{1.1} [Sharpness Particles] (锋利粒子mod.jar)\n\tLoaded coremods (and transformers):\nSkinCore (4626894634154779079@3@0.jar)\n  com.netease.mc.mod.skin.coremod.SkinCoreTransformer\nBaseModNeteaseCore (4620273813196076442@3@0.jar)\n  com.netease.mc.core.base.NeteaseCoreTransformer\nRandFTCore (4618421856281952104@2@33.jar)\n  com.netease.mc.mod.randomfont.RandFTCoreTransformer\nInputFix (4618424574399199550@3@0.jar)\n  lain.mods.inputfix.InputFixTransformer\nNeteaseCore (4619774556351054392@3@0.jar)\n  com.netease.mc.core.NeteaseCoreTransformer\n\tGL info: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.\n\tLaunched Version: 1.8.9\n\tLWJGL: 2.9.4\n\tOpenGL: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.\n\tGL Caps: Using GL 1.3 multitexturing.\nUsing GL 1.3 texture combiners.\nUsing framebuffer objects because OpenGL 3.0 is supported and separate blending is supported.\nShaders are available because OpenGL 2.1 is supported.\nVBOs are available because OpenGL 1.5 is supported.\n\n\tUsing VBOs: No\n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: Chroma锟斤拷效锟斤拷锟斤拷.zip, 锟斤拷c锟斤拷l药锟斤拷锟斤拷b锟斤拷lPVP锟斤拷锟绞帮拷 锟戒剑锟斤拷, 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟矫碉拷锟斤拷锟斤拷.zip\n\tCurrent Language: English (US)\n\tProfiler Position: N/A (disabled)\n\tCPU: <unknown>"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/too_old_java.txt",
    "content": "---- Minecraft Crash Report ----\n// Everything's going to plan. No, really, that was supposed to happen.\n\nTime: 19-1-27 下午10:14\nDescription: There was a severe problem during mod loading that has caused the game to fail\n\ncpw.mods.fml.common.LoaderException: java.lang.ClassNotFoundException: mods.flammpfeil.slashblade.SlashBlade\n\tat cpw.mods.fml.common.LoadController.transition(LoadController.java:163)\n\tat cpw.mods.fml.common.Loader.loadMods(Loader.java:544)\n\tat cpw.mods.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:208)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:482)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:877)\n\tat net.minecraft.client.main.Main.main(SourceFile:148)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\nCaused by: java.lang.ClassNotFoundException: mods.flammpfeil.slashblade.SlashBlade\n\tat net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:185)\n\tat java.lang.ClassLoader.loadClass(Unknown Source)\n\tat java.lang.ClassLoader.loadClass(Unknown Source)\n\tat cpw.mods.fml.common.ModClassLoader.loadClass(ModClassLoader.java:58)\n\tat java.lang.Class.forName0(Native Method)\n\tat java.lang.Class.forName(Unknown Source)\n\tat cpw.mods.fml.common.FMLModContainer.constructMod(FMLModContainer.java:440)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.EventSubscriber.handleEvent(EventSubscriber.java:74)\n\tat com.google.common.eventbus.SynchronizedEventSubscriber.handleEvent(SynchronizedEventSubscriber.java:47)\n\tat com.google.common.eventbus.EventBus.dispatch(EventBus.java:322)\n\tat com.google.common.eventbus.EventBus.dispatchQueuedEvents(EventBus.java:304)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:275)\n\tat cpw.mods.fml.common.LoadController.sendEventToModContainer(LoadController.java:212)\n\tat cpw.mods.fml.common.LoadController.propogateStateMessage(LoadController.java:190)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.EventSubscriber.handleEvent(EventSubscriber.java:74)\n\tat com.google.common.eventbus.SynchronizedEventSubscriber.handleEvent(SynchronizedEventSubscriber.java:47)\n\tat com.google.common.eventbus.EventBus.dispatch(EventBus.java:322)\n\tat com.google.common.eventbus.EventBus.dispatchQueuedEvents(EventBus.java:304)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:275)\n\tat cpw.mods.fml.common.LoadController.distributeStateMessage(LoadController.java:119)\n\tat cpw.mods.fml.common.Loader.loadMods(Loader.java:513)\n\t... 10 more\nCaused by: java.lang.UnsupportedClassVersionError: mods/flammpfeil/slashblade/SlashBlade : Unsupported major.minor version 52.0\n\tat java.lang.ClassLoader.defineClass1(Native Method)\n\tat java.lang.ClassLoader.defineClass(Unknown Source)\n\tat java.security.SecureClassLoader.defineClass(Unknown Source)\n\tat net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:176)\n\t... 38 more\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tOperating System: Windows 7 (x86) version 6.1\n\tJava Version: 1.7.0_03, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) Client VM (mixed mode), Oracle Corporation\n\tMemory: 186384328 bytes (177 MB) / 298643456 bytes (284 MB) up to 489947136 bytes (467 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx480M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.5.1558 Optifine OptiFine_1.7.10_HD_U_D8 46 mods loaded, 46 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUC\tmcp{9.05} [Minecraft Coder Pack] (minecraft.jar)\n\tUC\tFML{7.10.99.99} [Forge Mod Loader] (forge-1.7.10-10.13.5.0-1.7.10.jar)\n\tUC\tForge{10.13.5.1558} [Minecraft Forge] (forge-1.7.10-10.13.5.0-1.7.10.jar)\n\tUC\tInputFix{1.7.2-v5} [InputFix] (minecraft.jar)\n\tUC\tneteasecore{1.11.2} [NeteaseCore] (minecraft.jar)\n\tUC\tbasemodneteasecore{1.8} [BaseModNeteaseCore] (minecraft.jar)\n\tUC\tskincoremod{1.7.10} [skincoremod] (minecraft.jar)\n\tUC\tAvaritia{1.11} [Avaritia] (4618426965794801296@3@17.jar)\n\tUC\tnetworkmod{1.0} [network rpc Mod] (4620273600878080373@3@0.jar)\n\tUC\tantimod{2.0} [anti addiction Mod] (4618827397391566952@3@0.jar)\n\tUC\tcraftguide{1.7.1.0} [CraftGuide] (4619552507105661375@3@15.jar)\n\tUC\tfiltermod{1.0} [filtermod] (4619774497122295185@3@0.jar)\n\tUC\tautopickup{2.1} [AutoPickup] (4619907964369297679@3@15.jar)\n\tUC\tfriendplaymod{1.0} [Friendplay Mod] (4620273600817850558@3@0.jar)\n\tUC\tmcbasemod{1.0} [mcbasemod] (4620273600852623323@3@0.jar)\n\tUE\tflammpfeil.slashblade{mc1.7.10-r87} [SlashBlade] (4620440172958873521@3@38.jar)\n\tUC\tnomorerain{0.0.1} [No More Rain] (4620440333329871410@3@37.jar)\n\tUC\tupdatecore{1.0} [updatecore] (4620608816195582481@3@0.jar)\n\tUC\tarmourersWorkshop{1.7.10-0.47.1} [Armourer's Workshop] (4621122003232375079@3@36.jar)\n\tUC\tplushieWrapper{0.0.0} [Plushie Wrapper] (4621122003232375079@3@36.jar)\n\tUC\tChunkAnimator{@VERSION@} [Chunk Animator] (4621704345334061216@3@15.jar)\n\tUC\ttrashslot{1.0.31} [TrashSlot] (4621790239105874277@3@15.jar)\n\tUC\tNeat{GRADLE:VERSION-GRADLE:BUILD} [Neat] (4622916050975908350@3@15.jar)\n\tUC\tsamsfooddetails{1.7.10-1.0.0} [Food Details] (4623024481607864927@3@15.jar)\n\tUC\tplayermanager{1.0} [playermanager] (4623485520867757846@3@0.jar)\n\tUC\tworldedit{6.1.1} [WorldEdit] (4623662370617246414@3@15.jar)\n\tUC\tFpsReducer{1.7.10-1.10.1} [FPS Reducer] (4623801002337776029@3@15.jar)\n\tUC\tDragonMounts{r41-1.7.10} [Dragon Mounts] (4623997664137822297@3@15.jar)\n\tUC\topencommand{1.0} [opencommand Mod] (4624103922546557562@3@0.jar)\n\tUC\tfullscreenpopup{1.7.10.38000} [Fullscreen Popup Mod] (4624103922583048306@3@0.jar)\n\tUC\talluwant{2.1} [背包物品编辑器] (4624572238180731003@3@15.jar)\n\tUC\tcosmeticarmorreworked{1.7.10-v6b} [CosmeticArmorReworked] (4625343302731396781@3@15.jar)\n\tUC\tskinmod{1.0} [skinmod] (4626894487426574761@3@0.jar)\n\tUC\tMapWriter{2.1.8} [MapWriter] (4627714186354861036@3@15.jar)\n\tUE\tmrblade{1.0} [Technology Revolution and Fox Blade Extra] (4628673577301992828@3@15.jar)\n\tUC\tLIUtils{1.7.10.000} [LIUtils] (76010818897970176@3@16.jar)\n\tUC\tLambdaCraft{1.9} [LambdaCraft core] (76010818897970176@3@16.jar)\n\tUC\tLambdaCraft|World{1.9} [LambdaCraft World] (76010818897970176@3@16.jar)\n\tUC\tLambdaCraft|DeathMatch{1.9} [LambdaCraft DeathMatch] (76010818897970176@3@16.jar)\n\tUC\tLambdaCraft|Living{1.9} [LambdaCraft Living] (76010818897970176@3@16.jar)\n\tUC\tLambdaCraft|Terrain{1.9} [LambdaCraft Terrain] (76010818897970176@3@16.jar)\n\tUC\tLIUtils-Weapons{1.7.10.000} [LIUtils-MyWeaponry] (76010818897970176@3@16.jar)\n\tUC\tcustomnpcs{1.7.10d} [CustomNpcs] (76027706415776768@3@15.jar)\n\tUC\tchinacraft{0.4.208} [ChinaCraft] (76032743326090240@3@36.jar)\n\tUC\tFoodCraft{1.2.0} [FoodCraft(FoodCraft)] (76037269147878400@3@35.jar)\n\tUC\tchocolateQuest{1.0} [Chocolate Quest] (94829463832888320@3@16.jar)\n\tOptiFine Version: OptiFine_1.7.10_HD_U_D8\n\tRender Distance Chunks: 12\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.1.10367 Compatibility Profile Context\n\tOpenGlRenderer: AMD Radeon HD 6400M Series\n\tOpenGlVendor: ATI Technologies Inc.\n\tCpuCount: 4"
  },
  {
    "path": "HMCLCore/src/test/resources/crash-report/too_old_java2.txt",
    "content": "---- Minecraft Crash Report ----\n\n*** ATTENTION: detected classes with unsupported format ***\n*** DO NOT SUBMIT THIS CRASH REPORT TO FORGE ***\n\nContact authors of the following mods:\n  orangesimplemod\n\n\nWARNING: coremods are present:\n  NeteaseCore (4619774556351054392@3@0.jar)\n  ItemPatchingLoader ([1.8.9]物理掉落物品mod.jar)\n  FMLLoadingPlugin ([防砍动画修复模组]OldAnimationsModFix-3.1.jar)\n  SkinCore (4626894634154779079@3@0.jar)\n  BaseModNeteaseCore (4620273813196076442@3@0.jar)\n  InputFix (4618424574399199550@3@0.jar)\n  RandFTCore (4618421856281952104@2@33.jar)\n  Main ([防砍动画模组]OldAnimationsMod v2.4.2 FORGE MC1.8.9.jar)\nContact their authors BEFORE contacting forge\n\n// On the bright side, I bought you a teddy bear!\n\nTime: 19-8-13 上午8:06\nDescription: There was a severe problem during mod loading that has caused the game to fail\n\nnet.minecraftforge.fml.common.LoaderException: java.lang.ClassNotFoundException: com.orangemarshall.simplemod.SimpleMod\n\tat net.minecraftforge.fml.common.LoadController.transition(LoadController.java:162)\n\tat net.minecraftforge.fml.common.Loader.loadMods(Loader.java:543)\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:208)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:419)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:331)\n\tat net.minecraft.client.main.Main.main(SourceFile:124)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:146)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:25)\nCaused by: java.lang.ClassNotFoundException: com.orangemarshall.simplemod.SimpleMod\n\tat net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:185)\n\tat java.lang.ClassLoader.loadClass(Unknown Source)\n\tat java.lang.ClassLoader.loadClass(Unknown Source)\n\tat net.minecraftforge.fml.common.ModClassLoader.loadClass(ModClassLoader.java:65)\n\tat java.lang.Class.forName0(Native Method)\n\tat java.lang.Class.forName(Unknown Source)\n\tat net.minecraftforge.fml.common.FMLModContainer.constructMod(FMLModContainer.java:468)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.EventSubscriber.handleEvent(EventSubscriber.java:74)\n\tat com.google.common.eventbus.SynchronizedEventSubscriber.handleEvent(SynchronizedEventSubscriber.java:47)\n\tat com.google.common.eventbus.EventBus.dispatch(EventBus.java:322)\n\tat com.google.common.eventbus.EventBus.dispatchQueuedEvents(EventBus.java:304)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:275)\n\tat net.minecraftforge.fml.common.LoadController.sendEventToModContainer(LoadController.java:211)\n\tat net.minecraftforge.fml.common.LoadController.propogateStateMessage(LoadController.java:189)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat com.google.common.eventbus.EventSubscriber.handleEvent(EventSubscriber.java:74)\n\tat com.google.common.eventbus.SynchronizedEventSubscriber.handleEvent(SynchronizedEventSubscriber.java:47)\n\tat com.google.common.eventbus.EventBus.dispatch(EventBus.java:322)\n\tat com.google.common.eventbus.EventBus.dispatchQueuedEvents(EventBus.java:304)\n\tat com.google.common.eventbus.EventBus.post(EventBus.java:275)\n\tat net.minecraftforge.fml.common.LoadController.distributeStateMessage(LoadController.java:118)\n\tat net.minecraftforge.fml.common.Loader.loadMods(Loader.java:512)\n\t... 10 more\nCaused by: java.lang.UnsupportedClassVersionError: com/orangemarshall/simplemod/SimpleMod : Unsupported major.minor version 52.0\n\tat java.lang.ClassLoader.defineClass1(Native Method)\n\tat java.lang.ClassLoader.defineClass(Unknown Source)\n\tat java.security.SecureClassLoader.defineClass(Unknown Source)\n\tat net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:176)\n\t... 38 more\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.8.9\n\tOperating System: Windows 7 (amd64) version 6.1\n\tCPU: <unknown>\n\tJava Version: 1.7.0, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 132157416 bytes (126 MB) / 284274688 bytes (271 MB) up to 1576271872 bytes (1503 MB)\n\tJVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx1516M -Xmn128M -XX:PermSize=64M -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: MCP 9.19 Powered by Forge 11.15.1.0 Optifine OptiFine_1.8.9_HD_U_H8 25 mods loaded, 25 mods active\n\tStates: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored\n\tUC\tmcp{9.19} [Minecraft Coder Pack] (minecraft.jar)\n\tUC\tFML{8.0.99.99} [Forge Mod Loader] (forge-1.8.9-11.15.1.0.jar)\n\tUC\tForge{11.15.1.0} [Minecraft Forge] (forge-1.8.9-11.15.1.0.jar)\n\tUC\trandftcoremod{1.8.9} [randftcoremod] (minecraft.jar)\n\tUC\tInputFix{1.8.x-v2} [InputFix] (minecraft.jar)\n\tUC\tskincoremod{1.8.9} [skincoremod] (minecraft.jar)\n\tUC\titemphysic{1.3.0} [ItemPhysic] (minecraft.jar)\n\tUC\toldanimations{2.4.2} [OldAnimationsMod] (minecraft.jar)\n\tUC\toldanimationsmodfix{3.1} [OldAnimationsModFix] (minecraft.jar)\n\tUC\tneteasecore{1.11.2} [NeteaseCore] (minecraft.jar)\n\tUC\tbasemodneteasecore{1.8.9} [BaseModNeteaseCore] (minecraft.jar)\n\tUC\tkeystrokesmod{KMV5} [KeystrokesMod] (%5B1.8.9%5D+按键显示+Mod+V5 (1).jar)\n\tUC\tnetworkmod{1.0} [network rpc Mod] (4620273813222949778@3@0.jar)\n\tUC\tstoremod{1.0} [storemod] (4618419806295460477@3@0.jar)\n\tUC\tantimod{2.0} [anti addiction Mod] (4618827437296985101@3@0.jar)\n\tUC\tfiltermod{1.0} [filtermod] (4619774556351054392@3@0.jar)\n\tUC\tfriendplaymod{1.0} [Friendplay Mod] (4620273813159696403@3@0.jar)\n\tUC\tmcbasemod{1.0} [mcbasemod] (4620273813196076442@3@0.jar)\n\tUC\tupdatecore{1.0} [updatecore] (4620608833856825631@3@0.jar)\n\tUC\tplayermanager{1.0} [playermanager] (4620702952524438419@3@0.jar)\n\tUC\tfullscreenpopup{1.8.9.38000} [Fullscreen Popup Mod] (4624103992226684617@3@0.jar)\n\tUC\tskinmod{1.8.8-15739} [skinmod] (4626894634154779079@3@0.jar)\n\tUC\tblockoverlay{1.2} [BlockOverlay] ([1.8.9] BlockOverlay - 2.0边缘彩色.jar)\n\tUC\ttimechanger1.8{1.0} [TimeChanger 1.8] ([更改游戏时间] TimeChanger 1.8-1.8.9.jar)\n\tUE\torangesimplemod{1.0} [orangesimplemod] (Orange's Simple Mods-1.2.jar)\n\tLoaded coremods (and transformers):\nNeteaseCore (4619774556351054392@3@0.jar)\n  com.netease.mc.core.NeteaseCoreTransformer\nItemPatchingLoader ([1.8.9]物理掉落物品mod.jar)\n  com.creativemd.itemphysic.ItemTransformer\nFMLLoadingPlugin ([防砍动画修复模组]OldAnimationsModFix-3.1.jar)\n  io.github.zekerzhayard.oamfix.ClassTransformer\nSkinCore (4626894634154779079@3@0.jar)\n  com.netease.mc.mod.skin.coremod.SkinCoreTransformer\nBaseModNeteaseCore (4620273813196076442@3@0.jar)\n  com.netease.mc.core.base.NeteaseCoreTransformer\nInputFix (4618424574399199550@3@0.jar)\n  lain.mods.inputfix.InputFixTransformer\nRandFTCore (4618421856281952104@2@33.jar)\n  com.netease.mc.mod.randomfont.RandFTCoreTransformer\nMain ([防砍动画模组]OldAnimationsMod v2.4.2 FORGE MC1.8.9.jar)\n  com.spiderfrog.main.ClassTransformer\n\tOptiFine Version: OptiFine_1.8.9_HD_U_H8\n\tRender Distance Chunks: 12\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.6.0 NVIDIA 388.59\n\tOpenGlRenderer: GeForce GT 620/PCIe/SSE2\n\tOpenGlVendor: NVIDIA Corporation\n\tCpuCount: 2"
  },
  {
    "path": "HMCLCore/src/test/resources/game-json/tlauncher.json",
    "content": "{\n  \"id\": \"OptiFine 1.16.5\",\n  \"jar\": \"1.16.5\",\n  \"time\": \"2021-01-14T00:09:18+08:00\",\n  \"releaseTime\": \"2021-01-15T00:05:32+08:00\",\n  \"type\": \"modified\",\n  \"mainClass\": \"net.minecraft.launchwrapper.Launch\",\n  \"minimumLauncherVersion\": 21,\n  \"tlauncherVersion\": 0,\n  \"arguments\": {\n    \"game\": [\n      {\n        \"values\": [\n          \"--username\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${auth_player_name}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"--version\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${version_name}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"--gameDir\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${game_directory}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"--assetsDir\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${assets_root}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"--assetIndex\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${assets_index_name}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"--uuid\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${auth_uuid}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"--accessToken\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${auth_access_token}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"--userType\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${user_type}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"--versionType\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${version_type}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"--demo\"\n        ],\n        \"rules\": [\n          {\n            \"action\": \"allow\",\n            \"features\": {\n              \"is_demo_user\": true\n            }\n          }\n        ]\n      },\n      {\n        \"values\": [\n          \"--width\",\n          \"${resolution_width}\",\n          \"--height\",\n          \"${resolution_height}\"\n        ],\n        \"rules\": [\n          {\n            \"action\": \"allow\",\n            \"features\": {\n              \"has_custom_resolution\": true\n            }\n          }\n        ]\n      },\n      {\n        \"values\": [\n          \"--tweakClass\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"optifine.OptiFineTweaker\"\n        ],\n        \"rules\": []\n      }\n    ],\n    \"jvm\": [\n      {\n        \"values\": [\n          \"-XstartOnFirstThread\"\n        ],\n        \"rules\": [\n          {\n            \"action\": \"allow\",\n            \"os\": {\n              \"name\": \"osx\"\n            }\n          }\n        ]\n      },\n      {\n        \"values\": [\n          \"-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\"\n        ],\n        \"rules\": [\n          {\n            \"action\": \"allow\",\n            \"os\": {\n              \"name\": \"windows\"\n            }\n          }\n        ]\n      },\n      {\n        \"values\": [\n          \"-Dos.name=Windows 10\",\n          \"-Dos.version=10.0\"\n        ],\n        \"rules\": [\n          {\n            \"action\": \"allow\",\n            \"os\": {\n              \"name\": \"windows\",\n              \"version\": \"^10\\\\.\"\n            }\n          }\n        ]\n      },\n      {\n        \"values\": [\n          \"-Xss1M\"\n        ],\n        \"rules\": [\n          {\n            \"action\": \"allow\",\n            \"os\": {}\n          }\n        ]\n      },\n      {\n        \"values\": [\n          \"-Djava.library.path=${natives_directory}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"-Dminecraft.launcher.brand=${launcher_name}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"-Dminecraft.launcher.version=${launcher_version}\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"-cp\"\n        ],\n        \"rules\": []\n      },\n      {\n        \"values\": [\n          \"${classpath}\"\n        ],\n        \"rules\": []\n      }\n    ]\n  },\n  \"assets\": \"1.16\",\n  \"source\": \"local_version_repo\",\n  \"libraries\": [\n    {\n      \"name\": \"optifine:OptiFine:1.16.5_HD_U_G8\",\n      \"artifact\": {\n        \"sha1\": \"9cb751eb247f357c96463e406c946c0ebb9e1307\",\n        \"size\": 5520538,\n        \"path\": \"optifine/OptiFine/OptiFine-1.16.5_HD_U_G8/OptiFine-1.16.5_HD_U_G8.jar\",\n        \"url\": \"http://res.tlauncher.org/unb/libraries/optifine/OptiFine/1.16.5_HD_U_G8/OptiFine-1.16.5_HD_U_G8.jar\"\n      }\n    },\n    {\n      \"name\": \"optifine:launchwrapper-of:2.2\",\n      \"artifact\": {\n        \"sha1\": \"48d08d4b6d7b74b8c304c0a5bdb244a081c54175\",\n        \"size\": 15900,\n        \"path\": \"optifine/launchwrapper-of/2.2/launchwrapper-of-2.2.jar\",\n        \"url\": \"http://res.tlauncher.org/unb/libraries/optifine/launchwrapper-of/2.2/launchwrapper-of-2.2.jar\"\n      }\n    },\n    {\n      \"name\": \"com.mojang:patchy:1.2.3\",\n      \"artifact\": {\n        \"sha1\": \"e3107ca512d704a434076a153a6e1149e3787275\",\n        \"size\": 22340,\n        \"path\": \"com/mojang/patchy/1.2.3/patchy-1.2.3.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/mojang/patchy/1.2.3/patchy-1.2.3.jar\"\n      }\n    },\n    {\n      \"name\": \"oshi-project:oshi-core:1.1\",\n      \"artifact\": {\n        \"sha1\": \"9ddf7b048a8d701be231c0f4f95fd986198fd2d8\",\n        \"size\": 30973,\n        \"path\": \"oshi-project/oshi-core/1.1/oshi-core-1.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/oshi-project/oshi-core/1.1/oshi-core-1.1.jar\"\n      }\n    },\n    {\n      \"name\": \"net.java.dev.jna:jna:4.4.0\",\n      \"artifact\": {\n        \"sha1\": \"cb208278274bf12ebdb56c61bd7407e6f774d65a\",\n        \"size\": 1091208,\n        \"path\": \"net/java/dev/jna/jna/4.4.0/jna-4.4.0.jar\",\n        \"url\": \"https://libraries.minecraft.net/net/java/dev/jna/jna/4.4.0/jna-4.4.0.jar\"\n      }\n    },\n    {\n      \"name\": \"net.java.dev.jna:platform:3.4.0\",\n      \"artifact\": {\n        \"sha1\": \"e3f70017be8100d3d6923f50b3d2ee17714e9c13\",\n        \"size\": 913436,\n        \"path\": \"net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar\",\n        \"url\": \"https://libraries.minecraft.net/net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar\"\n      }\n    },\n    {\n      \"name\": \"com.ibm.icu:icu4j:66.1\",\n      \"artifact\": {\n        \"sha1\": \"72c7519b6d91f7a1f993bd44a99fe95d67211b27\",\n        \"size\": 12935630,\n        \"path\": \"com/ibm/icu/icu4j/66.1/icu4j-66.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/ibm/icu/icu4j/66.1/icu4j-66.1.jar\"\n      }\n    },\n    {\n      \"name\": \"com.mojang:javabridge:1.0.22\",\n      \"artifact\": {\n        \"sha1\": \"6aa6453aa99a52a5cd91749da1af6ab70e082ab3\",\n        \"size\": 5111,\n        \"path\": \"com/mojang/javabridge/1.0.22/javabridge-1.0.22.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/mojang/javabridge/1.0.22/javabridge-1.0.22.jar\"\n      }\n    },\n    {\n      \"name\": \"net.sf.jopt-simple:jopt-simple:5.0.3\",\n      \"artifact\": {\n        \"sha1\": \"cdd846cfc4e0f7eefafc02c0f5dce32b9303aa2a\",\n        \"size\": 78175,\n        \"path\": \"net/sf/jopt-simple/jopt-simple/5.0.3/jopt-simple-5.0.3.jar\",\n        \"url\": \"https://libraries.minecraft.net/net/sf/jopt-simple/jopt-simple/5.0.3/jopt-simple-5.0.3.jar\"\n      }\n    },\n    {\n      \"name\": \"io.netty:netty-all:4.1.25.Final\",\n      \"artifact\": {\n        \"sha1\": \"d0626cd3108294d1d58c05859add27b4ef21f83b\",\n        \"size\": 3823147,\n        \"path\": \"io/netty/netty-all/4.1.25.Final/netty-all-4.1.25.Final.jar\",\n        \"url\": \"https://libraries.minecraft.net/io/netty/netty-all/4.1.25.Final/netty-all-4.1.25.Final.jar\"\n      }\n    },\n    {\n      \"name\": \"com.google.guava:guava:21.0\",\n      \"artifact\": {\n        \"sha1\": \"3a3d111be1be1b745edfa7d91678a12d7ed38709\",\n        \"size\": 2521113,\n        \"path\": \"com/google/guava/guava/21.0/guava-21.0.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/google/guava/guava/21.0/guava-21.0.jar\"\n      }\n    },\n    {\n      \"name\": \"org.apache.commons:commons-lang3:3.5\",\n      \"artifact\": {\n        \"sha1\": \"6c6c702c89bfff3cd9e80b04d668c5e190d588c6\",\n        \"size\": 479881,\n        \"path\": \"org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar\"\n      }\n    },\n    {\n      \"name\": \"commons-io:commons-io:2.5\",\n      \"artifact\": {\n        \"sha1\": \"2852e6e05fbb95076fc091f6d1780f1f8fe35e0f\",\n        \"size\": 208700,\n        \"path\": \"commons-io/commons-io/2.5/commons-io-2.5.jar\",\n        \"url\": \"https://libraries.minecraft.net/commons-io/commons-io/2.5/commons-io-2.5.jar\"\n      }\n    },\n    {\n      \"name\": \"commons-codec:commons-codec:1.10\",\n      \"artifact\": {\n        \"sha1\": \"4b95f4897fa13f2cd904aee711aeafc0c5295cd8\",\n        \"size\": 284184,\n        \"path\": \"commons-codec/commons-codec/1.10/commons-codec-1.10.jar\",\n        \"url\": \"https://libraries.minecraft.net/commons-codec/commons-codec/1.10/commons-codec-1.10.jar\"\n      }\n    },\n    {\n      \"name\": \"net.java.jinput:jinput:2.0.5\",\n      \"artifact\": {\n        \"sha1\": \"39c7796b469a600f72380316f6b1f11db6c2c7c4\",\n        \"size\": 208338,\n        \"path\": \"net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar\",\n        \"url\": \"https://libraries.minecraft.net/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar\"\n      }\n    },\n    {\n      \"name\": \"net.java.jutils:jutils:1.0.0\",\n      \"artifact\": {\n        \"sha1\": \"e12fe1fda814bd348c1579329c86943d2cd3c6a6\",\n        \"size\": 7508,\n        \"path\": \"net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar\",\n        \"url\": \"https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar\"\n      }\n    },\n    {\n      \"name\": \"com.mojang:brigadier:1.0.17\",\n      \"artifact\": {\n        \"sha1\": \"c6b7dc51dd44379cc751b7504816006e9be4b1e6\",\n        \"size\": 77392,\n        \"path\": \"com/mojang/brigadier/1.0.17/brigadier-1.0.17.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/mojang/brigadier/1.0.17/brigadier-1.0.17.jar\"\n      }\n    },\n    {\n      \"name\": \"com.mojang:datafixerupper:4.0.26\",\n      \"artifact\": {\n        \"sha1\": \"ebd6690f33871ccee9b6132c6480668ee2e35020\",\n        \"size\": 673183,\n        \"path\": \"com/mojang/datafixerupper/4.0.26/datafixerupper-4.0.26.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/mojang/datafixerupper/4.0.26/datafixerupper-4.0.26.jar\"\n      }\n    },\n    {\n      \"name\": \"com.google.code.gson:gson:2.8.0\",\n      \"artifact\": {\n        \"sha1\": \"c4ba5371a29ac9b2ad6129b1d39ea38750043eff\",\n        \"size\": 231952,\n        \"path\": \"com/google/code/gson/gson/2.8.0/gson-2.8.0.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar\"\n      }\n    },\n    {\n      \"name\": \"com.mojang:authlib:2.1.28\",\n      \"artifact\": {\n        \"sha1\": \"ad54da276bf59983d02d5ed16fc14541354c71fd\",\n        \"size\": 76328,\n        \"path\": \"com/mojang/authlib/2.1.28/authlib-2.1.28.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/mojang/authlib/2.1.28/authlib-2.1.28.jar\"\n      }\n    },\n    {\n      \"name\": \"org.apache.commons:commons-compress:1.8.1\",\n      \"artifact\": {\n        \"sha1\": \"a698750c16740fd5b3871425f4cb3bbaa87f529d\",\n        \"size\": 365552,\n        \"path\": \"org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.apache.httpcomponents:httpclient:4.3.3\",\n      \"artifact\": {\n        \"sha1\": \"18f4247ff4572a074444572cee34647c43e7c9c7\",\n        \"size\": 589512,\n        \"path\": \"org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar\"\n      }\n    },\n    {\n      \"name\": \"commons-logging:commons-logging:1.1.3\",\n      \"artifact\": {\n        \"sha1\": \"f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f\",\n        \"size\": 62050,\n        \"path\": \"commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar\",\n        \"url\": \"https://libraries.minecraft.net/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar\"\n      }\n    },\n    {\n      \"name\": \"org.apache.httpcomponents:httpcore:4.3.2\",\n      \"artifact\": {\n        \"sha1\": \"31fbbff1ddbf98f3aa7377c94d33b0447c646b6e\",\n        \"size\": 282269,\n        \"path\": \"org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar\"\n      }\n    },\n    {\n      \"name\": \"it.unimi.dsi:fastutil:8.2.1\",\n      \"artifact\": {\n        \"sha1\": \"5ad88f325e424f8dbc2be5459e21ea5cab3864e9\",\n        \"size\": 18800417,\n        \"path\": \"it/unimi/dsi/fastutil/8.2.1/fastutil-8.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/it/unimi/dsi/fastutil/8.2.1/fastutil-8.2.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.apache.logging.log4j:log4j-api:2.8.1\",\n      \"artifact\": {\n        \"sha1\": \"e801d13612e22cad62a3f4f3fe7fdbe6334a8e72\",\n        \"size\": 228859,\n        \"path\": \"org/apache/logging/log4j/log4j-api/2.8.1/log4j-api-2.8.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/apache/logging/log4j/log4j-api/2.8.1/log4j-api-2.8.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.apache.logging.log4j:log4j-core:2.8.1\",\n      \"artifact\": {\n        \"sha1\": \"4ac28ff2f1ddf05dae3043a190451e8c46b73c31\",\n        \"size\": 1402925,\n        \"path\": \"org/apache/logging/log4j/log4j-core/2.8.1/log4j-core-2.8.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/apache/logging/log4j/log4j-core/2.8.1/log4j-core-2.8.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"2bb514e444994c6fece99a21f76e0c90438e377f\",\n        \"size\": 317748,\n        \"path\": \"org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"8ad6294407e15780b43e84929c40e4c5e997972e\",\n        \"size\": 321900,\n        \"path\": \"org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"7a0c583fcbec32b15784f846df536e1837d83666\",\n        \"size\": 38616,\n        \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"ee8e57a79300f78294576d87c4a587f8c99402e2\",\n        \"size\": 34848,\n        \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"dc7ff2dabb40a141ee9bf2e326d9b1b19f3278fb\",\n        \"size\": 80103,\n        \"path\": \"org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"2b772a102b0a11ee5f2109a5b136f4dc7c630827\",\n        \"size\": 80012,\n        \"path\": \"org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"57008c2374c5bc434b18adfef3f3653ee450ee18\",\n        \"size\": 931322,\n        \"path\": \"org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"6ac5bb88b44c43ea195a570aab059f63da004cd8\",\n        \"size\": 929780,\n        \"path\": \"org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"027abb7f64894b61cad163791acd8113f0b21296\",\n        \"size\": 116708,\n        \"path\": \"org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"d3ad4df38e400b8afba1de63f84338809399df5b\",\n        \"size\": 108907,\n        \"path\": \"org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"31f5eb5fce3791d58ec898bc5c1867d76d781ba1\",\n        \"size\": 105765,\n        \"path\": \"org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"3b8e6ebc5851dd3d17e37e5cadce2eff2a429f0f\",\n        \"size\": 104469,\n        \"path\": \"org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"259f1dbddb921e27e01b32458d6f584eb8bba13a\",\n        \"size\": 7088,\n        \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"fcbe606c8f8da6f8f9a05e2c540eb1ee8632b0e9\",\n        \"size\": 7092,\n        \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2.jar\"\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"osx\": \"natives-macos\"\n      },\n      \"artifact\": {\n        \"sha1\": \"2bb514e444994c6fece99a21f76e0c90438e377f\",\n        \"size\": 317748,\n        \"path\": \"org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1.jar\"\n      },\n      \"classifies\": {\n        \"javadoc\": {\n          \"sha1\": \"1f6b7050737559b775d797c0ea56612b8e373fd6\",\n          \"size\": 1287174,\n          \"path\": \"org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-javadoc.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-javadoc.jar\"\n        },\n        \"linux\": {\n          \"sha1\": \"9bdd47cd63ce102cec837a396c8ded597cb75a66\",\n          \"size\": 87484,\n          \"path\": \"org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"5a4c271d150906858d475603dcb9479453c60555\",\n          \"size\": 39835,\n          \"path\": \"org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"e799d06b8969db0610e68776e0eff4b6191098bd\",\n          \"size\": 255871,\n          \"path\": \"org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-natives-windows.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"106f90ac41449004a969309488aa6e3a2f7d6731\",\n          \"size\": 255671,\n          \"path\": \"org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"linux\": \"natives-linux\",\n        \"windows\": \"natives-windows\"\n      },\n      \"artifact\": {\n        \"sha1\": \"8ad6294407e15780b43e84929c40e4c5e997972e\",\n        \"size\": 321900,\n        \"path\": \"org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2.jar\"\n      },\n      \"classifies\": {\n        \"linux\": {\n          \"sha1\": \"ae7976827ca2a3741f6b9a843a89bacd637af350\",\n          \"size\": 124776,\n          \"path\": \"org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"bbfb75693bdb714c0c69c2c9f9be73d259b43b62\",\n          \"size\": 48462,\n          \"path\": \"org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"05359f3aa50d36352815fc662ea73e1c00d22170\",\n          \"size\": 279593,\n          \"path\": \"org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2-natives-windows.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"osx\": \"natives-macos\"\n      },\n      \"artifact\": {\n        \"sha1\": \"7a0c583fcbec32b15784f846df536e1837d83666\",\n        \"size\": 38616,\n        \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1.jar\"\n      },\n      \"classifies\": {\n        \"javadoc\": {\n          \"sha1\": \"04f6897be1e2d68bff5ec5e91a2b96e32f084c09\",\n          \"size\": 461041,\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-javadoc.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-javadoc.jar\"\n        },\n        \"linux\": {\n          \"sha1\": \"5536616b558cea2fea6330ca682fd7c733db9c43\",\n          \"size\": 156057,\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"439ab9d0264167a949cc7bcce673704322baaf50\",\n          \"size\": 117001,\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"3c869b3d7638c800b7039cd859d064658643ad6e\",\n          \"size\": 218136,\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-natives-windows.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"4450dca46228c02c51bb9bbda70e7cfc3154296d\",\n          \"size\": 31279,\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-jemalloc:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"linux\": \"natives-linux\",\n        \"windows\": \"natives-windows\"\n      },\n      \"artifact\": {\n        \"sha1\": \"ee8e57a79300f78294576d87c4a587f8c99402e2\",\n        \"size\": 34848,\n        \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2.jar\"\n      },\n      \"classifies\": {\n        \"linux\": {\n          \"sha1\": \"268c08a150347e04e44ba56e359d62c9b78669df\",\n          \"size\": 156173,\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"805f5a10465375ba034b27b72331912fd2846690\",\n          \"size\": 117127,\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"338b25b99da3ba5f441f6492f2ce2a9c608860ed\",\n          \"size\": 220623,\n          \"path\": \"org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2-natives-windows.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"osx\": \"natives-macos\"\n      },\n      \"artifact\": {\n        \"sha1\": \"dc7ff2dabb40a141ee9bf2e326d9b1b19f3278fb\",\n        \"size\": 80103,\n        \"path\": \"org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1.jar\"\n      },\n      \"classifies\": {\n        \"javadoc\": {\n          \"sha1\": \"95752f443686da1b3443e397dc83e730e1907a1e\",\n          \"size\": 617869,\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-javadoc.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-javadoc.jar\"\n        },\n        \"linux\": {\n          \"sha1\": \"bcd4be67863dd908f696f628c3ca9f6eb9ae5152\",\n          \"size\": 590716,\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"9357ebfc82a0d6f64e17093dd963219367cd6fa2\",\n          \"size\": 528004,\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"92fb931e65c637cea209ad5c3ffebd1b325ed41d\",\n          \"size\": 1310083,\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-natives-windows.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"8fe3d6e6353685164b1eb3a22980aaa1115d4a32\",\n          \"size\": 78379,\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-openal:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"linux\": \"natives-linux\",\n        \"windows\": \"natives-windows\"\n      },\n      \"artifact\": {\n        \"sha1\": \"2b772a102b0a11ee5f2109a5b136f4dc7c630827\",\n        \"size\": 80012,\n        \"path\": \"org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2.jar\"\n      },\n      \"classifies\": {\n        \"linux\": {\n          \"sha1\": \"0364f9f5c3947393083ab5f37a571f5603aadd0b\",\n          \"size\": 590997,\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"a97b6345d5a9ddf889e262bd7ad8eed43b1bb063\",\n          \"size\": 528006,\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"ec20a7d42a2438528fca87e60b1705f1e2339ddb\",\n          \"size\": 1310102,\n          \"path\": \"org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2-natives-windows.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"osx\": \"natives-macos\"\n      },\n      \"artifact\": {\n        \"sha1\": \"57008c2374c5bc434b18adfef3f3653ee450ee18\",\n        \"size\": 931322,\n        \"path\": \"org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1.jar\"\n      },\n      \"classifies\": {\n        \"javadoc\": {\n          \"sha1\": \"e25fc8cbcbee68182a6b7f13ad71b1f0961005ad\",\n          \"size\": 4307561,\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-javadoc.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-javadoc.jar\"\n        },\n        \"linux\": {\n          \"sha1\": \"c43bb08ed7dcf1a6d344803e464148b3b14dd274\",\n          \"size\": 77401,\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"dca9ad9e59a87172144d531e08ef7f6988073db0\",\n          \"size\": 38998,\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"80954961b31084d7b4f2f041d6b5a799a774c880\",\n          \"size\": 170804,\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-natives-windows.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"47930ffbef53c0f45c7e35c01b1c6ad5b2205809\",\n          \"size\": 1251582,\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-opengl:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"linux\": \"natives-linux\",\n        \"windows\": \"natives-windows\"\n      },\n      \"artifact\": {\n        \"sha1\": \"6ac5bb88b44c43ea195a570aab059f63da004cd8\",\n        \"size\": 929780,\n        \"path\": \"org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2.jar\"\n      },\n      \"classifies\": {\n        \"linux\": {\n          \"sha1\": \"338d33387919cb3f4cdba143c2b738a71ccfda60\",\n          \"size\": 77392,\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"cf4f43e69ee70d8ebfbb6ba93dec9016339e4fdc\",\n          \"size\": 38989,\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"d8dcdc91066cae2d2d8279cb4a9f9f05d9525826\",\n          \"size\": 170798,\n          \"path\": \"org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2-natives-windows.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"osx\": \"natives-macos\"\n      },\n      \"artifact\": {\n        \"sha1\": \"027abb7f64894b61cad163791acd8113f0b21296\",\n        \"size\": 116708,\n        \"path\": \"org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1.jar\"\n      },\n      \"classifies\": {\n        \"javadoc\": {\n          \"sha1\": \"81482a14b617e4fb0c7de69b3e06ef2e28ef894f\",\n          \"size\": 690774,\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-javadoc.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-javadoc.jar\"\n        },\n        \"linux\": {\n          \"sha1\": \"5a2fb27f9e12a34ecabf6f6a7606c61849e347ee\",\n          \"size\": 157431,\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"72fe6dab6110a5a1cd4833f11840eef7b2eadce5\",\n          \"size\": 64724,\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"00def7c58ad2e1cb258d6d73be181ffab8ef8bd5\",\n          \"size\": 265304,\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-natives-windows.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"4c56ae817da75996b19601c87d7e759b846c3902\",\n          \"size\": 101885,\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-glfw:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"linux\": \"natives-linux\",\n        \"windows\": \"natives-windows\"\n      },\n      \"artifact\": {\n        \"sha1\": \"d3ad4df38e400b8afba1de63f84338809399df5b\",\n        \"size\": 108907,\n        \"path\": \"org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar\"\n      },\n      \"classifies\": {\n        \"linux\": {\n          \"sha1\": \"0957733f26a6661d4883da0335f7ef46d3bbbd7d\",\n          \"size\": 159198,\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"98f745038d17ac3192fcd01dc44126b03ec1570d\",\n          \"size\": 67311,\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"dc6826d636bf796b33a49038c354210e661bfc17\",\n          \"size\": 266648,\n          \"path\": \"org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2-natives-windows.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"osx\": \"natives-macos\"\n      },\n      \"artifact\": {\n        \"sha1\": \"31f5eb5fce3791d58ec898bc5c1867d76d781ba1\",\n        \"size\": 105765,\n        \"path\": \"org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1.jar\"\n      },\n      \"classifies\": {\n        \"javadoc\": {\n          \"sha1\": \"524d79537f840d6cfe50e030d24413933f0d464b\",\n          \"size\": 684972,\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-javadoc.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-javadoc.jar\"\n        },\n        \"linux\": {\n          \"sha1\": \"66e01b8036258619332cb452b970ca0a52db1a87\",\n          \"size\": 197208,\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"1f5615c952451c30afafba4a6e3ba4e1cd9e7f5c\",\n          \"size\": 192364,\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"d100bfd2b0d03223a043cfcb64a2dfd2bb7f4c61\",\n          \"size\": 454473,\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-natives-windows.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"50ac43d4c6ea5846f354f9576134c0f9264345c2\",\n          \"size\": 96479,\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"linux\": \"natives-linux\",\n        \"windows\": \"natives-windows\"\n      },\n      \"artifact\": {\n        \"sha1\": \"fcbe606c8f8da6f8f9a05e2c540eb1ee8632b0e9\",\n        \"size\": 7092,\n        \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2.jar\"\n      },\n      \"classifies\": {\n        \"javadoc\": {\n          \"sha1\": \"ba657a222ee267b75fa81ae5ab29ae29b50f725f\",\n          \"size\": 368913,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-javadoc.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-javadoc.jar\"\n        },\n        \"linux\": {\n          \"sha1\": \"39e35b161c130635d9c8918ce04e887a30c5b687\",\n          \"size\": 38804,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"46d0798228b8a28e857a2a0f02310fd6ba2a4eab\",\n          \"size\": 42136,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"e9115958773644e863332a6a06488d26f9e1fc9f\",\n          \"size\": 208314,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-natives-windows.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"2fe76dcf2ca02ae0e64ac7c69eb251c09df0e922\",\n          \"size\": 5034,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-tinyfd:3.2.1\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"osx\": \"natives-macos\"\n      },\n      \"artifact\": {\n        \"sha1\": \"259f1dbddb921e27e01b32458d6f584eb8bba13a\",\n        \"size\": 7088,\n        \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1.jar\"\n      },\n      \"classifies\": {\n        \"javadoc\": {\n          \"sha1\": \"0a85d995178cdab6b94d9a172dd9e7d2a0d70cfb\",\n          \"size\": 368913,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-javadoc.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-javadoc.jar\"\n        },\n        \"linux\": {\n          \"sha1\": \"4ad49108397322596d7b85c2c687e5de6ee52157\",\n          \"size\": 38192,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"759c2fd9cc5c6ce0b5b7af77ac8200483b7fb660\",\n          \"size\": 41962,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"85750d2ca022852e15f58c0b94b3d1d4e7f0ba52\",\n          \"size\": 207577,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-natives-windows.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"c375699fd794c4c87d935e0f9a84e7d80d0de77e\",\n          \"size\": 5034,\n          \"path\": \"org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.2.1/lwjgl-tinyfd-3.2.1-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"org.lwjgl:lwjgl-stb:3.2.2\",\n      \"rules\": [\n        {\n          \"action\": \"allow\"\n        },\n        {\n          \"action\": \"disallow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"linux\": \"natives-linux\",\n        \"windows\": \"natives-windows\"\n      },\n      \"artifact\": {\n        \"sha1\": \"3b8e6ebc5851dd3d17e37e5cadce2eff2a429f0f\",\n        \"size\": 104469,\n        \"path\": \"org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2.jar\",\n        \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2.jar\"\n      },\n      \"classifies\": {\n        \"linux\": {\n          \"sha1\": \"172c52e586fecf43f759bc4f70a778c01f6fdcc1\",\n          \"size\": 203476,\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2-natives-linux.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"ee059b129b09fdecbd8595273926ae930bf5a5d7\",\n          \"size\": 196796,\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2-natives-macos.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2-natives-macos.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"811f705cbb29e8ae8d60bdf8fdd38c0c123ad3ef\",\n          \"size\": 465810,\n          \"path\": \"org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2-natives-windows.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"com.mojang:text2speech:1.11.3\",\n      \"artifact\": {\n        \"sha1\": \"f378f889797edd7df8d32272c06ca80a1b6b0f58\",\n        \"size\": 13164,\n        \"path\": \"com/mojang/text2speech/1.11.3/text2speech-1.11.3.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/mojang/text2speech/1.11.3/text2speech-1.11.3.jar\"\n      }\n    },\n    {\n      \"name\": \"com.mojang:text2speech:1.11.3\",\n      \"natives\": {\n        \"linux\": \"natives-linux\",\n        \"windows\": \"natives-windows\"\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"artifact\": {\n        \"sha1\": \"f378f889797edd7df8d32272c06ca80a1b6b0f58\",\n        \"size\": 13164,\n        \"path\": \"com/mojang/text2speech/1.11.3/text2speech-1.11.3.jar\",\n        \"url\": \"https://libraries.minecraft.net/com/mojang/text2speech/1.11.3/text2speech-1.11.3.jar\"\n      },\n      \"classifies\": {\n        \"linux\": {\n          \"sha1\": \"ac641755a2a841d1fca9e660194f42523ee5cfe0\",\n          \"size\": 7833,\n          \"path\": \"com/mojang/text2speech/1.11.3/text2speech-1.11.3-natives-linux.jar\",\n          \"url\": \"https://libraries.minecraft.net/com/mojang/text2speech/1.11.3/text2speech-1.11.3-natives-linux.jar\"\n        },\n        \"windows\": {\n          \"sha1\": \"c0b242c0091be5acbf303263c7eeeaedd70544c7\",\n          \"size\": 81379,\n          \"path\": \"com/mojang/text2speech/1.11.3/text2speech-1.11.3-natives-windows.jar\",\n          \"url\": \"https://libraries.minecraft.net/com/mojang/text2speech/1.11.3/text2speech-1.11.3-natives-windows.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"772a37dd77417571e6f119a8d306f0c14c2ee410\",\n          \"size\": 5332,\n          \"path\": \"com/mojang/text2speech/1.11.3/text2speech-1.11.3-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/com/mojang/text2speech/1.11.3/text2speech-1.11.3-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"ca.weblite:java-objc-bridge:1.0.0\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"natives\": {\n        \"osx\": \"natives-osx\"\n      },\n      \"extract\": {\n        \"exclude\": [\n          \"META-INF/\"\n        ]\n      },\n      \"artifact\": {\n        \"sha1\": \"6ef160c3133a78de015830860197602ca1c855d3\",\n        \"size\": 40502,\n        \"path\": \"ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0.jar\",\n        \"url\": \"https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0.jar\"\n      },\n      \"classifies\": {\n        \"javadoc\": {\n          \"sha1\": \"fb0092f22cb4fe8e631452f577b7a238778abf2a\",\n          \"size\": 174060,\n          \"path\": \"ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-javadoc.jar\",\n          \"url\": \"https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-javadoc.jar\"\n        },\n        \"osx\": {\n          \"sha1\": \"08befab4894d55875f33c3d300f4f71e6e828f64\",\n          \"size\": 5629,\n          \"path\": \"ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-natives-osx.jar\",\n          \"url\": \"https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-natives-osx.jar\"\n        },\n        \"sources\": {\n          \"sha1\": \"865837a198189aee737019561ece842827f24278\",\n          \"size\": 43283,\n          \"path\": \"ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-sources.jar\",\n          \"url\": \"https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-sources.jar\"\n        }\n      }\n    },\n    {\n      \"name\": \"ca.weblite:java-objc-bridge:1.0.0\",\n      \"rules\": [\n        {\n          \"action\": \"allow\",\n          \"os\": {\n            \"name\": \"osx\"\n          }\n        }\n      ],\n      \"artifact\": {\n        \"sha1\": \"6ef160c3133a78de015830860197602ca1c855d3\",\n        \"size\": 40502,\n        \"path\": \"ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0.jar\",\n        \"url\": \"https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0.jar\"\n      }\n    }\n  ],\n  \"downloads\": {\n    \"client\": {\n      \"sha1\": \"37fd3c903861eeff3bc24b71eed48f828b5269c8\",\n      \"size\": 17547153,\n      \"url\": \"https://launcher.mojang.com/v1/objects/37fd3c903861eeff3bc24b71eed48f828b5269c8/client.jar\"\n    },\n    \"client_mappings\": {\n      \"sha1\": \"374c6b789574afbdc901371207155661e0509e17\",\n      \"size\": 5746047,\n      \"url\": \"https://launcher.mojang.com/v1/objects/374c6b789574afbdc901371207155661e0509e17/client.txt\"\n    },\n    \"server\": {\n      \"sha1\": \"1b557e7b033b583cd9f66746b7a9ab1ec1673ced\",\n      \"size\": 37962360,\n      \"url\": \"https://launcher.mojang.com/v1/objects/1b557e7b033b583cd9f66746b7a9ab1ec1673ced/server.jar\"\n    },\n    \"server_mappings\": {\n      \"sha1\": \"41285beda6d251d190f2bf33beadd4fee187df7a\",\n      \"size\": 4400926,\n      \"url\": \"https://launcher.mojang.com/v1/objects/41285beda6d251d190f2bf33beadd4fee187df7a/server.txt\"\n    }\n  },\n  \"assetIndex\": {\n    \"id\": \"1.16\",\n    \"totalSize\": 332467385,\n    \"size\": 294843,\n    \"url\": \"https://launchermeta.mojang.com/v1/packages/ad75fb46ff5c89aa5f9c90e12481e963854f9a6e/1.16.json\"\n  },\n  \"complianceLevel\": 1.0,\n  \"javaVersion\": {\n    \"component\": \"jre-legacy\",\n    \"majorVersion\": 8.0\n  },\n  \"skinVersion\": false\n}"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/bootstrap.txt",
    "content": "[08:29:36] [main/INFO]: ModLauncher running: args [--username, schwabe22, --version, forge-28.0.75, --gameDir, C:\\Users\\alexa\\Twitch\\Minecraft\\Instances\\u, --assetsDir, C:\\Users\\alexa\\Twitch\\Minecraft\\Install\\assets, --assetIndex, 1.14, --uuid, dda7a75ded21403abffaf37a22658ae3, --accessToken, ????????, --userType, mojang, --versionType, release, --width, 1024, --height, 768, --launchTarget, fmlclient, --fml.forgeVersion, 28.0.75, --fml.mcVersion, 1.14.4, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20190829.143755]\n[08:29:36] [main/INFO]: ModLauncher 3.2.0+60+b86c1d4 starting: java version 1.8.0_221 by Oracle Corporation\n[08:29:41] [main/INFO]: OptiFineTransformationService.onLoad\n[08:29:41] [main/INFO]: OptiFine ZIP file: C:\\Users\\alexa\\Twitch\\Minecraft\\Instances\\u\\mods\\preview_OptiFine_1.14.4_HD_U_F4_pre3.jar\n[08:29:41] [main/INFO]: Added Lets Encrypt root certificates as additional trust\n[08:29:41] [main/INFO]: OptiFineTransformationService.initialize\n[08:29:42] [main/INFO]: OptiFineTransformationService.transformers\n[08:29:42] [main/INFO]: Targets: 238\n[08:29:42] [main/INFO]: additionalClassesLocator: [optifine., net.optifine.]\n[08:29:42] [main/INFO]: Launching target 'fmlclient' with arguments [--version, forge-28.0.75, --gameDir, C:\\Users\\alexa\\Twitch\\Minecraft\\Instances\\u, --assetsDir, C:\\Users\\alexa\\Twitch\\Minecraft\\Install\\assets, --username, schwabe22, --assetIndex, 1.14, --uuid, dda7a75ded21403abffaf37a22658ae3, --accessToken, ????????, --userType, mojang, --versionType, release, --width, 1024, --height, 768]\n[08:29:44] [Client thread/INFO]: [OptiFine] *** Reflector Forge ***\n[08:29:44] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: mods.betterfoliage.client.BetterFoliageClient\n[08:29:45] [Client thread/INFO]: [OptiFine] (Reflector) Method not present: net.minecraftforge.client.model.IModel.getTextures\n[08:29:46] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: net.minecraft.launchwrapper.Launch\n[08:29:46] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.fml.common.Loader\n[08:29:46] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.fml.client.SplashProgress\n[08:29:46] [Client thread/INFO]: [OptiFine] *** Reflector Vanilla ***\n[08:29:46] [Client thread/INFO]: Setting user: schwabe22\n[08:29:51] [Client thread/WARN]: Skipping bad option: lastServer:\n[08:29:51] [Client thread/INFO]: LWJGL Version: 3.2.2 build 10\n[08:29:52] [Client thread/INFO]: [OptiFine]\n[08:29:52] [Client thread/INFO]: [OptiFine] OptiFine_1.14.4_HD_U_F4_pre3\n[08:29:52] [Client thread/INFO]: [OptiFine] Build: 20190828-020229\n[08:29:52] [Client thread/INFO]: [OptiFine] OS: Windows 10 (amd64) version 10.0\n[08:29:52] [Client thread/INFO]: [OptiFine] Java: 1.8.0_221, Oracle Corporation\n[08:29:52] [Client thread/INFO]: [OptiFine] VM: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n[08:29:52] [Client thread/INFO]: [OptiFine] LWJGL: 3.3.0 Win32 WGL EGL OSMesa VisualC DLL\n[08:29:52] [Client thread/INFO]: [OptiFine] OpenGL: Radeon RX 580 Series, version 4.6.13570 Compatibility Profile Context 19.7.3 26.20.13001.18009, ATI Technologies Inc.\n[08:29:52] [Client thread/INFO]: [OptiFine] OpenGL Version: 4.6.13570\n[08:29:52] [Client thread/INFO]: [OptiFine] OpenGL Fancy fog: Not available (GL_NV_fog_distance)\n[08:29:52] [Client thread/INFO]: [OptiFine] Maximum texture size: 16384x16384\n[08:29:52] [VersionCheck/INFO]: [OptiFine] Checking for new version\n[08:29:52] [Client thread/INFO]: [Shaders] OpenGL Version: 4.6.13570 Compatibility Profile Context 19.7.3 26.20.13001.18009\n[08:29:52] [Client thread/INFO]: [Shaders] Vendor:  ATI Technologies Inc.\n[08:29:52] [Client thread/INFO]: [Shaders] Renderer: Radeon RX 580 Series\n[08:29:52] [Client thread/INFO]: [Shaders] Capabilities:  2.0  2.1  3.0  3.2  4.0\n[08:29:52] [Client thread/INFO]: [Shaders] GL_MAX_DRAW_BUFFERS: 8\n[08:29:52] [Client thread/INFO]: [Shaders] GL_MAX_COLOR_ATTACHMENTS_EXT: 8\n[08:29:52] [Client thread/INFO]: [Shaders] GL_MAX_TEXTURE_IMAGE_UNITS: 32\n[08:29:52] [Client thread/INFO]: [Shaders] Load shaders configuration.\n[08:29:52] [Client thread/INFO]: [Shaders] Loaded shaderpack: Sildurs Vibrant Shaders v1.22 High-Motionblur.zip\n[08:29:52] [Client thread/INFO]: [OptiFine] [Shaders] Worlds: -1, 1\n[08:29:52] [Client thread/WARN]: [OptiFine] Ambiguous shader option: SHADOW_MAP_BIAS\n[08:29:52] [Client thread/WARN]: [OptiFine]  - in shadow.vsh, shadow.fsh, gbuffers_textured.vsh, gbuffers_textured.fsh, gbuffers_skytextured.fsh, gbuffers_terrain.vsh, gbuffers_terrain.fsh, gbuffers_block.vsh, gbuffers_block.fsh, gbuffers_weather.vsh, gbuffers_water.vsh, gbuffers_water.fsh, composite.vsh, composite.fsh, composite1.vsh, composite1.fsh, composite2.fsh, composite3.vsh, composite3.fsh, final.vsh, final.fsh, world-1/shadow.vsh: 0.80\n[08:29:52] [Client thread/WARN]: [OptiFine]  - in world-1/gbuffers_textured.fsh: 0.8\n[08:29:52] [Client thread/WARN]: [OptiFine] Ambiguous shader option: SHADOW_MAP_BIAS\n[08:29:52] [Client thread/WARN]: [OptiFine]  - in shadow.vsh, shadow.fsh, gbuffers_textured.vsh, gbuffers_textured.fsh, gbuffers_skytextured.fsh, gbuffers_terrain.vsh, gbuffers_terrain.fsh, gbuffers_block.vsh, gbuffers_block.fsh, gbuffers_weather.vsh, gbuffers_water.vsh, gbuffers_water.fsh, composite.vsh, composite.fsh, composite1.vsh, composite1.fsh, composite2.fsh, composite3.vsh, composite3.fsh, final.vsh, final.fsh, world-1/shadow.vsh, world-1/gbuffers_textured.fsh, world-1/gbuffers_block.fsh: 0.80\n[08:29:52] [Client thread/WARN]: [OptiFine]  - in world-1/gbuffers_water.fsh: 0.8\n[08:29:52] [Client thread/INFO]: [OptiFine] [Shaders] Parsing block mappings: /shaders/block.properties\n[08:29:52] [Client thread/WARN]: [OptiFine] [Shaders] Invalid option: Nether_Fog, key: screen.FOG_SCREEN\n[08:29:52] [Client thread/WARN]: [OptiFine] [Shaders] Invalid option: nFogDensity, key: screen.FOG_SCREEN\n[08:29:52] [Client thread/WARN]: [OptiFine] [Shaders] Invalid option: Godrays_Quality, key: screen.SKY_SCREEN\n[08:29:52] [Client thread/INFO]: [Shaders] Custom uniform: inSwamp\n[08:29:52] [Client thread/INFO]: [Shaders] Custom uniform: BiomeTemp\n[08:29:52] [VersionCheck/INFO]: [OptiFine] Version found: F2\n[08:29:53] [Client thread/WARN]: No data fixer registered for entity clumps:xp_orb_big\n[08:29:53] [Client thread/INFO]: Potentially Dangerous alternative prefix `simplylight` for name `illuminant_block`, expected `minecraft`. This could be a intended override, but in most cases indicates a broken mod.\n[08:29:53] [Client thread/INFO]: Potentially Dangerous alternative prefix `simplylight` for name `illuminant_block_on`, expected `minecraft`. This could be a intended override, but in most cases indicates a broken mod.\n[08:29:53] [Client thread/INFO]: Potentially Dangerous alternative prefix `simplylight` for name `illuminant_slab`, expected `minecraft`. This could be a intended override, but in most cases indicates a broken mod.\n[08:29:53] [Client thread/INFO]: Potentially Dangerous alternative prefix `simplylight` for name `illuminant_panel`, expected `minecraft`. This could be a intended override, but in most cases indicates a broken mod.\n[08:29:53] [Client thread/INFO]: Potentially Dangerous alternative prefix `simplylight` for name `wall_lamp`, expected `minecraft`. This could be a intended override, but in most cases indicates a broken mod.\n[08:29:53] [Client thread/INFO]: Potentially Dangerous alternative prefix `simplylight` for name `lightbulb`, expected `minecraft`. This could be a intended override, but in most cases indicates a broken mod.\n[08:29:53] [Client thread/INFO]: Potentially Dangerous alternative prefix `simplylight` for name `rodlamp`, expected `minecraft`. This could be a intended override, but in most cases indicates a broken mod.\n[08:29:53] [Client thread/INFO]: Potentially Dangerous alternative prefix `yamda` for name `mining_dim`, expected `minecraft`. This could be a intended override, but in most cases indicates a broken mod.\n[08:29:53] [modloading-worker-7/INFO]: Forge mod loading, version 28.0.75, for MC 1.14.4 with MCP 20190829.143755\n[08:29:53] [modloading-worker-7/INFO]: MinecraftForge v28.0.75 Initialized\n[08:29:53] [modloading-worker-9/ERROR]: Failed to create mod instance. ModID: prefab, class com.wuest.prefab.Prefab\njava.lang.BootstrapMethodError: java.lang.IllegalAccessError: no such constructor: com.wuest.prefab.Proxy.ClientProxy.<init>()void/newInvokeSpecial\n\tat com.wuest.prefab.Prefab.lambda$new$0(Prefab.java:79) ~[?:1.4.0.4]\n\tat net.minecraftforge.fml.DistExecutor.runForDist(DistExecutor.java:63) ~[?:?]\n\tat com.wuest.prefab.Prefab.<init>(Prefab.java:79) ~[?:1.4.0.4]\n\tat sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[?:1.8.0_221]\n\tat sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) ~[?:1.8.0_221]\n\tat sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.reflect.Constructor.newInstance(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.Class.newInstance(Unknown Source) ~[?:1.8.0_221]\n\tat net.minecraftforge.fml.javafmlmod.FMLModContainer.constructMod(FMLModContainer.java:131) ~[?:28.0]\n\tat java.util.function.Consumer.lambda$andThen$0(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.function.Consumer.lambda$andThen$0(Unknown Source) ~[?:1.8.0_221]\n\tat net.minecraftforge.fml.ModContainer.transitionState(ModContainer.java:112) ~[?:?]\n\tat net.minecraftforge.fml.ModList.lambda$null$10(ModList.java:133) ~[?:?]\n\tat java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.ArrayList$ArrayListSpliterator.forEachRemaining(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.stream.AbstractPipeline.copyInto(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.stream.ForEachOps$ForEachTask.compute(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.concurrent.CountedCompleter.exec(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinTask.doExec(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinTask.doInvoke(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinTask.invoke(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.stream.ForEachOps$ForEachOp.evaluateParallel(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.stream.AbstractPipeline.evaluate(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.stream.ReferencePipeline.forEach(Unknown Source) ~[?:1.8.0_221]\n\tat java.util.stream.ReferencePipeline$Head.forEach(Unknown Source) ~[?:1.8.0_221]\n\tat net.minecraftforge.fml.ModList.lambda$dispatchParallelEvent$11(ModList.java:133) ~[?:?]\n\tat java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinTask.doExec(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinPool$WorkQueue.runTask(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinPool.runWorker(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source) [?:1.8.0_221]\nCaused by: java.lang.IllegalAccessError: no such constructor: com.wuest.prefab.Proxy.ClientProxy.<init>()void/newInvokeSpecial\n\tat java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(Unknown Source) ~[?:1.8.0_221]\n\t... 32 more\nCaused by: java.lang.NoClassDefFoundError: net/minecraftforge/common/crafting/IConditionSerializer\n\tat java.lang.invoke.MethodHandleNatives.resolve(Native Method) ~[?:1.8.0_221]\n\tat java.lang.invoke.MemberName$Factory.resolve(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.invoke.MemberName$Factory.resolveOrFail(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.invoke.MethodHandles$Lookup.resolveOrFail(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(Unknown Source) ~[?:1.8.0_221]\n\t... 32 more\nCaused by: java.lang.ClassNotFoundException: net.minecraftforge.common.crafting.IConditionSerializer\n\tat java.lang.ClassLoader.findClass(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.ClassLoader.loadClass(Unknown Source) ~[?:1.8.0_221]\n\tat cpw.mods.modlauncher.TransformingClassLoader.loadClass(TransformingClassLoader.java:102) ~[modlauncher-3.2.0.jar:?]\n\tat java.lang.ClassLoader.loadClass(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.invoke.MethodHandleNatives.resolve(Native Method) ~[?:1.8.0_221]\n\tat java.lang.invoke.MemberName$Factory.resolve(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.invoke.MemberName$Factory.resolveOrFail(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.invoke.MethodHandles$Lookup.resolveOrFail(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(Unknown Source) ~[?:1.8.0_221]\n\t... 32 more\n[08:29:53] [modloading-worker-3/WARN]: No data fixer registered for entity upgrade_aquatic:nautilus\n[08:29:53] [modloading-worker-3/WARN]: No data fixer registered for entity upgrade_aquatic:pike\n[08:29:53] [modloading-worker-2/INFO]: Loading config file C:\\Users\\alexa\\Twitch\\Minecraft\\Instances\\u\\config\\equipment-tooltips-client.toml\n[08:29:53] [modloading-worker-3/WARN]: No data fixer registered for entity upgrade_aquatic:lionfish\n[08:29:54] [modloading-worker-8/INFO]: Ruins instance built, events registered\n[08:29:55] [modloading-worker-9/WARN]: No data fixer registered for entity cfm:seat\n[08:29:55] [Client thread/FATAL]: Failed to complete lifecycle event CONSTRUCT, 1 errors found\n[08:29:55] [Client thread/FATAL]: EventBus 0 shutting down - future events will not be posted.\njava.lang.Exception: stacktrace\n\tat net.minecraftforge.eventbus.EventBus.shutdown(EventBus.java:278) ~[eventbus-1.0.0-service.jar:?]\n\tat net.minecraftforge.fml.client.ClientModLoader.lambda$createRunnableWithCatch$5(ClientModLoader.java:97) ~[?:?]\n\tat net.minecraftforge.fml.client.ClientModLoader.begin(ClientModLoader.java:79) ~[?:?]\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:453) ~[?:?]\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:365) ~[?:?]\n\tat net.minecraft.client.main.Main.main(SourceFile:155) ~[1.14.4.jar:?]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_221]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_221]\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_221]\n\tat java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_221]\n\tat net.minecraftforge.fml.loading.FMLClientLaunchProvider.lambda$launchService$0(FMLClientLaunchProvider.java:56) ~[forge-1.14.4-28.0.75.jar:28.0]\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) [modlauncher-3.2.0.jar:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:50) [modlauncher-3.2.0.jar:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:68) [modlauncher-3.2.0.jar:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:80) [modlauncher-3.2.0.jar:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:65) [modlauncher-3.2.0.jar:?]\n[08:29:55] [Client thread/INFO]: [OptiFine] (Reflector) Method not present: net.minecraftforge.client.model.ModelLoader.onRegisterItems\n[08:29:56] [Client thread/INFO]: Narrator library for x64 successfully loaded\n[08:29:56] [Client thread/INFO]: [OptiFine] *** Reloading textures ***\n[08:29:56] [Client thread/INFO]: [OptiFine] Resource packs: angelring-1.14.4-1.0.1.jar, AppleSkin-mc1.14.4-forge-1.0.12.jar, BadMobs-1.14.4-3.0.1.jar, BetterAdvancements-1.14.4-0.1.0.87.jar, BiomesOPlenty-1.14.4-9.0.0.250-universal.jar, Bookshelf-1.14.4-4.2.40.jar, furniture-7.0.0-pre5-1.14.4.jar, ChanceCubes-1.14.4-4.0.0.298.jar, classicbar-2.0.0a.jar, clearwater-1.3.jar, Clumps-4.0.0.jar, Controlling-5.0.3.jar, craftingstation-2.0.2a.jar, CraftingTweaks_1.14.4-10.1.3.jar, CustomSelectionBox-1.14.2-1.4.8.jar, Cyclic-1.14.4-0.0.9.jar, DefaultSettings-1.14.x-1.3.5.jar, elevatorid-1.14.4-1.5.1.jar, EquipmentTooltips-1.14.4-1.3.2+10.jar, ferroustry-1.1.jar, FleshToLeather-1.14.3-1.1.0.jar, forge-1.14.4-28.0.75-universal.jar, FTBUtilitiesBackups-2.0.0.10.jar, gravestone-1.15.0.jar, inventorysorter-1.14.4-15.2.0.jar, ironchest-1.14.4-9.0.4.jar, jei-1.14.4-6.0.0.10.jar, KleeSlabs_1.14.4-7.4.7.jar, LightOverlay-3.4.jar, LinkedDestruction-1.14.4-0.6.0.jar, MCAdditions-1.2.0.jar, MCFurnace-1.14.4-DEV-1.2.1.jar, mcjtylib-1.14-3.5.6-alpha.jar, mining_dimension-1.14.4-1.0.5.jar, modnametooltip_1.14.3-1.12.1.jar, Neat 1.5-19.jar, Placebo-1.14.4-2.1.11.jar, practicaltools-1.14.4-1.4.jar, prefab-1.4.0.4.jar, quickhomes-1.14.4-1.2.4.1.jar, randompatches-1.14.4-1.18.1.1.jar, reap-1.10.1.jar, roadstuff-1.14.4-2.0.4.0.jar, ruins-1.14.4.1.jar, simplylight-0.8.7.jar, smooth-scrolling-everywhere-1.0.jar, speedyladders-1.14.4-1.0.2.jar, SimpleStorageNetwork-1.14.4-0.0.10.jar, tramplestopper-1.14.4-2.0.0.22-universal.jar, Upgrade Aquatic Wave Three v5 - Beta v1.0.7.jar, useful_backpacks-1.14.4-1.7.0.34.jar, u_team_core-1.14.4-2.7.0.129.jar, Hwyla-forge-1.10.5-B66_1.14.4.jar, worldedit-forge-mc1.14.4-7.1.0-SNAPSHOT-dist.jar, Xaeros_Minimap_1.17.8_Forge_1.14.4.jar, XaerosWorldMap_1.4.8_Forge_1.14.4.jar, XL-Food-Mod-1.14.4-2.1.0.jar, YAMDA-4.0.1.jar, PureBDcraft 256x MC114.zip, PureBDcraft CTM Transitions MC114.zip\n[08:29:56] [Thread-1/FATAL]: Forge config just got changed on the file system!\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multitexture: false\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/glass/frame/glass.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/glass/frame/glass_pane.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/brick/brick.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/brick/brick2.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/brick/brick3.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/chiseled_sandstone/chiseled_sandstone.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/cut_red_sandstone/cut_red_sandstone.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/cut_sandstone/cut_sandstone.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/red_sandstone/red_sandstone.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/sandstone/sandstone.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/smooth_stone/smooth_stone.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/black/black_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/blue/blue_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/brown/brown_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/cyan/cyan_terracotta.properties\n[08:29:56] [Server-Worker-6/WARN]: Configuration file C:\\Users\\alexa\\Twitch\\Minecraft\\Instances\\u\\config\\craftingtweaks-client.toml is not correct. Correcting\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/gray/gray_terracotta.properties\n[08:29:56] [Server-Worker-6/WARN]: Incorrect key client.addons was corrected from SimpleCommentedConfig:{minecraft=ENABLED} to null\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/green/green_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/light_blue/light_blue_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/light_gray/light_gray_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/lime/lime_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/magenta/magenta_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/normal/terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/orange/orange_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/pink/pink_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/purple/purple_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/red/red_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/white/white_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/yellow/yellow_terracotta.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/diamond/diamond_block.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/emerald/emerald_block.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/gold/gold_block.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/iron/iron_block.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/lapis/lapis_block.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/quartz/side/quartz_block_sides.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/quartz/top/quartz_block_ends.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/redstone/anim/redstone_block.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/redstone/static/redstone_block.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mushroom/brown/brown_mushroom_block.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mushroom/inside/mushroom_block_inside.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mushroom/red/red_mushroom_block.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/sugar_cane/sugar_cane.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/0_clay/clay_overlay.properties\n[08:29:56] [Server-Worker-6/WARN]: Configuration file C:\\Users\\alexa\\Twitch\\Minecraft\\Instances\\u\\config\\gravestone-client.toml is not correct. Correcting\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/1_red_sand/red_sand_overlay.properties\n[08:29:56] [Server-Worker-6/WARN]: Incorrect key dimension_names was corrected from [minecraft:overworld=Overworld, minecraft:nether=Nether, minecraft:the_end=The End] to [minecraft:overworld=Overworld, minecraft:nether=Nether, minecraft:the_end=The End]\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/2_sand/clay_alt_overlay.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/2_sand/sand_overlay.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/3_gravel/gravel_overlay.properties\n[08:29:56] [Client thread/INFO]: [OptiFine] Loading optifine/color.properties\n[08:29:56] [Client thread/INFO]: [OptiFine] screen.loading = 1e1e1e\n[08:29:56] [Client thread/INFO]: [OptiFine] screen.loading.bar = ffffff\n[08:29:56] [Client thread/INFO]: [OptiFine] screen.loading.progress = b4c8ff\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/4_grass/grass_overlay.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/5_snow/snow_overlay.properties\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-6/INFO]: Loading blacklist from config file.\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-6/WARN]: Configuration file C:\\Users\\alexa\\Twitch\\Minecraft\\Instances\\u\\config\\craftingtweaks-common.toml is not correct. Correcting\n[08:29:56] [Server-Worker-6/WARN]: Incorrect key common.compressBlacklist was corrected from [minecraft:sandstone, minecraft:iron_trapdoor] to [minecraft:sandstone, minecraft:iron_trapdoor]\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-6/WARN]: Configuration file C:\\Users\\alexa\\Twitch\\Minecraft\\Instances\\u\\config\\kleeslabs-common.toml is not correct. Correcting\n[08:29:56] [Server-Worker-6/WARN]: Incorrect key common.disabledCompat was corrected from [] to []\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-6/ERROR]: Skipping lifecycle event SETUP, 1 errors found.\n[08:29:56] [Server-Worker-6/FATAL]: Failed to complete lifecycle event SETUP, 1 errors found\n[08:29:56] [Server-Worker-6/FATAL]: EventBus 0 shutting down - future events will not be posted.\njava.lang.Exception: stacktrace\n\tat net.minecraftforge.eventbus.EventBus.shutdown(EventBus.java:278) ~[eventbus-1.0.0-service.jar:?]\n\tat net.minecraftforge.fml.client.ClientModLoader.lambda$createRunnableWithCatch$5(ClientModLoader.java:97) ~[?:?]\n\tat net.minecraftforge.fml.client.ClientModLoader.startModLoading(ClientModLoader.java:105) ~[?:?]\n\tat net.minecraftforge.fml.client.ClientModLoader.lambda$onreload$3(ClientModLoader.java:87) ~[?:?]\n\tat net.minecraftforge.fml.client.ClientModLoader.lambda$createRunnableWithCatch$5(ClientModLoader.java:95) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncRun.run(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.CompletableFuture$AsyncRun.exec(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinTask.doExec(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinPool$WorkQueue.runTask(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinPool.runWorker(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source) [?:1.8.0_221]\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:56] [Server-Worker-2/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:57] [Server-Worker-2/INFO]: [OptiFine] BetterGrass: Parsing default configuration optifine/bettergrass.properties\n[08:29:57] [Server-Worker-2/INFO]: [OptiFine] Scaled non power of 2: jei:gui/icons/recipe_transfer, 7 -> 14\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] CustomItems: optifine/cit/elytra/broken_elytra.properties\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multitexture: false\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/glass/frame/glass.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/glass/frame/glass_pane.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/brick/brick.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/brick/brick2.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/brick/brick3.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/chiseled_sandstone/chiseled_sandstone.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/cut_red_sandstone/cut_red_sandstone.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/cut_sandstone/cut_sandstone.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/red_sandstone/red_sandstone.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/sandstone/sandstone/sandstone.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/smooth_stone/smooth_stone.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/black/black_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/blue/blue_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/brown/brown_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/cyan/cyan_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/gray/gray_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/green/green_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/light_blue/light_blue_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/light_gray/light_gray_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/lime/lime_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/magenta/magenta_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/normal/terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/orange/orange_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/pink/pink_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/purple/purple_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/red/red_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/white/white_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/hard_materials/terracotta/yellow/yellow_terracotta.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/diamond/diamond_block.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/emerald/emerald_block.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/gold/gold_block.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/iron/iron_block.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/lapis/lapis_block.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/quartz/side/quartz_block_sides.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/quartz/top/quartz_block_ends.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/redstone/anim/redstone_block.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/minerals/redstone/static/redstone_block.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mushroom/brown/brown_mushroom_block.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mushroom/inside/mushroom_block_inside.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mushroom/red/red_mushroom_block.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/sugar_cane/sugar_cane.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/0_clay/clay_overlay.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/1_red_sand/red_sand_overlay.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/2_sand/clay_alt_overlay.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/2_sand/sand_overlay.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/3_gravel/gravel_overlay.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/4_grass/grass_overlay.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/transitions/5_snow/snow_overlay.properties\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/WARN]: [OptiFine] Unknown resource pack file: dummy\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] Multipass connected textures: true\n[08:29:59] [Server-Worker-7/INFO]: [OptiFine] BetterGrass: Parsing default configuration optifine/bettergrass.properties\n[08:30:00] [Server-Worker-7/INFO]: [OptiFine] Sprite size: 256\n[08:30:00] [Server-Worker-7/INFO]: [OptiFine] Mipmap levels: 8\n[08:30:00] [Server-Worker-7/INFO]: [OptiFine] Scaled too small texture: minecraft:block/redstone_dust_overlay, 16 -> 256\n[08:30:00] [Server-Worker-7/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 256\n[08:30:00] [Server-Worker-7/INFO]: [OptiFine] Scaled too small texture: minecraft:block/conduit, 16 -> 256\n[08:30:00] [Server-Worker-7/INFO]: [OptiFine] Scaled too small texture: minecraft:block/water_overlay, 32 -> 256\n[08:30:02] [Server-Worker-7/ERROR]: Skipping lifecycle event ENQUEUE_IMC, 1 errors found.\n[08:30:02] [Server-Worker-7/FATAL]: Failed to complete lifecycle event ENQUEUE_IMC, 1 errors found\n[08:30:02] [Server-Worker-7/FATAL]: EventBus 0 shutting down - future events will not be posted.\njava.lang.Exception: stacktrace\n\tat net.minecraftforge.eventbus.EventBus.shutdown(EventBus.java:278) ~[eventbus-1.0.0-service.jar:?]\n\tat net.minecraftforge.fml.client.ClientModLoader.lambda$createRunnableWithCatch$5(ClientModLoader.java:97) ~[?:?]\n\tat net.minecraftforge.fml.client.ClientModLoader.finishModLoading(ClientModLoader.java:118) ~[?:?]\n\tat net.minecraftforge.fml.client.ClientModLoader.lambda$onreload$4(ClientModLoader.java:89) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniRun(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.CompletableFuture$UniRun.tryFire(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.CompletableFuture$Completion.exec(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinTask.doExec(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinPool$WorkQueue.runTask(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinPool.runWorker(Unknown Source) [?:1.8.0_221]\n\tat java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source) [?:1.8.0_221]\n[08:30:02] [Client thread/WARN]: Skipping bad option: lastServer:\n[08:30:02] [Client thread/INFO]: OpenAL initialized.\n[08:30:02] [Client thread/INFO]: Sound engine started\n[08:30:02] [Client thread/INFO]: Created: 8192x4096 textures-atlas\n[08:30:02] [Client thread/INFO]: [Shaders] Allocate texture map normal: 8192x4096, mipmaps: 0\n[08:30:02] [Client thread/INFO]: [Shaders] Allocate texture map specular: 8192x4096, mipmaps: 0\n[08:30:02] [Client thread/INFO]: [OptiFine] Animated sprites: 5\n[08:30:02] [Client thread/INFO]: Created: 16384x8192 textures-atlas\n[08:30:02] [Client thread/INFO]: [Shaders] Allocate texture map normal: 16384x8192, mipmaps: 8\n[08:30:02] [Client thread/INFO]: [Shaders] Allocate texture map specular: 16384x8192, mipmaps: 8\n[08:30:03] [Client thread/INFO]: [OptiFine] Animated sprites: 34\n[08:30:15] [Client thread/INFO]: Created: 4096x2048 textures/particle-atlas\n[08:30:15] [Client thread/INFO]: Created: 4096x4096 textures/painting-atlas\n[08:30:15] [Client thread/INFO]: Created: 512x512 textures/mob_effect-atlas"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/crash-report.txt",
    "content": "OpenJDK 64-Bit Server VM warning: Option --illegal-access is deprecated and will be removed in a future release.\n[23:17:00] [main/INFO]: Loading for game Minecraft 1.17.1\n[23:17:00] [main/INFO]: [FabricLoader] Loading 5 mods:\n\t- fabric@0.40.1+1.17\n\t- fabricloader@0.11.6\n\t- java@16\n\t- minecraft@1.17.1\n\t- modid@1.0.0\n[23:17:00] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.2 Source=file:/C:/Users/user/AppData/Roaming/.minecraft/libraries/net/fabricmc/sponge-mixin/0.9.4+mixin.0.8.2/sponge-mixin-0.9.4+mixin.0.8.2.jar Service=Knot/Fabric Env=CLIENT\n[23:17:00] [main/INFO]: Compatibility level set to JAVA_16\n[23:17:10] [Render thread/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD'\n[23:17:12] [Render thread/ERROR]: Failed to verify authentication\ncom.mojang.authlib.exceptions.InvalidCredentialsException: Status: 401\n\tat com.mojang.authlib.exceptions.MinecraftClientHttpException.toAuthenticationException(MinecraftClientHttpException.java:56) ~[authlib-2.3.31.jar:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilSocialInteractionsService.checkPrivileges(YggdrasilSocialInteractionsService.java:112) ~[authlib-2.3.31.jar:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilSocialInteractionsService.<init>(YggdrasilSocialInteractionsService.java:42) ~[authlib-2.3.31.jar:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService.createSocialInteractionsService(YggdrasilAuthenticationService.java:151) ~[authlib-2.3.31.jar:?]\n\tat net.minecraft.class_310.method_31382(class_310.java:670) [intermediary-1.17.1s.jar:?]\n\tat net.minecraft.class_310.<init>(class_310.java:429) [intermediary-1.17.1s.jar:?]\n\tat net.minecraft.client.main.Main.main(Main.java:179) [intermediary-1.17.1s.jar:?]\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[?:?]\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]\n\tat java.lang.reflect.Method.invoke(Method.java:567) ~[?:?]\n\tat net.fabricmc.loader.game.MinecraftGameProvider.launch(MinecraftGameProvider.java:234) [fabric-loader-0.11.6.jar:?]\n\tat net.fabricmc.loader.launch.knot.Knot.launch(Knot.java:153) [fabric-loader-0.11.6.jar:?]\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:28) [fabric-loader-0.11.6.jar:?]\nCaused by: com.mojang.authlib.exceptions.MinecraftClientHttpException: Status: 401\n\tat com.mojang.authlib.minecraft.client.MinecraftClient.readInputStream(MinecraftClient.java:77) ~[authlib-2.3.31.jar:?]\n\tat com.mojang.authlib.minecraft.client.MinecraftClient.get(MinecraftClient.java:47) ~[authlib-2.3.31.jar:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilSocialInteractionsService.checkPrivileges(YggdrasilSocialInteractionsService.java:104) ~[authlib-2.3.31.jar:?]\n\t... 12 more\n[23:17:12] [Render thread/INFO]: Setting user: HMCL\n[23:17:12] [Render thread/INFO]: [STDOUT]: Hello Fabric world!\n---- Minecraft Crash Report ----\n// Why did you do that?\n\nTime: 2021/9/20 下午11:17\nDescription: Initializing game\n\njava.lang.RuntimeException: Could not execute entrypoint stage 'main' due to errors, provided by 'modid'!\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke0(EntrypointUtils.java:50)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke(EntrypointUtils.java:33)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointClient.start(EntrypointClient.java:33)\n\tat net.minecraft.class_310.<init>(class_310.java:457)\n\tat net.minecraft.client.main.Main.main(Main.java:179)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:567)\n\tat net.fabricmc.loader.game.MinecraftGameProvider.launch(MinecraftGameProvider.java:234)\n\tat net.fabricmc.loader.launch.knot.Knot.launch(Knot.java:153)\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:28)\nCaused by: java.lang.IllegalArgumentException: The driver does not appear to support OpenGL!\n\tat net.fabricmc.example.ExampleMod.onInitialize(ExampleMod.java:14)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke0(EntrypointUtils.java:47)\n\t... 11 more\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Render thread\nStacktrace:\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke0(EntrypointUtils.java:50)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke(EntrypointUtils.java:33)\n\tat net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointClient.start(EntrypointClient.java:33)\n\tat net.minecraft.class_310.<init>(class_310.java:457)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.main.Main.main(Main.java:179)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:567)\n\tat net.fabricmc.loader.game.MinecraftGameProvider.launch(MinecraftGameProvider.java:234)\n\tat net.fabricmc.loader.launch.knot.Knot.launch(Knot.java:153)\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:28)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.17.1\n\tMinecraft Version ID: 1.17.1\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 16.0.1, Microsoft\n\tJava VM Version: OpenJDK 64-Bit Server VM (mixed mode, sharing), Microsoft\n\tMemory: 2917155336 bytes (2782 MiB) / 3221225472 bytes (3072 MiB) up to 4294967296 bytes (4096 MiB)\n\tCPUs: 12\n\tProcessor Vendor: GenuineIntel\n\tProcessor Name: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\n\tIdentifier: Intel64 Family 6 Model 158 Stepping 10\n\tMicroarchitecture: Coffee Lake\n\tFrequency (GHz): 2.59\n\tNumber of physical packages: 1\n\tNumber of physical CPUs: 6\n\tNumber of logical CPUs: 12\n\tGraphics card #0 name: NVIDIA GeForce GTX 1660 Ti\n\tGraphics card #0 vendor: NVIDIA (0x10de)\n\tGraphics card #0 VRAM (MB): 4095.00\n\tGraphics card #0 deviceId: 0x2191\n\tGraphics card #0 versionInfo: DriverVersion=27.21.14.6172\n\tMemory slot #0 capacity (MB): 8192.00\n\tMemory slot #0 clockSpeed (GHz): 2.67\n\tMemory slot #0 type: DDR4\n\tMemory slot #1 capacity (MB): 8192.00\n\tMemory slot #1 clockSpeed (GHz): 2.67\n\tMemory slot #1 type: DDR4\n\tVirtual memory max (MB): 53167.24\n\tVirtual memory used (MB): 35112.71\n\tSwap memory total (MB): 36864.00\n\tSwap memory used (MB): 3643.04\n\tJVM Flags: 12 total; -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -Xmn128m -Xmx4096m -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tLaunched Version: 1.17.1s\n\tBackend library: LWJGL version 3.2.2 build 10\n\tBackend API: NO CONTEXT\n\tWindow size: <not initialized>\n\tGL Caps: Using framebuffer using OpenGL 3.2\n\tGL debug messages: <disabled>\n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'fabric'\n\tType: Client (map_client.txt)\n\tCPU: <unknown>\n#@!@# Game crashed! Crash report saved to: #@!@# C:\\Users\\user\\AppData\\Roaming\\.minecraft\\crash-reports\\crash-2021-09-20_23.17.13-client.txt\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/duplicated_mod.txt",
    "content": "﻿[14:54:54] [main/INFO]: Loading tweak class name net.minecraftforge.fml.common.launcher.FMLTweaker\n[14:54:54] [main/INFO]: Using primary tweak class name net.minecraftforge.fml.common.launcher.FMLTweaker\n[14:54:54] [main/INFO]: Loading tweak class name optifine.OptiFineForgeTweaker\n[14:54:54] [main/INFO]: Calling tweak class net.minecraftforge.fml.common.launcher.FMLTweaker\n[14:54:54] [main/INFO]: Forge Mod Loader version 14.23.5.2860 for Minecraft 1.12.2 loading\n[14:54:54] [main/INFO]: Java is Java HotSpot(TM) 64-Bit Server VM, version 1.8.0_51, running on Windows 8.1:amd64:6.3, installed at C:\\Users\\FIREBAT\\AppData\\Roaming\\.minecraft\\cache\\java\\jre-legacy\\windows-x64\\jre-legacy\n[14:54:54] [main/INFO]: Searching A:\\MC启动器\\..\\MC\\versions\\1.12.2\\mods for mods\n[14:54:54] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in 4633763120329575466@3@16.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[14:54:54] [main/WARN]: The coremod atomicstryker.dynamiclights.common.DLFMLCorePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[14:54:54] [main/WARN]: The coremod DLFMLCorePlugin (atomicstryker.dynamiclights.common.DLFMLCorePlugin) is not signed!\n[14:54:54] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in Bloodmoon-MC1.12.2-1.5.3.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[14:54:54] [main/WARN]: The coremod lumien.bloodmoon.asm.LoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[14:54:54] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in ftb-ultimine-1202.3.5.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[14:54:54] [main/WARN]: The coremod FTBUltimineASM (com.feed_the_beast.mods.ftbultimine.FTBUltimineLoadingPlugin) is not signed!\n[14:54:54] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in InventoryTweaks-1.64+dev.146.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[14:54:54] [main/WARN]: The coremod invtweaks.forge.asm.FMLPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[14:54:54] [main/INFO]: Calling tweak class optifine.OptiFineForgeTweaker\n[14:54:54] [main/INFO]: [optifine.OptiFineForgeTweaker:dbg:56]: OptiFineForgeTweaker: acceptOptions\n[14:54:54] [main/INFO]: [optifine.OptiFineForgeTweaker:dbg:56]: OptiFineForgeTweaker: injectIntoClassLoader\n[14:54:54] [main/INFO]: [optifine.OptiFineClassTransformer:dbg:242]: OptiFine ClassTransformer\n[14:54:54] [main/INFO]: [optifine.OptiFineClassTransformer:dbg:242]: OptiFine ZIP file: A:\\MC\\libraries\\optifine\\OptiFine\\1.12.2_HD_U_G6_pre1\\OptiFine-1.12.2_HD_U_G6_pre1-installer.jar\n[14:54:54] [main/INFO]: Loading tweak class name net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker\n[14:54:54] [main/INFO]: Loading tweak class name net.minecraftforge.fml.common.launcher.FMLDeobfTweaker\n[14:54:54] [main/INFO]: Calling tweak class net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker\n[14:54:54] [main/INFO]: Calling tweak class net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker\n[14:54:54] [main/INFO]: Calling tweak class net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper\n[14:54:55] [main/INFO]: Found valid fingerprint for Minecraft Forge. Certificate fingerprint e3c3d50c7c986df74c645c0ac54639741c90a557\n[14:54:55] [main/INFO]: Found valid fingerprint for Minecraft. Certificate fingerprint cd99959656f753dc28d863b46769f7f8fbaefcfc\n[14:54:55] [main/INFO]: Calling tweak class net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper\n[14:54:55] [main/INFO]: Calling tweak class net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper\n[14:54:55] [main/INFO]: Calling tweak class net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper\n[14:54:55] [main/INFO]: Calling tweak class net.minecraftforge.fml.common.launcher.FMLDeobfTweaker\n[14:54:56] [main/INFO]: Calling tweak class net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper\n[14:54:56] [main/INFO]: Calling tweak class net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper\n[14:54:56] [main/INFO]: Loading tweak class name net.minecraftforge.fml.common.launcher.TerminalTweaker\n[14:54:56] [main/INFO]: Calling tweak class net.minecraftforge.fml.common.launcher.TerminalTweaker\n[14:54:56] [main/INFO]: [optifine.OptiFineForgeTweaker:dbg:56]: OptiFineForgeTweaker: getLaunchArguments\n[14:54:56] [main/INFO]: Launching wrapped minecraft {net.minecraft.client.main.Main}\n[14:54:56] [main/INFO]: [atomicstryker.dynamiclights.common.DLTransformer:handleWorldTransform:80]: **************** Dynamic Lights transform running on World, obf: true *********************** \n[14:54:56] [main/INFO]: [atomicstryker.dynamiclights.common.DLTransformer:handleWorldTransform:91]: In target method a:(Let;Lana;)I, Patching!\n[14:54:56] [main/INFO]: [atomicstryker.dynamiclights.common.DLTransformer:handleWorldTransform:148]: Found ASTORE Node at index 19, is writing variable number: 3\n[14:54:56] [Client thread/INFO]: Setting user: liangg\n[14:54:58] [Client thread/INFO]: [OptiFine] *** Reflector Forge ***\n[14:54:58] [Client thread/INFO]: [OptiFine] *** Reflector Vanilla ***\n[14:54:58] [Client thread/WARN]: Skipping bad option: lastServer:\n[14:54:58] [Client thread/INFO]: LWJGL Version: 2.9.4\n[14:54:59] [Client thread/INFO]: [OptiFine] \n[14:54:59] [Client thread/INFO]: [OptiFine] OptiFine_1.12.2_HD_U_G6_pre1\n[14:54:59] [Client thread/INFO]: [OptiFine] Build: 20210323-161358\n[14:54:59] [Client thread/INFO]: [OptiFine] OS: Windows 8.1 (amd64) version 6.3\n[14:54:59] [Client thread/INFO]: [OptiFine] Java: 1.8.0_51, Oracle Corporation\n[14:54:59] [Client thread/INFO]: [OptiFine] VM: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n[14:54:59] [Client thread/INFO]: [OptiFine] LWJGL: 2.9.4\n[14:54:59] [Client thread/INFO]: [OptiFine] OpenGL: NVIDIA GeForce RTX 3060 Laptop GPU/PCIe/SSE2, version 4.6.0 NVIDIA 522.25, NVIDIA Corporation\n[14:54:59] [Client thread/INFO]: [OptiFine] OpenGL Version: 4.6.0\n[14:54:59] [Client thread/INFO]: [OptiFine] Maximum texture size: 32768x32768\n[14:54:59] [VersionCheck/INFO]: [OptiFine] Checking for new version\n[14:54:59] [Client thread/INFO]: [Shaders] OpenGL Version: 4.6.0 NVIDIA 522.25\n[14:54:59] [Client thread/INFO]: [Shaders] Vendor:  NVIDIA Corporation\n[14:54:59] [Client thread/INFO]: [Shaders] Renderer: NVIDIA GeForce RTX 3060 Laptop GPU/PCIe/SSE2\n[14:54:59] [Client thread/INFO]: [Shaders] Capabilities:  2.0  2.1  3.0  3.2  4.0 \n[14:54:59] [Client thread/INFO]: [Shaders] GL_MAX_DRAW_BUFFERS: 8\n[14:54:59] [Client thread/INFO]: [Shaders] GL_MAX_COLOR_ATTACHMENTS_EXT: 8\n[14:54:59] [Client thread/INFO]: [Shaders] GL_MAX_TEXTURE_IMAGE_UNITS: 32\n[14:54:59] [Client thread/INFO]: [Shaders] Load shaders configuration.\n[14:54:59] [Client thread/INFO]: [Shaders] Loaded shaderpack: Dramatic+Skys+Demo+1.5.3.3.zip\n[14:54:59] [Client thread/INFO]: [OptiFine] [Shaders] Delayed loading of block mappings after resources are loaded\n[14:54:59] [Client thread/INFO]: [OptiFine] [Shaders] Delayed loading of item mappings after resources are loaded\n[14:54:59] [Client thread/INFO]: [OptiFine] [Shaders] Delayed loading of entity mappings after resources are loaded\n[14:54:59] [Client thread/INFO]: Forge Mod Loader has detected optifine OptiFine_1.12.2_HD_U_G6_pre1, enabling compatibility features\n[14:54:59] [Client thread/INFO]: -- System Details --\nDetails:\n\tMinecraft Version: 1.12.2\n\tOperating System: Windows 8.1 (amd64) version 6.3\n\tJava Version: 1.8.0_51, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 723938528 bytes (690 MB) / 1073741824 bytes (1024 MB) up to 8489271296 bytes (8096 MB)\n\tJVM Flags: 11 total; -Xmx8096m -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: \n\tLoaded coremods (and transformers): \nFTBUltimineASM (ftb-ultimine-1202.3.5.jar)\n  \nDLFMLCorePlugin (4633763120329575466@3@16.jar)\n  atomicstryker.dynamiclights.common.DLTransformer\nInventory Tweaks Coremod (InventoryTweaks-1.64+dev.146.jar)\n  invtweaks.forge.asm.ContainerTransformer\nLoadingPlugin (Bloodmoon-MC1.12.2-1.5.3.jar)\n  lumien.bloodmoon.asm.ClassTransformer\n\tGL info: ' Vendor: 'NVIDIA Corporation' Version: '4.6.0 NVIDIA 522.25' Renderer: 'NVIDIA GeForce RTX 3060 Laptop GPU/PCIe/SSE2'\n[14:54:59] [Client thread/INFO]: MinecraftForge v14.23.5.2860 Initialized\n[14:54:59] [Client thread/INFO]: Starts to replace vanilla recipe ingredients with ore ingredients.\n[14:54:59] [Client thread/INFO]: Invalid recipe found with multiple oredict ingredients in the same ingredient...\n[14:54:59] [Client thread/INFO]: Replaced 1227 ore ingredients\n[14:54:59] [Client thread/INFO]: Searching A:\\MC启动器\\..\\MC\\versions\\1.12.2\\mods for mods\n[14:55:00] [Client thread/WARN]: Mod betterbuilderswands is missing the required element 'version' and a version.properties file could not be found. Falling back to metadata version 0.11.1\n[14:55:00] [VersionCheck/INFO]: [OptiFine] Version found: G5\n[14:55:00] [Client thread/WARN]: Mod codechickenlib is missing the required element 'version' and a version.properties file could not be found. Falling back to metadata version 3.2.3.358\n[14:55:00] [Client thread/WARN]: Mod ironchest is missing the required element 'version' and a version.properties file could not be found. Falling back to metadata version 1.12.2-7.0.67.844\n[14:55:01] [Client thread/FATAL]: Found a duplicate mod durabilityviewer at [A:\\MC启动器\\..\\MC\\versions\\1.12.2\\mods\\DurabilityViewer-1.12-forge14.21.1.2387-1.6.jar, A:\\MC启动器\\..\\MC\\versions\\1.12.2\\mods\\4659524562224055466@3@38.jar]\n[14:55:01] [Client thread/ERROR]: An exception was thrown, the game will display an error screen and halt.\nnet.minecraftforge.fml.common.DuplicateModsFoundException: \nDuplicate Mods:\n\tdurabilityviewer : A:\\MC启动器\\..\\MC\\versions\\1.12.2\\mods\\DurabilityViewer-1.12-forge14.21.1.2387-1.6.jar\n\tdurabilityviewer : A:\\MC启动器\\..\\MC\\versions\\1.12.2\\mods\\4659524562224055466@3@38.jar\n\n\n\tat net.minecraftforge.fml.common.Loader.identifyDuplicates(Loader.java:466) ~[Loader.class:?]\n\tat net.minecraftforge.fml.common.Loader.identifyMods(Loader.java:428) ~[Loader.class:?]\n\tat net.minecraftforge.fml.common.Loader.loadMods(Loader.java:568) ~[Loader.class:?]\n\tat net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:232) [FMLClientHandler.class:?]\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:467) [bib.class:?]\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:378) [bib.class:?]\n\tat net.minecraft.client.main.Main.main(SourceFile:123) [Main.class:?]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_51]\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_51]\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_51]\n\tat java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_51]\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135) [launchwrapper-1.12.jar:?]\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.12.jar:?]\n[14:55:01] [Client thread/WARN]: EventBus 0 shutting down - future events will not be posted.\n[14:55:01] [Client thread/INFO]: Reloading ResourceManager: Default, 4621521193099187385@4@19.zip, 4638669915187065296@4@19.zip\n[14:55:01] [Client thread/WARN]: There were errors previously. Not beginning mod initialization phase\n[14:55:01] [Client thread/INFO]: [OptiFine] *** Reloading textures ***\n[14:55:01] [Client thread/INFO]: [OptiFine] Resource packs: 4621521193099187385@4@19.zip, 4638669915187065296@4@19.zip\n[14:55:01] [Thread-4/INFO]: Using sync timing. 200 frames of Display.update took 20405300 nanos\n[14:55:02] [Sound Library Loader/INFO]: Starting up SoundSystem...\n[14:55:02] [Thread-6/INFO]: Initializing LWJGL OpenAL\n[14:55:02] [Thread-6/INFO]: (The LWJGL binding of OpenAL.  For more information, see http://www.lwjgl.org)\n[14:55:02] [Thread-6/INFO]: OpenAL initialized.\n[14:55:02] [Client thread/INFO]: Narrator library for x64 successfully loaded\n[14:55:02] [Sound Library Loader/INFO]: Sound engine started\n[14:55:03] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: mods.betterfoliage.client.BetterFoliageClient\n[14:55:03] [Client thread/INFO]: [OptiFine] *** Reloading custom textures ***\n[14:55:04] [Realms Notification Availability checker #1/INFO]: Could not authorize you against Realms server: Invalid session id\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/fabric-minecraft.txt",
    "content": "[14:49:11] [main/INFO]: Loading for game Minecraft 1.17.1\n[14:49:12] [main/FATAL]: A critical error occurred\nnet.fabricmc.loader.discovery.ModResolutionException: Could not find required mod: fabric requires {minecraft @ [~1.16.2-alpha.20.28.a]}\n\tat net.fabricmc.loader.discovery.ModResolver.findCompatibleSet(ModResolver.java:198) ~[fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.discovery.ModResolver.resolve(ModResolver.java:832) ~[fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.FabricLoader.setup(FabricLoader.java:195) ~[fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.FabricLoader.load(FabricLoader.java:185) [fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.launch.knot.Knot.init(Knot.java:132) [fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:28) [fabric-loader-0.11.7.jar:?]\nCaused by: net.fabricmc.loader.util.sat4j.specs.ContradictionException: Creating Empty clause ?\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.cnf.Clauses.propagationCheck(Clauses.java:117) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.cnf.Clauses.sanityCheck(Clauses.java:97) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.MixedDataStructureDanielWL.createClause(MixedDataStructureDanielWL.java:81) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.util.sat4j.minisat.core.Solver.addClause(Solver.java:401) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.discovery.ModResolver.findCompatibleSet(ModResolver.java:195) ~[fabric-loader-0.11.7.jar:?]\n\t... 5 more\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/fabric-mod-conflict.txt",
    "content": "[18:27:35] [main/INFO]: Loading for game Minecraft 1.16.5\n[18:27:36] [ForkJoinPool-1-worker-7/WARN]: The mod \"autoconfig1u\" contains invalid entries in its mod json:\n- Unsupported root entry \"$schema\" at line 2 column 14\n[18:27:36] [ForkJoinPool-1-worker-2/WARN]: Non-Fabric mod JAR at \"/storage/emulated/0/games/PojavLauncher/.minecraft/mods/stoneholm-1.16.52-1.2.2.jar\", ignoring\n[18:27:36] [ForkJoinPool-1-worker-4/WARN]: Non-Fabric mod JAR at \"/storage/emulated/0/games/PojavLauncher/.minecraft/mods/OfflineSkins_1.16.5_v1a.jar\", ignoring\n[18:27:36] [ForkJoinPool-1-worker-6/WARN]: The mod \"autoconfig1u\" contains invalid entries in its mod json:\n- Unsupported root entry \"$schema\" at line 2 column 14\n[18:27:36] [ForkJoinPool-1-worker-2/WARN]: The mod \"autoconfig1u\" contains invalid entries in its mod json:\n- Unsupported root entry \"$schema\" at line 2 column 14\n[18:27:36] [ForkJoinPool-1-worker-7/WARN]: The mod \"autoconfig1u\" contains invalid entries in its mod json:\n- Unsupported root entry \"$schema\" at line 2 column 14\n[18:27:36] [ForkJoinPool-1-worker-6/WARN]: The mod \"autoconfig1u\" contains invalid entries in its mod json:\n- Unsupported root entry \"$schema\" at line 2 column 14\n[18:27:37] [main/FATAL]: A critical error occurred\nnet.fabricmc.loader.discovery.ModResolutionException: Found conflicting mods: phosphor conflicts with {starlight @ [*]}\n\tat net.fabricmc.loader.discovery.ModResolver.findCompatibleSet(ModResolver.java:219) ~[fabric-loader-0.11.6.jar:?]\n\tat net.fabricmc.loader.discovery.ModResolver.resolve(ModResolver.java:832) ~[fabric-loader-0.11.6.jar:?]\n\tat net.fabricmc.loader.FabricLoader.setup(FabricLoader.java:195) ~[fabric-loader-0.11.6.jar:?]\n\tat net.fabricmc.loader.FabricLoader.load(FabricLoader.java:185) [fabric-loader-0.11.6.jar:?]\n\tat net.fabricmc.loader.launch.knot.Knot.init(Knot.java:132) [fabric-loader-0.11.6.jar:?]\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:28) [fabric-loader-0.11.6.jar:?]\nCaused by: net.fabricmc.loader.util.sat4j.specs.ContradictionException: Creating Empty clause ?\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.cnf.Clauses.propagationCheck(Clauses.java:117) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.cnf.Clauses.sanityCheck(Clauses.java:97) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.MixedDataStructureDanielWL.createClause(MixedDataStructureDanielWL.java:81) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.util.sat4j.minisat.core.Solver.addClause(Solver.java:401) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.discovery.ModResolver.findCompatibleSet(ModResolver.java:216) ~[fabric-loader-0.11.6.jar:?]\n\t... 5 more\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/fabric-mod-missing.txt",
    "content": "OpenJDK 64-Bit Server VM warning: Option --illegal-access is deprecated and will be removed in a future release.\n[16:18:52] [main/INFO]: Loading for game Minecraft 1.17.1\n[16:18:52] [main/INFO]: Fabric is preparing JARs on first launch, this may take a few seconds...\n[16:18:55] [ForkJoinPool-1-worker-17/WARN]: The mod \"autoconfig1u\" contains invalid entries in its mod json:\n- Unsupported root entry \"$schema\" at line 2 column 14\n[16:18:55] [main/FATAL]: A critical error occurred\nnet.fabricmc.loader.discovery.ModResolutionException: Could not find required mod: pca requires {fabric @ [>=0.39.2]}\n\tat net.fabricmc.loader.discovery.ModResolver.findCompatibleSet(ModResolver.java:198) ~[fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.discovery.ModResolver.resolve(ModResolver.java:832) ~[fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.FabricLoader.setup(FabricLoader.java:195) ~[fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.FabricLoader.load(FabricLoader.java:185) [fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.launch.knot.Knot.init(Knot.java:132) [fabric-loader-0.11.7.jar:?]\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:28) [fabric-loader-0.11.7.jar:?]\nCaused by: net.fabricmc.loader.util.sat4j.specs.ContradictionException: Creating Empty clause ?\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.cnf.Clauses.propagationCheck(Clauses.java:117) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.cnf.Clauses.sanityCheck(Clauses.java:97) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.MixedDataStructureDanielWL.createClause(MixedDataStructureDanielWL.java:81) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.util.sat4j.minisat.core.Solver.addClause(Solver.java:401) ~[fabric-loader-sat4j-2.3.5.4.jar:?]\n\tat net.fabricmc.loader.discovery.ModResolver.findCompatibleSet(ModResolver.java:195) ~[fabric-loader-0.11.7.jar:?]\n\t... 5 more\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/fabric-version-0.12.txt",
    "content": "java.lang.RuntimeException: Could not execute entrypoint stage 'preLaunch' due to errors, provided by 'advanced_runtime_resource_pack'!\n\tat net.fabricmc.loader.impl.entrypoint.EntrypointUtils.lambda$invoke0$0(EntrypointUtils.java:51)\n\tat net.fabricmc.loader.impl.util.ExceptionUtil.gatherExceptions(ExceptionUtil.java:33)\n\tat net.fabricmc.loader.impl.entrypoint.EntrypointUtils.invoke0(EntrypointUtils.java:49)\n\tat net.fabricmc.loader.impl.entrypoint.EntrypointUtils.invoke(EntrypointUtils.java:35)\n\tat net.fabricmc.loader.impl.launch.knot.Knot.init(Knot.java:134)\n\tat net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:68)\n\tat net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23)\nCaused by: net.fabricmc.loader.api.EntrypointException: Exception while loading entries for entrypoint 'preLaunch' provided by 'advanced_runtime_resource_pack'\n\tat net.fabricmc.loader.impl.entrypoint.EntrypointContainerImpl.getEntrypoint(EntrypointContainerImpl.java:56)\n\tat net.fabricmc.loader.impl.entrypoint.EntrypointUtils.invoke0(EntrypointUtils.java:47)\n\t... 4 more\nCaused by: java.lang.RuntimeException: Mixin transformation of net.devtech.arrp.ARRP failed\n\tat net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:224)\n\tat net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:133)\n\tat net.fabricmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:155)\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)\n\tat java.base/java.lang.Class.forName0(Native Method)\n\tat java.base/java.lang.Class.forName(Class.java:466)\n\tat net.fabricmc.loader.impl.util.DefaultLanguageAdapter.create(DefaultLanguageAdapter.java:50)\n\tat net.fabricmc.loader.impl.entrypoint.EntrypointStorage$NewEntry.getOrCreate(EntrypointStorage.java:117)\n\tat net.fabricmc.loader.impl.entrypoint.EntrypointContainerImpl.getEntrypoint(EntrypointContainerImpl.java:53)\n\t... 5 more\nCaused by: java.lang.NoClassDefFoundError: org/spongepowered/asm/mixin/transformer/FabricMixinTransformerProxy\n\tat java.base/java.lang.ClassLoader.defineClass1(Native Method)\n\tat java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1010)\n\tat java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)\n\tat net.fabricmc.loader.impl.launch.knot.KnotClassLoader.defineClassFwd(KnotClassLoader.java:218)\n\tat net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:149)\n\tat net.fabricmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:155)\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)\n\tat meteordevelopment.meteorclient.MixinPlugin.onLoad(MixinPlugin.java:59)\n\tat org.spongepowered.asm.mixin.transformer.PluginHandle.onLoad(PluginHandle.java:119)\n\tat org.spongepowered.asm.mixin.transformer.MixinConfig.onSelect(MixinConfig.java:709)\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.selectConfigs(MixinProcessor.java:498)\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.select(MixinProcessor.java:460)\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.checkSelect(MixinProcessor.java:438)\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:290)\n\tat org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234)\n\tat org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202)\n\tat net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:222)\n\t... 13 more\nCaused by: java.lang.ClassNotFoundException: org.spongepowered.asm.mixin.transformer.FabricMixinTransformerProxy\n\tat java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:636)\n\tat java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182)\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)\n\tat net.fabricmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:158)\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)\n\t... 30 more"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/fabric_warnings.txt",
    "content": "[14:12:53] [main/INFO]: Loading for game Minecraft 1.17.1\n[14:12:53] [main/WARN]: Warnings were found!\n - Conflicting versions found for fabric-api-base: used 0.3.0+a02b446313, also found 0.3.0+a02b44633d, 0.3.0+a02b446318\n - Conflicting versions found for fabric-rendering-data-attachment-v1: used 0.1.5+a02b446313, also found 0.1.5+a02b446318\n - Conflicting versions found for fabric-rendering-fluids-v1: used 0.1.13+a02b446318, also found 0.1.13+a02b446313\n - Conflicting versions found for fabric-lifecycle-events-v1: used 1.4.4+a02b44633d, also found 1.4.4+a02b446318\n - Mod 'Sodium Extra' (sodium-extra) recommends any version of mod reeses-sodium-options, which is missing!\n\t - You must install any version of reeses-sodium-options.\n - Conflicting versions found for fabric-screen-api-v1: used 1.0.4+155f865c18, also found 1.0.4+198a96213d\n - Conflicting versions found for fabric-key-binding-api-v1: used 1.0.4+a02b446318, also found 1.0.4+a02b44633d\n[14:12:53] [main/INFO]: [FabricLoader] Loading 58 mods:\n\t- appleskin@mc1.17-2.1.3\n\t- bettersodiumvideosettingsbutton@2.0.1\n\t- cloth-basic-math@0.5.1\n\t- cloth-config2@5.0.34\n\t- fabric@0.39.1+1.17\n\t- fabric-api-base@0.3.0+a02b446313\n\t- fabric-api-lookup-api-v1@1.3.0+2f75c6ce18\n\t- fabric-biome-api-v1@3.2.0+b06cb95b18\n\t- fabric-blockrenderlayer-v1@1.1.5+a02b446318\n\t- fabric-command-api-v1@1.1.3+5ab9934c18\n\t- fabric-commands-v0@0.2.2+92519afa18\n\t- fabric-containers-v0@0.1.12+a02b446318\n\t- fabric-content-registries-v0@0.3.0+211ddf9518\n\t- fabric-crash-report-info-v1@0.1.5+be9da31018\n\t- fabric-dimensions-v1@2.0.11+6cefd57718\n\t- fabric-entity-events-v1@1.2.1+077fc48418\n\t- fabric-events-interaction-v0@0.4.9+a722d8c018\n\t- fabric-events-lifecycle-v0@0.2.1+92519afa18\n\t- fabric-game-rule-api-v1@1.0.7+6cefd57718\n\t- fabric-item-api-v1@1.2.4+a02b446318\n\t- fabric-item-groups-v0@0.2.10+b7ab612118\n\t- fabric-key-binding-api-v1@1.0.4+a02b446318\n\t- fabric-keybindings-v0@0.2.2+36b77c3e18\n\t- fabric-lifecycle-events-v1@1.4.4+a02b44633d\n\t- fabric-loot-tables-v1@1.0.4+a02b446318\n\t- fabric-mining-levels-v0@0.1.3+92519afa18\n\t- fabric-models-v0@0.3.0+a02b446318\n\t- fabric-networking-api-v1@1.0.13+2e8bd82f18\n\t- fabric-networking-blockentity-v0@0.2.11+a02b446318\n\t- fabric-networking-v0@0.3.2+92519afa18\n\t- fabric-object-builder-api-v1@1.10.9+b7ab612118\n\t- fabric-object-builders-v0@0.7.3+a02b446318\n\t- fabric-particles-v1@0.2.4+a02b446318\n\t- fabric-registry-sync-v0@0.7.10+e2961fee18\n\t- fabric-renderer-api-v1@0.4.4+5f02c96918\n\t- fabric-renderer-indigo@0.4.8+a02b446318\n\t- fabric-renderer-registries-v1@3.2.1+b06cb95b18\n\t- fabric-rendering-data-attachment-v1@0.1.5+a02b446313\n\t- fabric-rendering-fluids-v1@0.1.13+a02b446318\n\t- fabric-rendering-v0@1.1.2+92519afa18\n\t- fabric-rendering-v1@1.8.0+b06cb95b18\n\t- fabric-resource-loader-v0@0.4.8+a00e834b18\n\t- fabric-screen-api-v1@1.0.4+155f865c18\n\t- fabric-screen-handler-api-v1@1.1.8+a02b446318\n\t- fabric-structure-api-v1@1.1.13+5ab9934c18\n\t- fabric-tag-extensions-v0@1.2.1+b06cb95b18\n\t- fabric-textures-v0@1.0.6+a02b446318\n\t- fabric-tool-attribute-api-v1@1.2.12+b7ab612118\n\t- fabric-transfer-api-v1@1.1.2+96bf6a7e18\n\t- fabricloader@0.11.6\n\t- hydrogen@0.3\n\t- iris@1.1.1\n\t- java@16\n\t- lithium@0.7.4\n\t- minecraft@1.17.1\n\t- modmenu@2.0.7\n\t- sodium@0.3.2+build.7\n\t- sodium-extra@0.3.4\n[14:12:53] [main/WARN]: Mod `appleskin` (mc1.17-2.1.3) does not respect SemVer - comparison support is limited.\n[14:12:53] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.2 Source=file:/C:/Users/Administrator/%e6%88%91%e7%9a%84%e4%b8%96%e7%95%8c/HMCL/.minecraft/libraries/net/fabricmc/sponge-mixin/0.9.4+mixin.0.8.2/sponge-mixin-0.9.4+mixin.0.8.2.jar Service=Knot/Fabric Env=CLIENT\n[14:12:53] [main/INFO]: Compatibility level set to JAVA_16\n[14:12:54] [main/INFO]: Loaded configuration file for Sodium: 28 options available, 0 override(s) found\n[14:12:54] [main/INFO]: Loaded configuration file for Lithium: 86 options available, 0 override(s) found\n[14:12:54] [main/WARN]: Error loading class: me/flashyreese/mods/reeses_sodium_options/client/gui/SodiumVideoOptionsScreen (java.lang.ClassNotFoundException: me/flashyreese/mods/reeses_sodium_options/client/gui/SodiumVideoOptionsScreen)\n[14:12:54] [main/WARN]: @Mixin target net/minecraft/class_2474$class_5124 is public in fabric-tag-extensions-v0.mixins.json:MixinObjectBuilder and should be specified in value\n[14:12:54] [main/WARN]: Error loading class: me/shedaniel/rei/impl/client/gui/fabric/ScreenOverlayImplImpl (java.lang.ClassNotFoundException: me/shedaniel/rei/impl/client/gui/fabric/ScreenOverlayImplImpl)\n[14:12:54] [main/WARN]: @Mixin target me.shedaniel.rei.impl.client.gui.fabric.ScreenOverlayImplImpl was not found appleskin.mixins.json:REITooltipMixin\n[14:12:54] [main/WARN]: @Mixin target net/minecraft/class_3218$class_5526 is public in fabric-lifecycle-events-v1.mixins.json:ServerWorldEntityLoaderMixin and should be specified in value\n[14:12:54] [main/WARN]: @Mixin target net/minecraft/class_3898$class_3208 is public in fabric-networking-api-v1.mixins.json:accessor.EntityTrackerAccessor and should be specified in value\n[14:12:54] [main/INFO]: Injecting class 'com.google.common.collect.HydrogenImmutableMapEntry' (url: jar:file:/C:/Users/Administrator/我的世界/HMCL/.minecraft/versions/1.17.1Sodium-Iris/mods/hydrogen-fabric-mc1.17.1-0.3.jar!/com/google/common/collect/HydrogenImmutableMapEntry.class)\n[14:12:54] [main/INFO]: Injecting class 'com.google.common.collect.HydrogenEntrySetIterator' (url: jar:file:/C:/Users/Administrator/我的世界/HMCL/.minecraft/versions/1.17.1Sodium-Iris/mods/hydrogen-fabric-mc1.17.1-0.3.jar!/com/google/common/collect/HydrogenEntrySetIterator.class)\n[14:12:54] [main/INFO]: Injecting class 'com.google.common.collect.HydrogenEntrySet' (url: jar:file:/C:/Users/Administrator/我的世界/HMCL/.minecraft/versions/1.17.1Sodium-Iris/mods/hydrogen-fabric-mc1.17.1-0.3.jar!/com/google/common/collect/HydrogenEntrySet.class)\n[14:12:54] [main/INFO]: Injecting class 'com.google.common.collect.HydrogenImmutableReferenceHashMap' (url: jar:file:/C:/Users/Administrator/我的世界/HMCL/.minecraft/versions/1.17.1Sodium-Iris/mods/hydrogen-fabric-mc1.17.1-0.3.jar!/com/google/common/collect/HydrogenImmutableReferenceHashMap.class)\n[14:12:54] [main/INFO]: Trying to switch memory allocators to work around memory leaks present with Jemalloc 5.0.0 through 5.2.0 on Windows\n[14:12:58] [Render thread/WARN]: Method overwrite conflict for method_22920 in mixins.iris.vertexformat.json:block_rendering.MixinBufferBuilder_SeparateAo, previously written by me.jellysquid.mods.sodium.mixin.features.buffer_builder.intrinsics.MixinBufferBuilder. Skipping method.\n[14:12:59] [Render thread/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD'\n[14:13:00] [Render thread/INFO]: Setting user: LingXianKe\n[14:13:00] [Render thread/WARN]: @Inject(@At(\"INVOKE\")) Shift.BY=3 on fabric-lifecycle-events-v1.mixins.json:client.WorldChunkMixin::handler$zpa000$onLoadBlockEntity exceeds the maximum allowed value: 0. Increase the value of maxShiftBy to suppress this warning.\n[14:13:01] [Render thread/WARN]: WARNING! Mod cloth-basic-math is only using deprecated 'modmenu:api' custom value! This will be removed in 1.18 snapshots, so ask the author of this mod to support the new API.\n[14:13:01] [Render thread/INFO]: [Indigo] Different rendering plugin detected; not applying Indigo.\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/fabric_warnings2.txt",
    "content": "[authlib-injector] [INFO] Logging file: C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\versions\\1.17.1 PxUni\\authlib-injector.log\n[authlib-injector] [INFO] Version: 1.2.1\n[authlib-injector] [INFO] Authentication server: http://localhost:52773\n[authlib-injector] [WARNING] You are using HTTP protocol, which is INSECURE! Please switch to HTTPS if possible.\n2023-02-03 07:43:03,385 main WARN JNDI lookup class is not available because this JRE does not support JNDI. JNDI string lookups will not be available, continuing configuration. Ignoring java.lang.ClassCastException: class org.apache.logging.log4j.core.lookup.JndiLookup\n[07:43:03] [main/INFO]: Loading Minecraft 1.17.1 with Fabric Loader 0.14.12\n[07:43:03] [main/WARN]: Can't remove Log4J2 JNDI substitution Lookup: java.lang.RuntimeException: couldn't find JNDI lookup entry\n[07:43:04] [ForkJoinPool-1-worker-3/WARN]: Mod com_github_wearblackallday_javautils uses the version 1b369d41cd which isn't compatible with Loader's extended semantic version format (Could not parse version number component '1b369d41cd'!), SemVer is recommended for reliably evaluating dependencies and prioritizing newer version\n[07:43:04] [main/WARN]: Mod resolution failed\n[07:43:04] [main/INFO]: Immediate reason: [HARD_DEP_NO_CANDIDATE roughlysearchable 2.2.1+1.17.1 {depends roughlyenoughitems @ [>=6.0.2]}, ROOT_FORCELOAD_SINGLE roughlysearchable 2.2.1+1.17.1]\n[07:43:04] [main/INFO]: Reason: [HARD_DEP roughlysearchable 2.2.1+1.17.1 {depends roughlyenoughitems @ [>=6.0.2]}]\n[07:43:04] [main/INFO]: Fix: add [add:roughlyenoughitems 6.0.2 ([[6.0.2,?)])], remove [], replace []\n[07:43:04] [main/ERROR]: Incompatible mod set!\nnet.fabricmc.loader.impl.FormattedException: Mod resolution encountered an incompatible mod set!\nA potential solution has been determined:\n\t - Install roughlyenoughitems, version 6.0.2 or later.\nUnmet dependency listing:\n\t - Mod 'Roughly Searchable' (roughlysearchable) 2.2.1+1.17.1 requires version 6.0.2 or later of roughlyenoughitems, which is missing!\n\tat net.fabricmc.loader.impl.FabricLoaderImpl.load(FabricLoaderImpl.java:190) ~[fabric-loader-0.14.12.jar:?]\n\tat net.fabricmc.loader.impl.launch.knot.Knot.init(Knot.java:148) ~[fabric-loader-0.14.12.jar:?]\n\tat net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:68) [fabric-loader-0.14.12.jar:?]\n\tat net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) [fabric-loader-0.14.12.jar:?]"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/fabric_warnings3.txt",
    "content": "[01:27:16] [main/INFO]: Loading Minecraft 1.20.4 with Fabric Loader 0.15.6\n[01:27:16] [main/WARN]: Mod resolution failed\n[01:27:16] [main/INFO]: Immediate reason: [HARD_DEP_NO_CANDIDATE sodium-extra 0.5.4+mc1.20.4-build.116 {depends fabric-api @ [*]}, ROOT_FORCELOAD_SINGLE sodium-extra 0.5.4+mc1.20.4-build.116]\n[01:27:16] [main/INFO]: Reason: [HARD_DEP sodium-extra 0.5.4+mc1.20.4-build.116 {depends fabric-api @ [*]}, HARD_DEP sodium-extra 0.5.4+mc1.20.4-build.116 {depends sodium @ [>=0.5.6]}]\n[01:27:16] [main/INFO]: Fix: add [add:fabric-api 1 ([(-∞,∞)]), add:sodium 0.5.6 ([[0.5.6,∞)])], remove [], replace []\n[01:27:17] [main/ERROR]: Incompatible mods found!\nnet.fabricmc.loader.impl.FormattedException: Some of your mods are incompatible with the game or each other!\n确定了一种可能的解决方法，这样做可能会解决你的问题：\n\t - 安装 fabric-api，任意版本。\n\t - 安装 sodium，0.5.6 及以上版本。\n更多信息：\n\t - 模组 'Sodium Extra' (sodium-extra) 0.5.4+mc1.20.4-build.116 需要 fabric-api 的 任意版本，但没有安装它！\n\t - 模组 'Sodium Extra' (sodium-extra) 0.5.4+mc1.20.4-build.116 需要 sodium 的 0.5.6 及以上版本，但没有安装它！\n\tat net.fabricmc.loader.impl.FormattedException.ofLocalized(FormattedException.java:51) ~[fabric-loader-0.15.6.jar:?]\n\tat net.fabricmc.loader.impl.FabricLoaderImpl.load(FabricLoaderImpl.java:195) ~[fabric-loader-0.15.6.jar:?]\n\tat net.fabricmc.loader.impl.launch.knot.Knot.init(Knot.java:146) ~[fabric-loader-0.15.6.jar:?]\n\tat net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:68) ~[fabric-loader-0.15.6.jar:?]\n\tat net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) ~[fabric-loader-0.15.6.jar:?]\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/failed_to_load_a_library.txt",
    "content": "[authlib-injector] [INFO] Logging file: /home/zkitefly/Desktop/新建文件夹1/.minecraft/authlib-injector.log\n[authlib-injector] [INFO] Version: 1.2.3\n[authlib-injector] [INFO] Authentication server: http://localhost:37549\n[authlib-injector] [WARNING] You are using HTTP protocol, which is INSECURE! Please switch to HTTPS if possible.\n[22:35:10] [main/INFO]: Loading tweak class name optifine.OptiFineTweaker\n[22:35:10] [main/INFO]: Using primary tweak class name optifine.OptiFineTweaker\n[22:35:10] [main/INFO]: Calling tweak class optifine.OptiFineTweaker\n[22:35:10] [main/INFO]: [OptiFine] OptiFineTweaker: acceptOptions\n[22:35:10] [main/INFO]: [OptiFine] OptiFineTweaker: injectIntoClassLoader\n[22:35:10] [main/INFO]: [OptiFine] OptiFine ClassTransformer\n[22:35:10] [main/INFO]: [OptiFine] OptiFine ZIP file: /home/zkitefly/Desktop/新建文件夹1/.minecraft/libraries/optifine/OptiFine/1.20.1_HD_U_I5/OptiFine-1.20.1_HD_U_I5.jar\n[22:35:10] [main/INFO]: [OptiFine] OptiFineTweaker: getLaunchArguments\n[22:35:10] [main/INFO]: [OptiFine] OptiFineTweaker: getLaunchTarget\n[authlib-injector] [INFO] Transformed [net.minecraft.client.main.Main] with [Main Arguments Transformer]\n[22:35:10] [main/INFO]: Launching wrapped minecraft {net.minecraft.client.main.Main}\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.properties.Property] with [Yggdrasil Public Key Transformer]\n[authlib-injector] [INFO] Httpd is running on port 41575\n[authlib-injector] [INFO] Transformed [fiv] with [Constant URL Transformer]\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.HttpAuthenticationService] with [ConcatenateURL Workaround]\n[22:35:11] [main/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.eventbus.api.Event$Result\n[22:35:11] [main/INFO]: [OptiFine] (Reflector) Method not present: net.minecraftforge.common.extensions.IForgeEntity.canUpdate\n[22:35:11] [main/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.logging.CrashReportExtender\n[22:35:12] [main/INFO]: [OptiFine] (Reflector) Method not present: ane.create\n[22:35:15] [Datafixer Bootstrap/INFO]: 188 Datafixer optimizations took 360 milliseconds\n[22:35:17] [Render thread/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.client.ForgeHooksClient\n[22:35:17] [Render thread/INFO]: [STDERR]: [LWJGL] Platform/architecture mismatch detected for module: org.lwjgl\n\t\tLinux amd64 17.0.6\n\t\tOpenJDK 64-Bit Server VM v17.0.6+10-LTS by BellSoft\n\tPlatform available on classpath:\n\t\tlinux/x64\n[22:35:17] [Render thread/INFO]: [STDERR]: \tJVM platform:\n[22:35:17] [Render thread/INFO]: [STDERR]: [LWJGL] Failed to load a library. Possible solutions:\n\ta) Add the directory that contains the shared library to -Djava.library.path or -Dorg.lwjgl.librarypath.\n\tb) Add the JAR that contains the shared library to the classpath.\n[22:35:17] [Render thread/INFO]: [STDERR]: [LWJGL] Enable debug mode with -Dorg.lwjgl.util.Debug=true for better diagnostics.\n[22:35:17] [Render thread/INFO]: [STDERR]: [LWJGL] Enable the SharedLibraryLoader debug mode with -Dorg.lwjgl.util.DebugLoader=true for better diagnostics.\n[22:35:17] [Render thread/ERROR]: Unable to launch\njava.lang.reflect.InvocationTargetException: null\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]\n\tat java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:159) ~[launchwrapper-of-2.3.jar:2.3]\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:30) ~[launchwrapper-of-2.3.jar:2.3]\nCaused by: java.lang.NoClassDefFoundError: Could not initialize class com.mojang.blaze3d.systems.RenderSystem\n\tat ab.a(SourceFile:66) ~[ab.class:?]\n\tat enn.a(SourceFile:2424) ~[enn.class:?]\n\tat enn.a(SourceFile:2419) ~[enn.class:?]\n\tat net.minecraft.client.main.Main.main(SourceFile:220) ~[Main.class:?]\n\t... 6 more\n[HMCL ProcessListener] Minecraft exit with code 1.\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/forge_found_duplicate_mods.txt",
    "content": "[10:41:20] [main/INFO]: ModLauncher running: args [--username, pretentiou, --version, 1.20.1, --gameDir, C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft, --assetsDir, C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\assets, --assetIndex, 5, --uuid, 202b1899ef5247368d9a289c5d2ec63f, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, msa, --versionType, HMCL 3.5.5.235, --width, 854, --height, 480, --launchTarget, forgeclient, --fml.forgeVersion, 47.2.16, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412]\n[10:41:20] [main/INFO]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.8 by Microsoft; OS Windows 10 arch amd64 version 10.0\n[10:41:20] [main/INFO]: Loading ImmediateWindowProvider fmlearlywindow\n[10:41:20] [main/INFO]: Trying GL version 4.6\n[10:41:20] [main/INFO]: Requested GL version 4.6 got version 4.6\n[10:41:21] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/C:/Users/姚资柱/Desktop/新建文件夹/game/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%23100!/ Service=ModLauncher Env=CLIENT\n[10:41:21] [pool-2-thread-1/INFO]: GL info: AMD Radeon(TM) Graphics GL version 4.6.13596 Core Profile Forward-Compatible Context 20.10.28.10 27.20.11028.10001, ATI Technologies Inc.\n[10:41:21] [main/INFO]: Found mod file architectury-9.1.13-forge.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file cloth-config-11.1.118-forge.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file create-1.20.1-0.5.1.f.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file ftb-library-forge-2001.1.5.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file ftb-ultimine-forge-2001.1.4.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file Jade-1.20.1-forge-11.7.1.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file jei-1.20.1-forge-15.2.0.27.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file REIPluginCompatibilities-forge-12.0.93.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file RoughlyEnoughItems-12.0.684-forge.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file Xaeros_Minimap_23.9.7_Forge_1.20.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/INFO]: Found mod file XaerosWorldMap_1.37.7_Forge_1.20.jar of type MOD with provider {mods folder locator at C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\mods}\n[10:41:21] [main/WARN]: Mod file C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\libraries\\net\\minecraftforge\\fmlcore\\1.20.1-47.2.16\\fmlcore-1.20.1-47.2.16.jar is missing mods.toml file\n[10:41:21] [main/WARN]: Mod file C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\libraries\\net\\minecraftforge\\javafmllanguage\\1.20.1-47.2.16\\javafmllanguage-1.20.1-47.2.16.jar is missing mods.toml file\n[10:41:21] [main/WARN]: Mod file C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\libraries\\net\\minecraftforge\\lowcodelanguage\\1.20.1-47.2.16\\lowcodelanguage-1.20.1-47.2.16.jar is missing mods.toml file\n[10:41:21] [main/WARN]: Mod file C:\\Users\\姚资柱\\Desktop\\新建文件夹\\game\\.minecraft\\libraries\\net\\minecraftforge\\mclanguage\\1.20.1-47.2.16\\mclanguage-1.20.1-47.2.16.jar is missing mods.toml file\n[10:41:21] [main/INFO]: Found mod file fmlcore-1.20.1-47.2.16.jar of type LIBRARY with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d\n[10:41:21] [main/INFO]: Found mod file javafmllanguage-1.20.1-47.2.16.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d\n[10:41:21] [main/INFO]: Found mod file lowcodelanguage-1.20.1-47.2.16.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d\n[10:41:21] [main/INFO]: Found mod file mclanguage-1.20.1-47.2.16.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d\n[10:41:21] [main/INFO]: Found mod file client-1.20.1-20230612.114412-srg.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d\n[10:41:21] [main/INFO]: Found mod file forge-1.20.1-47.2.16-universal.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d\n[10:41:21] [main/ERROR]: Found duplicate mods:\n\tMod ID: 'jei' from mod files: REIPluginCompatibilities-forge-12.0.93.jar, jei-1.20.1-forge-15.2.0.27.jar\n[10:41:21] [main/ERROR]: Failed to build unique mod list after mod discovery.\nnet.minecraftforge.fml.loading.EarlyLoadingException: Duplicate mods found\n\tat net.minecraftforge.fml.loading.UniqueModListBuilder.buildUniqueList(UniqueModListBuilder.java:87) ~[fmlloader-1.20.1-47.2.16.jar:1.0]\n\tat net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer.discoverMods(ModDiscoverer.java:106) ~[fmlloader-1.20.1-47.2.16.jar:?]\n\tat net.minecraftforge.fml.loading.FMLLoader.beginModScan(FMLLoader.java:164) ~[fmlloader-1.20.1-47.2.16.jar:1.0]\n\tat net.minecraftforge.fml.loading.FMLServiceProvider.beginScanning(FMLServiceProvider.java:86) ~[fmlloader-1.20.1-47.2.16.jar:1.0]\n\tat cpw.mods.modlauncher.TransformationServiceDecorator.runScan(TransformationServiceDecorator.java:112) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.lambda$runScanningTransformationServices$8(TransformationServicesHandler.java:100) ~[modlauncher-10.0.9.jar:?]\n\tat java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[?:?]\n\tat java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779) ~[?:?]\n\tat java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[?:?]\n\tat java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[?:?]\n\tat java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) ~[?:?]\n\tat java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) ~[?:?]\n\tat java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) ~[?:?]\n\tat java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) ~[?:?]\n\tat java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) ~[?:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.runScanningTransformationServices(TransformationServicesHandler.java:102) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:55) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:88) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:78) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.9.jar:?]\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?]\n[10:41:21] [main/ERROR]: Mod Discovery failed. Skipping dependency discovery.\nException in thread \"main\" java.lang.IllegalStateException: Failed to find system mod: minecraft\n\tat MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.ModSorter.detectSystemMods(ModSorter.java:181)\n\tat MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.ModSorter.buildUniqueList(ModSorter.java:145)\n\tat MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.ModSorter.sort(ModSorter.java:53)\n\tat MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.moddiscovery.ModValidator.stage2Validation(ModValidator.java:98)\n\tat MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.FMLLoader.completeScan(FMLLoader.java:172)\n\tat MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.FMLServiceProvider.completeScan(FMLServiceProvider.java:91)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.TransformationServiceDecorator.onCompleteScan(TransformationServiceDecorator.java:174)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.TransformationServicesHandler.lambda$triggerScanCompletion$24(TransformationServicesHandler.java:145)\n\tat java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)\n\tat java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779)\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)\n\tat java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)\n\tat java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.TransformationServicesHandler.triggerScanCompletion(TransformationServicesHandler.java:147)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.run(Launcher.java:95)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.main(Launcher.java:78)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141)"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/forge_repeat_installation.txt",
    "content": "2022-11-15 09:59:44,353 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND\n2022-11-15 09:59:44,369 main WARN JNDI lookup class is not available because this JRE does not support JNDI. JNDI string lookups will not be available, continuing configuration. Ignoring java.lang.ClassCastException: class org.apache.logging.log4j.core.lookup.JndiLookup\n[09:59:44] [main/INFO]: ModLauncher running: args [--username, 98878/, --version, 1.16.5-Forge_36.2.20-OptiFine_G8, --gameDir, C:\\Users\\Administrator\\Desktop\\自然之旅Nature's Journey-1.0\\.minecraft, --assetsDir, C:\\Users\\Administrator\\Desktop\\自然之旅Nature's Journey-1.0\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 84091fa67dc23d5cb3a4562696d239a4, --accessToken, ????????, --userType, legacy, --versionType, HMCL 3.5.3.227, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.20, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550, --username, 98878/, --version, 1.16.5-Forge_36.2.20-OptiFine_G8, --gameDir, C:\\Users\\Administrator\\Desktop\\自然之旅Nature's Journey-1.0\\.minecraft, --assetsDir, C:\\Users\\Administrator\\Desktop\\自然之旅Nature's Journey-1.0\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 84091fa67dc23d5cb3a4562696d239a4, --accessToken, ????????, --userType, legacy, --versionType, HMCL 3.5.3.227, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.39, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550]\n[09:59:44] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 17.0.3 by Microsoft\nException in thread \"main\" joptsimple.MultipleArgumentsForOptionException: Found multiple arguments for option gameDir, but you asked for only one\n\tat joptsimple.OptionSet.valueOf(OptionSet.java:179)\n\tat cpw.mods.modlauncher.ArgumentHandler.setArgs(ArgumentHandler.java:47)\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:74)\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/forge_repeat_installation2.txt",
    "content": "Command: \"C:\\\\Program Files\\\\Microsoft\\\\jdk-17.0.8.7-hotspot\\\\bin\\\\java.exe\" -Xmx7289m -Dfile.encoding=GB18030 -Dsun.stdout.encoding=GB18030 -Dsun.stderr.encoding=GB18030 -Djava.rmi.server.useCodebaseOnly=true -Dcom.sun.jndi.rmi.object.trustURLCodebase=false -Dcom.sun.jndi.cosnaming.object.trustURLCodebase=false -Dlog4j2.formatMsgNoLookups=true -Dlog4j.configurationFile=C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\versions\\1.20.1-forge-47.1.44\\log4j2.xml -Dminecraft.client.jar=C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\versions\\1.20.1\\1.20.1.jar -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Djava.library.path=C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\versions\\1.20.1-forge-47.1.44\\natives-windows-x86_64 -Djna.tmpdir=C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\versions\\1.20.1-forge-47.1.44\\natives-windows-x86_64 -Dorg.lwjgl.system.SharedLibraryExtractPath=C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\versions\\1.20.1-forge-47.1.44\\natives-windows-x86_64 -Dio.netty.native.workdir=C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\versions\\1.20.1-forge-47.1.44\\natives-windows-x86_64 -Dminecraft.launcher.brand=HMCL -Dminecraft.launcher.version=3.5.5 -cp C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\cpw\\mods\\securejarhandler\\2.1.10\\securejarhandler-2.1.10.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\ow2\\asm\\asm\\9.5\\asm-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\ow2\\asm\\asm-commons\\9.5\\asm-commons-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\ow2\\asm\\asm-tree\\9.5\\asm-tree-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\ow2\\asm\\asm-util\\9.5\\asm-util-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\ow2\\asm\\asm-analysis\\9.5\\asm-analysis-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\accesstransformers\\8.0.4\\accesstransformers-8.0.4.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\antlr\\antlr4-runtime\\4.9.1\\antlr4-runtime-4.9.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\eventbus\\6.0.5\\eventbus-6.0.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\forgespi\\7.0.1\\forgespi-7.0.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\coremods\\5.0.1\\coremods-5.0.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\cpw\\mods\\modlauncher\\10.0.9\\modlauncher-10.0.9.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\unsafe\\0.2.0\\unsafe-0.2.0.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\mergetool\\1.1.5\\mergetool-1.1.5-api.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\electronwill\\night-config\\core\\3.6.4\\core-3.6.4.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\electronwill\\night-config\\toml\\3.6.4\\toml-3.6.4.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\maven\\maven-artifact\\3.8.5\\maven-artifact-3.8.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\jodah\\typetools\\0.6.3\\typetools-0.6.3.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecrell\\terminalconsoleappender\\1.2.0\\terminalconsoleappender-1.2.0.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\jline\\jline-reader\\3.12.1\\jline-reader-3.12.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\jline\\jline-terminal\\3.12.1\\jline-terminal-3.12.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\spongepowered\\mixin\\0.8.5\\mixin-0.8.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\openjdk\\nashorn\\nashorn-core\\15.3\\nashorn-core-15.3.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\JarJarSelector\\0.3.19\\JarJarSelector-0.3.19.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\JarJarMetadata\\0.3.19\\JarJarMetadata-0.3.19.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\cpw\\mods\\bootstraplauncher\\1.1.2\\bootstraplauncher-1.1.2.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\JarJarFileSystems\\0.3.19\\JarJarFileSystems-0.3.19.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\fmlloader\\1.20.1-47.1.44\\fmlloader-1.20.1-47.1.44.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\fmlearlydisplay\\1.20.1-47.1.44\\fmlearlydisplay-1.20.1-47.1.44.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\github\\oshi\\oshi-core\\6.2.2\\oshi-core-6.2.2.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\google\\code\\gson\\gson\\2.10\\gson-2.10.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\google\\guava\\failureaccess\\1.0.1\\failureaccess-1.0.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\google\\guava\\guava\\31.1-jre\\guava-31.1-jre.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\ibm\\icu\\icu4j\\71.1\\icu4j-71.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\authlib\\4.0.43\\authlib-4.0.43.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\blocklist\\1.0.10\\blocklist-1.0.10.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\brigadier\\1.1.8\\brigadier-1.1.8.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\datafixerupper\\6.0.8\\datafixerupper-6.0.8.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\logging\\1.1.1\\logging-1.1.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\patchy\\2.2.10\\patchy-2.2.10.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\text2speech\\1.17.9\\text2speech-1.17.9.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\commons-codec\\commons-codec\\1.15\\commons-codec-1.15.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\commons-io\\commons-io\\2.11.0\\commons-io-2.11.0.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\commons-logging\\commons-logging\\1.2\\commons-logging-1.2.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\io\\netty\\netty-buffer\\4.1.82.Final\\netty-buffer-4.1.82.Final.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\io\\netty\\netty-codec\\4.1.82.Final\\netty-codec-4.1.82.Final.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\io\\netty\\netty-common\\4.1.82.Final\\netty-common-4.1.82.Final.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\io\\netty\\netty-handler\\4.1.82.Final\\netty-handler-4.1.82.Final.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\io\\netty\\netty-resolver\\4.1.82.Final\\netty-resolver-4.1.82.Final.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\io\\netty\\netty-transport-classes-epoll\\4.1.82.Final\\netty-transport-classes-epoll-4.1.82.Final.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\io\\netty\\netty-transport-native-unix-common\\4.1.82.Final\\netty-transport-native-unix-common-4.1.82.Final.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\io\\netty\\netty-transport\\4.1.82.Final\\netty-transport-4.1.82.Final.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\it\\unimi\\dsi\\fastutil\\8.5.9\\fastutil-8.5.9.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\java\\dev\\jna\\jna-platform\\5.12.1\\jna-platform-5.12.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\java\\dev\\jna\\jna\\5.12.1\\jna-5.12.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\sf\\jopt-simple\\jopt-simple\\5.0.4\\jopt-simple-5.0.4.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\commons\\commons-compress\\1.21\\commons-compress-1.21.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\commons\\commons-lang3\\3.12.0\\commons-lang3-3.12.0.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\httpcomponents\\httpclient\\4.5.13\\httpclient-4.5.13.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\httpcomponents\\httpcore\\4.4.15\\httpcore-4.4.15.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\logging\\log4j\\log4j-api\\2.19.0\\log4j-api-2.19.0.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\logging\\log4j\\log4j-core\\2.19.0\\log4j-core-2.19.0.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\logging\\log4j\\log4j-slf4j2-impl\\2.19.0\\log4j-slf4j2-impl-2.19.0.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\joml\\joml\\1.10.5\\joml-1.10.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-glfw\\3.3.1\\lwjgl-glfw-3.3.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-glfw\\3.3.1\\lwjgl-glfw-3.3.1-natives-windows.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-glfw\\3.3.1\\lwjgl-glfw-3.3.1-natives-windows-arm64.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-glfw\\3.3.1\\lwjgl-glfw-3.3.1-natives-windows-x86.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-jemalloc\\3.3.1\\lwjgl-jemalloc-3.3.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-jemalloc\\3.3.1\\lwjgl-jemalloc-3.3.1-natives-windows.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-jemalloc\\3.3.1\\lwjgl-jemalloc-3.3.1-natives-windows-arm64.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-jemalloc\\3.3.1\\lwjgl-jemalloc-3.3.1-natives-windows-x86.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-openal\\3.3.1\\lwjgl-openal-3.3.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-openal\\3.3.1\\lwjgl-openal-3.3.1-natives-windows.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-openal\\3.3.1\\lwjgl-openal-3.3.1-natives-windows-arm64.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-openal\\3.3.1\\lwjgl-openal-3.3.1-natives-windows-x86.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-opengl\\3.3.1\\lwjgl-opengl-3.3.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-opengl\\3.3.1\\lwjgl-opengl-3.3.1-natives-windows.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-opengl\\3.3.1\\lwjgl-opengl-3.3.1-natives-windows-arm64.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-opengl\\3.3.1\\lwjgl-opengl-3.3.1-natives-windows-x86.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-stb\\3.3.1\\lwjgl-stb-3.3.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-stb\\3.3.1\\lwjgl-stb-3.3.1-natives-windows.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-stb\\3.3.1\\lwjgl-stb-3.3.1-natives-windows-arm64.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-stb\\3.3.1\\lwjgl-stb-3.3.1-natives-windows-x86.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-tinyfd\\3.3.1\\lwjgl-tinyfd-3.3.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-tinyfd\\3.3.1\\lwjgl-tinyfd-3.3.1-natives-windows.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-tinyfd\\3.3.1\\lwjgl-tinyfd-3.3.1-natives-windows-arm64.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-tinyfd\\3.3.1\\lwjgl-tinyfd-3.3.1-natives-windows-x86.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl\\3.3.1\\lwjgl-3.3.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl\\3.3.1\\lwjgl-3.3.1-natives-windows.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl\\3.3.1\\lwjgl-3.3.1-natives-windows-arm64.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\lwjgl\\lwjgl\\3.3.1\\lwjgl-3.3.1-natives-windows-x86.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\org\\slf4j\\slf4j-api\\2.0.1\\slf4j-api-2.0.1.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\versions\\1.20.1\\1.20.1.jar -Djava.net.preferIPv6Addresses=system -DignoreList=bootstraplauncher,securejarhandler,asm-commons,asm-util,asm-analysis,asm-tree,asm,JarJarFileSystems,client-extra,fmlcore,javafmllanguage,lowcodelanguage,mclanguage,forge-,1.20.1-forge-47.1.44.jar,1.20.1.jar -DmergeModules=jna-5.10.0.jar,jna-platform-5.10.0.jar -DlibraryDirectory=C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries -p C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/cpw/mods/securejarhandler/2.1.10/securejarhandler-2.1.10.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm-commons/9.5/asm-commons-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm-util/9.5/asm-util-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm-analysis/9.5/asm-analysis-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm-tree/9.5/asm-tree-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm/9.5/asm-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/net/minecraftforge/JarJarFileSystems/0.3.19/JarJarFileSystems-0.3.19.jar --add-modules ALL-MODULE-PATH --add-opens java.base/java.util.jar=cpw.mods.securejarhandler --add-opens java.base/java.lang.invoke=cpw.mods.securejarhandler --add-exports java.base/sun.security.util=cpw.mods.securejarhandler --add-exports jdk.naming.dns/com.sun.jndi.dns=java.naming -Djava.net.preferIPv6Addresses=system -DignoreList=bootstraplauncher,securejarhandler,asm-commons,asm-util,asm-analysis,asm-tree,asm,JarJarFileSystems,client-extra,fmlcore,javafmllanguage,lowcodelanguage,mclanguage,forge-,1.20.1-forge-47.1.44.jar,1.20.1.jar -DmergeModules=jna-5.10.0.jar,jna-platform-5.10.0.jar -DlibraryDirectory=C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries -p C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/cpw/mods/securejarhandler/2.1.10/securejarhandler-2.1.10.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm-commons/9.5/asm-commons-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm-util/9.5/asm-util-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm-analysis/9.5/asm-analysis-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm-tree/9.5/asm-tree-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/org/ow2/asm/asm/9.5/asm-9.5.jar;C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries/net/minecraftforge/JarJarFileSystems/0.3.19/JarJarFileSystems-0.3.19.jar --add-modules ALL-MODULE-PATH --add-opens java.base/java.util.jar=cpw.mods.securejarhandler --add-opens java.base/java.lang.invoke=cpw.mods.securejarhandler --add-exports java.base/sun.security.util=cpw.mods.securejarhandler --add-exports jdk.naming.dns/com.sun.jndi.dns=java.naming cpw.mods.bootstraplauncher.BootstrapLauncher --username ghvghv --version 1.20.1-forge-47.1.44 --gameDir C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft --assetsDir C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\assets --assetIndex 5 --uuid a91ea04815cb3965a7542ea7dd652af9 --accessToken 06ae17d1831d4004a8911e3e84da39e1 --clientId ${clientid} --xuid ${auth_xuid} --userType msa --versionType \"HMCL 3.5.5\" --width 854 --height 480 --launchTarget forgeclient --fml.forgeVersion 47.1.44 --fml.mcVersion 1.20.1 --fml.forgeGroup net.minecraftforge --fml.mcpVersion 20230612.114412 --launchTarget forgeclient --fml.forgeVersion 47.1.44 --fml.mcVersion 1.20.1 --fml.forgeGroup net.minecraftforge --fml.mcpVersion 20230612.114412\n[09:54:52] [main/INFO]: ModLauncher running: args [--username, ghvghv, --version, 1.20.1-forge-47.1.44, --gameDir, C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft, --assetsDir, C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\assets, --assetIndex, 5, --uuid, a91ea04815cb3965a7542ea7dd652af9, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, msa, --versionType, HMCL 3.5.5, --width, 854, --height, 480, --launchTarget, forgeclient, --fml.forgeVersion, 47.1.44, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412, --launchTarget, forgeclient, --fml.forgeVersion, 47.1.44, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412]\n[09:54:52] [main/INFO]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.8 by Microsoft; OS Windows 11 arch amd64 version 10.0\nException in thread \"main\" joptsimple.MultipleArgumentsForOptionException: Found multiple arguments for option launchTarget, but you asked for only one\n\tat MC-BOOTSTRAP/jopt.simple@5.0.4/joptsimple.OptionSet.valueOf(OptionSet.java:179)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.ArgumentHandler.setArgs(ArgumentHandler.java:50)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.run(Launcher.java:86)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.main(Launcher.java:78)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141)\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/forgemod_resolution.txt",
    "content": "[08:45:13] [main/INFO]: ModLauncher running: args [--username, Chase, --version, 1.19.3, --gameDir, E:\\姝ｇ増MC\\.minecraft, --assetsDir, E:\\姝ｇ増MC\\.minecraft\\assets, --assetIndex, 2, --uuid, beb1f0e1a20e397e9661e2e380a8e954, --accessToken, 鉂勨潉鉂勨潉鉂勨潉鉂勨潉, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, legacy, --versionType, HMCL 3.5.4, --width, 854, --height, 480, --launchTarget, forgeclient, --fml.forgeVersion, 44.1.22, --fml.mcVersion, 1.19.3, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20221207.122022]\n[08:45:13] [main/INFO]: ModLauncher 10.0.8+10.0.8+main.0ef7e830 starting: java version 17.0.7 by Oracle Corporation; OS Windows 10 arch amd64 version 10.0\n[08:45:13] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/E:/姝ｇ増MC/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%2398!/ Service=ModLauncher Env=CLIENT\n[08:45:13] [main/INFO]: Found mod file Werewolves-1.19.3-1.1.0.0.jar of type MOD with provider {mods folder locator at E:\\姝ｇ増MC\\.minecraft\\mods}\n[08:45:14] [main/WARN]: Mod file E:\\姝ｇ増MC\\.minecraft\\libraries\\net\\minecraftforge\\fmlcore\\1.19.3-44.1.22\\fmlcore-1.19.3-44.1.22.jar is missing mods.toml file\n[08:45:14] [main/WARN]: Mod file E:\\姝ｇ増MC\\.minecraft\\libraries\\net\\minecraftforge\\javafmllanguage\\1.19.3-44.1.22\\javafmllanguage-1.19.3-44.1.22.jar is missing mods.toml file\n[08:45:14] [main/WARN]: Mod file E:\\姝ｇ増MC\\.minecraft\\libraries\\net\\minecraftforge\\lowcodelanguage\\1.19.3-44.1.22\\lowcodelanguage-1.19.3-44.1.22.jar is missing mods.toml file\n[08:45:14] [main/WARN]: Mod file E:\\姝ｇ増MC\\.minecraft\\libraries\\net\\minecraftforge\\mclanguage\\1.19.3-44.1.22\\mclanguage-1.19.3-44.1.22.jar is missing mods.toml file\n[08:45:14] [main/INFO]: Found mod file fmlcore-1.19.3-44.1.22.jar of type LIBRARY with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@2d35442b\n[08:45:14] [main/INFO]: Found mod file javafmllanguage-1.19.3-44.1.22.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@2d35442b\n[08:45:14] [main/INFO]: Found mod file lowcodelanguage-1.19.3-44.1.22.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@2d35442b\n[08:45:14] [main/INFO]: Found mod file mclanguage-1.19.3-44.1.22.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@2d35442b\n[08:45:14] [main/INFO]: Found mod file client-1.19.3-20221207.122022-srg.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@2d35442b\n[08:45:14] [main/INFO]: Found mod file forge-1.19.3-44.1.22-universal.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@2d35442b\n[08:45:14] [main/INFO]: No dependencies to load found. Skipping!\n[08:45:14] [main/ERROR]: Missing or unsupported mandatory dependencies:\n\tMod ID: 'vampirism', Requested by: 'werewolves', Expected range: '[1.9.0-beta.1,)', Actual version: '[MISSING]'\n[08:45:17] [main/INFO]: Compatibility level set to JAVA_17\n[08:45:17] [main/INFO]: Launching target 'forgeclient' with arguments [--version, 1.19.3, --gameDir, E:\\姝ｇ増MC\\.minecraft, --assetsDir, E:\\姝ｇ増MC\\.minecraft\\assets, --uuid, beb1f0e1a20e397e9661e2e380a8e954, --username, Chase, --assetIndex, 2, --accessToken, 鉂勨潉鉂勨潉鉂勨潉鉂勨潉, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, legacy, --versionType, HMCL 3.5.4, --width, 854, --height, 480]\n[08:45:21] [pool-3-thread-1/WARN]: Error loading class: de/teamlapen/vampirism/entity/player/FactionBasePlayer (java.lang.ClassNotFoundException: de.teamlapen.vampirism.entity.player.FactionBasePlayer)\n[08:45:21] [pool-3-thread-1/WARN]: Error loading class: de/teamlapen/vampirism/api/entity/player/IFactionPlayer (java.lang.ClassNotFoundException: de.teamlapen.vampirism.api.entity.player.IFactionPlayer)\n[08:45:21] [pool-3-thread-1/WARN]: Error loading class: de/teamlapen/vampirism/api/entity/factions/IFactionEntity (java.lang.ClassNotFoundException: de.teamlapen.vampirism.api.entity.factions.IFactionEntity)\n[08:45:21] [pool-3-thread-1/WARN]: Error loading class: de/teamlapen/vampirism/api/entity/player/skills/ISkillHandler (java.lang.ClassNotFoundException: de.teamlapen.vampirism.api.entity.player.skills.ISkillHandler)\nException in thread \"main\" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:32)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.run(Launcher.java:106)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.main(Launcher.java:77)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141)\nCaused by: java.lang.reflect.InvocationTargetException\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n\tat MC-BOOTSTRAP/fmlloader@1.19.3-44.1.22/net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:27)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30)\n\t... 7 more\nCaused by: java.lang.RuntimeException: org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered\n\tat MC-BOOTSTRAP/fmlloader@1.19.3-44.1.22/net.minecraftforge.fml.loading.BackgroundWaiter.runAndTick(BackgroundWaiter.java:29)\n\tat TRANSFORMER/minecraft@1.19.3/net.minecraft.client.main.Main.m_239872_(Main.java:145)\n\tat TRANSFORMER/minecraft@1.19.3/net.minecraft.client.main.Main.main(Main.java:51)\n\t... 13 more\nCaused by: org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:392)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:250)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.service.modlauncher.MixinTransformationHandler.processClass(MixinTransformationHandler.java:131)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.launch.MixinLaunchPluginLegacy.processClass(MixinLaunchPluginLegacy.java:131)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.serviceapi.ILaunchPluginService.processClassWithFlags(ILaunchPluginService.java:156)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.LaunchPluginHandler.offerClassNodeToPlugins(LaunchPluginHandler.java:88)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.ClassTransformer.transform(ClassTransformer.java:120)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.TransformingClassLoader.maybeTransformClassBytes(TransformingClassLoader.java:50)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.readerToClass(ModuleClassLoader.java:113)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.lambda$findClass$15(ModuleClassLoader.java:219)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.loadFromModule(ModuleClassLoader.java:229)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.findClass(ModuleClassLoader.java:219)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.loadClass(ModuleClassLoader.java:135)\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)\n\tat TRANSFORMER/forge@44.1.22/net.minecraftforge.registries.GameData.init(GameData.java:136)\n\tat TRANSFORMER/forge@44.1.22/net.minecraftforge.registries.GameData.<clinit>(GameData.java:121)\n\tat TRANSFORMER/minecraft@1.19.3/net.minecraft.core.registries.BuiltInRegistries.forge(BuiltInRegistries.java:420)\n\tat TRANSFORMER/minecraft@1.19.3/net.minecraft.core.registries.BuiltInRegistries.forge(BuiltInRegistries.java:400)\n\tat TRANSFORMER/minecraft@1.19.3/net.minecraft.core.registries.BuiltInRegistries.<clinit>(BuiltInRegistries.java:117)\n\tat TRANSFORMER/minecraft@1.19.3/net.minecraft.server.Bootstrap.m_135870_(Bootstrap.java:37)\n\tat TRANSFORMER/minecraft@1.19.3/net.minecraft.client.main.Main.lambda$run$0(Main.java:145)\n\tat java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)\n\tat java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)\n\tat java.base/java.lang.Thread.run(Thread.java:833)\nCaused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: de.teamlapen.vampirism.api.entity.player.skills.ISkillHandler\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinPreProcessorStandard.transformMethod(MixinPreProcessorStandard.java:754)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinPreProcessorStandard.transform(MixinPreProcessorStandard.java:739)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinPreProcessorStandard.attach(MixinPreProcessorStandard.java:310)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinPreProcessorStandard.createContextFor(MixinPreProcessorStandard.java:280)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinInfo.createContextFor(MixinInfo.java:1288)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:292)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:383)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:365)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363)\n\t... 25 more"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/graphics_driver.txt",
    "content": "[20:04:06] [Client thread/INFO]: Setting user: dragondecimator\n[20:04:06] [Client thread/INFO]: (Session ID is token:f5c4b35f76f643488e5207346aabfbf5:31e215dcad9647e6afbef45d1687ae61)\n[20:04:09] [Client thread/INFO]: LWJGL Version: 2.9.1\n[20:04:09] [Client thread/INFO]: Reloading ResourceManager: Default\n[20:04:10] [Sound Library Loader/INFO]: Starting up SoundSystem...\n[20:04:10] [Client thread/WARN]: File minecraft:sounds/mob/ghast/fireball.ogg does not exist, cannot add it to event minecraft:item.fireCharge.use\n[20:04:10] [Thread-6/INFO]: Initializing LWJGL OpenAL\n[20:04:10] [Thread-6/INFO]: (The LWJGL binding of OpenAL.  For more information, see http://www.lwjgl.org)\n[20:04:10] [Thread-6/INFO]: OpenAL initialized.\n[20:04:11] [Sound Library Loader/INFO]: Sound engine started\n[20:04:13] [Client thread/INFO]: Created: 512x512 textures-atlas\n#\n# A fatal error has been detected by the Java Runtime Environment:\n#\n#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x27583e88, pid=4092, tid=3656\n#\n# JRE version: Java(TM) SE Runtime Environment (8.0_25-b18) (build 1.8.0_25-b18)\n# Java VM: Java HotSpot(TM) Client VM (25.25-b02 mixed mode windows-x86 )\n# Problematic frame:\n# C  [ig4dev32.dll+0x3e88]\n#\n# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows\n#\n# An error report file with more information is saved as:\n# C:\\Users\\Andrea\\AppData\\Roaming\\.minecraft\\hs_err_pid4092.log\n#\n# If you would like to submit a bug report, please visit:\n#   http://bugreport.sun.com/bugreport/crash.jsp\n# The crash happened outside the Java Virtual Machine in native code.\n# See problematic frame for where to report the bug.\n#\nAL lib: (EE) alc_cleanup: 1 device not closed\nJava HotSpot(TM) Client VM warning: Using incremental CMS is deprecated and will likely be removed in a future release"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/incomplete_forge_installation.txt",
    "content": "[authlib-injector] [INFO] Logging file: D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\authlib-injector.log\n[authlib-injector] [INFO] Version: 1.2.2\n[authlib-injector] [INFO] Authentication server: http://localhost:59486\n[authlib-injector] [WARNING] You are using HTTP protocol, which is INSECURE! Please switch to HTTPS if possible.\n[11:25:59] [main/INFO]: ModLauncher running: args [--username, 666, --version, 1.19.2, --gameDir, D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft, --assetsDir, D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\assets, --assetIndex, 1.19, --uuid, 1ee797995aa33433bd3790203ecd5ce7, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, legacy, --versionType, HMCL 3.5.3.230, --width, 854, --height, 480, --launchTarget, forgeclient, --fml.forgeVersion, 43.1.47, --fml.mcVersion, 1.19.2, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20220805.130853]\n[11:26:00] [main/INFO]: ModLauncher 10.0.8+10.0.8+main.0ef7e830 starting: java version 17.0.1 by Microsoft; OS Windows 10 arch amd64 version 10.0\n[11:26:00] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/D:/❄❄❄❄❄❄❄❄/FileRecv/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%2391!/ Service=ModLauncher Env=CLIENT\n[11:26:00] [main/INFO]: Found mod file CTM-1.19.2-1.1.6+8.jar of type MOD with provider {mods folder locator at D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\mods}\n[11:26:00] [main/INFO]: Found mod file Patchouli-1.19.2-77.jar of type MOD with provider {mods folder locator at D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\mods}\n[11:26:00] [main/INFO]: Found mod file twilightforest-fabric-1.19.2-4.2.301.jar of type MOD with provider {mods folder locator at D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\mods}\nException in thread \"main\" java.io.UncheckedIOException: java.io.IOException: Invalid paths argument, contained no existing paths: [D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\libraries\\net\\minecraft\\client\\1.19.2-20220805.130853\\client-1.19.2-20220805.130853-srg.jar, D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\libraries\\net\\minecraft\\client\\1.19.2-20220805.130853\\client-1.19.2-20220805.130853-extra.jar, D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\libraries\\net\\minecraftforge\\forge\\1.19.2-43.1.47\\forge-1.19.2-43.1.47-client.jar]\n\tat cpw.mods.securejarhandler/cpw.mods.jarhandling.impl.Jar.<init>(Jar.java:82)\n\tat cpw.mods.securejarhandler/cpw.mods.jarhandling.SecureJar.from(SecureJar.java:70)\n\tat MC-BOOTSTRAP/fmlloader@1.19.2-43.1.47/net.minecraftforge.fml.loading.moddiscovery.ModJarMetadata.buildFile(ModJarMetadata.java:41)\n\tat MC-BOOTSTRAP/fmlloader@1.19.2-43.1.47/net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator.scanMods(MinecraftLocator.java:37)\n\tat MC-BOOTSTRAP/fmlloader@1.19.2-43.1.47/net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer.discoverMods(ModDiscoverer.java:74)\n\tat MC-BOOTSTRAP/fmlloader@1.19.2-43.1.47/net.minecraftforge.fml.loading.FMLLoader.beginModScan(FMLLoader.java:166)\n\tat MC-BOOTSTRAP/fmlloader@1.19.2-43.1.47/net.minecraftforge.fml.loading.FMLServiceProvider.beginScanning(FMLServiceProvider.java:86)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.TransformationServiceDecorator.runScan(TransformationServiceDecorator.java:112)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.TransformationServicesHandler.lambda$runScanningTransformationServices$8(TransformationServicesHandler.java:100)\n\tat java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)\n\tat java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779)\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)\n\tat java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)\n\tat java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.TransformationServicesHandler.runScanningTransformationServices(TransformationServicesHandler.java:102)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:55)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.run(Launcher.java:87)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.main(Launcher.java:77)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141)\nCaused by: java.io.IOException: Invalid paths argument, contained no existing paths: [D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\libraries\\net\\minecraft\\client\\1.19.2-20220805.130853\\client-1.19.2-20220805.130853-srg.jar, D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\libraries\\net\\minecraft\\client\\1.19.2-20220805.130853\\client-1.19.2-20220805.130853-extra.jar, D:\\❄❄❄❄❄❄❄❄\\FileRecv\\.minecraft\\libraries\\net\\minecraftforge\\forge\\1.19.2-43.1.47\\forge-1.19.2-43.1.47-client.jar]\n\t... 25 more"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/incomplete_forge_installation2.txt",
    "content": "2023-03-18 12:54:04,303 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND\n[12:54:05] [main/INFO]: ModLauncher running: args [--username, 2333, --version, Minecraft Pixelmon, --gameDir, C:\\Users\\Administrator\\Downloads\\.minecraft, --assetsDir, C:\\Users\\Administrator\\Downloads\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 19a73ce1f6c13f9bbdbaecaab8291e74, --accessToken, ❄❄❄❄❄❄❄❄, --userType, legacy, --versionType, HMCL 3.5.4, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.34, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550]\n[12:54:05] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 1.8.0_361 by Oracle Corporation\n[12:54:08] [main/INFO]: Added Lets Encrypt root certificates as additional trust\n[12:54:08] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/C:/Users/Administrator/Downloads/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\n[12:54:09] [main/FATAL]: Failed to find Minecraft resource version 1.16.5-20210115.111550 at C:\\Users\\Administrator\\Downloads\\.minecraft\\libraries\\net\\minecraftforge\\forge\\1.16.5-36.2.34\\forge-1.16.5-36.2.34-client.jar\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: java.lang.RuntimeException: Missing minecraft resource!\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.FMLCommonLaunchHandler.lambda$validatePaths$4(FMLCommonLaunchHandler.java:124)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.util.Spliterators$ArraySpliterator.forEachRemaining(Unknown Source)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.util.stream.ReferencePipeline$Head.forEach(Unknown Source)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.FMLCommonLaunchHandler.validatePaths(FMLCommonLaunchHandler.java:121)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.FMLLoader.setupLaunchHandler(FMLLoader.java:202)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.FMLServiceProvider.initialize(FMLServiceProvider.java:94)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformationServiceDecorator.onInitialize(TransformationServiceDecorator.java:68)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformationServicesHandler.lambda$initialiseTransformationServices$7(TransformationServicesHandler.java:107)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.util.HashMap$Values.forEach(Unknown Source)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformationServicesHandler.initialiseTransformationServices(TransformationServicesHandler.java:107)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:59)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.run(Launcher.java:76)\n[12:54:09] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)\nException in thread \"main\" "
  },
  {
    "path": "HMCLCore/src/test/resources/logs/incomplete_forge_installation3.txt",
    "content": "2023-03-16 22:19:06,155 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND\n[22:19:06] [main/INFO]: ModLauncher running: args [--username, 111, --version, RAD2-1.16.5, --gameDir, C:\\Users\\Administrator\\Desktop\\HMCL\\.minecraft\\versions\\RAD2-1.16.5, --assetsDir, C:\\Users\\Administrator\\Desktop\\HMCL\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 2a386545f1ff370eab061a766a28340a, --accessToken, ❄❄❄❄❄❄❄❄, --userType, legacy, --versionType, HMCL 3.5.3.228, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.39, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550]\n[22:19:06] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 1.8.0_362 by BellSoft\n[22:19:06] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/C:/Users/Administrator/Desktop/HMCL/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\n[22:19:06] [main/ERROR]: Cannot find launch target fmlclient, unable to launch\nException in thread \"main\" java.lang.RuntimeException: Cannot find launch target\n\tat cpw.mods.modlauncher.LaunchServiceHandler.validateLaunchTarget(LaunchServiceHandler.java:87)\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:78)\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/incomplete_forge_installation4.txt",
    "content": "[035月2023 10:17:42.178] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher running: args [--username, Lin_morning, --version, 匠魂3, --gameDir, C:\\Users\\********\\Desktop\\.minecraft\\versions\\匠魂3, --assetsDir, C:\\Users\\********\\Desktop\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 6758aaf5ac48499b927fff9395a71d19, --accessToken, ????????, --userType, msa, --versionType, PCL, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.34, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550]\n[035月2023 10:17:42.185] [main/INFO] [cpw.mods.modlauncher.Launcher/MODLAUNCHER]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 17.0.6 by Oracle Corporation\n[035月2023 10:17:42.621] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/C:/Users/********/Desktop/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\n[035月2023 10:17:42.637] [main/ERROR] [cpw.mods.modlauncher.LaunchServiceHandler/MODLAUNCHER]: Cannot find launch target fmlclient, unable to launch"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/incomplete_forge_installation5.txt",
    "content": "2023-04-24 13:16:44,288 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND\n[13:16:44] [main/INFO]: ModLauncher running: args [--username, 1227, --version, 龙之冒险v3.2a, --gameDir, E:\\MC\\.minecraft\\versions\\龙之冒险v3.2a, --assetsDir, E:\\MC\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 33c5ab003c2436469ce8ac5585bcf830, --accessToken, ❄❄❄❄❄❄❄❄, --userType, legacy, --versionType, HMCL 3.5.4, --width, 854, --height, 480, --tweakClass, optifine.OptiFineForgeTweaker, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.35, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550]\n[13:16:44] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 11.0.12 by Oracle Corporation\n[13:16:44] [main/INFO]: OptiFineTransformationService.onLoad\n[13:16:44] [main/INFO]: OptiFine ZIP file: E:\\MC\\.minecraft\\libraries\\optifine\\OptiFine\\1.16.5_HD_U_G8\\OptiFine-1.16.5_HD_U_G8.jar\n[13:16:44] [main/INFO]: Target.PRE_CLASS is available\n[13:16:44] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/E:/MC/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\n[13:16:44] [main/INFO]: OptiFineTransformationService.initialize\n[13:16:44] [main/INFO]: OptiFineTransformationService.transformers\n[13:16:44] [main/INFO]: Targets: 311\n[13:16:44] [main/ERROR]: Cannot find launch target fmlclient, unable to launch\nException in thread \"main\" java.lang.RuntimeException: Cannot find launch target\n\tat cpw.mods.modlauncher.LaunchServiceHandler.validateLaunchTarget(LaunchServiceHandler.java:87)\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:78)\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/incomplete_forge_installation6.txt",
    "content": "[authlib-injector] [INFO] Logging file: /home/zkitefly/Desktop/.minecraft/authlib-injector.log\n[authlib-injector] [INFO] Version: 1.2.4\n[authlib-injector] [INFO] Authentication server: http://localhost:37183\n[authlib-injector] [WARNING] You are using HTTP protocol, which is INSECURE! Please switch to HTTPS if possible.\n[14:44:51] [main/INFO]: ModLauncher running: args [--username, Zkitefky, --version, 1.20.4, --gameDir, /home/zkitefly/Desktop/.minecraft, --assetsDir, /home/zkitefly/Desktop/.minecraft/assets, --assetIndex, 12, --uuid, 627aa9226fd33670a0196027fa781599, --accessToken, **********, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, msa, --versionType, HMCL (PR Collection) 3.5.unofficial-99173a1 (PR Collection), --width, 854, --height, 480, --launchTarget, forge_client]\n[14:44:51] [main/INFO]: JVM identified as BellSoft OpenJDK 64-Bit Client VM 17.0.6+10-LTS\n[14:44:51] [main/INFO]: ModLauncher 10.1.2 starting: java version 17.0.6 by BellSoft; OS Linux arch amd64 version 5.10.0-amd64-desktop\n[14:44:51] [main/INFO]: Loading ImmediateWindowProvider fmlearlywindow\n[14:44:51] [main/INFO]: Trying GL version 4.6\n[14:44:51] [main/INFO]: Requested GL version 4.6 got version 4.6\n[14:44:51] [EarlyDisplay/INFO]: GL info: Mesa Intel(R) HD Graphics 530 (SKL GT2) GL version 4.6 (Core Profile) Mesa 21.3.8, Intel\n[14:44:51] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=jar:file:///home/zkitefly/Desktop/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar!/ Service=ModLauncher Env=CLIENT\nException in thread \"main\" java.lang.reflect.InvocationTargetException\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n\tat net.minecraftforge.bootstrap.Bootstrap.start(Bootstrap.java:48)\n\tat net.minecraftforge.bootstrap.ForgeBootstrap.main(ForgeBootstrap.java:18)\nCaused by: java.lang.IllegalStateException: Could not find net/minecraft/client/Minecraft.class in classloader SecureModuleClassLoader[SECURE-BOOTSTRAP]@2074820378\n\tat SECURE-BOOTSTRAP/net.minecraftforge.fmlloader@1.20.4-49.0.19/net.minecraftforge.fml.loading.targets.CommonLaunchHandler.getPathFromResource(CommonLaunchHandler.java:98)\n\tat SECURE-BOOTSTRAP/net.minecraftforge.fmlloader@1.20.4-49.0.19/net.minecraftforge.fml.loading.targets.ForgeProdLaunchHandler$Client.getMinecraftPaths(ForgeProdLaunchHandler.java:28)\n\tat SECURE-BOOTSTRAP/net.minecraftforge.fmlloader@1.20.4-49.0.19/net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator.scanMods(MinecraftLocator.java:24)\n\tat SECURE-BOOTSTRAP/net.minecraftforge.fmlloader@1.20.4-49.0.19/net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer.discoverMods(ModDiscoverer.java:80)\n\tat SECURE-BOOTSTRAP/net.minecraftforge.fmlloader@1.20.4-49.0.19/net.minecraftforge.fml.loading.FMLLoader.beginModScan(FMLLoader.java:154)\n\tat SECURE-BOOTSTRAP/net.minecraftforge.fmlloader@1.20.4-49.0.19/net.minecraftforge.fml.loading.FMLServiceProvider.beginScanning(FMLServiceProvider.java:58)\n\tat SECURE-BOOTSTRAP/cpw.mods.modlauncher@10.1.2/cpw.mods.modlauncher.TransformationServiceDecorator.runScan(TransformationServiceDecorator.java:98)\n\tat SECURE-BOOTSTRAP/cpw.mods.modlauncher@10.1.2/cpw.mods.modlauncher.TransformationServicesHandler.lambda$runScanningTransformationServices$7(TransformationServicesHandler.java:96)\n\tat java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)\n\tat java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779)\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)\n\tat java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)\n\tat java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)\n\tat SECURE-BOOTSTRAP/cpw.mods.modlauncher@10.1.2/cpw.mods.modlauncher.TransformationServicesHandler.runScanningTransformationServices(TransformationServicesHandler.java:98)\n\tat SECURE-BOOTSTRAP/cpw.mods.modlauncher@10.1.2/cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:49)\n\tat SECURE-BOOTSTRAP/cpw.mods.modlauncher@10.1.2/cpw.mods.modlauncher.Launcher.run(Launcher.java:86)\n\tat SECURE-BOOTSTRAP/cpw.mods.modlauncher@10.1.2/cpw.mods.modlauncher.Launcher.main(Launcher.java:73)\n\tat SECURE-BOOTSTRAP/cpw.mods.modlauncher@10.1.2/cpw.mods.modlauncher.BootstrapEntry.main(BootstrapEntry.java:17)\n\tat net.minecraftforge.bootstrap@2.0.0/net.minecraftforge.bootstrap.Bootstrap.moduleMain(Bootstrap.java:97)\n\t... 6 more"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/incomplete_forge_installation7.txt",
    "content": "2024-02-19 11:23:55,738 main WARN Advanced terminal features are not available in this environment\n[11:23:55] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher running: args [--username, Lod_Superdark, --version, The Legend of Tinker, --gameDir, F:\\我你爹\\The Legend of Tinker\\.minecraft\\versions\\The Legend of Tinker, --assetsDir, F:\\我你爹\\The Legend of Tinker\\.minecraft\\assets, --assetIndex, 1.18, --uuid, 000000000000300D9C90BD99F9099C10, --accessToken, ????????, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, Legacy, --versionType, sad, --width, 854, --height, 480, --launchTarget, forgeclient, --fml.forgeVersion, 40.2.10, --fml.mcVersion, 1.18.2, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20220404.173914]\n[11:23:55] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher 9.1.3+9.1.3+main.9b69c82a starting: java version 17.0.2 by Oracle Corporation\n[11:23:56] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/F:/我你爹/The%20Legend%20of%20Tinker/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%2348!/ Service=ModLauncher Env=CLIENT\n[2024-02-19 11:23:56] [INFO]: I18nUpdate Mod 3.4.1 is loaded in 1.18.2 with Forge\nException in thread \"main\" java.lang.reflect.InvocationTargetException\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n\tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58)\n\tat oolloo.jlw.Wrapper.main(Wrapper.java:51)\nCaused by: java.io.UncheckedIOException: java.io.IOException: Invalid paths argument, contained no existing paths: [F:\\我你爹\\The Legend of Tinker\\.minecraft\\libraries\\net\\minecraftforge\\fmlcore\\1.18.2-40.2.10\\fmlcore-1.18.2-40.2.10.jar]\n\tat cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.impl.Jar.<init>(Jar.java:74)\n\tat cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.SecureJar.from(SecureJar.java:58)\n\tat cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.SecureJar.from(SecureJar.java:54)\n\tat cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.SecureJar.from(SecureJar.java:46)\n\tat cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.SecureJar.from(SecureJar.java:38)\n\tat MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator.lambda$scanMods$3(MinecraftLocator.java:35)\n\tat java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)\n\tat java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)\n\tat java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)\n\tat java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)\n\tat MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator.scanMods(MinecraftLocator.java:37)\n\tat MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer.discoverMods(ModDiscoverer.java:59)\n\tat MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.FMLLoader.beginModScan(FMLLoader.java:166)\n\tat MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.FMLServiceProvider.beginScanning(FMLServiceProvider.java:86)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.TransformationServiceDecorator.runScan(TransformationServiceDecorator.java:112)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.TransformationServicesHandler.lambda$runScanningTransformationServices$8(TransformationServicesHandler.java:100)\n\tat java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)\n\tat java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779)\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)\n\tat java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)\n\tat java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.TransformationServicesHandler.runScanningTransformationServices(TransformationServicesHandler.java:102)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:55)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.Launcher.run(Launcher.java:87)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.Launcher.main(Launcher.java:77)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.0.0/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:149)\n\t... 6 more\nCaused by: java.io.IOException: Invalid paths argument, contained no existing paths: [F:\\我你爹\\The Legend of Tinker\\.minecraft\\libraries\\net\\minecraftforge\\fmlcore\\1.18.2-40.2.10\\fmlcore-1.18.2-40.2.10.jar]\n\t... 41 more"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/install_mixinbootstrap.txt",
    "content": "[20:24:24] [main/INFO]: Loading tweak class name net.minecraftforge.fml.common.launcher.FMLTweaker\n[20:24:25] [main/INFO]: Using primary tweak class name net.minecraftforge.fml.common.launcher.FMLTweaker\n[20:24:25] [main/INFO]: Calling tweak class net.minecraftforge.fml.common.launcher.FMLTweaker\n[20:24:25] [main/INFO]: Forge Mod Loader version 14.23.5.2808 for Minecraft 1.12.2 loading\n[20:24:25] [main/INFO]: Java is OpenJDK 64-Bit Server VM, version 1.8.0_382, running on Windows 11:amd64:10.0, installed at C:\\Program Files\\BellSoft\\LibericaJRE-8-Full\n[20:24:25] [main/INFO]: Searching E:\\Mc\\HMCL-3.5.5\\.minecraft\\versions\\Modern Skyblock 3\\mods for mods\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in AppleCore-mc1.12.2-3.2.0.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod AppleCore (squeek.applecore.AppleCore) is not signed!\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in astralsorcery-1.12.2-1.10.11.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod hellfirepvp.astralsorcery.core.AstralCore does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/INFO]: [AstralCore] Initialized.\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in bedpatch-2.2-1.12.2.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod BedPatch (com.mordenkainen.bedpatch.BedPatch) is not signed!\n[20:24:25] [main/INFO]: Loading tweaker guichaguri.betterfps.tweaker.BetterFpsTweaker from BetterFps-1.4.8.jar\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in CTM-MC1.12.2-0.3.3.22.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod team.chisel.ctm.client.asm.CTMCorePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: The coremod CTMCorePlugin (team.chisel.ctm.client.asm.CTMCorePlugin) is not signed!\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in EnderCore-1.12.2-0.5.45.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in ExpandedEquivalence-1.12.2-11b.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod com.zeitheron.expequiv.core.EECore does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in foamfix-0.10.3-1.12.2.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod pl.asie.foamfix.coremod.FoamFixCore does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in Forgelin-1.8.2.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod net.shadowfacts.forgelin.preloader.ForgelinPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: The coremod ForgelinPlugin (net.shadowfacts.forgelin.preloader.ForgelinPlugin) is not signed!\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in HammerCore-1.12.2-2.0.4.2.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/INFO]: Loading tweaker i18nupdatemod.launchwrapper.LaunchWrapperTweaker from I18nUpdateMod-3.5.0-all.jar\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in InventoryTweaks-1.64+dev.146.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod invtweaks.forge.asm.FMLPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in NotEnoughIDs-1.5.4.3.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod ru.fewizz.neid.asm.Plugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: The coremod Plugin (ru.fewizz.neid.asm.Plugin) is not signed!\n[20:24:25] [main/INFO]: Loading tweaker org.spongepowered.asm.launch.MixinTweaker from Nothirium-1.12.2-0.3.4-beta.jar\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in OpenComputers-MC1.12.2-1.7.4.153.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod li.cil.oc.common.launch.TransformerLoader does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: The coremod TransformerLoader (li.cil.oc.common.launch.TransformerLoader) is not signed!\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in OpenModsLib-1.12.2-0.12.1.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod openmods.core.OpenModsCorePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in Quark-r1.5-146.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod vazkii.quark.base.asm.LoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: The coremod LoadingPlugin (vazkii.quark.base.asm.LoadingPlugin) is not signed!\n[20:24:25] [main/WARN]: The coremod com.therandomlabs.randompatches.core.RPCore does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in RandomThings-MC1.12.2-4.2.6.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod lumien.randomthings.asm.LoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: Found FMLCorePluginContainsFMLMod marker in ResourceLoader-MC1.12.1-1.5.3.jar. This is not recommended, @Mods should be in a separate jar from the coremod.\n[20:24:25] [main/WARN]: The coremod lumien.resourceloader.asm.LoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: The coremod blusunrize.immersiveengineering.common.asm.IELoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: The coremod IELoadingPlugin (blusunrize.immersiveengineering.common.asm.IELoadingPlugin) is not signed!\n[20:24:25] [main/WARN]: The coremod shetiphian.asm.TweakPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[20:24:25] [main/WARN]: The coremod ShetiPhian-ASM (shetiphian.asm.TweakPlugin) is not signed!\n[20:24:25] [main/INFO]: Loading tweak class name net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker\n[20:24:25] [main/INFO]: Loading tweak class name guichaguri.betterfps.tweaker.BetterFpsTweaker\n[20:24:25] [main/INFO]: Loading tweak class name i18nupdatemod.launchwrapper.LaunchWrapperTweaker\n[20:24:25] [main/INFO]: Loading tweak class name org.spongepowered.asm.launch.MixinTweaker\n[20:24:25] [main/ERROR]: Unable to launch\njava.lang.ClassNotFoundException: org.spongepowered.asm.launch.MixinTweaker\n\tat java.net.URLClassLoader.findClass(URLClassLoader.java:387) ~[?:1.8.0_382]\n\tat java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_382]\n\tat sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352) ~[?:1.8.0_382]\n\tat java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_382]\n\tat net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:106) ~[launchwrapper-1.12.jar:?]\n\tat java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_382]\n\tat java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_382]\n\tat java.lang.Class.forName0(Native Method) ~[?:1.8.0_382]\n\tat java.lang.Class.forName(Class.java:348) ~[?:1.8.0_382]\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:98) [launchwrapper-1.12.jar:?]\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.12.jar:?]\n[20:24:25] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1052]: net.minecraftforge.fml.relauncher.FMLSecurityManager$ExitTrappedException\n[20:24:25] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1052]: \tat net.minecraftforge.fml.relauncher.FMLSecurityManager.checkPermission(FMLSecurityManager.java:49)\n[20:24:25] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1052]: \tat java.lang.SecurityManager.checkExit(SecurityManager.java:761)\n[20:24:25] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1052]: \tat java.lang.Runtime.exit(Runtime.java:101)\n[20:24:25] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1052]: \tat java.lang.System.exit(System.java:987)\n[20:24:25] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1052]: \tat net.minecraft.launchwrapper.Launch.launch(Launch.java:138)\n[20:24:25] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1052]: \tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\nException in thread \"main\" "
  },
  {
    "path": "HMCLCore/src/test/resources/logs/jade_forest_optifine.txt",
    "content": "Picked up _JAVA_OPTIONS: \n2023-06-22 20:59:01,471 main WARN Advanced terminal features are not available in this environment\n[20:59:01] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher running: args [--username, TranMC, --version, 1.20.1-forge-47.0.16, --gameDir, C:\\Users\\TranMC\\AppData\\Roaming\\.tlauncher\\legacy\\Minecraft\\game, --assetsDir, C:\\Users\\TranMC\\AppData\\Roaming\\.tlauncher\\legacy\\Minecraft\\game\\assets, --assetIndex, 5, --uuid, 631afb7abe234bd391272fe56beb8b39, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, , --xuid, , --userType, mojang, --versionType, release, --width, 925, --height, 530, --launchTarget, forgeclient, --fml.forgeVersion, 47.0.16, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412]\n[20:59:01] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.3 by Microsoft; OS Windows 10 arch amd64 version 10.0\n[20:59:02] [main/INFO] [ne.mi.fm.lo.ImmediateWindowHandler/]: Loading ImmediateWindowProvider fmlearlywindow\n[20:59:02] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.6\n[20:59:02] [main/INFO] [EARLYDISPLAY/]: Requested GL version 4.6 got version 4.6\n[20:59:02] [main/INFO] [op.OptiFineTransformationService/]: OptiFineTransformationService.onLoad\n[20:59:02] [main/INFO] [op.OptiFineTransformationService/]: OptiFine ZIP file URL: union:/C:/Users/TranMC/AppData/Roaming/.tlauncher/legacy/Minecraft/game/mods/preview_OptiFine_1.20.1_HD_U_I5_pre4.jar%23164!/\n[20:59:02] [main/INFO] [op.OptiFineTransformationService/]: OptiFine ZIP file: C:\\Users\\TranMC\\AppData\\Roaming\\.tlauncher\\legacy\\Minecraft\\game\\mods\\preview_OptiFine_1.20.1_HD_U_I5_pre4.jar\n[20:59:02] [main/INFO] [op.OptiFineTransformer/]: Target.PRE_CLASS is available\n[20:59:03] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/C:/Users/TranMC/AppData/Roaming/.tlauncher/legacy/Minecraft/game/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%23100!/ Service=ModLauncher Env=CLIENT\n[20:59:03] [main/INFO] [op.OptiFineTransformationService/]: OptiFineTransformationService.initialize\n[20:59:04] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file C:\\Users\\TranMC\\AppData\\Roaming\\.tlauncher\\legacy\\Minecraft\\game\\libraries\\net\\minecraftforge\\fmlcore\\1.20.1-47.0.16\\fmlcore-1.20.1-47.0.16.jar is missing mods.toml file\n[20:59:04] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file C:\\Users\\TranMC\\AppData\\Roaming\\.tlauncher\\legacy\\Minecraft\\game\\libraries\\net\\minecraftforge\\javafmllanguage\\1.20.1-47.0.16\\javafmllanguage-1.20.1-47.0.16.jar is missing mods.toml file\n[20:59:04] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file C:\\Users\\TranMC\\AppData\\Roaming\\.tlauncher\\legacy\\Minecraft\\game\\libraries\\net\\minecraftforge\\lowcodelanguage\\1.20.1-47.0.16\\lowcodelanguage-1.20.1-47.0.16.jar is missing mods.toml file\n[20:59:04] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file C:\\Users\\TranMC\\AppData\\Roaming\\.tlauncher\\legacy\\Minecraft\\game\\libraries\\net\\minecraftforge\\mclanguage\\1.20.1-47.0.16\\mclanguage-1.20.1-47.0.16.jar is missing mods.toml file\n[20:59:04] [main/INFO] [ne.mi.fm.lo.mo.JarInJarDependencyLocator/]: No dependencies to load found. Skipping!\n[20:59:05] [main/INFO] [op.OptiFineTransformationService/]: OptiFineTransformationService.transformers\n[20:59:05] [main/INFO] [op.OptiFineTransformer/]: Targets: 410\n[20:59:07] [main/INFO] [op.OptiFineTransformationService/]: additionalClassesLocator: [optifine., net.optifine.]\n[20:59:07] [main/INFO] [cp.mo.mo.LaunchServiceHandler/MODLAUNCHER]: Launching target 'forgeclient' with arguments [--version, 1.20.1-forge-47.0.16, --gameDir, C:\\Users\\TranMC\\AppData\\Roaming\\.tlauncher\\legacy\\Minecraft\\game, --assetsDir, C:\\Users\\TranMC\\AppData\\Roaming\\.tlauncher\\legacy\\Minecraft\\game\\assets, --uuid, 631afb7abe234bd391272fe56beb8b39, --username, TranMC, --assetIndex, 5, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, , --xuid, , --userType, mojang, --versionType, release, --width, 925, --height, 530]\n[20:59:12] [main/ERROR] [mixin/]: Critical injection failure: LVT in net/minecraft/client/renderer/GameRenderer::m_109093_(FJZ)V has incompatible changes at opcode 760 in callback jade.mixins.json:GameRendererMixin->@Inject::jade$runTick(FJZLorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;IILcom/mojang/blaze3d/platform/Window;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/gui/GuiGraphics;)V.\n Expected: [I, I, Lcom/mojang/blaze3d/platform/Window;, Lorg/joml/Matrix4f;, Lcom/mojang/blaze3d/vertex/PoseStack;, Lnet/minecraft/client/gui/GuiGraphics;]\n    Found: [I, I, Lcom/mojang/blaze3d/platform/Window;, F, Lorg/joml/Matrix4f;, Lcom/mojang/blaze3d/vertex/PoseStack;]\nAvailable: [I, I, Lcom/mojang/blaze3d/platform/Window;, F, Lorg/joml/Matrix4f;, Lcom/mojang/blaze3d/vertex/PoseStack;, F, Lnet/minecraft/client/gui/GuiGraphics;, Ljava/lang/Throwable;, Lnet/minecraft/CrashReport;, Lnet/minecraft/CrashReportCategory;]\nException in thread \"main\" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:32)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.run(Launcher.java:108)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.main(Launcher.java:78)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141)\nCaused by: java.lang.reflect.InvocationTargetException\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n\tat MC-BOOTSTRAP/fmlloader@1.20.1-47.0.16/net.minecraftforge.fml.loading.targets.CommonLaunchHandler.runTarget(CommonLaunchHandler.java:111)\n\tat MC-BOOTSTRAP/fmlloader@1.20.1-47.0.16/net.minecraftforge.fml.loading.targets.CommonLaunchHandler.clientService(CommonLaunchHandler.java:99)\n\tat MC-BOOTSTRAP/fmlloader@1.20.1-47.0.16/net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$makeService$0(CommonClientLaunchHandler.java:25)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30)\n\t... 7 more\nCaused by: org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:392)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:250)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.service.modlauncher.MixinTransformationHandler.processClass(MixinTransformationHandler.java:131)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.launch.MixinLaunchPluginLegacy.processClass(MixinLaunchPluginLegacy.java:131)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.serviceapi.ILaunchPluginService.processClassWithFlags(ILaunchPluginService.java:156)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.LaunchPluginHandler.offerClassNodeToPlugins(LaunchPluginHandler.java:88)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.ClassTransformer.transform(ClassTransformer.java:120)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.TransformingClassLoader.maybeTransformClassBytes(TransformingClassLoader.java:50)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.readerToClass(ModuleClassLoader.java:113)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.lambda$findClass$15(ModuleClassLoader.java:219)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.loadFromModule(ModuleClassLoader.java:229)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.findClass(ModuleClassLoader.java:219)\n\tat cpw.mods.securejarhandler/cpw.mods.cl.ModuleClassLoader.loadClass(ModuleClassLoader.java:135)\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)\n\tat java.base/java.lang.Class.getDeclaredFields0(Native Method)\n\tat java.base/java.lang.Class.privateGetDeclaredFields(Class.java:3297)\n\tat java.base/java.lang.Class.getDeclaredFields(Class.java:2371)\n\tat TRANSFORMER/net.optifine/net.optifine.reflect.FieldLocatorTypes.<init>(FieldLocatorTypes.java:25)\n\tat TRANSFORMER/net.optifine/net.optifine.reflect.Reflector.<clinit>(Reflector.java:524)\n\tat TRANSFORMER/minecraft@1.20.1/net.minecraft.CrashReport.m_127526_(CrashReport.java:175)\n\tat TRANSFORMER/minecraft@1.20.1/net.minecraft.CrashReport.m_127529_(CrashReport.java:345)\n\tat TRANSFORMER/minecraft@1.20.1/net.minecraft.client.main.Main.main(Main.java:149)\n\t... 15 more\nCaused by: org.spongepowered.asm.mixin.injection.throwables.InjectionError: LVT in net/minecraft/client/renderer/GameRenderer::m_109093_(FJZ)V has incompatible changes at opcode 760 in callback jade.mixins.json:GameRendererMixin->@Inject::jade$runTick(FJZLorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;IILcom/mojang/blaze3d/platform/Window;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/gui/GuiGraphics;)V.\n Expected: [I, I, Lcom/mojang/blaze3d/platform/Window;, Lorg/joml/Matrix4f;, Lcom/mojang/blaze3d/vertex/PoseStack;, Lnet/minecraft/client/gui/GuiGraphics;]\n    Found: [I, I, Lcom/mojang/blaze3d/platform/Window;, F, Lorg/joml/Matrix4f;, Lcom/mojang/blaze3d/vertex/PoseStack;]\nAvailable: [I, I, Lcom/mojang/blaze3d/platform/Window;, F, Lorg/joml/Matrix4f;, Lcom/mojang/blaze3d/vertex/PoseStack;, F, Lnet/minecraft/client/gui/GuiGraphics;, Ljava/lang/Throwable;, Lnet/minecraft/CrashReport;, Lnet/minecraft/CrashReportCategory;]\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.injection.callback.CallbackInjector.inject(CallbackInjector.java:497)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.injection.callback.CallbackInjector.inject(CallbackInjector.java:447)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.injection.code.Injector.inject(Injector.java:276)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.injection.struct.InjectionInfo.inject(InjectionInfo.java:445)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinTargetContext.applyInjections(MixinTargetContext.java:1355)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyInjections(MixinApplicatorStandard.java:1051)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:400)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:325)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:383)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:365)\n\tat MC-BOOTSTRAP/org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363)\n\t... 36 more"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/java9.txt",
    "content": "Exception in thread \"main\" java.lang.ClassCastException: class jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to class java.net.URLClassLoader (jdk.internal.loader.ClassLoaders$AppClassLoader and java.net.URLClassLoader are in module java.base of loader 'bootstrap')\n\tat net.minecraft.launchwrapper.Launch.<init>(Launch.java:34)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/java_version_is_too_high.txt",
    "content": "[authlib-injector] [INFO] Logging file: F:\\.minecraft\\authlib-injector.log\n[authlib-injector] [INFO] Version: 1.2.1\n[authlib-injector] [INFO] Authentication server: https://littleskin.cn/api/yggdrasil/\n2022-12-08 13:15:41,589 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND\n[13:15:43] [main/INFO]: ModLauncher running: args [--username, 哎呀呀呀, --version, 1.16.2, --gameDir, F:\\\\.minecraft, --assetsDir, F:\\.minecraft\\assets, --assetIndex, 1.16, --uuid, e3c2fb57f8764ecfa1564c1cc92143f2, --accessToken, ❄❄❄❄❄❄❄❄, --userType, mojang, --versionType, HMCL 3.5.3.228, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 33.0.61, --fml.mcVersion, 1.16.2, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20200812.004259]\n[13:15:43] [main/INFO]: ModLauncher 7.0.1+78+master.e9771d8 starting: java version 17.0.4.1 by Oracle Corporation\njava.lang.NoSuchFieldException: ucp\n\tat java.base/java.lang.Class.getDeclaredField(Class.java:2610)\n\tat cpw.mods.gross.Java9ClassLoaderUtil.getSystemClassPathURLs(Java9ClassLoaderUtil.java:28)\n\tat cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(TransformationServicesHandler.java:139)\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:74)\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:65)\nException in thread \"main\" java.lang.NullPointerException: Cannot read the array length because \"urls\" is null\n\tat java.base/jdk.internal.loader.URLClassPath.<init>(URLClassPath.java:155)\n\tat java.base/jdk.internal.loader.URLClassPath.<init>(URLClassPath.java:176)\n\tat java.base/java.net.URLClassLoader.<init>(URLClassLoader.java:152)\n\tat cpw.mods.modlauncher.TransformationServicesHandler$TransformerClassLoader.<init>(TransformationServicesHandler.java:159)\n\tat cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(TransformationServicesHandler.java:139)\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:74)\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:65)"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/jvm_32bit.txt",
    "content": "Invalid initial heap size: -Xms4096M\nThe specified size exceeds the maximum representable size.\nError: Could not create the Java Virtual Machine.\nError: A fatal exception has occurred. Program will exit."
  },
  {
    "path": "HMCLCore/src/test/resources/logs/jvm_32bit2.txt",
    "content": "﻿Error occurred during initialization of VM\nCould not reserve enough space for 3571712KB object heap"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/macos_failed_to_find_service_port_for_display.txt",
    "content": "[16:28:06] [main/INFO]: ModLauncher running: args [--username, byc, --version, 1.16.5guangying, --gameDir, /Users/biwenbo/Library/Application Support/minecraft/versions/1.16.5guangying, --assetsDir, /Users/biwenbo/Library/Application Support/minecraft/assets, --assetIndex, 1.16, --uuid, 6b38b80450703b1b91dd32b6d78cb31c, --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, HMCL 3.5.5, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.41, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550]\n[16:28:06] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 1.8.0_381 by Oracle Corporation\n[16:28:07] [main/INFO]: OptiFineTransformationService.onLoad\n[16:28:07] [main/INFO]: OptiFine ZIP file: /Users/biwenbo/Library/Application Support/minecraft/libraries/optifine/OptiFine/1.16.5_HD_U_G8/OptiFine-1.16.5_HD_U_G8.jar\n[16:28:07] [main/INFO]: Target.PRE_CLASS is available\n[16:28:07] [main/INFO]: Added Lets Encrypt root certificates as additional trust\n[16:28:07] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/Users/biwenbo/Library/Application%20Support/minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\n[16:28:07] [main/INFO]: OptiFineTransformationService.initialize\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:105]: \tDescription : Cocoa: Failed to find service port for display\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:106]: \tStacktrace  :\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: org.lwjgl.glfw.GLFW.glfwInit(GLFW.java:827)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: net.minecraftforge.fml.loading.progress.ClientVisualization.initWindow(ClientVisualization.java:58)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: net.minecraftforge.fml.loading.progress.ClientVisualization.start(ClientVisualization.java:335)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: net.minecraftforge.fml.loading.progress.EarlyProgressVisualization.accept(EarlyProgressVisualization.java:29)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: net.minecraftforge.fml.loading.FMLLoader.setupLaunchHandler(FMLLoader.java:176)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: net.minecraftforge.fml.loading.FMLServiceProvider.initialize(FMLServiceProvider.java:80)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.TransformationServiceDecorator.onInitialize(TransformationServiceDecorator.java:68)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.TransformationServicesHandler.lambda$initialiseTransformationServices$7(TransformationServicesHandler.java:107)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: java.util.HashMap$Values.forEach(HashMap.java:982)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.TransformationServicesHandler.initialiseTransformationServices(TransformationServicesHandler.java:107)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:59)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.Launcher.run(Launcher.java:76)\n[16:28:09] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.Launcher.main(Launcher.java:66)\n[16:28:09] [main/ERROR]: Suppressing Last GLFW error: [0x10008]Cocoa: Failed to find service port for display\n[16:28:10] [main/INFO]: OptiFineTransformationService.transformers\n[16:28:10] [main/INFO]: Targets: 311\n[16:28:11] [main/INFO]: additionalClassesLocator: [optifine., net.optifine.]\n[16:28:11] [main/INFO]: Launching target 'fmlclient' with arguments [--version, 1.16.5guangying, --gameDir, /Users/biwenbo/Library/Application Support/minecraft/versions/1.16.5guangying, --assetsDir, /Users/biwenbo/Library/Application Support/minecraft/assets, --uuid, 6b38b80450703b1b91dd32b6d78cb31c, --username, byc, --assetIndex, 1.16, --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, HMCL 3.5.5, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker]\n[16:28:12] [main/INFO]: [net.minecraft.client.main.Main:main:80]: Completely ignored arguments: [--tweakClass, optifine.OptiFineTweaker]\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:105]: \tDescription : Cocoa: Failed to find service port for display\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:106]: \tStacktrace  :\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: org.lwjgl.glfw.GLFW.glfwPollEvents(GLFW.java:3050)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: net.minecraftforge.fml.loading.BackgroundWaiter.runAndTick(BackgroundWaiter.java:19)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: net.minecraft.client.main.Main.main(Main.java:123)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: java.lang.reflect.Method.invoke(Method.java:498)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: net.minecraftforge.fml.loading.FMLClientLaunchProvider.lambda$launchService$0(FMLClientLaunchProvider.java:37)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.Launcher.run(Launcher.java:82)\n[16:28:14] [main/INFO]: [org.lwjgl.glfw.GLFWErrorCallback$1:invoke:110]: cpw.mods.modlauncher.Launcher.main(Launcher.java:66)\n[16:28:26] [Render thread/INFO]: Environment: authHost='http://localhost:58297/authserver', accountsHost='http://127.0.0.1:58307/https/api.mojang.com', sessionHost='http://127.0.0.1:58307/https/sessionserver.mojang.com', servicesHost='http://127.0.0.1:58307/https/api.minecraftservices.com', name='PROD'\n[16:28:27] [Render thread/INFO]: Setting user: byc\n[16:28:27] [Render thread/INFO]: [OptiFine] (Reflector) Class not present: net.minecraft.launchwrapper.Launch\n[16:28:27] [Render thread/INFO]: Backend library: LWJGL version 3.2.1 build 12\n[16:28:27] [Render thread/INFO]: [net.minecraft.util.registry.Bootstrap:func_179870_a:123]: ---- Minecraft Crash Report ----\n// I just don't know what went wrong :(\n\nTime: 24-2-1 下午4:28\nDescription: Initializing game\n\njava.lang.IllegalStateException: GLFW error before init: [0x10008]Cocoa: Failed to find service port for display\n\tat com.mojang.blaze3d.platform.GLX.lambda$_initGlfw$1(GLX.java:81) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.MainWindow.func_211162_a(MainWindow.java:178) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat com.mojang.blaze3d.platform.GLX._initGlfw(GLX.java:79) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat com.mojang.blaze3d.systems.RenderSystem.initBackendSystem(SourceFile:1060) ~[?:?] {re:classloading}\n\tat net.minecraft.client.Minecraft.<init>(Minecraft.java:416) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.main(Main.java:149) ~[1.16.5guangying.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_381] {}\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_381] {}\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_381] {}\n\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_381] {}\n\tat net.minecraftforge.fml.loading.FMLClientLaunchProvider.lambda$launchService$0(FMLClientLaunchProvider.java:37) ~[forge-1.16.5-36.2.41.jar:36.2] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) [modlauncher-8.1.3.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54) [modlauncher-8.1.3.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72) [modlauncher-8.1.3.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:82) [modlauncher-8.1.3.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66) [modlauncher-8.1.3.jar:?] {}\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Render thread\nStacktrace:\n\tat com.mojang.blaze3d.platform.GLX.lambda$_initGlfw$1(GLX.java:81) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.MainWindow.func_211162_a(MainWindow.java:178) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat com.mojang.blaze3d.platform.GLX._initGlfw(GLX.java:79) ~[?:?] {re:classloading,xf:OptiFine:default}\n\tat com.mojang.blaze3d.systems.RenderSystem.initBackendSystem(SourceFile:1060) ~[?:?] {re:classloading}\n\tat net.minecraft.client.Minecraft.<init>(Minecraft.java:416) ~[?:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.main.Main.main(Main.java:149) ~[1.16.5guangying.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_381] {}\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_381] {}\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_381] {}\n\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_381] {}\n\tat net.minecraftforge.fml.loading.FMLClientLaunchProvider.lambda$launchService$0(FMLClientLaunchProvider.java:37) ~[forge-1.16.5-36.2.41.jar:36.2] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) [modlauncher-8.1.3.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:54) [modlauncher-8.1.3.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:72) [modlauncher-8.1.3.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:82) [modlauncher-8.1.3.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66) [modlauncher-8.1.3.jar:?] {}\n\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.16.5\n\tMinecraft Version ID: 1.16.5\n\tOperating System: Mac OS X (x86_64) version 14.1.2\n\tJava Version: 1.8.0_381, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 2220089736 bytes (2117 MB) / 3355443200 bytes (3200 MB) up to 4294967296 bytes (4096 MB)\n\tCPUs: 8\n\tJVM Flags: 11 total; -Xmx4069m -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -XX:+IgnoreUnrecognizedVMOptions\n\tLaunched Version: 1.16.5guangying\n\tBackend library: LWJGL version 3.2.1 build 12\n\tBackend API: NO CONTEXT\n\tGL Caps: \n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'forge'\n\tType: Client (map_client.txt)\n\tCPU: <unknown>\n\tOptiFine Version: OptiFine_1.16.5_HD_U_G8\n\tOptiFine Build: 20210515-161946\n\tRender Distance Chunks: 8\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: null\n\tOpenGlRenderer: null\n\tOpenGlVendor: null\n\tCpuCount: 8\n[16:28:27] [Render thread/INFO]: [net.minecraft.util.registry.Bootstrap:func_179870_a:123]: #@!@# Game crashed! Crash report saved to: #@!@# /Users/biwenbo/Library/Application Support/minecraft/versions/1.16.5guangying/crash-reports/crash-2024-02-01_16.28.27-client.txt\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/memory_exceeded.txt",
    "content": "[19:39:13] [main/INFO]: Loading tweak class name optifine.OptiFineTweaker\n[19:39:13] [main/INFO]: Using primary tweak class name optifine.OptiFineTweaker\n[19:39:13] [main/INFO]: Calling tweak class optifine.OptiFineTweaker\nOptiFineTweaker: acceptOptions\nOptiFineTweaker: injectIntoClassLoader\nOptiFine ClassTransformer\nOptiFine ZIP file: D:\\我的世界\\minecraft\\.minecraft\\libraries\\optifine\\OptiFine\\1.16.4_HD_U_G7\\OptiFine-1.16.4_HD_U_G7.jar\nOptiFineTweaker: getLaunchArguments\nOptiFineTweaker: getLaunchTarget\n[19:39:13] [main/INFO]: Launching wrapped minecraft {net.minecraft.client.main.Main}\n[19:39:14] [main/INFO]: [OptiFine] (Reflector) Field not found: World.tileEntitiesToBeRemoved\n[19:39:14] [main/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.eventbus.api.Event$Result\n[19:39:14] [main/INFO]: [OptiFine] (Reflector) Method not present: net.minecraftforge.common.extensions.IForgeBlockState.hasTileEntity\n[19:39:14] [main/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.common.extensions.IForgeEntity\n[19:39:14] [main/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.fml.CrashReportExtender\n[19:39:19] [main/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.client.ForgeHooksClient\n[19:39:20] [main/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD'\nJava HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000007b4000000, 50331648, 0) failed; error='页面文件太小，无法完成操作。' (DOS error/errno=1455)\n#\n# There is insufficient memory for the Java Runtime Environment to continue.\n# Native memory allocation (mmap) failed to map 50331648 bytes for Failed to commit area from 0x00000007b4000000 to 0x00000007b7000000 of length 50331648.\n# An error report file with more information is saved as:\n# D:\\我的世界\\minecraft\\.minecraft\\hs_err_pid1224.log\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/mixin_apply_mod_failed.txt",
    "content": "\tat org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\nCaused by: java.net.SocketTimeoutException: Read timed out\r\n\tat sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283) ~[?:?]\r\n\tat sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309) ~[?:?]\r\n\tat sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350) ~[?:?]\r\n\tat sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803) ~[?:?]\r\n\tat java.net.Socket$SocketInputStream.read(Socket.java:966) ~[?:?]\r\n\tat java.io.BufferedInputStream.fill(BufferedInputStream.java:244) ~[?:?]\r\n\tat java.io.BufferedInputStream.read1(BufferedInputStream.java:284) ~[?:?]\r\n\tat java.io.BufferedInputStream.read(BufferedInputStream.java:343) ~[?:?]\r\n\tat sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:791) ~[?:?]\r\n\tat sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:726) ~[?:?]\r\n\tat sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1688) ~[?:?]\r\n\tat sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589) ~[?:?]\r\n\tat sun.net.www.protocol.http.HttpURLConnection.getHeaderField(HttpURLConnection.java:3221) ~[?:?]\r\n\tat java.net.URLConnection.getHeaderFieldLong(URLConnection.java:637) ~[?:?]\r\n\tat java.net.URLConnection.getContentLengthLong(URLConnection.java:509) ~[?:?]\r\n\tat xaero.map.misc.Internet.checkModVersion(Internet.java:51) ~[transformed-mod-xaeroworldmap.i0:0/:?]\r\n\t... 17 more\r\n[12:16:23] [Render thread/INFO]: Loading Xaero's Minimap - Stage 1/2\r\n[12:16:23] [Render thread/WARN]: Static binding violation: PRIVATE @Overwrite method method_23182 in #sodium:sodium.mixins.json:features.item.MixinItemRenderer from mod sodium cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded.\r\n[12:16:23] [Render thread/INFO]: [Quilt Command|Client] Networking support is enabled\r\n[12:16:23] [Render thread/INFO]: ARRP register - before vanilla\r\n[12:16:23] [Render thread/INFO]: ARRP register - after vanilla\r\n[12:16:23] [Render thread/INFO]: Backend library: LWJGL version 3.3.1 build 7\r\n[12:16:24] [Render thread/INFO]: ARRP register - before vanilla\r\n[12:16:24] [Render thread/INFO]: ARRP register - after vanilla\r\n[12:16:24] [Render thread/INFO]: ARRP register - before vanilla\r\n[12:16:24] [Render thread/INFO]: ARRP register - after vanilla\r\n[12:16:24] [Render thread/INFO]: ARRP register - before vanilla\r\n[12:16:24] [Render thread/INFO]: ARRP register - after vanilla\r\n[12:16:24] [Render thread/INFO]: ARRP register - before user\r\n[12:16:24] [Render thread/WARN]: Error loading class: net/minecraft/class_809$class_811 (java.lang.ClassNotFoundException: net/minecraft/class_809$class_811)\r\n[12:16:24] [Render thread/ERROR]: Mixin apply for mod enhancedblockentities failed #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> net.minecraft.class_756: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]\r\norg.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:510) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:453) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[?:?]\r\n\tat net.minecraft.class_310.<init>(class_310.java:573) ~[transformed-mod-minecraft.i0:0/:?]\r\n\tat net.minecraft.client.main.Main.main(Main.java:198) ~[1.19.jar:?]\r\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]\r\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]\r\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]\r\n\tat java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]\r\n\tat org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\nCaused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: net.minecraft.class_809$class_811\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:1002) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:962) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformLVT(MixinTargetContext.java:563) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:469) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\t... 24 more\r\n[12:16:24] [Render thread/WARN]: Mixin transformation of net.minecraft.class_756 failed\r\norg.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:392) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:453) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[?:?]\r\n\tat net.minecraft.class_310.<init>(class_310.java:573) ~[transformed-mod-minecraft.i0:0/:?]\r\n\tat net.minecraft.client.main.Main.main(Main.java:198) ~[1.19.jar:?]\r\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]\r\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]\r\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]\r\n\tat java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]\r\n\tat org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28) ~[quilt-loader-0.19.0-beta.11.jar:?]\r\nCaused by: org.spongepowered.asm.mixin.throwables.MixinApplyError: Mixin [#enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities] from phase [DEFAULT] in config [#enhancedblockentities:enhancedblockentities.mixins.json] FAILED during APPLY\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinError(MixinProcessor.java:638) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinApplyError(MixinProcessor.java:589) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:379) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\t... 17 more\r\nCaused by: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:510) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\t... 17 more\r\nCaused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: net.minecraft.class_809$class_811\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:1002) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:962) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformLVT(MixinTargetContext.java:563) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:469) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]\r\n\t... 17 more\r\n---- Minecraft Crash Report ----\r\n// Hi. I'm Minecraft, and I'm a crashaholic.\r\n\r\nTime: 2023-04-16 12:16:24\r\nDescription: Initializing game\r\n\r\njava.lang.RuntimeException: Mixin transformation of net.minecraft.class_756 failed\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:458)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228)\r\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)\r\n\tat net.minecraft.class_310.<init>(class_310.java:573)\r\n\tat net.minecraft.client.main.Main.main(Main.java:198)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\r\n\tat org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527)\r\n\tat org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28)\r\nCaused by: org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:392)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:453)\r\n\t... 14 more\r\nCaused by: org.spongepowered.asm.mixin.throwables.MixinApplyError: Mixin [#enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities] from phase [DEFAULT] in config [#enhancedblockentities:enhancedblockentities.mixins.json] FAILED during APPLY\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinError(MixinProcessor.java:638)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinApplyError(MixinProcessor.java:589)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:379)\r\n\t... 17 more\r\nCaused by: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:510)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327)\r\n\tat org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421)\r\n\tat org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363)\r\n\t... 17 more\r\nCaused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: net.minecraft.class_809$class_811\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:1002)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:962)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformLVT(MixinTargetContext.java:563)\r\n\tat org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:469)\r\n\t... 24 more\r\n\r\n\r\nA detailed walkthrough of the error, its code path and all known details is as follows:\r\n---------------------------------------------------------------------------------------\r\n\r\n-- Head --\r\nThread: Render thread\r\nStacktrace:\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:458)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228)\r\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)\r\n\tat net.minecraft.class_310.<init>(class_310.java:573)\r\n\r\n-- Initialization --\r\nDetails:\r\n\tModules: \r\n\t\tADVAPI32.dll:Advanced Windows 32 Base API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tCOMCTL32.dll:User Experience Controls Library:6.10 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tCRYPT32.dll:Crypto API32:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tCRYPTBASE.dll:Base cryptographic API DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tCRYPTSP.dll:Cryptographic Service Provider API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tCoreMessaging.dll:Microsoft CoreMessaging Dll:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tDBGHELP.DLL:Windows Image Helper:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tDEVOBJ.dll:Device Information Set DLL:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tDNSAPI.dll:DNS Client API DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tGDI32.dll:GDI Client DLL:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tGLU32.dll:OpenGL Utility Library DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tIMM32.DLL:Multi-User Windows IMM32 API Client DLL:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tIPHLPAPI.DLL:IP Helper API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tKERNEL32.DLL:Windows NT BASE API Client DLL:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tKERNELBASE.dll:Windows NT BASE API Client DLL:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tMMDevApi.dll:MMDevice API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tMSCTF.dll:MSCTF Server DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tMpOav.dll:IOfficeAntiVirus Module:4.18.2303.8 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tNSI.dll:NSI User-mode interface DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tNTASN1.dll:Microsoft ASN.1 API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tOLEAUT32.dll:OLEAUT32.DLL:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tOle32.dll:Microsoft OLE for Windows:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tOpenAL.dll:Main implementation library:1.21.1:\r\n\t\tPSAPI.DLL:Process Status Helper:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tPdh.dll:Windows Performance Data Helper DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tRPCRT4.dll:Remote Procedure Call Runtime:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tSETUPAPI.dll:Windows Setup API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tSHCORE.dll:SHCORE:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tSHELL32.dll:Windows Shell Common Dll:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tUMPDC.dll:User Mode Power Dependency Coordinator:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tUSER32.dll:Multi-User Windows USER API Client DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tUSERENV.dll:Userenv:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tVCRUNTIME140.dll:Microsoft® C Runtime Library:14.29.30133.0 built by: vcwrkspc:Microsoft Corporation\r\n\t\tVERSION.dll:Version Checking and File Installation Libraries:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tWINHTTP.dll:Windows HTTP Services:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tWINMM.dll:MCI API DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tWINSTA.dll:Winstation Library:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tWINTRUST.dll:Microsoft Trust Verification APIs:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tWS2_32.dll:Windows Socket 2.0 32-Bit DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tWSOCK32.dll:Windows Socket 32-Bit DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tWTSAPI32.dll:Windows Remote Desktop Session Host Server SDK APIs:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tamsi.dll:Anti-Malware Scan Interface:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tbcrypt.dll:Windows Cryptographic Primitives Library:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tbcryptPrimitives.dll:Windows Cryptographic Primitives Library:10.0.22621.1175 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tcfgmgr32.dll:Configuration Manager DLL:10.0.22621.746 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tclbcatq.dll:COM+ Configuration Catalog:2001.12.10941.16384 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tcombase.dll:Microsoft COM for Windows:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tcryptnet.dll:Crypto Network Related API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tdbgcore.DLL:Windows Core Debugging Helpers:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tdhcpcsvc.DLL:DHCP Client Service:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tdhcpcsvc6.DLL:DHCPv6 Client:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tdinput8.dll:Microsoft DirectInput:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tdrvstore.dll:Driver Store API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tdwmapi.dll:Microsoft Desktop Window Manager API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tdxcore.dll:DXCore:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tfwpuclnt.dll:FWP/IPsec User-Mode API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tgdi32full.dll:GDI Client DLL:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tglfw.dll:GLFW 3.4.0 DLL:3.4.0:GLFW\r\n\t\ticm32.dll:Microsoft Color Management Module (CMM):10.0.22621.586 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tiertutil.dll:Run time utility for Internet Explorer:11.00.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tinputhost.dll:InputHost:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tjava.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\tjava.exe:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\tjemalloc.dll\r\n\t\tjimage.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\tjli.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\tjna4873420272456839116.dll:JNA native library:6.1.4:Java(TM) Native Access (JNA)\r\n\t\tjvm.dll:OpenJDK 64-Bit server VM:17.0.1.0:Microsoft\r\n\t\tkernel.appcore.dll:AppModel API Host:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tlwjgl.dll\r\n\t\tlwjgl_opengl.dll\r\n\t\tlwjgl_stb.dll\r\n\t\tmanagement.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\tmanagement_ext.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\tmsasn1.dll:ASN.1 Runtime APIs:10.0.22621.891 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tmscms.dll:Microsoft Color Matching System DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tmsvcp140.dll:Microsoft® C Runtime Library:14.29.30133.0 built by: vcwrkspc:Microsoft Corporation\r\n\t\tmsvcp_win.dll:Microsoft® C Runtime Library:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tmsvcrt.dll:Windows NT CRT DLL:7.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tmswsock.dll:Microsoft Windows Sockets 2.0 Service Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tnapinsp.dll:E-mail Naming Shim Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tncrypt.dll:Windows NCrypt Router:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tnet.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\tnetutils.dll:Net Win32 API Helpers DLL:10.0.22621.870 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tnio.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\tnlansp_c.dll:NLA Namespace Service Provider DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tntdll.dll:NT Layer DLL:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tntmarta.dll:Windows NT MARTA provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tnvoglv64.dll:NVIDIA Compatible OpenGL ICD:31.0.15.2802:NVIDIA Corporation\r\n\t\tnvspcap64.dll:NVIDIA Game Proxy:3.26.0.160:NVIDIA Corporation\r\n\t\topengl32.dll:OpenGL Client DLL:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tperfos.dll:Windows System Performance Objects DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tpfclient.dll:SysMain Client:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tpnrpnsp.dll:PNRP Name Space Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tpowrprof.dll:Power Profile Helper DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tprofapi.dll:User Profile Basic API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\trasadhlp.dll:Remote Access AutoDial Helper:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\trsaenh.dll:Microsoft Enhanced Cryptographic Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tsechost.dll:Host for SCM/SDDL/LSA Lookup APIs:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tshlwapi.dll:Shell Light-weight Utility Library:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tsrvcli.dll:Server Service Client DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tsunmscapi.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\tsvml.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\ttextinputframework.dll:\"TextInputFramework.DYNLINK\":10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tucrtbase.dll:Microsoft® C Runtime Library:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\turlmon.dll:OLE32 Extensions for Win32:11.00.22621.746 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tuxtheme.dll:Microsoft UxTheme Library:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tvcruntime140_1.dll:Microsoft® C Runtime Library:14.29.30133.0 built by: vcwrkspc:Microsoft Corporation\r\n\t\tverify.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\n\t\twin32u.dll:Win32u:10.0.22621.1470 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\twindows.storage.dll:Microsoft WinRT Storage API:10.0.22621.580 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\twinrnr.dll:LDAP RnR Provider DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\twintypes.dll:Windows Base Types DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\twldp.dll:Windows Lockdown Policy:10.0.22621.885 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\twshbth.dll:Windows Sockets Helper DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\txinput1_4.dll:Microsoft Common Controller API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation\r\n\t\tzip.dll:OpenJDK Platform binary:17.0.1.0:Microsoft\r\nStacktrace:\r\n\tat net.minecraft.client.main.Main.main(Main.java:198)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\r\n\tat org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527)\r\n\tat org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82)\r\n\tat org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28)\r\n\r\n-- System Details --\r\nDetails:\r\n\tMinecraft Version: 1.19.4\r\n\tMinecraft Version ID: 1.19.4\r\n\tOperating System: Windows 11 (amd64) version 10.0\r\n\tJava Version: 17.0.1, Microsoft\r\n\tJava VM Version: OpenJDK 64-Bit Server VM (mixed mode), Microsoft\r\n\tMemory: 627025632 bytes (597 MiB) / 1577058304 bytes (1504 MiB) up to 6845104128 bytes (6528 MiB)\r\n\tCPUs: 12\r\n\tProcessor Vendor: AuthenticAMD\r\n\tProcessor Name: AMD Ryzen 5 3600 6-Core Processor              \r\n\tIdentifier: AuthenticAMD Family 23 Model 113 Stepping 0\r\n\tMicroarchitecture: Zen 2\r\n\tFrequency (GHz): 3.60\r\n\tNumber of physical packages: 1\r\n\tNumber of physical CPUs: 6\r\n\tNumber of logical CPUs: 12\r\n\tGraphics card #0 name: NVIDIA GeForce RTX 2060\r\n\tGraphics card #0 vendor: NVIDIA (0x10de)\r\n\tGraphics card #0 VRAM (MB): 4095.00\r\n\tGraphics card #0 deviceId: 0x1f08\r\n\tGraphics card #0 versionInfo: DriverVersion=31.0.15.2802\r\n\tMemory slot #0 capacity (MB): 8192.00\r\n\tMemory slot #0 clockSpeed (GHz): 3.00\r\n\tMemory slot #0 type: DDR4\r\n\tMemory slot #1 capacity (MB): 8192.00\r\n\tMemory slot #1 clockSpeed (GHz): 3.00\r\n\tMemory slot #1 type: DDR4\r\n\tVirtual memory max (MB): 29133.06\r\n\tVirtual memory used (MB): 20495.20\r\n\tSwap memory total (MB): 12800.00\r\n\tSwap memory used (MB): 833.95\r\n\tJVM Flags: 11 total; -Xmx6505m -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\r\n\tQuilt Mods: \r\n\t\t| Index | Mod                                               | ID                                                    | Version                     | Plugin                 | File(s)                                                                                                               | Sub-Files                                                                           | Sub-Files                            |\r\n\t\t|------:|---------------------------------------------------|-------------------------------------------------------|-----------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------------------------|\r\n\t\t|   123 | Advanced XRay (Fabric)                            | advanced-xray-fabric                                  | 1.19.4-1.4.0-build.15       | quilted_fabric_loader  | <mods>\\advanced-xray-fabric-1.19.4-1.4.0-build.15.jar                                                                 |                                                                                     |                                      |\r\n\t\t|    45 | AppleSkin                                         | appleskin                                             | 2.4.3+mc1.19.4              | quilted_fabric_loader  | <mods>\\appleskin-fabric-mc1.19.4-2.4.3.jar                                                                            |                                                                                     |                                      |\r\n\t\t|    32 | Autofish                                          | autofish                                              | 0.9.8-SNAPSHOT              | quilted_fabric_loader  | <mods>\\Autofish-0.9.8-fabric-mc1.19.3.jar                                                                             |                                                                                     |                                      |\r\n\t\t|    19 | Baritone                                          | baritone                                              | 1.5.3-830-g3e249073         | quilted_fabric_loader  | <mods>\\baritone-api-fabric-1.5.3-830-g3e249073.jar                                                                    |                                                                                     |                                      |\r\n\t\t|     8 | CaffeineConfig                                    | caffeineconfig                                        | 1.1.0+1.17                  | quilted_fabric_loader  | <mods>\\sodium-extra-0.4.18+mc1.19.4-build.100.jar                                                                     | /META-INF/jars/CaffeineConfig-1.1.0+1.17.jar                                        |                                      |\r\n\t\t|    39 | Cloth Config v9                                   | cloth-config                                          | 10.0.95                     | quilted_fabric_loader  | <mods>\\appleskin-fabric-mc1.19.4-2.4.3.jar                                                                            | /META-INF/jars/cloth-config-fabric-10.0.95.jar                                      |                                      |\r\n\t\t|    33 | CrowdinTranslate                                  | crowdin-translate                                     | 1.4+1.19.3                  | quilted_fabric_loader  | <mods>\\sodium-extra-0.4.18+mc1.19.4-build.100.jar                                                                     | /META-INF/jars/crowdin-translate-1.4+1.19.3.jar                                     |                                      |\r\n\t\t|    29 | Dynamic FPS                                       | dynamicfps                                            | 2.2.0                       | quilted_fabric_loader  | <mods>\\dynamic-fps-2.2.0.jar                                                                                          |                                                                                     |                                      |\r\n\t\t|    26 | Enhanced Block Entities                           | enhancedblockentities                                 | 0.8+1.19.3                  | quilted_fabric_loader  | <mods>\\enhancedblockentities-0.8+1.19.3.jar                                                                           |                                                                                     |                                      |\r\n\t\t|    14 | EntityCulling-Fabric                              | entityculling                                         | 1.6.2-mc1.19.4              | quilted_fabric_loader  | <mods>\\entityculling-fabric-1.6.2-mc1.19.4.jar                                                                        |                                                                                     |                                      |\r\n\t\t|    81 | Fabric Language Kotlin                            | fabric-language-kotlin                                | 1.9.3+kotlin.1.8.20         | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 |                                                                                     |                                      |\r\n\t\t|    28 | Farsight Mod                                      | farsight                                              | 1.19-3.4                    | quilted_fabric_loader  | <mods>\\farsight-fabric-1.19-3.4.jar                                                                                   |                                                                                     |                                      |\r\n\t\t|   105 | Inventory Profiles Next                           | inventoryprofilesnext                                 | 1.9.5                       | quilted_fabric_loader  | <mods>\\InventoryProfilesNext-fabric-1.19.4-1.9.5.jar                                                                  |                                                                                     |                                      |\r\n\t\t|    13 | Litematica                                        | litematica                                            | 0.14.2                      | quilted_fabric_loader  | <mods>\\litematica-fabric-1.19.4-0.14.2.jar                                                                            |                                                                                     |                                      |\r\n\t\t|   103 | Lithium                                           | lithium                                               | 0.11.1                      | quilted_fabric_loader  | <mods>\\lithium-fabric-mc1.19.4-0.11.1.jar                                                                             |                                                                                     |                                      |\r\n\t\t|     3 | MaLiLib                                           | malilib                                               | 0.15.3                      | quilted_fabric_loader  | <mods>\\malilib-fabric-1.19.4-0.15.3.jar                                                                               |                                                                                     |                                      |\r\n\t\t|   126 | Minecraft                                         | minecraft                                             | 1.19.4                      | quilt_loader           | <game>\\.cache\\quilt_loader\\remappedJars\\minecraft-1.19.4-0.19.0-beta.11\\client-intermediary.jar                       |                                                                                     |                                      |\r\n\t\t|    20 | MiniHUD                                           | minihud                                               | 0.26.2                      | quilted_fabric_loader  | <mods>\\minihud-fabric-1.19.4-0.26.2.jar                                                                               |                                                                                     |                                      |\r\n\t\t|    99 | MobHealthBar                                      | mobhealthbar                                          | 2.1.2                       | quilted_fabric_loader  | <mods>\\mobhealthbar-fabric-1.19.3-2.1.2.jar                                                                           |                                                                                     |                                      |\r\n\t\t|    65 | More Culling                                      | moreculling                                           | 1.19.4-0.17.0               | quilted_fabric_loader  | <mods>\\moreculling-1.19.4-0.17.0.jar                                                                                  |                                                                                     |                                      |\r\n\t\t|     5 | MoreChatHistory                                   | morechathistory                                       | 1.1.1                       | quilted_fabric_loader  | <mods>\\morechathistory-1.19.1-1.1.1.jar                                                                               |                                                                                     |                                      |\r\n\t\t|    62 | OpenJDK 64-Bit Server VM                          | java                                                  | 17                          | quilt_loader           | <user>\\AppData\\Roaming\\.minecraft\\cache\\java\\java-runtime-beta\\windows-x64\\java-runtime-beta                          |                                                                                     |                                      |\r\n\t\t|   133 | Quilt Advancement API                             | quilt_advancement                                     | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/advancement-5.0.0-beta.2+1.19.4.jar                                  |                                      |\r\n\t\t|    35 | Quilt Base API                                    | quilt_base                                            | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/qsl_base-5.0.0-beta.2+1.19.4.jar                                     |                                      |\r\n\t\t|    92 | Quilt Biome API                                   | quilt_biome                                           | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/biome-5.0.0-beta.2+1.19.4.jar                                        |                                      |\r\n\t\t|   102 | Quilt Block Content Registry API                  | quilt_block_content_registry                          | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/block_content_registry-5.0.0-beta.2+1.19.4.jar                       |                                      |\r\n\t\t|    71 | Quilt Block Entity API                            | quilt_block_entity                                    | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/block_entity-5.0.0-beta.2+1.19.4.jar                                 |                                      |\r\n\t\t|    75 | Quilt Block Extensions API                        | quilt_block_extensions                                | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/block_extensions-5.0.0-beta.2+1.19.4.jar                             |                                      |\r\n\t\t|   119 | Quilt Chat API                                    | quilt_chat                                            | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/chat-5.0.0-beta.2+1.19.4.jar                                         |                                      |\r\n\t\t|    84 | Quilt Client Command API                          | quilt_client_command                                  | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/client_command-5.0.0-beta.2+1.19.4.jar                               |                                      |\r\n\t\t|   111 | Quilt Command API                                 | quilt_command                                         | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/command-5.0.0-beta.2+1.19.4.jar                                      |                                      |\r\n\t\t|    76 | Quilt Crash Info                                  | quilt_crash_info                                      | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/crash_info-5.0.0-beta.2+1.19.4.jar                                   |                                      |\r\n\t\t|    86 | Quilt Dimension API                               | quilt_dimension                                       | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/dimension-5.0.0-beta.2+1.19.4.jar                                    |                                      |\r\n\t\t|    68 | Quilt Entity API                                  | quilt_entity                                          | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/entity-5.0.0-beta.2+1.19.4.jar                                       |                                      |\r\n\t\t|    46 | Quilt Entity Events                               | quilt_entity_events                                   | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/entity_events-5.0.0-beta.2+1.19.4.jar                                |                                      |\r\n\t\t|    52 | Quilt Entity Networking API                       | quilt_entity_networking                               | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/entity_networking-5.0.0-beta.2+1.19.4.jar                            |                                      |\r\n\t\t|   117 | Quilt Entity Rendering API                        | quilt_entity_rendering                                | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/entity_rendering-5.0.0-beta.2+1.19.4.jar                             |                                      |\r\n\t\t|    63 | Quilt Item Content Registry API                   | quilt_item_content_registry                           | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/item_content_registry-5.0.0-beta.2+1.19.4.jar                        |                                      |\r\n\t\t|    88 | Quilt Item Extensions API                         | quilt_item_extensions                                 | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/item_extensions-5.0.0-beta.2+1.19.4.jar                              |                                      |\r\n\t\t|   122 | Quilt Item Setting API                            | quilt_item_setting                                    | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/item_setting-5.0.0-beta.2+1.19.4.jar                                 |                                      |\r\n\t\t|    51 | Quilt Lifecycle Events                            | quilt_lifecycle_events                                | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/lifecycle_events-5.0.0-beta.2+1.19.4.jar                             |                                      |\r\n\t\t|   125 | Quilt Loader                                      | quilt_loader                                          | 0.19.0-beta.11              | quilt_loader           | <user>\\OneDrive\\Desktop\\.minecraft\\libraries\\org\\quiltmc\\quilt-loader\\0.19.0-beta.11\\quilt-loader-0.19.0-beta.11.jar  |                                                                                     |                                      |\r\n\t\t|    97 | Quilt Multipart Entity API                        | quilt_entity_multipart                                | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/multipart-5.0.0-beta.2+1.19.4.jar                                    |                                      |\r\n\t\t|    50 | Quilt Networking                                  | quilt_networking                                      | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/networking-5.0.0-beta.2+1.19.4.jar                                   |                                      |\r\n\t\t|    40 | Quilt Point of Interest API                       | quilt_point_of_interest                               | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/point_of_interest-5.0.0-beta.2+1.19.4.jar                            |                                      |\r\n\t\t|    94 | Quilt Recipe API                                  | quilt_recipe                                          | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/recipe-5.0.0-beta.2+1.19.4.jar                                       |                                      |\r\n\t\t|    89 | Quilt Registry API                                | quilt_registry                                        | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/registry-5.0.0-beta.2+1.19.4.jar                                     |                                      |\r\n\t\t|    60 | Quilt Registry Entry Attachment                   | quilt_registry_entry_attachment                       | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/registry_entry_attachment-5.0.0-beta.2+1.19.4.jar                    |                                      |\r\n\t\t|    48 | Quilt Resource Loader                             | quilt_resource_loader                                 | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/resource_loader-5.0.0-beta.2+1.19.4.jar                              |                                      |\r\n\t\t|    90 | Quilt Screen API                                  | quilt_screen                                          | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/screen-5.0.0-beta.2+1.19.4.jar                                       |                                      |\r\n\t\t|    82 | Quilt Status Effect API                           | quilt_status_effect                                   | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/status_effect-5.0.0-beta.2+1.19.4.jar                                |                                      |\r\n\t\t|    96 | Quilt Surface Rule API                            | quilt_surface_rule                                    | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/surface_rule-5.0.0-beta.2+1.19.4.jar                                 |                                      |\r\n\t\t|   136 | Quilt Tags API                                    | quilt_tags                                            | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/tags-5.0.0-beta.2+1.19.4.jar                                         |                                      |\r\n\t\t|    54 | Quilt Testing API                                 | quilt_testing                                         | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/testing-5.0.0-beta.2+1.19.4.jar                                      |                                      |\r\n\t\t|   124 | Quilt Tooltip API                                 | quilt_tooltip                                         | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/tooltip-5.0.0-beta.2+1.19.4.jar                                      |                                      |\r\n\t\t|   131 | Quilt Vehicle API                                 | quilt_vehicle                                         | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/vehicle-5.0.0-beta.2+1.19.4.jar                                      |                                      |\r\n\t\t|   104 | Quilt Villager API                                | quilt_villager                                        | 5.0.0-beta.2+1.19.4         | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/villager-5.0.0-beta.2+1.19.4.jar                                     |                                      |\r\n\t\t|   138 | Quilted Fabric API                                | quilted_fabric_api                                    | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  |                                                                                     |                                      |\r\n\t\t|    42 | Quilted Fabric API Base                           | quilted_fabric_api_base                               | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-api-base-6.0.0-beta.3+0.76.0-1.19.4.jar                       |                                      |\r\n\t\t|   130 | Quilted Fabric API Lookup API (v1)                | quilted_fabric_api_lookup_api_v1                      | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-api-lookup-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar              |                                      |\r\n\t\t|   100 | Quilted Fabric Biome API (v1)                     | quilted_fabric_biome_api_v1                           | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-biome-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                   |                                      |\r\n\t\t|    77 | Quilted Fabric Block API (v1)                     | quilted_fabric_block_api_v1                           | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-block-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                   |                                      |\r\n\t\t|   101 | Quilted Fabric BlockRenderLayer Registration (v1) | quilted_fabric_blockrenderlayer_v1                    | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-blockrenderlayer-v1-6.0.0-beta.3+0.76.0-1.19.4.jar            |                                      |\r\n\t\t|    55 | Quilted Fabric Client Tags                        | quilted_fabric_client_tags_api_v1                     | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-client-tags-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar             |                                      |\r\n\t\t|   129 | Quilted Fabric Command API (v1)                   | quilted_fabric_command_api_v1                         | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-command-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                 |                                      |\r\n\t\t|   121 | Quilted Fabric Command API (v2)                   | quilted_fabric_command_api_v2                         | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-command-api-v2-6.0.0-beta.3+0.76.0-1.19.4.jar                 |                                      |\r\n\t\t|    67 | Quilted Fabric Commands (v0)                      | quilted_fabric_commands_v0                            | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-commands-v0-6.0.0-beta.3+0.76.0-1.19.4.jar                    |                                      |\r\n\t\t|   112 | Quilted Fabric Containers (v0)                    | quilted_fabric_containers_v0                          | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-containers-v0-6.0.0-beta.3+0.76.0-1.19.4.jar                  |                                      |\r\n\t\t|    91 | Quilted Fabric Content Registries (v0)            | quilted_fabric_content_registries_v0                  | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-content-registries-v0-6.0.0-beta.3+0.76.0-1.19.4.jar          |                                      |\r\n\t\t|    47 | Quilted Fabric Convention Tags API (v1)           | quilted_fabric_convention_tags_api_v1                 | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-convention-tags-v1-6.0.0-beta.3+0.76.0-1.19.4.jar             |                                      |\r\n\t\t|   135 | Quilted Fabric Crash Report Info (v1)             | quilted_fabric_crash_report_info_v1                   | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-crash-report-info-v1-6.0.0-beta.3+0.76.0-1.19.4.jar           |                                      |\r\n\t\t|    37 | Quilted Fabric Data Generation API (v1)           | quilted_fabric_data_generation_api_v1                 | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-data-generation-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar         |                                      |\r\n\t\t|    74 | Quilted Fabric Dimensions API (v1)                | quilted_fabric_dimensions_v1                          | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-dimensions-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                  |                                      |\r\n\t\t|    79 | Quilted Fabric Entity Events (v1)                 | quilted_fabric_entity_events_v1                       | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-entity-events-v1-6.0.0-beta.3+0.76.0-1.19.4.jar               |                                      |\r\n\t\t|    43 | Quilted Fabric Events Interaction (v0)            | quilted_fabric_events_interaction_v0                  | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-events-interaction-v0-6.0.0-beta.3+0.76.0-1.19.4.jar          |                                      |\r\n\t\t|   108 | Quilted Fabric Events Lifecycle (v0)              | quilted_fabric_events_lifecycle_v0                    | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-events-lifecycle-v0-6.0.0-beta.3+0.76.0-1.19.4.jar            |                                      |\r\n\t\t|   109 | Quilted Fabric Game Rule API (v1)                 | quilted_fabric_game_rule_api_v1                       | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-game-rule-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar               |                                      |\r\n\t\t|    44 | Quilted Fabric Item API (v1)                      | quilted_fabric_item_api_v1                            | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-item-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                    |                                      |\r\n\t\t|   127 | Quilted Fabric Item Group API (v1)                | quilted_fabric_item_group_api_v1                      | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-item-group-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar              |                                      |\r\n\t\t|    80 | Quilted Fabric Key Binding API (v1)               | quilted_fabric_key_binding_api_v1                     | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-key-binding-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar             |                                      |\r\n\t\t|   132 | Quilted Fabric Key Bindings (v0)                  | quilted_fabric_keybindings_v0                         | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-keybindings-v0-6.0.0-beta.3+0.76.0-1.19.4.jar                 |                                      |\r\n\t\t|   110 | Quilted Fabric Lifecycle Events (v1)              | quilted_fabric_lifecycle_events_v1                    | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-lifecycle-events-v1-6.0.0-beta.3+0.76.0-1.19.4.jar            |                                      |\r\n\t\t|    49 | Quilted Fabric Loot API (v2)                      | quilted_fabric_loot_api_v2                            | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-loot-api-v2-6.0.0-beta.3+0.76.0-1.19.4.jar                    |                                      |\r\n\t\t|    78 | Quilted Fabric Loot Tables (v1)                   | quilted_fabric_loot_tables_v1                         | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-loot-tables-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                 |                                      |\r\n\t\t|    61 | Quilted Fabric Message API (v1)                   | quilted_fabric_message_api_v1                         | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-message-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                 |                                      |\r\n\t\t|    64 | Quilted Fabric Mining Level API (v1)              | quilted_fabric_mining_level_api_v1                    | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-mining-level-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar            |                                      |\r\n\t\t|    36 | Quilted Fabric Models (v0)                        | quilted_fabric_models_v0                              | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-models-v0-6.0.0-beta.3+0.76.0-1.19.4.jar                      |                                      |\r\n\t\t|   134 | Quilted Fabric Networking (v0)                    | quilted_fabric_networking_v0                          | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-networking-v0-6.0.0-beta.3+0.76.0-1.19.4.jar                  |                                      |\r\n\t\t|    38 | Quilted Fabric Networking API (v1)                | quilted_fabric_networking_api_v1                      | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-networking-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar              |                                      |\r\n\t\t|    87 | Quilted Fabric Object Builder API (v1)            | quilted_fabric_object_builder_api_v1                  | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-object-builder-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar          |                                      |\r\n\t\t|    59 | Quilted Fabric Particles (v1)                     | quilted_fabric_particles_v1                           | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-particles-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                   |                                      |\r\n\t\t|    69 | Quilted Fabric Recipe API (v1)                    | quilted_fabric_recipe_api_v1                          | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-recipe-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                  |                                      |\r\n\t\t|   115 | Quilted Fabric Registry Sync (v0)                 | quilted_fabric_registry_sync_v0                       | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-registry-sync-v0-6.0.0-beta.3+0.76.0-1.19.4.jar               |                                      |\r\n\t\t|    58 | Quilted Fabric Renderer - Indigo                  | quilted_fabric_renderer_indigo                        | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-renderer-indigo-6.0.0-beta.3+0.76.0-1.19.4.jar                |                                      |\r\n\t\t|    70 | Quilted Fabric Renderer API (v1)                  | quilted_fabric_renderer_api_v1                        | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-renderer-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                |                                      |\r\n\t\t|    85 | Quilted Fabric Renderer Registries (v1)           | quilted_fabric_renderer_registries_v1                 | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-renderer-registries-v1-6.0.0-beta.3+0.76.0-1.19.4.jar         |                                      |\r\n\t\t|   118 | Quilted Fabric Rendering (v0)                     | quilted_fabric_rendering_v0                           | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-rendering-v0-6.0.0-beta.3+0.76.0-1.19.4.jar                   |                                      |\r\n\t\t|    57 | Quilted Fabric Rendering (v1)                     | quilted_fabric_rendering_v1                           | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-rendering-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                   |                                      |\r\n\t\t|   116 | Quilted Fabric Rendering Data Attachment (v1)     | quilted_fabric_rendering_data_attachment_v1           | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-rendering-data-attachment-v1-6.0.0-beta.3+0.76.0-1.19.4.jar   |                                      |\r\n\t\t|   114 | Quilted Fabric Rendering Fluids (v1)              | quilted_fabric_rendering_fluids_v1                    | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-rendering-fluids-v1-6.0.0-beta.3+0.76.0-1.19.4.jar            |                                      |\r\n\t\t|    56 | Quilted Fabric Resource Conditions API (v1)       | quilted_fabric_resource_conditions_api_v1             | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-resource-conditions-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar     |                                      |\r\n\t\t|    95 | Quilted Fabric Resource Loader (v0)               | quilted_fabric_resource_loader_v0                     | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-resource-loader-v0-6.0.0-beta.3+0.76.0-1.19.4.jar             |                                      |\r\n\t\t|    66 | Quilted Fabric Screen API (v1)                    | quilted_fabric_screen_api_v1                          | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-screen-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                  |                                      |\r\n\t\t|   113 | Quilted Fabric Screen Handler API (v1)            | quilted_fabric_screen_handler_api_v1                  | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-screen-handler-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar          |                                      |\r\n\t\t|    53 | Quilted Fabric Sound API (v1)                     | quilted_fabric_sound_api_v1                           | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-sound-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                   |                                      |\r\n\t\t|   107 | Quilted Fabric Transfer API (v1)                  | quilted_fabric_transfer_api_v1                        | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-transfer-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar                |                                      |\r\n\t\t|    83 | Quilted Fabric Transitive Access Wideners (v1)    | quilted_fabric_transitive_access_wideners_v1          | 6.0.0-beta.3+0.76.0-1.19.4  | quilt_loader           | <mods>\\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar                                                  | /META-INF/jars/fabric-transitive-access-wideners-v1-6.0.0-beta.3+0.76.0-1.19.4.jar  |                                      |\r\n\t\t|    25 | Reese's Sodium Options                            | reeses-sodium-options                                 | 1.5.0+mc1.19.4-build.72     | quilted_fabric_loader  | <mods>\\reeses_sodium_options-1.5.0+mc1.19.4-build.72.jar                                                              |                                                                                     |                                      |\r\n\t\t|    22 | Runtime Resource Pack                             | advanced_runtime_resource_pack                        | 0.6.7                       | quilted_fabric_loader  | <mods>\\enhancedblockentities-0.8+1.19.3.jar                                                                           | /META-INF/jars/arrp-0.6.7.jar                                                       |                                      |\r\n\t\t|    73 | Settxi Gui (YetAnotherConfigLib)                  | settxi-gui-yacl                                       | 2.10.6                      | quilted_fabric_loader  | <mods>\\Zoomify-2.9.2.jar                                                                                              | /META-INF/jars/settxi-gui-yacl-2.10.6-fabric-1.19.3.jar                             |                                      |\r\n\t\t|    93 | Sodium                                            | sodium                                                | 0.4.10+build.24             | quilted_fabric_loader  | <mods>\\sodium-fabric-mc1.19.4-0.4.10+build.24.jar                                                                     |                                                                                     |                                      |\r\n\t\t|   128 | Sodium Extra                                      | sodium-extra                                          | 0.4.18+mc1.19.4-build.100   | quilted_fabric_loader  | <mods>\\sodium-extra-0.4.18+mc1.19.4-build.100.jar                                                                     |                                                                                     |                                      |\r\n\t\t|   137 | SpruceUI                                          | spruceui                                              | 4.1.0+1.19.3                | quilted_fabric_loader  | <mods>\\enhancedblockentities-0.8+1.19.3.jar                                                                           | /META-INF/jars/spruceui-4.1.0+1.19.3.jar                                            |                                      |\r\n\t\t|    30 | Tweakeroo                                         | tweakeroo                                             | 0.16.0                      | quilted_fabric_loader  | <mods>\\tweakeroo-fabric-1.19.4-0.16.0.jar                                                                             |                                                                                     |                                      |\r\n\t\t|   106 | Xaero's Minimap                                   | xaerominimap                                          | 23.3.3                      | quilted_fabric_loader  | <mods>\\Xaeros_Minimap_23.3.3_Fabric_1.19.4.jar                                                                        |                                                                                     |                                      |\r\n\t\t|    98 | Xaero's World Map                                 | xaeroworldmap                                         | 1.29.5                      | quilted_fabric_loader  | <mods>\\XaerosWorldMap_1.29.5_Fabric_1.19.4.jar                                                                        |                                                                                     |                                      |\r\n\t\t|   120 | YetAnotherConfigLib                               | yet-another-config-lib                                | 2.4.1                       | quilted_fabric_loader  | <mods>\\YetAnotherConfigLib-2.4.1.jar                                                                                  |                                                                                     |                                      |\r\n\t\t|    72 | Zoomify                                           | zoomify                                               | 2.9.2                       | quilted_fabric_loader  | <mods>\\Zoomify-2.9.2.jar                                                                                              |                                                                                     |                                      |\r\n\t\t|    18 | atomicfu-jvm                                      | org_jetbrains_kotlinx_atomicfu-jvm                    | 0.20.1                      | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/atomicfu-jvm-0.20.1.jar                                              |                                      |\r\n\t\t|    15 | cloth-basic-math                                  | cloth-basic-math                                      | 0.6.1                       | quilted_fabric_loader  | <mods>\\moreculling-1.19.4-0.17.0.jar                                                                                  | /META-INF/jars/cloth-config-fabric-9.0.94.jar                                       | /META-INF/jars/basic-math-0.6.1.jar  |\r\n\t\t|    34 | conditional mixin                                 | conditional-mixin                                     | 0.3.2                       | quilted_fabric_loader  | <mods>\\moreculling-1.19.4-0.17.0.jar                                                                                  | /META-INF/jars/conditional-mixin-v0.3.2.jar                                         |                                      |\r\n\t\t|    27 | jctools-core                                      | com_github_jctools_jctools_jctools-core               | v4.0.1                      | quilted_fabric_loader  | <mods>\\farsight-fabric-1.19-3.4.jar                                                                                   | /META-INF/jars/jctools-core-v4.0.1.jar                                              |                                      |\r\n\t\t|    24 | kotlin-reflect                                    | org_jetbrains_kotlin_kotlin-reflect                   | 1.8.20                      | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlin-reflect-1.8.20.jar                                            |                                      |\r\n\t\t|     6 | kotlin-stdlib                                     | org_jetbrains_kotlin_kotlin-stdlib                    | 1.8.20                      | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlin-stdlib-1.8.20.jar                                             |                                      |\r\n\t\t|     0 | kotlin-stdlib-jdk7                                | org_jetbrains_kotlin_kotlin-stdlib-jdk7               | 1.8.20                      | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlin-stdlib-jdk7-1.8.20.jar                                        |                                      |\r\n\t\t|     4 | kotlin-stdlib-jdk8                                | org_jetbrains_kotlin_kotlin-stdlib-jdk8               | 1.8.20                      | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlin-stdlib-jdk8-1.8.20.jar                                        |                                      |\r\n\t\t|    11 | kotlinx-coroutines-core-jvm                       | org_jetbrains_kotlinx_kotlinx-coroutines-core-jvm     | 1.6.4                       | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlinx-coroutines-core-jvm-1.6.4.jar                                |                                      |\r\n\t\t|    16 | kotlinx-coroutines-jdk8                           | org_jetbrains_kotlinx_kotlinx-coroutines-jdk8         | 1.6.4                       | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlinx-coroutines-jdk8-1.6.4.jar                                    |                                      |\r\n\t\t|     7 | kotlinx-datetime-jvm                              | org_jetbrains_kotlinx_kotlinx-datetime-jvm            | 0.4.0                       | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlinx-datetime-jvm-0.4.0.jar                                       |                                      |\r\n\t\t|    31 | kotlinx-serialization-cbor-jvm                    | org_jetbrains_kotlinx_kotlinx-serialization-cbor-jvm  | 1.5.0                       | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlinx-serialization-cbor-jvm-1.5.0.jar                             |                                      |\r\n\t\t|     1 | kotlinx-serialization-core-jvm                    | org_jetbrains_kotlinx_kotlinx-serialization-core-jvm  | 1.5.0                       | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlinx-serialization-core-jvm-1.5.0.jar                             |                                      |\r\n\t\t|    21 | kotlinx-serialization-json-jvm                    | org_jetbrains_kotlinx_kotlinx-serialization-json-jvm  | 1.5.0                       | quilted_fabric_loader  | <mods>\\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar                                                                 | /META-INF/jars/kotlinx-serialization-json-jvm-1.5.0.jar                             |                                      |\r\n\t\t|    17 | ktoml-core-jvm                                    | com_akuleshov7_ktoml-core-jvm                         | 0.3.0                       | quilted_fabric_loader  | <mods>\\Zoomify-2.9.2.jar                                                                                              | /META-INF/jars/ktoml-core-jvm-0.3.0.jar                                             |                                      |\r\n\t\t|    41 | libIPN                                            | libipn                                                | 2.0.4                       | quilted_fabric_loader  | <mods>\\libIPN-fabric-1.19.4-2.0.4.jar                                                                                 |                                                                                     |                                      |\r\n\t\t|     9 | mixinextras                                       | com_github_llamalad7_mixinextras                      | 0.1.1                       | quilted_fabric_loader  | <mods>\\Zoomify-2.9.2.jar                                                                                              | /META-INF/jars/mixinextras-0.1.1.jar                                                |                                      |\r\n\t\t|    23 | settxi-core                                       | dev_isxander_settxi_settxi-core                       | 2.10.6                      | quilted_fabric_loader  | <mods>\\Zoomify-2.9.2.jar                                                                                              | /META-INF/jars/settxi-core-2.10.6.jar                                               |                                      |\r\n\t\t|    10 | settxi-gui                                        | dev_isxander_settxi_settxi-gui                        | 2.10.6                      | quilted_fabric_loader  | <mods>\\Zoomify-2.9.2.jar                                                                                              | /META-INF/jars/settxi-gui-2.10.6.jar                                                |                                      |\r\n\t\t|    12 | settxi-kotlinx-serialization                      | dev_isxander_settxi_settxi-kotlinx-serialization      | 2.10.6                      | quilted_fabric_loader  | <mods>\\Zoomify-2.9.2.jar                                                                                              | /META-INF/jars/settxi-kotlinx-serialization-2.10.6.jar                              |                                      |\r\n\t\t|     2 | toml4j                                            | com_moandjiezana_toml_toml4j                          | 0.7.2                       | quilted_fabric_loader  | <mods>\\dynamic-fps-2.2.0.jar                                                                                          | /META-INF/jars/toml4j-0.7.2.jar                                                     |                                      |\r\n\t\t|------:|---------------------------------------------------|-------------------------------------------------------|-----------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------------------------|\r\n\t\t\r\n\tQuilted Fabric API: !! WARNING !! This instance is using Fabric API modules re-implemented by QSL. If the issue comes from Quilted Fabric API, DO NOT report to Fabric; report them to Quilt instead!\r\n\tLaunched Version: 1.19\r\n\tBackend library: LWJGL version 3.3.1 build 7\r\n\tBackend API: NVIDIA GeForce RTX 2060/PCIe/SSE2 GL version 3.2.0 NVIDIA 528.02, NVIDIA Corporation\r\n\tWindow size: <not initialized>\r\n\tGL Caps: Using framebuffer using OpenGL 3.2\r\n\tGL debug messages: \r\n\tUsing VBOs: Yes\r\n\tIs Modded: Definitely; Client brand changed to 'quilt'\r\n\tType: Client (map_client.txt)\r\n\tCPU: 12x AMD Ryzen 5 3600 6-Core Processor \r\n#@!@# Game crashed! Crash report saved to: #@!@# C:\\Users\\arthu\\OneDrive\\Desktop\\.minecraft\\versions\\1.19\\crash-reports\\crash-2023-04-16_12.16.24-client.txt"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/mod_name.txt",
    "content": "[23:16:40] [main/INFO]: ModLauncher running: args [--username, ygu, --version, 1.19.3, --gameDir, D:\\MC\\.minecraft, --assetsDir, D:\\MC\\.minecraft\\assets, --assetIndex, 2, --uuid, 28ae47c586363ebfae84cce7b2d47198, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, legacy, --versionType, HMCL 3.5.4, --width, 854, --height, 480, --launchTarget, forgeclient, --fml.forgeVersion, 44.1.23, --fml.mcVersion, 1.19.3, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20221207.122022]\n[23:16:40] [main/INFO]: ModLauncher 10.0.8+10.0.8+main.0ef7e830 starting: java version 17.0.6 by Microsoft; OS Windows 11 arch amd64 version 10.0\nException in thread \"main\" java.lang.IllegalArgumentException: : Invalid module name: '' is not a Java identifier\n\tat java.base/jdk.internal.module.Checks.requireModuleName(Checks.java:59)\n\tat java.base/java.lang.module.ModuleDescriptor$Builder.<init>(ModuleDescriptor.java:1515)\n\tat java.base/java.lang.module.ModuleDescriptor.newAutomaticModule(ModuleDescriptor.java:2395)\n\tat cpw.mods.securejarhandler/cpw.mods.jarhandling.impl.SimpleJarMetadata.descriptor(SimpleJarMetadata.java:13)\n\tat cpw.mods.securejarhandler/cpw.mods.jarhandling.impl.Jar.computeDescriptor(Jar.java:51)\n\tat cpw.mods.securejarhandler/cpw.mods.jarhandling.impl.Jar$JarModuleDataProvider.descriptor(Jar.java:276)\n\tat MC-BOOTSTRAP/fmlloader@1.19.3-44.1.23/net.minecraftforge.fml.loading.ModDirTransformerDiscoverer.visitFile(ModDirTransformerDiscoverer.java:62)\n\tat java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)\n\tat java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)\n\tat java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)\n\tat java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1845)\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)\n\tat java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)\n\tat java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)\n\tat java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)\n\tat MC-BOOTSTRAP/fmlloader@1.19.3-44.1.23/net.minecraftforge.fml.loading.ModDirTransformerDiscoverer.scan(ModDirTransformerDiscoverer.java:50)\n\tat MC-BOOTSTRAP/fmlloader@1.19.3-44.1.23/net.minecraftforge.fml.loading.ModDirTransformerDiscoverer.candidates(ModDirTransformerDiscoverer.java:33)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService.candidates(ITransformerDiscoveryService.java:48)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.TransformationServicesHandler.lambda$discoverServices$14(TransformationServicesHandler.java:125)\n\tat java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)\n\tat java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)\n\tat java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)\n\tat java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)\n\tat java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(TransformationServicesHandler.java:127)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.run(Launcher.java:86)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.main(Launcher.java:77)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141)"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/mod_resolution.txt",
    "content": "OpenJDK 64-Bit Server VM warning: Option --illegal-access is deprecated and will be removed in a future release.\n[06:34:05] [main/INFO]: Loading for game Minecraft 1.17.1\nException in thread \"main\" java.lang.RuntimeException: Failed to resolve mods!\n\tat net.fabricmc.loader.FabricLoader.load(FabricLoader.java:159)\n\tat net.fabricmc.loader.launch.knot.Knot.init(Knot.java:122)\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:26)\nCaused by: net.fabricmc.loader.discovery.ModResolutionException: Errors were found!\n - Mod test depends on mod {fabricloader @ [>=0.11.3]}, which is missing!\n - Mod test depends on mod {fabric @ [*]}, which is missing!\n - Mod test depends on mod {java @ [>=16]}, which is missing!\n\tat net.fabricmc.loader.discovery.ModResolver.findCompatibleSet(ModResolver.java:323)\n\tat net.fabricmc.loader.discovery.ModResolver.resolve(ModResolver.java:508)\n\tat net.fabricmc.loader.FabricLoader.load(FabricLoader.java:157)\n\t... 2 more\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/mod_resolution_collection.txt",
    "content": "[00:39:00] [main/INFO]: Loading for game Minecraft 1.17.1\nException in thread \"main\" java.lang.RuntimeException: Failed to resolve mods!\n\tat net.fabricmc.loader.FabricLoader.load(FabricLoader.java:159)\n\tat net.fabricmc.loader.launch.knot.Knot.init(Knot.java:122)\n\tat net.fabricmc.loader.launch.knot.KnotClient.main(KnotClient.java:26)\nCaused by: net.fabricmc.loader.discovery.ModResolutionException: Could not resolve valid mod collection (at: tabtps-fabric requires {fabricloader @ [>=0.11.1]})\n\tat net.fabricmc.loader.discovery.ModResolver.findCompatibleSet(ModResolver.java:184)\n\tat net.fabricmc.loader.discovery.ModResolver.resolve(ModResolver.java:508)\n\tat net.fabricmc.loader.FabricLoader.load(FabricLoader.java:157)\n\t... 2 more\nCaused by: net.fabricmc.loader.util.sat4j.specs.ContradictionException: Creating Empty clause ?\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.cnf.Clauses.propagationCheck(Clauses.java:117)\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.cnf.Clauses.sanityCheck(Clauses.java:97)\n\tat net.fabricmc.loader.util.sat4j.minisat.constraints.MixedDataStructureDanielWL.createClause(MixedDataStructureDanielWL.java:81)\n\tat net.fabricmc.loader.util.sat4j.minisat.core.Solver.addClause(Solver.java:401)\n\tat net.fabricmc.loader.discovery.ModResolver.findCompatibleSet(ModResolver.java:182)\n\t... 4 more\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/openj9-unsupported_charset.txt",
    "content": "线程 \"main\" 中发生异常java/lang/ExceptionInInitializerError\n        位于 java/lang/J9VMInternals.ensureError (java.base@9/J9VMInternals.java:191)\n        位于 java/lang/J9VMInternals.recordInitializationFailure (java.base@9/J9VMInternals.java:180)\n        位于 sun/nio/fs/WindowsFileSystemProvider.getFileAttributeView (java.base@9/WindowsFileSystemProvider.java:163)\n        位于 sun/nio/fs/WindowsFileSystemProvider.readAttributes (java.base@9/WindowsFileSystemProvider.java:194)\n        位于 sun/nio/fs/AbstractFileSystemProvider.isRegularFile (java.base@9/AbstractFileSystemProvider.java:137)\n        位于 java/nio/file/Files.isRegularFile (java.base@9/Files.java:2265)\n        位于 jdk/internal/loader/BuiltinClassLoader.access$100 (java.base@9/BuiltinClassLoader.java:109)\n        位于 jdk/internal/loader/BuiltinClassLoader$LoadedModule.convertJrtToFileURL (java.base@9/BuiltinClassLoader.java:296)\n        位于 jdk/internal/loader/BuiltinClassLoader$LoadedModule.<init> (java.base@9/BuiltinClassLoader.java:286)\n        位于 jdk/internal/loader/BuiltinClassLoader.loadModule (java.base@9/BuiltinClassLoader.java:354)\n        位于 jdk/internal/loader/BootLoader.loadModule (java.base@9/BootLoader.java:108)\n        位于 jdk/internal/module/ModuleBootstrap.boot (java.base@9/ModuleBootstrap.java:218)\n        位于 java/lang/ClassLoader.initializeClassLoaders (java.base@9/ClassLoader.java:217)\n        位于 java/lang/Thread.initialize (java.base@9/Thread.java:422)\n        位于 java/lang/Thread.<init> (java.base@9/Thread.java:153)\njava/nio/charset/UnsupportedCharsetException: GB18030\n        位于 java/nio/charset/Charset.forName (java.base@9/Charset.java:529)\n        位于 sun/nio/fs/Util.<clinit> (java.base@9/Util.java:40)\n        位于 sun/nio/fs/WindowsFileSystemProvider.getFileAttributeView (java.base@9/WindowsFileSystemProvider.java:163)\n        位于 sun/nio/fs/WindowsFileSystemProvider.readAttributes (java.base@9/WindowsFileSystemProvider.java:194)\n        位于 sun/nio/fs/AbstractFileSystemProvider.isRegularFile (java.base@9/AbstractFileSystemProvider.java:137)\n        位于 java/nio/file/Files.isRegularFile (java.base@9/Files.java:2265)\n        位于 jdk/internal/loader/BuiltinClassLoader.setJimageURL (java.base@9/BuiltinClassLoader.java:257)\n        位于 jdk/internal/loader/BuiltinClassLoader.access$100 (java.base@9/BuiltinClassLoader.java:109)\n        位于 jdk/internal/loader/BuiltinClassLoader$LoadedModule.convertJrtToFileURL (java.base@9/BuiltinClassLoader.java:296)\n        位于 jdk/internal/loader/BuiltinClassLoader$LoadedModule.<init> (java.base@9/BuiltinClassLoader.java:286)\n        位于 jdk/internal/loader/BuiltinClassLoader.loadModule (java.base@9/BuiltinClassLoader.java:354)\n        位于 jdk/internal/loader/BootLoader.loadModule (java.base@9/BootLoader.java:108)\n        位于 jdk/internal/module/ModuleBootstrap.boot (java.base@9/ModuleBootstrap.java:218)\n        位于 java/lang/ClassLoader.initializeClassLoaders (java.base@9/ClassLoader.java:217)\n        位于 java/lang/Thread.initialize (java.base@9/Thread.java:422)\n        位于 java/lang/Thread.<init> (java.base@9/Thread.java:153)"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/openj9.txt",
    "content": "You are attempting to run with an unsupported Java Virtual Machine : Eclipse OpenJ9\nPlease visit https://adoptopenjdk.net and install the HotSpot variant.\nOpenJ9 is incompatible with several of the transformation behaviours that we rely on to work.\nException in thread \"main\" java.lang.IllegalStateException: Open J9 is not supported\n\tat cpw.mods.modlauncher.ValidateLibraries.validate(ValidateLibraries.java:33)\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:63)"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/optifine_is_not_compatible_with_forge.txt",
    "content": "[17:39:15] [main/INFO]: ModLauncher running: args [--username, xjy, --version, 1.18.2（2), --gameDir, D:\\hmcl启动器\\.minecraft, --assetsDir, D:\\hmcl启动器\\.minecraft\\assets, --assetIndex, 1.18, --uuid, f879573aee333778962e2a175ad0c481, --accessToken, �7�6�7�6�7�6�7�6�7�6�7�6�7�6�7�6, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, legacy, --versionType, HMCL 3.5.dev-1ec9e41, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker, --launchTarget, forgeclient, --fml.forgeVersion, 40.1.59, --fml.mcVersion, 1.18.2, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20220404.173914]\n[17:39:15] [main/INFO]: ModLauncher 9.1.3+9.1.3+main.9b69c82a starting: java version 17.0.2 by BellSoft\n[17:39:15] [main/INFO]: OptiFineTransformationService.onLoad\n[17:39:15] [main/INFO]: OptiFine ZIP file URL: union:/D:/hmcl启动器/.minecraft/libraries/optifine/OptiFine/1.18.2_HD_U_H7/OptiFine-1.18.2_HD_U_H7.jar%2317!/\n[17:39:15] [main/INFO]: OptiFine ZIP file: D:\\hmcl启动器\\.minecraft\\libraries\\optifine\\OptiFine\\1.18.2_HD_U_H7\\OptiFine-1.18.2_HD_U_H7.jar\n[17:39:15] [main/INFO]: Target.PRE_CLASS is available\n[17:39:15] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/D:/hmcl启动器/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%2314!/ Service=ModLauncher Env=CLIENT\n[17:39:15] [main/INFO]: OptiFineTransformationService.initialize\n[17:39:15] [main/INFO]: Found mod file balm-3.2.0+0.jar of type MOD with locator {mods folder locator at D:\\hmcl启动器\\.minecraft\\mods}\n[17:39:15] [main/INFO]: Found mod file buildinggadgets-3.13.1-build.18+mc1.18.2.jar of type MOD with locator {mods folder locator at D:\\hmcl启动器\\.minecraft\\mods}\n[17:39:15] [main/INFO]: Found mod file cgm-1.2.7-1.18.2.jar of type MOD with locator {mods folder locator at D:\\hmcl启动器\\.minecraft\\mods}\n[17:39:15] [main/INFO]: Found mod file framework-0.2.4-1.18.2.jar of type MOD with locator {mods folder locator at D:\\hmcl启动器\\.minecraft\\mods}\n[17:39:15] [main/INFO]: Found mod file ironchest-1.18.2-13.2.11.jar of type MOD with locator {mods folder locator at D:\\hmcl启动器\\.minecraft\\mods}\n[17:39:15] [main/INFO]: Found mod file jei-1.18.2-forge-10.2.1.283.jar of type MOD with locator {mods folder locator at D:\\hmcl启动器\\.minecraft\\mods}\n[17:39:15] [main/INFO]: Found mod file waystones-forge-1.18.2-10.1.0.jar of type MOD with locator {mods folder locator at D:\\hmcl启动器\\.minecraft\\mods}\n[17:39:15] [main/INFO]: Found mod file fmlcore-1.18.2-40.1.59.jar of type LIBRARY with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@235f4c10\n[17:39:15] [main/INFO]: Found mod file javafmllanguage-1.18.2-40.1.59.jar of type LANGPROVIDER with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@235f4c10\n[17:39:15] [main/INFO]: Found mod file lowcodelanguage-1.18.2-40.1.59.jar of type LANGPROVIDER with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@235f4c10\n[17:39:15] [main/INFO]: Found mod file mclanguage-1.18.2-40.1.59.jar of type LANGPROVIDER with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@235f4c10\n[17:39:15] [main/INFO]: Found mod file client-1.18.2-20220404.173914-srg.jar of type MOD with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@235f4c10\n[17:39:15] [main/INFO]: Found mod file forge-1.18.2-40.1.59-universal.jar of type MOD with locator net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@235f4c10\n[17:39:15] [main/INFO]: OptiFineTransformationService.transformers\n[17:39:15] [main/INFO]: Targets: 350\n[17:39:16] [main/INFO]: additionalClassesLocator: [optifine., net.optifine.]\n[17:39:16] [main/INFO]: Compatibility level set to JAVA_17\n[17:39:16] [main/INFO]: Launching target 'forgeclient' with arguments [--version, 1.18.2（2), --gameDir, D:\\hmcl启动器\\.minecraft, --assetsDir, D:\\hmcl启动器\\.minecraft\\assets, --uuid, f879573aee333778962e2a175ad0c481, --username, xjy, --assetIndex, 1.18, --accessToken, �7�6�7�6�7�6�7�6�7�6�7�6�7�6�7�6, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, legacy, --versionType, HMCL 3.5.dev-1ec9e41, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker]\n[17:39:21] [Render thread/WARN]: Assets URL 'union:/D:/hmcl启动器/.minecraft/libraries/net/minecraft/client/1.18.2-20220404.173914/client-1.18.2-20220404.173914-srg.jar%2363!/assets/.mcassetsroot' uses unexpected schema\n[17:39:21] [Render thread/WARN]: Assets URL 'union:/D:/hmcl启动器/.minecraft/libraries/net/minecraft/client/1.18.2-20220404.173914/client-1.18.2-20220404.173914-srg.jar%2363!/data/.mcassetsroot' uses unexpected schema\n[17:39:21] [Render thread/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD'\n[17:39:23] [Render thread/ERROR]: Failed to verify authentication\ncom.mojang.authlib.exceptions.InvalidCredentialsException: Status: 401\n\tat com.mojang.authlib.exceptions.MinecraftClientHttpException.toAuthenticationException(MinecraftClientHttpException.java:56) ~[authlib-3.3.39.jar%2337!/:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilUserApiService.fetchProperties(YggdrasilUserApiService.java:132) ~[authlib-3.3.39.jar%2337!/:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilUserApiService.<init>(YggdrasilUserApiService.java:44) ~[authlib-3.3.39.jar%2337!/:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService.createUserApiService(YggdrasilAuthenticationService.java:153) ~[authlib-3.3.39.jar%2337!/:?]\n\tat net.minecraft.client.Minecraft.m_193585_(Minecraft.java:609) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat net.minecraft.client.Minecraft.<init>(Minecraft.java:401) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat net.minecraft.client.main.Main.main(Main.java:169) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:?]\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:?]\n\tat java.lang.reflect.Method.invoke(Unknown Source) ~[?:?]\n\tat net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:31) ~[fmlloader-1.18.2-40.1.59.jar%2316!/:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:37) [modlauncher-9.1.3.jar%235!/:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) [modlauncher-9.1.3.jar%235!/:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) [modlauncher-9.1.3.jar%235!/:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:106) [modlauncher-9.1.3.jar%235!/:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:77) [modlauncher-9.1.3.jar%235!/:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) [modlauncher-9.1.3.jar%235!/:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) [modlauncher-9.1.3.jar%235!/:?]\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:149) [bootstraplauncher-1.0.0.jar:?]\nCaused by: com.mojang.authlib.exceptions.MinecraftClientHttpException: Status: 401\n\tat com.mojang.authlib.minecraft.client.MinecraftClient.readInputStream(MinecraftClient.java:78) ~[authlib-3.3.39.jar%2337!/:?]\n\tat com.mojang.authlib.minecraft.client.MinecraftClient.get(MinecraftClient.java:48) ~[authlib-3.3.39.jar%2337!/:?]\n\tat com.mojang.authlib.yggdrasil.YggdrasilUserApiService.fetchProperties(YggdrasilUserApiService.java:113) ~[authlib-3.3.39.jar%2337!/:?]\n\t... 18 more\n[17:39:23] [Render thread/INFO]: Setting user: xjy\n[17:39:23] [Render thread/INFO]: Backend library: LWJGL version 3.2.2 SNAPSHOT\n[17:39:24] [Render thread/INFO]: [OptiFine] \n[17:39:24] [Render thread/INFO]: [OptiFine] OptiFine_1.18.2_HD_U_H7\n[17:39:24] [Render thread/INFO]: [OptiFine] Build: 20220410-185216\n[17:39:24] [Render thread/INFO]: [OptiFine] OS: Windows 10 (amd64) version 10.0\n[17:39:24] [Render thread/INFO]: [OptiFine] Java: 17.0.2, BellSoft\n[17:39:24] [Render thread/INFO]: [OptiFine] VM: OpenJDK 64-Bit Server VM (mixed mode, sharing), BellSoft\n[17:39:24] [Render thread/INFO]: [OptiFine] LWJGL: 3.3.0 Win32 WGL EGL OSMesa VisualC DLL\n[17:39:24] [Render thread/INFO]: [OptiFine] OpenGL: GeForce GTX 1660 Ti/PCIe/SSE2, version 3.2.0 NVIDIA 457.69, NVIDIA Corporation\n[17:39:24] [Render thread/INFO]: [OptiFine] OpenGL Version: 3.2.0\n[17:39:24] [Render thread/INFO]: [OptiFine] Maximum texture size: 32768x32768\n[17:39:24] [VersionCheck/INFO]: [OptiFine] Checking for new version\n[17:39:24] [Render thread/INFO]: [Shaders] OpenGL Version: 3.2.0 NVIDIA 457.69\n[17:39:24] [Render thread/INFO]: [Shaders] Vendor:  NVIDIA Corporation\n[17:39:24] [Render thread/INFO]: [Shaders] Renderer: GeForce GTX 1660 Ti/PCIe/SSE2\n[17:39:24] [Render thread/INFO]: [Shaders] Capabilities:  2.0  2.1  3.0  3.2  - \n[17:39:24] [Render thread/INFO]: [Shaders] GL_MAX_DRAW_BUFFERS: 8\n[17:39:24] [Render thread/INFO]: [Shaders] GL_MAX_COLOR_ATTACHMENTS: 8\n[17:39:24] [Render thread/INFO]: [Shaders] GL_MAX_TEXTURE_IMAGE_UNITS: 32\n[17:39:24] [Render thread/INFO]: [Shaders] Load shaders configuration.\n[17:39:24] [Render thread/INFO]: [Shaders] Loaded shaderpack: BSL_Edit_Pasquale (2).zip\n[17:39:24] [Render thread/INFO]: [OptiFine] [Shaders] Worlds: -1, 1\n[17:39:24] [Render thread/WARN]: [OptiFine] Ambiguous shader option: POMQuality\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in gbuffers_terrain.fsh, gbuffers_hand.fsh: 32\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in gbuffers_water.fsh: 64\n[17:39:24] [Render thread/WARN]: [OptiFine] Ambiguous shader option: POMDepth\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in gbuffers_terrain.fsh, gbuffers_hand.fsh: 1.00\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in gbuffers_water.fsh: 0.80\n[17:39:24] [Render thread/WARN]: [OptiFine] Ambiguous shader option: POMDistance\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in gbuffers_terrain.fsh: 64.0\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in gbuffers_water.fsh: 32.0\n[17:39:24] [Render thread/INFO]: [OptiFine] java.io.IOException: Included file not found: /shaders/world-1/gbuffers_skytextured.fsh\n[17:39:24] [Render thread/WARN]: [OptiFine] Ambiguous shader option: ColS1\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in composite5.fsh, world-1/composite5.fsh: 1.05\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in world-1/composite5.fsh: 1.10\n[17:39:24] [Render thread/WARN]: [OptiFine] Ambiguous shader option: noiseTextureResolution\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in final.fsh: 256\n[17:39:24] [Render thread/WARN]: [OptiFine]  - in world-1/final.fsh: 512\n[17:39:24] [Render thread/INFO]: [OptiFine] [Shaders] Delayed loading of block mappings after resources are loaded\n[17:39:24] [Render thread/INFO]: [OptiFine] [Shaders] Delayed loading of item mappings after resources are loaded\n[17:39:24] [Render thread/INFO]: [OptiFine] [Shaders] Delayed loading of entity mappings after resources are loaded\n[17:39:24] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.ClassNotFoundException: sun.misc.SharedSecrets\n[17:39:24] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.ClassNotFoundException: jdk.internal.misc.SharedSecrets\n[17:39:24] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.ClassNotFoundException: sun.misc.VM\n[17:39:24] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.reflect.InaccessibleObjectException: Unable to make public static long jdk.internal.misc.VM.maxDirectMemory() accessible: module java.base does not \"exports jdk.internal.misc\" to module net.optifine\n[17:39:24] [Render thread/INFO]: [Shaders] Custom texture: texture.composite.depthtex2 = tex/dirt.png\n[17:39:24] [Render thread/INFO]: [Shaders] Custom variable: tAmin\n[17:39:24] [Render thread/INFO]: [Shaders] Custom variable: tAlin\n[17:39:24] [Render thread/INFO]: [Shaders] Custom variable: hA\n[17:39:24] [Render thread/INFO]: [Shaders] Custom variable: tAfrc\n[17:39:24] [Render thread/INFO]: [Shaders] Custom variable: tAfrs\n[17:39:24] [Render thread/INFO]: [Shaders] Custom variable: tAmix\n[17:39:24] [Render thread/INFO]: [Shaders] Custom uniform: timeAngle\n[17:39:24] [Render thread/INFO]: [Shaders] Custom uniform: timeBrightness\n[17:39:24] [Render thread/INFO]: [Shaders] Custom uniform: shadowFade\n[17:39:24] [Render thread/INFO]: [Shaders] Custom uniform: isCold\n[17:39:24] [Render thread/INFO]: [Shaders] Custom uniform: isDesert\n[17:39:24] [Render thread/INFO]: [Shaders] Custom uniform: isMesa\n[17:39:24] [Render thread/INFO]: [Shaders] Custom uniform: isSwamp\n[17:39:24] [Render thread/INFO]: [Shaders] Custom uniform: isMushroom\n[17:39:24] [VersionCheck/INFO]: [OptiFine] Version found: H6\n[17:39:24] [modloading-worker-0/INFO]: Forge mod loading, version 40.1.59, for MC 1.18.2 with MCP 20220404.173914\n[17:39:24] [modloading-worker-0/INFO]: MinecraftForge v40.1.59 Initialized\n[17:39:26] [Render thread/INFO]: Narrator library for x64 successfully loaded\n[17:39:26] [Render thread/INFO]: Saving config file: D:\\hmcl启动器\\.minecraft\\config\\jei\\jei-client.ini\n[17:39:26] [Render thread/INFO]: Reloading ResourceManager: Mod Resources, Default\n[17:39:26] [Render thread/INFO]: [OptiFine] *** Reloading textures ***\n[17:39:26] [Render thread/INFO]: [OptiFine] Resource packs: Mod Resources\n[17:39:26] [modloading-worker-0/WARN]: Configuration file D:\\hmcl启动器\\.minecraft\\config\\buildinggadgets-client.toml is not correct. Correcting\n[17:39:26] [modloading-worker-0/WARN]: Incorrect key general was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key general.Default to absolute Coord-Mode was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Configuration file D:\\hmcl启动器\\.minecraft\\config\\cgm-client.toml is not correct. Correcting\n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.sounds was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.sounds.playSoundWhenHeadshot was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.sounds.headshotSound was corrected from null to its default, minecraft:entity.player.attack.knockback. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.sounds.playSoundWhenCritical was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.sounds.criticalSound was corrected from null to its default, minecraft:entity.player.attack.crit. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.sounds.impactSoundDistance was corrected from null to its default, 32.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.oldAnimations was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.crosshair was corrected from null to its default, minecraft:default. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.cooldownIndicator was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.weaponSway was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.swaySensitivity was corrected from null to its default, 0.3. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.swayType was corrected from null to its default, DRAG. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.cameraRollEffect was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.cameraRollAngle was corrected from null to its default, 1.5. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.sprintingAnimation was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.display.bobbingIntensity was corrected from null to its default, 1.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.particle was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.particle.bulletHoleLifeMin was corrected from null to its default, 150. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.particle.bulletHoleLifeMax was corrected from null to its default, 200. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.particle.bulletHoleFadeThreshold was corrected from null to its default, 0.98. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.particle.enableBlood was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.particle.impactParticleDistance was corrected from null to its default, 32.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.controls was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.controls.aimDownSightSensitivity was corrected from null to its default, 0.75. \n[17:39:26] [modloading-worker-0/WARN]: Configuration file D:\\hmcl启动器\\.minecraft\\config\\cgm-common.toml is not correct. Correcting\n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.griefing was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.griefing.enableBlockRemovalOnExplosions was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.griefing.enableGlassBreaking was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.griefing.setFireToBlocks was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.growBoundingBoxAmount was corrected from null to its default, 0.3. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.enableHeadShots was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.headShotDamageMultiplier was corrected from null to its default, 1.25. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.criticalDamageMultiplier was corrected from null to its default, 1.5. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.ignoreLeaves was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.enableKnockback was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.knockbackStrength was corrected from null to its default, 0.15. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.gameplay.improvedHitboxes was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.network was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.network.projectileTrackingRange was corrected from null to its default, 200.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.aggro_mobs was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.aggro_mobs.enabled was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.aggro_mobs.angerHostileMobs was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.aggro_mobs.unsilencedRange was corrected from null to its default, 20.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.aggro_mobs.exemptMobs was corrected from null to its default, []. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.missiles was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.missiles.explosionRadius was corrected from null to its default, 5.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.grenades was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.grenades.explosionRadius was corrected from null to its default, 5.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.blind was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.blind.effect_criteria was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.blind.effect_criteria.radius was corrected from null to its default, 15.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.blind.effect_criteria.durationMax was corrected from null to its default, 220. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.blind.effect_criteria.durationMin was corrected from null to its default, 10. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.blind.effect_criteria.angleEffect was corrected from null to its default, 170.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.blind.effect_criteria.angleAttenuationMax was corrected from null to its default, 0.75. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.blind.effect_criteria.raytraceOpaqueBlocks was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.blind.blindMobs was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.deafen was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.deafen.effect_criteria was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.deafen.effect_criteria.radius was corrected from null to its default, 15.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.deafen.effect_criteria.durationMax was corrected from null to its default, 280. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.deafen.effect_criteria.durationMin was corrected from null to its default, 100. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.deafen.effect_criteria.angleEffect was corrected from null to its default, 360.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.deafen.effect_criteria.angleAttenuationMax was corrected from null to its default, 0.75. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.deafen.effect_criteria.raytraceOpaqueBlocks was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.stun_grenades.deafen.panicMobs was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.projectile_spread was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.projectile_spread.spreadThreshold was corrected from null to its default, 300. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key common.projectile_spread.maxCount was corrected from null to its default, 10. \n[17:39:26] [modloading-worker-0/WARN]: Configuration file D:\\hmcl启动器\\.minecraft\\config\\waystones-common.toml is not correct. Correcting\n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.inverseXpCost was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.blocksPerXpLevel was corrected from null to its default, 1000. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.minimumBaseXpCost was corrected from null to its default, 0.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.maximumBaseXpCost was corrected from null to its default, 3.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.xpCostPerLeashed was corrected from null to its default, 1. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.dimensionalWarpXpCost was corrected from null to its default, 3. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.globalWaystoneXpCostMultiplier was corrected from null to its default, 1.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.warpStoneXpCostMultiplier was corrected from null to its default, 0.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.waystoneXpCostMultiplier was corrected from null to its default, 1.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.sharestoneXpCostMultiplier was corrected from null to its default, 1.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.portstoneXpCostMultiplier was corrected from null to its default, 0.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.warpPlateXpCostMultiplier was corrected from null to its default, 0.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key xpCost.inventoryButtonXpCostMultiplier was corrected from null to its default, 0.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.restrictToCreative was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.restrictRenameToOwner was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.generatedWaystonesUnbreakable was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.transportLeashed was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.transportLeashedDimensional was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.leashedDenyList was corrected from null to its default, [minecraft:wither]. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.dimensionalWarp was corrected from null to its default, ALLOW. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.dimensionalWarpAllowList was corrected from null to its default, []. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.dimensionalWarpDenyList was corrected from null to its default, []. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.allowWaystoneToWaystoneTeleport was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key restrictions.globalWaystoneSetupRequiresCreativeMode was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key cooldowns was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [Render thread/INFO]: [OptiFine] *** Reflector Forge ***\n[17:39:26] [modloading-worker-0/WARN]: Incorrect key cooldowns.globalWaystoneCooldownMultiplier was corrected from null to its default, 1.0. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key cooldowns.warpStoneCooldown was corrected from null to its default, 300. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key cooldowns.warpStoneUseTime was corrected from null to its default, 32. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key cooldowns.warpPlateUseTime was corrected from null to its default, 20. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key cooldowns.scrollUseTime was corrected from null to its default, 32. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key cooldowns.inventoryButtonCooldown was corrected from null to its default, 300. \n[17:39:26] [Render thread/INFO]: [OptiFine] (Reflector) Class not present: mods.betterfoliage.client.BetterFoliageClient\n[17:39:26] [modloading-worker-0/WARN]: Incorrect key inventoryButton was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key inventoryButton.inventoryButton was corrected from null to its default, . \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key inventoryButton.warpButtonX was corrected from null to its default, 58. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key inventoryButton.warpButtonY was corrected from null to its default, 60. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key inventoryButton.creativeWarpButtonX was corrected from null to its default, 88. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key inventoryButton.creativeWarpButtonY was corrected from null to its default, 33. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key worldGen was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key worldGen.worldGenStyle was corrected from null to its default, BIOME. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key worldGen.frequency was corrected from null to its default, 25. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key worldGen.dimensionAllowList was corrected from null to its default, [minecraft:overworld, minecraft:the_nether, minecraft:the_end]. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key worldGen.dimensionDenyList was corrected from null to its default, []. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key worldGen.nameGenerationMode was corrected from null to its default, PRESET_FIRST. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key worldGen.customWaystoneNames was corrected from null to its default, []. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key worldGen.spawnInVillages was corrected from null to its default, true. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key worldGen.forceSpawnInVillages was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.disableTextGlow was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key client.disableParticles was corrected from null to its default, false. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key compatibility was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:26] [modloading-worker-0/WARN]: Incorrect key compatibility.displayWaystonesOnJourneyMap was corrected from null to its default, true. \n[17:39:26] [Render thread/WARN]: [OptiFine] (Reflector) More than one method found: net.minecraftforge.client.ForgeHooksClient.onFogRender\n[17:39:26] [Render thread/WARN]: [OptiFine] (Reflector)  - public static void net.minecraftforge.client.ForgeHooksClient.onFogRender(net.minecraft.client.renderer.FogRenderer$FogMode,net.minecraft.client.Camera,float,float,float,com.mojang.blaze3d.shaders.FogShape)\n[17:39:26] [Render thread/WARN]: [OptiFine] (Reflector)  - public static void net.minecraftforge.client.ForgeHooksClient.onFogRender(net.minecraft.client.renderer.FogRenderer$FogMode,net.minecraft.client.Camera,float,float)\n[17:39:26] [Render thread/INFO]: [OptiFine] *** Reflector Vanilla ***\n[17:39:26] [Worker-Main-11/INFO]: [OptiFine] Multitexture: false\n[17:39:26] [Worker-Main-6/INFO]: [OptiFine] Multitexture: false\n[17:39:26] [Worker-Main-8/INFO]: [OptiFine] Multitexture: false\n[17:39:26] [Forge Version Check/INFO]: [framework] Starting version check at https://mrcrayfish.com/modupdatejson?id=framework\n[17:39:28] [Worker-Main-9/INFO]: [OptiFine] Multitexture: false\n[17:39:30] [Worker-Main-10/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:30] [Worker-Main-10/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:30] [Worker-Main-10/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:30] [Worker-Main-10/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:30] [Worker-Main-10/INFO]: [OptiFine] Multitexture: false\n[17:39:30] [Worker-Main-10/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:30] [Worker-Main-10/INFO]: [OptiFine] Multipass connected textures: false\n[17:39:30] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/00_glass_white/glass_pane_white.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/00_glass_white/glass_white.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/01_glass_orange/glass_orange.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/01_glass_orange/glass_pane_orange.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/02_glass_magenta/glass_magenta.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/02_glass_magenta/glass_pane_magenta.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/03_glass_light_blue/glass_light_blue.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/03_glass_light_blue/glass_pane_light_blue.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/04_glass_yellow/glass_pane_yellow.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/04_glass_yellow/glass_yellow.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/05_glass_lime/glass_lime.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/05_glass_lime/glass_pane_lime.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/06_glass_pink/glass_pane_pink.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/06_glass_pink/glass_pink.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/07_glass_gray/glass_gray.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/07_glass_gray/glass_pane_gray.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/08_glass_light_gray/glass_light_gray.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/08_glass_light_gray/glass_pane_light_gray.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/09_glass_cyan/glass_cyan.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/09_glass_cyan/glass_pane_cyan.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/10_glass_purple/glass_pane_purple.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/10_glass_purple/glass_purple.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/11_glass_blue/glass_blue.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/11_glass_blue/glass_pane_blue.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/12_glass_brown/glass_brown.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/12_glass_brown/glass_pane_brown.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/13_glass_green/glass_green.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/13_glass_green/glass_pane_green.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/14_glass_red/glass_pane_red.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/14_glass_red/glass_red.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/15_glass_black/glass_black.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/15_glass_black/glass_pane_black.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/20_glass/glass.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/20_glass/glass_pane.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/21_tinted_glass/tinted_glass.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/30_bookshelf/bookshelf.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/40_sandstone/sandstone.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/41_red_sandstone/red_sandstone.properties\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] Multipass connected textures: false\n[17:39:31] [Worker-Main-10/INFO]: [OptiFine] BetterGrass: Parsing default configuration optifine/bettergrass.properties\n[17:39:31] [Forge Version Check/INFO]: [framework] Found status: BETA Current: 0.2.4 Target: 0.2.4\n[17:39:31] [Forge Version Check/INFO]: [cgm] Starting version check at https://raw.githubusercontent.com/MrCrayfish/ModUpdates/master/cgm/update.json\n[17:39:31] [Forge Version Check/WARN]: Failed to process update information\njava.net.ConnectException: null\n\tat jdk.internal.net.http.HttpClientImpl.send(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(Unknown Source) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:131) ~[fmlcore-1.18.2-40.1.59.jar%2364!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:169) ~[fmlcore-1.18.2-40.1.59.jar%2364!/:?]\n\tat java.lang.Iterable.forEach(Unknown Source) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.18.2-40.1.59.jar%2364!/:?]\nCaused by: java.net.ConnectException\n\tat jdk.internal.net.http.common.Utils.toConnectException(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(Unknown Source) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(Unknown Source) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(Unknown Source) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(Unknown Source) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(Unknown Source) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(Unknown Source) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(Unknown Source) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(Unknown Source) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(Unknown Source) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(Unknown Source) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(Unknown Source) ~[java.net.http:?]\n\t... 6 more\nCaused by: java.nio.channels.UnresolvedAddressException\n\tat sun.nio.ch.Net.checkAddress(Unknown Source) ~[?:?]\n\tat sun.nio.ch.Net.checkAddress(Unknown Source) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.checkRemote(Unknown Source) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.connect(Unknown Source) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$0(Unknown Source) ~[java.net.http:?]\n\tat java.security.AccessController.doPrivileged(Unknown Source) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(Unknown Source) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(Unknown Source) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(Unknown Source) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(Unknown Source) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(Unknown Source) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(Unknown Source) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(Unknown Source) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(Unknown Source) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(Unknown Source) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(Unknown Source) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(Unknown Source) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(Unknown Source) ~[java.net.http:?]\n\t... 6 more\n[17:39:31] [Forge Version Check/INFO]: [forge] Starting version check at https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json\n[17:39:32] [Forge Version Check/INFO]: [forge] Found status: OUTDATED Current: 40.1.59 Target: 40.1.86\n[17:39:34] [Worker-Main-10/INFO]: [OptiFine] Mipmap levels: 4\n[17:39:34] [Worker-Main-9/INFO]: [OptiFine] Scaled non power of 2: jei:gui/icons/recipe_transfer, 7 -> 14\n[17:39:34] [Render thread/INFO]: Registered synced data key cgm:aiming for minecraft:player\n[17:39:34] [Render thread/INFO]: Registered synced data key cgm:shooting for minecraft:player\n[17:39:34] [Render thread/INFO]: Registered synced data key cgm:reloading for minecraft:player\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Multitexture: false\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Sprite size: 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Mipmap levels: 6\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_copy_paste_gadget, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_template, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[17:39:35] [Worker-Main-10/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Multitexture: false\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Sprite size: 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Mipmap levels: 6\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_template, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_copy_paste_gadget, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[17:39:35] [Worker-Main-10/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Multitexture: false\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Sprite size: 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Mipmap levels: 6\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_template, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_copy_paste_gadget, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[17:39:35] [Worker-Main-10/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Multitexture: false\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Sprite size: 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Mipmap levels: 6\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_template, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_copy_paste_gadget, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[17:39:35] [Worker-Main-10/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Multitexture: false\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Sprite size: 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Mipmap levels: 6\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_template, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_copy_paste_gadget, 16 -> 64\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[17:39:35] [Worker-Main-10/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[17:39:35] [Worker-Main-10/INFO]: [OptiFine] Multitexture: false\n[17:39:36] [Worker-Main-10/INFO]: [OptiFine] Sprite size: 64\n[17:39:36] [Worker-Main-10/INFO]: [OptiFine] Mipmap levels: 6\n[17:39:36] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_template, 16 -> 64\n[17:39:36] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: buildinggadgets:gui/slot_copy_paste_gadget, 16 -> 64\n[17:39:36] [Worker-Main-10/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[17:39:36] [Worker-Main-10/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[17:39:37] [Render thread/INFO]: OpenAL initialized on device OpenAL Soft on 扬声器 (3- Realtek(R) Audio)\n[17:39:37] [Render thread/INFO]: Sound engine started\n[17:39:37] [Render thread/INFO]: Created: 1024x1024x4 minecraft:textures/atlas/blocks.png-atlas\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map normal: 1024x1024, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map specular: 1024x1024, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [OptiFine] Animated sprites: 45\n[17:39:37] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/signs.png-atlas\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x512, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x512, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[17:39:37] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/banner_patterns.png-atlas\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x512, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x512, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[17:39:37] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/shield_patterns.png-atlas\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x512, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x512, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[17:39:37] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/chest.png-atlas\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x256, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x256, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[17:39:37] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x256, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x256, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[17:39:37] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x256, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x256, mipmaps: 4\n[17:39:37] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[17:39:38] [Render thread/INFO]: Created: 256x256x0 minecraft:textures/atlas/particles.png-atlas\n[17:39:38] [Render thread/INFO]: [Shaders] Allocate texture map normal: 256x256, mipmaps: 0\n[17:39:38] [Render thread/INFO]: [Shaders] Allocate texture map specular: 256x256, mipmaps: 0\n[17:39:38] [Render thread/INFO]: [OptiFine] Animated sprites: 1\n[17:39:38] [Render thread/INFO]: Created: 256x256x0 minecraft:textures/atlas/paintings.png-atlas\n[17:39:38] [Render thread/INFO]: [Shaders] Allocate texture map normal: 256x256, mipmaps: 0\n[17:39:38] [Render thread/INFO]: [Shaders] Allocate texture map specular: 256x256, mipmaps: 0\n[17:39:38] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[17:39:38] [Render thread/INFO]: Created: 128x128x0 minecraft:textures/atlas/mob_effects.png-atlas\n[17:39:38] [Render thread/INFO]: [Shaders] Allocate texture map normal: 128x128, mipmaps: 0\n[17:39:38] [Render thread/INFO]: [Shaders] Allocate texture map specular: 128x128, mipmaps: 0\n[17:39:38] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[17:39:38] [Render thread/INFO]: Created: 256x128x0 jei:textures/atlas/gui.png-atlas\n[17:39:38] [Render thread/INFO]: [Shaders] Allocate texture map normal: 256x128, mipmaps: 0\n[17:39:38] [Render thread/INFO]: [Shaders] Allocate texture map specular: 256x128, mipmaps: 0\n[17:39:38] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[17:39:38] [Render thread/INFO]: [OptiFine] *** Reloading custom textures ***\n[17:39:38] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:38] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:38] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:43] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:43] [Render thread/INFO]: [OptiFine] [Shaders] Parsing block mappings: /shaders/block.properties\n[17:39:43] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:azure_blue\n[17:39:43] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:flowing_lava\n[17:39:43] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:flowing_water\n[17:39:43] [Render thread/INFO]: [OptiFine] [Shaders] Parsing item mappings: /shaders/item.properties\n[17:39:43] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:43] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[17:39:43] [Render thread/INFO]: [OptiFine] Disable Forge light pipeline\n[17:39:43] [Render thread/INFO]: [OptiFine] Set ForgeConfig.CLIENT.experimentalForgeLightPipelineEnabled=false\n[17:39:44] [Realms Notification Availability checker #1/INFO]: Could not authorize you against Realms server: Invalid session id\n[17:39:51] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[17:39:51] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]\n[17:39:51] [Render thread/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[17:39:51] [Render thread/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[17:39:51] [Render thread/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[17:39:52] [Render thread/INFO]: Loaded 8 recipes\n[17:39:53] [Render thread/INFO]: Loaded 1188 advancements\n[17:39:54] [Render thread/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD'\n[17:39:54] [Server thread/INFO]: Starting integrated minecraft server version 1.18.2\n[17:39:54] [Server thread/INFO]: Generating keypair\n[17:39:54] [Server thread/WARN]: Configuration file D:\\hmcl启动器\\.minecraft\\saves\\新的世界\\serverconfig\\buildinggadgets-server.toml is not correct. Correcting\n[17:39:54] [Server thread/WARN]: Incorrect key general was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key general.Allow Absolute Coords was corrected from null to its default, true. \n[17:39:54] [Server thread/WARN]: Incorrect key general.MaxBuildDistance was corrected from null to its default, 32.0. \n[17:39:54] [Server thread/WARN]: Incorrect key general.Allow non-Air-Block-Overwrite was corrected from null to its default, true. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Maximum allowed Range was corrected from null to its default, 15. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Max Placement/Tick was corrected from null to its default, 1024. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Building Gadget was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Building Gadget.Maximum Energy was corrected from null to its default, 500000. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Building Gadget.Energy Cost was corrected from null to its default, 50. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Building Gadget.Max Undo History Size was corrected from null to its default, 10. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Exchanging Gadget was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Exchanging Gadget.Maximum Energy was corrected from null to its default, 500000. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Exchanging Gadget.Energy Cost was corrected from null to its default, 100. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Exchanging Gadget.Max Undo History Size was corrected from null to its default, 10. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Destruction Gadget was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Destruction Gadget.Maximum Energy was corrected from null to its default, 1000000. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Destruction Gadget.Energy Cost was corrected from null to its default, 200. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Destruction Gadget.Max Undo History Size was corrected from null to its default, 1. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Destruction Gadget.Destroy Dimensions was corrected from null to its default, 16. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Destruction Gadget.Non-Fuzzy Mode Multiplier was corrected from null to its default, 2.0. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Destruction Gadget.Non-Fuzzy Mode Enabled was corrected from null to its default, false. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Copy-Paste Gadget was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Copy-Paste Gadget.Maximum Energy was corrected from null to its default, 500000. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Copy-Paste Gadget.Energy Cost was corrected from null to its default, 50. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Copy-Paste Gadget.Max Undo History Size was corrected from null to its default, 1. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Copy-Paste Gadget.Max Copy/Tick was corrected from null to its default, 32768. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Copy-Paste Gadget.Max Copy Dimensions was corrected from null to its default, 256. \n[17:39:54] [Server thread/WARN]: Incorrect key Gadgets.Copy-Paste Gadget.Max Build Dimensions was corrected from null to its default, 256. \n[17:39:54] [Server thread/WARN]: Incorrect key Paste Containers was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key Paste Containers.T1 Container Capacity was corrected from null to its default, 512. \n[17:39:54] [Server thread/WARN]: Incorrect key Paste Containers.T2 Container Capacity was corrected from null to its default, 2048. \n[17:39:54] [Server thread/WARN]: Incorrect key Paste Containers.T3 Container Capacity was corrected from null to its default, 8192. \n[17:39:54] [Server thread/WARN]: Configuration file D:\\hmcl启动器\\.minecraft\\saves\\新的世界\\serverconfig\\cgm-server.toml is not correct. Correcting\n[17:39:54] [Server thread/WARN]: Incorrect key server was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key server.grenade was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key server.grenade.alphaOverlay was corrected from null to its default, 255. \n[17:39:54] [Server thread/WARN]: Incorrect key server.grenade.alphaFadeThreshold was corrected from null to its default, 40. \n[17:39:54] [Server thread/WARN]: Incorrect key server.grenade.soundPercentage was corrected from null to its default, 0.05. \n[17:39:54] [Server thread/WARN]: Incorrect key server.grenade.soundFadeThreshold was corrected from null to its default, 90. \n[17:39:54] [Server thread/WARN]: Incorrect key server.grenade.ringVolume was corrected from null to its default, 1.0. \n[17:39:54] [Server thread/WARN]: Incorrect key server.audio was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key server.audio.gunShotMaxDistance was corrected from null to its default, 100.0. \n[17:39:54] [Server thread/WARN]: Incorrect key server.audio.reloadMaxDistance was corrected from null to its default, 24.0. \n[17:39:54] [Server thread/WARN]: Incorrect key server.enableCameraRecoil was corrected from null to its default, true. \n[17:39:54] [Server thread/WARN]: Incorrect key server.cooldownThreshold was corrected from null to its default, 0. \n[17:39:54] [Server thread/WARN]: Incorrect key server.experimental was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key server.experimental.forceDyeableAttachments was corrected from null to its default, false. \n[17:39:54] [Server thread/WARN]: Configuration file D:\\hmcl启动器\\.minecraft\\saves\\新的世界\\serverconfig\\jei-server.toml is not correct. Correcting\n[17:39:54] [Server thread/WARN]: Incorrect key cheat mode was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key cheat mode.enableCheatModeForOp was corrected from null to its default, true. \n[17:39:54] [Server thread/WARN]: Incorrect key cheat mode.enableCheatModeForCreative was corrected from null to its default, true. \n[17:39:54] [Server thread/WARN]: Incorrect key cheat mode.enableCheatModeForGive was corrected from null to its default, false. \n[17:39:54] [Server thread/WARN]: Configuration file D:\\hmcl启动器\\.minecraft\\saves\\新的世界\\serverconfig\\forge-server.toml is not correct. Correcting\n[17:39:54] [Server thread/WARN]: Incorrect key server was corrected from null to its default, SimpleCommentedConfig:{}. \n[17:39:54] [Server thread/WARN]: Incorrect key server.removeErroringBlockEntities was corrected from null to its default, false. \n[17:39:54] [Server thread/WARN]: Incorrect key server.removeErroringEntities was corrected from null to its default, false. \n[17:39:54] [Server thread/WARN]: Incorrect key server.fullBoundingBoxLadders was corrected from null to its default, false. \n[17:39:54] [Server thread/WARN]: Incorrect key server.zombieBaseSummonChance was corrected from null to its default, 0.1. \n[17:39:54] [Server thread/WARN]: Incorrect key server.zombieBabyChance was corrected from null to its default, 0.05. \n[17:39:54] [Server thread/WARN]: Incorrect key server.treatEmptyTagsAsAir was corrected from null to its default, false. \n[17:39:54] [Server thread/WARN]: Incorrect key server.skipEmptyShapelessCheck was corrected from null to its default, true. \n[17:39:54] [Server thread/WARN]: Incorrect key server.fixAdvancementLoading was corrected from null to its default, true. \n[17:39:54] [Server thread/WARN]: Incorrect key server.permissionHandler was corrected from null to its default, forge:default_handler. \n[17:39:54] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld\n[17:39:54] [Server thread/ERROR]: Encountered an unexpected exception\njava.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager.addRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'\n\tat net.minecraft.server.level.ServerChunkCache.addRegionTicket(ServerChunkCache.java:429) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat net.minecraft.server.level.ServerChunkCache.m_8387_(ServerChunkCache.java:425) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat net.minecraft.server.MinecraftServer.m_129940_(MinecraftServer.java:471) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat net.minecraft.server.MinecraftServer.m_130006_(MinecraftServer.java:318) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat net.minecraft.client.server.IntegratedServer.m_7038_(IntegratedServer.java:84) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat net.minecraft.server.MinecraftServer.m_130011_(MinecraftServer.java:661) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat net.minecraft.server.MinecraftServer.m_177918_(MinecraftServer.java:261) ~[client-1.18.2-20220404.173914-srg.jar%2363!/:?]\n\tat java.lang.Thread.run(Unknown Source) [?:?]\n[17:39:54] [Server thread/FATAL]: Preparing crash report with UUID 498c0444-95f8-4efc-8096-527b083f1739\n[17:39:55] [Server thread/FATAL]: Preparing crash report with UUID 442dec9f-c942-443e-83e5-f93583cb0c12\n[17:39:55] [Server thread/ERROR]: This crash report has been saved to: D:\\hmcl启动器\\.minecraft\\crash-reports\\crash-2022-12-06_17.39.54-server.txt\n[17:39:55] [Server thread/INFO]: Stopping server\n[17:39:55] [Server thread/INFO]: Saving players\n[17:39:55] [Server thread/INFO]: Saving worlds\n[17:39:55] [Server thread/INFO]: Saving chunks for level 'ServerLevel[新的世界]'/minecraft:overworld\n[17:39:55] [Render thread/FATAL]: Preparing crash report with UUID afc41ea2-ffb3-473d-8438-83a132ab9734\n[17:39:55] [Server thread/INFO]: Saving chunks for level 'ServerLevel[新的世界]'/minecraft:the_nether"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/optifine_is_not_compatible_with_forge2.txt",
    "content": "[22:11:23] [main/INFO]: ModLauncher running: args [--username, THFOL, --version, 1.19.2, --gameDir, C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft, --assetsDir, C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\assets, --assetIndex, 1.19, --uuid, c135223bb1483f12b12171c2ad9a333d, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, mojang, --versionType, HMCL 3.5.dev-c113670, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker, --launchTarget, forgeclient, --fml.forgeVersion, 43.1.43, --fml.mcVersion, 1.19.2, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20220805.130853]\n[22:11:23] [main/INFO]: ModLauncher 10.0.8+10.0.8+main.0ef7e830 starting: java version 19 by Oracle Corporation; OS Windows 10 arch amd64 version 10.0\n[22:11:24] [main/INFO]: OptiFineTransformationService.onLoad\n[22:11:24] [main/INFO]: OptiFine ZIP file URL: union:/C:/Users/Administrator/AppData/Roaming/.minecraft/libraries/optifine/OptiFine/1.19.2_HD_U_H9/OptiFine-1.19.2_HD_U_H9.jar%2398!/\n[22:11:24] [main/INFO]: OptiFine ZIP file: C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\optifine\\OptiFine\\1.19.2_HD_U_H9\\OptiFine-1.19.2_HD_U_H9.jar\n[22:11:24] [main/INFO]: Target.PRE_CLASS is available\n[22:11:24] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/C:/Users/Administrator/AppData/Roaming/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%2393!/ Service=ModLauncher Env=CLIENT\n[22:11:24] [main/INFO]: OptiFineTransformationService.initialize\n[22:11:24] [main/INFO]: Found mod file 3dskinlayers-forge-1.5.2-mc1.19.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file [1.19.x]-Epic-Knights-Armor-and-Weapons-6.7v.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file additional-guns-0.7.2-1.19.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file additional_lights-1.19-2.1.5.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Adorn-3.6.1+1.19-forge.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file alexsmobs-1.20.2.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file apexcore-1.19.2-7.3.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Aquaculture-1.19.2-2.4.7.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file artifacts-1.19.2-5.0.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file AutoRegLib-1.8-54.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file badpackets-forge-0.2.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file balm-4.5.3.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Beautify 1.19.2-V.1.4.2 [Forge].jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file BiomesOPlenty-1.19.2-17.1.1.160.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file blue_skies-1.19.2-1.3.20.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file blueprint-1.19.2-6.1.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Bookshelf-Forge-1.19.2-16.1.8.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file buildersaddition-1.19.2-20220926a.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Cataclysm-0.20-1.19.2-hotfix.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file cfm-7.0.0-pre34-1.19.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file cgm-1.2.8-1.19.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Chimes-1.1.4-1.19.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file citadel-1.13.3-1.19.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file cloth-config-8.2.88-forge.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file connectedglass-1.1.4b-forge-mc1.19.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Controlling-forge-1.19.2-10.0+6.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file cookingforblockheads-forge-1.19.2-13.2.3.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file create-1.19.2-0.5.0.f.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file create-confectionery1.19.2_v1.0.8.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file create-stuff-additions1.19.2_v2.0.1c.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file createcafe-1.0.0-1.19.2.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file creeperoverhaul-2.0.4-forge.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Croptopia-1.19.2-FORGE-2.1.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file curios-forge-1.19.2-5.1.1.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file DarkPaintings-Forge-1.19.2-13.1.2.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Decorative Blocks-forge-1.19.2-3.0.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Delightful-1.19-3.1.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file DungeonsArise-1.19-2.1.51-beta.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file eatinganimation-1.19-3.1.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file effective_fg-1.3.4.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file EnchantmentDescriptions-Forge-1.19.2-13.0.7.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file entityculling-forge-1.5.2-mc1.19.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file exoticbirds-1.19.2-2.6.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file expandability-7.0.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file ExtraArmor 1.19.2-1.17.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file fantasyfurniture-1.19.2-6.5.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file FarmersDelight-1.19-1.2.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file framework-0.3.3-1.19.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file geckolib-forge-1.19-3.1.27.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Gobber2-Forge-1.19.2-2.7.23.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file HealthOverlay-1.19.2-7.1.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file immersive_armors-1.4.2.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file jei-1.19.2-forge-11.3.0.271.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file kotlinforforge-3.7.1-obf.jar of type LANGPROVIDER with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file mahoutsukai-1.19.2-v1.34.35.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file mcw-doors-1.0.7-mc1.19.2.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file mcw-fences-1.0.6-mc1.19.2.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file mcw-furniture-3.0.2-mc1.19.2.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file mcw-lights-1.0.4-mc1.19.2.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file mcw-roofs-2.2.1-mc1.19.2-forge.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file moonlight-1.19.2-2.0.37.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file morevillagers-forge-1.19-4.0.3.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file neapolitan-1.19.2-4.0.2.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file nekoration-1.19.2-1.8.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Oh_The_Biomes_You'll_Go-forge-1.19.2-2.0.0.13.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Paintings-forge-1.19.2-10.2.1.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Paraglider-1.19.2-1.7.0.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Patchouli-1.19.2-77.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file PizzaCraft-1.19.2-6.2.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Quark-3.3-371.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file QuarkOddities-1.18.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file SereneSeasons-1.19.2-8.1.0.21.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file simpleplanes-1.19-5.0.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file skinned_lanterns-1.19.2-1.3.4.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file stalwart-dungeons-1.19.2-1.2.4.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file structure_gel-1.19.2-2.7.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file supplementaries-1.19.2-2.2.7.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file TerraBlender-forge-1.19.2-2.0.1.128.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file transparent-5.1.2+1.19-forge.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file TravelersBackpack-1.19.2-8.2.3.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file twilightforest-1.19-4.2.1421-universal.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file untitledduckmod-0.6.1-1.19.2-forge.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file useless-sword-1.19.2-V1.4.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file waystones-forge-1.19-11.1.0.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file Xaeros_Minimap_22.16.0_Forge_1.19.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/INFO]: Found mod file XaerosWorldMap_1.28.1_Forge_1.19.1.jar of type MOD with provider {mods folder locator at C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods}\n[22:11:24] [main/WARN]: Mod file C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\fmlcore\\1.19.2-43.1.43\\fmlcore-1.19.2-43.1.43.jar is missing mods.toml file\n[22:11:24] [main/WARN]: Mod file C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\javafmllanguage\\1.19.2-43.1.43\\javafmllanguage-1.19.2-43.1.43.jar is missing mods.toml file\n[22:11:24] [main/WARN]: Mod file C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\lowcodelanguage\\1.19.2-43.1.43\\lowcodelanguage-1.19.2-43.1.43.jar is missing mods.toml file\n[22:11:24] [main/WARN]: Mod file C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\libraries\\net\\minecraftforge\\mclanguage\\1.19.2-43.1.43\\mclanguage-1.19.2-43.1.43.jar is missing mods.toml file\n[22:11:24] [main/INFO]: Found mod file fmlcore-1.19.2-43.1.43.jar of type LIBRARY with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@78d39a69\n[22:11:24] [main/INFO]: Found mod file javafmllanguage-1.19.2-43.1.43.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@78d39a69\n[22:11:24] [main/INFO]: Found mod file lowcodelanguage-1.19.2-43.1.43.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@78d39a69\n[22:11:24] [main/INFO]: Found mod file mclanguage-1.19.2-43.1.43.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@78d39a69\n[22:11:24] [main/INFO]: Found mod file client-1.19.2-20220805.130853-srg.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@78d39a69\n[22:11:24] [main/INFO]: Found mod file forge-1.19.2-43.1.43-universal.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@78d39a69\n[22:11:24] [main/WARN]: Attempted to select a dependency jar for JarJar which was passed in as source: expandability. Using Mod File: C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods\\expandability-7.0.0.jar\n[22:11:24] [main/WARN]: Attempted to select a dependency jar for JarJar which was passed in as source: apexcore. Using Mod File: C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\mods\\apexcore-1.19.2-7.3.0.jar\n[22:11:24] [main/INFO]: Found 4 dependencies adding them to mods collection\n[22:11:24] [main/INFO]: Found mod file MixinExtras-0.1.0-rc5.jar of type GAMELIBRARY with provider net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator@267517e4\n[22:11:24] [main/INFO]: Found mod file commonality-1.19.2-4.2.0.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator@267517e4\n[22:11:24] [main/INFO]: Found mod file flywheel-forge-1.19.2-0.6.7-8.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator@267517e4\n[22:11:24] [main/INFO]: Found mod file Registrate-MC1.19-1.1.5.jar of type GAMELIBRARY with provider net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator@267517e4\n[22:11:25] [main/INFO]: OptiFineTransformationService.transformers\n[22:11:25] [main/INFO]: Targets: 383\n[22:11:25] [main/INFO]: additionalClassesLocator: [optifine., net.optifine.]\n[22:11:26] [main/INFO]: Compatibility level set to JAVA_17\n[22:11:27] [main/INFO]: Successfully loaded Mixin Connector [tictim.paraglider.MixinConnector]\n[22:11:27] [main/INFO]: Launching target 'forgeclient' with arguments [--version, 1.19.2, --gameDir, C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft, --assetsDir, C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\assets, --uuid, c135223bb1483f12b12171c2ad9a333d, --username, THFOL, --assetIndex, 1.19, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, mojang, --versionType, HMCL 3.5.dev-c113670, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker]\n[22:11:27] [main/WARN]: Reference map 'morevillagers-forge-forge-refmap.json' for morevillagers.mixins.json could not be read. If this is a development environment you can ignore this message\n[22:11:28] [main/WARN]: Error loading class: me/jellysquid/mods/sodium/client/render/chunk/data/ChunkRenderData$Builder (java.lang.ClassNotFoundException: me.jellysquid.mods.sodium.client.render.chunk.data.ChunkRenderData$Builder)\n[22:11:28] [main/WARN]: @Mixin target me.jellysquid.mods.sodium.client.render.chunk.data.ChunkRenderData$Builder was not found flywheel.mixins.json:instancemanage.SodiumChunkRenderDataMixin\n[22:11:30] [main/WARN]: Discarding @Unique public method getAnimatedSkin in entityculling.mixins.json:DonorAbstractClientPlayerMixin because it already exists in net.minecraft.client.player.AbstractClientPlayer\n[22:11:33] [pool-3-thread-1/INFO]: Building unoptimized datafixer\n[22:11:35] [Render thread/WARN]: Assets URL 'union:/C:/Users/Administrator/AppData/Roaming/.minecraft/libraries/net/minecraft/client/1.19.2-20220805.130853/client-1.19.2-20220805.130853-srg.jar%23238!/assets/.mcassetsroot' uses unexpected schema\n[22:11:35] [Render thread/WARN]: Assets URL 'union:/C:/Users/Administrator/AppData/Roaming/.minecraft/libraries/net/minecraft/client/1.19.2-20220805.130853/client-1.19.2-20220805.130853-srg.jar%23238!/data/.mcassetsroot' uses unexpected schema\n[22:11:35] [Render thread/INFO]: Environment: authHost='https://skin.prinzeugen.net/api/yggdrasil/authserver', accountsHost='http://127.0.0.1:62857/https/api.mojang.com', sessionHost='http://127.0.0.1:62857/https/sessionserver.mojang.com', servicesHost='http://127.0.0.1:62857/https/api.minecraftservices.com', name='PROD'\n[22:11:35] [Render thread/INFO]: Setting user: THFOL\n[22:11:35] [Render thread/INFO]: Backend library: LWJGL version 3.3.1 build 7\n[22:11:36] [Render thread/INFO]: [OptiFine] \n[22:11:36] [Render thread/INFO]: [OptiFine] OptiFine_1.19.2_HD_U_H9\n[22:11:36] [Render thread/INFO]: [OptiFine] Build: 20220820-230904\n[22:11:36] [Render thread/INFO]: [OptiFine] OS: Windows 10 (amd64) version 10.0\n[22:11:36] [Render thread/INFO]: [OptiFine] Java: 19, Oracle Corporation\n[22:11:36] [Render thread/INFO]: [OptiFine] VM: Java HotSpot(TM) 64-Bit Server VM (mixed mode, sharing), Oracle Corporation\n[22:11:36] [Render thread/INFO]: [OptiFine] LWJGL: 3.4.0 Win32 WGL Null EGL OSMesa VisualC DLL\n[22:11:36] [Render thread/INFO]: [OptiFine] OpenGL: NVIDIA GeForce GTX 1660/PCIe/SSE2, version 3.2.0 NVIDIA 516.94, NVIDIA Corporation\n[22:11:36] [Render thread/INFO]: [OptiFine] OpenGL Version: 3.2.0\n[22:11:36] [Render thread/INFO]: [OptiFine] Maximum texture size: 32768x32768\n[22:11:36] [VersionCheck/INFO]: [OptiFine] Checking for new version\n[22:11:36] [Render thread/INFO]: [Shaders] OpenGL Version: 3.2.0 NVIDIA 516.94\n[22:11:36] [Render thread/INFO]: [Shaders] Vendor:  NVIDIA Corporation\n[22:11:36] [Render thread/INFO]: [Shaders] Renderer: NVIDIA GeForce GTX 1660/PCIe/SSE2\n[22:11:36] [Render thread/INFO]: [Shaders] Capabilities:  2.0  2.1  3.0  3.2  - \n[22:11:36] [Render thread/INFO]: [Shaders] GL_MAX_DRAW_BUFFERS: 8\n[22:11:36] [Render thread/INFO]: [Shaders] GL_MAX_COLOR_ATTACHMENTS: 8\n[22:11:36] [Render thread/INFO]: [Shaders] GL_MAX_TEXTURE_IMAGE_UNITS: 32\n[22:11:36] [Render thread/INFO]: [Shaders] Load shaders configuration.\n[22:11:36] [Render thread/INFO]: [Shaders] Save shaders configuration.\n[22:11:36] [Render thread/INFO]: [Shaders] No shaderpack loaded.\n[22:11:36] [Render thread/INFO]: [OptiFine] [Shaders] Delayed loading of item mappings after resources are loaded\n[22:11:36] [Render thread/INFO]: [OptiFine] [Shaders] Delayed loading of entity mappings after resources are loaded\n[22:11:36] [Modding Legacy/blue_skies/Supporters thread/INFO]: Attempting to load the Modding Legacy supporters list from https://moddinglegacy.com/supporters-changelogs/supporters.txt\n[22:11:36] [VersionCheck/INFO]: [OptiFine] java.io.FileNotFoundException: http://optifine.net/version/1.19.2/HD_U.txt\n[22:11:37] [modloading-worker-0/INFO]: Fixing MC-151457. Crafting remainder for minecraft:pufferfish_bucket is now minecraft:bucket.\n[22:11:37] [modloading-worker-0/INFO]: Fixing MC-151457. Crafting remainder for minecraft:salmon_bucket is now minecraft:bucket.\n[22:11:37] [modloading-worker-0/INFO]: Fixing MC-151457. Crafting remainder for minecraft:cod_bucket is now minecraft:bucket.\n[22:11:37] [modloading-worker-0/INFO]: Fixing MC-151457. Crafting remainder for minecraft:tropical_fish_bucket is now minecraft:bucket.\n[22:11:37] [modloading-worker-0/INFO]: Fixing MC-151457. Crafting remainder for minecraft:axolotl_bucket is now minecraft:bucket.\n[22:11:37] [modloading-worker-0/INFO]: Fixing MC-151457. Crafting remainder for minecraft:powder_snow_bucket is now minecraft:bucket.\n[22:11:37] [modloading-worker-0/INFO]: Fixing MC-151457. Crafting remainder for minecraft:tadpole_bucket is now minecraft:bucket.\n[22:11:37] [Thread-4/WARN]: Failed to fetch contributors data from url https://raw.githubusercontent.com/MehVahdJukaar/Supplementaries/master/credits.json, java.net.UnknownHostException: raw.githubusercontent.com\n[22:11:37] [Modding Legacy/blue_skies/Supporters thread/INFO]: Successfully loaded the Modding Legacy supporters list.\n[22:11:38] [modloading-worker-0/INFO]: Loaded config file.\n[22:11:38] [modloading-worker-0/INFO]: Saved config file.\n[22:11:38] [modloading-worker-0/INFO]: Meow~~ Miaow~~~\n[22:11:38] [modloading-worker-0/INFO]: loading json file and contents for paintings.\n[22:11:38] [modloading-worker-0/INFO]: **Paintings++ no longer copies the base file outside if the mod. **\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting abstract_blue , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting abstract_rainbow , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting abstract_red , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting abstract_sunset , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting arachnophobe , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting barn_owl , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting big_z , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting blue_bird , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting bluesclues , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting borgia , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting cane , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting cat_black , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting cat_gray , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting cat_orange , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting cat , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting colorful_squares , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting crest , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting danger_zone , 32 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting decorative_gun , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting exit_down , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting exit_up , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting exit_left , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting exit_right , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_bat , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_chicken , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_cow , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_creeper , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_dog , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_enderman , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_pig , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_pigman , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_silverfish , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_skeleton , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_squid , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting face_zombie , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting fishes , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting flowers , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting fruits , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting ghost , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting glowlamp , 32 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting glowstone_hourglass , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting iluvmc , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting link , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting mine_prosperity , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting no_trespassing_for_mobs , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting ocelot , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting penguin , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting pig_on_a_cliff , 32 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting pkmn_blue , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting pkmn_red , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting pkmn_green , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting plains_hut , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting portrait_2 , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting portrait , 32 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting prison , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting prosperity , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting rest , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting skeleton , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting sky , 32 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting skyblock , 32 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting snake , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting snow_landscape , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting subaraki , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting synth_city , 16 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting tapistry_a , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting tapistry_b , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting tapistry_purple , 16 x 32\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting torched , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting waterfall , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting whale , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting wheat_field , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Loaded json painting wolf_in_wheat , 32 x 16\n[22:11:38] [modloading-worker-0/INFO]: Reading out ResourcePacks to find painting related json files\n[22:11:38] [modloading-worker-0/INFO]: Reading `Quark Programmer Art.zip`\n[22:11:38] [modloading-worker-0/INFO]: Optifine detected.\n[22:11:38] [modloading-worker-0/INFO]: Forge mod loading, version 43.1.43, for MC 1.19.2 with MCP 20220805.130853\n[22:11:38] [modloading-worker-0/INFO]: MinecraftForge v43.1.43 Initialized\n[22:11:38] [modloading-worker-0/INFO]: Running Minecraft '1.19.2', & Forge '43.1.43' on Java '19, Oracle Corporation'\n[22:11:38] [modloading-worker-0/INFO]: Loading BlackListed sources from remote... (https://api.stopmodreposts.org/minecraft/sites.txt)\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.SpiralSpiresModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.PermafrostModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.NoMoreLavaPocketsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.NewStoneTypesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.NetherObsidianSpikesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.MonsterBoxModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.GlimmeringWealdModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.FairyRingsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.CorundumModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.ChorusVegetationModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.BlossomTreesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.BigStoneClustersModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.world.module.AzaleaWoodModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.VillagersFollowEmeraldsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.VexesDieWithTheirMastersModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.UtilityRecipesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.SnowGolemPlayerHeadsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.SimpleHarvestModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.SignEditingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.ShulkerPackingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.ReplaceScaffoldingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.ReacharoundPlacingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.PoisonPotatoUsageModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.PigLittersModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.PatTheDogsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.MoreNoteBlockSounds\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.MoreBannerLayersModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.LockRotationModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.HoeHarvestingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.GrabChickensModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.GlassShardModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.EnhancedLaddersModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.EmotesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.DragonScalesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.DoubleDoorOpeningModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.CompassesWorkEverywhereModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.CampfiresBoostElytraModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.BetterElytraRocketModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.AutomaticRecipeUnlockModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tweaks.module.ArmedArmorStandsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.TrowelModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.SlimeInABucketModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.SkullPikesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.SeedPouchModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.PickarangModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.PathfinderMapsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.ParrotEggsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.EndermoshMusicDiscModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.ColorRunesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.CameraModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.BundleRecipeModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.BottledCloudModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.BeaconRedirectionModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.AncientTomesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.AmbientDiscsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.tools.module.AbacusModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.mobs.module.WraithModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.mobs.module.ToretoiseModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.mobs.module.StonelingsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.mobs.module.ShibaModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.mobs.module.FoxhoundModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.mobs.module.ForgottenModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.mobs.module.CrabsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.management.module.QuickArmorSwappingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.management.module.ItemSharingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.management.module.InventorySortingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.management.module.HotbarChangerModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.management.module.ExpandedItemInteractionsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.management.module.EasyTransferingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.experimental.module.ZombieVillagersOnNormalModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.experimental.module.SpawnerReplacerModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.experimental.module.SawModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.experimental.module.OverlayShaderModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.experimental.module.NarratorReadoutModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.experimental.module.GameNerfsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.experimental.module.EnchantmentsBegoneModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.experimental.module.AdjustableChatModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.WoolShutsUpMinecartsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.VariantAnimalTexturesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.UsesForCursesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.UsageTickerModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.SoulCandlesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.MicrocraftingHelperModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.ImprovedTooltipsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.GreenerGrassModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.ChestSearchingModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.BackButtonKeybind\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.client.module.AutoWalkKeybindModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.WoodenPostsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.VerticalSlabsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.VerticalPlanksModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.VariantLaddersModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.VariantFurnacesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.VariantChestsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.VariantBookshelvesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.ThatchModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.SturdyStoneModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.StoolsModule\n[22:11:38] [modloading-worker-0/INFO]: Loading mod 'apexcore' TrustWorthiness...\n[22:11:38] [modloading-worker-0/INFO]: Loading mod 'fantasyfurniture' TrustWorthiness...\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.SoulSandstoneModule\n[22:11:38] [modloading-worker-0/FATAL]: Could not determine mod trust worthiness, Assuming Jar was downloaded from trusted source!\n[22:11:38] [modloading-worker-0/INFO]: Loaded mod 'fantasyfurniture' TrustWorthiness: UNKNOWN\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.ShinglesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.ShearVinesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.RopeModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.RawMetalBricksModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.NetherBrickFenceGateModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.MoreStoneVariantsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.MorePottedPlantsModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.MoreBrickTypesModule\n[22:11:38] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.MidoriModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.LeafCarpetModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.JapanesePaletteModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.IndustrialPaletteModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.HedgesModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.GrateModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.GoldBarsModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.GlassItemFrameModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.FramedGlassModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.DuskboundBlocksModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.CompressedBlocksModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.building.module.CelebratoryLampsModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.RedstoneRandomizerModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.PistonsMoveTileEntitiesModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.ObsidianPlateModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.MetalButtonsModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.JukeboxAutomationModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.IronRodModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.GravisandModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.FeedingTroughModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.EnderWatcherModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.DispensersPlaceBlocksModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.ChuteModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.content.automation.module.ChainsConnectBlocksModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.addons.oddities.module.TotemOfHoldingModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.addons.oddities.module.TinyPotatoModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.addons.oddities.module.PipesModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.addons.oddities.module.MatrixEnchantingModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.addons.oddities.module.MagnetsModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.addons.oddities.module.CrateModule\n[22:11:39] [modloading-worker-0/INFO]: Found Quark module class vazkii.quark.addons.oddities.module.BackpackModule\n[22:11:39] [modloading-worker-0/INFO]: Dispatching Module Step CONSTRUCT\n[22:11:39] [modloading-worker-0/INFO]: Dispatching Module Step CONSTRUCT_CLIENT\n[22:11:39] [modloading-worker-0/INFO]: ExpandAbility here, who dis?\n[22:11:39] [modloading-worker-0/INFO]: Loaded 564 BlackListed sources from remote\n[22:11:39] [modloading-worker-0/INFO]: Loading mod 'commonality' TrustWorthiness...\n[22:11:39] [modloading-worker-0/FATAL]: Could not determine mod trust worthiness, Assuming Jar was downloaded from trusted source!\n[22:11:39] [modloading-worker-0/INFO]: Loaded mod 'commonality' TrustWorthiness: UNKNOWN\n[22:11:40] [Render thread/INFO]: Dispatching Module Step REGISTER\n[22:11:41] [Render thread/INFO]: Dispatching Module Step POST_REGISTER\n[22:11:50] [Render thread/INFO]: Registered variant abstract_blue\n[22:11:50] [Render thread/INFO]: Registered variant abstract_rainbow\n[22:11:50] [Render thread/INFO]: Registered variant abstract_red\n[22:11:50] [Render thread/INFO]: Registered variant abstract_sunset\n[22:11:50] [Render thread/INFO]: Registered variant arachnophobe\n[22:11:50] [Render thread/INFO]: Registered variant barn_owl\n[22:11:50] [Render thread/INFO]: Registered variant big_z\n[22:11:50] [Render thread/INFO]: Registered variant blue_bird\n[22:11:50] [Render thread/INFO]: Registered variant bluesclues\n[22:11:50] [Render thread/INFO]: Registered variant borgia\n[22:11:50] [Render thread/INFO]: Registered variant cane\n[22:11:50] [Render thread/INFO]: Registered variant cat_black\n[22:11:50] [Render thread/INFO]: Registered variant cat_gray\n[22:11:50] [Render thread/INFO]: Registered variant cat_orange\n[22:11:50] [Render thread/INFO]: Registered variant cat\n[22:11:50] [Render thread/INFO]: Registered variant colorful_squares\n[22:11:50] [Render thread/INFO]: Registered variant crest\n[22:11:50] [Render thread/INFO]: Registered variant danger_zone\n[22:11:50] [Render thread/INFO]: Registered variant decorative_gun\n[22:11:50] [Render thread/INFO]: Registered variant exit_down\n[22:11:50] [Render thread/INFO]: Registered variant exit_up\n[22:11:50] [Render thread/INFO]: Registered variant exit_left\n[22:11:50] [Render thread/INFO]: Registered variant exit_right\n[22:11:50] [Render thread/INFO]: Registered variant face_bat\n[22:11:50] [Render thread/INFO]: Registered variant face_chicken\n[22:11:50] [Render thread/INFO]: Registered variant face_cow\n[22:11:50] [Render thread/INFO]: Registered variant face_creeper\n[22:11:50] [Render thread/INFO]: Registered variant face_dog\n[22:11:50] [Render thread/INFO]: Registered variant face_enderman\n[22:11:50] [Render thread/INFO]: Registered variant face_pig\n[22:11:50] [Render thread/INFO]: Registered variant face_pigman\n[22:11:50] [Render thread/INFO]: Registered variant face_silverfish\n[22:11:50] [Render thread/INFO]: Registered variant face_skeleton\n[22:11:50] [Render thread/INFO]: Registered variant face_squid\n[22:11:50] [Render thread/INFO]: Registered variant face_zombie\n[22:11:50] [Render thread/INFO]: Registered variant fishes\n[22:11:50] [Render thread/INFO]: Registered variant flowers\n[22:11:50] [Render thread/INFO]: Registered variant fruits\n[22:11:50] [Render thread/INFO]: Registered variant ghost\n[22:11:50] [Render thread/INFO]: Registered variant glowlamp\n[22:11:50] [Render thread/INFO]: Registered variant glowstone_hourglass\n[22:11:50] [Render thread/INFO]: Registered variant iluvmc\n[22:11:50] [Render thread/INFO]: Registered variant link\n[22:11:50] [Render thread/INFO]: Registered variant mine_prosperity\n[22:11:50] [Render thread/INFO]: Registered variant no_trespassing_for_mobs\n[22:11:50] [Render thread/INFO]: Registered variant ocelot\n[22:11:50] [Render thread/INFO]: Registered variant penguin\n[22:11:50] [Render thread/INFO]: Registered variant pig_on_a_cliff\n[22:11:50] [Render thread/INFO]: Registered variant pkmn_blue\n[22:11:50] [Render thread/INFO]: Registered variant pkmn_red\n[22:11:50] [Render thread/INFO]: Registered variant pkmn_green\n[22:11:50] [Render thread/INFO]: Registered variant plains_hut\n[22:11:50] [Render thread/INFO]: Registered variant portrait_2\n[22:11:50] [Render thread/INFO]: Registered variant portrait\n[22:11:50] [Render thread/INFO]: Registered variant prison\n[22:11:50] [Render thread/INFO]: Registered variant prosperity\n[22:11:50] [Render thread/INFO]: Registered variant rest\n[22:11:50] [Render thread/INFO]: Registered variant skeleton\n[22:11:50] [Render thread/INFO]: Registered variant sky\n[22:11:50] [Render thread/INFO]: Registered variant skyblock\n[22:11:50] [Render thread/INFO]: Registered variant snake\n[22:11:50] [Render thread/INFO]: Registered variant snow_landscape\n[22:11:50] [Render thread/INFO]: Registered variant subaraki\n[22:11:50] [Render thread/INFO]: Registered variant synth_city\n[22:11:50] [Render thread/INFO]: Registered variant tapistry_a\n[22:11:50] [Render thread/INFO]: Registered variant tapistry_b\n[22:11:50] [Render thread/INFO]: Registered variant tapistry_purple\n[22:11:50] [Render thread/INFO]: Registered variant torched\n[22:11:50] [Render thread/INFO]: Registered variant waterfall\n[22:11:50] [Render thread/INFO]: Registered variant whale\n[22:11:50] [Render thread/INFO]: Registered variant wheat_field\n[22:11:50] [Render thread/INFO]: Registered variant wolf_in_wheat\n[22:11:51] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.ClassNotFoundException: sun.misc.SharedSecrets\n[22:11:51] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.ClassNotFoundException: jdk.internal.misc.SharedSecrets\n[22:11:51] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.ClassNotFoundException: sun.misc.VM\n[22:11:51] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.reflect.InaccessibleObjectException: Unable to make public static long jdk.internal.misc.VM.maxDirectMemory() accessible: module java.base does not \"exports jdk.internal.misc\" to module net.optifine\n[22:11:51] [Render thread/INFO]: loaded in block colorizer\n[22:11:51] [Render thread/INFO]: Block Colors Registered.\n[22:11:51] [Render thread/INFO]: loaded in item colorizer\n[22:11:51] [Render thread/INFO]: Item Colors Registered.\n[22:11:51] [Render thread/ERROR]: Some mod loaded the Sheets class to early! This causes the banner texture maps to not contain modded patterns. Supplementaries will not attempt to fix...\n[22:11:51] [Render thread/INFO]: Dispatching Module Step SETUP_CLIENT\n[22:11:51] [Render thread/INFO]: Registering model layer quark:crab#main\n[22:11:51] [Render thread/INFO]: Registering model layer quark:shiba#main\n[22:11:51] [Render thread/INFO]: Registering model layer quark:quark_boat_chest#main\n[22:11:51] [Render thread/INFO]: Registering model layer quark:wraith#main\n[22:11:51] [Render thread/INFO]: Registering model layer quark:forgotten_hat#main\n[22:11:51] [Render thread/INFO]: Registering model layer quark:stoneling#main\n[22:11:51] [Render thread/INFO]: Registering model layer quark:toretoise#main\n[22:11:51] [Render thread/INFO]: Registering model layer quark:quark_boat#main\n[22:11:51] [Render thread/INFO]: Registering model layer quark:foxhound#main\n[22:11:51] [Render thread/INFO]: Registering model layer quark:backpack#main\n[22:11:52] [Render thread/INFO]: Dispatching Module Step REGISTER_TOOLTIP_COMPONENT_FACTORIES\n[22:11:52] [Render thread/INFO]: Dispatching Module Step REGISTER_KEYBINDS\n[22:11:52] [Render thread/INFO]: Narrator library for x64 successfully loaded\n[22:11:52] [Render thread/INFO]: Reloading ResourceManager: Default, Mod Resources, Supplementaries Generated Pack, quark-emote-pack\n[22:11:52] [Render thread/INFO]: Generated runtime CLIENT_RESOURCES for pack Supplementaries Generated Pack in: 258 ms\n[22:11:53] [Render thread/INFO]: [OptiFine] *** Reloading textures ***\n[22:11:53] [Render thread/INFO]: [OptiFine] Resource packs: Mod Resources, Supplementaries Generated Pack, quark-emote-pack\n[22:11:53] [Render thread/INFO]: [OptiFine] EmissiveTextures: Loading optifine/emissive.properties\n[22:11:53] [Render thread/INFO]: [OptiFine] *** Reflector Forge ***\n[22:11:53] [Render thread/WARN]: [OptiFine] (Reflector) More than one method found: net.minecraftforge.common.ForgeHooks.onLivingSetAttackTarget\n[22:11:53] [Render thread/WARN]: [OptiFine] (Reflector)  - public static void net.minecraftforge.common.ForgeHooks.onLivingSetAttackTarget(net.minecraft.world.entity.LivingEntity,net.minecraft.world.entity.LivingEntity)\n[22:11:53] [Render thread/WARN]: [OptiFine] (Reflector)  - public static void net.minecraftforge.common.ForgeHooks.onLivingSetAttackTarget(net.minecraft.world.entity.LivingEntity,net.minecraft.world.entity.LivingEntity,net.minecraftforge.event.entity.living.LivingChangeTargetEvent$ILivingTargetType)\n[22:11:53] [Render thread/INFO]: [OptiFine] *** Reflector Vanilla ***\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Redstone Randomizer\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Pistons Move Tile Entities\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Obsidian Plate\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Metal Buttons\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Jukebox Automation\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Iron Rod\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Gravisand\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Feeding Trough\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Ender Watcher\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Dispensers Place Blocks\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Chute\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Chains Connect Blocks\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Wooden Posts\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Vertical Slabs\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Vertical Planks\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Variant Ladders\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Variant Furnaces\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Variant Chests\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Variant Bookshelves\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Thatch\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Sturdy Stone\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Stools\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Soul Sandstone\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Shingles\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Shear Vines\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Rope\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Raw Metal Bricks\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Nether Brick Fence Gate\n[22:11:53] [modloading-worker-0/INFO]: Loading Module More Stone Variants\n[22:11:53] [modloading-worker-0/INFO]: Loading Module More Potted Plants\n[22:11:53] [modloading-worker-0/INFO]: Loading Module More Brick Types\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Midori\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Leaf Carpet\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Japanese Palette\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Industrial Palette\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Hedges\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Grate\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Gold Bars\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Glass Item Frame\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Framed Glass\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Duskbound Blocks\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Compressed Blocks\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Celebratory Lamps\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Quick Armor Swapping\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Item Sharing\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Inventory Sorting\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Hotbar Changer\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Expanded Item Interactions\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Easy Transfering\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Trowel\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Slime In A Bucket\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Seed Pouch\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Pickarang\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Pathfinder Maps\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Parrot Eggs\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Endermosh Music Disc\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Color Runes\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Camera\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Bundle Recipe\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Bottled Cloud\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Beacon Redirection\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Ancient Tomes\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Ambient Discs\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Abacus\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Villagers Follow Emeralds\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Vexes Die With Their Masters\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Utility Recipes\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Snow Golem Player Heads\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Simple Harvest\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Sign Editing\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Shulker Packing\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Replace Scaffolding\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Reacharound Placing\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Poison Potato Usage\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Pig Litters\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Pat The Dogs\n[22:11:53] [modloading-worker-0/INFO]: Loading Module More Note Block Sounds\n[22:11:53] [modloading-worker-0/INFO]: Loading Module More Banner Layers\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Lock Rotation\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Hoe Harvesting\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Grab Chickens\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Glass Shard\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Enhanced Ladders\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Emotes\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Dragon Scales\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Double Door Opening\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Compasses Work Everywhere\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Campfires Boost Elytra\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Better Elytra Rocket\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Automatic Recipe Unlock\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Armed Armor Stands\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Skull Pikes\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Spiral Spires\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Permafrost\n[22:11:53] [modloading-worker-0/INFO]: Loading Module No More Lava Pockets\n[22:11:53] [modloading-worker-0/INFO]: Loading Module New Stone Types\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Nether Obsidian Spikes\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Monster Box\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Glimmering Weald\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Fairy Rings\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Corundum\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Chorus Vegetation\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Blossom Trees\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Big Stone Clusters\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Azalea Wood\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Wraith\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Toretoise\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Stonelings\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Shiba\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Foxhound\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Forgotten\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Crabs\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Wool Shuts Up Minecarts\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Variant Animal Textures\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Uses For Curses\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Usage Ticker\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Soul Candles\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Microcrafting Helper\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Improved Tooltips\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Greener Grass\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Chest Searching\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Back Button Keybind\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Auto Walk Keybind\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Zombie Villagers On Normal\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Spawner Replacer\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Saw\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Overlay Shader\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Narrator Readout\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Game Nerfs\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Enchantments Begone\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Adjustable Chat\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Totem Of Holding\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Tiny Potato\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Pipes\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Matrix Enchanting\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Magnets\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Crate\n[22:11:53] [modloading-worker-0/INFO]: Loading Module Backpack\n[22:11:53] [modloading-worker-0/INFO]: Dispatching Module Step CONFIG_CHANGED\n[22:11:53] [modloading-worker-0/INFO]: Dispatching Module Step CONFIG_CHANGED_CLIENT\n[22:11:53] [Worker-Main-5/INFO]: [OptiFine] Multitexture: false\n[22:11:53] [Worker-Main-1/INFO]: [OptiFine] Multitexture: false\n[22:11:53] [Worker-Main-1/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:11:53] [Worker-Main-5/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:11:54] [Worker-Main-4/WARN]: File cataclysm:sounds/entity/monstrosityhurt.ogg does not exist, cannot add it to event cataclysm:monstrosityhurt\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/wednesday.ogg does not exist, cannot add it to event quark:entity.frog.wednesday\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/die.ogg does not exist, cannot add it to event quark:entity.frog.die\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/hurt1.ogg does not exist, cannot add it to event quark:entity.frog.hurt\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/hurt2.ogg does not exist, cannot add it to event quark:entity.frog.hurt\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/hurt3.ogg does not exist, cannot add it to event quark:entity.frog.hurt\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/hurt4.ogg does not exist, cannot add it to event quark:entity.frog.hurt\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/idle1.ogg does not exist, cannot add it to event quark:entity.frog.idle\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/idle2.ogg does not exist, cannot add it to event quark:entity.frog.idle\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/idle3.ogg does not exist, cannot add it to event quark:entity.frog.idle\n[22:11:54] [Worker-Main-4/WARN]: File quark:sounds/entity/frog/idle4.ogg does not exist, cannot add it to event quark:entity.frog.idle\n[22:11:54] [Worker-Main-4/INFO]: [OptiFine] Multitexture: false\n[22:11:54] [Worker-Main-4/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:11:55] [Worker-Main-2/INFO]: [OptiFine] Multitexture: false\n[22:11:55] [Worker-Main-2/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:11:57] [Worker-Main-3/WARN]: Unable to load model: 'minecraft:shruster' referenced from: stalwart_dungeons:shruster#: {}\n[22:11:57] [Worker-Main-3/WARN]: java.io.FileNotFoundException: minecraft:models/shruster.json\n[22:12:06] [Worker-Main-3/WARN]: Unable to load model: 'minecraft:item/celeste2a' referenced from: stalwart_dungeons:tungsten_shield#inventory: {}\n[22:12:06] [Worker-Main-3/WARN]: java.io.FileNotFoundException: minecraft:models/item/celeste2a.json\n[22:12:06] [Worker-Main-3/WARN]: Unable to load model: 'minecraft:item/celeste2b' referenced from: stalwart_dungeons:tungsten_shield#inventory: {}\n[22:12:06] [Worker-Main-3/WARN]: java.io.FileNotFoundException: minecraft:models/item/celeste2b.json\n[22:12:06] [Worker-Main-3/WARN]: Unable to load model: 'minecraft:hammer' referenced from: stalwart_dungeons:tungsten_hammer#inventory: {}\n[22:12:06] [Worker-Main-3/WARN]: java.io.FileNotFoundException: minecraft:models/hammer.json\n[22:12:06] [Worker-Main-3/WARN]: Unable to load model: 'minecraft:awfulgun' referenced from: stalwart_dungeons:awful_gun#inventory: {}\n[22:12:06] [Worker-Main-3/WARN]: java.io.FileNotFoundException: minecraft:models/awfulgun.json\n[22:12:06] [Worker-Main-3/WARN]: Unable to load model: 'decorative_blocks:blockstate_copy_item#inventory' referenced from: decorative_blocks:blockstate_copy_item#inventory: {}\n[22:12:06] [Worker-Main-3/WARN]: java.io.FileNotFoundException: decorative_blocks:models/item/blockstate_copy_item.json\n[22:12:06] [Worker-Main-3/WARN]: Unable to load model: 'magistuarmory:item/commonshield_blocking' referenced from: magistuarmory:wood_ellipticalshield#inventory: {}\n[22:12:06] [Worker-Main-3/WARN]: java.io.FileNotFoundException: magistuarmory:models/item/commonshield_blocking.json\n[22:12:06] [Worker-Main-3/WARN]: Unable to load model: 'minecraft:useless_sword_icon' referenced from: useless_sword:uselesswordicon#inventory: {}\n[22:12:06] [Worker-Main-3/WARN]: java.io.FileNotFoundException: minecraft:models/useless_sword_icon.json\n[22:12:07] [Worker-Main-3/WARN]: Unable to load model: 'minecraft:brass_drone' referenced from: create_sa:brass_drone_item#inventory: {}\n[22:12:07] [Worker-Main-3/WARN]: java.io.FileNotFoundException: minecraft:models/brass_drone.json\n[22:12:07] [Worker-Main-3/WARN]: Unable to load model: 'minecraft:flamethrower' referenced from: create_sa:flamethrower#inventory: {}\n[22:12:07] [Worker-Main-3/WARN]: java.io.FileNotFoundException: minecraft:models/flamethrower.json\n[22:12:07] [Worker-Main-2/INFO]: [OptiFine] Scaled non power of 2: jei:gui/icons/recipe_transfer, 7 -> 14\n[22:12:07] [Worker-Main-7/WARN]: Failed to load patreon contributor perks\n[22:12:07] [Worker-Main-7/INFO]: HELLO FROM PREINIT\n[22:12:07] [Worker-Main-7/INFO]: Initializing Vanilla Compat\n[22:12:07] [Worker-Main-7/INFO]: Flammability Registered!\n[22:12:07] [Worker-Main-3/INFO]: Dispatching Module Step REIGSTER_ADDITIONAL_MODELS\n[22:12:07] [Worker-Main-7/INFO]: Gobber common setup complete\n[22:12:07] [Worker-Main-7/INFO]: Dispatching Module Step CONFIG_CHANGED\n[22:12:07] [Worker-Main-7/INFO]: Dispatching Module Step CONFIG_CHANGED_CLIENT\n[22:12:07] [Worker-Main-7/INFO]: Dispatching Module Step SETUP\n[22:12:07] [Worker-Main-3/WARN]: Unable to load model: 'supplementaries:blocks/wall_lanterns/wall_lantern_glowstone' referenced from: supplementaries:blocks/wall_lanterns/wall_lantern_glowstone: {}\n[22:12:07] [Worker-Main-3/WARN]: java.io.FileNotFoundException: supplementaries:models/blocks/wall_lanterns/wall_lantern_glowstone.json\n[22:12:07] [Worker-Main-3/WARN]: Unable to load model: 'supplementaries:blocks/wall_lanterns/wall_lantern_gold' referenced from: supplementaries:blocks/wall_lanterns/wall_lantern_gold: {}\n[22:12:07] [Worker-Main-3/WARN]: java.io.FileNotFoundException: supplementaries:models/blocks/wall_lanterns/wall_lantern_gold.json\n[22:12:07] [Worker-Main-3/WARN]: Unable to load model: 'supplementaries:blocks/wall_lanterns/wall_lantern_cryptic' referenced from: supplementaries:blocks/wall_lanterns/wall_lantern_cryptic: {}\n[22:12:07] [Worker-Main-3/WARN]: java.io.FileNotFoundException: supplementaries:models/blocks/wall_lanterns/wall_lantern_cryptic.json\n[22:12:07] [Worker-Main-3/WARN]: Unable to load model: 'supplementaries:blocks/wall_lanterns/wall_lantern_copper' referenced from: supplementaries:blocks/wall_lanterns/wall_lantern_copper: {}\n[22:12:07] [Worker-Main-3/WARN]: java.io.FileNotFoundException: supplementaries:models/blocks/wall_lanterns/wall_lantern_copper.json\n[22:12:07] [Forge Version Check/INFO]: [blue_skies] Starting version check at http://changelogs.moddinglegacy.com/blue-skies.json\n[22:12:07] [Quark Contributor Loading Thread/ERROR]: Failed to load patreon information\njava.net.UnknownHostException: raw.githubusercontent.com\n\tat sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:560) ~[?:?]\n\tat java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) ~[?:?]\n\tat java.net.Socket.connect(Socket.java:666) ~[?:?]\n\tat sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:304) ~[?:?]\n\tat sun.net.NetworkClient.doConnect(NetworkClient.java:178) ~[?:?]\n\tat sun.net.www.http.HttpClient.openServer(HttpClient.java:531) ~[?:?]\n\tat sun.net.www.http.HttpClient.openServer(HttpClient.java:636) ~[?:?]\n\tat sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:264) ~[?:?]\n\tat sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:378) ~[?:?]\n\tat sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127) ~[?:?]\n\tat sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1661) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1585) ~[?:?]\n\tat sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:224) ~[?:?]\n\tat vazkii.quark.base.handler.ContributorRewardHandler$ThreadContributorListLoader.run(ContributorRewardHandler.java:133) [Quark-3.3-371.jar%23219!/:3.3-371]\n[22:12:07] [Worker-Main-3/WARN]: Unable to load model: 'supplementaries:blocks/wall_lanterns/wall_lantern_brass' referenced from: supplementaries:blocks/wall_lanterns/wall_lantern_brass: {}\n[22:12:07] [Worker-Main-3/WARN]: java.io.FileNotFoundException: supplementaries:models/blocks/wall_lanterns/wall_lantern_brass.json\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\end-biomes.json5\" was read.\n[22:12:07] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack type: Supplementaries Generated Pack\n[22:12:07] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack type: Supplementaries Generated Pack\n[22:12:07] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack type: Supplementaries Generated Pack\n[22:12:07] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack type: Supplementaries Generated Pack\n[22:12:07] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[22:12:07] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[22:12:07] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[22:12:07] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\nether-biomes.json5\" was read.\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\overworld\\byg-overworld-biomes.json5\" was read.\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\surface_rules\\end_surface_rules.json5\" was read.\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\surface_rules\\overworld_surface_rules.json5\" was read.\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\surface_rules\\nether_surface_rules.json5\" was read.\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\growing-patterns.json5\" was read.\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\trades.json5\" was read.\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\settings.json5\" was read.\n[22:12:07] [Worker-Main-7/INFO]: \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\biomepedia.json5\" was read.\n[22:12:07] [Worker-Main-7/INFO]: Created compressed zip back up for \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\" in \"C:\\Users\\Administrator\\AppData\\Roaming\\.minecraft\\config\\byg\\backups\\last_working_configs_backup.zip\"\n[22:12:07] [Worker-Main-7/INFO]: BYG: Added strippable Blocks...\n[22:12:07] [Render thread/INFO]: Registered the class com.tiviacz.travelersbackpack.fluids.effects.WaterEffect as a FluidEffect for Water with the ID 0\n[22:12:07] [Render thread/INFO]: Registered the class com.tiviacz.travelersbackpack.fluids.effects.LavaEffect as a FluidEffect for Lava with the ID 1\n[22:12:07] [Render thread/INFO]: Registered the class com.tiviacz.travelersbackpack.fluids.effects.PotionEffect as a FluidEffect for fluid_type.travelersbackpack.potion with the ID 2\n[22:12:07] [Render thread/INFO]: Registered the class com.tiviacz.travelersbackpack.fluids.effects.MilkEffect as a FluidEffect for Milk with the ID 3\n[22:12:07] [Render thread/INFO]: Finished mod setup in: 52 ms\n[22:12:08] [Render thread/INFO]: [structure_gel] Registering data for structure_gel:loot_table_alias\n[22:12:08] [Render thread/INFO]: [structure_gel] Registering data for structure_gel:data_handler_type\n[22:12:08] [Render thread/INFO]: [structure_gel] Registering data for structure_gel:dynamic_spawner\n[22:12:08] [Render thread/INFO]: [structure_gel] Registering data for structure_gel:jigsaw_type\n[22:12:08] [Worker-Main-4/INFO]: [OptiFine] Scaled too small texture: paraglider:mob_effect/exhausted, 1 -> 8\n[22:12:08] [Render thread/INFO]: Registered region minecraft:overworld to index 0 for type OVERWORLD\n[22:12:08] [Render thread/INFO]: Registered region minecraft:nether to index 0 for type NETHER\n[22:12:08] [Render thread/INFO]: Registered region biomesoplenty:overworld_primary to index 1 for type OVERWORLD\n[22:12:08] [Render thread/INFO]: Registered region biomesoplenty:overworld_secondary to index 2 for type OVERWORLD\n[22:12:08] [Render thread/INFO]: Registered region biomesoplenty:overworld_rare to index 3 for type OVERWORLD\n[22:12:08] [Render thread/INFO]: Registered region biomesoplenty:nether_common to index 1 for type NETHER\n[22:12:08] [Render thread/INFO]: Registered region biomesoplenty:nether_rare to index 2 for type NETHER\n[22:12:08] [Render thread/INFO]: Initializing BYG network...\n[22:12:08] [Render thread/INFO]: Initialized BYG network!\n[22:12:08] [Render thread/INFO]: Registered region byg:region_0 to index 4 for type OVERWORLD\n[22:12:08] [Render thread/INFO]: Registered region byg:region_1 to index 5 for type OVERWORLD\n[22:12:08] [Render thread/INFO]: Registered region byg:region_2 to index 6 for type OVERWORLD\n[22:12:08] [Render thread/INFO]: Registered region byg:region_3 to index 7 for type OVERWORLD\n[22:12:08] [Worker-Main-5/INFO]: [OptiFine] Scaled too small texture: blue_skies:particle/frose_snow_1, 1 -> 8\n[22:12:08] [Worker-Main-5/INFO]: [OptiFine] Scaled too small texture: blue_skies:particle/frose_snow_0, 1 -> 8\n[22:12:08] [Worker-Main-5/INFO]: [OptiFine] Scaled non power of 2: twilightforest:particle/fallen_leaf, 5 -> 10\n[22:12:08] [Worker-Main-5/INFO]: [OptiFine] Scaled too small texture: effective_fg:particle/droplet, 1 -> 8\n[22:12:08] [Render thread/INFO]: Registered synced data key cgm:aiming for minecraft:player\n[22:12:08] [Render thread/INFO]: Registered synced data key cgm:shooting for minecraft:player\n[22:12:08] [Render thread/INFO]: Registered synced data key cgm:reloading for minecraft:player\n[22:12:08] [Worker-Main-2/INFO]: Loading Xaero's World Map - Stage 1/2\n[22:12:08] [Worker-Main-5/INFO]: Client Side Setup.\n[22:12:08] [Worker-Main-5/INFO]: BlockEntities Renderers Bound.\n[22:12:08] [Worker-Main-5/INFO]: Then Entities Renderers Bound.\n[22:12:08] [Worker-Main-5/INFO]: CreativeInv Events Registered.\n[22:12:08] [Worker-Main-5/INFO]: Property Overrides Registered.\n[22:12:08] [Worker-Main-5/INFO]: Nekoration Screens Registered.\n[22:12:08] [Worker-Main-1/INFO]: Loading Xaero's Minimap - Stage 1/2\n[22:12:08] [Worker-Main-4/INFO]: Gobber client setup complete\n[22:12:08] [Worker-Main-4/INFO]: Dispatching Module Step SETUP_CLIENT\n[22:12:08] [Worker-Main-5/INFO]: BYG: \"Client Setup\" Event Complete!\n[22:12:09] [Forge Version Check/INFO]: [blue_skies] Found status: BETA Current: 1.3.20 Target: null\n[22:12:09] [Forge Version Check/INFO]: [cgm] Starting version check at https://raw.githubusercontent.com/MrCrayfish/ModUpdates/master/cgm/update.json\n[22:12:09] [Forge Version Check/WARN]: Failed to process update information\njava.net.ConnectException: null\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:846) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\nCaused by: java.net.ConnectException\n\tat jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1045) ~[java.net.http:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:224) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\nCaused by: java.nio.channels.UnresolvedAddressException\n\tat sun.nio.ch.Net.checkAddress(Net.java:149) ~[?:?]\n\tat sun.nio.ch.Net.checkAddress(Net.java:157) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.checkRemote(SocketChannelImpl.java:842) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:865) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:208) ~[java.net.http:?]\n\tat java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:210) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\n[22:12:09] [Forge Version Check/INFO]: [controlling] Starting version check at https://updates.blamejared.com/get?n=controlling&gv=1.19.2\n[22:12:09] [Forge Version Check/INFO]: [controlling] Found status: BETA Current: 10.0+6 Target: 10.0+6\n[22:12:09] [Forge Version Check/INFO]: [travelersbackpack] Starting version check at https://gist.githubusercontent.com/Tiviacz1337/906937677aa472285dff9d6c2a189d5e/raw\n[22:12:10] [Render thread/INFO]: Loading Xaero's Minimap - Stage 2/2\n[22:12:11] [Render thread/ERROR]: suppressed exception\njava.net.SocketTimeoutException: Connect timed out\n\tat jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67) ~[?:?]\n\tat java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500) ~[?:?]\n\tat java.lang.reflect.Constructor.newInstance(Constructor.java:484) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:2044) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:2039) ~[?:?]\n\tat java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:2038) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1605) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1585) ~[?:?]\n\tat xaero.common.misc.Internet.checkModVersion(Internet.java:53) ~[Xaeros_Minimap_22.16.0_Forge_1.19.1.jar%23236!/:22.16.0]\n\tat xaero.minimap.XaeroMinimap.loadLater(XaeroMinimap.java:221) ~[Xaeros_Minimap_22.16.0_Forge_1.19.1.jar%23236!/:22.16.0]\n\tat java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804) ~[?:?]\n\tat net.minecraftforge.fml.DeferredWorkQueue.lambda$makeRunnable$1(DeferredWorkQueue.java:67) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.DeferredWorkQueue.makeRunnable(DeferredWorkQueue.java:63) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.DeferredWorkQueue.lambda$runTasks$0(DeferredWorkQueue.java:57) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.util.concurrent.ConcurrentLinkedDeque.forEach(ConcurrentLinkedDeque.java:1650) ~[?:?]\n\tat net.minecraftforge.fml.DeferredWorkQueue.runTasks(DeferredWorkQueue.java:57) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.core.ParallelTransition.lambda$finalActivityGenerator$2(ParallelTransition.java:35) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:646) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_143940_(SimpleReloadInstance.java:69) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.m_6367_(BlockableEventLoop.java:198) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.ReentrantBlockableEventLoop.m_6367_(ReentrantBlockableEventLoop.java:23) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.m_7245_(BlockableEventLoop.java:163) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.m_18699_(BlockableEventLoop.java:140) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.Minecraft.m_91383_(Minecraft.java:1072) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.Minecraft.m_91374_(Minecraft.java:700) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.main.Main.m_239872_(Main.java:212) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.main.Main.main(Main.java:51) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]\n\tat java.lang.reflect.Method.invoke(Method.java:578) ~[?:?]\n\tat net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:27) ~[fmlloader-1.19.2-43.1.43.jar%2397!/:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:106) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:77) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) [bootstraplauncher-1.1.2.jar:?]\nCaused by: java.net.SocketTimeoutException: Connect timed out\n\tat sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:539) ~[?:?]\n\tat sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:585) ~[?:?]\n\tat java.net.Socket.connect(Socket.java:666) ~[?:?]\n\tat sun.net.NetworkClient.doConnect(NetworkClient.java:178) ~[?:?]\n\tat sun.net.www.http.HttpClient.openServer(HttpClient.java:531) ~[?:?]\n\tat sun.net.www.http.HttpClient.openServer(HttpClient.java:636) ~[?:?]\n\tat sun.net.www.http.HttpClient.<init>(HttpClient.java:279) ~[?:?]\n\tat sun.net.www.http.HttpClient.New(HttpClient.java:384) ~[?:?]\n\tat sun.net.www.http.HttpClient.New(HttpClient.java:406) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1308) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1056) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.followRedirect0(HttpURLConnection.java:2898) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2807) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1923) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1585) ~[?:?]\n\tat sun.net.www.protocol.http.HttpURLConnection.getHeaderField(HttpURLConnection.java:3232) ~[?:?]\n\tat java.net.URLConnection.getHeaderFieldLong(URLConnection.java:645) ~[?:?]\n\tat java.net.URLConnection.getContentLengthLong(URLConnection.java:509) ~[?:?]\n\tat xaero.common.misc.Internet.checkModVersion(Internet.java:51) ~[Xaeros_Minimap_22.16.0_Forge_1.19.1.jar%23236!/:22.16.0]\n\t... 30 more\n[22:12:11] [Render thread/INFO]: Xaero's Minimap: World Map found!\n[22:12:11] [Render thread/INFO]: Optifine!\n[22:12:11] [Render thread/INFO]: Xaero's Minimap: No Vivecraft!\n[22:12:11] [Render thread/WARN]: Mod 'xaerominimap' took 1.240 s to run a deferred task.\n[22:12:11] [Render thread/INFO]: Loading Xaero's World Map - Stage 2/2\n[22:12:11] [Render thread/INFO]: New world map region cache hash code: 1033525412\n[22:12:11] [Render thread/INFO]: Xaero's WorldMap Mod: Xaero's minimap found!\n[22:12:11] [Render thread/INFO]: Optifine!\n[22:12:11] [Render thread/INFO]: Xaero's World Map: No Vivecraft!\n[22:12:16] [Worker-Main-3/WARN]: Unable to resolve texture reference: #missing in delightful:item/cantaloupe\n[22:12:16] [Worker-Main-3/WARN]: Unable to resolve texture reference: #missing in delightful:block/cantaloupe\n[22:12:16] [Worker-Main-3/WARN]: Unable to resolve texture reference: #missing in additionalguns:item/angled_grip\n[22:12:16] [Worker-Main-3/WARN]: Unable to resolve texture reference: #missing in cataclysm:item/infernal_forge\n[22:12:16] [Worker-Main-3/WARN]: Unable to resolve texture reference: #missing in additionalguns:item/ace_of_spades\n[22:12:16] [Worker-Main-3/WARN]: Unable to resolve texture reference: #missing in additionalguns:item/m1911\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] Multipass connected textures: false\n[22:12:16] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack type: Supplementaries Generated Pack\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] Multipass connected textures: false\n[22:12:16] [Worker-Main-3/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] Multipass connected textures: false\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/00_glass_white/glass_pane_white.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/00_glass_white/glass_white.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/01_glass_orange/glass_orange.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/01_glass_orange/glass_pane_orange.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/02_glass_magenta/glass_magenta.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/02_glass_magenta/glass_pane_magenta.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/03_glass_light_blue/glass_light_blue.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/03_glass_light_blue/glass_pane_light_blue.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/04_glass_yellow/glass_pane_yellow.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/04_glass_yellow/glass_yellow.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/05_glass_lime/glass_lime.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/05_glass_lime/glass_pane_lime.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/06_glass_pink/glass_pane_pink.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/06_glass_pink/glass_pink.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/07_glass_gray/glass_gray.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/07_glass_gray/glass_pane_gray.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/08_glass_light_gray/glass_light_gray.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/08_glass_light_gray/glass_pane_light_gray.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/09_glass_cyan/glass_cyan.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/09_glass_cyan/glass_pane_cyan.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/10_glass_purple/glass_pane_purple.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/10_glass_purple/glass_purple.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/11_glass_blue/glass_blue.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/11_glass_blue/glass_pane_blue.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/12_glass_brown/glass_brown.properties\n[22:12:16] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/12_glass_brown/glass_pane_brown.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/13_glass_green/glass_green.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/13_glass_green/glass_pane_green.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/14_glass_red/glass_pane_red.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/14_glass_red/glass_red.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/15_glass_black/glass_black.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/15_glass_black/glass_pane_black.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/20_glass/glass.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/20_glass/glass_pane.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/21_tinted_glass/tinted_glass.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/30_bookshelf/bookshelf.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/40_sandstone/sandstone.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/41_red_sandstone/red_sandstone.properties\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] Multipass connected textures: false\n[22:12:17] [Worker-Main-3/INFO]: [OptiFine] BetterGrass: Parsing default configuration optifine/bettergrass.properties\n[22:12:24] [Forge Version Check/WARN]: Failed to process update information\njava.net.http.HttpTimeoutException: request timed out\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:844) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n[22:12:24] [Forge Version Check/INFO]: [additionalguns] Starting version check at https://raw.githubusercontent.com/Pinelog-Studios/VersionChecker/main/additionalguns/update.json\n[22:12:24] [Forge Version Check/WARN]: Failed to process update information\njava.net.ConnectException: null\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:846) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\nCaused by: java.net.ConnectException\n\tat jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1045) ~[java.net.http:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:224) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\nCaused by: java.nio.channels.UnresolvedAddressException\n\tat sun.nio.ch.Net.checkAddress(Net.java:149) ~[?:?]\n\tat sun.nio.ch.Net.checkAddress(Net.java:157) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.checkRemote(SocketChannelImpl.java:842) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:865) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:208) ~[java.net.http:?]\n\tat java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:210) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\n[22:12:24] [Forge Version Check/INFO]: [bookshelf] Starting version check at https://updates.blamejared.com/get?n=bookshelf&gv=1.19.2\n[22:12:24] [Forge Version Check/INFO]: [bookshelf] Found status: BETA_OUTDATED Current: 16.1.8 Target: 16.1.9\n[22:12:24] [Forge Version Check/INFO]: [mcwdoors] Starting version check at https://raw.githubusercontent.com/sketchmacaw/macawsmods/master/doors.json\n[22:12:24] [Forge Version Check/WARN]: Failed to process update information\njava.net.ConnectException: null\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:846) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\nCaused by: java.net.ConnectException\n\tat jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1045) ~[java.net.http:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:224) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\nCaused by: java.nio.channels.UnresolvedAddressException\n\tat sun.nio.ch.Net.checkAddress(Net.java:149) ~[?:?]\n\tat sun.nio.ch.Net.checkAddress(Net.java:157) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.checkRemote(SocketChannelImpl.java:842) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:865) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:208) ~[java.net.http:?]\n\tat java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:210) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\n[22:12:24] [Forge Version Check/INFO]: [additional_lights] Starting version check at https://gist.githubusercontent.com/mgen256/59abe85e7950f2319e7538afe2f910ba/raw\n[22:12:27] [Worker-Main-3/INFO]: [OptiFine] Mipmap levels: 4\n[22:12:27] [Worker-Main-3/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:12:27] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/wood_buckler_pattern.png not found\n[22:12:27] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/netherite_target_pattern.png not found\n[22:12:27] [Worker-Main-5/ERROR]: Using missing texture, file minecraft:textures/items/tin_chivalrylance.png not found\n[22:12:27] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/steel_target_pattern.png not found\n[22:12:27] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/netherite_buckler_pattern.png not found\n[22:12:27] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/bronze_buckler_pattern.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file minecraft:textures/block/soul_candle.png not found\n[22:12:27] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/copper_target_pattern.png not found\n[22:12:27] [Worker-Main-4/ERROR]: Using missing texture, file minecraft:textures/items/silver_chivalrylance.png not found\n[22:12:27] [Worker-Main-5/ERROR]: Using missing texture, file minecraft:textures/items/netherite_chivalrylance.png not found\n[22:12:27] [Worker-Main-7/ERROR]: Using missing texture, file minecraft:textures/items/wood_chivalrylance.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/stone_buckler_pattern.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file minecraft:textures/items/bronze_chivalrylance.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/bronze_target_pattern.png not found\n[22:12:27] [Worker-Main-4/ERROR]: Using missing texture, file minecraft:textures/items/gold_chivalrylance.png not found\n[22:12:27] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/diamond_target_pattern.png not found\n[22:12:27] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/silver_target_pattern.png not found\n[22:12:27] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/diamond_buckler_pattern.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/steel_buckler_pattern.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/iron_target_pattern.png not found\n[22:12:27] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/gold_target_pattern.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file biomesoplenty:textures/block/mud_bricks.png not found\n[22:12:27] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/copper_buckler_pattern.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/stone_target_pattern.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/tin_buckler_pattern.png not found\n[22:12:27] [Worker-Main-7/ERROR]: Using missing texture, file buzzier_bees:textures/block/soul_candle_lit.png not found\n[22:12:27] [Worker-Main-4/ERROR]: Using missing texture, file supplementaries:textures/items/sconce_nether_brass.png not found\n[22:12:27] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/wood_target_pattern.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file minecraft:textures/items/steel_chivalrylance.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/gold_buckler_pattern.png not found\n[22:12:27] [Worker-Main-5/ERROR]: Using missing texture, file supplementaries:textures/blocks/sconce_nether_brass.png not found\n[22:12:27] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/tin_target_pattern.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/iron_buckler_pattern.png not found\n[22:12:27] [Worker-Main-4/ERROR]: Using missing texture, file minecraft:textures/items/stone_chivalrylance.png not found\n[22:12:27] [Worker-Main-4/ERROR]: Using missing texture, file buzzier_bees:textures/block/soul_candle.png not found\n[22:12:27] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/blocks/sconce_fire_nether_brass.png not found\n[22:12:27] [Worker-Main-1/ERROR]: Using missing texture, file minecraft:textures/items/copper_chivalrylance.png not found\n[22:12:27] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/silver_buckler_pattern.png not found\n[22:12:28] [Worker-Main-3/INFO]: [OptiFine] Scaled non power of 2: beautify:blocks/trellis_lilac_hanging, 20 -> 32\n[22:12:28] [Worker-Main-3/WARN]: Texture beautify:blocks/trellis_lilac_hanging with size 32x25 limits mip level from 4 to 0\n[22:12:28] [Worker-Main-3/INFO]: [OptiFine] Scaled non power of 2: beautify:blocks/trellis_rose_hanging, 20 -> 32\n[22:12:28] [Worker-Main-3/INFO]: [OptiFine] Scaled non power of 2: beautify:blocks/trellis_vine_hanging, 20 -> 32\n[22:12:28] [Worker-Main-3/INFO]: [OptiFine] Scaled non power of 2: beautify:blocks/trellis_lichen_hanging, 20 -> 32\n[22:12:28] [Worker-Main-3/INFO]: [OptiFine] Scaled non power of 2: beautify:blocks/trellis_sunflower_hanging, 20 -> 32\n[22:12:28] [Worker-Main-3/INFO]: [OptiFine] Scaled non power of 2: beautify:blocks/trellis_weeping_vines_hanging, 20 -> 32\n[22:12:28] [Worker-Main-3/INFO]: [OptiFine] Scaled non power of 2: beautify:blocks/trellis_twisting_vines_hanging, 20 -> 32\n[22:12:28] [Worker-Main-3/INFO]: [OptiFine] Scaled non power of 2: beautify:blocks/trellis_peony_hanging, 20 -> 32\n[22:12:28] [Worker-Main-3/WARN]: minecraft:textures/atlas/blocks.png: dropping miplevel from 4 to 0, because of minimum power of two: 1\n[22:12:29] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[22:12:29] [Worker-Main-3/INFO]: [OptiFine] Sprite size: 64\n[22:12:29] [Worker-Main-3/INFO]: [OptiFine] Mipmap levels: 6\n[22:12:29] [Worker-Main-3/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:12:29] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: twilightforest:block/mosspatch, 16 -> 64\n[22:12:29] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[22:12:29] [Worker-Main-3/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[22:12:29] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Sprite size: 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Mipmap levels: 6\n[22:12:30] [Worker-Main-3/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/twilightforest/snow_queen.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/sun.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/bowl.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/alexsmobs/brazil.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/orthodox_cross.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/lily.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/two-headed_eagle.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/lion1.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/lion2.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/twilightforest/alpha_yeti.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/twilightforest/knight_phantom.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/tower.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/chess.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/twilightforest/quest_ram.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/neapolitan/chimpanzee.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/tree.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/apostolic_cross.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/twilightforest/minoshroom.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/eagle.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/horse.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/snake.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/dragon.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/twilightforest/hydra.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/twilightforest/ur_ghast.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/crusader_cross.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/twilightforest/naga.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/bull.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/twilightforest/lich.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file supplementaries:textures/entity/flags/magistuarmory/swords.png not found\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/half_vertical, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/triangle_bottom, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/square_top_right, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/stripe_downleft, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/half_vertical_right, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/stripe_downright, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/diagonal_up_left, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: twilightforest:block/mosspatch, 16 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/square_bottom_right, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/circle, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/triangle_top, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/half_horizontal_bottom, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/triangles_top, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/gradient_up, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/stripe_left, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/bricks, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/border, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/stripe_center, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/square_top_left, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/diagonal_right, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/stripe_right, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/small_stripes, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/diagonal_up_right, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/square_bottom_left, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/cross, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/skull, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/triangles_bottom, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/base, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/globe, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/creeper, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/flower, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/rhombus, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/mojang, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/gradient, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/straight_cross, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/alexsmobs/new_mexico, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/curly_border, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/stripe_top, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/stripe_middle, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/alexsmobs/bear, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/diagonal_left, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/stripe_bottom, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/piglin, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/alexsmobs/australia_0, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/alexsmobs/australia_1, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/flags/half_horizontal, 32 -> 64\n[22:12:30] [Worker-Main-3/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Sprite size: 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Mipmap levels: 6\n[22:12:30] [Worker-Main-3/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/chess.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/crusader_cross.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/ur_ghast.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/square_bottom_left.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/lich.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/mojang.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/hydra.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/new_mexico.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/stripe_downleft.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/australia_0.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/bear.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/australia_1.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/naga.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/quest_ram.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/knight_phantom.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/two-headed_eagle.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/creeper.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/apostolic_cross.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/straight_cross.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/stripe_bottom.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/new_mexico.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/hydra.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/half_horizontal.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/lich.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/base.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/half_vertical_right.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/quest_ram.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/small_stripes.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/snow_queen.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/brazil.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/curly_border.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/square_bottom_right.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/bear.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/lion2.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/lion1.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/sun.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/hydra.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/triangles_top.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/minoshroom.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/tower.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/lion2.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/lion1.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/bear.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/ur_ghast.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/small_stripes.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/tree.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/eagle.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/border.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/triangle_bottom.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/lily.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/stripe_right.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/diagonal_left.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/lich.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/alpha_yeti.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/globe.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/ur_ghast.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/chimpanzee.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/flower.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/skull.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/naga.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/brazil.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/bricks.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/stripe_middle.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/square_top_right.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/alpha_yeti.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/chimpanzee.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/triangles_top.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/minoshroom.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/naga.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/naga.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/horse.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/brazil.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/snow_queen.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/two-headed_eagle.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/tower.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/lich.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/diagonal_up_left.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/knight_phantom.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/rhombus.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/circle.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/sun.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/cross.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/alpha_yeti.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/gradient.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/flower.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/alpha_yeti.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/new_mexico.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/minoshroom.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/orthodox_cross.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/knight_phantom.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/minoshroom.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/brazil.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/horse.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/triangles_bottom.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/new_mexico.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/skull.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/stripe_downleft.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/brazil.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/diagonal_left.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/stripe_middle.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/square_bottom_left.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/dragon.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/circle.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/piglin.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/quest_ram.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/diagonal_up_right.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/bull.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/chess.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/hydra.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/snow_queen.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/half_horizontal_bottom.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/gradient.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/bull.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/alpha_yeti.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/minoshroom.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/naga.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/naga.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/square_bottom_right.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/dragon.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/diagonal_right.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/stripe_downright.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/chimpanzee.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/half_vertical.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/hydra.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/bear.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/brazil.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/australia_0.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/australia_1.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/rhombus.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/piglin.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/hydra.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/knight_phantom.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/swords.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/quest_ram.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/diagonal_up_right.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/alpha_yeti.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/quest_ram.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/australia_1.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/australia_0.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/alpha_yeti.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/apostolic_cross.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/chimpanzee.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/knight_phantom.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/ur_ghast.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/chimpanzee.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/knight_phantom.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/chimpanzee.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/mojang.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/ur_ghast.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/gradient_up.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/snow_queen.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/new_mexico.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/orthodox_cross.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/square_top_left.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/triangle_top.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/naga.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/hydra.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/alpha_yeti.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/bowl.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/stripe_top.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/chimpanzee.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/half_vertical_right.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/half_vertical.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/stripe_right.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/snake.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/lily.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/minoshroom.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/knight_phantom.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/hydra.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/snow_queen.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/naga.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/cross.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/stripe_top.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/ur_ghast.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/tree.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/australia_0.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/bear.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/australia_1.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/australia_0.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/australia_1.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/quest_ram.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/straight_cross.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/half_horizontal_bottom.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/bowl.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/border.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/brazil.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/bear.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/lich.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/australia_1.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/australia_0.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/bear.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/quest_ram.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/chimpanzee.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/triangle_top.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/creeper.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/australia_0.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/stripe_downright.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/australia_1.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/ur_ghast.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/diagonal_right.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/bear.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/gradient_up.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/lich.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/minoshroom.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/bricks.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/snake.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/curly_border.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/triangle_bottom.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/ur_ghast.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/snow_queen.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/eagle.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/stripe_center.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/globe.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/roundshield/new_mexico.png not found\n[22:12:30] [Worker-Main-4/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/snow_queen.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/new_mexico.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/tartsche/new_mexico.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/stripe_bottom.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/stripe_center.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/lich.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/lich.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/stripe_left.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/heatershield/quest_ram.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/minoshroom.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/triangles_bottom.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/pavese/snow_queen.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/diagonal_up_left.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/target/square_top_left.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/half_horizontal.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/ellipticalshield/knight_phantom.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/square_top_right.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/brazil.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/swords.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/stripe_left.png not found\n[22:12:30] [Worker-Main-2/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/australia_1.png not found\n[22:12:30] [Worker-Main-7/ERROR]: Using missing texture, file magistuarmory:textures/entity/kiteshield/australia_0.png not found\n[22:12:30] [Worker-Main-5/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/base.png not found\n[22:12:30] [Worker-Main-1/ERROR]: Using missing texture, file magistuarmory:textures/entity/buckler/crusader_cross.png not found\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: twilightforest:block/mosspatch, 16 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[22:12:30] [Worker-Main-3/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Sprite size: 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Mipmap levels: 6\n[22:12:30] [Worker-Main-3/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: twilightforest:block/mosspatch, 16 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[22:12:30] [Worker-Main-3/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Sprite size: 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Mipmap levels: 6\n[22:12:30] [Worker-Main-3/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: twilightforest:block/mosspatch, 16 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[22:12:30] [Worker-Main-3/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Sprite size: 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Mipmap levels: 6\n[22:12:30] [Worker-Main-3/INFO]: Dispatching Module Step TEXTURE_STITCH\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_orange, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_green, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_black, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_pink, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_antique, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_brown, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_written, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_white, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_light_blue, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: twilightforest:block/mosspatch, 16 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_enchanted, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_and_quill, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_lime, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_tome, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_light_gray, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_yellow, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_red, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_blue, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_purple, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_gray, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_cyan, 32 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[22:12:30] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: supplementaries:entity/books/book_magenta, 32 -> 64\n[22:12:30] [Worker-Main-3/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[22:12:30] [Render thread/INFO]: Loaded 9 journal_lang\n[22:12:30] [Render thread/INFO]: Painting transparency set to: true\n[22:12:30] [Render thread/INFO]: Item Frame transparency set to: true\n[22:12:30] [Render thread/INFO]: Beacon Beam transparency set to: false\n[22:12:30] [Render thread/INFO]: Loaded all shader sources.\n[22:12:30] [Worker-Main-3/INFO]: Gobber IMC setup complete\n[22:12:30] [Worker-Main-2/INFO]: Got IMC []\n[22:12:30] [Worker-Main-7/INFO]: Dispatching Module Step LOAD_COMPLETE\n[22:12:30] [Render thread/INFO]: BYG: Compostible Blocks Added!\n[22:12:30] [Render thread/INFO]: BYG: Added tillables!\n[22:12:30] [Render thread/INFO]: BYG: Added Flammables!\n[22:12:30] [Render thread/INFO]: BYG: \"Load Complete\" Event Complete!\n[22:12:30] [Render thread/INFO]: Registered region quark:biome_provider to index 8 for type OVERWORLD\n[22:12:39] [Forge Version Check/WARN]: Failed to process update information\njava.net.http.HttpTimeoutException: request timed out\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:844) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n[22:12:39] [Forge Version Check/INFO]: [twilightforest] Starting version check at https://gh.tamaized.com/TeamTwilight/twilightforest/update.json\n[22:12:40] [Forge Version Check/INFO]: [twilightforest] Found status: UP_TO_DATE Current: 4.2.1421 Target: null\n[22:12:40] [Forge Version Check/INFO]: [supplementaries] Starting version check at https://raw.githubusercontent.com/MehVahdJukaar/Supplementaries/master/forge/update.json\n[22:12:40] [Forge Version Check/WARN]: Failed to process update information\njava.net.ConnectException: null\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:846) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\nCaused by: java.net.ConnectException\n\tat jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1045) ~[java.net.http:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:224) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\nCaused by: java.nio.channels.UnresolvedAddressException\n\tat sun.nio.ch.Net.checkAddress(Net.java:149) ~[?:?]\n\tat sun.nio.ch.Net.checkAddress(Net.java:157) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.checkRemote(SocketChannelImpl.java:842) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:865) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:208) ~[java.net.http:?]\n\tat java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:210) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\n[22:12:40] [Forge Version Check/INFO]: [enchdesc] Starting version check at https://updates.blamejared.com/get?n=enchdesc&gv=1.19.2\n[22:12:40] [Forge Version Check/INFO]: [enchdesc] Found status: BETA Current: 13.0.7 Target: 13.0.7\n[22:12:40] [Forge Version Check/INFO]: [moonlight] Starting version check at https://raw.githubusercontent.com/MehVahdJukaar/Moonlight/master/forge/update.json\n[22:12:40] [Forge Version Check/WARN]: Failed to process update information\njava.net.ConnectException: null\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:846) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\nCaused by: java.net.ConnectException\n\tat jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1045) ~[java.net.http:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:224) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\nCaused by: java.nio.channels.UnresolvedAddressException\n\tat sun.nio.ch.Net.checkAddress(Net.java:149) ~[?:?]\n\tat sun.nio.ch.Net.checkAddress(Net.java:157) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.checkRemote(SocketChannelImpl.java:842) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:865) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:208) ~[java.net.http:?]\n\tat java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:210) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\n[22:12:40] [Forge Version Check/INFO]: [mcwfences] Starting version check at https://raw.githubusercontent.com/sketchmacaw/macawsmods/master/fences.json\n[22:12:40] [Forge Version Check/WARN]: Failed to process update information\njava.net.ConnectException: null\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:846) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\nCaused by: java.net.ConnectException\n\tat jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1045) ~[java.net.http:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:224) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\nCaused by: java.nio.channels.UnresolvedAddressException\n\tat sun.nio.ch.Net.checkAddress(Net.java:149) ~[?:?]\n\tat sun.nio.ch.Net.checkAddress(Net.java:157) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.checkRemote(SocketChannelImpl.java:842) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:865) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:208) ~[java.net.http:?]\n\tat java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:210) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\n[22:12:40] [Forge Version Check/INFO]: [flywheel] Starting version check at https://api.modrinth.com/updates/5lpsZoRi/forge_updates.json\n[22:12:41] [Forge Version Check/INFO]: [flywheel] Found status: BETA Current: 0.6.7-8 Target: null\n[22:12:41] [Forge Version Check/INFO]: [forge] Starting version check at https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json\n[22:12:42] [Forge Version Check/INFO]: [forge] Found status: AHEAD Current: 43.1.43 Target: null\n[22:12:42] [Forge Version Check/INFO]: [commonality] Starting version check at https://api.modrinth.com/updates/apexcore/forge_updates.json\n[22:12:42] [Forge Version Check/INFO]: [commonality] Found status: OUTDATED Current: 4.2.0 Target: 7.3.0\n[22:12:42] [Forge Version Check/INFO]: [mcwroofs] Starting version check at https://raw.githubusercontent.com/sketchmacaw/macawsmods/master/roofs.json\n[22:12:42] [Forge Version Check/WARN]: Failed to process update information\njava.net.ConnectException: null\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:846) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\nCaused by: java.net.ConnectException\n\tat jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1045) ~[java.net.http:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:224) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\nCaused by: java.nio.channels.UnresolvedAddressException\n\tat sun.nio.ch.Net.checkAddress(Net.java:149) ~[?:?]\n\tat sun.nio.ch.Net.checkAddress(Net.java:157) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.checkRemote(SocketChannelImpl.java:842) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:865) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:208) ~[java.net.http:?]\n\tat java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:210) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\n[22:12:42] [Forge Version Check/INFO]: [cfm] Starting version check at https://mrcrayfish.com/modupdatejson?id=cfm\n[22:12:42] [Forge Version Check/INFO]: [cfm] Found status: BETA Current: 7.0.0-pre34 Target: null\n[22:12:42] [Forge Version Check/INFO]: [mcwfurnitures] Starting version check at https://raw.githubusercontent.com/sketchmacaw/macawsmods/master/furniture.json\n[22:12:42] [Forge Version Check/WARN]: Failed to process update information\njava.net.ConnectException: null\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:846) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.19.2-43.1.43.jar%23239!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.19.2-43.1.43.jar%23239!/:?]\nCaused by: java.net.ConnectException\n\tat jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1045) ~[java.net.http:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:224) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\nCaused by: java.nio.channels.UnresolvedAddressException\n\tat sun.nio.ch.Net.checkAddress(Net.java:149) ~[?:?]\n\tat sun.nio.ch.Net.checkAddress(Net.java:157) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.checkRemote(SocketChannelImpl.java:842) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:865) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:208) ~[java.net.http:?]\n\tat java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]\n\tat jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:210) ~[java.net.http:?]\n\tat jdk.internal.net.http.AsyncSSLConnection.connectAsync(AsyncSSLConnection.java:56) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:380) ~[java.net.http:?]\n\tat jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128) ~[java.net.http:?]\n\tat jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:94) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.establishExchange(Exchange.java:368) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:500) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:405) ~[java.net.http:?]\n\tat jdk.internal.net.http.Exchange.responseAsync(Exchange.java:397) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:409) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:450) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:440) ~[java.net.http:?]\n\tat jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:342) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:172) ~[java.net.http:?]\n\tat java.util.concurrent.CompletableFuture.completeAsync(CompletableFuture.java:2719) ~[?:?]\n\tat jdk.internal.net.http.MultiExchange.responseAsync(MultiExchange.java:295) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.sendAsync(HttpClientImpl.java:931) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:825) ~[java.net.http:?]\n\t... 5 more\n[22:12:42] [Forge Version Check/INFO]: [apexcore] Starting version check at https://api.modrinth.com/updates/apexcore/forge_updates.json\n[22:12:43] [Forge Version Check/INFO]: [apexcore] Found status: UP_TO_DATE Current: 7.3.0 Target: null\n[22:12:43] [Forge Version Check/INFO]: [fantasyfurniture] Starting version check at https://api.modrinth.com/updates/fantasy-furniture/forge_updates.json\n[22:12:43] [Forge Version Check/INFO]: [fantasyfurniture] Found status: UP_TO_DATE Current: 6.5.0 Target: null\n[22:12:43] [Forge Version Check/INFO]: [pizzacraft] Starting version check at https://gist.githubusercontent.com/Tiviacz1337/b916e3981957f1e6f2de99ea0aa328fa/raw\n[22:12:44] [Render thread/WARN]: Missing sound for event: minecraft:item.goat_horn.play\n[22:12:44] [Render thread/WARN]: Missing sound for event: minecraft:entity.goat.screaming.horn_break\n[22:12:44] [Render thread/WARN]: Missing sound for event: additionalguns:item.1911.fire\n[22:12:44] [Render thread/WARN]: Missing sound for event: additionalguns:item.1911.suppressed\n[22:12:44] [Render thread/WARN]: Missing sound for event: additionalguns:item.1911.enchanted\n[22:12:44] [Render thread/WARN]: Missing sound for event: supplementaries:item.wrench.hit\n[22:12:44] [Render thread/WARN]: Missing sound for event: cataclysm:ignisshieldbreak\n[22:12:44] [Render thread/INFO]: OpenAL initialized on device OpenAL Soft on 扬声器 (Realtek High Definition Audio)\n[22:12:44] [Render thread/INFO]: Sound engine started\n[22:12:44] [Render thread/INFO]: Endimation Loader has loaded 1 endimations\n[22:12:44] [Render thread/INFO]: Created: 4096x2048x0 minecraft:textures/atlas/blocks.png-atlas\n[22:12:44] [Render thread/INFO]: [OptiFine] Animated sprites: 308\n[22:12:44] [Render thread/INFO]: Dispatching Module Step POST_TEXTURE_STITCH\n[22:12:44] [Render thread/INFO]: Created: 1024x512x4 minecraft:textures/atlas/signs.png-atlas\n[22:12:44] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[22:12:44] [Render thread/INFO]: Dispatching Module Step POST_TEXTURE_STITCH\n[22:12:44] [Render thread/INFO]: Created: 1024x512x4 minecraft:textures/atlas/banner_patterns.png-atlas\n[22:12:44] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[22:12:44] [Render thread/INFO]: Dispatching Module Step POST_TEXTURE_STITCH\n[22:12:44] [Render thread/INFO]: Created: 2048x1024x4 minecraft:textures/atlas/shield_patterns.png-atlas\n[22:12:44] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[22:12:44] [Render thread/INFO]: Dispatching Module Step POST_TEXTURE_STITCH\n[22:12:44] [Render thread/INFO]: Created: 1024x1024x4 minecraft:textures/atlas/chest.png-atlas\n[22:12:44] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[22:12:44] [Render thread/INFO]: Dispatching Module Step POST_TEXTURE_STITCH\n[22:12:44] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas\n[22:12:44] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[22:12:44] [Render thread/INFO]: Dispatching Module Step POST_TEXTURE_STITCH\n[22:12:44] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/shulker_boxes.png-atlas\n[22:12:44] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[22:12:44] [Render thread/INFO]: Dispatching Module Step POST_TEXTURE_STITCH\n[22:12:47] [Render thread/ERROR]: Reported exception thrown!\nnet.minecraft.ReportedException: Rendering overlay\n\tat net.minecraft.client.renderer.GameRenderer.m_109093_(GameRenderer.java:1300) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.Minecraft.m_91383_(Minecraft.java:1115) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.Minecraft.m_91374_(Minecraft.java:700) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.main.Main.m_239872_(Main.java:212) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.main.Main.main(Main.java:51) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]\n\tat java.lang.reflect.Method.invoke(Method.java:578) ~[?:?]\n\tat net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:27) ~[fmlloader-1.19.2-43.1.43.jar%2397!/:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:106) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:77) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) [modlauncher-10.0.8.jar%2384!/:?]\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) [bootstraplauncher-1.1.2.jar:?]\nCaused by: java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.block.model.BakedQuad.<init>(int[], int, net.minecraft.core.Direction, net.minecraft.client.renderer.texture.TextureAtlasSprite, boolean, boolean)'\n\tat net.minecraftforge.client.model.pipeline.QuadBakingVertexConsumer.m_5752_(QuadBakingVertexConsumer.java:131) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat net.minecraftforge.client.model.obj.ObjModel.makeQuad(ObjModel.java:461) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat net.minecraftforge.client.model.obj.ObjModel$ModelMesh.addQuads(ObjModel.java:657) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat net.minecraftforge.client.model.obj.ObjModel$ModelObject.addQuads(ObjModel.java:556) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat net.minecraftforge.client.model.obj.ObjModel$ModelGroup.addQuads(ObjModel.java:595) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat net.minecraftforge.client.model.obj.ObjModel.lambda$addQuads$2(ObjModel.java:355) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183) ~[?:?]\n\tat java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[?:?]\n\tat java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1787) ~[?:?]\n\tat java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[?:?]\n\tat java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[?:?]\n\tat java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150) ~[?:?]\n\tat java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173) ~[?:?]\n\tat java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[?:?]\n\tat java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[?:?]\n\tat net.minecraftforge.client.model.obj.ObjModel.addQuads(ObjModel.java:355) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat net.minecraftforge.client.model.geometry.SimpleUnbakedGeometry.bake(SimpleUnbakedGeometry.java:41) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat net.minecraftforge.client.model.geometry.UnbakedGeometryHelper.bake(UnbakedGeometryHelper.java:99) ~[forge-1.19.2-43.1.43-universal.jar%23243!/:?]\n\tat net.minecraft.client.renderer.block.model.BlockModel.m_111449_(BlockModel.java:203) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.renderer.block.model.BlockModel.m_7611_(BlockModel.java:199) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.resources.model.ModelBakery.bake(ModelBakery.java:659) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.renderer.block.model.MultiVariant.m_7611_(MultiVariant.java:73) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.resources.model.ModelBakery.bake(ModelBakery.java:659) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.resources.model.ModelBakery.m_119349_(ModelBakery.java:626) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.resources.model.ModelBakery.lambda$uploadTextures$12(ModelBakery.java:285) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat java.util.HashMap$KeySet.forEach(HashMap.java:1016) ~[?:?]\n\tat net.minecraft.client.resources.model.ModelBakery.m_119298_(ModelBakery.java:280) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.resources.model.ModelManager.m_5787_(ModelManager.java:70) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.resources.model.ModelManager.m_5787_(ModelManager.java:20) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.server.packs.resources.SimplePreparableReloadListener.m_10789_(SimplePreparableReloadListener.java:13) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:718) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_143940_(SimpleReloadInstance.java:69) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.execute(BlockableEventLoop.java:118) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_10834_(SimpleReloadInstance.java:68) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat java.util.concurrent.CompletableFuture$UniCompletion.claim(CompletableFuture.java:572) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:714) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:614) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:726) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_143940_(SimpleReloadInstance.java:69) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.execute(BlockableEventLoop.java:118) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_10834_(SimpleReloadInstance.java:68) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat java.util.concurrent.CompletableFuture$UniCompletion.claim(CompletableFuture.java:572) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:714) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:614) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:726) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_143940_(SimpleReloadInstance.java:69) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.execute(BlockableEventLoop.java:118) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_10834_(SimpleReloadInstance.java:68) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat java.util.concurrent.CompletableFuture$UniCompletion.claim(CompletableFuture.java:572) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:714) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:614) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:726) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_143940_(SimpleReloadInstance.java:69) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.execute(BlockableEventLoop.java:118) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_10834_(SimpleReloadInstance.java:68) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat java.util.concurrent.CompletableFuture$UniCompletion.claim(CompletableFuture.java:572) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:714) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:614) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:726) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_143940_(SimpleReloadInstance.java:69) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.execute(BlockableEventLoop.java:118) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_10834_(SimpleReloadInstance.java:68) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat java.util.concurrent.CompletableFuture$UniCompletion.claim(CompletableFuture.java:572) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:714) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:614) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:726) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_143940_(SimpleReloadInstance.java:69) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.execute(BlockableEventLoop.java:118) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_10834_(SimpleReloadInstance.java:68) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat java.util.concurrent.CompletableFuture$UniCompletion.claim(CompletableFuture.java:572) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:714) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:614) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:726) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_143940_(SimpleReloadInstance.java:69) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.util.thread.BlockableEventLoop.execute(BlockableEventLoop.java:118) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.server.packs.resources.SimpleReloadInstance.m_10834_(SimpleReloadInstance.java:68) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat java.util.concurrent.CompletableFuture$UniCompletion.claim(CompletableFuture.java:572) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:714) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[?:?]\n\tat java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:614) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:726) ~[?:?]\n\tat java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]\n\tat com.mojang.blaze3d.systems.RenderSystem.m_69884_(RenderSystem.java:212) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat com.mojang.blaze3d.systems.RenderSystem.m_69495_(RenderSystem.java:199) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat com.mojang.blaze3d.platform.Window.m_85435_(Window.java:453) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\tat net.minecraft.client.Minecraft.m_91383_(Minecraft.java:1143) ~[client-1.19.2-20220805.130853-srg.jar%23238!/:?]\n\t... 14 more\n[22:12:47] [Render thread/FATAL]: Preparing crash report with UUID 7cc7a9f0-48c3-4957-bf79-3476d6b0cbd7\n[22:12:48] [Render thread/FATAL]: Preparing crash report with UUID 087b26cd-cdde-4d9b-a0df-eb3926991b38\n[22:12:48] [Render thread/FATAL]: Preparing crash report with UUID 3bf882f7-8903-401e-a3dd-989f462b0c9f"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/optifine_is_not_compatible_with_forge3.txt",
    "content": "[15:07:11] [Render thread/INFO]: Registering world-gen core codecs\n[15:07:11] [Render thread/INFO]: Registering world-gen component codecs\n[15:07:11] [Render thread/INFO]: Locking mod world-gen registries\n[15:07:11] [Render thread/INFO]: Registering world-gen content\n[15:07:11] [Render thread/INFO]: Extracting default datapack to F:\\我的世界\\生存魂\\config\\terraforged\\pack-v0.1\n[15:07:11] [Render thread/INFO]: Initializing Packet Registries\n[15:07:11] [Render thread/INFO]: Registered the class com.tiviacz.travelersbackpack.fluids.effects.WaterEffect as a FluidEffect for Water with the ID 0\n[15:07:11] [Render thread/INFO]: Registered the class com.tiviacz.travelersbackpack.fluids.effects.LavaEffect as a FluidEffect for Lava with the ID 1\n[15:07:11] [Render thread/INFO]: Registered the class com.tiviacz.travelersbackpack.fluids.effects.PotionEffect as a FluidEffect for fluid.travelersbackpack.potion_fluid with the ID 2\n[15:07:11] [Render thread/INFO]: Registered the class com.tiviacz.travelersbackpack.fluids.effects.MilkEffect as a FluidEffect for Milk with the ID 3\n[15:07:11] [Worker-Main-10/INFO]: Built TOML config for F:\\我的世界\\生存魂\\config\\prefab.toml\n[15:07:11] [Worker-Main-10/INFO]: Loaded TOML config file F:\\我的世界\\生存魂\\config\\prefab.toml\n[15:07:12] [Forge Version Check/INFO]: [iceberg] Found status: UP_TO_DATE Current: 1.0.49 Target: null\n[15:07:12] [Forge Version Check/INFO]: [flywheel] Starting version check at https://api.modrinth.com/updates/flywheel/forge_updates.json\n[15:07:12] [Render thread/INFO]: Registering custom overworld effects\n[15:07:12] [Render thread/INFO]: Journeymap Initializing\n[15:07:12] [Render thread/INFO]: JourneyMap log initialized.\n[15:07:12] [Render thread/INFO]: initialize ENTER\n[15:07:12] [Render thread/INFO]: [ClientAPI] built with JourneyMap API 1.9-SNAPSHOT\n[15:07:12] [Render thread/INFO]: Initializing plugins with Client API: journeymap.client.api.impl.ClientAPI\n[15:07:12] [Render thread/INFO]: Initialized IClientPlugin: net.blay09.mods.waystones.compat.JourneyMapAddon\n[15:07:12] [Forge Version Check/INFO]: [flywheel] Found status: UP_TO_DATE Current: 0.6.8.a Target: null\n[15:07:12] [Forge Version Check/INFO]: [journeymap] Starting version check at https://forge.curseupdate.com/32274/journeymap\n[15:07:12] [Render thread/WARN]: core (Initialized) Bad configField entry during updateFrom(): optionsManagerViewed=null\n[15:07:12] [Render thread/WARN]: core (Initialized) Bad configField entry during updateFrom(): splashViewed=null\n[15:07:12] [Render thread/INFO]: initialize EXIT, elapsed count 1 avg 319.6ms\n[15:07:14] [Forge Version Check/INFO]: [journeymap] Found status: UP_TO_DATE Current: 5.9.5 Target: null\n[15:07:14] [Forge Version Check/INFO]: [travelersbackpack] Starting version check at https://gist.githubusercontent.com/Tiviacz1337/906937677aa472285dff9d6c2a189d5e/raw\n[15:07:17] [Worker-Main-13/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:17] [Worker-Main-13/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:17] [Worker-Main-13/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:17] [Worker-Main-13/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:18] [Worker-Main-13/WARN]: Unable to resolve texture reference: #missing in createaddition:block/connector/small_light\n[15:07:18] [Worker-Main-13/WARN]: Unable to resolve texture reference: #missing in cataclysm:item/infernal_forge\n[15:07:18] [Worker-Main-13/INFO]: [OptiFine] Multitexture: false\n[15:07:18] [Worker-Main-13/INFO]: [OptiFine] Multipass connected textures: false\n[15:07:18] [Worker-Main-13/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:18] [Worker-Main-13/INFO]: [OptiFine] Multipass connected textures: false\n[15:07:18] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/00_glass_white/glass_pane_white.properties\n[15:07:18] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/00_glass_white/glass_white.properties\n[15:07:18] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/01_glass_orange/glass_orange.properties\n[15:07:18] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/01_glass_orange/glass_pane_orange.properties\n[15:07:18] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/02_glass_magenta/glass_magenta.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/02_glass_magenta/glass_pane_magenta.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/03_glass_light_blue/glass_light_blue.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/03_glass_light_blue/glass_pane_light_blue.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/04_glass_yellow/glass_pane_yellow.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/04_glass_yellow/glass_yellow.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/05_glass_lime/glass_lime.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/05_glass_lime/glass_pane_lime.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/06_glass_pink/glass_pane_pink.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/06_glass_pink/glass_pink.properties\n[15:07:19] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/07_glass_gray/glass_gray.properties\n[15:07:20] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/07_glass_gray/glass_pane_gray.properties\n[15:07:20] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/08_glass_light_gray/glass_light_gray.properties\n[15:07:20] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/08_glass_light_gray/glass_pane_light_gray.properties\n[15:07:20] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/09_glass_cyan/glass_cyan.properties\n[15:07:20] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/09_glass_cyan/glass_pane_cyan.properties\n[15:07:20] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/10_glass_purple/glass_pane_purple.properties\n[15:07:20] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/10_glass_purple/glass_purple.properties\n[15:07:20] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/11_glass_blue/glass_blue.properties\n[15:07:20] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/11_glass_blue/glass_pane_blue.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/12_glass_brown/glass_brown.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/12_glass_brown/glass_pane_brown.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/13_glass_green/glass_green.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/13_glass_green/glass_pane_green.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/14_glass_red/glass_pane_red.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/14_glass_red/glass_red.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/15_glass_black/glass_black.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/15_glass_black/glass_pane_black.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/20_glass/glass.properties\n[15:07:21] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/20_glass/glass_pane.properties\n[15:07:22] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/21_tinted_glass/tinted_glass.properties\n[15:07:22] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/30_bookshelf/bookshelf.properties\n[15:07:22] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/40_sandstone/sandstone.properties\n[15:07:22] [Worker-Main-13/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/41_red_sandstone/red_sandstone.properties\n[15:07:22] [Worker-Main-13/INFO]: [OptiFine] Multipass connected textures: false\n[15:07:22] [Worker-Main-13/INFO]: [OptiFine] BetterGrass: Parsing default configuration optifine/bettergrass.properties\n[15:07:27] [Worker-Main-13/INFO]: [OptiFine] Mipmap levels: 4\n[15:07:27] [Worker-Main-12/ERROR]: Using missing texture, unable to load ars_nouveau:textures/blocks/active_middle.png : java.io.FileNotFoundException: ars_nouveau:textures/blocks/active_middle.png\n[15:07:27] [Worker-Main-12/ERROR]: Using missing texture, unable to load ars_nouveau:textures/blocks/active_inner.png : java.io.FileNotFoundException: ars_nouveau:textures/blocks/active_inner.png\n[15:07:27] [Worker-Main-9/ERROR]: Using missing texture, unable to load ars_nouveau:textures/blocks/active_outer.png : java.io.FileNotFoundException: ars_nouveau:textures/blocks/active_outer.png\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Multitexture: false\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Sprite size: 64\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Mipmap levels: 6\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[15:07:28] [Worker-Main-13/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Multitexture: false\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Sprite size: 64\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Mipmap levels: 6\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[15:07:28] [Worker-Main-13/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Multitexture: false\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Sprite size: 64\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Mipmap levels: 6\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[15:07:28] [Worker-Main-13/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Multitexture: false\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Sprite size: 64\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Mipmap levels: 6\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[15:07:28] [Worker-Main-13/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Multitexture: false\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Sprite size: 64\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Mipmap levels: 6\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[15:07:28] [Worker-Main-13/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Multitexture: false\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Sprite size: 64\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Mipmap levels: 6\n[15:07:28] [Worker-Main-13/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[15:07:28] [Worker-Main-13/WARN]: Unused frames in sprite minecraft:missingno: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n[15:07:29] [Forge Version Check/WARN]: Failed to process update information\njava.net.http.HttpConnectTimeoutException: HTTP connect timed out\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:567) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.18.2-40.2.2.jar%2393!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.18.2-40.2.2.jar%2393!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.18.2-40.2.2.jar%2393!/:?]\nCaused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out\n\tat jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:68) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1270) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:899) ~[java.net.http:?]\nCaused by: java.net.ConnectException: HTTP connect timed out\n\tat jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1270) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:899) ~[java.net.http:?]\n[15:07:29] [Forge Version Check/INFO]: [itemborders] Starting version check at https://mc-update-check.anthonyhilyard.com/513769\n[15:07:29] [Render thread/INFO]: Loaded all shader sources.\nCreate Crafts & Additions Initialized!\n[15:07:29] [Render thread/INFO]: Journeymap PostInit\n[15:07:30] [Render thread/INFO]: Preloaded theme textures: 73\n[15:07:30] [Render thread/INFO]: OptiFine detected.\n[15:07:30] [Render thread/INFO]: postInitialize EXIT, elapsed count 1 avg 288.99ms\n[15:07:32] [Forge Version Check/INFO]: [itemborders] Found status: UP_TO_DATE Current: 1.1.5 Target: null\n[15:07:32] [Forge Version Check/INFO]: [villagersrespawn] Starting version check at https://github.com/mactso/VillagersRespawn/raw/main/update.json\n[15:07:34] [Render thread/WARN]: Skipped language file: carpet_trapdoors:lang/zh_cn.json (java.lang.NullPointerException: Cannot invoke \"com.google.gson.JsonObject.entrySet()\" because \"jsonobject\" is null)\n[15:07:36] [Render thread/WARN]: Missing sound for event: cataclysm:ignisshieldbreak\n[15:07:36] [Render thread/INFO]: OpenAL initialized on device OpenAL Soft on 扬声器 (Realtek High Definition Audio)\n[15:07:36] [Render thread/INFO]: Sound engine started\n[15:07:36] [Render thread/INFO]: Created: 2048x1024x4 minecraft:textures/atlas/blocks.png-atlas\n[15:07:36] [Render thread/INFO]: [OptiFine] Animated sprites: 90\n[15:07:36] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/signs.png-atlas\n[15:07:36] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[15:07:36] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/banner_patterns.png-atlas\n[15:07:36] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[15:07:36] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/shield_patterns.png-atlas\n[15:07:36] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[15:07:36] [Render thread/INFO]: Created: 256x256x4 minecraft:textures/atlas/chest.png-atlas\n[15:07:36] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[15:07:36] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas\n[15:07:36] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[15:07:36] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas\n[15:07:36] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[15:07:39] [Render thread/INFO]: Created: 1024x1024x0 minecraft:textures/atlas/particles.png-atlas\n[15:07:39] [Render thread/INFO]: [OptiFine] Animated sprites: 1\n[15:07:39] [Render thread/INFO]: Created: 256x256x0 minecraft:textures/atlas/paintings.png-atlas\n[15:07:39] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[15:07:39] [Render thread/INFO]: Created: 128x256x0 minecraft:textures/atlas/mob_effects.png-atlas\n[15:07:39] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/summoning_sickness\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/summoning_sickness\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/summoning_sickness\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/summoning_sickness\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/shield\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/shield\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/shield\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/shield\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/shocked\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/shocked\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/shocked\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/shocked\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: minecraft:optifine/ctm/default/empty\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: minecraft:optifine/ctm/default/empty\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: minecraft:optifine/ctm/default/empty\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: minecraft:optifine/ctm/default/empty\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/snared\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/snared\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/snared\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/snared\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/spell_damage\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/spell_damage\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 16, icon: ars_nouveau:mob_effect/spell_damage\n[15:07:39] [Render thread/WARN]: [OptiFine] Invalid grid V: 17, icon: ars_nouveau:mob_effect/spell_damage\n[15:07:39] [Render thread/INFO]: Created: 256x128x0 jei:textures/atlas/gui.png-atlas\n[15:07:39] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[15:07:39] [Render thread/INFO]: BookContentResourceListenerLoader preloaded 655 jsons\n[15:07:39] [Render thread/INFO]: Not reloading resource pack-based books as client world is missing\n[15:07:39] [Render thread/INFO]: [OptiFine] *** Reloading custom textures ***\n[15:07:39] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:39] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:39] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:47] [Forge Version Check/WARN]: Failed to process update information\njava.net.http.HttpTimeoutException: request timed out\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:571) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.18.2-40.2.2.jar%2393!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.18.2-40.2.2.jar%2393!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.18.2-40.2.2.jar%2393!/:?]\n[15:07:47] [Forge Version Check/INFO]: [forge] Starting version check at https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json\n[15:07:48] [Forge Version Check/INFO]: [forge] Found status: AHEAD Current: 40.2.2 Target: null\n[15:07:48] [Forge Version Check/INFO]: [dynamictrees] Starting version check at https://github.com/DynamicTreesTeam/DynamicTreesVersionInfo/blob/master/DynamicTrees.json?raw=true\n[15:07:52] [Forge Version Check/WARN]: Failed to process update information\njava.io.IOException: Connection reset\n\tat jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:586) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]\n\tat net.minecraftforge.fml.VersionChecker$1.openUrlString(VersionChecker.java:139) ~[fmlcore-1.18.2-40.2.2.jar%2393!/:?]\n\tat net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:177) ~[fmlcore-1.18.2-40.2.2.jar%2393!/:?]\n\tat java.lang.Iterable.forEach(Iterable.java:75) [?:?]\n\tat net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) [fmlcore-1.18.2-40.2.2.jar%2393!/:?]\nCaused by: java.net.SocketException: Connection reset\n\tat sun.nio.ch.SocketChannelImpl.throwConnectionReset(SocketChannelImpl.java:394) ~[?:?]\n\tat sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:426) ~[?:?]\n\tat jdk.internal.net.http.SocketTube.readAvailable(SocketTube.java:1170) ~[java.net.http:?]\n\tat jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.read(SocketTube.java:833) ~[java.net.http:?]\n\tat jdk.internal.net.http.SocketTube$SocketFlowTask.run(SocketTube.java:181) ~[java.net.http:?]\n\tat jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:230) ~[java.net.http:?]\n\tat jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:303) ~[java.net.http:?]\n\tat jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:256) ~[java.net.http:?]\n\tat jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.signalReadable(SocketTube.java:774) ~[java.net.http:?]\n\tat jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadEvent.signalEvent(SocketTube.java:957) ~[java.net.http:?]\n\tat jdk.internal.net.http.SocketTube$SocketFlowEvent.handle(SocketTube.java:253) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl$SelectorManager.handleEvent(HttpClientImpl.java:979) ~[java.net.http:?]\n\tat jdk.internal.net.http.HttpClientImpl$SelectorManager.lambda$run$3(HttpClientImpl.java:934) ~[java.net.http:?]\n\tat java.util.ArrayList.forEach(ArrayList.java:1511) ~[?:?]\n\tat jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:934) ~[java.net.http:?]\n[15:07:52] [Forge Version Check/INFO]: [corpse] Starting version check at https://maxhenkel.de/update/corpse.json\n[15:07:53] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:54] [Forge Version Check/INFO]: [corpse] Found status: AHEAD Current: 1.18.2-1.0.2 Target: null\n[15:07:54] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:54] [Render thread/WARN]: [OptiFine] Unknown resource pack file: mod_resources\n[15:07:54] [Render thread/INFO]: [OptiFine] Disable Forge light pipeline\n[15:07:54] [Render thread/INFO]: [OptiFine] Set ForgeConfig.CLIENT.experimentalForgeLightPipelineEnabled=false\n[LSP CoreMod] Patched: ShareToLanScreen.<init>()!\n[LSP CoreMod] Redirect \"net/minecraft/util/HttpUtil.m_13939_()I\" to rikka/lanserverproperties/OpenToLanScreenEx.getServerPort()I\n[LSP CoreMod] Found 1 RETURN instruction(s)\n[LSP CoreMod] Patched: ShareToLanScreen_Button_Lambda\n[LSP CoreMod] Add field(s) to ShareToLanScreen!\n[15:07:55] [Realms Notification Availability checker #1/INFO]: Could not authorize you against Realms server: Invalid session id\n[15:08:35] [Render thread/INFO]: Injecting existing registry data into this CLIENT instance\n[15:08:37] [Render thread/INFO]: Registering commands with com.sk89q.worldedit.forge.ForgePlatform\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [tb, access, pos] and [tb, access, target] with inputs: [0 0 0]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [tb, unpack, pos] and [tb, unpack, target] with inputs: [0 0 0]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [create, coupling, add, cart1] and [create, coupling, add, carts] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [create, coupling, add, carts] and [create, coupling, add, cart1] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [create, coupling, add, cart1] and [create, coupling, add, carts] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [create, coupling, add, carts] and [create, coupling, add, cart1] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]\n[15:08:37] [Render thread/WARN]: Ambiguity between arguments [deathhistory, player] and [deathhistory, player_uuid] with inputs: [dd12be42-52a9-4a91-a8a1-11c01849e498]\n[15:08:40] [Render thread/INFO]: Loaded 32 recipes\n[15:08:42] [Render thread/INFO]: Loaded 2332 advancements\n[15:08:42] [Render thread/ERROR]: Error whilst loading type \"Species\" with name \"dynamictrees:cocoa\": [growth_logic_kit] Growth Logic Kit couldn't be found from input \"\"deciduous\"\".\n[15:08:43] [Render thread/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD'\n[15:08:43] [Server thread/INFO]: Starting integrated minecraft server version 1.18.2\n[15:08:43] [Server thread/INFO]: Generating keypair\n[15:08:43] [Server thread/INFO]: Loading JourneyMap Forge Configs\n[15:08:43] [Server thread/INFO]: [Village Spawn Point] Finding the nearest village. This might take a few seconds.\n[15:08:45] [Server thread/INFO]: [Village Spawn Point] Village found! The world will now generate.\n[15:08:45] [Server thread/ERROR]: Exception caught during firing event: 'void net.minecraft.server.level.DistanceManager.removeRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'\n\tIndex: 1\n\tListeners:\n\t\t0: NORMAL\n\t\t1: ASM: com.natamus.villagespawnpoint.forge.events.ForgeVillageSpawnEvent@4604fd3d onWorldLoad(Lnet/minecraftforge/event/world/WorldEvent$CreateSpawnPosition;)V\njava.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager.removeRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'\n\tat TRANSFORMER/minecraft@1.18.2/net.minecraft.server.level.ServerChunkCache.removeRegionTicket(ServerChunkCache.java:437)\n\tat TRANSFORMER/minecraft@1.18.2/net.minecraft.server.level.ServerChunkCache.m_8438_(ServerChunkCache.java:433)\n\tat TRANSFORMER/minecraft@1.18.2/net.minecraft.server.level.ServerLevel.m_8733_(ServerLevel.java:1138)\n\tat TRANSFORMER/villagespawnpoint@4.0/com.natamus.villagespawnpoint_common_forge.events.VillageSpawnEvent.onWorldLoad(VillageSpawnEvent.java:35)\n\tat TRANSFORMER/villagespawnpoint@4.0/com.natamus.villagespawnpoint.forge.events.ForgeVillageSpawnEvent.onWorldLoad(ForgeVillageSpawnEvent.java:22)\n\tat net.minecraftforge.eventbus.ASMEventHandler_587_ForgeVillageSpawnEvent_onWorldLoad_CreateSpawnPosition.invoke(.dynamic)\n\tat MC-BOOTSTRAP/eventbus@5.0.3/net.minecraftforge.eventbus.ASMEventHandler.invoke(ASMEventHandler.java:85)\n\tat MC-BOOTSTRAP/eventbus@5.0.3/net.minecraftforge.eventbus.EventBus.post(EventBus.java:302)\n\tat MC-BOOTSTRAP/eventbus@5.0.3/net.minecraftforge.eventbus.EventBus.post(EventBus.java:283)\n\tat TRANSFORMER/forge@40.2.2/net.minecraftforge.event.ForgeEventFactory.onCreateWorldSpawn(ForgeEventFactory.java:523)\n\tat TRANSFORMER/minecraft@1.18.2/net.minecraft.server.MinecraftServer.m_177896_(MinecraftServer.java:410)\n\tat TRANSFORMER/minecraft@1.18.2/net.minecraft.server.MinecraftServer.m_129815_(MinecraftServer.java:364)\n\tat TRANSFORMER/minecraft@1.18.2/net.minecraft.server.MinecraftServer.m_130006_(MinecraftServer.java:316)\n\tat TRANSFORMER/minecraft@1.18.2/net.minecraft.client.server.IntegratedServer.m_7038_(IntegratedServer.java:84)\n\tat TRANSFORMER/minecraft@1.18.2/net.minecraft.server.MinecraftServer.m_130011_(MinecraftServer.java:661)\n\tat TRANSFORMER/minecraft@1.18.2/net.minecraft.server.MinecraftServer.m_177918_(MinecraftServer.java:261)\n\tat java.base/java.lang.Thread.run(Thread.java:833)\n\n[15:08:45] [Server thread/ERROR]: Encountered an unexpected exception\nnet.minecraft.ReportedException: Exception initializing level\n\tat net.minecraft.server.MinecraftServer.m_129815_(MinecraftServer.java:377) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\tat net.minecraft.server.MinecraftServer.m_130006_(MinecraftServer.java:316) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\tat net.minecraft.client.server.IntegratedServer.m_7038_(IntegratedServer.java:84) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\tat net.minecraft.server.MinecraftServer.m_130011_(MinecraftServer.java:661) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\tat net.minecraft.server.MinecraftServer.m_177918_(MinecraftServer.java:261) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\tat java.lang.Thread.run(Thread.java:833) [?:?]\nCaused by: java.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager.removeRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'\n\tat net.minecraft.server.level.ServerChunkCache.removeRegionTicket(ServerChunkCache.java:437) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\tat net.minecraft.server.level.ServerChunkCache.m_8438_(ServerChunkCache.java:433) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\tat net.minecraft.server.level.ServerLevel.m_8733_(ServerLevel.java:1138) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\tat com.natamus.villagespawnpoint_common_forge.events.VillageSpawnEvent.onWorldLoad(VillageSpawnEvent.java:35) ~[villagespawnpoint-1.18.2-4.0.jar%2388!/:?]\n\tat com.natamus.villagespawnpoint.forge.events.ForgeVillageSpawnEvent.onWorldLoad(ForgeVillageSpawnEvent.java:22) ~[villagespawnpoint-1.18.2-4.0.jar%2388!/:?]\n\tat net.minecraftforge.eventbus.ASMEventHandler_587_ForgeVillageSpawnEvent_onWorldLoad_CreateSpawnPosition.invoke(.dynamic) ~[?:?]\n\tat net.minecraftforge.eventbus.ASMEventHandler.invoke(ASMEventHandler.java:85) ~[eventbus-5.0.3.jar%232!/:?]\n\tat net.minecraftforge.eventbus.EventBus.post(EventBus.java:302) ~[eventbus-5.0.3.jar%232!/:?]\n\tat net.minecraftforge.eventbus.EventBus.post(EventBus.java:283) ~[eventbus-5.0.3.jar%232!/:?]\n\tat net.minecraftforge.event.ForgeEventFactory.onCreateWorldSpawn(ForgeEventFactory.java:523) ~[forge-1.18.2-40.2.2-universal.jar%2397!/:?]\n\tat net.minecraft.server.MinecraftServer.m_177896_(MinecraftServer.java:410) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\tat net.minecraft.server.MinecraftServer.m_129815_(MinecraftServer.java:364) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?]\n\t... 5 more\n[15:08:45] [Server thread/FATAL]: Preparing crash report with UUID 8e116d2e-a2e1-484e-aa79-967548216c62\n[15:08:46] [Server thread/FATAL]: Preparing crash report with UUID 1afa00b4-742d-42ea-99de-2319be9d5602\n[15:08:46] [Server thread/ERROR]: This crash report has been saved to: F:\\我的世界\\生存魂\\crash-reports\\crash-2023-05-02_15.08.45-server.txt\n[15:08:46] [Server thread/INFO]: Stopping server\n[15:08:46] [Server thread/INFO]: Saving players\n[15:08:46] [Server thread/INFO]: Saving worlds\n[15:08:46] [Render thread/FATAL]: Preparing crash report with UUID 0c26fdb2-c733-4380-9808-fc021f633df4\n---- Minecraft Crash Report ----\n// Hi. I'm Minecraft, and I'm a crashaholic.\n\nTime: 2023/5/2 下午3:08\nDescription: Exception initializing level\n\njava.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager.removeRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'\n\tat net.minecraft.server.level.ServerChunkCache.removeRegionTicket(ServerChunkCache.java:437) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:classloading,pl:accesstransformer:B}\n\tat net.minecraft.server.level.ServerChunkCache.m_8438_(ServerChunkCache.java:433) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:classloading,pl:accesstransformer:B}\n\tat net.minecraft.server.level.ServerLevel.m_8733_(ServerLevel.java:1138) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:computing_frames,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:zombieawareness.mixins.json:MixinPlaySound,pl:mixin:APP:zombieawareness.mixins.json:MixinLevelEvent,pl:mixin:APP:create.mixins.json:accessor.ServerLevelAccessor,pl:mixin:A}\n\tat com.natamus.villagespawnpoint_common_forge.events.VillageSpawnEvent.onWorldLoad(VillageSpawnEvent.java:35) ~[villagespawnpoint-1.18.2-4.0.jar%2388!/:?] {re:classloading}\n\tat com.natamus.villagespawnpoint.forge.events.ForgeVillageSpawnEvent.onWorldLoad(ForgeVillageSpawnEvent.java:22) ~[villagespawnpoint-1.18.2-4.0.jar%2388!/:?] {re:classloading}\n\tat net.minecraftforge.eventbus.ASMEventHandler_587_ForgeVillageSpawnEvent_onWorldLoad_CreateSpawnPosition.invoke(.dynamic) ~[?:?] {}\n\tat net.minecraftforge.eventbus.ASMEventHandler.invoke(ASMEventHandler.java:85) ~[eventbus-5.0.3.jar%232!/:?] {}\n\tat net.minecraftforge.eventbus.EventBus.post(EventBus.java:302) ~[eventbus-5.0.3.jar%232!/:?] {}\n\tat net.minecraftforge.eventbus.EventBus.post(EventBus.java:283) ~[eventbus-5.0.3.jar%232!/:?] {}\n\tat net.minecraftforge.event.ForgeEventFactory.onCreateWorldSpawn(ForgeEventFactory.java:523) ~[forge-1.18.2-40.2.2-universal.jar%2397!/:?] {re:mixin,re:classloading}\n\tat net.minecraft.server.MinecraftServer.m_177896_(MinecraftServer.java:410) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat net.minecraft.server.MinecraftServer.m_129815_(MinecraftServer.java:364) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat net.minecraft.server.MinecraftServer.m_130006_(MinecraftServer.java:316) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat net.minecraft.client.server.IntegratedServer.m_7038_(IntegratedServer.java:84) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.server.MinecraftServer.m_130011_(MinecraftServer.java:661) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat net.minecraft.server.MinecraftServer.m_177918_(MinecraftServer.java:261) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat java.lang.Thread.run(Thread.java:833) ~[?:?] {}\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Render thread\nStacktrace:\n\tat net.minecraft.server.level.ServerChunkCache.removeRegionTicket(ServerChunkCache.java:437) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:classloading,pl:accesstransformer:B}\n\tat net.minecraft.server.level.ServerChunkCache.m_8438_(ServerChunkCache.java:433) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:classloading,pl:accesstransformer:B}\n\tat net.minecraft.server.level.ServerLevel.m_8733_(ServerLevel.java:1138) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:computing_frames,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:zombieawareness.mixins.json:MixinPlaySound,pl:mixin:APP:zombieawareness.mixins.json:MixinLevelEvent,pl:mixin:APP:create.mixins.json:accessor.ServerLevelAccessor,pl:mixin:A}\n\tat com.natamus.villagespawnpoint_common_forge.events.VillageSpawnEvent.onWorldLoad(VillageSpawnEvent.java:35) ~[villagespawnpoint-1.18.2-4.0.jar%2388!/:?] {re:classloading}\n\tat com.natamus.villagespawnpoint.forge.events.ForgeVillageSpawnEvent.onWorldLoad(ForgeVillageSpawnEvent.java:22) ~[villagespawnpoint-1.18.2-4.0.jar%2388!/:?] {re:classloading}\n\tat net.minecraftforge.eventbus.ASMEventHandler_587_ForgeVillageSpawnEvent_onWorldLoad_CreateSpawnPosition.invoke(.dynamic) ~[?:?] {}\n\tat net.minecraftforge.eventbus.ASMEventHandler.invoke(ASMEventHandler.java:85) ~[eventbus-5.0.3.jar%232!/:?] {}\n\tat net.minecraftforge.eventbus.EventBus.post(EventBus.java:302) ~[eventbus-5.0.3.jar%232!/:?] {}\n\tat net.minecraftforge.eventbus.EventBus.post(EventBus.java:283) ~[eventbus-5.0.3.jar%232!/:?] {}\n\tat net.minecraftforge.event.ForgeEventFactory.onCreateWorldSpawn(ForgeEventFactory.java:523) ~[forge-1.18.2-40.2.2-universal.jar%2397!/:?] {re:mixin,re:classloading}\n\tat net.minecraft.server.MinecraftServer.m_177896_(MinecraftServer.java:410) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n-- Affected level --\nDetails:\n\tAll players: 0 total; []\n\tChunk stats: 7921\n\tLevel dimension: minecraft:overworld\n\tLevel spawn location: World: (368,108,864), Section: (at 0,12,0 in 23,6,54; chunk contains blocks 368,-64,864 to 383,319,879), Region: (0,1; contains chunks 0,32 to 31,63, blocks 0,-64,512 to 511,319,1023)\n\tLevel time: 0 game time, 0 day time\n\tLevel name: 新的世界\n\tLevel game mode: Game mode: creative (ID 1). Hardcore: false. Cheats: true\n\tLevel weather: Rain time: 0 (now: false), thunder time: 0 (now: false)\n\tKnown server brands: forge\n\tLevel was modded: true\n\tLevel storage version: 0x04ABD - Anvil\nStacktrace:\n\tat net.minecraft.server.MinecraftServer.m_129815_(MinecraftServer.java:364) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat net.minecraft.server.MinecraftServer.m_130006_(MinecraftServer.java:316) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat net.minecraft.client.server.IntegratedServer.m_7038_(IntegratedServer.java:84) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.server.MinecraftServer.m_130011_(MinecraftServer.java:661) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat net.minecraft.server.MinecraftServer.m_177918_(MinecraftServer.java:261) ~[client-1.18.2-20220404.173914-srg.jar%2392!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:balm.mixins.json:MinecraftServerMixin,pl:mixin:A}\n\tat java.lang.Thread.run(Thread.java:833) ~[?:?] {}\n\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.18.2\n\tMinecraft Version ID: 1.18.2\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 17.0.4, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode, sharing), Oracle Corporation\n\tMemory: 3691970048 bytes (3520 MiB) / 5066719232 bytes (4832 MiB) up to 10871635968 bytes (10368 MiB)\n\tCPUs: 8\n\tProcessor Vendor: GenuineIntel\n\tProcessor Name: Intel(R) Core(TM) i3-10105F CPU @ 3.70GHz\n\tIdentifier: Intel64 Family 6 Model 165 Stepping 3\n\tMicroarchitecture: unknown\n\tFrequency (GHz): 3.70\n\tNumber of physical packages: 1\n\tNumber of physical CPUs: 4\n\tNumber of logical CPUs: 8\n\tGraphics card #0 name: NVIDIA GeForce GTX 1070\n\tGraphics card #0 vendor: NVIDIA (0x10de)\n\tGraphics card #0 VRAM (MB): 4095.00\n\tGraphics card #0 deviceId: 0x1b81\n\tGraphics card #0 versionInfo: DriverVersion=31.0.15.2686\n\tMemory slot #0 capacity (MB): 8192.00\n\tMemory slot #0 clockSpeed (GHz): 2.67\n\tMemory slot #0 type: DDR4\n\tMemory slot #1 capacity (MB): 16384.00\n\tMemory slot #1 clockSpeed (GHz): 2.67\n\tMemory slot #1 type: DDR4\n\tVirtual memory max (MB): 40471.16\n\tVirtual memory used (MB): 17402.90\n\tSwap memory total (MB): 16000.00\n\tSwap memory used (MB): 221.52\n\tJVM Flags: 11 total; -Xmx10358m -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tServer Running: true\n\tPlayer Count: 0 / 8; []\n\tData Packs: vanilla, mod:betteranimalmodels (incompatible), mod:creativecore (incompatible), mod:jei (incompatible), mod:prefab, mod:villagespawnpoint, mod:realistic_itemdrops_mr (incompatible), mod:iceberg (incompatible), mod:flywheel (incompatible), mod:curios (incompatible), mod:patchouli (incompatible), mod:create, mod:waystones (incompatible), mod:terraforged, mod:epicfight, mod:collective (incompatible), mod:journeymap, mod:citadel (incompatible), mod:cataclysm (incompatible), mod:travelersbackpack (incompatible), mod:parcool (incompatible), mod:zombieawareness (incompatible), mod:itemborders (incompatible), mod:worldedit (incompatible), mod:coroutil (incompatible), mod:lanserverproperties (incompatible), mod:balm (incompatible), mod:villagersrespawn (incompatible), mod:ars_nouveau, mod:paraglider, mod:cloth_config (incompatible), mod:forge, mod:dummmmmmy (incompatible), mod:dynamictrees (incompatible), mod:playerrevive (incompatible), mod:geckolib3 (incompatible), mod:corpse (incompatible), mod:createaddition (incompatible)\n\tWorld Generation: Experimental\n\tType: Integrated Server (map_client.txt)\n\tIs Modded: Definitely; Client brand changed to 'forge'; Server brand changed to 'forge'\n\tOptiFine Version: OptiFine_1.18.2_HD_U_H7\n\tOptiFine Build: 20220410-185216\n\tRender Distance Chunks: 8\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 3.2.0 NVIDIA 526.86\n\tOpenGlRenderer: NVIDIA GeForce GTX 1070/PCIe/SSE2\n\tOpenGlVendor: NVIDIA Corporation\n\tCpuCount: 8\n\tModLauncher: 9.1.3+9.1.3+main.9b69c82a\n\tModLauncher launch target: forgeclient\n\tModLauncher naming: srg\n\tModLauncher services: \n\t\t mixin PLUGINSERVICE \n\t\t eventbus PLUGINSERVICE \n\t\t slf4jfixer PLUGINSERVICE \n\t\t object_holder_definalize PLUGINSERVICE \n\t\t runtime_enum_extender PLUGINSERVICE \n\t\t capability_token_subclass PLUGINSERVICE \n\t\t accesstransformer PLUGINSERVICE \n\t\t runtimedistcleaner PLUGINSERVICE \n\t\t mixin TRANSFORMATIONSERVICE \n\t\t OptiFine TRANSFORMATIONSERVICE \n\t\t fml TRANSFORMATIONSERVICE \n\t\t I18nUpdateMod TRANSFORMATIONSERVICE \n\tFML Language Providers: \n\t\tminecraft@1.0\n\t\tlowcodefml@null\n\t\tjavafml@null\n\tMod List: \n\t\tclient-1.18.2-20220404.173914-srg.jar             |Minecraft                     |minecraft                     |1.18.2              |DONE      |Manifest: ****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****\n\t\tbetteranimalmodels-1.18.2-5.6.0-forge.jar         |Cyber's Better Animal Models  |betteranimalmodels            |1.18.2-5.6.0        |DONE      |Manifest: NOSIGNATURE\n\t\tCreativeCore_FORGE_v2.6.16_mc1.18.2.jar           |CreativeCore                  |creativecore                  |0.0NONE             |DONE      |Manifest: NOSIGNATURE\n\t\tjei-1.18.2-forge-10.2.1.1004.jar                  |Just Enough Items             |jei                           |10.2.1.1004         |DONE      |Manifest: NOSIGNATURE\n\t\tprefab-1.8.2.3.jar                                |Prefab                        |prefab                        |**.**.**.**             |DONE      |Manifest: NOSIGNATURE\n\t\tvillagespawnpoint-1.18.2-4.0.jar                  |Village Spawn Point           |villagespawnpoint             |4.0                 |DONE      |Manifest: NOSIGNATURE\n\t\trealistic-item-drops-2.7DP.jar                    |Realistic Item Drops          |realistic_itemdrops_mr        |2.7DP               |DONE      |Manifest: NOSIGNATURE\n\t\tIceberg-1.18.2-forge-1.0.49.jar                   |Iceberg                       |iceberg                       |1.0.49              |DONE      |Manifest: NOSIGNATURE\n\t\tflywheel-forge-1.18.2-0.6.8.a.jar                 |Flywheel                      |flywheel                      |0.6.8.a             |DONE      |Manifest: NOSIGNATURE\n\t\tcurios-forge-1.18.2-5.0.9.0.jar                   |Curios API                    |curios                        |1.18.2-5.0.9.0      |DONE      |Manifest: NOSIGNATURE\n\t\tPatchouli-1.18.2-71.1.jar                         |Patchouli                     |patchouli                     |1.18.2-71.1         |DONE      |Manifest: NOSIGNATURE\n\t\tcreate-1.18.2-0.5.0.i.jar                         |Create                        |create                        |0.5.0.i             |DONE      |Manifest: NOSIGNATURE\n\t\twaystones-forge-1.18.2-10.2.1.jar                 |Waystones                     |waystones                     |10.2.1              |DONE      |Manifest: NOSIGNATURE\n\t\tTerraForged-1.18.2-0.3.1-alpha-2.jar              |TerraForged                   |terraforged                   |0.3.1               |DONE      |Manifest: NOSIGNATURE\n\t\tEpicFight-18.3.7.jar                              |Epic Fight                    |epicfight                     |18.3.7              |DONE      |Manifest: NOSIGNATURE\n\t\tcollective-1.18.2-6.53.jar                        |Collective                    |collective                    |6.53                |DONE      |Manifest: NOSIGNATURE\n\t\tjourneymap-1.18.2-5.9.5-forge.jar                 |Journeymap                    |journeymap                    |5.9.5               |DONE      |Manifest: NOSIGNATURE\n\t\tcitadel-1.11.3-1.18.2.jar                         |Citadel                       |citadel                       |1.11.3              |DONE      |Manifest: NOSIGNATURE\n\t\tL_Enders+Cataclysm-0.51-changed+Them+-1.18.2.jar  |Cataclysm Mod                 |cataclysm                     |1.0                 |DONE      |Manifest: NOSIGNATURE\n\t\tTravelersBackpack-1.18.2-7.1.30.jar               |Traveler's Backpack           |travelersbackpack             |7.1.30              |DONE      |Manifest: NOSIGNATURE\n\t\tParCool-1.18.2-3.0.1.0-R.jar                      |ParCool!                      |parcool                       |1.18.2-3.0.1.0-R    |DONE      |Manifest: NOSIGNATURE\n\t\tzombieawareness-1.18.1-1.12.3.jar                 |Zombie Awareness              |zombieawareness               |1.18.1-1.12.3       |DONE      |Manifest: NOSIGNATURE\n\t\tItemBorders-1.18.1-1.1.5.jar                      |Item Borders                  |itemborders                   |1.1.5               |DONE      |Manifest: NOSIGNATURE\n\t\tworldedit-mod-7.2.10.jar                          |WorldEdit                     |worldedit                     |7.2.10+1742f98      |DONE      |Manifest: NOSIGNATURE\n\t\tcoroutil-1.18.2-1.2.45.jar                        |CoroUtil                      |coroutil                      |1.18.2-1.2.45       |DONE      |Manifest: NOSIGNATURE\n\t\tlanserverproperties-1.8-forge.jar                 |Lan Server Properties         |lanserverproperties           |1.8                 |DONE      |Manifest: NOSIGNATURE\n\t\tbalm-3.2.6.jar                                    |Balm                          |balm                          |3.2.6               |DONE      |Manifest: NOSIGNATURE\n\t\tVillagersRespawn-1.18.1-1.39.0.5.jar              |Villagers Respawn  Mod        |villagersrespawn              |1.18.1-1.39.0.5     |DONE      |Manifest: NOSIGNATURE\n\t\tars_nouveau-1.18.2-2.8.0.jar                      |Ars Nouveau                   |ars_nouveau                   |2.8.0               |DONE      |Manifest: NOSIGNATURE\n\t\tParaglider-1.18.2-1.6.0.6.jar                     |Paraglider                    |paraglider                    |**.**.**.**             |DONE      |Manifest: NOSIGNATURE\n\t\tcloth-config-6.4.90-forge.jar                     |Cloth Config v4 API           |cloth_config                  |6.4.90              |DONE      |Manifest: NOSIGNATURE\n\t\tforge-1.18.2-40.2.2-universal.jar                 |Forge                         |forge                         |40.2.2              |DONE      |Manifest: ****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****\n\t\tMmmMmmMmmMmm-1.18.2-1.5.2.jar                     |MmmMmmMmmMmm                  |dummmmmmy                     |1.18-1.5.2          |DONE      |Manifest: NOSIGNATURE\n\t\tDynamicTrees-1.18.2-1.0.1.jar                     |Dynamic Trees                 |dynamictrees                  |1.18.2-1.0.1        |DONE      |Manifest: NOSIGNATURE\n\t\tPlayerRevive_FORGE_v2.0.13_mc1.18.2.jar           |PlayerRevive                  |playerrevive                  |2.0.13              |DONE      |Manifest: NOSIGNATURE\n\t\tgeckolib-forge-1.18-3.0.57.jar                    |GeckoLib                      |geckolib3                     |3.0.57              |DONE      |Manifest: NOSIGNATURE\n\t\tcorpse-1.18.2-1.0.2.jar                           |Corpse                        |corpse                        |1.18.2-1.0.2        |DONE      |Manifest: NOSIGNATURE\n\t\tcreateaddition-1.18.2-20230426a.jar               |Create Crafts & Additions     |createaddition                |1.18.2-20230426a    |DONE      |Manifest: NOSIGNATURE\n\tFlywheel Backend: GL33 Instanced Arrays\n\tCrash Report UUID: 0c26fdb2-c733-4380-9808-fc021f633df4\n\tFML: 40.2\n\tForge: net.minecraftforge:40.2.2\n#@!@# Game crashed! Crash report saved to: #@!@# F:\\我的世界\\生存魂\\crash-reports\\crash-2023-05-02_15.08.45-server.txt\n2023-05-02 15:08:47,089 Server thread WARN Error parsing URI F:\\我的世界\\生存魂\\versions\\1.18.2\\log4j2.xml\n2023-05-02 15:08:47,117 Server thread WARN Unable to register Log4j shutdown hook because JVM is shutting down. Using SimpleLogger\nAL lib: (EE) alc_cleanup: 1 device not closed"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/optifine_is_not_compatible_with_forge4.txt",
    "content": "[authlib-injector] [INFO] Logging file: E:\\.minecraft\\versions\\1.19.3\\authlib-injector.log\n[authlib-injector] [INFO] Version: 1.2.2\n[authlib-injector] [INFO] Authentication server: http://localhost:12011\n[authlib-injector] [WARNING] You are using HTTP protocol, which is INSECURE! Please switch to HTTPS if possible.\n[16:34:52] [main/INFO]: ModLauncher running: args [--username, sroYors, --version, 1.19.3, --gameDir, E:\\\\.minecraft\\versions\\1.19.3, --assetsDir, E:\\.minecraft\\assets, --assetIndex, 2, --uuid, 8872cb99f6a830e0bd038210ff4122c4, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, legacy, --versionType, HMCL **.**.**.**, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker, --launchTarget, forgeclient, --fml.forgeVersion, 44.1.23, --fml.mcVersion, 1.19.3, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20221207.122022]\n[16:34:52] [main/INFO]: ModLauncher 10.0.8+10.0.8+main.0ef7e830 starting: java version 17.0.6 by BellSoft; OS Windows 10 arch amd64 version 10.0\n[16:34:52] [main/INFO]: OptiFineTransformationService.onLoad\n[16:34:52] [main/INFO]: OptiFine ZIP file URL: union:/E:/.minecraft/libraries/optifine/OptiFine/1.19.3_HD_U_I2_pre5/OptiFine-1.19.3_HD_U_I2_pre5.jar%2399!/\n[16:34:52] [main/INFO]: OptiFine ZIP file: E:\\.minecraft\\libraries\\optifine\\OptiFine\\1.19.3_HD_U_I2_pre5\\OptiFine-1.19.3_HD_U_I2_pre5.jar\n[16:34:52] [main/INFO]: Target.PRE_CLASS is available\n[16:34:52] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/E:/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%2394!/ Service=ModLauncher Env=CLIENT\n[16:34:52] [main/INFO]: OptiFineTransformationService.initialize\n[2023-04-10 16:34:53] [INFO]: I18nUpdate Mod 3.4.1 is loaded in 1.19.3 with Forge\n[2023-04-10 16:34:53] [INFO]: Downloading: http://downloader1.meitangdehulu.com:22943/Minecraft-Mod-Language-Modpack-1-19.zip -> C:\\Users\\********\\.i18nupdatemod\\1.19.3\\Minecraft-Mod-Language-Modpack-1-19.zip.tmp\n[2023-04-10 16:35:05] [INFO]: Downloading: http://downloader1.meitangdehulu.com:22943/Minecraft-Mod-Language-Modpack-1-18.zip -> C:\\Users\\********\\.i18nupdatemod\\1.18.2\\Minecraft-Mod-Language-Modpack-1-18.zip.tmp\n[2023-04-10 16:35:28] [INFO]: Converting: C:\\Users\\********\\.i18nupdatemod\\1.19.3\\Minecraft-Mod-Language-Modpack-1-19.zip\n[2023-04-10 16:35:28] [INFO]: Converting: C:\\Users\\********\\.i18nupdatemod\\1.18.2\\Minecraft-Mod-Language-Modpack-1-18.zip\n[2023-04-10 16:35:31] [INFO]: Converted: [C:\\Users\\********\\.i18nupdatemod\\1.19.3\\Minecraft-Mod-Language-Modpack-1-19.zip, C:\\Users\\********\\.i18nupdatemod\\1.18.2\\Minecraft-Mod-Language-Modpack-1-18.zip] -> C:\\Users\\********\\.i18nupdatemod\\1.19.3\\Minecraft-Mod-Language-Modpack-Converted-12.zip\n[2023-04-10 16:35:31] [INFO]: Synchronized: C:\\Users\\********\\.i18nupdatemod\\1.19.3\\Minecraft-Mod-Language-Modpack-Converted-12.zip -> E:\\.minecraft\\versions\\1.19.3\\resourcepacks\\Minecraft-Mod-Language-Modpack-Converted-12.zip\n[16:35:31] [main/ERROR]: Locator {mods folder locator at E:\\.minecraft\\versions\\1.19.3\\mods} found an invalid mod file net.minecraftforge.fml.loading.moddiscovery.ModFileInfo@6968bcec\nnet.minecraftforge.fml.loading.moddiscovery.InvalidModFileException: Illegal version number specified forge-1.0-BetaPreview2-1.19.3 (mythicupgrades-forge-1.0-BetaPreview2-1.19.3.jar)\n\tat net.minecraftforge.fml.loading.moddiscovery.ModInfo.<init>(ModInfo.java:104) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.ModFileInfo.lambda$new$2(ModFileInfo.java:69) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat java.util.stream.ReferencePipeline$3$1.accept(Unknown Source) ~[?:?]\n\tat java.util.ArrayList$ArrayListSpliterator.forEachRemaining(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.copyInto(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) ~[?:?]\n\tat java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.evaluate(Unknown Source) ~[?:?]\n\tat java.util.stream.ReferencePipeline.collect(Unknown Source) ~[?:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.ModFileInfo.<init>(ModFileInfo.java:70) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.ModFileParser.modsTomlParser(ModFileParser.java:47) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.ModFileParser.readModList(ModFileParser.java:31) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.ModFile.<init>(ModFile.java:79) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.ModFile.<init>(ModFile.java:68) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.AbstractModProvider.createMod(AbstractModProvider.java:52) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator.lambda$scanMods$0(AbstractJarFileModLocator.java:19) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat java.util.stream.ReferencePipeline$3$1.accept(Unknown Source) ~[?:?]\n\tat java.util.ArrayList.forEach(Unknown Source) ~[?:?]\n\tat java.util.stream.SortedOps$RefSortingSink.end(Unknown Source) ~[?:?]\n\tat java.util.stream.Sink$ChainedReference.end(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.copyInto(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.evaluate(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.evaluateToArrayNode(Unknown Source) ~[?:?]\n\tat java.util.stream.ReferencePipeline.toArray(Unknown Source) ~[?:?]\n\tat java.util.stream.ReferencePipeline.toArray(Unknown Source) ~[?:?]\n\tat java.util.stream.ReferencePipeline.toList(Unknown Source) ~[?:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator.scanMods(AbstractJarFileModLocator.java:19) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer.discoverMods(ModDiscoverer.java:74) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat net.minecraftforge.fml.loading.FMLLoader.beginModScan(FMLLoader.java:166) ~[fmlloader-1.19.3-44.1.23.jar:1.0]\n\tat net.minecraftforge.fml.loading.FMLServiceProvider.beginScanning(FMLServiceProvider.java:86) ~[fmlloader-1.19.3-44.1.23.jar:1.0]\n\tat cpw.mods.modlauncher.TransformationServiceDecorator.runScan(TransformationServiceDecorator.java:112) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.lambda$runScanningTransformationServices$8(TransformationServicesHandler.java:100) ~[modlauncher-10.0.8.jar:?]\n\tat java.util.stream.ReferencePipeline$3$1.accept(Unknown Source) ~[?:?]\n\tat java.util.HashMap$ValueSpliterator.forEachRemaining(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.copyInto(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.evaluate(Unknown Source) ~[?:?]\n\tat java.util.stream.AbstractPipeline.evaluateToArrayNode(Unknown Source) ~[?:?]\n\tat java.util.stream.ReferencePipeline.toArray(Unknown Source) ~[?:?]\n\tat java.util.stream.ReferencePipeline.toArray(Unknown Source) ~[?:?]\n\tat java.util.stream.ReferencePipeline.toList(Unknown Source) ~[?:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.runScanningTransformationServices(TransformationServicesHandler.java:102) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:55) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:87) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:77) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?]\n[16:35:31] [main/WARN]: Mod file E:\\.minecraft\\libraries\\net\\minecraftforge\\fmlcore\\1.19.3-44.1.23\\fmlcore-1.19.3-44.1.23.jar is missing mods.toml file\n[16:35:31] [main/WARN]: Mod file E:\\.minecraft\\libraries\\net\\minecraftforge\\javafmllanguage\\1.19.3-44.1.23\\javafmllanguage-1.19.3-44.1.23.jar is missing mods.toml file\n[16:35:31] [main/WARN]: Mod file E:\\.minecraft\\libraries\\net\\minecraftforge\\lowcodelanguage\\1.19.3-44.1.23\\lowcodelanguage-1.19.3-44.1.23.jar is missing mods.toml file\n[16:35:31] [main/WARN]: Mod file E:\\.minecraft\\libraries\\net\\minecraftforge\\mclanguage\\1.19.3-44.1.23\\mclanguage-1.19.3-44.1.23.jar is missing mods.toml file\n[16:35:32] [main/INFO]: Found mod file fmlcore-1.19.3-44.1.23.jar of type LIBRARY with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@889a8a8\n[16:35:32] [main/INFO]: Found mod file javafmllanguage-1.19.3-44.1.23.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@889a8a8\n[16:35:32] [main/INFO]: Found mod file lowcodelanguage-1.19.3-44.1.23.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@889a8a8\n[16:35:32] [main/INFO]: Found mod file mclanguage-1.19.3-44.1.23.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@889a8a8\n[16:35:32] [main/INFO]: Found mod file client-1.19.3-20221207.122022-srg.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@889a8a8\n[16:35:32] [main/INFO]: Found mod file forge-1.19.3-44.1.23-universal.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@889a8a8\n[16:35:32] [main/INFO]: No dependencies to load found. Skipping!\n[16:35:32] [main/INFO]: OptiFineTransformationService.transformers\n[16:35:32] [main/INFO]: Targets: 394\n[16:35:32] [main/INFO]: additionalClassesLocator: [optifine., net.optifine.]\n[16:35:33] [main/INFO]: Launching target 'forgeclient' with arguments [--version, 1.19.3, --gameDir, E:\\.minecraft\\versions\\1.19.3, --assetsDir, E:\\.minecraft\\assets, --uuid, 8872cb99f6a830e0bd038210ff4122c4, --username, sroYors, --assetIndex, 2, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, legacy, --versionType, HMCL **.**.**.**, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker]\n[authlib-injector] [INFO] Transformed [net.minecraft.client.main.Main] with [Main Arguments Transformer]\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.properties.Property] with [Yggdrasil Public Key Transformer]\nCompletely ignored arguments: [--tweakClass, optifine.OptiFineTweaker]\n[authlib-injector] [INFO] Httpd is running on port 12231\n[authlib-injector] [INFO] Transformed [net.minecraft.client.player.AbstractClientPlayer] with [Constant URL Transformer]\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.HttpAuthenticationService] with [ConcatenateURL Workaround]\n[authlib-injector] [INFO] Transformed [net.minecraft.client.main.Main] with [Main Arguments Transformer]\n[16:35:36] [pool-3-thread-1/INFO]: Building unoptimized datafixer\n[16:35:38] [Render thread/WARN]: Assets URL 'union:/E:/.minecraft/libraries/net/minecraft/client/1.19.3-20221207.122022/client-1.19.3-20221207.122022-srg.jar%23312!/assets/.mcassetsroot' uses unexpected schema\n[16:35:38] [Render thread/WARN]: Assets URL 'union:/E:/.minecraft/libraries/net/minecraft/client/1.19.3-20221207.122022/client-1.19.3-20221207.122022-srg.jar%23312!/data/.mcassetsroot' uses unexpected schema\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilEnvironment] with [Constant URL Transformer]\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilServicesKeyInfo] with [Yggdrasil Public Key Transformer]\n[16:35:38] [Render thread/INFO]: Environment: authHost='http://localhost:12011/authserver', accountsHost='http://127.0.0.1:12231/https/api.mojang.com', sessionHost='http://127.0.0.1:12231/https/sessionserver.mojang.com', servicesHost='http://127.0.0.1:12231/https/api.minecraftservices.com', name='PROD'\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService] with [Texture Whitelist Transformer]\n[16:35:38] [Render thread/INFO]: Setting user: sroYors\n[16:35:38] [Render thread/INFO]: Backend library: LWJGL version 3.3.1 build 7\n[16:35:39] [Render thread/INFO]: [OptiFine] \n[16:35:39] [Render thread/INFO]: [OptiFine] OptiFine_1.19.3_HD_U_I2_pre5\n[16:35:39] [Render thread/INFO]: [OptiFine] Build: 20230219-203817\n[16:35:39] [Render thread/INFO]: [OptiFine] OS: Windows 10 (amd64) version 10.0\n[16:35:39] [Render thread/INFO]: [OptiFine] Java: 17.0.6, BellSoft\n[16:35:39] [Render thread/INFO]: [OptiFine] VM: OpenJDK 64-Bit Server VM (mixed mode, sharing), BellSoft\n[16:35:39] [Render thread/INFO]: [OptiFine] LWJGL: 3.4.0 Win32 WGL Null EGL OSMesa VisualC DLL\n[16:35:39] [Render thread/INFO]: [OptiFine] OpenGL: Intel(R) Iris(R) Xe Graphics, version 3.2.0 - Build 31.0.101.3358, Intel\n[16:35:39] [Render thread/INFO]: [OptiFine] OpenGL Version: 3.2.0\n[16:35:39] [Render thread/INFO]: [OptiFine] Maximum texture size: 16384x16384\n[16:35:39] [VersionCheck/INFO]: [OptiFine] Checking for new version\n[16:35:39] [Render thread/INFO]: [Shaders] OpenGL Version: 3.2.0 - Build 31.0.101.3358\n[16:35:39] [Render thread/INFO]: [Shaders] Vendor:  Intel\n[16:35:39] [Render thread/INFO]: [Shaders] Renderer: Intel(R) Iris(R) Xe Graphics\n[16:35:39] [Render thread/INFO]: [Shaders] Capabilities:  2.0  2.1  3.0  3.2  - \n[16:35:39] [Render thread/INFO]: [Shaders] GL_MAX_DRAW_BUFFERS: 8\n[16:35:39] [Render thread/INFO]: [Shaders] GL_MAX_COLOR_ATTACHMENTS: 8\n[16:35:39] [Render thread/INFO]: [Shaders] GL_MAX_TEXTURE_IMAGE_UNITS: 32\n[16:35:39] [Render thread/INFO]: [Shaders] Load shaders configuration.\n[16:35:39] [Render thread/INFO]: [Shaders] Loaded shaderpack: ComplementaryReimagined_r2.0.1.zip\n[16:35:39] [Render thread/INFO]: [OptiFine] [Shaders] Worlds: -1, 0, 1\n[16:35:39] [Render thread/WARN]: [OptiFine] Ambiguous shader option: WATER_STYLE_DEFAULT\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: 1\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: 3\n[16:35:39] [Render thread/WARN]: [OptiFine] Ambiguous shader option: AURORA_STYLE_DEFAULT\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: 1\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: 2\n[16:35:39] [Render thread/WARN]: [OptiFine] Ambiguous shader option: SUN_MOON_STYLE_DEFAULT\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: 1\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: 2\n[16:35:39] [Render thread/WARN]: [OptiFine] Ambiguous shader option: CLOUD_STYLE_DEFAULT\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: 1\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: 3\n[16:35:39] [Render thread/WARN]: [OptiFine] Ambiguous shader option: WATER_STYLE\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: WATER_STYLE_DEFAULT\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: WATER_STYLE_DEFINE\n[16:35:39] [Render thread/WARN]: [OptiFine] Ambiguous shader option: AURORA_STYLE\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: AURORA_STYLE_DEFAULT\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: AURORA_STYLE_DEFINE\n[16:35:39] [Render thread/WARN]: [OptiFine] Ambiguous shader option: SUN_MOON_STYLE\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: SUN_MOON_STYLE_DEFAULT\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: SUN_MOON_STYLE_DEFINE\n[16:35:39] [Render thread/WARN]: [OptiFine] Ambiguous shader option: CLOUD_STYLE\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: CLOUD_STYLE_DEFAULT\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: CLOUD_STYLE_DEFINE\n[16:35:39] [Render thread/WARN]: [OptiFine] Ambiguous shader option: sunPathRotation\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: 0.0\n[16:35:39] [Render thread/WARN]: [OptiFine]  - in world-1/gbuffers_basic.vsh: -40.0\n[16:35:39] [VersionCheck/INFO]: [OptiFine] java.io.FileNotFoundException: http://optifine.net/version/1.19.3/HD_U.txt\n[16:35:39] [Render thread/INFO]: [OptiFine] [Shaders] Delayed loading of block mappings after resources are loaded\n[16:35:39] [Render thread/INFO]: [OptiFine] [Shaders] Delayed loading of item mappings after resources are loaded\n[16:35:39] [Render thread/INFO]: [OptiFine] [Shaders] Delayed loading of entity mappings after resources are loaded\n[16:35:40] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.ClassNotFoundException: sun.misc.SharedSecrets\n[16:35:40] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.ClassNotFoundException: jdk.internal.misc.SharedSecrets\n[16:35:40] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.ClassNotFoundException: sun.misc.VM\n[16:35:40] [Render thread/WARN]: [OptiFine] (Reflector) java.lang.reflect.InaccessibleObjectException: Unable to make public static long jdk.internal.misc.VM.maxDirectMemory() accessible: module java.base does not \"exports jdk.internal.misc\" to module net.optifine\n[16:35:40] [Render thread/INFO]: [Shaders] Custom texture: texture.deferred.colortex3 = lib/textures/clouds.png\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: framemod8\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: isEyeInCave\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: isDry\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: isRainy\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: isSnowy\n[16:35:40] [Render thread/INFO]: [Shaders] Custom variable: difX\n[16:35:40] [Render thread/INFO]: [Shaders] Custom variable: difY\n[16:35:40] [Render thread/INFO]: [Shaders] Custom variable: difZ\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: velocity\n[16:35:40] [Render thread/INFO]: [Shaders] Custom variable: difSum\n[16:35:40] [Render thread/INFO]: [Shaders] Custom variable: moving\n[16:35:40] [Render thread/INFO]: [Shaders] Custom variable: moved\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: starter\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: frameTimeSmooth\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: eyeBrightnessM\n[16:35:40] [Render thread/INFO]: [Shaders] Custom uniform: rainFactor\n[16:35:40] [modloading-worker-0/INFO]: Forge mod loading, version 44.1.23, for MC 1.19.3 with MCP 20221207.122022\n[16:35:40] [modloading-worker-0/INFO]: MinecraftForge v44.1.23 Initialized\n[16:35:41] [Render thread/INFO]: Narrator library for x64 successfully loaded\n[16:35:41] [Render thread/INFO]: Reloading ResourceManager: vanilla, file/Minecraft-Mod-Language-Modpack-Converted-12.zip, file/FreshAnimations_v1.8.zip, file/Better-Leaves-7.1-1.19+.zip, file/Resource+pack+for+Better+End+Sky+mod.zip, file/Stay_True_1.19.zip, mod_resources\n[16:35:41] [Render thread/INFO]: [OptiFine] *** Reloading textures ***\n[16:35:41] [Render thread/INFO]: [OptiFine] Resource packs: file/Minecraft-Mod-Language-Modpack-Converted-12.zip, file/FreshAnimations_v1.8.zip, file/Better-Leaves-7.1-1.19+.zip, file/Resource+pack+for+Better+End+Sky+mod.zip, file/Stay_True_1.19.zip, mod_resources\n[16:35:41] [Render thread/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:41] [Render thread/INFO]: [OptiFine] *** Reflector Forge ***\n[16:35:41] [Forge Version Check/INFO]: [forge] Starting version check at https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json\n[16:35:41] [Render thread/INFO]: [OptiFine] *** Reflector Vanilla ***\n[16:35:41] [Render thread/INFO]: [OptiFine] Loading optifine/color.properties\n[16:35:41] [Render thread/INFO]: [OptiFine] screen.loading = 111114\n[16:35:41] [Render thread/INFO]: [OptiFine] screen.loading.bar = 72655b\n[16:35:41] [Render thread/INFO]: [OptiFine] screen.loading.progress = ece5d9\n[16:35:42] [Forge Version Check/INFO]: [forge] Found status: AHEAD Current: 44.1.23 Target: null\n[16:35:43] [Worker-Main-3/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/signs.png\n[16:35:43] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[16:35:43] [Worker-Main-3/INFO]: [OptiFine] Sprite size: 64\n[16:35:43] [Worker-Main-3/INFO]: [OptiFine] Mipmap levels: 6\n[16:35:43] [Worker-Main-1/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/banner_patterns.png\n[16:35:43] [Worker-Main-1/INFO]: [OptiFine] Multitexture: false\n[16:35:43] [Worker-Main-1/INFO]: [OptiFine] Sprite size: 64\n[16:35:43] [Worker-Main-1/INFO]: [OptiFine] Mipmap levels: 6\n[16:35:44] [Worker-Main-6/WARN]: Exception loading blockstate definition: 'minecraft:blockstates/cut_sandstone.json' in resourcepack: 'file/Stay_True_1.19.zip' for variant: 'axis=z': Unknown blockstate property: 'axis'\n[16:35:44] [Worker-Main-6/WARN]: Exception loading blockstate definition: 'minecraft:blockstates/cut_sandstone.json' in resourcepack: 'file/Stay_True_1.19.zip' for variant: 'axis=x': Unknown blockstate property: 'axis'\n[16:35:44] [Worker-Main-6/WARN]: Exception loading blockstate definition: 'minecraft:blockstates/cut_sandstone.json' in resourcepack: 'file/Stay_True_1.19.zip' for variant: 'axis=y': Unknown blockstate property: 'axis'\n[16:35:45] [Worker-Main-6/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:45] [Worker-Main-6/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:45] [Worker-Main-6/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:45] [Worker-Main-6/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:45] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:missingno, 16 -> 64\n[16:35:45] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[16:35:45] [Worker-Main-1/INFO]: [OptiFine] Scaled too small texture: minecraft:missingno, 16 -> 64\n[16:35:45] [Worker-Main-1/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[16:35:46] [Worker-Main-5/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/shield_patterns.png\n[16:35:46] [Worker-Main-5/INFO]: [OptiFine] Multitexture: false\n[16:35:46] [Worker-Main-5/INFO]: [OptiFine] Sprite size: 64\n[16:35:46] [Worker-Main-5/INFO]: [OptiFine] Mipmap levels: 6\n[16:35:46] [Worker-Main-2/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/chest.png\n[16:35:46] [Worker-Main-2/INFO]: [OptiFine] Multitexture: false\n[16:35:46] [Worker-Main-2/INFO]: [OptiFine] Sprite size: 64\n[16:35:46] [Worker-Main-2/INFO]: [OptiFine] Mipmap levels: 6\n[16:35:47] [Worker-Main-3/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/shulker_boxes.png\n[16:35:47] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[16:35:47] [Worker-Main-3/INFO]: [OptiFine] Sprite size: 64\n[16:35:47] [Worker-Main-3/INFO]: [OptiFine] Mipmap levels: 6\n[16:35:47] [Worker-Main-5/INFO]: [OptiFine] Scaled too small texture: minecraft:missingno, 16 -> 64\n[16:35:47] [Worker-Main-5/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[16:35:48] [Worker-Main-6/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/beds.png\n[16:35:48] [Worker-Main-6/INFO]: [OptiFine] Multitexture: false\n[16:35:48] [Worker-Main-6/INFO]: [OptiFine] Sprite size: 64\n[16:35:48] [Worker-Main-6/INFO]: [OptiFine] Mipmap levels: 6\n[16:35:48] [Worker-Main-2/INFO]: [OptiFine] Scaled too small texture: minecraft:missingno, 16 -> 64\n[16:35:48] [Worker-Main-2/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/blocks.png\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] Multitexture: false\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] Multipass connected textures: false\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/andesite/andesite.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/bricks/bricks.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/coal_ore/coal_ore.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/coarse_dirt/coarse_dirt.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:pumking\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:jack_o'lantern\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/copper_ore/copper_ore.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/cracked_stone_bricks/cracked_stone_bricks.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/crimson_nylium/crimson_nylium.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:chiseled_sanstone\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hay_bale_side\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:deepslate_emerald\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/diamond_ore/diamond_ore.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/dirt/dirt.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:pumking\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:jack_o'lantern\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/emerald_ore/emerald_ore.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/end_stone_bricks/end_stone_bricks.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/gold_ore/gold_ore.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/granite/granite.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/grass_block/grass_block.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:chiseled_sanstone\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/gravel/gravel.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:chiseled_sanstone\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:farmland_moist\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: stone\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/iron_ore/iron_ore.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/lapis_ore/lapis_ore.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/log_dirt_cmt/acacia_dirt/acacia_log.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/log_dirt_cmt/birch_dirt/birch_log.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/log_dirt_cmt/darkoak_dirt/dark_oak_log.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/log_dirt_cmt/jungle_dirt/jungle_log.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/log_dirt_cmt/mangrove_dirt/mangrove_log.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/log_dirt_cmt/oak_dirt/oak_logs.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/log_dirt_cmt/spruce_dirt/spruce_log.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/moss_block/moss_block.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:chiseled_sanstone\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:mossy_block\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/mossy_stone_bricks/mossy_stone_bricks.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/mushroom_stem_dirt/mushroom_stem.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] Invalid tiles, must be at least 47: optifine/ctm/_overlays/mushroom_stem_dirt/mushroom_stem.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/nether_bricks/nether_bricks.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/nether_gold_ore/nether_gold_ore.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: netherrack\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/nether_quartz_ore/nether_quartz_ore.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: netherrack\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/netherrack/netherrack.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: netherrack\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/podzol/podzol.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:pumking\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:jack_o'lantern\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:farmland_moist\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: podzol\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/red_nether_bricks/red_nether_bricks.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/red_sand/red_sand.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:chiseled_sanstone\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:farmland_moist\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: sand\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/redstone_ore/redstone_ore.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/rooted_dirt/rooted_dirt.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:pumking\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:jack_o'lantern\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/sand/sand.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:farmland_moist\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: sand\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/sandstone/sandstone.properties\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/soul_soil/soul_soil.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:farmland_moist\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:grass_path\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: soul_soil\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/stone_bricks/stone_bricks.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:48] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/_overlays/warped_nylium/warped_nylium.properties\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:chiseled_sanstone\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:48] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hay_bale_side\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/acacia_log/acacia_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/acacia_planks/acacia_planks.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/birch_log/birch_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/birch_planks/birch_planks.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/chiseled_sandstone/chiseled_sandstone.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/crack_polished_blackstone_bricks/cracked_polished_blackstone_bricks.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:basalt_top\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/cracked_deepslate_bricks/cracked_deepslate_bricks.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/crimson_planks/crimson_planks.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/cut_sandstone/cut_sandstone.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/dark_oak_log/dark_oak_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/dark_oak_planks/dark_oak_planks.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/deepslate_bricks/deepslate_bricks.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/glass/aregular/glass.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/glass/aregular/glass_pane.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/jungle_log/jungle_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/jungle_planks/jungle_planks.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/leaves/birch_leaves/cherry_blossom/birch_leaves.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:wooded_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:wooded_hills\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:snowy_tundra\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:snowy_taiga_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:gravelly_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:wooded_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:mountain_edge\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:snowy_mountains\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/leaves/birch_leaves/cherry_blossom/birch_leaves_bushy.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:wooded_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:wooded_hills\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:snowy_tundra\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:snowy_taiga_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:gravelly_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:wooded_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:mountain_edge\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: snowy_mountains\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/leaves/birch_leaves/cherry_blossom/birch_leaves_bushy1.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:wooded_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:wooded_hills\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:snowy_tundra\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:snowy_taiga_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:gravelly_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:wooded_mountains\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:mountain_edge\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: minecraft:\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Biome not found: snowy_mountains\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/leaves/birch_leaves/orange/birch_leaves.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/leaves/birch_leaves/orange/birch_leaves_bushy.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/leaves/birch_leaves/orange/birch_leaves_bushy1.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mangrove_log/mangrove_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mangrove_planks/mangrove_planks.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mud/mud.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:black_terracottaterracotta\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:planks\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:silver_glazed_terracotta\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:stained_hardened_clay\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:hardened_clay\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:pumking\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:jack_o'lantern\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_cracked_stone_brick\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bed_rock\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:bone_blok\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:infested_stonebrick\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/mushroom_stem/muchsroom_stem.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/nether_gold_ore/nether_gold_ore.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: netherrack\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/nether_quartz_ore/nether_quartz_ore.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: netherrack\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/netherrack/netherrack.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Invalid number: netherrack\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/oak_log/oak_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/oak_planks/oak_planks.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/polished_blackstone_bricks/polished_blackstone_bricks.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:netherrack_gold_ore\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:basalt_top\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/purpur_pillar/purpure_pillar.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/quartz_bricks/quartz_bricks.properties\n[16:35:49] [Worker-Main-4/WARN]: [OptiFine] ConnectedTextures: Block not found for name: minecraft:light_glazed_terracotta\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/quartz_pillar/quartz_pillar.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/spruce_log/spruce_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/spruce_planks/spruce_planks.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/stone/stone.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/stripped_acacia_log/stripped_acacia_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/stripped_birch_log/stripped_birch_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/stripped_dark_oak_log/stripped_dark_oak_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/stripped_jungle_log/stripped_jungle_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/stripped_mangrove_log/stripped_mangrove_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/stripped_oak_log/stripped_oak_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/stripped_spruce_log/stripped_spruce_log.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/warped_planks/warped_planks.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] Multipass connected textures: false\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] Multipass connected textures: false\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] Multipass connected textures: false\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] Multipass connected textures: false\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] Multipass connected textures: false\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/00_glass_white/glass_pane_white.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/00_glass_white/glass_white.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/01_glass_orange/glass_orange.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/01_glass_orange/glass_pane_orange.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/02_glass_magenta/glass_magenta.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/02_glass_magenta/glass_pane_magenta.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/03_glass_light_blue/glass_light_blue.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/03_glass_light_blue/glass_pane_light_blue.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/04_glass_yellow/glass_pane_yellow.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/04_glass_yellow/glass_yellow.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/05_glass_lime/glass_lime.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/05_glass_lime/glass_pane_lime.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/06_glass_pink/glass_pane_pink.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/06_glass_pink/glass_pink.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/07_glass_gray/glass_gray.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/07_glass_gray/glass_pane_gray.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/08_glass_light_gray/glass_light_gray.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/08_glass_light_gray/glass_pane_light_gray.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/09_glass_cyan/glass_cyan.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/09_glass_cyan/glass_pane_cyan.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/10_glass_purple/glass_pane_purple.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/10_glass_purple/glass_purple.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/11_glass_blue/glass_blue.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/11_glass_blue/glass_pane_blue.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/12_glass_brown/glass_brown.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/12_glass_brown/glass_pane_brown.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/13_glass_green/glass_green.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/13_glass_green/glass_pane_green.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/14_glass_red/glass_pane_red.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/14_glass_red/glass_red.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/15_glass_black/glass_black.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/15_glass_black/glass_pane_black.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] ConnectedTextures: optifine/ctm/default/21_tinted_glass/tinted_glass.properties\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] Multipass connected textures: false\n[16:35:49] [Worker-Main-4/INFO]: [OptiFine] BetterGrass: Parsing default configuration optifine/bettergrass.properties\n[16:35:49] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:missingno, 16 -> 64\n[16:35:49] [Worker-Main-3/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[16:35:49] [Worker-Main-2/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/paintings.png\n[16:35:49] [Worker-Main-2/INFO]: [OptiFine] Multitexture: false\n[16:35:50] [Worker-Main-1/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/particles.png\n[16:35:50] [Worker-Main-1/INFO]: [OptiFine] Multitexture: false\n[16:35:50] [Worker-Main-5/INFO]: [OptiFine] Scaled too small texture: minecraft:missingno, 16 -> 64\n[16:35:50] [Worker-Main-5/INFO]: [OptiFine] Scaled too small texture: minecraft:optifine/ctm/default/empty, 16 -> 64\n[16:35:50] [Worker-Main-3/INFO]: [OptiFine] Pre-stitch: minecraft:textures/atlas/mob_effects.png\n[16:35:50] [Worker-Main-3/INFO]: [OptiFine] Multitexture: false\n[16:35:51] [Worker-Main-4/INFO]: [OptiFine] Mipmap levels: 4\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_axe_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_axe_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_axe_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_axe_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_boots_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_boots_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_boots_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_boots_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_chestplate_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_chestplate_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_chestplate_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_chestplate_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_helmet_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_helmet_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_helmet_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_helmet_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_hoe_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_hoe_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_hoe_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_hoe_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_leggings_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_leggings_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_leggings_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_leggings_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_pickaxe_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_pickaxe_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_pickaxe_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_pickaxe_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_shovel_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_shovel_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_shovel_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_shovel_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_sword_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_sword_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/netherite_sword_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/netherite_sword_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/pack_n.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/pack_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/cool netherrite/pack_s.png: Invalid segment 'cool netherrite' in path 'textures/item/cool netherrite/pack_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/netherite no gold/netherite_boots_n.png: Invalid segment 'netherite no gold' in path 'textures/item/netherite no gold/netherite_boots_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/netherite no gold/netherite_boots_s.png: Invalid segment 'netherite no gold' in path 'textures/item/netherite no gold/netherite_boots_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/netherite no gold/netherite_chestplate_n.png: Invalid segment 'netherite no gold' in path 'textures/item/netherite no gold/netherite_chestplate_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/netherite no gold/netherite_chestplate_s.png: Invalid segment 'netherite no gold' in path 'textures/item/netherite no gold/netherite_chestplate_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/netherite no gold/netherite_helmet_n.png: Invalid segment 'netherite no gold' in path 'textures/item/netherite no gold/netherite_helmet_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/netherite no gold/netherite_helmet_s.png: Invalid segment 'netherite no gold' in path 'textures/item/netherite no gold/netherite_helmet_s.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/netherite no gold/netherite_leggings_n.png: Invalid segment 'netherite no gold' in path 'textures/item/netherite no gold/netherite_leggings_n.png'\n[16:35:52] [Worker-Main-4/ERROR]: Invalid path minecraft:textures/item/netherite no gold/netherite_leggings_s.png: Invalid segment 'netherite no gold' in path 'textures/item/netherite no gold/netherite_leggings_s.png'\n[16:35:53] [Worker-Main-6/WARN]: Missing textures in model minecraft:grass_block#snowy=false:\n    minecraft:textures/atlas/blocks.png:minecraft:block/dirts\n[16:35:55] [Render thread/WARN]: Skipped language file: carpet_trapdoors:lang/zh_cn.json (java.lang.NullPointerException: Cannot invoke \"com.google.gson.JsonObject.entrySet()\" because \"jsonobject\" is null)\n[16:35:56] [Render thread/WARN]: Missing sound for event: minecraft:item.goat_horn.play\n[16:35:56] [Render thread/WARN]: Missing sound for event: minecraft:entity.goat.screaming.horn_break\n[16:35:57] [Render thread/INFO]: OpenAL initialized on device OpenAL Soft on 扬声器 (Realtek(R) Audio)\n[16:35:57] [Render thread/INFO]: Sound engine started\n[16:35:57] [Render thread/INFO]: Created: 2048x1024x4 minecraft:textures/atlas/blocks.png-atlas\n[16:35:57] [Render thread/INFO]: [Shaders] Allocate texture map normal: 2048x1024, mipmaps: 4\n[16:35:57] [Render thread/INFO]: [Shaders] Allocate texture map specular: 2048x1024, mipmaps: 4\n[16:35:57] [Render thread/INFO]: [OptiFine] Animated sprites: 80\n[16:35:58] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/signs.png-atlas\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x256, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x256, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[16:35:58] [Render thread/INFO]: Created: 1024x512x4 minecraft:textures/atlas/banner_patterns.png-atlas\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map normal: 1024x512, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map specular: 1024x512, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[16:35:58] [Render thread/INFO]: Created: 1024x512x4 minecraft:textures/atlas/shield_patterns.png-atlas\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map normal: 1024x512, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map specular: 1024x512, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[16:35:58] [Render thread/INFO]: Created: 256x256x4 minecraft:textures/atlas/chest.png-atlas\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map normal: 256x256, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map specular: 256x256, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [OptiFine] Animated sprites: 1\n[16:35:58] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x256, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x256, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[16:35:58] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x256, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x256, mipmaps: 4\n[16:35:58] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[16:35:58] [Render thread/WARN]: Shader rendertype_entity_translucent_emissive could not find sampler named Sampler2 in the specified shader program.\n[16:35:58] [Render thread/INFO]: Created: 512x256x0 minecraft:textures/atlas/particles.png-atlas\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map normal: 512x256, mipmaps: 0\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map specular: 512x256, mipmaps: 0\n[16:35:58] [Render thread/INFO]: [OptiFine] Animated sprites: 1\n[16:35:58] [Render thread/INFO]: Created: 256x256x0 minecraft:textures/atlas/paintings.png-atlas\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map normal: 256x256, mipmaps: 0\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map specular: 256x256, mipmaps: 0\n[16:35:58] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[16:35:58] [Render thread/INFO]: Created: 256x128x0 minecraft:textures/atlas/mob_effects.png-atlas\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map normal: 256x128, mipmaps: 0\n[16:35:58] [Render thread/INFO]: [Shaders] Allocate texture map specular: 256x128, mipmaps: 0\n[16:35:58] [Render thread/INFO]: [OptiFine] Animated sprites: 0\n[16:35:58] [Render thread/INFO]: [OptiFine] *** Reloading custom textures ***\n[16:35:58] [Render thread/INFO]: [OptiFine] Natural Textures: Parsing default configuration \"optifine/natural.properties\"\n[16:35:58] [Render thread/INFO]: [OptiFine] Natural Textures: Valid only for textures from default resource pack\n[16:35:58] [Render thread/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:58] [Render thread/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:58] [Render thread/INFO]: [OptiFine] CustomColors: Loading optifine/color.properties\n[16:35:58] [Render thread/INFO]: [OptiFine] CustomColors: text.xpbar = a7d431\n[16:35:58] [Render thread/INFO]: [OptiFine] CustomColors: Wolf collar colors: 16\n[16:35:58] [Render thread/INFO]: [OptiFine] CustomColors: Sheep colors: 16\n[16:35:58] [Render thread/INFO]: [OptiFine] CustomColors: Map colors: 1\n[16:35:58] [Render thread/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: chest_boat, name: chest_base\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: chest_boat, name: chest_lid\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: chest_boat, name: chest_knob\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: vex, name: head\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: vex, name: headwear\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: vex, name: body\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: vex, name: left_arm\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: vex, name: right_arm\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: vex, name: left_leg\n[16:35:59] [Render thread/WARN]: [OptiFine] Model renderer not found, model: vex, name: right_leg\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/axolotl.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/axolotl_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/axolotl_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/blaze.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/blaze_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/blaze_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/blaze_layer1.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/blaze_layer2.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/blaze_layer3.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/cave_spider.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_middle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_leg_lfm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_leg_rfm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_leg_lbm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_leg_rbm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_legs_left.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_legs_right.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_legs_left.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_legs_right.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_legs_left.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_legs_right.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_legs_left.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cave_spider_legs_right.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/chicken.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/chicken_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/chicken_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/chicken_right_wing.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/chicken_left_wing.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/chicken_right_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/chicken_left_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/cat.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_tail.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/cow.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cow_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/cod.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cod_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/creeper.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/donkey.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_neck.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_tail.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_baby_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_baby_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_baby_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_baby_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_left_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_right_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_headpiece_neck.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_left_chest.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/donkey_right_chest.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/dolphin.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/dolphin_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/drowned.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/elder_guardian.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/elder_guardian_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/enderman.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/enderman_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/enderman_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/enderman_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/enderman_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/enderman_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/enderman_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/evoker.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/evoker_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/evoker_right_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/evoker_left_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/evoker_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/evoker_arms.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/evoker_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/evoker_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/evoker_right_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/evoker_left_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/fox.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/fox_tail.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/frog.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/frog_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/frog_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/frog_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/frog_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/frog_leg_rb.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/ghast.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ghast_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/giant.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/giant_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/giant_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/giant_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/giant_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/giant_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/giant_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/guardian.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/guardian_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/hoglin.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/hoglin_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/hoglin_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/hoglin_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/hoglin_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/hoglin_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/hoglin_leg_rf.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/horse.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_neck.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_tail.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_baby_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_baby_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_baby_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_baby_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_left_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_right_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_headpiece_neck.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/husk.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/husk_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/husk_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/husk_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/husk_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/husk_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/husk_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/illusioner.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/illusioner_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/illusioner_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/illusioner_arms.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/illusioner_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/illusioner_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/illusioner_right_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/illusioner_left_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/iron_golem.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/iron_golem_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/iron_golem_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/iron_golem_waist.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/iron_golem_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/iron_golem_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/iron_golem_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/iron_golem_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/llama.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_left_chest.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_right_chest.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/magma_cube.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/magma_cube_core.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/magma_cube_layer1.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/magma_cube_layer2.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/magma_cube_layer3.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/magma_cube_layer4.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/magma_cube_layer5.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/magma_cube_layer6.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/magma_cube_layer7.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/magma_cube_layer8.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/mooshroom.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mooshroom_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/mule.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_neck.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_tail.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_baby_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_baby_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_baby_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_baby_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_left_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_right_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_headpiece_neck.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_left_chest.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/mule_right_chest.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/ocelot.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ocelot_tail.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/parrot.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/parrot_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/parrot_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/phantom.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/phantom_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/pig.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/piglin.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/piglin_brute.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_brute_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_brute_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_brute_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_brute_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_brute_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/piglin_brute_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/pillager.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pillager_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pillager_right_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pillager_left_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pillager_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pillager_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pillager_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pillager_right_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pillager_left_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/puffer_fish_big.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pufferfish_large_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/puffer_fish_medium.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pufferfish_medium_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/puffer_fish_small.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pufferfish_small_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/ravager.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_legs.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_legs.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_legs.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/ravager_legs.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/salmon.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/salmon_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/sheep.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/silverfish.jem\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/skeleton.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/skeleton_horse.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_neck.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_tail.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_baby_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_baby_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_baby_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_baby_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_left_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_right_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/skeleton_horse_headpiece_neck.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/slime.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/slime_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/spider.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_middle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_leg_lfm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_leg_rfm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_leg_lbm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_leg_rbm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_legs_left.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_legs_right.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_legs_left.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_legs_right.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_legs_left.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_legs_right.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_legs_left.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/spider_legs_right.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/stray.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/strider.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/strider_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/strider_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/strider_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/tadpole.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/tadpole_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/trader_llama.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_left_chest.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_right_chest.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/tropical_fish_a.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/tropical_fish_a_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/tropical_fish_b.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/tropical_fish_b_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/turtle.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/turtle_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/vex.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vex_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vex_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vex_body2.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vex_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vex_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vex_right_wing.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vex_left_wing.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Model part not found: head, model: net.minecraft.client.model.VexModel@250e9655\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/villager.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_left_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_right_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_arms.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_right_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_left_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/vindicator.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_right_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_left_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_arms.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_right_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/vindicator_left_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/wandering_trader.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wandering_trader_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wandering_trader_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wandering_trader_left_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wandering_trader_right_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wandering_trader_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wandering_trader_arms.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wandering_trader_right_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wandering_trader_left_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/witch.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/witch_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/witch_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/witch_arms.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/witch_right_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/witch_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/witch_hat.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/wither_skeleton.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wither_skeleton_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wither_skeleton_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wither_skeleton_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wither_skeleton_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wither_skeleton_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wither_skeleton_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/wolf.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_tail.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/zoglin.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zoglin_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zoglin_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zoglin_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zoglin_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zoglin_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zoglin_leg_rf.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/zombie.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/zombie_horse.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_neck.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_tail.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_baby_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_baby_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_baby_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_baby_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_left_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_right_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_horse_headpiece_neck.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/zombie_villager.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_villager_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_villager_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_villager_left_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_villager_right_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_villager_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_villager_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_villager_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_villager_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombie_villager_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/zombified_piglin.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombified_piglin_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombified_piglin_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombified_piglin_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombified_piglin_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombified_piglin_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/zombified_piglin_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/cat_collar.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/cat_collar_tail.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/creeper_charge.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/creeper_charge_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/drowned_outer.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_outer_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_outer_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_outer_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_outer_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_outer_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/drowned_outer_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/horse_armor.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_neck.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_tail.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_baby_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_baby_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_baby_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_baby_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_back_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_left_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_baby_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: child_front_right_leg_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_left_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_right_saddle.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/horse_armor_headpiece_neck.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/llama_decor.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_left_chest.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_right_chest.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/pig_saddle.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/pig_saddle_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/sheep_wool.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/sheep_wool_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/slime_outer.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/slime_outer.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/stray_outer.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_outer_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_outer_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_outer_left_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_outer_right_arm.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_outer_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/stray_outer_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/strider_saddle.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/strider_saddle_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/strider_saddle_left_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/strider_saddle_right_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/trader_llama_decor.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_left_chest.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/llama_decor_right_chest.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/tropical_fish_pattern_a.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/tropical_fish_pattern_a_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/tropical_fish_pattern_b.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/tropical_fish_pattern_b_body.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModel: optifine/cem/wolf_collar.jem\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_leg_lb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_leg_rb.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_leg_lf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_leg_rf.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg1_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg2_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg3_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_legs.jpm\n[16:35:59] [Render thread/WARN]: [OptiFine] Duplicate model ID: leg4_part\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/wolf_collar_tail.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomEntityModels: optifine/cem/villager.jem, properties: optifine/cem/villager.properties\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_head.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_left_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_right_brow.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_body.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_arms.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_right_leg.jpm\n[16:35:59] [Render thread/ERROR]: [OptiFine] java.io.FileNotFoundException: minecraft:optifine/cem/villager_left_leg.jpm\n[16:35:59] [Render thread/INFO]: [OptiFine] [Shaders] Parsing block mappings: /shaders/block.properties\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:leaves\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:leaves2\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:double_plant\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:stonebrick\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone_slab}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:monster_egg\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:monster_egg\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:monster_egg\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:monster_egg\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:golden_rail\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:flowing_lava\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:grass_path\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone_slab}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:monster_egg\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:planks\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:wooden_slab\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:wooden_door\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:trapdoor\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:fence\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:fence_gate\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:log\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:planks\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:wooden_slab\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:log\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:planks\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:wooden_slab\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:log\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:planks\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:wooden_slab\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:log\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:planks\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:wooden_slab\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:log2\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:planks\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:wooden_slab\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:log2\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone_slab}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:stone_slab2\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone_slab}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:quartz_ore\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:snow_layer\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:lit_pumpkin\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:nether_brick\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Property not found: variant, block: Block{minecraft:stone_slab}\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:melon_block\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:end_bricks\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:hardened_clay\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:stained_hardened_clay\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:magma\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:concrete\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:concrete_powder\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:waterlily\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:lit_furnace\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:noteblock\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:powered_repeater\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:unpowered_repeater\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:powered_comparator\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:unpowered_comparator\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:carpet\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:wool\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:mob_spawner\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:stained_glass\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:stained_glass_pane\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:slime\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:portal\n[16:35:59] [Render thread/WARN]: [OptiFine] Shaders: Block not found for name: minecraft:flowing_water\n[16:35:59] [Render thread/WARN]: [OptiFine] [Shaders] Invalid block ID mapping: block.60024=\n[16:35:59] [Render thread/INFO]: [OptiFine] [Shaders] Parsing item mappings: /shaders/item.properties\n[16:35:59] [Render thread/INFO]: [OptiFine] [Shaders] Parsing entity mappings: /shaders/entity.properties\n[16:35:59] [Render thread/WARN]: [OptiFine] [Shaders] Invalid entity ID mapping: entity.50012=\n[16:35:59] [Render thread/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:59] [Render thread/WARN]: [OptiFine] Unknown resource pack type: net.minecraftforge.resource.DelegatingPackResources: mod_resources\n[16:35:59] [Render thread/INFO]: [OptiFine] CustomBlockLayers: shaders/block.properties\n[16:35:59] [Render thread/INFO]: [OptiFine] Disable Forge light pipeline\n[16:35:59] [Render thread/INFO]: [OptiFine] Set ForgeConfig.CLIENT.experimentalForgeLightPipelineEnabled=false\n[16:36:01] [Render thread/ERROR]: Reported exception thrown!\nnet.minecraft.ReportedException: Rendering overlay\n\tat net.minecraft.client.renderer.GameRenderer.m_109093_(GameRenderer.java:1342) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?]\n\tat net.minecraft.client.Minecraft.m_91383_(Minecraft.java:1135) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?]\n\tat net.minecraft.client.Minecraft.m_91374_(Minecraft.java:713) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?]\n\tat net.minecraft.client.main.Main.m_239872_(Main.java:212) ~[1.19.3.jar:?]\n\tat net.minecraft.client.main.Main.main(Main.java:51) ~[1.19.3.jar:?]\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:?]\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:?]\n\tat java.lang.reflect.Method.invoke(Unknown Source) ~[?:?]\n\tat net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:27) ~[fmlloader-1.19.3-44.1.23.jar:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:106) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:77) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.8.jar:?]\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?]\nCaused by: java.lang.NoSuchMethodError: 'net.minecraft.network.chat.FormattedText net.minecraft.client.gui.Font.ellipsize(net.minecraft.network.chat.FormattedText, int)'\n\tat net.minecraftforge.client.gui.widget.ExtendedButton.m_6303_(ExtendedButton.java:54) ~[forge-1.19.3-44.1.23-universal.jar%23317!/:?]\n\tat net.minecraft.client.gui.components.AbstractWidget.m_86412_(AbstractWidget.java:74) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?]\n\tat net.minecraftforge.client.gui.LoadingErrorScreen.lambda$render$4(LoadingErrorScreen.java:86) ~[forge-1.19.3-44.1.23-universal.jar%23317!/:?]\n\tat java.util.ArrayList.forEach(Unknown Source) ~[?:?]\n\tat net.minecraftforge.client.gui.LoadingErrorScreen.m_86412_(LoadingErrorScreen.java:86) ~[forge-1.19.3-44.1.23-universal.jar%23317!/:?]\n\tat net.minecraft.client.gui.screens.LoadingOverlay.m_86412_(LoadingOverlay.java:109) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?]\n\tat net.minecraft.client.renderer.GameRenderer.m_109093_(GameRenderer.java:1332) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?]\n\t... 17 more\n[16:36:01] [Render thread/FATAL]: Preparing crash report with UUID 9f5030e7-62cd-418f-843a-bc24ad862ba0\n[16:36:02] [Render thread/FATAL]: Preparing crash report with UUID 8c962cea-bfbb-4d8b-ac3a-025da5aa3a7b\n---- Minecraft Crash Report ----\n// Don't be sad, have a hug! <3\n\nTime: 2023-04-10 16:36:02\nDescription: Rendering overlay\n\njava.lang.NoSuchMethodError: 'net.minecraft.network.chat.FormattedText net.minecraft.client.gui.Font.ellipsize(net.minecraft.network.chat.FormattedText, int)'\n\tat net.minecraftforge.client.gui.widget.ExtendedButton.m_6303_(ExtendedButton.java:54) ~[forge-1.19.3-44.1.23-universal.jar%23317!/:?] {re:classloading}\n\tat net.minecraft.client.gui.components.AbstractWidget.m_86412_(AbstractWidget.java:74) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraftforge.client.gui.LoadingErrorScreen.lambda$render$4(LoadingErrorScreen.java:86) ~[forge-1.19.3-44.1.23-universal.jar%23317!/:?] {re:classloading}\n\tat java.util.ArrayList.forEach(Unknown Source) ~[?:?] {}\n\tat net.minecraftforge.client.gui.LoadingErrorScreen.m_86412_(LoadingErrorScreen.java:86) ~[forge-1.19.3-44.1.23-universal.jar%23317!/:?] {re:classloading}\n\tat net.minecraft.client.gui.screens.LoadingOverlay.m_86412_(LoadingOverlay.java:109) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,xf:OptiFine:default}\n\tat net.minecraft.client.renderer.GameRenderer.m_109093_(GameRenderer.java:1332) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:accesstransformer:B,xf:OptiFine:default}\n\tat net.minecraft.client.Minecraft.m_91383_(Minecraft.java:1135) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.m_91374_(Minecraft.java:713) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.m_239872_(Main.java:212) ~[1.19.3.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.main(Main.java:51) ~[1.19.3.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] {}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:?] {}\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:?] {}\n\tat java.lang.reflect.Method.invoke(Unknown Source) ~[?:?] {}\n\tat net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:27) ~[fmlloader-1.19.3-44.1.23.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:106) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:77) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?] {}\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nThread: Render thread\nStacktrace:\n\tat net.minecraftforge.client.gui.widget.ExtendedButton.m_6303_(ExtendedButton.java:54) ~[forge-1.19.3-44.1.23-universal.jar%23317!/:?] {re:classloading}\n\tat net.minecraft.client.gui.components.AbstractWidget.m_86412_(AbstractWidget.java:74) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraftforge.client.gui.LoadingErrorScreen.lambda$render$4(LoadingErrorScreen.java:86) ~[forge-1.19.3-44.1.23-universal.jar%23317!/:?] {re:classloading}\n\tat java.util.ArrayList.forEach(Unknown Source) ~[?:?] {}\n\tat net.minecraftforge.client.gui.LoadingErrorScreen.m_86412_(LoadingErrorScreen.java:86) ~[forge-1.19.3-44.1.23-universal.jar%23317!/:?] {re:classloading}\n\tat net.minecraft.client.gui.screens.LoadingOverlay.m_86412_(LoadingOverlay.java:109) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,xf:OptiFine:default}\n-- Overlay render details --\nDetails:\n\tOverlay name: net.minecraft.client.gui.screens.LoadingOverlay\nStacktrace:\n\tat net.minecraft.client.renderer.GameRenderer.m_109093_(GameRenderer.java:1332) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:accesstransformer:B,xf:OptiFine:default}\n\tat net.minecraft.client.Minecraft.m_91383_(Minecraft.java:1135) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.m_91374_(Minecraft.java:713) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.m_239872_(Main.java:212) ~[1.19.3.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.main(Main.java:51) ~[1.19.3.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] {}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:?] {}\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:?] {}\n\tat java.lang.reflect.Method.invoke(Unknown Source) ~[?:?] {}\n\tat net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:27) ~[fmlloader-1.19.3-44.1.23.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:106) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:77) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?] {}\n\n\n-- Last reload --\nDetails:\n\tReload number: 1\n\tReload reason: initial\n\tFinished: Yes\n\tPacks: vanilla, file/Minecraft-Mod-Language-Modpack-Converted-12.zip, file/FreshAnimations_v1.8.zip, file/Better-Leaves-7.1-1.19+.zip, file/Resource+pack+for+Better+End+Sky+mod.zip, file/Stay_True_1.19.zip, mod_resources\nStacktrace:\n\tat net.minecraft.client.ResourceLoadStateTracker.m_168562_(ResourceLoadStateTracker.java:49) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading}\n\tat net.minecraft.client.Minecraft.m_91354_(Minecraft.java:2328) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.Minecraft.m_91374_(Minecraft.java:730) ~[client-1.19.3-20221207.122022-srg.jar%23312!/:?] {re:classloading,pl:accesstransformer:B,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.m_239872_(Main.java:212) ~[1.19.3.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat net.minecraft.client.main.Main.main(Main.java:51) ~[1.19.3.jar:?] {re:classloading,pl:runtimedistcleaner:A}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] {}\n\tat jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:?] {}\n\tat jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:?] {}\n\tat java.lang.reflect.Method.invoke(Unknown Source) ~[?:?] {}\n\tat net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:27) ~[fmlloader-1.19.3-44.1.23.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.run(Launcher.java:106) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.Launcher.main(Launcher.java:77) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.8.jar:?] {}\n\tat cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?] {}\n\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.19.3\n\tMinecraft Version ID: 1.19.3\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 17.0.6, BellSoft\n\tJava VM Version: OpenJDK 64-Bit Server VM (mixed mode, sharing), BellSoft\n\tMemory: 1152326624 bytes (1098 MiB) / 1711276032 bytes (1632 MiB) up to 8019509248 bytes (7648 MiB)\n\tCPUs: 8\n\tProcessor Vendor: GenuineIntel\n\tProcessor Name: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz\n\tIdentifier: Intel64 Family 6 Model 140 Stepping 1\n\tMicroarchitecture: Tiger Lake\n\tFrequency (GHz): 2.42\n\tNumber of physical packages: 1\n\tNumber of physical CPUs: 4\n\tNumber of logical CPUs: 8\n\tGraphics card #0 name: Intel(R) Iris(R) Xe Graphics\n\tGraphics card #0 vendor: Intel Corporation (0x8086)\n\tGraphics card #0 VRAM (MB): 1024.00\n\tGraphics card #0 deviceId: 0x9a49\n\tGraphics card #0 versionInfo: DriverVersion=31.0.101.3358\n\tMemory slot #0 capacity (MB): 8192.00\n\tMemory slot #0 clockSpeed (GHz): 3.20\n\tMemory slot #0 type: DDR4\n\tMemory slot #1 capacity (MB): 8192.00\n\tMemory slot #1 clockSpeed (GHz): 3.20\n\tMemory slot #1 type: DDR4\n\tVirtual memory max (MB): 17088.26\n\tVirtual memory used (MB): 8522.84\n\tSwap memory total (MB): 1024.00\n\tSwap memory used (MB): 9.09\n\tJVM Flags: 13 total; -XX:+ExplicitGCInvokesConcurrent -XX:+UseCompressedOops -Xmx7629m -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tLaunched Version: 1.19.3\n\tBackend library: LWJGL version 3.3.1 build 7\n\tBackend API: Intel(R) Iris(R) Xe Graphics GL version 3.2.0 - Build 31.0.101.3358, Intel\n\tWindow size: 1920x1080\n\tGL Caps: Using framebuffer using OpenGL 3.2\n\tGL debug messages: \n\tUsing VBOs: Yes\n\tIs Modded: Definitely; Client brand changed to 'forge'\n\tType: Client (map_client.txt)\n\tGraphics mode: fancy\n\tResource Packs: vanilla, file/Minecraft-Mod-Language-Modpack-Converted-12.zip, file/FreshAnimations_v1.8.zip (incompatible), file/Better-Leaves-7.1-1.19+.zip (incompatible), file/Resource+pack+for+Better+End+Sky+mod.zip (incompatible), file/Stay_True_1.19.zip (incompatible), mod_resources\n\tCurrent Language: 简体中文 (中国大陆)\n\tCPU: 8x 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz\n\tOptiFine Version: OptiFine_1.19.3_HD_U_I2_pre5\n\tOptiFine Build: 20230219-203817\n\tRender Distance Chunks: 7\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: ComplementaryReimagined_r2.0.1.zip\n\tOpenGlVersion: 3.2.0 - Build 31.0.101.3358\n\tOpenGlRenderer: Intel(R) Iris(R) Xe Graphics\n\tOpenGlVendor: Intel\n\tCpuCount: 8\n\tModLauncher: 10.0.8+10.0.8+main.0ef7e830\n\tModLauncher launch target: forgeclient\n\tModLauncher naming: srg\n\tModLauncher services: \n\t\tmixin-0.8.5.jar mixin PLUGINSERVICE \n\t\teventbus-6.0.3.jar eventbus PLUGINSERVICE \n\t\tfmlloader-1.19.3-44.1.23.jar slf4jfixer PLUGINSERVICE \n\t\tfmlloader-1.19.3-44.1.23.jar object_holder_definalize PLUGINSERVICE \n\t\tfmlloader-1.19.3-44.1.23.jar runtime_enum_extender PLUGINSERVICE \n\t\tfmlloader-1.19.3-44.1.23.jar capability_token_subclass PLUGINSERVICE \n\t\taccesstransformers-8.0.4.jar accesstransformer PLUGINSERVICE \n\t\tfmlloader-1.19.3-44.1.23.jar runtimedistcleaner PLUGINSERVICE \n\t\tmodlauncher-10.0.8.jar mixin TRANSFORMATIONSERVICE \n\t\tmodlauncher-10.0.8.jar OptiFine TRANSFORMATIONSERVICE \n\t\tmodlauncher-10.0.8.jar fml TRANSFORMATIONSERVICE \n\t\tmodlauncher-10.0.8.jar I18nUpdateMod TRANSFORMATIONSERVICE \n\tFML Language Providers: \n\t\tminecraft@1.0\n\t\tlowcodefml@null\n\t\tjavafml@null\n\tMod List: \n\t\tclient-1.19.3-20221207.122022-srg.jar             |Minecraft                     |minecraft                     |1.19.3              |DONE      |Manifest: ****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****\n\t\tforge-1.19.3-44.1.23-universal.jar                |Forge                         |forge                         |44.1.23             |DONE      |Manifest: ****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****:****\n\tCrash Report UUID: 8c962cea-bfbb-4d8b-ac3a-025da5aa3a7b\n\tFML: 44.1\n\tForge: net.minecraftforge:44.1.23\n[16:36:02] [Render thread/FATAL]: Preparing crash report with UUID 51a28f7b-3f93-4777-b628-1741b60fb5d9\n#@!@# Game crashed! Crash report saved to: #@!@# E:\\.minecraft\\versions\\1.19.3\\crash-reports\\crash-2023-04-10_16.36.01-client.txt"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/optifine_is_not_compatible_with_forge5.txt",
    "content": "[19:59:16] [main/INFO]: ModLauncher running: args [--username, ljjjjj, --version, 1.20, --gameDir, F:\\鎴戠殑涓栫晫\\.minecraft\\versions\\1.20, --assetsDir, F:\\鎴戠殑涓栫晫\\.minecraft\\assets, --assetIndex, 5, --uuid, dad18d8b1bc53620bb6b9288bee30929, --accessToken, 鉂勨潉鉂勨潉鉂勨潉鉂勨潉, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, msa, --versionType, HMCL **.**.**.**, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker, --launchTarget, forgeclient, --fml.forgeVersion, 46.0.14, --fml.mcVersion, 1.20, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230608.053357]\n[19:59:16] [main/INFO]: ModLauncher 10.0.8+10.0.8+main.0ef7e830 starting: java version 17.0.7 by BellSoft; OS Windows 10 arch amd64 version 10.0\n[19:59:16] [main/INFO]: OptiFineTransformationService.onLoad\n[19:59:16] [main/INFO]: OptiFine ZIP file URL: union:/F:/鎴戠殑涓栫晫/.minecraft/libraries/optifine/OptiFine/1.20_HD_U_I5_pre3/OptiFine-1.20_HD_U_I5_pre3.jar%23106!/\n[19:59:16] [main/INFO]: OptiFine ZIP file: F:\\鎴戠殑涓栫晫\\.minecraft\\libraries\\optifine\\OptiFine\\1.20_HD_U_I5_pre3\\OptiFine-1.20_HD_U_I5_pre3.jar\n[19:59:16] [main/INFO]: Target.PRE_CLASS is available\n[19:59:16] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/F:/鎴戠殑涓栫晫/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%23101!/ Service=ModLauncher Env=CLIENT\n[19:59:16] [main/INFO]: OptiFineTransformationService.initialize\n[19:59:16] [main/WARN]: Configuration file F:\\鎴戠殑涓栫晫\\.minecraft\\versions\\1.20\\config\\fml.toml is not correct. Correcting\n[19:59:16] [main/WARN]: Incorrect key [defaultConfigPath] was corrected from null to defaultconfigs\n[19:59:17] [main/WARN]: Mod file F:\\鎴戠殑涓栫晫\\.minecraft\\libraries\\net\\minecraftforge\\fmlcore\\1.20-46.0.14\\fmlcore-1.20-46.0.14.jar is missing mods.toml file\n[19:59:17] [main/WARN]: Mod file F:\\鎴戠殑涓栫晫\\.minecraft\\libraries\\net\\minecraftforge\\javafmllanguage\\1.20-46.0.14\\javafmllanguage-1.20-46.0.14.jar is missing mods.toml file\n[19:59:17] [main/WARN]: Mod file F:\\鎴戠殑涓栫晫\\.minecraft\\libraries\\net\\minecraftforge\\lowcodelanguage\\1.20-46.0.14\\lowcodelanguage-1.20-46.0.14.jar is missing mods.toml file\n[19:59:17] [main/WARN]: Mod file F:\\鎴戠殑涓栫晫\\.minecraft\\libraries\\net\\minecraftforge\\mclanguage\\1.20-46.0.14\\mclanguage-1.20-46.0.14.jar is missing mods.toml file\n[19:59:17] [main/INFO]: Found mod file fmlcore-1.20-46.0.14.jar of type LIBRARY with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@51a06cbe\n[19:59:17] [main/INFO]: Found mod file javafmllanguage-1.20-46.0.14.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@51a06cbe\n[19:59:17] [main/INFO]: Found mod file lowcodelanguage-1.20-46.0.14.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@51a06cbe\n[19:59:17] [main/INFO]: Found mod file mclanguage-1.20-46.0.14.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@51a06cbe\n[19:59:17] [main/INFO]: Found mod file client-1.20-20230608.053357-srg.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@51a06cbe\n[19:59:17] [main/INFO]: Found mod file forge-1.20-46.0.14-universal.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@51a06cbe\n[19:59:17] [main/INFO]: No dependencies to load found. Skipping!\n[19:59:17] [main/INFO]: OptiFineTransformationService.transformers\n[19:59:17] [main/INFO]: Targets: 410\n[19:59:17] [main/INFO]: additionalClassesLocator: [optifine., net.optifine.]\n[19:59:18] [main/INFO]: Launching target 'forgeclient' with arguments [--version, 1.20, --gameDir, F:\\鎴戠殑涓栫晫\\.minecraft\\versions\\1.20, --assetsDir, F:\\鎴戠殑涓栫晫\\.minecraft\\assets, --uuid, dad18d8b1bc53620bb6b9288bee30929, --username, ljjjjj, --assetIndex, 5, --accessToken, 鉂勨潉鉂勨潉鉂勨潉鉂勨潉, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, msa, --versionType, HMCL **.**.**.**, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker]\n2023-06-16 19:59:18,190 main WARN Error parsing URI F:\\鎴戠殑涓栫晫\\.minecraft\\versions\\1.20\\log4j2.xml\n2023-06-16 19:59:18,229 main WARN Advanced terminal features are not available in this environment\nCompletely ignored arguments: [--tweakClass, optifine.OptiFineTweaker]\n[19:59:23] [Datafixer Bootstrap/INFO] [mojang/DataFixerBuilder]: 188 Datafixer optimizations took 170 milliseconds\nException in thread \"Render thread\" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:32)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:53)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.LaunchServiceHandler.launch(LaunchServiceHandler.java:71)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.run(Launcher.java:106)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.main(Launcher.java:77)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141)\nCaused by: java.lang.reflect.InvocationTargetException\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.base/java.lang.reflect.Method.invoke(Unknown Source)\n\tat MC-BOOTSTRAP/fmlloader@1.20-46.0.14/net.minecraftforge.fml.loading.targets.CommonClientLaunchHandler.lambda$launchService$0(CommonClientLaunchHandler.java:28)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.LaunchServiceHandlerDecorator.launch(LaunchServiceHandlerDecorator.java:30)\n\t... 7 more\nCaused by: java.lang.NoSuchMethodError: 'java.lang.String com.mojang.blaze3d.systems.RenderSystem.getBackendDescription()'\n\tat TRANSFORMER/minecraft@1.20/net.minecraft.client.Minecraft.m_167850_(Minecraft.java:2339)\n\tat TRANSFORMER/minecraft@1.20/net.minecraft.client.Minecraft.m_167872_(Minecraft.java:2332)\n\tat TRANSFORMER/minecraft@1.20/net.minecraft.client.main.Main.main(Main.java:191)\n\t... 13 more"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/optifine_is_not_compatible_with_forge6.txt",
    "content": "WARNING: Unknown module: cpw.mods.bootstraplauncher specified to --add-exports\r\n[authlib-injector] [INFO] Logging file: C:\\Users\\********\\AppData\\Roaming\\.minecraft\\versions\\鎮犵劧浜虹敓1.0\\authlib-injector.log\r\n[authlib-injector] [INFO] Version: 1.2.3\r\n[authlib-injector] [INFO] Authentication server: https://littleskin.cn/api/yggdrasil\r\n[authlib-injector] [INFO] Httpd is running on port 50187\r\n[authlib-injector] [INFO] Transformed [com.mojang.patchy.BlockedServers] with [Constant URL Transformer]\r\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.HttpAuthenticationService] with [ConcatenateURL Workaround]\r\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilEnvironment] with [Constant URL Transformer]\r\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService] with [Texture Whitelist Transformer]\r\n[authlib-injector] [INFO] Transformed [com.mojang.authlib.properties.Property] with [Yggdrasil Public Key Transformer]\r\n[16:59:07] [main/INFO]: ModLauncher running: args [--username, butie, --version, 鎮犵劧浜虹敓1.0, --gameDir, C:\\Users\\********\\AppData\\Roaming\\.minecraft\\versions\\鎮犵劧浜虹敓1.0, --assetsDir, C:\\Users\\********\\AppData\\Roaming\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 35bbf0e6417f47e78f536c90400c0c8b, --accessToken, ????????, --userType, Mojang, --versionType, PCL2, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.39, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550, --tweakClass, optifine.OptiFineForgeTweaker]\r\n[16:59:07] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 17.0.1 by Microsoft\r\n[16:59:07] [main/INFO]: OptiFineTransformationService.onLoad\r\n[16:59:07] [main/INFO]: OptiFine ZIP file: C:\\Users\\********\\AppData\\Roaming\\.minecraft\\libraries\\optifine\\OptiFine\\1.16.5_HD_U_G8\\OptiFine-1.16.5_HD_U_G8.jar\r\n[16:59:07] [main/INFO]: Target.PRE_CLASS is available\r\n[16:59:07] [main/INFO]: Added Lets Encrypt root certificates as additional trust\r\n[16:59:07] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/C:/Users/********/AppData/Roaming/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT\r\n[16:59:07] [main/INFO]: OptiFineTransformationService.initialize\r\n[16:59:09] [main/INFO]: OptiFineTransformationService.transformers\r\n[16:59:09] [main/INFO]: Targets: 311\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: java.lang.reflect.InvocationTargetException\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: \tat oolloo.jlw.Wrapper.main(Wrapper.java:51)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: Caused by: java.lang.ExceptionInInitializerError\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.internal.runtime.Context.compile(Context.java:1509)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.internal.runtime.Context.compileScript(Context.java:1449)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.internal.runtime.Context.compileScript(Context.java:759)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:528)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:517)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:395)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat org.openjdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:146)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat java.scripting/javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:247)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat net.minecraftforge.coremod.CoreMod.initialize(CoreMod.java:40)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat net.minecraftforge.coremod.CoreModEngine.initialize(CoreModEngine.java:75)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat java.base/java.util.ArrayList.forEach(ArrayList.java:1511)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat net.minecraftforge.coremod.CoreModEngine.initializeCoreMods(CoreModEngine.java:69)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat net.minecraftforge.coremod.CoreModProvider.getCoreModTransformers(CoreModProvider.java:17)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat net.minecraftforge.fml.loading.FMLServiceProvider.transformers(FMLServiceProvider.java:142)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat cpw.mods.modlauncher.TransformationServiceDecorator.gatherTransformers(TransformationServiceDecorator.java:74)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat cpw.mods.modlauncher.TransformationServicesHandler.lambda$initialiseServiceTransformers$6(TransformationServicesHandler.java:101)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat java.base/java.util.HashMap$Values.forEach(HashMap.java:1065)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat cpw.mods.modlauncher.TransformationServicesHandler.initialiseServiceTransformers(TransformationServicesHandler.java:101)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:64)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat cpw.mods.modlauncher.Launcher.run(Launcher.java:76)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)\r\n[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: \t... 6 more\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: Caused by: java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \tat org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.lambda$getDefineAnonymousClass$0(Context.java:335)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \tat java.base/java.security.AccessController.doPrivileged(AccessController.java:318)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \tat org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.getDefineAnonymousClass(Context.java:327)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \tat org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.<clinit>(Context.java:317)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: \t... 27 more\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: Caused by: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \tat java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:976)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \tat java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1117)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \tat java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:3649)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \tat java.base/java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:2680)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \tat org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.lambda$getDefineAnonymousClass$0(Context.java:329)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: \t... 30 more\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: Caused by: java.lang.NoSuchMethodError: 'java.lang.Class sun.misc.Unsafe.defineAnonymousClass(java.lang.Class, byte[], java.lang.Object[])'\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: \tat java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: \tat java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1085)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: \tat java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1114)\r\n[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: \t... 33 more\r\nException in thread \"main\""
  },
  {
    "path": "HMCLCore/src/test/resources/logs/optifine_repeat_installation.txt",
    "content": "Command: \"C:\\\\Program Files\\\\Common Files\\\\Oracle\\\\Java\\\\javapath_target_946365031\\\\java.exe\" -Dfile.encoding=GB18030 -Dminecraft.client.jar=.minecraft\\versions\\1.19.2\\1.19.2.jar -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -Xmn128m -Xmx1048m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true -Djava.rmi.server.useCodebaseOnly=true -Dcom.sun.jndi.rmi.object.trustURLCodebase=false -Dcom.sun.jndi.cosnaming.object.trustURLCodebase=false -Dlog4j2.formatMsgNoLookups=true -Dlog4j.configurationFile=C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\versions\\1.19.2\\log4j2.xml -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Djava.library.path=C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\versions\\1.19.2\\natives-windows-x86_64 -Dminecraft.launcher.brand=HMCL -Dminecraft.launcher.version=3.5.3 -cp C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\cpw\\mods\\securejarhandler\\2.1.4\\securejarhandler-2.1.4.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\ow2\\asm\\asm\\9.3\\asm-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\ow2\\asm\\asm-commons\\9.3\\asm-commons-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\ow2\\asm\\asm-tree\\9.3\\asm-tree-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\ow2\\asm\\asm-util\\9.3\\asm-util-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\ow2\\asm\\asm-analysis\\9.3\\asm-analysis-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecraftforge\\accesstransformers\\8.0.4\\accesstransformers-8.0.4.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\antlr\\antlr4-runtime\\4.9.1\\antlr4-runtime-4.9.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecraftforge\\eventbus\\6.0.3\\eventbus-6.0.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecraftforge\\forgespi\\6.0.0\\forgespi-6.0.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecraftforge\\coremods\\5.0.1\\coremods-5.0.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\cpw\\mods\\modlauncher\\10.0.8\\modlauncher-10.0.8.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecraftforge\\unsafe\\0.2.0\\unsafe-0.2.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\electronwill\\night-config\\core\\3.6.4\\core-3.6.4.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\electronwill\\night-config\\toml\\3.6.4\\toml-3.6.4.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\apache\\maven\\maven-artifact\\3.8.5\\maven-artifact-3.8.5.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\jodah\\typetools\\0.8.3\\typetools-0.8.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecrell\\terminalconsoleappender\\1.2.0\\terminalconsoleappender-1.2.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\jline\\jline-reader\\3.12.1\\jline-reader-3.12.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\jline\\jline-terminal\\3.12.1\\jline-terminal-3.12.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\spongepowered\\mixin\\0.8.5\\mixin-0.8.5.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\openjdk\\nashorn\\nashorn-core\\15.3\\nashorn-core-15.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecraftforge\\JarJarSelector\\0.3.16\\JarJarSelector-0.3.16.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecraftforge\\JarJarMetadata\\0.3.16\\JarJarMetadata-0.3.16.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\cpw\\mods\\bootstraplauncher\\1.1.2\\bootstraplauncher-1.1.2.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecraftforge\\JarJarFileSystems\\0.3.16\\JarJarFileSystems-0.3.16.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\minecraftforge\\fmlloader\\1.19.2-43.1.65\\fmlloader-1.19.2-43.1.65.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\optifine\\OptiFine\\1.19.2_HD_U_H9\\OptiFine-1.19.2_HD_U_H9.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\optifine\\launchwrapper-of\\2.3\\launchwrapper-of-2.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\mojang\\logging\\1.0.0\\logging-1.0.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\mojang\\blocklist\\1.0.10\\blocklist-1.0.10.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\mojang\\patchy\\2.2.10\\patchy-2.2.10.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\github\\oshi\\oshi-core\\5.8.5\\oshi-core-5.8.5.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\java\\dev\\jna\\jna\\5.10.0\\jna-5.10.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\java\\dev\\jna\\jna-platform\\5.10.0\\jna-platform-5.10.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\slf4j\\slf4j-api\\1.8.0-beta4\\slf4j-api-1.8.0-beta4.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\apache\\logging\\log4j\\log4j-slf4j18-impl\\2.17.0\\log4j-slf4j18-impl-2.17.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\ibm\\icu\\icu4j\\70.1\\icu4j-70.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\mojang\\javabridge\\1.2.24\\javabridge-1.2.24.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\net\\sf\\jopt-simple\\jopt-simple\\5.0.4\\jopt-simple-5.0.4.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\io\\netty\\netty-common\\4.1.77.Final\\netty-common-4.1.77.Final.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\io\\netty\\netty-buffer\\4.1.77.Final\\netty-buffer-4.1.77.Final.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\io\\netty\\netty-codec\\4.1.77.Final\\netty-codec-4.1.77.Final.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\io\\netty\\netty-handler\\4.1.77.Final\\netty-handler-4.1.77.Final.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\io\\netty\\netty-resolver\\4.1.77.Final\\netty-resolver-4.1.77.Final.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\io\\netty\\netty-transport\\4.1.77.Final\\netty-transport-4.1.77.Final.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\io\\netty\\netty-transport-native-unix-common\\4.1.77.Final\\netty-transport-native-unix-common-4.1.77.Final.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\io\\netty\\netty-transport-classes-epoll\\4.1.77.Final\\netty-transport-classes-epoll-4.1.77.Final.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\google\\guava\\failureaccess\\1.0.1\\failureaccess-1.0.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\google\\guava\\guava\\31.0.1-jre\\guava-31.0.1-jre.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\apache\\commons\\commons-lang3\\3.12.0\\commons-lang3-3.12.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\commons-io\\commons-io\\2.11.0\\commons-io-2.11.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\commons-codec\\commons-codec\\1.15\\commons-codec-1.15.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\mojang\\brigadier\\1.0.18\\brigadier-1.0.18.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\mojang\\datafixerupper\\5.0.28\\datafixerupper-5.0.28.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\google\\code\\gson\\gson\\2.8.9\\gson-2.8.9.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\mojang\\authlib\\3.11.49\\authlib-3.11.49.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\apache\\commons\\commons-compress\\1.21\\commons-compress-1.21.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\apache\\httpcomponents\\httpclient\\4.5.13\\httpclient-4.5.13.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\commons-logging\\commons-logging\\1.2\\commons-logging-1.2.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\apache\\httpcomponents\\httpcore\\4.4.14\\httpcore-4.4.14.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\it\\unimi\\dsi\\fastutil\\8.5.6\\fastutil-8.5.6.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\apache\\logging\\log4j\\log4j-api\\2.17.0\\log4j-api-2.17.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\apache\\logging\\log4j\\log4j-core\\2.17.0\\log4j-core-2.17.0.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl\\3.3.1\\lwjgl-3.3.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl\\3.3.1\\lwjgl-3.3.1-natives-windows.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl\\3.3.1\\lwjgl-3.3.1-natives-windows-x86.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-jemalloc\\3.3.1\\lwjgl-jemalloc-3.3.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-jemalloc\\3.3.1\\lwjgl-jemalloc-3.3.1-natives-windows.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-jemalloc\\3.3.1\\lwjgl-jemalloc-3.3.1-natives-windows-x86.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-openal\\3.3.1\\lwjgl-openal-3.3.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-openal\\3.3.1\\lwjgl-openal-3.3.1-natives-windows.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-openal\\3.3.1\\lwjgl-openal-3.3.1-natives-windows-x86.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-opengl\\3.3.1\\lwjgl-opengl-3.3.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-opengl\\3.3.1\\lwjgl-opengl-3.3.1-natives-windows.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-opengl\\3.3.1\\lwjgl-opengl-3.3.1-natives-windows-x86.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-glfw\\3.3.1\\lwjgl-glfw-3.3.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-glfw\\3.3.1\\lwjgl-glfw-3.3.1-natives-windows.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-glfw\\3.3.1\\lwjgl-glfw-3.3.1-natives-windows-x86.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-stb\\3.3.1\\lwjgl-stb-3.3.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-stb\\3.3.1\\lwjgl-stb-3.3.1-natives-windows.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-stb\\3.3.1\\lwjgl-stb-3.3.1-natives-windows-x86.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-tinyfd\\3.3.1\\lwjgl-tinyfd-3.3.1.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-tinyfd\\3.3.1\\lwjgl-tinyfd-3.3.1-natives-windows.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\org\\lwjgl\\lwjgl-tinyfd\\3.3.1\\lwjgl-tinyfd-3.3.1-natives-windows-x86.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\mojang\\text2speech\\1.13.9\\text2speech-1.13.9.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries\\com\\mojang\\text2speech\\1.13.9\\text2speech-1.13.9-natives-windows.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\versions\\1.19.2\\1.19.2.jar -Djava.net.preferIPv6Addresses=system -DignoreList=bootstraplauncher,securejarhandler,asm-commons,asm-util,asm-analysis,asm-tree,asm,JarJarFileSystems,client-extra,fmlcore,javafmllanguage,lowcodelanguage,mclanguage,forge-,1.19.2.jar,1.19.2.jar -DmergeModules=jna-5.10.0.jar,jna-platform-5.10.0.jar -DlibraryDirectory=C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries -p C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries/cpw/mods/securejarhandler/2.1.4/securejarhandler-2.1.4.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries/org/ow2/asm/asm-commons/9.3/asm-commons-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries/org/ow2/asm/asm-util/9.3/asm-util-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries/org/ow2/asm/asm-analysis/9.3/asm-analysis-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries/org/ow2/asm/asm-tree/9.3/asm-tree-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries/org/ow2/asm/asm/9.3/asm-9.3.jar;C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\libraries/net/minecraftforge/JarJarFileSystems/0.3.16/JarJarFileSystems-0.3.16.jar --add-modules ALL-MODULE-PATH --add-opens java.base/java.util.jar=cpw.mods.securejarhandler --add-opens java.base/java.lang.invoke=cpw.mods.securejarhandler --add-exports java.base/sun.security.util=cpw.mods.securejarhandler --add-exports jdk.naming.dns/com.sun.jndi.dns=java.naming cpw.mods.bootstraplauncher.BootstrapLauncher --username 123 --version 1.19.2 --gameDir C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft --assetsDir C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\assets --assetIndex 1.19 --uuid 75f41576c9ae3be19e5af6091a7fa867 --accessToken e110cf1e70784e079e82b02e90e62407 --clientId ${clientid} --xuid ${auth_xuid} --userType mojang --versionType \"HMCL 3.5.3\" --width 854 --height 480 --tweakClass optifine.OptiFineTweaker --launchTarget forgeclient --fml.forgeVersion 43.1.65 --fml.mcVersion 1.19.2 --fml.forgeGroup net.minecraftforge --fml.mcpVersion 20220805.130853\n[14:24:33] [main/INFO]: ModLauncher running: args [--username, 123, --version, 1.19.2, --gameDir, C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft, --assetsDir, C:\\Users\\lenovo\\Desktop\\HMCL-3.5.3.exe\\.minecraft\\assets, --assetIndex, 1.19, --uuid, 75f41576c9ae3be19e5af6091a7fa867, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, mojang, --versionType, HMCL 3.5.3, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker, --launchTarget, forgeclient, --fml.forgeVersion, 43.1.65, --fml.mcVersion, 1.19.2, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20220805.130853]\n[14:24:33] [main/INFO]: ModLauncher 10.0.8+10.0.8+main.0ef7e830 starting: java version 17.0.3.1 by Oracle Corporation; OS Windows 11 arch amd64 version 10.0\nException in thread \"main\" java.lang.module.ResolutionException: Module optifine reads another module named optifine\n\tat java.base/java.lang.module.Resolver.resolveFail(Resolver.java:901)\n\tat java.base/java.lang.module.Resolver.checkExportSuppliers(Resolver.java:719)\n\tat java.base/java.lang.module.Resolver.finish(Resolver.java:380)\n\tat java.base/java.lang.module.Configuration.<init>(Configuration.java:140)\n\tat java.base/java.lang.module.Configuration.resolveAndBind(Configuration.java:494)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.ModuleLayerHandler.buildLayer(ModuleLayerHandler.java:75)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.ModuleLayerHandler.buildLayer(ModuleLayerHandler.java:87)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(TransformationServicesHandler.java:130)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.run(Launcher.java:86)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.Launcher.main(Launcher.java:77)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26)\n\tat MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.8/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23)\n\tat cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141)\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/out_of_memory.txt",
    "content": "#\n# There is insufficient memory for the Java Runtime Environment to continue.\n# Native memory allocation (mmap) failed to map 671088640 bytes for Failed to commit pages from 262144 of length 163840\n# Possible reasons:\n#   The system is out of physical RAM or swap space\n#   In 32 bit mode, the process size limit was hit\n# Possible solutions:\n#   Reduce memory load on the system\n#   Increase physical memory or swap space\n#   Check if swap backing store is full\n#   Use 64 bit Java on a 64 bit OS\n#   Decrease Java heap size (-Xmx/-Xms)\n#   Decrease number of Java threads\n#   Decrease Java thread stack sizes (-Xss)\n#   Set larger code cache with -XX:ReservedCodeCacheSize=\n# This output file may be truncated or incomplete.\n#\n#  Out of Memory Error (os_windows.cpp:3324), pid=13468, tid=7348\n#\n# JRE version: Java(TM) SE Runtime Environment (8.0_51-b16) (build 1.8.0_51-b16)\n# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.51-b03 mixed mode windows-amd64 compressed oops)\n# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows\n#\n\n---------------  T H R E A D  ---------------\n\nCurrent thread (0x0000000021553000):  VMThread [stack: 0x0000000021640000,0x0000000021740000] [id=7348]\n\nStack: [0x0000000021640000,0x0000000021740000]\nNative frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)\nV  [jvm.dll+0x32b4ca]\nV  [jvm.dll+0x2797e3]\nV  [jvm.dll+0x27a479]\nV  [jvm.dll+0x271765]\nV  [jvm.dll+0x296868]\nV  [jvm.dll+0x21acb5]\nV  [jvm.dll+0x3d73ff]\nV  [jvm.dll+0x3d83e8]\nV  [jvm.dll+0x3e0639]\nV  [jvm.dll+0x3e0ff6]\nV  [jvm.dll+0x3e1210]\nV  [jvm.dll+0x3c133a]\nV  [jvm.dll+0x3cdebc]\nV  [jvm.dll+0x3e66df]\nV  [jvm.dll+0x247e47]\nV  [jvm.dll+0x2470e6]\nV  [jvm.dll+0x247581]\nV  [jvm.dll+0x24779e]\nV  [jvm.dll+0x29846a]\nC  [msvcr100.dll+0x21d9f]\nC  [msvcr100.dll+0x21e3b]\nC  [KERNEL32.DLL+0x137e4]\nC  [ntdll.dll+0x6cb81]\n\nVM_Operation (0x000000002796bca0): G1IncCollectionPause, mode: safepoint, requested by thread 0x0000000026b85800\n\n\n---------------  P R O C E S S  ---------------\n\nJava Threads: ( => current thread )\n  0x00000000230b9000 JavaThread \"Timer hack thread\" daemon [_thread_blocked, id=12884, stack(0x0000000028fc0000,0x00000000290c0000)]\n  0x0000000026b86000 JavaThread \"Server-Worker-3\" daemon [_thread_blocked, id=14256, stack(0x0000000027970000,0x0000000027a70000)]\n  0x0000000026b85800 JavaThread \"Server-Worker-2\" daemon [_thread_blocked, id=7408, stack(0x0000000027870000,0x0000000027970000)]\n  0x0000000026b56800 JavaThread \"Server-Worker-1\" daemon [_thread_blocked, id=16332, stack(0x0000000025c70000,0x0000000025d70000)]\n  0x00000000237ac000 JavaThread \"Snooper Timer\" daemon [_thread_blocked, id=15156, stack(0x0000000024ba0000,0x0000000024ca0000)]\n  0x0000000022048800 JavaThread \"Service Thread\" daemon [_thread_blocked, id=1516, stack(0x0000000022440000,0x0000000022540000)]\n  0x000000002157d800 JavaThread \"C1 CompilerThread2\" daemon [_thread_blocked, id=10328, stack(0x0000000021f40000,0x0000000022040000)]\n  0x000000002157d000 JavaThread \"C2 CompilerThread1\" daemon [_thread_blocked, id=8184, stack(0x0000000021e40000,0x0000000021f40000)]\n  0x0000000021577000 JavaThread \"C2 CompilerThread0\" daemon [_thread_in_native, id=2540, stack(0x0000000021d40000,0x0000000021e40000)]\n  0x00000000215ca000 JavaThread \"Attach Listener\" daemon [_thread_blocked, id=13068, stack(0x0000000021c40000,0x0000000021d40000)]\n  0x00000000215c7000 JavaThread \"Signal Dispatcher\" daemon [_thread_blocked, id=8044, stack(0x0000000021b40000,0x0000000021c40000)]\n  0x00000000215c5800 JavaThread \"Surrogate Locker Thread (Concurrent GC)\" daemon [_thread_blocked, id=11912, stack(0x0000000021a40000,0x0000000021b40000)]\n  0x000000002155f000 JavaThread \"Finalizer\" daemon [_thread_blocked, id=14992, stack(0x0000000021840000,0x0000000021940000)]\n  0x0000000021558000 JavaThread \"Reference Handler\" daemon [_thread_blocked, id=8088, stack(0x0000000021740000,0x0000000021840000)]\n  0x0000000004692800 JavaThread \"Client thread\" [_thread_blocked, id=11812, stack(0x00000000044a0000,0x00000000045a0000)]\n\nOther Threads:\n=>0x0000000021553000 VMThread [stack: 0x0000000021640000,0x0000000021740000] [id=7348]\n  0x0000000022052800 WatcherThread [stack: 0x0000000022540000,0x0000000022640000] [id=16572]\n\nVM state:at safepoint (normal execution)\n\nVM Mutex/Monitor currently owned by a thread:  ([mutex/lock_event])\n[0x0000000004691cd0] Threads_lock - owner thread: 0x0000000021553000\n[0x00000000046903d0] Heap_lock - owner thread: 0x0000000026b85800\n\nHeap:\n garbage-first heap   total 1703936K, used 173494K [0x00000006c0000000, 0x00000006c20001a0, 0x00000007c0000000)\n  region size 32768K, 1 young (32768K), 1 survivors (32768K)\n Metaspace       used 35418K, capacity 37971K, committed 38272K, reserved 1081344K\n  class space    used 5189K, capacity 5955K, committed 6016K, reserved 1048576K\n\nHeap Regions: (Y=young(eden), SU=young(survivor), HS=humongous(starts), HC=humongous(continues), CS=collection set, F=free, TS=gc time stamp, PTAMS=previous top-at-mark-start, NTAMS=next top-at-mark-start)\nAC   0  O    TS     0 PTAMS 0x00000006c2000000 NTAMS 0x00000006c2000000 space 32768K, 100% used [0x00000006c0000000, 0x00000006c2000000)\nAC   0  O    TS     9 PTAMS 0x00000006c2000000 NTAMS 0x00000006c2000000 space 32768K,  29% used [0x00000006c2000000, 0x00000006c4000000)\nAC   0  O    TS     5 PTAMS 0x00000006c4000000 NTAMS 0x00000006c4000000 space 32768K, 100% used [0x00000006c4000000, 0x00000006c6000000)\nAC   0  F    TS     5 PTAMS 0x00000006c6000000 NTAMS 0x00000006c6000000 space 32768K,   0% used [0x00000006c6000000, 0x00000006c8000000)\nAC   0  F    TS     0 PTAMS 0x00000006c8000000 NTAMS 0x00000006c8000000 space 32768K,   0% used [0x00000006c8000000, 0x00000006ca000000)\nAC   0  F    TS     0 PTAMS 0x00000006ca000000 NTAMS 0x00000006ca000000 space 32768K,   0% used [0x00000006ca000000, 0x00000006cc000000)\nAC   0  O    TS     5 PTAMS 0x00000006cc260c00 NTAMS 0x00000006cc260c00 space 32768K, 100% used [0x00000006cc000000, 0x00000006ce000000)\nAC   0  O    TS     9 PTAMS 0x00000006ce000000 NTAMS 0x00000006ce000000 space 32768K, 100% used [0x00000006ce000000, 0x00000006d0000000)\nAC   0  F    TS     5 PTAMS 0x00000006d0000000 NTAMS 0x00000006d0000000 space 32768K,   0% used [0x00000006d0000000, 0x00000006d2000000)\nAC   0  F    TS     5 PTAMS 0x00000006d2000000 NTAMS 0x00000006d2000000 space 32768K,   0% used [0x00000006d2000000, 0x00000006d4000000)\nAC   0  F    TS     5 PTAMS 0x00000006d4000000 NTAMS 0x00000006d4000000 space 32768K,   0% used [0x00000006d4000000, 0x00000006d6000000)\nAC   0  F    TS     5 PTAMS 0x00000006d6000000 NTAMS 0x00000006d6000000 space 32768K,   0% used [0x00000006d6000000, 0x00000006d8000000)\nAC   0  F    TS     5 PTAMS 0x00000006d8000000 NTAMS 0x00000006d8000000 space 32768K,   0% used [0x00000006d8000000, 0x00000006da000000)\nAC   0  F    TS     7 PTAMS 0x00000006da000000 NTAMS 0x00000006da000000 space 32768K,   0% used [0x00000006da000000, 0x00000006dc000000)\nAC   0  F    TS     5 PTAMS 0x00000006dc000000 NTAMS 0x00000006dc000000 space 32768K,   0% used [0x00000006dc000000, 0x00000006de000000)\nAC   0  F    TS     5 PTAMS 0x00000006de000000 NTAMS 0x00000006de000000 space 32768K,   0% used [0x00000006de000000, 0x00000006e0000000)\nAC   0  F    TS     7 PTAMS 0x00000006e0000000 NTAMS 0x00000006e0000000 space 32768K,   0% used [0x00000006e0000000, 0x00000006e2000000)\nAC   0  F    TS     7 PTAMS 0x00000006e2000000 NTAMS 0x00000006e2000000 space 32768K,   0% used [0x00000006e2000000, 0x00000006e4000000)\nAC   0  F    TS     7 PTAMS 0x00000006e4000000 NTAMS 0x00000006e4000000 space 32768K,   0% used [0x00000006e4000000, 0x00000006e6000000)\nAC   0  F    TS     7 PTAMS 0x00000006e6000000 NTAMS 0x00000006e6000000 space 32768K,   0% used [0x00000006e6000000, 0x00000006e8000000)\nAC   0  F    TS     7 PTAMS 0x00000006e8000000 NTAMS 0x00000006e8000000 space 32768K,   0% used [0x00000006e8000000, 0x00000006ea000000)\nAC   0  F    TS     7 PTAMS 0x00000006ea000000 NTAMS 0x00000006ea000000 space 32768K,   0% used [0x00000006ea000000, 0x00000006ec000000)\nAC   0  F    TS     7 PTAMS 0x00000006ec000000 NTAMS 0x00000006ec000000 space 32768K,   0% used [0x00000006ec000000, 0x00000006ee000000)\nAC   0  F    TS     7 PTAMS 0x00000006ee000000 NTAMS 0x00000006ee000000 space 32768K,   0% used [0x00000006ee000000, 0x00000006f0000000)\nAC   0  F    TS     7 PTAMS 0x00000006f0000000 NTAMS 0x00000006f0000000 space 32768K,   0% used [0x00000006f0000000, 0x00000006f2000000)\nAC   0  F    TS     7 PTAMS 0x00000006f2000000 NTAMS 0x00000006f2000000 space 32768K,   0% used [0x00000006f2000000, 0x00000006f4000000)\nAC   0  S CS TS     9 PTAMS 0x00000006f4000000 NTAMS 0x00000006f4000000 space 32768K, 100% used [0x00000006f4000000, 0x00000006f6000000)\nAC   0  F    TS     7 PTAMS 0x00000006f6000000 NTAMS 0x00000006f6000000 space 32768K,   0% used [0x00000006f6000000, 0x00000006f8000000)\nAC   0  F    TS     7 PTAMS 0x00000006f8000000 NTAMS 0x00000006f8000000 space 32768K,   0% used [0x00000006f8000000, 0x00000006fa000000)\nAC   0  F    TS     7 PTAMS 0x00000006fa000000 NTAMS 0x00000006fa000000 space 32768K,   0% used [0x00000006fa000000, 0x00000006fc000000)\nAC   0  F    TS     7 PTAMS 0x00000006fc000000 NTAMS 0x00000006fc000000 space 32768K,   0% used [0x00000006fc000000, 0x00000006fe000000)\nAC   0  F    TS     7 PTAMS 0x00000006fe000000 NTAMS 0x00000006fe000000 space 32768K,   0% used [0x00000006fe000000, 0x0000000700000000)\n\nCard table byte_map: [0x0000000015090000,0x0000000015890000] byte_map_base: 0x0000000011a90000\n\nMarking Bits (Prev, Next): (CMBitMap*) 0x00000000046ddc48, (CMBitMap*) 0x00000000046ddca0\n Prev Bits: [0x0000000016090000, 0x000000001a090000)\n Next Bits: [0x000000001a090000, 0x000000001e090000)\n\nPolling page: 0x0000000002be0000\n\nCodeCache: size=245760Kb used=10751Kb max_used=10763Kb free=235008Kb\n bounds [0x0000000004790000, 0x0000000005220000, 0x0000000013790000]\n total_blobs=4157 nmethods=3556 adapters=513\n compilation: enabled\n\nCompilation events (10 events):\nEvent: 14.605 Thread 0x000000002157d800 4277       3       java.lang.invoke.LambdaForm$Name::<init> (194 bytes)\nEvent: 14.606 Thread 0x000000002157d800 nmethod 4277 0x0000000005214b90 code [0x0000000005214d80, 0x0000000005215678]\nEvent: 14.606 Thread 0x000000002157d800 4279       3       java.lang.invoke.MethodHandle::intrinsicName (4 bytes)\nEvent: 14.606 Thread 0x000000002157d800 nmethod 4279 0x000000000520c290 code [0x000000000520c3e0, 0x000000000520c530]\nEvent: 14.606 Thread 0x000000002157d800 4280       3       java.lang.invoke.LambdaForm$Name::useCount (42 bytes)\nEvent: 14.607 Thread 0x000000002157d800 nmethod 4280 0x0000000005214590 code [0x0000000005214700, 0x0000000005214a10]\nEvent: 14.611 Thread 0x000000002157d000 nmethod 4250 0x000000000521a410 code [0x000000000521a560, 0x000000000521a958]\nEvent: 14.611 Thread 0x000000002157d000 4254       4       java.lang.String::concat (47 bytes)\nEvent: 14.618 Thread 0x000000002157d000 nmethod 4254 0x0000000005218710 code [0x0000000005218860, 0x0000000005218c78]\nEvent: 14.618 Thread 0x000000002157d000 4282       4       com.mojang.datafixers.functions.PointFreeRule$CataFuseDifferent::doRewrite (452 bytes)\n\nGC Heap History (10 events):\nEvent: 12.767 GC heap after\nHeap after GC invocations=15 (full 0):\n garbage-first heap   total 229376K, used 119797K [0x00000006c0000000, 0x00000006c2000038, 0x00000007c0000000)\n  region size 32768K, 1 young (32768K), 1 survivors (32768K)\n Metaspace       used 30663K, capacity 32735K, committed 33024K, reserved 1077248K\n  class space    used 4322K, capacity 4795K, committed 4864K, reserved 1048576K\n}\nEvent: 13.064 GC heap before\n{Heap before GC invocations=16 (full 0):\n garbage-first heap   total 229376K, used 152565K [0x00000006c0000000, 0x00000006c2000038, 0x00000007c0000000)\n  region size 32768K, 2 young (65536K), 1 survivors (32768K)\n Metaspace       used 31746K, capacity 33873K, committed 34048K, reserved 1079296K\n  class space    used 4561K, capacity 5072K, committed 5120K, reserved 1048576K\nEvent: 13.117 GC heap after\nHeap after GC invocations=17 (full 0):\n garbage-first heap   total 229376K, used 130796K [0x00000006c0000000, 0x00000006c2000038, 0x00000007c0000000)\n  region size 32768K, 1 young (32768K), 1 survivors (32768K)\n Metaspace       used 31746K, capacity 33873K, committed 34048K, reserved 1079296K\n  class space    used 4561K, capacity 5072K, committed 5120K, reserved 1048576K\n}\nEvent: 13.310 GC heap before\n{Heap before GC invocations=17 (full 0):\n garbage-first heap   total 229376K, used 163564K [0x00000006c0000000, 0x00000006c2000038, 0x00000007c0000000)\n  region size 32768K, 2 young (65536K), 1 survivors (32768K)\n Metaspace       used 32375K, capacity 34565K, committed 34688K, reserved 1079296K\n  class space    used 4655K, capacity 5202K, committed 5248K, reserved 1048576K\nEvent: 13.410 GC heap after\nHeap after GC invocations=18 (full 0):\n garbage-first heap   total 229376K, used 123012K [0x00000006c0000000, 0x00000006c2000038, 0x00000007c0000000)\n  region size 32768K, 1 young (32768K), 1 survivors (32768K)\n Metaspace       used 32375K, capacity 34565K, committed 34688K, reserved 1079296K\n  class space    used 4655K, capacity 5202K, committed 5248K, reserved 1048576K\n}\nEvent: 13.564 GC heap before\n{Heap before GC invocations=18 (full 0):\n garbage-first heap   total 229376K, used 155780K [0x00000006c0000000, 0x00000006c2000038, 0x00000007c0000000)\n  region size 32768K, 2 young (65536K), 1 survivors (32768K)\n Metaspace       used 32647K, capacity 34845K, committed 34944K, reserved 1079296K\n  class space    used 4698K, capacity 5246K, committed 5248K, reserved 1048576K\nEvent: 13.647 GC heap after\nHeap after GC invocations=19 (full 0):\n garbage-first heap   total 524288K, used 135474K [0x00000006c0000000, 0x00000006c2000080, 0x00000007c0000000)\n  region size 32768K, 1 young (32768K), 1 survivors (32768K)\n Metaspace       used 32647K, capacity 34845K, committed 34944K, reserved 1079296K\n  class space    used 4698K, capacity 5246K, committed 5248K, reserved 1048576K\n}\nEvent: 13.881 GC heap before\n{Heap before GC invocations=19 (full 0):\n garbage-first heap   total 524288K, used 201010K [0x00000006c0000000, 0x00000006c2000080, 0x00000007c0000000)\n  region size 32768K, 3 young (98304K), 1 survivors (32768K)\n Metaspace       used 33328K, capacity 35607K, committed 35968K, reserved 1079296K\n  class space    used 4816K, capacity 5399K, committed 5504K, reserved 1048576K\nEvent: 13.950 GC heap after\nHeap after GC invocations=20 (full 0):\n garbage-first heap   total 1048576K, used 146038K [0x00000006c0000000, 0x00000006c2000100, 0x00000007c0000000)\n  region size 32768K, 1 young (32768K), 1 survivors (32768K)\n Metaspace       used 33328K, capacity 35607K, committed 35968K, reserved 1079296K\n  class space    used 4816K, capacity 5399K, committed 5504K, reserved 1048576K\n}\nEvent: 14.631 GC heap before\n{Heap before GC invocations=20 (full 0):\n garbage-first heap   total 1048576K, used 309878K [0x00000006c0000000, 0x00000006c2000100, 0x00000007c0000000)\n  region size 32768K, 6 young (196608K), 1 survivors (32768K)\n Metaspace       used 35418K, capacity 37971K, committed 38272K, reserved 1081344K\n  class space    used 5189K, capacity 5955K, committed 6016K, reserved 1048576K\n\nDeoptimization events (10 events):\nEvent: 14.341 Thread 0x0000000026b56800 Uncommon trap: reason=unstable_if action=reinterpret pc=0x000000000511d558 method=com.mojang.datafixers.types.templates.Product.equals(Ljava/lang/Object;)Z @ 46\nEvent: 14.341 Thread 0x0000000026b56800 Uncommon trap: reason=unstable_if action=reinterpret pc=0x000000000511d558 method=com.mojang.datafixers.types.templates.Product.equals(Ljava/lang/Object;)Z @ 46\nEvent: 14.375 Thread 0x0000000026b86000 Uncommon trap: reason=unstable_if action=reinterpret pc=0x000000000510599c method=com.mojang.datafixers.functions.PointFreeRule$CataFuseSame.doRewrite(Lcom/mojang/datafixers/types/Type;Lcom/mojang/datafixers/types/Type;Lcom/mojang/datafixers/functions/Poin ãNA1À,@\nEvent: 14.375 Thread 0x0000000026b86000 Uncommon trap: reason=unstable_if action=reinterpret pc=0x00000000051076d4 method=com.mojang.datafixers.functions.PointFreeRule$CataFuseDifferent.doRewrite(Lcom/mojang/datafixers/types/Type;Lcom/mojang/datafixers/types/Type;Lcom/mojang/datafixers/functionsF\u001fÚdàÂ,@\nEvent: 14.381 Thread 0x0000000004692800 Uncommon trap: reason=unstable_if action=reinterpret pc=0x0000000005138898 method=it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap.get(I)Ljava/lang/Object; @ 59\nEvent: 14.433 Thread 0x0000000026b86000 Uncommon trap: reason=class_check action=maybe_recompile pc=0x00000000051eb684 method=com.mojang.datafixers.types.templates.Product.equals(Ljava/lang/Object;)Z @ 8\nEvent: 14.433 Thread 0x0000000026b86000 Uncommon trap: reason=class_check action=maybe_recompile pc=0x00000000051eb684 method=com.mojang.datafixers.types.templates.Product.equals(Ljava/lang/Object;)Z @ 8\nEvent: 14.433 Thread 0x0000000026b86000 Uncommon trap: reason=class_check action=maybe_recompile pc=0x00000000051eb684 method=com.mojang.datafixers.types.templates.Product.equals(Ljava/lang/Object;)Z @ 8\nEvent: 14.433 Thread 0x0000000026b86000 Uncommon trap: reason=class_check action=maybe_recompile pc=0x00000000051eb684 method=com.mojang.datafixers.types.templates.Product.equals(Ljava/lang/Object;)Z @ 8\nEvent: 14.473 Thread 0x0000000004692800 Uncommon trap: reason=unstable_if action=reinterpret pc=0x0000000004bc0420 method=com.google.gson.stream.JsonReader.hasNext()Z @ 21\n\nInternal exceptions (10 events):\nEvent: 13.682 Thread 0x0000000004692800 Exception <a 'java/lang/NoSuchMethodError': java.lang.Object.a(I)Ljava/lang/String;> (0x00000006de11eca0) thrown at [C:\\re\\workspace\\8-2-build-windows-amd64-cygwin\\jdk8u51\\3951\\hotspot\\src\\share\\vm\\interpreter\\linkResolver.cpp, line 582]\nEvent: 13.716 Thread 0x0000000026b86000 Implicit null exception at 0x0000000004dd97ec to 0x0000000004dd9df5\nEvent: 13.871 Thread 0x0000000004692800 Exception <a 'java/lang/NoSuchMethodError': java.lang.Object.a(Lcom/mojang/datafixers/Dynamic;)Lcjc;> (0x00000006ddc86548) thrown at [C:\\re\\workspace\\8-2-build-windows-amd64-cygwin\\jdk8u51\\3951\\hotspot\\src\\share\\vm\\interpreter\\linkResolver.cpp, line 582]\nEvent: 13.967 Thread 0x0000000004692800 Exception <a 'java/lang/NoSuchMethodError': java.lang.Object.b(Lcom/mojang/datafixers/Dynamic;)Lcjg;> (0x00000006fe002f80) thrown at [C:\\re\\workspace\\8-2-build-windows-amd64-cygwin\\jdk8u51\\3951\\hotspot\\src\\share\\vm\\interpreter\\linkResolver.cpp, line 582]\nEvent: 13.969 Thread 0x0000000004692800 Exception <a 'java/lang/NoSuchMethodError': java.lang.Object.a(Lcom/mojang/datafixers/Dynamic;)Lcjg;> (0x00000006fe00d610) thrown at [C:\\re\\workspace\\8-2-build-windows-amd64-cygwin\\jdk8u51\\3951\\hotspot\\src\\share\\vm\\interpreter\\linkResolver.cpp, line 582]\nEvent: 13.976 Thread 0x0000000004692800 Exception <a 'java/lang/NoSuchMethodError': java.lang.Object.a(Lcom/mojang/datafixers/Dynamic;)Lcfg;> (0x00000006fe0352c0) thrown at [C:\\re\\workspace\\8-2-build-windows-amd64-cygwin\\jdk8u51\\3951\\hotspot\\src\\share\\vm\\interpreter\\linkResolver.cpp, line 582]\nEvent: 14.082 Thread 0x0000000004692800 Exception <a 'java/lang/NoSuchMethodError': java.lang.Object.a(Ljava/util/HashMap;)V> (0x00000006ffe485b8) thrown at [C:\\re\\workspace\\8-2-build-windows-amd64-cygwin\\jdk8u51\\3951\\hotspot\\src\\share\\vm\\interpreter\\linkResolver.cpp, line 582]\nEvent: 14.204 Thread 0x0000000004692800 Exception <a 'java/lang/NoSuchMethodError': java.lang.Object.a(Lew;Lbca;)Lbca;> (0x00000006fa956fa0) thrown at [C:\\re\\workspace\\8-2-build-windows-amd64-cygwin\\jdk8u51\\3951\\hotspot\\src\\share\\vm\\interpreter\\linkResolver.cpp, line 582]\nEvent: 14.386 Thread 0x0000000026b86000 Exception <a 'java/lang/NoSuchMethodError': java.lang.Object.lambda$rewrite$0(Lcom/mojang/datafixers/View;Lcom/mojang/datafixers/functions/PointFree;)Lcom/mojang/datafixers/View;> (0x00000006f69fae98) thrown at [C:\\re\\workspace\\8-2-build-windows-amd64-cygw…Sg6\u0010\u0013-@\nEvent: 14.537 Thread 0x0000000004692800 Exception <a 'java/lang/NoSuchMethodError': java.lang.Object.lambda$static$0(Ljava/lang/String;)Ljava/lang/Boolean;> (0x00000006f73a6688) thrown at [C:\\re\\workspace\\8-2-build-windows-amd64-cygwin\\jdk8u51\\3951\\hotspot\\src\\share\\vm\\interpreter\\linkResolver.c@KŽrg]+@\n\nEvents (10 events):\nEvent: 14.596 loading class java/lang/invoke/MethodHandleImpl$BindCaller$T done\nEvent: 14.599 loading class java/lang/invoke/MethodHandleImpl$ArrayAccessor\nEvent: 14.599 loading class java/lang/invoke/MethodHandleImpl$ArrayAccessor done\nEvent: 14.599 loading class java/lang/invoke/MethodHandleImpl$ArrayAccessor$1\nEvent: 14.599 loading class java/lang/invoke/MethodHandleImpl$ArrayAccessor$1 done\nEvent: 14.603 loading class java/lang/invoke/DirectMethodHandle$EnsureInitialized\nEvent: 14.604 loading class java/lang/invoke/DirectMethodHandle$EnsureInitialized done\nEvent: 14.604 loading class sun/invoke/util/ValueConversions$WrapperCache\nEvent: 14.604 loading class sun/invoke/util/ValueConversions$WrapperCache done\nEvent: 14.631 Executing VM operation: G1IncCollectionPause\n\n\nDynamic libraries:\n0x00007ff76c690000 - 0x00007ff76c6c7000 \tC:\\Program Files (x86)\\Minecraft\\runtime\\jre-x64\\bin\\javaw.exe\n0x00007ffbb6f10000 - 0x00007ffbb70f0000 \tC:\\WINDOWS\\SYSTEM32\\ntdll.dll\n0x00007ffb9ed30000 - 0x00007ffb9ed42000 \tC:\\Program Files\\AVAST Software\\Avast\\aswhook.dll\n0x00007ffbb52c0000 - 0x00007ffbb536e000 \tC:\\WINDOWS\\System32\\KERNEL32.DLL\n0x00007ffbb4110000 - 0x00007ffbb4376000 \tC:\\WINDOWS\\System32\\KERNELBASE.dll\n0x00007ffbb0480000 - 0x00007ffbb0508000 \tC:\\WINDOWS\\SYSTEM32\\apphelp.dll\n0x00007ffbb4ff0000 - 0x00007ffbb5091000 \tC:\\WINDOWS\\System32\\ADVAPI32.dll\n0x00007ffbb5220000 - 0x00007ffbb52bd000 \tC:\\WINDOWS\\System32\\msvcrt.dll\n0x00007ffbb43e0000 - 0x00007ffbb443b000 \tC:\\WINDOWS\\System32\\sechost.dll\n0x00007ffbb5780000 - 0x00007ffbb589f000 \tC:\\WINDOWS\\System32\\RPCRT4.dll\n0x00007ffbb4980000 - 0x00007ffbb4b0f000 \tC:\\WINDOWS\\System32\\USER32.dll\n0x00007ffbb3430000 - 0x00007ffbb3450000 \tC:\\WINDOWS\\System32\\win32u.dll\n0x00007ffbb5640000 - 0x00007ffbb5668000 \tC:\\WINDOWS\\System32\\GDI32.dll\n0x00007ffbb3620000 - 0x00007ffbb37b3000 \tC:\\WINDOWS\\System32\\gdi32full.dll\n0x00007ffbb3310000 - 0x00007ffbb33ab000 \tC:\\WINDOWS\\System32\\msvcp_win.dll\n0x00007ffbb3810000 - 0x00007ffbb3904000 \tC:\\WINDOWS\\System32\\ucrtbase.dll\n0x00007ffba3740000 - 0x00007ffba39a9000 \tC:\\WINDOWS\\WinSxS\\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.16299.1087_none_0f9074b65a6589b7\\COMCTL32.dll\n0x00007ffbb4b40000 - 0x00007ffbb4e46000 \tC:\\WINDOWS\\System32\\combase.dll\n0x00007ffbb33b0000 - 0x00007ffbb3422000 \tC:\\WINDOWS\\System32\\bcryptPrimitives.dll\n0x00007ffbb4b10000 - 0x00007ffbb4b3d000 \tC:\\WINDOWS\\System32\\IMM32.DLL\n0x0000000071280000 - 0x0000000071352000 \tC:\\Program Files (x86)\\Minecraft\\runtime\\jre-x64\\bin\\msvcr100.dll\n0x00000000709f0000 - 0x0000000071273000 \tC:\\Program Files (x86)\\Minecraft\\runtime\\jre-x64\\bin\\server\\jvm.dll\n0x00007ffbb4900000 - 0x00007ffbb4908000 \tC:\\WINDOWS\\System32\\PSAPI.DLL\n0x00007ffba8b80000 - 0x00007ffba8b89000 \tC:\\WINDOWS\\SYSTEM32\\WSOCK32.dll\n0x00007ffbb4910000 - 0x00007ffbb497c000 \tC:\\WINDOWS\\System32\\WS2_32.dll\n0x00007ffba8b90000 - 0x00007ffba8bb3000 \tC:\\WINDOWS\\SYSTEM32\\WINMM.dll\n0x00007ffbaebe0000 - 0x00007ffbaebea000 \tC:\\WINDOWS\\SYSTEM32\\VERSION.dll\n0x00007ffba8af0000 - 0x00007ffba8b1a000 \tC:\\WINDOWS\\SYSTEM32\\WINMMBASE.dll\n0x00007ffbb37c0000 - 0x00007ffbb380a000 \tC:\\WINDOWS\\System32\\cfgmgr32.dll\n0x0000000071b80000 - 0x0000000071b8f000 \tC:\\Program Files (x86)\\Minecraft\\runtime\\jre-x64\\bin\\verify.dll\n0x0000000071b50000 - 0x0000000071b79000 \tC:\\Program Files (x86)\\Minecraft\\runtime\\jre-x64\\bin\\java.dll\n0x00000000709d0000 - 0x00000000709e6000 \tC:\\Program Files (x86)\\Minecraft\\runtime\\jre-x64\\bin\\zip.dll\n0x00007ffbb58a0000 - 0x00007ffbb6cd8000 \tC:\\WINDOWS\\System32\\SHELL32.dll\n0x00007ffbb6e30000 - 0x00007ffbb6ed6000 \tC:\\WINDOWS\\System32\\shcore.dll\n0x00007ffbb3910000 - 0x00007ffbb4057000 \tC:\\WINDOWS\\System32\\windows.storage.dll\n0x00007ffbb55e0000 - 0x00007ffbb5631000 \tC:\\WINDOWS\\System32\\shlwapi.dll\n0x00007ffbb3280000 - 0x00007ffbb3291000 \tC:\\WINDOWS\\System32\\kernel.appcore.dll\n0x00007ffbb32a0000 - 0x00007ffbb32ec000 \tC:\\WINDOWS\\System32\\powrprof.dll\n0x00007ffbb3260000 - 0x00007ffbb327b000 \tC:\\WINDOWS\\System32\\profapi.dll\n0x0000000071b40000 - 0x0000000071b4d000 \tC:\\Program Files (x86)\\Minecraft\\runtime\\jre-x64\\bin\\management.dll\n0x00000000709b0000 - 0x00000000709ca000 \tC:\\Program Files (x86)\\Minecraft\\runtime\\jre-x64\\bin\\net.dll\n0x00007ffbb2a90000 - 0x00007ffbb2af6000 \tC:\\WINDOWS\\system32\\mswsock.dll\n0x00007ffbae450000 - 0x00007ffbae466000 \tC:\\WINDOWS\\system32\\napinsp.dll\n0x00007ffbae430000 - 0x00007ffbae44a000 \tC:\\WINDOWS\\system32\\pnrpnsp.dll\n0x00007ffbabfa0000 - 0x00007ffbabfb8000 \tC:\\WINDOWS\\system32\\NLAapi.dll\n0x00007ffbb2870000 - 0x00007ffbb2926000 \tC:\\WINDOWS\\SYSTEM32\\DNSAPI.dll\n0x00007ffbb5210000 - 0x00007ffbb5218000 \tC:\\WINDOWS\\System32\\NSI.dll\n0x00007ffbb2820000 - 0x00007ffbb2859000 \tC:\\WINDOWS\\SYSTEM32\\IPHLPAPI.DLL\n0x00007ffbafcc0000 - 0x00007ffbafcce000 \tC:\\WINDOWS\\System32\\winrnr.dll\n0x0000000071a60000 - 0x0000000071a86000 \tC:\\Program Files\\Bonjour\\mdnsNSP.dll\n0x00007ffbae410000 - 0x00007ffbae425000 \tC:\\WINDOWS\\System32\\wshbth.dll\n0x00007ffbaae50000 - 0x00007ffbaae5a000 \tC:\\Windows\\System32\\rasadhlp.dll\n0x00007ffbaabc0000 - 0x00007ffbaac30000 \tC:\\WINDOWS\\System32\\fwpuclnt.dll\n0x00007ffbb2d60000 - 0x00007ffbb2d85000 \tC:\\WINDOWS\\SYSTEM32\\bcrypt.dll\n0x0000000070990000 - 0x00000000709a1000 \tC:\\Program Files (x86)\\Minecraft\\runtime\\jre-x64\\bin\\nio.dll\n0x00007ffbb2c50000 - 0x00007ffbb2c67000 \tC:\\WINDOWS\\SYSTEM32\\CRYPTSP.dll\n0x00007ffbb2690000 - 0x00007ffbb26c3000 \tC:\\WINDOWS\\system32\\rsaenh.dll\n0x00007ffbb3190000 - 0x00007ffbb31b9000 \tC:\\WINDOWS\\SYSTEM32\\USERENV.dll\n0x00007ffbb2c70000 - 0x00007ffbb2c7b000 \tC:\\WINDOWS\\SYSTEM32\\CRYPTBASE.dll\n0x00007ffbac290000 - 0x00007ffbac2a6000 \tC:\\WINDOWS\\SYSTEM32\\dhcpcsvc6.DLL\n0x00007ffbac410000 - 0x00007ffbac42a000 \tC:\\WINDOWS\\SYSTEM32\\dhcpcsvc.DLL\n0x00007ffbb1d60000 - 0x00007ffbb1f28000 \tC:\\WINDOWS\\SYSTEM32\\dbghelp.dll\n\nVM Arguments:\njvm_args: -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Dos.name=Windows 10 -Dos.version=10.0 -Xss1M -Djava.library.path=C:\\Users\\kgvaz\\AppData\\Local\\Temp\\8d1a-5c63-4e6c-9e96 -Dminecraft.launcher.brand=minecraft-launcher -Dminecraft.launcher.version=2.1.3674 -Xmx4G -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M -Dlog4j.configurationFile=C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\assets\\log_configs\\client-1.12.xml\njava_command: net.minecraft.client.main.Main --username Kevin6424 --version 1.14 --gameDir C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft --assetsDir C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft/assets --assetIndex 1.14 --uuid 4fc13624aaeb4d068cacf8755ec711b8 --accessToken c68a3aa2b1c248ec86e1d377e6fff5b0 --userType mojang --versionType release\njava_class_path (initial): C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\patchy\\1.1\\patchy-1.1.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\oshi-project\\oshi-core\\1.1\\oshi-core-1.1.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\net\\java\\dev\\jna\\jna\\4.4.0\\jna-4.4.0.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\net\\java\\dev\\jna\\platform\\3.4.0\\platform-3.4.0.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\com\\ibm\\icu\\icu4j-core-mojang\\51.2\\icu4j-core-mojang-51.2.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\javabridge\\1.0.22\\javabridge-1.0.22.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\net\\sf\\jopt-simple\\jopt-simple\\5.0.3\\jopt-simple-5.0.3.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\io\\netty\\netty-all\\4.1.25.Final\\netty-all-4.1.25.Final.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\com\\google\\guava\\guava\\21.0\\guava-21.0.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\commons\\commons-lang3\\3.5\\commons-lang3-3.5.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\commons-io\\commons-io\\2.5\\commons-io-2.5.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\commons-codec\\commons-codec\\1.10\\commons-codec-1.10.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\net\\java\\jinput\\jinput\\2.0.5\\jinput-2.0.5.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\net\\java\\jutils\\jutils\\1.0.0\\jutils-1.0.0.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\brigadier\\1.0.17\\brigadier-1.0.17.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\datafixerupper\\2.0.24\\datafixerupper-2.0.24.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\com\\google\\code\\gson\\gson\\2.8.0\\gson-2.8.0.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\com\\mojang\\authlib\\1.5.25\\authlib-1.5.25.jar;C:\\Users\\kgvaz\\AppData\\Roaming\\.minecraft\\libraries\\org\\apache\\commons\\commons-compress\\1.8.1\\commons-compress-1.8.1.jar;C:\\Users\\kgvaz\nLauncher Type: SUN_STANDARD\n\nEnvironment Variables:\nPATH=C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\ProgramData\\Oracle\\Java\\javapath;C:\\Program Files (x86)\\Intel\\iCLS Client\\;C:\\Program Files\\Intel\\iCLS Client\\;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\DAL;C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\DAL;C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\IPT;C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\IPT;C:\\Users\\kgvaz\\AppData\\Local\\Microsoft\\WindowsApps;\nUSERNAME=kgvaz\nOS=Windows_NT\nPROCESSOR_IDENTIFIER=Intel64 Family 6 Model 142 Stepping 9, GenuineIntel\n\n\n\n---------------  S Y S T E M  ---------------\n\nOS: Windows 10.0 , 64 bit Build 16299 (10.0.16299.1087)\n\nCPU:total 4 (2 cores per cpu, 2 threads per core) family 6 model 142 stepping 9, cmov, cx8, fxsr, mmx, sse, sse2, sse3, ssse3, sse4.1, sse4.2, popcnt, avx, avx2, aes, clmul, erms, 3dnowpref, lzcnt, ht, tsc, tscinvbit, bmi1, bmi2, adx\n\nMemory: 4k page, physical 8303524k(3728500k free), swap 10349980k(511092k free)\n\nvm_info: Java HotSpot(TM) 64-Bit Server VM (25.51-b03) for windows-amd64 JRE (1.8.0_51-b16), built on Jun  8 2015 18:03:07 by \"java_re\" with MS VC++ 10.0 (VS2010)\n\ntime: Wed May 08 05:14:22 2019\nelapsed time: 14 seconds (0d 0h 0m 14s)\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/out_of_memory2.txt",
    "content": "Error occurred during initialization of VM\nToo small maximum heap\n[HMCL ProcessListener] Minecraft exit with code 1(0x1).\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/shaders_mod.txt",
    "content": "[10:04:22] [main/INFO]: Loading tweak class name cpw.mods.fml.common.launcher.FMLTweaker\n[10:04:22] [main/INFO]: Using primary tweak class name cpw.mods.fml.common.launcher.FMLTweaker\n[10:04:22] [main/INFO]: Loading tweak class name com.mumfrey.liteloader.launch.LiteLoaderTweaker\n[10:04:22] [main/INFO]: Calling tweak class cpw.mods.fml.common.launcher.FMLTweaker\n[10:04:22] [main/INFO]: Forge Mod Loader version 7.99.40.1614 for Minecraft 1.7.10 loading\n[10:04:22] [main/INFO]: Java is Java HotSpot(TM) 64-Bit Server VM, version 1.8.0_371, running on Windows 10:amd64:10.0, installed at C:\\Program Files\\Java\\jre-1.8\n[10:04:22] [main/WARN]: The coremod atomicstryker.dynamiclights.common.DLFMLCorePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:22] [main/INFO]: [AppEng] Core Init\n[10:04:22] [main/WARN]: The coremod codechicken.core.launch.CodeChickenCorePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/INFO]: [codechicken.core.launch.DepLoader$DepLoadInst:checkExisting:436]: Warning: version of CodeChickenLib, **.**.**.** is newer than request **.**.**.**\n[10:04:23] [main/WARN]: The coremod cofh.asm.LoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod DummyCore.ASM.DCLoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod codechicken.core.launch.DepLoader does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/INFO]: Loading tweaker i18nupdatemod.launchwrapper.LaunchWrapperTweaker from I18nUpdateMod-3.4.2-all.jar\n[10:04:23] [main/WARN]: The coremod mekanism.common.asm.LoadingHook does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod openblocks.OpenBlocksCorePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod openmods.core.OpenModsCorePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/INFO]: Loading tweaker optifine.OptiFineForgeTweaker from OptiFine_1.7.10_HD_U_E7.jar\n[10:04:23] [main/WARN]: The coremod ak.potionextension.asm.PotionExtensionCorePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod lumien.resourceloader.asm.LoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod thaumcraft.codechicken.core.launch.DepLoader does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod thaumic.tinkerer.preloader.ThaumicTLoaderContainer does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod codechicken.core.launch.DepLoader does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod makeo.gadomancy.coremod.GadomancyCore does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod ic2.core.coremod.IC2core does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/INFO]: Loading tweaker forestry.shade.javacheck.Java7Checker from 【Technology】工业联动-林业 forestry_1.7.10-4.2.16.64.jar\n[10:04:23] [main/WARN]: The coremod micdoodle8.mods.miccore.MicdoodlePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/INFO]: [micdoodle8.mods.miccore.DepLoader$DepLoadInst:<init>:333]: MicdoodleCore searching for dependencies in mods file: E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\n[10:04:23] [main/WARN]: The coremod codechicken.nei.asm.NEICorePlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod invtweaks.forge.asm.FMLPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/WARN]: The coremod lain.mods.inputfix.InputFix does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/INFO]: Loading tweaker shadersmodcore.loading.SMCTweaker from 【基础】光影核心 shadersmod（低配甚用）.jar\n[10:04:23] [main/WARN]: The coremod fastcraft.LoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/INFO]: Loading tweaker me.guichaguri.betterfps.tweaker.BetterFpsTweaker from 【基础】更好的FPS-Ex-BetterFps-1.0.1.jar\n[10:04:23] [main/WARN]: The coremod lumien.nonausea.asm.LoadingPlugin does not have a MCVersion annotation, it may cause issues with this version of Minecraft\n[10:04:23] [main/INFO]: Loading tweaker i18nupdatemod.launchwrapper.LaunchWrapperTweaker from 汉化1.7.10.jar\n[10:04:23] [main/INFO]: Calling tweak class com.mumfrey.liteloader.launch.LiteLoaderTweaker\n[10:04:23] [main/INFO]: Bootstrapping LiteLoader 1.7.10\n[10:04:23] [main/INFO]: Registering API provider class com.mumfrey.liteloader.client.api.LiteLoaderCoreAPIClient\n[10:04:23] [main/INFO]: Spawning API provider class 'com.mumfrey.liteloader.client.api.LiteLoaderCoreAPIClient' ...\n[10:04:23] [main/INFO]: API provider class 'com.mumfrey.liteloader.client.api.LiteLoaderCoreAPIClient' provides API 'liteloader'\n[10:04:23] [main/INFO]: Initialising API 'liteloader' ...\n[10:04:23] [main/INFO]: LiteLoader begin PREINIT...\n[10:04:23] [main/INFO]: Initialising Loader properties...\n[10:04:23] [main/INFO]: Setting up logger...\n[10:04:23] [main/INFO]: LiteLoader 1.7.10_04 starting up...\n[10:04:23] [main/INFO]: Java reports OS=\"windows 10\"\n[10:04:23] [main/INFO]: Enumerating class path...\n[10:04:23] [main/INFO]: Class path separator=\";\"\n[10:04:23] [main/INFO]: Class path entries=(\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\mumfrey\\liteloader\\1.7.10_04\\liteloader-1.7.10_04.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\net\\minecraft\\launchwrapper\\1.12\\launchwrapper-1.12.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\ow2\\asm\\asm-all\\5.0.3\\asm-all-5.0.3.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\net\\minecraftforge\\forge\\1.7.10-10.13.4.1614-1.7.10\\forge-1.7.10-10.13.4.1614-1.7.10.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\typesafe\\akka\\akka-actor_2.11\\2.3.3\\akka-actor_2.11-2.3.3.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\typesafe\\config\\1.2.1\\config-1.2.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\scala-lang\\scala-actors-migration_2.11\\1.1.0\\scala-actors-migration_2.11-1.1.0.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\scala-lang\\scala-compiler\\2.11.1\\scala-compiler-2.11.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\scala-lang\\plugins\\scala-continuations-library_2.11\\1.0.2\\scala-continuations-library_2.11-1.0.2.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\scala-lang\\plugins\\scala-continuations-plugin_2.11.1\\1.0.2\\scala-continuations-plugin_2.11.1-1.0.2.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\scala-lang\\scala-library\\2.11.1\\scala-library-2.11.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\scala-lang\\scala-parser-combinators_2.11\\1.0.1\\scala-parser-combinators_2.11-1.0.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\scala-lang\\scala-reflect\\2.11.1\\scala-reflect-2.11.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\scala-lang\\scala-swing_2.11\\1.0.1\\scala-swing_2.11-1.0.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\scala-lang\\scala-xml_2.11\\1.0.2\\scala-xml_2.11-1.0.2.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\lzma\\lzma\\0.0.1\\lzma-0.0.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\net\\sf\\jopt-simple\\jopt-simple\\4.5\\jopt-simple-4.5.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\google\\guava\\guava\\17.0\\guava-17.0.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\apache\\commons\\commons-lang3\\3.3.2\\commons-lang3-3.3.2.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\mojang\\realms\\1.2.4\\realms-1.2.4.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\apache\\commons\\commons-compress\\1.8.1\\commons-compress-1.8.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\apache\\httpcomponents\\httpclient\\4.3.3\\httpclient-4.3.3.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\commons-logging\\commons-logging\\1.1.3\\commons-logging-1.1.3.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\apache\\httpcomponents\\httpcore\\4.3.2\\httpcore-4.3.2.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\java3d\\vecmath\\1.3.1\\vecmath-1.3.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\net\\sf\\trove4j\\trove4j\\3.0.3\\trove4j-3.0.3.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\ibm\\icu\\icu4j-core-mojang\\51.2\\icu4j-core-mojang-51.2.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\paulscode\\codecjorbis\\20101023\\codecjorbis-20101023.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\paulscode\\codecwav\\20101023\\codecwav-20101023.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\paulscode\\libraryjavasound\\20101123\\libraryjavasound-20101123.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\paulscode\\librarylwjglopenal\\20100824\\librarylwjglopenal-20100824.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\paulscode\\soundsystem\\20120107\\soundsystem-20120107.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\io\\netty\\netty-all\\4.0.10.Final\\netty-all-4.0.10.Final.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\commons-io\\commons-io\\2.4\\commons-io-2.4.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\commons-codec\\commons-codec\\1.9\\commons-codec-1.9.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\net\\java\\jinput\\jinput\\2.0.5\\jinput-2.0.5.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\net\\java\\jutils\\jutils\\1.0.0\\jutils-1.0.0.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\google\\code\\gson\\gson\\2.2.4\\gson-2.2.4.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\com\\mojang\\authlib\\1.5.13\\authlib-1.5.13.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\apache\\logging\\log4j\\log4j-api\\2.0-beta9\\log4j-api-2.0-beta9.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\apache\\logging\\log4j\\log4j-core\\2.0-beta9\\log4j-core-2.0-beta9.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\lwjgl\\lwjgl\\lwjgl\\2.9.1\\lwjgl-2.9.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\org\\lwjgl\\lwjgl\\lwjgl_util\\2.9.1\\lwjgl_util-2.9.1.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\libraries\\tv\\twitch\\twitch\\5.16\\twitch-5.16.jar\n   classpathEntry=E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\versions\\1.7.10\\1.7.10.jar\n)\n[10:04:23] [main/INFO]: Registering discovery module EnumeratorModuleClassPath: [<Java Class Path>]\n[10:04:23] [main/INFO]: Registering discovery module EnumeratorModuleFolder: [E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods]\n[10:04:23] [main/INFO]: Registering discovery module EnumeratorModuleFolder: [E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10]\n[10:04:23] [main/INFO]: Adding supported mod class prefix 'LiteMod'\n[10:04:23] [main/INFO]: Discovering tweaks on class path...\n[10:04:23] [main/INFO]: Discovering valid mod files in folder E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\n[10:04:23] [main/INFO]: Discovering valid mod files in folder E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\n[10:04:23] [main/INFO]: Searching for tweaks in 'Baubles-1.7.10-1.0.1.10.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\Baubles-1.7.10-1.0.1.10.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'bdlib-1.9.4.109-mc1.7.10.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\bdlib-1.9.4.109-mc1.7.10.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'CodeChickenCore-1.7.10-1.0.7.47-universal.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\CodeChickenCore-1.7.10-1.0.7.47-universal.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'CodeChickenLib-1.7.10-1.1.3.140-universal.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\CodeChickenLib-1.7.10-1.1.3.140-universal.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'CoFHCore-[1.7.10]3.1.4-329.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\CoFHCore-[1.7.10]3.1.4-329.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'CoFHLib-[1.7.10]1.1.2-182.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\CoFHLib-[1.7.10]1.1.2-182.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'CookieCore-1.7.10-1.4.0-11.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\CookieCore-1.7.10-1.4.0-11.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'Custom Crosshair Mod.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\Custom Crosshair Mod.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'CustomMainMenu-MC1.7.10-1.9.2.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\CustomMainMenu-MC1.7.10-1.9.2.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'DummyCore1.13.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\DummyCore1.13.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'ForgeMultipart-1.7.10-1.1.2.331-universal.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\ForgeMultipart-1.7.10-1.1.2.331-universal.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'Guide-API-1.7.10-1.0.1-23.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\Guide-API-1.7.10-1.0.1-23.jar'\n[10:04:23] [main/INFO]: Considering valid mod file: E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\mod_voxelMap_1.6.17_for_1.7.10.litemod\n[10:04:23] [main/INFO]: Searching for tweaks in 'PotionExtension-1.7.10-1.1.0.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\PotionExtension-1.7.10-1.1.0.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'ResourceLoader-1.2.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\ResourceLoader-1.2.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'SonarCore-1.7.10-1.1.3.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\SonarCore-1.7.10-1.1.3.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in 'YuutoLib-1.7.10-1.0.2.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\YuutoLib-1.7.10-1.0.2.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】NEI-本体模组 NotEnoughItems-1.7.10-1.0.5.120-universal.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】NEI-本体模组 NotEnoughItems-1.7.10-1.0.5.120-universal.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】NEI-神秘插件 Thaumcraftneiplugin-1.7.10-1.6a.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】NEI-神秘插件 Thaumcraftneiplugin-1.7.10-1.6a.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】R键整理-InventoryTweaks-1.58-147.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】R键整理-InventoryTweaks-1.58-147.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】中文输入补丁-InputFix-1.7.10-v5.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】中文输入补丁-InputFix-1.7.10-v5.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】内存清理-MemoryCleaner-1.4-mc.1.7.10.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】内存清理-MemoryCleaner-1.4-mc.1.7.10.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】快速模组-fastcraft-1.21.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】快速模组-fastcraft-1.21.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】更好的FPS-Ex-BetterFps-1.0.1.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】死亡不会掉落-Keeping Inventory.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】死亡不会掉落-Keeping Inventory.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】没有反胃！-NoNausea-MC1.7.10-1.0.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】没有反胃！-NoNausea-MC1.7.10-1.0.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】耐久显示-Durability Show 1.7.10.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】耐久显示-Durability Show 1.7.10.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】重置堆放数量 stacksize-1.7.10.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】重置堆放数量 stacksize-1.7.10.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】高亮显示主体-Waila-1.5.10_1.7.10.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】高亮显示主体-Waila-1.5.10_1.7.10.jar'\n[10:04:23] [main/INFO]: Searching for tweaks in '【基础】高亮显示扩展-WailaHarvestability-mc1.7.10-1.1.6.jar'\n[10:04:23] [main/WARN]: Error parsing manifest entries in 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】高亮显示扩展-WailaHarvestability-mc1.7.10-1.1.6.jar'\n[10:04:23] [main/INFO]: Considering valid mod file: E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】鼠标手势-MouseTweaks-2.4.4-mc1.7.10.jar\n[10:04:23] [main/INFO]: Adding newest valid mod file 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\mod_voxelMap_1.6.17_for_1.7.10.litemod' at revision 1617.0000\n[10:04:23] [main/INFO]: Adding newest valid mod file 'E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\1.7.10\\【基础】鼠标手势-MouseTweaks-2.4.4-mc1.7.10.jar' at revision 184.0000\n[10:04:23] [main/INFO]: Mod file '【基础】更好的FPS-Ex-BetterFps-1.0.1.jar' provides tweakClass 'me.guichaguri.betterfps.tweaker.BetterFpsTweaker', adding to Launch queue with priority 600\n[10:04:23] [main/INFO]: Mod file 'mod_voxelMap_1.6.17_for_1.7.10.litemod' provides classTransformer 'com.thevoxelbox.voxelmap.litemod.VoxelMapTransformer', adding to class loader\n[10:04:23] [main/INFO]: classTransformer 'com.thevoxelbox.voxelmap.litemod.VoxelMapTransformer' was successfully added\n[10:04:23] [main/INFO]: LiteLoader PREINIT complete\n[10:04:23] [main/INFO]: Sorting registered packet transformers by priority\n[10:04:23] [main/INFO]: Added 0 packet transformer classes to the transformer list\n[10:04:23] [main/INFO]: Injecting required class transformer 'com.mumfrey.liteloader.transformers.event.EventProxyTransformer'\n[10:04:23] [main/INFO]: Injecting required class transformer 'com.mumfrey.liteloader.launch.LiteLoaderTransformer'\n[10:04:23] [main/INFO]: Injecting required class transformer 'com.mumfrey.liteloader.client.transformers.CrashReportTransformer'\n[10:04:23] [main/INFO]: Queuing required class transformer 'com.mumfrey.liteloader.common.transformers.LiteLoaderPacketTransformer'\n[10:04:23] [main/INFO]: Queuing required class transformer 'com.mumfrey.liteloader.client.transformers.LiteLoaderEventInjectionTransformer'\n[10:04:23] [main/INFO]: Queuing required class transformer 'com.mumfrey.liteloader.client.transformers.MinecraftOverlayTransformer'\n[10:04:23] [main/INFO]: Loading tweak class name cpw.mods.fml.common.launcher.FMLInjectionAndSortingTweaker\n[10:04:23] [main/INFO]: Loading tweak class name i18nupdatemod.launchwrapper.LaunchWrapperTweaker\n[10:04:23] [main/INFO]: Loading tweak class name optifine.OptiFineForgeTweaker\n[10:04:23] [main/INFO]: Loading tweak class name forestry.shade.javacheck.Java7Checker\n[10:04:23] [main/INFO]: Loading tweak class name shadersmodcore.loading.SMCTweaker\n[10:04:23] [main/INFO]: Loading tweak class name me.guichaguri.betterfps.tweaker.BetterFpsTweaker\n[10:04:23] [main/WARN]: Tweak class name i18nupdatemod.launchwrapper.LaunchWrapperTweaker has already been visited -- skipping\n[10:04:23] [main/INFO]: Loading tweak class name cpw.mods.fml.common.launcher.FMLDeobfTweaker\n[10:04:23] [main/INFO]: Calling tweak class cpw.mods.fml.common.launcher.FMLInjectionAndSortingTweaker\n[10:04:23] [main/INFO]: Calling tweak class cpw.mods.fml.common.launcher.FMLInjectionAndSortingTweaker\n[10:04:23] [main/INFO]: Calling tweak class optifine.OptiFineForgeTweaker\n[10:04:23] [main/INFO]: [optifine.OptiFineForgeTweaker:dbg:56]: OptiFineForgeTweaker: acceptOptions\n[10:04:23] [main/INFO]: [optifine.OptiFineForgeTweaker:dbg:56]: OptiFineForgeTweaker: injectIntoClassLoader\n[10:04:23] [main/INFO]: [optifine.OptiFineClassTransformer:dbg:221]: OptiFine ClassTransformer\n[10:04:23] [main/INFO]: [optifine.OptiFineClassTransformer:dbg:221]: OptiFine ZIP file: E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\mods\\OptiFine_1.7.10_HD_U_E7.jar\n[10:04:23] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Found valid fingerprint for Minecraft. Certificate fingerprint cd99959656f753dc28d863b46769f7f8fbaefcfc\n[10:04:25] [main/ERROR]: FML appears to be missing any signature data. This is not a good thing\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: Loaded library EJML-core-0.26.jar.\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:25] [main/INFO]: [micdoodle8.mods.miccore.MicdoodlePlugin:getASMTransformerClass:102]: Successfully Registered Transformer\n[10:04:25] [main/INFO]: Failed to find MicdoodleCore file in mods folder, skipping GC version check.\n[10:04:25] [main/INFO]: [micdoodle8.mods.miccore.MicdoodlePlugin:injectData:295]: [Micdoodle8Core]: Patching game...\n[10:04:25] [main/INFO]: [micdoodle8.mods.miccore.MicdoodlePlugin:injectData:295]: [Micdoodle8Core]: Patching game...\n[10:04:25] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:26] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:26] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:26] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:26] [main/INFO]: Calling tweak class forestry.shade.javacheck.Java7Checker\n[10:04:26] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:26] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:26] [main/INFO]: Calling tweak class i18nupdatemod.launchwrapper.LaunchWrapperTweaker\n[10:04:29] [main/INFO]: Calling tweak class me.guichaguri.betterfps.tweaker.BetterFpsTweaker\n[10:04:29] [main/INFO]: Calling tweak class cpw.mods.fml.common.launcher.FMLDeobfTweaker\n[10:04:29] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:29] [main/INFO]: Starting to apply transformations\n[10:04:29] [main/INFO]: Starting to apply transformations\n[10:04:29] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:29] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:29] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:29] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:29] [main/INFO]: Calling tweak class shadersmodcore.loading.SMCTweaker\n[10:04:29] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:29] [main/INFO]: Calling tweak class cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper\n[10:04:29] [main/INFO]: Loading tweak class name cpw.mods.fml.common.launcher.TerminalTweaker\n[10:04:29] [main/INFO]: Calling tweak class cpw.mods.fml.common.launcher.TerminalTweaker\n[10:04:29] [main/INFO]: [optifine.OptiFineForgeTweaker:dbg:56]: OptiFineForgeTweaker: getLaunchArguments\n[10:04:29] [main/INFO]: Launching wrapped minecraft {net.minecraft.client.main.Main}\n[10:04:29] [main/INFO]: Injecting downstream transformers\n[10:04:29] [main/INFO]: Injecting additional class transformer class 'com.mumfrey.liteloader.client.transformers.LiteLoaderEventInjectionTransformer'\n[10:04:30] [main/INFO]: Injecting additional class transformer class 'com.mumfrey.liteloader.client.transformers.MinecraftOverlayTransformer'\n[10:04:30] [main/INFO]: Injecting additional class transformer class 'com.mumfrey.liteloader.common.transformers.LiteLoaderPacketTransformer'\n[10:04:30] [main/INFO]: Injecting additional class transformer class 'com.thevoxelbox.voxelmap.litemod.VoxelMapTransformer'\n[10:04:30] [main/INFO]: Patching Game Start...\n[10:04:30] [main/INFO]: Injecting onstartupcomplete[x1] in func_71384_a in Minecraft\n[10:04:30] [main/INFO]: Injecting prerenderfbo[x1] in func_71411_J in Minecraft\n[10:04:30] [main/INFO]: Injecting postrenderfbo[x1] in func_71411_J in Minecraft\n[10:04:30] [main/INFO]: Injecting ontimerupdate[x1] in func_71411_J in Minecraft\n[10:04:30] [main/INFO]: Injecting onrender[x1] in func_71411_J in Minecraft\n[10:04:30] [main/INFO]: Injecting ontick[x1] in func_71411_J in Minecraft\n[10:04:30] [main/INFO]: Injecting shutdown[x1] in func_71400_g in Minecraft\n[10:04:30] [main/INFO]: Injecting updateframebuffersize[x1] in func_147119_ah in Minecraft\n[10:04:30] [main/INFO]: Injecting newtick[x1] in func_71407_l in Minecraft\n[10:04:30] [main/INFO]: Applying overlay com.mumfrey.liteloader.client.overlays.MinecraftOverlay to net.minecraft.client.Minecraft\n[10:04:30] [main/INFO]: MinecraftOverlayTransformer found INIT injection point, this is good.\n[10:04:30] [main/INFO]: [atomicstryker.dynamiclights.common.DLTransformer:handleWorldTransform:70]: **************** Dynamic Lights transform running on World, obf: true *********************** \n[10:04:30] [main/INFO]: [atomicstryker.dynamiclights.common.DLTransformer:handleWorldTransform:83]: In target method! Patching!\n[10:04:30] [main/INFO]: [atomicstryker.dynamiclights.common.DLTransformer:handleWorldTransform:106]: Bytecode ISTORE 6 case!\n[10:04:30] [main/INFO]: [atomicstryker.dynamiclights.common.DLTransformer:handleWorldTransform:166]: Patching Complete!\n[10:04:30] [main/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: ahb (1 / 1)\n[10:04:30] [main/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: sv (1 / 1)\n[10:04:30] [main/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: sa (1 / 1)\n[10:04:30] [main/INFO]: [GadomancyTransformer] Transforming sv: net.minecraft.entity.EntityLivingBase\n[10:04:30] [main/INFO]: [EntityLivingBaseTransformer]INSIDE OBFUSCATED ENTITYLIVINGBASE CLASS - ABOUT TO PATCH: sv (net.minecraft.entity.EntityLivingBase)\n[10:04:30] [main/INFO]: [PlayerTransformer]INSIDE OBFUSCATED PLAYER CLASS - ABOUT TO PATCH: yz (net.minecraft.entity.player.EntityPlayer)\n[10:04:30] [main/INFO]: Injecting onoutboundchat[x1] in func_71165_d in EntityClientPlayerMP\n[10:04:30] [main/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: bdi (1 / 1)\n[10:04:30] [main/INFO]: Injecting onc16packetclientstatus[x1] in func_148833_a in C16PacketClientStatus\n[10:04:30] [main/INFO]: func_146977_a(Lnet/minecraft/inventory/Slot;)V - Transformed\n[10:04:30] [main/INFO]: func_146977_a_original(Lnet/minecraft/inventory/Slot;)V - New Method\n[10:04:30] [main/INFO]: func_146977_a(Lnet/minecraft/inventory/Slot;)V - Invoke Virtual\n[10:04:30] [main/INFO]: Injecting onrenderchat[x1] in func_73830_a in GuiIngame\n[10:04:30] [main/INFO]: Injecting postrenderchat[x1] in func_73830_a in GuiIngame\n[10:04:30] [main/INFO]: Injecting onc00handshake[x1] in func_148833_a in C00Handshake\n[10:04:30] [main/INFO]: Injecting onc00packetloginstart[x1] in func_148833_a in C00PacketLoginStart\n[10:04:30] [main/INFO]: Injecting ons03packettimeupdate[x1] in func_148833_a in S03PacketTimeUpdate\n[10:04:31] [main/INFO]: Setting user: Candle_Dragon\n[10:04:31] [main/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: xk (1 / 1)\n[10:04:31] [main/INFO]: Patching leaves.\n[10:04:31] [main/INFO]: Found leaf Class: net/minecraft/block/BlockLeavesBase\n[10:04:32] [main/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: zc (1 / 1)\n[10:04:32] [main/INFO]: Transforming Class [net.minecraft.entity.projectile.EntityArrow], Method [func_70071_h_]\n[10:04:32] [main/INFO]: Transforming net.minecraft.entity.projectile.EntityArrow Finished.\n[10:04:32] [main/INFO]: [PlayerMPTransformer]INSIDE OBFUSCATED PLAYER MP CLASS - ABOUT TO PATCH: mw (net.minecraft.entity.player.EntityPlayerMP)\n[10:04:32] [main/INFO]: Injecting ons34packetmaps[x1] in func_148833_a in S34PacketMaps\n[10:04:32] [main/INFO]: Transforming Class [net.minecraft.item.ItemStack], Method [func_77953_t]\n[10:04:32] [main/INFO]: Transforming net.minecraft.item.ItemStack Finished.\n[10:04:32] [main/INFO]: InvTweaks: net.minecraft.inventory.Container\n[10:04:32] [main/INFO]: InvTweaks: net.minecraft.inventory.ContainerFurnace\n[10:04:32] [main/INFO]: Transforming Class [net.minecraft.inventory.ContainerFurnace], Method [func_82846_b]\n[10:04:32] [main/INFO]: Transforming net.minecraft.inventory.ContainerFurnace Finished.\n[10:04:32] [main/INFO]: Patching Minecraft using Riven's \"Half\" Algorithm\n[10:04:32] [Client thread/INFO]: Injecting onc15packetclientsettings[x1] in func_148833_a in C15PacketClientSettings\n[10:04:33] [Client thread/INFO]: Patching Key Event...\n[10:04:33] [Client thread/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: bkn (1 / 1)\n[10:04:33] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: ModLoader\n[10:04:33] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: LightCache\n[10:04:33] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: BlockCoord\n[10:04:33] [Client thread/INFO]: Injecting ons23packetblockchange[x1] in func_148833_a in S23PacketBlockChange\n[10:04:33] [Client thread/INFO]: InvTweaks: net.minecraft.inventory.ContainerRepair\n[10:04:33] [Client thread/INFO]: Transforming Class [net.minecraft.inventory.ContainerRepair], Method [func_82848_d]\n[10:04:33] [Client thread/INFO]: Transforming net.minecraft.inventory.ContainerRepair Finished.\n[10:04:33] [Client thread/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: net/minecraftforge/client/ForgeHooksClient (1 / 1)\n[10:04:33] [Client thread/INFO]: [RenderTransformer]INSIDE OBFUSCATED RENDER CLASS - ABOUT TO PATCH: bno\n[10:04:33] [Client thread/INFO]: Injecting oncreateintegratedserver[x1] in <init> in IntegratedServer\n[10:04:33] [Client thread/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: bjb (2 / 2)\n[10:04:33] [Client thread/INFO]: Injecting constructchunkfrompacket[x1] in func_76607_a in Chunk\n[10:04:33] [Client thread/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printResultsAndReset:1905]: Potential problem: Galacticraft did not complete injection of bytecode into: blt (4 / 5)\n[10:04:33] [Client thread/INFO]: [EntityRendererTransformer]INSIDE OBFUSCATED RENDERER CLASS - ABOUT TO PATCH: blt\n[10:04:33] [Client thread/INFO]: Injecting prerendergui[x1] in func_78480_b in EntityRenderer\n[10:04:33] [Client thread/INFO]: Injecting onrenderhud[x1] in func_78480_b in EntityRenderer\n[10:04:33] [Client thread/INFO]: Injecting postrenderhud[x1] in func_78480_b in EntityRenderer\n[10:04:33] [Client thread/INFO]: Injecting onrenderworld[x1] in func_78471_a in EntityRenderer\n[10:04:33] [Client thread/INFO]: Injecting onsetupcameratransform[x1] in func_78471_a in EntityRenderer\n[10:04:33] [Client thread/INFO]: Injecting postrenderentities[x1] in func_78471_a in EntityRenderer\n[10:04:33] [Client thread/INFO]: Injecting postrender[x1] in func_78471_a in EntityRenderer\n[10:04:33] [Client thread/INFO]: Injecting postrender[x1] in func_78471_a in EntityRenderer\n[10:04:33] [Client thread/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: boh (1 / 1)\n[10:04:34] [Client thread/INFO]: Injecting ons35packetupdatetileentity[x1] in func_148833_a in S35PacketUpdateTileEntity\n[10:04:34] [Client thread/INFO]: [micdoodle8.mods.miccore.MicdoodleTransformer:printLog:1989]: Galacticraft successfully injected bytecode into: bly (1 / 1)\n[10:04:34] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.fml.common.Loader\n[10:04:34] [Client thread/INFO]: [OptiFine] (Reflector) Class not present: net.minecraftforge.fml.common.ModContainer\n[10:04:34] [Client thread/INFO]: LWJGL Version: 2.9.1\n[10:04:34] [Client thread/INFO]: Injecting renderfbo[x1] in func_147615_c in Framebuffer\n[10:04:34] [Client thread/INFO]: [OptiFine] \n[10:04:34] [Client thread/INFO]: [OptiFine] OptiFine_1.7.10_HD_U_E7\n[10:04:34] [Client thread/INFO]: [OptiFine] Build: 20180404-111312\n[10:04:34] [Client thread/INFO]: [OptiFine] OS: Windows 10 (amd64) version 10.0\n[10:04:34] [Client thread/INFO]: [OptiFine] Java: 1.8.0_371, Oracle Corporation\n[10:04:34] [Client thread/INFO]: [OptiFine] VM: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n[10:04:34] [Client thread/INFO]: [OptiFine] LWJGL: 2.9.1\n[10:04:34] [Client thread/INFO]: [OptiFine] OpenGL: NVIDIA GeForce GTX 1050 Ti/PCIe/SSE2, version 4.6.0 NVIDIA 531.68, NVIDIA Corporation\n[10:04:34] [Client thread/INFO]: [OptiFine] OpenGL Version: 4.6.0\n[10:04:34] [Client thread/INFO]: [OptiFine] Maximum texture size: 32768x32768\n[10:04:34] [Thread-7/INFO]: [OptiFine] Checking for new version\n[10:04:35] [Client thread/INFO]: [net.minecraft.client.Minecraft:func_71377_b:349]: ---- Minecraft Crash Report ----\n// Shall we play a game?\n\nTime: 23-5-3 上午10:04\nDescription: Initializing game\n\njava.lang.RuntimeException: Shaders Mod detected. Please remove it, OptiFine has built-in support for shaders.\n\tat shadersmod.client.Shaders.checkShadersModInstalled(Shaders.java:5623)\n\tat shadersmod.client.Shaders.startup(Shaders.java:1881)\n\tat Config.initDisplay(Config.java:216)\n\tat net.minecraft.client.renderer.OpenGlHelper.func_77474_a(OpenGlHelper.java:82)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:455)\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:878)\n\tat net.minecraft.client.main.Main.main(SourceFile:148)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n---------------------------------------------------------------------------------------\n\n-- Head --\nStacktrace:\n\tat shadersmod.client.Shaders.checkShadersModInstalled(Shaders.java:5623)\n\tat shadersmod.client.Shaders.startup(Shaders.java:1881)\n\tat Config.initDisplay(Config.java:216)\n\tat net.minecraft.client.renderer.OpenGlHelper.func_77474_a(OpenGlHelper.java:82)\n\tat net.minecraft.client.Minecraft.func_71384_a(Minecraft.java:455)\n\n-- Initialization --\nDetails:\nStacktrace:\n\tat net.minecraft.client.Minecraft.func_99999_d(Minecraft.java:878)\n\tat net.minecraft.client.main.Main.main(SourceFile:148)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)\n\tat java.lang.reflect.Method.invoke(Unknown Source)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:135)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\n\n-- System Details --\nDetails:\n\tMinecraft Version: 1.7.10\n\tOperating System: Windows 10 (amd64) version 10.0\n\tJava Version: 1.8.0_371, Oracle Corporation\n\tJava VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation\n\tMemory: 152017256 bytes (144 MB) / 268435456 bytes (256 MB) up to 10603200512 bytes (10112 MB)\n\tJVM Flags: 11 total; -Xmx10110m -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump\n\tAABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tIntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0\n\tFML: \n\tLaunched Version: M.T.A 璇瑜之路\n\tLWJGL: 2.9.1\n\tOpenGL: NVIDIA GeForce GTX 1050 Ti/PCIe/SSE2 GL version 4.6.0 NVIDIA 531.68, NVIDIA Corporation\n\tGL Caps: \n\tIs Modded: Definitely; Client brand changed to 'fml,forge'\n\tType: Client (map_client.txt)\n\tResource Packs: [Minecraft-Mod-Language-Modpack-Converted-1.zip, R3D SR 锟接诧拷锟斤拷图-256x.zip, [锟斤拷詹锟斤拷锟�.zip, 锟斤拷锟斤拷锟斤拷纸.zip]\n\tCurrent Language: ~~ERROR~~ NullPointerException: null\n\tProfiler Position: N/A (disabled)\n\tVec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used\n\tAnisotropic Filtering: Off (1)\n\tOptiFine Version: OptiFine_1.7.10_HD_U_E7\n\tRender Distance Chunks: 9\n\tMipmaps: 4\n\tAnisotropic Filtering: 1\n\tAntialiasing: 0\n\tMultitexture: false\n\tShaders: null\n\tOpenGlVersion: 4.6.0 NVIDIA 531.68\n\tOpenGlRenderer: NVIDIA GeForce GTX 1050 Ti/PCIe/SSE2\n\tOpenGlVendor: NVIDIA Corporation\n\tCpuCount: 12\n[10:04:35] [Client thread/INFO]: [net.minecraft.client.Minecraft:func_71377_b:359]: #@!@# Game crashed! Crash report saved to: #@!@# E:\\000000000000000000000000000000\\M.T.A-ModPack\\M.T.A-ModPack 1.1（正式）\\联机\\M.T.A-ModPack 1.1（埃罗芒老师的主场） (2)\\M.T.A-ModPack 1.1（埃罗芒老师的主场）\\.minecraft\\crash-reports\\crash-2023-05-03_10.04.34-client.txt"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/too_old_java.txt",
    "content": "[23:31:31] [main/INFO]: Loading tweak class name net.fabricmc.loader.launch.FabricClientTweaker\n[23:31:31] [main/INFO]: Using primary tweak class name net.fabricmc.loader.launch.FabricClientTweaker\n[23:31:31] [main/INFO]: Calling tweak class net.fabricmc.loader.launch.FabricClientTweaker\n[23:31:31] [main/ERROR]: Unable to load mod from fabric-example-mod-1.0.0.jar\ncom.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $.mixins\n\tat com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:224)\n\tat com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)\n\tat com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)\n\tat com.google.gson.Gson.fromJson(Gson.java:887)\n\tat com.google.gson.Gson.fromJson(Gson.java:952)\n\tat com.google.gson.Gson.fromJson(Gson.java:925)\n\tat net.fabricmc.loader.FabricLoader.getMods(FabricLoader.java:489)\n\tat net.fabricmc.loader.FabricLoader.getJarMods(FabricLoader.java:474)\n\tat net.fabricmc.loader.FabricLoader.load(FabricLoader.java:191)\n\tat net.fabricmc.loader.FabricLoader.load(FabricLoader.java:148)\n\tat net.fabricmc.loader.launch.FabricTweaker.injectIntoClassLoader(FabricTweaker.java:132)\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:115)\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28)\nCaused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $.mixins\n\tat com.google.gson.internal.bind.JsonTreeReader.expect(JsonTreeReader.java:162)\n\tat com.google.gson.internal.bind.JsonTreeReader.beginObject(JsonTreeReader.java:87)\n\tat com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:213)\n\t... 12 more\n[23:31:31] [main/INFO]: Loading 1 mod: fabric\n[23:31:31] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.7.11 Source=file:/C:/Users/user/AppData/Roaming/.minecraft/libraries/net/fabricmc/sponge-mixin/0.7.11.10/sponge-mixin-0.7.11.10.jar Service=LaunchWrapper Env=UNKNOWN\n[23:31:32] [main/INFO]: FML platform manager could not load class cpw.mods.fml.relauncher.CoreModManager. Proceeding without FML support.\n[23:31:32] [main/INFO]: Compatibility level set to JAVA_8\n[23:31:32] [main/INFO]: Loading tweak class name org.spongepowered.asm.mixin.EnvironmentStateTweaker\n[23:31:32] [main/INFO]: Calling tweak class org.spongepowered.asm.mixin.EnvironmentStateTweaker\n[23:31:33] [main/WARN]: Error loading class: net/minecraft/server/MinecraftServer (java.lang.IllegalArgumentException: Unsupported class file major version 60)\n[23:31:33] [main/WARN]: @Mixin target net.minecraft.server.MinecraftServer was not found fabric-loader.mixins.common.json:server.MixinMinecraftServerBrand\n[23:31:33] [main/WARN]: Error loading class: net/minecraft/class_310 (java.lang.ClassNotFoundException: The specified class 'net.minecraft.class_310' was not found)\n[23:31:33] [main/WARN]: @Mixin target net.minecraft.class_310 was not found fabric-loader.mixins.client.json:MixinMinecraftClient\n[23:31:33] [main/WARN]: Error loading class: net/minecraft/client/ClientBrandRetriever (java.lang.IllegalArgumentException: Unsupported class file major version 60)\n[23:31:33] [main/WARN]: @Mixin target net.minecraft.client.ClientBrandRetriever was not found fabric-loader.mixins.client.json:MixinClientBrandRetriever\n[23:31:33] [main/ERROR]: Unable to launch\njava.lang.ClassNotFoundException: net.minecraft.client.main.Main\n\tat net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:191) ~[launchwrapper-1.12.jar:?]\n\tat java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_241]\n\tat java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_241]\n\tat java.lang.Class.forName0(Native Method) ~[?:1.8.0_241]\n\tat java.lang.Class.forName(Class.java:348) ~[?:1.8.0_241]\n\tat net.minecraft.launchwrapper.Launch.launch(Launch.java:131) [launchwrapper-1.12.jar:?]\n\tat net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.12.jar:?]\nCaused by: java.lang.UnsupportedClassVersionError: net/minecraft/client/main/Main has been compiled by a more recent version of the Java Runtime (class file version 60.0), this version of the Java Runtime only recognizes class file versions up to 52.0\n\tat java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_241]\n\tat java.lang.ClassLoader.defineClass(ClassLoader.java:756) ~[?:1.8.0_241]\n\tat java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[?:1.8.0_241]\n\tat net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:182) ~[launchwrapper-1.12.jar:?]\n\t... 6 more\n"
  },
  {
    "path": "HMCLCore/src/test/resources/logs/unsatisfied_link_error.txt",
    "content": "2021-10-09 00:17:06,325 main WARN Advanced terminal features are not available in this environment\n[00:17:06] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher running: args [--username, 95_bx, --version, 1.16.4, --gameDir, C:\\Users\\69510\\Desktop\\.minecraft, --assetsDir, C:\\Users\\69510\\Desktop\\.minecraft\\assets, --assetIndex, 1.16, --uuid, 960e00a7ed553d53b6d1e9cfac5fe6e2, --accessToken, ????????, --userType, mojang, --versionType, HMCL 3.4.202, --width, 854, --height, 480, --tweakClass, optifine.OptiFineTweaker, --launchTarget, fmlclient, --fml.forgeVersion, 35.1.37, --fml.mcVersion, 1.16.4, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20201102.104115]\n[00:17:06] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher 8.0.9+86+master.3cf110c starting: java version 1.8.0_151 by Oracle Corporation\n[00:17:06] [main/WARN] [cp.mo.mo.SecureJarHandler/]: LEGACY JDK DETECTED, SECURED JAR HANDLING DISABLED\n[00:17:06] [main/INFO] [op.OptiFineTransformationService/]: OptiFineTransformationService.onLoad\n[00:17:06] [main/INFO] [op.OptiFineTransformationService/]: OptiFine ZIP file: C:\\Users\\69510\\Desktop\\.minecraft\\libraries\\optifine\\OptiFine\\1.16.4_HD_U_G7\\OptiFine-1.16.4_HD_U_G7.jar\n[00:17:06] [main/INFO] [op.OptiFineTransformer/]: Target.PRE_CLASS is available\n[00:17:07] [main/INFO] [ne.mi.fm.lo.FixSSL/CORE]: Added Lets Encrypt root certificates as additional trust\n[00:17:07] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.2 Source=file:/C:/Users/69510/Desktop/.minecraft/libraries/org/spongepowered/mixin/0.8.2/mixin-0.8.2.jar Service=ModLauncher Env=CLIENT\n[00:17:07] [main/INFO] [op.OptiFineTransformationService/]: OptiFineTransformationService.initialize\n[00:17:07] [main/INFO] [STDERR/]: [org.lwjgl.system.Library:printError:424]: [LWJGL] Failed to load a library. Possible solutions:\n\ta) Add the directory that contains the shared library to -Djava.library.path or -Dorg.lwjgl.librarypath.\n\tb) Add the JAR that contains the shared library to the classpath.\n[00:17:07] [main/INFO] [STDERR/]: [org.lwjgl.system.Library:printError:426]: [LWJGL] Enable debug mode with -Dorg.lwjgl.util.Debug=true for better diagnostics.\n[00:17:07] [main/INFO] [STDERR/]: [org.lwjgl.system.Library:printError:428]: [LWJGL] Enable the SharedLibraryLoader debug mode with -Dorg.lwjgl.util.DebugLoader=true for better diagnostics.\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: java.lang.UnsatisfiedLinkError: Failed to locate library: lwjgl.dll\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.lwjgl.system.Library.loadSystem(Library.java:147)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.lwjgl.system.Library.loadSystem(Library.java:67)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.lwjgl.system.Library.<clinit>(Library.java:50)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.lwjgl.system.MemoryUtil.<clinit>(MemoryUtil.java:97)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.lwjgl.system.Pointer$Default.<clinit>(Pointer.java:67)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat org.lwjgl.system.Callback.<clinit>(Callback.java:40)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.progress.ClientVisualization.initWindow(ClientVisualization.java:66)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.progress.ClientVisualization.start(ClientVisualization.java:317)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.progress.EarlyProgressVisualization.accept(EarlyProgressVisualization.java:43)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.FMLLoader.setupLaunchHandler(FMLLoader.java:190)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat net.minecraftforge.fml.loading.FMLServiceProvider.initialize(FMLServiceProvider.java:94)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformationServiceDecorator.onInitialize(TransformationServiceDecorator.java:68)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformationServicesHandler.lambda$initialiseTransformationServices$7(TransformationServicesHandler.java:107)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat java.util.HashMap$Values.forEach(Unknown Source)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformationServicesHandler.initialiseTransformationServices(TransformationServicesHandler.java:107)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:59)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.run(Launcher.java:76)\n[00:17:07] [main/INFO] [STDERR/]: [java.lang.ThreadGroup:uncaughtException:-1]: \tat cpw.mods.modlauncher.Launcher.main(Launcher.java:66)\nException in thread \"main\"\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "import org.jackhuang.hmcl.gradle.ci.CheckUpdate\nimport org.jackhuang.hmcl.gradle.docs.UpdateDocuments\nimport org.jackhuang.hmcl.gradle.l10n.ParseLanguageSubtagRegistry\n\nplugins {\n    id(\"checkstyle\")\n}\n\ngroup = \"org.jackhuang\"\nversion = \"3.0\"\n\nsubprojects {\n    apply {\n        plugin(\"java\")\n        plugin(\"idea\")\n        plugin(\"maven-publish\")\n        plugin(\"checkstyle\")\n    }\n\n    repositories {\n        flatDir {\n            name = \"libs\"\n            dirs = setOf(rootProject.file(\"lib\"))\n        }\n\n        System.getenv(\"MAVEN_CENTRAL_REPO\").let { repo ->\n            if (repo.isNullOrBlank())\n                mavenCentral()\n            else\n                maven(url = repo)\n        }\n\n        maven(url = \"https://jitpack.io\")\n        maven(url = \"https://libraries.minecraft.net\")\n    }\n\n    tasks.withType<JavaCompile> {\n        options.encoding = \"UTF-8\"\n    }\n\n    @Suppress(\"UnstableApiUsage\")\n    tasks.withType<Checkstyle> {\n        maxHeapSize.set(\"2g\")\n    }\n\n    configure<CheckstyleExtension> {\n        sourceSets = setOf()\n    }\n\n    dependencies {\n        \"testImplementation\"(rootProject.libs.junit.jupiter)\n        \"testRuntimeOnly\"(\"org.junit.platform:junit-platform-launcher\")\n    }\n\n    tasks.withType<Test> {\n        useJUnitPlatform()\n        testLogging.showStandardStreams = true\n    }\n\n    configure<PublishingExtension> {\n        publications {\n            create<MavenPublication>(\"maven\") {\n                from(components[\"java\"])\n            }\n        }\n        repositories {\n            mavenLocal()\n        }\n    }\n\n    tasks.register(\"checkstyle\") {\n        dependsOn(tasks[\"checkstyleMain\"], tasks[\"checkstyleTest\"])\n    }\n}\n\norg.jackhuang.hmcl.gradle.javafx.JavaFXUtils.register(rootProject)\n\ndefaultTasks(\"clean\", \"build\")\n\ntasks.register<ParseLanguageSubtagRegistry>(\"parseLanguageSubtagRegistry\") {\n    languageSubtagRegistryFile.set(layout.projectDirectory.file(\"language-subtag-registry\"))\n\n    sublanguagesFile.set(layout.projectDirectory.file(\"HMCLCore/src/main/resources/assets/lang/sublanguages.csv\"))\n    defaultScriptFile.set(layout.projectDirectory.file(\"HMCLCore/src/main/resources/assets/lang/default_script.csv\"))\n}\n\ntasks.register<UpdateDocuments>(\"updateDocuments\") {\n    documentsDir.set(layout.projectDirectory.dir(\"docs\"))\n}\n\ntasks.register<CheckUpdate>(\"checkUpdateDev\") {\n    uri.set(\"https://ci.huangyuhui.net/job/HMCL-nightly\")\n}\n\ntasks.register<CheckUpdate>(\"checkUpdateStable\") {\n    uri.set(\"https://ci.huangyuhui.net/job/HMCL-stable\")\n}\n"
  },
  {
    "path": "buildSrc/build.gradle.kts",
    "content": "repositories {\n    System.getenv(\"MAVEN_CENTRAL_REPO\").let { repo ->\n        if (repo.isNullOrBlank())\n            mavenCentral()\n        else\n            maven(url = repo)\n    }\n}\n\ndependencies {\n    implementation(libs.gson)\n    implementation(libs.jna)\n    implementation(libs.kala.compress.tar)\n}\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_17\n    targetCompatibility = JavaVersion.VERSION_17\n}\n\ntasks.withType<JavaCompile> {\n    options.encoding = \"UTF-8\"\n}\n\ntasks.processResources {\n    into(\"org/jackhuang/hmcl/gradle/l10n\") {\n        from(projectDir.resolve(\"../HMCLCore/src/main/resources/assets/lang/\"))\n    }\n}\n"
  },
  {
    "path": "buildSrc/settings.gradle.kts",
    "content": "dependencyResolutionManagement {\n    versionCatalogs {\n        create(\"libs\") {\n            from(files(\"../gradle/libs.versions.toml\"))\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/TerracottaConfigUpgradeTask.java",
    "content": "package org.jackhuang.hmcl.gradle;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonPrimitive;\nimport com.google.gson.annotations.SerializedName;\nimport kala.compress.archivers.tar.TarArchiveEntry;\nimport kala.compress.archivers.tar.TarArchiveReader;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.file.RegularFileProperty;\nimport org.gradle.api.provider.ListProperty;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.InputFile;\nimport org.gradle.api.tasks.OutputFile;\nimport org.gradle.api.tasks.TaskAction;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.DigestInputStream;\nimport java.security.MessageDigest;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.HexFormat;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.zip.GZIPInputStream;\n\npublic abstract class TerracottaConfigUpgradeTask extends DefaultTask {\n\n    private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();\n\n    @Input\n    public abstract ListProperty<@NotNull String> getClassifiers();\n\n    @Input\n    public abstract Property<@NotNull String> getVersion();\n\n    @Input\n    public abstract Property<@NotNull String> getDownloadURL();\n\n    @InputFile\n    public abstract RegularFileProperty getTemplateFile();\n\n    @OutputFile\n    public abstract RegularFileProperty getOutputFile();\n\n    @TaskAction\n    public void run() throws Exception {\n        JsonObject config = GSON.fromJson(\n                Files.readString(getTemplateFile().get().getAsFile().toPath(), StandardCharsets.UTF_8),\n                JsonObject.class\n        );\n\n        Map<String, Path> files = new LinkedHashMap<>();\n        HttpClient client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build();\n        try {\n            List<CompletableFuture<HttpResponse<Path>>> tasks = new ArrayList<>();\n            for (String classifier : getClassifiers().get()) {\n                Path path = Files.createTempFile(\"terracotta-bundle-\", \".tar.gz\");\n                String url = getDownloadURL().get().replace(\"${classifier}\", classifier).replace(\"${version}\", getVersion().get());\n                files.put(classifier, path);\n\n                tasks.add(client.sendAsync(\n                        HttpRequest.newBuilder().GET().uri(URI.create(url)).build(),\n                        HttpResponse.BodyHandlers.ofFile(path)\n                ));\n            }\n\n            for (CompletableFuture<HttpResponse<Path>> task : tasks) {\n                HttpResponse<Path> response = task.get();\n                if (response.statusCode() != 200) {\n                    throw new IOException(String.format(\"Unable to request %s: %d\", response.uri(), response.statusCode()));\n                }\n            }\n        } finally {\n            if (client instanceof AutoCloseable) { // Since Java21, HttpClient implements AutoCloseable: https://bugs.openjdk.org/browse/JDK-8304165\n                ((AutoCloseable) client).close();\n            }\n        }\n\n        Map<String, Bundle> bundles = new LinkedHashMap<>();\n        MessageDigest digest = MessageDigest.getInstance(\"SHA-512\");\n        HexFormat hexFormat = HexFormat.of();\n\n        for (Map.Entry<String, Path> entry : files.entrySet()) {\n            String classifier = entry.getKey();\n            Path bundle = entry.getValue();\n            Path decompressedBundle = Files.createTempFile(\"terracotta-bundle-\", \".tar\");\n            try (InputStream is = new GZIPInputStream(new DigestInputStream(Files.newInputStream(bundle), digest));\n                 OutputStream os = Files.newOutputStream(decompressedBundle)) {\n                is.transferTo(os);\n            }\n\n            String bundleHash = hexFormat.formatHex(digest.digest());\n\n            Map<String, String> bundleContents = new LinkedHashMap<>();\n            try (TarArchiveReader reader = new TarArchiveReader(decompressedBundle)) {\n                List<TarArchiveEntry> entries = new ArrayList<>(reader.getEntries());\n                entries.sort(Comparator.comparing(TarArchiveEntry::getName));\n\n                for (TarArchiveEntry archiveEntry : entries) {\n                    String[] split = archiveEntry.getName().split(\"/\", 2);\n                    if (split.length != 1) {\n                        throw new IllegalStateException(\n                                String.format(\"Illegal bundle %s: files (%s) in sub directories are unsupported.\", classifier, archiveEntry.getName())\n                        );\n                    }\n                    String name = split[0];\n\n                    try (InputStream is = new DigestInputStream(reader.getInputStream(archiveEntry), digest)) {\n                        is.transferTo(OutputStream.nullOutputStream());\n                    }\n                    String hash = hexFormat.formatHex(digest.digest());\n\n                    bundleContents.put(name, hash);\n                }\n            }\n\n            bundles.put(classifier, new Bundle(bundleHash, bundleContents));\n\n            Files.delete(bundle);\n            Files.delete(decompressedBundle);\n        }\n\n        config.add(\"__comment__\", new JsonPrimitive(\"THIS FILE IS MACHINE GENERATED! DO NOT EDIT!\"));\n        config.add(\"version_latest\", new JsonPrimitive(getVersion().get()));\n        config.add(\"packages\", GSON.toJsonTree(bundles));\n\n        Files.writeString(getOutputFile().get().getAsFile().toPath(), GSON.toJson(config), StandardCharsets.UTF_8);\n    }\n\n    public void checkValid() throws IOException {\n        Path output = getOutputFile().get().getAsFile().toPath();\n        if (Files.isReadable(output)) {\n            String version = GSON.fromJson(Files.readString(output, StandardCharsets.UTF_8), JsonObject.class)\n                    .get(\"version_latest\").getAsJsonPrimitive().getAsString();\n            if (Objects.equals(version, getVersion().get())) {\n                return;\n            }\n        }\n\n        throw new GradleException(String.format(\"Terracotta config isn't up-to-date! \" +\n                \"You might have just edited the version number in libs.version.toml. \" +\n                \"Please run task %s to resolve the new config.\", getPath()));\n    }\n\n    private record Bundle(\n            @SerializedName(\"hash\") String hash,\n            @SerializedName(\"files\") Map<String, String> files\n    ) {\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/ci/CheckUpdate.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.ci;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonObject;\nimport com.google.gson.reflect.TypeToken;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.logging.Logger;\nimport org.gradle.api.logging.Logging;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.TaskAction;\n\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.BiConsumer;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/// @author Glavo\npublic abstract class CheckUpdate extends DefaultTask {\n    private static final Logger LOGGER = Logging.getLogger(CheckUpdate.class);\n\n    private static final String WORKFLOW_JOB = \"org.jenkinsci.plugins.workflow.job.WorkflowJob\";\n    private static final String WORKFLOW_MULTI_BRANCH_PROJECT = \"org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject\";\n    private static final String FREE_STYLE_PROJECT = \"hudson.model.FreeStyleProject\";\n\n    @Input\n    public abstract Property<String> getUri();\n\n    public CheckUpdate() {\n        getOutputs().upToDateWhen(task -> false);\n    }\n\n    private static URI toURI(String baseUri, String suffix) {\n        return URI.create(baseUri.endsWith(\"/\")\n                ? baseUri + suffix\n                : baseUri + \"/\" + suffix\n        );\n    }\n\n    private static String concatUri(String base, String... others) {\n        var builder = new StringBuilder(base);\n        for (String other : others) {\n            if (builder.charAt(builder.length() - 1) != '/') {\n                builder.append('/');\n            }\n            builder.append(Objects.requireNonNull(other));\n        }\n        return builder.toString();\n    }\n\n    @TaskAction\n    public void run() throws Exception {\n        String uri = getUri().get();\n        URI apiUri = URI.create(concatUri(uri, \"api\", \"json\"));\n        LOGGER.quiet(\"Fetching metadata from {}\", apiUri);\n\n        BuildMetadata buildMetadata;\n\n        try (var helper = new Helper()) {\n            JsonObject body = Objects.requireNonNull(helper.fetch(apiUri, JsonObject.class));\n            String jobType = Objects.requireNonNull(body.getAsJsonPrimitive(\"_class\"), \"Missing _class property\")\n                    .getAsString();\n\n            if (WORKFLOW_MULTI_BRANCH_PROJECT.equals(jobType)) {\n                Pattern namePattern = Pattern.compile(\"release%2F3\\\\.\\\\d+\");\n\n                List<BuildMetadata> metadatas = Objects.requireNonNull(helper.gson.fromJson(body.get(\"jobs\"), new TypeToken<List<SubJobInfo>>() {\n                        }), \"jobs\")\n                        .stream()\n                        .filter(it -> WORKFLOW_JOB.equals(it._class()))\n                        .filter(it -> namePattern.matcher(it.name()).matches())\n                        .filter(it -> !it.color().equals(\"disabled\"))\n                        .map(it -> {\n                            try {\n                                return fetchBuildInfo(helper, it.url);\n                            } catch (Throwable e) {\n                                throw new GradleException(\"Failed to fetch build info from \" + it.url(), e);\n                            }\n                        }).sorted(Comparator.comparing(BuildMetadata::timestamp))\n                        .toList();\n\n                if (metadatas.isEmpty())\n                    throw new GradleException(\"Failed to fetch build metadata from \" + apiUri);\n\n                buildMetadata = metadatas.get(metadatas.size() - 1);\n            } else if (WORKFLOW_JOB.equals(jobType) || FREE_STYLE_PROJECT.equals(jobType)) {\n                buildMetadata = fetchBuildInfo(helper, uri);\n            } else {\n                throw new GradleException(\"Unsupported job type: \" + jobType);\n            }\n        }\n\n        LOGGER.quiet(\"Build metadata found: {}\", buildMetadata);\n\n        String githubEnv = Objects.requireNonNullElse(System.getenv(\"GITHUB_ENV\"), \"\");\n        if (githubEnv.isBlank())\n            LOGGER.warn(\"GITHUB_ENV is not set\");\n\n        try (PrintWriter writer = githubEnv.isBlank()\n                ? null\n                : new PrintWriter(Files.newBufferedWriter(Path.of(githubEnv), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND))) {\n\n            BiConsumer<String, String> addEnv = (name, value) -> {\n                String item = name + \"=\" + value;\n                LOGGER.quiet(item);\n                if (writer != null)\n                    writer.println(item);\n            };\n\n            addEnv.accept(\"HMCL_COMMIT_SHA\", buildMetadata.revision());\n            addEnv.accept(\"HMCL_VERSION\", buildMetadata.version());\n            addEnv.accept(\"HMCL_TAG_NAME\", \"v\" + buildMetadata.version());\n            addEnv.accept(\"HMCL_CI_DOWNLOAD_BASE_URI\", buildMetadata.downloadBaseUri);\n        }\n    }\n\n    private record BuildMetadata(String version, String revision, long timestamp, String downloadBaseUri) {\n    }\n\n    private BuildMetadata fetchBuildInfo(Helper helper, String jobUri) throws IOException, InterruptedException {\n        URI uri = URI.create(concatUri(jobUri, \"lastSuccessfulBuild\", \"api\", \"json\"));\n\n        LOGGER.quiet(\"Fetching build info from {}\", uri);\n\n        BuildInfo buildInfo = Objects.requireNonNull(helper.fetch(uri, BuildInfo.class), \"build info\");\n\n        String revision = Objects.requireNonNullElse(buildInfo.actions(), List.<ActionInfo>of())\n                .stream()\n                .filter(action -> \"hudson.plugins.git.util.BuildData\".equals(action._class()))\n                .map(ActionInfo::lastBuiltRevision)\n                .map(BuiltRevision::SHA1)\n                .findFirst()\n                .orElseThrow(() -> new GradleException(\"Could not find revision\"));\n        if (!revision.matches(\"[0-9a-z]{40}\"))\n            throw new GradleException(\"Invalid revision: \" + revision);\n\n        Pattern fileNamePattern = Pattern.compile(\"HMCL-(?<version>\\\\d+(?:\\\\.\\\\d+)+)\\\\.jar\");\n        ArtifactInfo jarArtifact = Objects.requireNonNullElse(buildInfo.artifacts(), List.<ArtifactInfo>of()).stream()\n                .filter(it -> fileNamePattern.matcher(it.fileName()).matches())\n                .findFirst()\n                .orElseThrow(() -> new GradleException(\"Could not find .jar artifact\"));\n\n        String fileName = jarArtifact.fileName();\n        String relativePath = jarArtifact.relativePath();\n        if (!relativePath.endsWith(\"/\" + fileName)) {\n            throw new GradleException(\"Invalid artifact relative path: \" + jarArtifact);\n        }\n\n        Matcher matcher = fileNamePattern.matcher(fileName);\n        if (!matcher.matches()) {\n            throw new AssertionError(\"Artifact: \" + jarArtifact.fileName());\n        }\n\n        String version = matcher.group(\"version\");\n\n        String downloadBaseUrl = concatUri(buildInfo.url(), \"artifact\",\n                relativePath.substring(0, relativePath.length() - fileName.length() - 1));\n\n        return new BuildMetadata(version, revision, buildInfo.timestamp(), downloadBaseUrl);\n    }\n\n    private static final class Helper implements AutoCloseable {\n        private final HttpClient client = HttpClient.newBuilder()\n                .followRedirects(HttpClient.Redirect.ALWAYS)\n                .build();\n        private final Gson gson = new Gson();\n\n        private <T> T fetch(URI uri, Class<T> type) throws IOException, InterruptedException {\n            HttpResponse<String> response = client.send(HttpRequest.newBuilder(uri).build(), HttpResponse.BodyHandlers.ofString());\n            if (response.statusCode() / 100 != 2) {\n                throw new IOException(\"Bad status code \" + response.statusCode() + \" for \" + uri);\n            }\n\n            return gson.fromJson(response.body(), type);\n        }\n\n        @Override\n        public void close() throws Exception {\n            // HttpClient implements AutoCloseable since Java 21\n            if (((Object) client) instanceof AutoCloseable closeable) {\n                closeable.close();\n            }\n        }\n    }\n\n    private record SubJobInfo(String _class, String name, String url, String color) {\n\n    }\n\n    private record BuildInfo(String url,\n                             long number,\n                             long timestamp,\n                             List<ArtifactInfo> artifacts,\n                             List<ActionInfo> actions\n    ) {\n    }\n\n    record ArtifactInfo(String fileName, String relativePath) {\n    }\n\n    record ActionInfo(String _class, BuiltRevision lastBuiltRevision) {\n    }\n\n    record BuiltRevision(String SHA1) {\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/ci/GitHubActionUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.ci;\n\nimport java.util.Objects;\n\n/// @author Glavo\npublic final class GitHubActionUtils {\n    private static final String OFFICIAL_ORGANIZATION = \"HMCL-dev\";\n\n    public static final boolean IS_ON_OFFICIAL_REPO =\n            OFFICIAL_ORGANIZATION.equalsIgnoreCase(System.getenv(\"GITHUB_REPOSITORY_OWNER\"))\n                    && Objects.requireNonNullElse(System.getenv(\"GITHUB_BASE_REF\"), \"\").isBlank();\n\n    private GitHubActionUtils() {\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/ci/JenkinsUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.ci;\n\n/// @author Glavo\npublic final class JenkinsUtils {\n\n    public static final boolean IS_ON_CI = \"1\".equals(System.getenv(\"HMCL_CI\"));\n\n    private JenkinsUtils() {\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/docs/Document.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.docs;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/// @author Glavo\npublic record Document(DocumentFileTree directory,\n                       Path file,\n                       String name, DocumentLocale locale,\n                       List<Item> items) {\n\n    private static final Pattern MACRO_BEGIN = Pattern.compile(\n            \"<!-- #BEGIN (?<name>\\\\w+) -->\"\n    );\n\n    private static final Pattern MACRO_PROPERTY_LINE = Pattern.compile(\n            \"<!-- #PROPERTY (?<name>\\\\w+)=(?<value>.*) -->\"\n    );\n\n    private static String parsePropertyValue(String value) {\n        int i = 0;\n        while (i < value.length()) {\n            char ch = value.charAt(i);\n            if (ch == '\\\\')\n                break;\n            i++;\n        }\n\n        if (i == value.length())\n            return value;\n\n        StringBuilder builder = new StringBuilder(value.length());\n        builder.append(value, 0, i);\n        for (; i < value.length(); i++) {\n            char ch = value.charAt(i);\n            if (ch == '\\\\' && i < value.length() - 1) {\n                char next = value.charAt(++i);\n                switch (next) {\n                    case 'n' -> builder.append('\\n');\n                    case 'r' -> builder.append('\\r');\n                    case '\\\\' -> builder.append('\\\\');\n                    default -> builder.append(next);\n                }\n            } else {\n                builder.append(ch);\n            }\n        }\n        return builder.toString();\n    }\n\n    static void writePropertyValue(StringBuilder builder, String value) {\n        for (int i = 0; i < value.length(); i++) {\n            char ch = value.charAt(i);\n\n            switch (ch) {\n                case '\\\\' -> builder.append(\"\\\\\\\\\");\n                case '\\r' -> builder.append(\"\\\\r\");\n                case '\\n' -> builder.append(\"\\\\n\");\n                default -> builder.append(ch);\n            }\n        }\n    }\n\n    public static Document load(DocumentFileTree directory, Path file, String name, DocumentLocale locale) throws IOException {\n        var items = new ArrayList<Item>();\n        try (var reader = Files.newBufferedReader(file)) {\n            String line;\n\n            while ((line = reader.readLine()) != null) {\n                if (!line.startsWith(\"<!-- #\")) {\n                    items.add(new Line(line));\n                } else {\n                    Matcher matcher = MACRO_BEGIN.matcher(line);\n                    if (!matcher.matches())\n                        throw new IOException(\"Invalid macro begin line: \" + line);\n\n                    String macroName = matcher.group(\"name\");\n                    String endLine = \"<!-- #END \" + macroName + \" -->\";\n                    var lines = new ArrayList<String>();\n                    while (true) {\n                        line = reader.readLine();\n\n                        if (line == null)\n                            throw new IOException(\"Missing end line for macro: \" + macroName);\n                        else if (line.equals(endLine)) {\n                            break;\n                        } else {\n                            lines.add(line);\n                        }\n                    }\n\n                    var properties = new LinkedHashMap<String, List<String>>();\n                    int propertiesCount = 0;\n\n                    // Handle properties\n                    for (String macroBodyLine : lines) {\n                        if (!macroBodyLine.startsWith(\"<!-- #\"))\n                            break;\n\n                        Matcher propertyMatcher = MACRO_PROPERTY_LINE.matcher(macroBodyLine);\n                        if (propertyMatcher.matches()) {\n                            String propertyName = propertyMatcher.group(\"name\");\n                            String propertyValue = parsePropertyValue(propertyMatcher.group(\"value\"));\n\n                            properties.computeIfAbsent(propertyName, k -> new ArrayList<>(1))\n                                    .add(propertyValue);\n                            propertiesCount++;\n                        } else {\n                            throw new IOException(\"Invalid macro property line: \" + macroBodyLine);\n                        }\n                    }\n\n                    if (propertiesCount > 0)\n                        lines.subList(0, propertiesCount).clear();\n\n                    items.add(new MacroBlock(macroName,\n                            Collections.unmodifiableMap(properties),\n                            Collections.unmodifiableList(lines)));\n                }\n            }\n        }\n        return new Document(directory, file, name, locale, items);\n    }\n\n    public sealed interface Item {\n    }\n\n    public record MacroBlock(String name, Map<String, List<String>> properties,\n                             List<String> contentLines) implements Item {\n    }\n\n    public record Line(String content) implements Item {\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/docs/DocumentFileTree.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.docs;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.TreeMap;\n\n/// @author Glavo\npublic final class DocumentFileTree {\n\n    public static DocumentFileTree load(Path dir) throws IOException {\n        Path documentsDir = dir.toRealPath();\n        DocumentFileTree rootTree = new DocumentFileTree();\n\n        Files.walkFileTree(documentsDir, new SimpleFileVisitor<>() {\n            @Override\n            public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {\n                String fileName = file.getFileName().toString();\n                if (fileName.endsWith(\".md\")) {\n                    DocumentFileTree tree = rootTree.getFileTree(documentsDir.relativize(file.getParent()));\n                    if (tree == null)\n                        throw new AssertionError();\n\n                    var result = DocumentLocale.parseFileName(fileName.substring(0, fileName.length() - \".md\".length()));\n                    tree.getFiles().computeIfAbsent(result.name(), name -> new LocalizedDocument(tree, name))\n                            .getDocuments()\n                            .put(result.locale(), Document.load(tree, file, result.name(), result.locale()));\n                }\n                return FileVisitResult.CONTINUE;\n            }\n        });\n\n        return rootTree;\n    }\n\n    private final @Nullable DocumentFileTree parent;\n    private final TreeMap<String, DocumentFileTree> children = new TreeMap<>();\n    private final TreeMap<String, LocalizedDocument> files = new TreeMap<>();\n\n    public DocumentFileTree() {\n        this(null);\n    }\n\n    public DocumentFileTree(@Nullable DocumentFileTree parent) {\n        this.parent = parent;\n    }\n\n    @Nullable DocumentFileTree getFileTree(Path relativePath) {\n        if (relativePath.isAbsolute())\n            throw new IllegalArgumentException(relativePath + \" is absolute\");\n\n        if (relativePath.getNameCount() == 0)\n            throw new IllegalArgumentException(relativePath + \" is empty\");\n\n        if (relativePath.getNameCount() == 1 && relativePath.getName(0).toString().isEmpty())\n            return this;\n\n        DocumentFileTree current = this;\n        for (int i = 0; i < relativePath.getNameCount(); i++) {\n            String name = relativePath.getName(i).toString();\n            if (name.isEmpty())\n                throw new IllegalStateException(name + \" is empty\");\n            else if (name.equals(\".\"))\n                continue;\n            else if (name.equals(\"..\")) {\n                current = current.parent;\n                if (current == null)\n                    return null;\n            } else {\n                DocumentFileTree finalCurrent = current;\n                current = current.children.computeIfAbsent(name, ignored -> new DocumentFileTree(finalCurrent));\n            }\n        }\n\n        return current;\n    }\n\n    public @Nullable DocumentFileTree getParent() {\n        return parent;\n    }\n\n    public TreeMap<String, DocumentFileTree> getChildren() {\n        return children;\n    }\n\n    public TreeMap<String, LocalizedDocument> getFiles() {\n        return files;\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/docs/DocumentLocale.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.docs;\n\nimport java.util.List;\nimport java.util.Locale;\n\n/// @author Glavo\npublic enum DocumentLocale {\n    ENGLISH(Locale.ENGLISH, \"\") {\n        @Override\n        public String getSubLanguageDisplayName() {\n            return \"Standard\";\n        }\n\n        @Override\n        public List<DocumentLocale> getCandidates() {\n            return List.of(ENGLISH);\n        }\n    },\n    ENGLISH_UPSIDE_DOWN(Locale.forLanguageTag(\"en-Qabs\"), \"en-Qabs\") {\n        @Override\n        public String getSubLanguageDisplayName() {\n            return \"uʍoᗡ ǝpᴉsd∩\";\n        }\n    },\n    SIMPLIFIED_CHINESE(Locale.forLanguageTag(\"zh-Hans\"), \"zh\"),\n    TRADITIONAL_CHINESE(\"zh-Hant\") {\n        @Override\n        public List<DocumentLocale> getCandidates() {\n            return List.of(TRADITIONAL_CHINESE, SIMPLIFIED_CHINESE, ENGLISH);\n        }\n    },\n    WENYAN(\"lzh\") {\n        @Override\n        public String getLanguageDisplayName() {\n            return TRADITIONAL_CHINESE.getLanguageDisplayName();\n        }\n\n        @Override\n        public String getSubLanguageDisplayName() {\n            return \"文言\";\n        }\n\n        @Override\n        public List<DocumentLocale> getCandidates() {\n            return List.of(WENYAN, TRADITIONAL_CHINESE, SIMPLIFIED_CHINESE, ENGLISH);\n        }\n    },\n    JAPANESE(\"ja\"),\n    SPANISH(\"es\"),\n    RUSSIAN(\"ru\"),\n    UKRAINIAN(\"uk\"),\n    ;\n\n    public record LocaleAndName(DocumentLocale locale, String name) {\n    }\n\n    public static LocaleAndName parseFileName(String fileNameWithoutExtension) {\n        for (DocumentLocale locale : values()) {\n            String suffix = locale.getFileNameSuffix();\n            if (suffix.isEmpty())\n                continue;\n\n            if (fileNameWithoutExtension.endsWith(suffix))\n                return new LocaleAndName(locale, fileNameWithoutExtension.substring(0, fileNameWithoutExtension.length() - locale.getFileNameSuffix().length()));\n        }\n        return new LocaleAndName(ENGLISH, fileNameWithoutExtension);\n    }\n\n    private final Locale locale;\n    private final String languageTag;\n    private final String fileNameSuffix;\n\n    DocumentLocale(String languageTag) {\n        this(Locale.forLanguageTag(languageTag), languageTag);\n    }\n\n    DocumentLocale(Locale locale, String languageTag) {\n        this.locale = locale;\n        this.languageTag = languageTag;\n        this.fileNameSuffix = languageTag.isEmpty() ? \"\" : \"_\" + languageTag.replace('-', '_');\n    }\n\n    public String getLanguageDisplayName() {\n        return locale.getDisplayLanguage(locale);\n    }\n\n    public String getSubLanguageDisplayName() {\n        boolean hasScript = !locale.getScript().isEmpty();\n        boolean hasRegion = !locale.getCountry().isEmpty();\n\n        if (hasScript && hasRegion)\n            throw new AssertionError(\"Unsupported locale: \" + locale);\n\n        if (hasScript)\n            return locale.getDisplayScript(locale);\n        if (hasRegion)\n            return locale.getDisplayCountry(locale);\n        return \"\";\n    }\n\n    public Locale getLocale() {\n        return locale;\n    }\n\n    public String getFileNameSuffix() {\n        return fileNameSuffix;\n    }\n\n    public List<DocumentLocale> getCandidates() {\n        return List.of(this, ENGLISH);\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/docs/LocalizedDocument.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.docs;\n\nimport java.util.EnumMap;\n\n/// @author Glavo\npublic final class LocalizedDocument {\n    private final DocumentFileTree directory;\n    private final String name;\n    private final EnumMap<DocumentLocale, Document> documents = new EnumMap<>(DocumentLocale.class);\n\n    public LocalizedDocument(DocumentFileTree directory, String name) {\n        this.directory = directory;\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public DocumentFileTree getDirectory() {\n        return directory;\n    }\n\n    public EnumMap<DocumentLocale, Document> getDocuments() {\n        return documents;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return obj instanceof LocalizedDocument that\n                && this.documents.equals(that.documents);\n    }\n\n    @Override\n    public int hashCode() {\n        return documents.hashCode();\n    }\n\n    @Override\n    public String toString() {\n        return \"LocalizedDocument[\" +\n                \"files=\" + documents + ']';\n    }\n\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/docs/MacroProcessor.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.docs;\n\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.regex.Pattern;\n\n/// Macro processor for automatically updating documentation.\n///\n/// Users can use the macro processor in `.md` documents within the `docs` folder and its subfolders.\n/// The parts to be processed should be wrapped with `<!-- #BEGIN MACRO_NAME -->` and `<!-- #END MACRO_NAME -->` lines.\n///\n/// For example, if you create a document `FOO.md` and translate it into Simplified Chinese, Traditional Chinese, and Japanese,\n/// you can add the following content in these files to create links to other language versions:\n///\n/// ```markdown\n/// <!-- #BEGIN LANGUAGE_SWITCHER -->\n/// <!-- #END LANGUAGE_SWITCHER -->\n/// ```\n///\n/// After running `./gradlew updateDocuments`, the macro processor will automatically update the content between these two lines:\n///\n/// ```\n/// <!-- #BEGIN LANGUAGE_SWITCHER -->\n/// **English** | 中文 ([简体](FOO_zh.md), [繁體](FOO_zh_Hant.md)) | [日本語](FOO_ja.md)\n/// <!-- #END LANGUAGE_SWITCHER -->\n/// ```\n///\n/// @author Glavo\npublic enum MacroProcessor {\n    /// Does not process the content in any way.\n    ///\n    /// Supported properties:\n    ///\n    /// - `NAME`: The name of this block (used by other macros).\n    /// - `PROCESS_LINK`: If set to `FALSE`, document links in the content will not be automatically updated.\n    BLOCK {\n        @Override\n        public void apply(Document document, Document.MacroBlock macroBlock, StringBuilder outputBuilder) throws IOException {\n            var mutableProperties = new LinkedHashMap<>(macroBlock.properties());\n            MacroProcessor.removeSingleProperty(mutableProperties, \"NAME\");\n            boolean processLink = !\"FALSE\".equalsIgnoreCase(MacroProcessor.removeSingleProperty(mutableProperties, \"PROCESS_LINK\"));\n\n            if (!mutableProperties.isEmpty())\n                throw new IllegalArgumentException(\"Unsupported properties: \" + mutableProperties.keySet());\n\n            MacroProcessor.writeBegin(outputBuilder, macroBlock);\n            MacroProcessor.writeProperties(outputBuilder, macroBlock);\n            for (String line : macroBlock.contentLines()) {\n                if (processLink)\n                    MacroProcessor.processLine(outputBuilder, line, document);\n                else\n                    outputBuilder.append(line).append('\\n');\n            }\n            MacroProcessor.writeEnd(outputBuilder, macroBlock);\n        }\n    },\n\n    /// Used to automatically generate links to other language versions of the current document.\n    ///\n    /// Does not support any properties.\n    LANGUAGE_SWITCHER {\n        private static <T> boolean containsIdentity(List<T> list, T element) {\n            for (T t : list) {\n                if (t == element)\n                    return true;\n            }\n\n            return false;\n        }\n\n        @Override\n        public void apply(Document document,\n                          Document.MacroBlock macroBlock,\n                          StringBuilder outputBuilder) throws IOException {\n            LocalizedDocument localized = document.directory().getFiles().get(document.name());\n            if (localized == null || localized.getDocuments().isEmpty())\n                throw new AssertionError(\"Document \" + document.name() + \" does not exist\");\n\n            MacroProcessor.writeBegin(outputBuilder, macroBlock);\n            if (localized.getDocuments().size() > 1) {\n                var languageToDocs = new LinkedHashMap<String, List<Document>>();\n                for (DocumentLocale locale : DocumentLocale.values()) {\n                    Document targetDoc = localized.getDocuments().get(locale);\n                    if (targetDoc != null) {\n                        languageToDocs.computeIfAbsent(locale.getLanguageDisplayName(), name -> new ArrayList<>(1))\n                                .add(targetDoc);\n                    }\n                }\n\n                boolean firstLanguage = true;\n\n                for (var entry : languageToDocs.entrySet()) {\n                    if (firstLanguage)\n                        firstLanguage = false;\n                    else\n                        outputBuilder.append(\" | \");\n\n                    String languageName = entry.getKey();\n                    List<Document> targetDocs = entry.getValue();\n\n                    boolean containsCurrent = containsIdentity(targetDocs, document);\n                    if (targetDocs.size() == 1) {\n                        if (containsCurrent)\n                            outputBuilder.append(\"**\").append(languageName).append(\"**\");\n                        else\n                            outputBuilder.append(\"[\").append(languageName).append(\"](\").append(targetDocs.get(0).file().getFileName()).append(\")\");\n                    } else {\n                        if (containsCurrent)\n                            outputBuilder.append(\"**\").append(languageName).append(\"**\");\n                        else\n                            outputBuilder.append(languageName);\n\n                        outputBuilder.append(\" (\");\n\n                        boolean isFirst = true;\n                        for (Document targetDoc : targetDocs) {\n                            if (isFirst)\n                                isFirst = false;\n                            else\n                                outputBuilder.append(\", \");\n\n                            String subLanguage = targetDoc.locale().getSubLanguageDisplayName();\n\n                            if (targetDoc == document) {\n                                outputBuilder.append(\"**\").append(subLanguage).append(\"**\");\n                            } else {\n                                outputBuilder.append('[').append(subLanguage).append(\"](\").append(targetDoc.file().getFileName()).append(\")\");\n                            }\n                        }\n\n                        outputBuilder.append(\")\");\n                    }\n                }\n\n                outputBuilder.append('\\n');\n            }\n            MacroProcessor.writeEnd(outputBuilder, macroBlock);\n        }\n    },\n\n    /// Copy the block with the specified name from the English version of the current document.\n    ///\n    /// Supported properties:\n    ///\n    /// - `NAME` (required): Specifies the block to be copied.\n    /// - `REPLACE` (repeatable): Used to replace specified text. Accepts a list containing two strings. The first string is a regular expression for matching content; the second string is the replacement target.\n    /// - `PROCESS_LINK`: If set to `FALSE`, document links in the content will not be automatically updated.\n    COPY {\n        private record Replace(Pattern pattern, String replacement) {\n        }\n\n        private static IllegalArgumentException illegalReplace(String value) {\n            return new IllegalArgumentException(\"Illegal replacement pattern: \" + value);\n        }\n\n        private static Replace parseReplace(String value) {\n            List<String> list = MacroProcessor.parseStringList(value);\n            if (list.size() != 2)\n                throw illegalReplace(value);\n\n            return new Replace(Pattern.compile(list.get(0)), list.get(1));\n        }\n\n        @Override\n        public void apply(Document document, Document.MacroBlock macroBlock, StringBuilder outputBuilder) throws IOException {\n            var mutableProperties = new LinkedHashMap<>(macroBlock.properties());\n            String blockName = MacroProcessor.removeSingleProperty(mutableProperties, \"NAME\");\n            if (blockName == null)\n                throw new IllegalArgumentException(\"Missing property: NAME\");\n\n            List<Replace> replaces = Objects.requireNonNullElse(mutableProperties.remove(\"REPLACE\"), List.<String>of())\n                    .stream()\n                    .map(it -> parseReplace(it))\n                    .toList();\n\n            boolean processLink = !\"FALSE\".equalsIgnoreCase(MacroProcessor.removeSingleProperty(mutableProperties, \"PROCESS_LINK\"));\n\n            if (!mutableProperties.isEmpty())\n                throw new IllegalArgumentException(\"Unsupported properties: \" + mutableProperties.keySet());\n\n            LocalizedDocument localizedDocument = document.directory().getFiles().get(document.name());\n            Document fromDocument;\n            if (localizedDocument == null || (fromDocument = localizedDocument.getDocuments().get(DocumentLocale.ENGLISH)) == null)\n                throw new IOException(\"Document \" + document.name() + \" for english does not exist\");\n\n            List<String> nameList = List.of(blockName);\n\n            var fromBlock = (Document.MacroBlock) fromDocument.items().stream()\n                    .filter(it -> it instanceof Document.MacroBlock macro\n                            && macro.name().equals(BLOCK.name())\n                            && nameList.equals(macro.properties().get(\"NAME\"))\n                    )\n                    .findFirst()\n                    .orElseThrow(() -> new IOException(\"Cannot find the block \\\"\" + blockName + \"\\\" in \" + fromDocument.file()));\n\n            MacroProcessor.writeBegin(outputBuilder, macroBlock);\n            MacroProcessor.writeProperties(outputBuilder, macroBlock);\n            for (String line : fromBlock.contentLines()) {\n                for (Replace replace : replaces) {\n                    line = replace.pattern.matcher(line).replaceAll(replace.replacement());\n                }\n                if (processLink)\n                    processLine(outputBuilder, line, document);\n                else\n                    outputBuilder.append(line).append('\\n');\n            }\n            MacroProcessor.writeEnd(outputBuilder, macroBlock);\n        }\n    },\n    ;\n\n    private static String removeSingleProperty(Map<String, List<String>> properties, String name) {\n        List<String> values = properties.remove(name);\n        if (values == null || values.isEmpty())\n            return null;\n\n        if (values.size() != 1)\n            throw new IllegalArgumentException(\"Unexpected number of property \" + name + \": \" + values.size());\n\n        return values.get(0);\n    }\n\n    private static List<String> parseStringList(String str) {\n        if (str.isBlank()) {\n            return new ArrayList<>();\n        }\n\n        // Split the string with ' and space cleverly.\n        ArrayList<String> parts = new ArrayList<>(2);\n\n        boolean hasValue = false;\n        StringBuilder current = new StringBuilder(str.length());\n        for (int i = 0; i < str.length(); ) {\n            char c = str.charAt(i);\n            if (c == '\\'' || c == '\"') {\n                hasValue = true;\n                int end = str.indexOf(c, i + 1);\n                if (end < 0) {\n                    end = str.length();\n                }\n                current.append(str, i + 1, end);\n                i = end + 1;\n\n            } else if (c == ' ' || c == '\\t') {\n                if (hasValue) {\n                    parts.add(current.toString());\n                    current.setLength(0);\n                    hasValue = false;\n                }\n                i++;\n            } else {\n                hasValue = true;\n                current.append(c);\n                i++;\n            }\n        }\n        if (hasValue)\n            parts.add(current.toString());\n\n        return parts;\n    }\n\n    private static final Pattern LINK_PATTERN = Pattern.compile(\n            \"(?<=]\\\\()[a-zA-Z0-9_\\\\-./]+\\\\.md(?=\\\\))\"\n    );\n\n    static void processLine(StringBuilder outputBuilder, String line, Document document) {\n        outputBuilder.append(LINK_PATTERN.matcher(line).replaceAll(matchResult -> {\n            String rawLink = matchResult.group();\n            String[] splitPath = rawLink.split(\"/\");\n\n            if (splitPath.length == 0)\n                return rawLink;\n\n            String fileName = splitPath[splitPath.length - 1];\n            if (!fileName.endsWith(\".md\"))\n                return rawLink;\n\n            DocumentFileTree current = document.directory();\n            for (int i = 0; i < splitPath.length - 1; i++) {\n                String name = splitPath[i];\n                switch (name) {\n                    case \"\" -> {\n                        return rawLink;\n                    }\n                    case \".\" -> {\n                        continue;\n                    }\n                    case \"..\" -> {\n                        current = current.getParent();\n                        if (current == null)\n                            return rawLink;\n                    }\n                    default -> {\n                        current = current.getChildren().get(name);\n                        if (current == null)\n                            return rawLink;\n                    }\n                }\n            }\n\n            DocumentLocale.LocaleAndName currentLocaleAndName = DocumentLocale.parseFileName(fileName.substring(0, fileName.length() - \".md\".length()));\n            LocalizedDocument localizedDocument = current.getFiles().get(currentLocaleAndName.name());\n            if (localizedDocument != null) {\n                List<DocumentLocale> candidateLocales = document.locale().getCandidates();\n                for (DocumentLocale candidateLocale : candidateLocales) {\n                    if (candidateLocale == currentLocaleAndName.locale())\n                        return rawLink;\n\n                    Document targetDoc = localizedDocument.getDocuments().get(candidateLocale);\n                    if (targetDoc != null) {\n                        splitPath[splitPath.length - 1] = targetDoc.file().getFileName().toString();\n                        return String.join(\"/\", splitPath);\n                    }\n                }\n            }\n\n            return rawLink;\n        })).append('\\n');\n    }\n\n    private static void writeBegin(StringBuilder builder, Document.MacroBlock macroBlock) throws IOException {\n        builder.append(\"<!-- #BEGIN \");\n        builder.append(macroBlock.name());\n        builder.append(\" -->\\n\");\n    }\n\n    private static void writeEnd(StringBuilder builder, Document.MacroBlock macroBlock) throws IOException {\n        builder.append(\"<!-- #END \");\n        builder.append(macroBlock.name());\n        builder.append(\" -->\\n\");\n    }\n\n    private static void writeProperties(StringBuilder builder, Document.MacroBlock macroBlock) throws IOException {\n        macroBlock.properties().forEach((key, values) -> {\n            for (String value : values) {\n                builder.append(\"<!-- #PROPERTY \").append(key).append('=');\n                Document.writePropertyValue(builder, value);\n                builder.append(\" -->\\n\");\n            }\n        });\n    }\n\n    public abstract void apply(Document document,\n                               Document.MacroBlock macroBlock,\n                               StringBuilder outputBuilder) throws IOException;\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/docs/UpdateDocuments.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.docs;\n\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.file.DirectoryProperty;\nimport org.gradle.api.tasks.InputDirectory;\nimport org.gradle.api.tasks.TaskAction;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\n/// @author Glavo\npublic abstract class UpdateDocuments extends DefaultTask {\n\n    @InputDirectory\n    public abstract DirectoryProperty getDocumentsDir();\n\n    // ---\n\n    private void updateDocument(Document document) throws IOException {\n        StringBuilder outputBuilder = new StringBuilder(8192);\n\n        for (Document.Item item : document.items()) {\n            if (item instanceof Document.Line line) {\n                MacroProcessor.processLine(outputBuilder, line.content(), document);\n            } else if (item instanceof Document.MacroBlock macro) {\n                var processor = MacroProcessor.valueOf(macro.name());\n                processor.apply(document, macro, outputBuilder);\n            } else\n                throw new IllegalArgumentException(\"Unknown item type: \" + item.getClass());\n        }\n\n        Files.writeString(document.file(), outputBuilder.toString());\n    }\n\n    private void processDocuments(DocumentFileTree tree) throws IOException {\n        for (LocalizedDocument localizedDocument : tree.getFiles().values()) {\n            for (Document document : localizedDocument.getDocuments().values()) {\n                updateDocument(document);\n            }\n        }\n\n        for (DocumentFileTree subTree : tree.getChildren().values()) {\n            processDocuments(subTree);\n        }\n    }\n\n    @TaskAction\n    public void run() throws IOException {\n        Path rootDir = getDocumentsDir().get().getAsFile().toPath();\n        processDocuments(DocumentFileTree.load(rootDir));\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/javafx/JavaFXPlatform.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.javafx;\n\nimport java.util.*;\n\n/**\n * @author Glavo\n */\npublic final class JavaFXPlatform {\n\n    public static final String LEGACY_JAVAFX_VERSION = \"19.0.2.1\";\n    public static final String CLASSIC_JAVAFX_VERSION = \"21.0.8\";\n    public static final String MODERN_JAVAFX_VERSION = \"25\";\n\n    private static final String OFFICIAL_GROUP_ID = \"org.openjfx\";\n    private static final String GLAVO_GROUP_ID = \"org.glavo.hmcl.openjfx\";\n\n    public static final Map<String, JavaFXPlatform> ALL = new LinkedHashMap<>();\n\n    static {\n        Map<JavaFXVersionType, String> legacyVersions = Map.of(JavaFXVersionType.CLASSIC, LEGACY_JAVAFX_VERSION);\n\n        // Windows\n        ALL.put(\"windows-x86\", new JavaFXPlatform(\"win-x86\", legacyVersions));\n        ALL.put(\"windows-x86_64\", new JavaFXPlatform(\"win\"));\n        ALL.put(\"windows-arm64\", new JavaFXPlatform(\"win\", GLAVO_GROUP_ID, \"18.0.2+1-arm64\"));\n\n        // macOS\n        ALL.put(\"macos-x86_64\", new JavaFXPlatform(\"mac\"));\n        ALL.put(\"macos-arm64\", new JavaFXPlatform(\"mac-aarch64\"));\n\n        // Linux\n        ALL.put(\"linux-x86_64\", new JavaFXPlatform(\"linux\"));\n        ALL.put(\"linux-arm32\", new JavaFXPlatform(\"linux-arm32-monocle\", legacyVersions));\n        ALL.put(\"linux-arm64\", new JavaFXPlatform(\"linux-aarch64\", Map.of(\n                JavaFXVersionType.CLASSIC, \"21.0.1\",\n                JavaFXVersionType.MODERN, MODERN_JAVAFX_VERSION\n        )));\n        ALL.put(\"linux-loongarch64\", new JavaFXPlatform(\"linux\", GLAVO_GROUP_ID, \"17.0.8-loongarch64\"));\n        ALL.put(\"linux-loongarch64_ow\", new JavaFXPlatform(\"linux\", GLAVO_GROUP_ID, \"19-ea+10-loongson64\"));\n        ALL.put(\"linux-riscv64\", new JavaFXPlatform(\"linux\", GLAVO_GROUP_ID, \"19.0.2.1-riscv64\"));\n\n        // FreeBSD\n        ALL.put(\"freebsd-x86_64\", new JavaFXPlatform(\"freebsd\", GLAVO_GROUP_ID, \"14.0.2.1-freebsd\"));\n    }\n\n    private final String classifier;\n    private final String groupId;\n    private final SortedMap<JavaFXVersionType, String> versions;\n\n    public JavaFXPlatform(String classifier) {\n        this(classifier, OFFICIAL_GROUP_ID, Map.of(\n                JavaFXVersionType.CLASSIC, CLASSIC_JAVAFX_VERSION,\n                JavaFXVersionType.MODERN, MODERN_JAVAFX_VERSION\n        ));\n    }\n\n    public JavaFXPlatform(String classifier, Map<JavaFXVersionType, String> versions) {\n        this(classifier, OFFICIAL_GROUP_ID, versions);\n    }\n\n    public JavaFXPlatform(String classifier, String groupId, String version) {\n        this(classifier, groupId, Map.of(JavaFXVersionType.CLASSIC, version));\n    }\n\n    public JavaFXPlatform(String classifier, String groupId, Map<JavaFXVersionType, String> versions) {\n        this.classifier = classifier;\n        this.groupId = groupId;\n        this.versions = Collections.unmodifiableSortedMap(new TreeMap<>(versions));\n    }\n\n    public String getClassifier() {\n        return classifier;\n    }\n\n    public String getGroupId() {\n        return groupId;\n    }\n\n    public SortedMap<JavaFXVersionType, String> getVersions() {\n        return versions;\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/javafx/JavaFXUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.javafx;\n\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonObject;\nimport com.sun.jna.Platform;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.Project;\nimport org.gradle.api.Task;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UncheckedIOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\n/**\n * @author Glavo\n */\npublic final class JavaFXUtils {\n    public static final String[] MODULES = {\"base\", \"graphics\", \"controls\"};\n\n    private static void addDependencies(Project rootProject) {\n        try {\n            Class.forName(\"javafx.application.Application\", false, JavaFXUtils.class.getClassLoader());\n            return;\n        } catch (Throwable ignored) {\n        }\n\n        String os;\n        if (Platform.isWindows())\n            os = \"windows\";\n        else if (Platform.isMac())\n            os = \"macos\";\n        else if (Platform.isLinux())\n            os = \"linux\";\n        else if (Platform.isFreeBSD())\n            os = \"freebsd\";\n        else\n            return;\n\n        String arch;\n        if (Platform.isIntel())\n            arch = Platform.is64Bit() ? \"x86_64\" : \"x86\";\n        else if (Platform.isARM())\n            arch = Platform.is64Bit() ? \"arm64\" : \"arm32\";\n        else if (Platform.isLoongArch() && Platform.is64Bit())\n            arch = \"loongarch64\";\n        else if (Platform.isRISCV())\n            arch = \"riscv64\";\n        else\n            return;\n\n        JavaFXPlatform platform = JavaFXPlatform.ALL.get(os + \"-\" + arch);\n        if (platform != null) {\n            int featureVersion = Runtime.version().feature();\n\n            String version;\n            if (featureVersion >= 23)\n                version = platform.getVersions().getOrDefault(JavaFXVersionType.MODERN, platform.getVersions().get(JavaFXVersionType.CLASSIC));\n            else\n                version = platform.getVersions().get(JavaFXVersionType.CLASSIC);\n\n            if (version != null) {\n                rootProject.subprojects(project -> {\n                    for (String module : MODULES) {\n                        String dependency = String.format(\"%s:javafx-%s:%s:%s\", platform.getGroupId(), module, version, platform.getClassifier());\n                        project.getDependencies().add(\"compileOnly\", dependency);\n                        project.getDependencies().add(\"testImplementation\", dependency);\n                    }\n                });\n            }\n        }\n    }\n\n    private static void generateOpenJFXDependencies(Task task) {\n        JsonObject dependenciesJson = new JsonObject();\n        JavaFXPlatform.ALL.forEach((name, platform) -> {\n            JsonObject platformJson = new JsonObject();\n            platform.getVersions().forEach((versionType, version) -> {\n                JsonArray modulesJson = new JsonArray();\n\n                for (String module : MODULES) {\n                    JsonObject moduleJson = new JsonObject();\n                    moduleJson.addProperty(\"module\", \"javafx.\" + module);\n                    moduleJson.addProperty(\"groupId\", platform.getGroupId());\n                    moduleJson.addProperty(\"artifactId\", \"javafx-\" + module);\n                    moduleJson.addProperty(\"version\", version);\n                    moduleJson.addProperty(\"classifier\", platform.getClassifier());\n\n                    String shaUrl = String.format(\"https://repo1.maven.org/maven2/%s/javafx-%s/%s/javafx-%s-%s-%s.jar.sha1\",\n                            platform.getGroupId().replace('.', '/'),\n                            module, version,\n                            module, version, platform.getClassifier()\n                    );\n                    task.getLogger().info(\"Fetching {}\", shaUrl);\n                    try (InputStream stream = new URI(shaUrl).toURL().openStream()) {\n                        moduleJson.addProperty(\"sha1\", new String(stream.readAllBytes(), StandardCharsets.UTF_8).trim());\n                    } catch (IOException | URISyntaxException e) {\n                        throw new GradleException(\"Failed to fetch sha from \" + shaUrl, e);\n                    }\n\n                    modulesJson.add(moduleJson);\n                }\n\n                platformJson.add(versionType.getName(), modulesJson);\n            });\n            dependenciesJson.add(name, platformJson);\n        });\n\n        Path outputFile = task.getProject().getRootProject().file(\"HMCL/src/main/resources/assets/openjfx-dependencies.json\").toPath().toAbsolutePath().normalize();\n        try {\n            Files.createDirectories(outputFile.getParent());\n            Files.writeString(outputFile, new GsonBuilder().setPrettyPrinting().create().toJson(dependenciesJson));\n        } catch (IOException e) {\n            throw new UncheckedIOException(e);\n        }\n    }\n\n    private static void preTouchOpenJFXDependencies(Task task) {\n        String mirrorRepo = \"https://mirrors.cloud.tencent.com/nexus/repository/maven-public\";\n\n        JavaFXPlatform.ALL.forEach((name, platform) -> {\n            platform.getVersions().forEach((versionType, version) -> {\n                for (String module : MODULES) {\n                    String jarUrl = String.format(\"%s/%s/javafx-%s/%s/javafx-%s-%s-%s.jar\",\n                            mirrorRepo,\n                            platform.getGroupId().replace('.', '/'),\n                            module, version,\n                            module, version, platform.getClassifier()\n                    );\n                    task.getLogger().info(\"Fetching {}\", jarUrl);\n                    try (InputStream stream = new URI(jarUrl).toURL().openStream()) {\n                        stream.readNBytes(128);\n                    } catch (IOException ignored) {\n                    } catch (URISyntaxException e) {\n                        throw new GradleException(\"Failed to fetch jar from \" + jarUrl, e);\n                    }\n                }\n            });\n        });\n    }\n\n    public static void register(Project rootProject) {\n        addDependencies(rootProject);\n\n        rootProject.getTasks().register(\"generateOpenJFXDependencies\", task -> task.doLast(JavaFXUtils::generateOpenJFXDependencies));\n        rootProject.getTasks().register(\"preTouchOpenJFXDependencies\", task -> task.doLast(JavaFXUtils::preTouchOpenJFXDependencies));\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/javafx/JavaFXVersionType.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.javafx;\n\n/**\n * @author Glavo\n */\npublic enum JavaFXVersionType {\n    CLASSIC(\"classic\", 17),\n    MODERN(\"modern\", 23);\n\n    private final String name;\n    private final int javaVersion;\n\n    JavaFXVersionType(String name, int javaVersion) {\n        this.name = name;\n        this.javaVersion = javaVersion;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public int getJavaVersion() {\n        return javaVersion;\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/CheckTranslations.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.l10n;\n\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.file.RegularFileProperty;\nimport org.gradle.api.logging.Logger;\nimport org.gradle.api.logging.Logging;\nimport org.gradle.api.tasks.InputFile;\nimport org.gradle.api.tasks.TaskAction;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.function.BiConsumer;\n\n/// @author Glavo\npublic abstract class CheckTranslations extends DefaultTask {\n\n    private static final Logger LOGGER = Logging.getLogger(CheckTranslations.class);\n\n    @InputFile\n    public abstract RegularFileProperty getEnglishFile();\n\n    @InputFile\n    public abstract RegularFileProperty getSimplifiedChineseFile();\n\n    @InputFile\n    public abstract RegularFileProperty getTraditionalChineseFile();\n\n    @InputFile\n    public abstract RegularFileProperty getClassicalChineseFile();\n\n    @TaskAction\n    public void run() throws IOException {\n        Checker checker = new Checker();\n\n        var english = new PropertiesFile(getEnglishFile());\n        var simplifiedChinese = new PropertiesFile(getSimplifiedChineseFile());\n        var traditionalChinese = new PropertiesFile(getTraditionalChineseFile());\n        var classicalChinese = new PropertiesFile(getClassicalChineseFile());\n\n        simplifiedChinese.forEach((key, value) -> {\n            checker.checkKeyExists(english, key);\n            checker.checkKeyExists(traditionalChinese, key);\n\n            checker.checkMisspelled(simplifiedChinese, key, value, \"账户\", \"帐户\");\n            checker.checkMisspelled(simplifiedChinese, key, value, \"其他\", \"其它\");\n\n            checker.checkMisspelled(simplifiedChinese, key, value, \"(\", \"（\");\n            checker.checkMisspelled(simplifiedChinese, key, value, \")\", \"）\");\n        });\n\n        traditionalChinese.forEach((key, value) -> {\n            checker.checkMisspelled(traditionalChinese, key, value, \"(\", \"（\");\n            checker.checkMisspelled(traditionalChinese, key, value, \")\", \"）\");\n        });\n\n        classicalChinese.forEach((key, value) -> {\n            checker.checkMisspelled(classicalChinese, key, value, \"綫\", \"線\");\n            checker.checkMisspelled(classicalChinese, key, value, \"爲\", \"為\");\n            checker.checkMisspelled(classicalChinese, key, value, \"啟\", \"啓\");\n        });\n\n        checker.check();\n    }\n\n    private static final class PropertiesFile {\n        final Path path;\n        final Properties properties = new Properties();\n\n        PropertiesFile(RegularFileProperty property) throws IOException {\n            this(property.getAsFile().get().toPath().toAbsolutePath().normalize());\n        }\n\n        PropertiesFile(Path path) throws IOException {\n            this.path = path;\n            try (var reader = Files.newBufferedReader(path)) {\n                properties.load(reader);\n            }\n        }\n\n        public String getFileName() {\n            return path.getFileName().toString();\n        }\n\n        public void forEach(BiConsumer<String, String> consumer) {\n            properties.forEach((key, value) -> consumer.accept(key.toString(), value.toString()));\n        }\n    }\n\n    private static final class Checker {\n\n        private final Map<PropertiesFile, Map<Class<?>, Set<Problem>>> problems = new LinkedHashMap<>();\n        private int problemsCount;\n\n        public void checkKeyExists(PropertiesFile file, String key) {\n            if (!file.properties.containsKey(key)) {\n                onFailure(file, new Problem.MissingKey(key));\n            }\n        }\n\n        public void checkMisspelled(PropertiesFile file, String key, String value,\n                                    String correct, String misspelled) {\n            if (value.contains(misspelled)) {\n                onFailure(file, new Problem.Misspelled(correct, misspelled));\n            }\n        }\n\n        public void onFailure(PropertiesFile file, Problem problem) {\n            problemsCount++;\n            problems.computeIfAbsent(file, ignored -> new HashMap<>())\n                    .computeIfAbsent(problem.getClass(), ignored -> new LinkedHashSet<>())\n                    .add(problem);\n        }\n\n        public void check() {\n            if (problemsCount > 0) {\n                problems.forEach((file, problems) -> {\n                    problems.values().stream().flatMap(Collection::stream).forEach(problem ->\n                            LOGGER.warn(\"{}: {}\", file.getFileName(), problem.getMessage()));\n                });\n\n                throw new GradleException(\"Failed to check translations, \" + problemsCount + \" found problems.\");\n            }\n        }\n    }\n\n    private static abstract sealed class Problem {\n        public abstract String getMessage();\n\n        private static final class MissingKey extends Problem {\n            private final String key;\n\n            MissingKey(String key) {\n                this.key = key;\n            }\n\n            @Override\n            public String getMessage() {\n                return \"missing key '%s'\".formatted(key);\n            }\n        }\n\n        private static final class Misspelled extends Problem {\n            private final String correct;\n            private final String misspelled;\n\n            Misspelled(String correct, String misspelled) {\n                this.correct = correct;\n                this.misspelled = misspelled;\n            }\n\n            @Override\n            public String getMessage() {\n                return \"misspelled '%s' should be replaced by '%s'\".formatted(misspelled, correct);\n            }\n\n            @Override\n            public int hashCode() {\n                return misspelled.hashCode();\n            }\n\n            @Override\n            public boolean equals(Object obj) {\n                return obj instanceof Misspelled that && this.misspelled.equals(that.misspelled);\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/CreateLanguageList.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.l10n;\n\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.file.DirectoryProperty;\nimport org.gradle.api.file.RegularFileProperty;\nimport org.gradle.api.provider.ListProperty;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.InputDirectory;\nimport org.gradle.api.tasks.OutputFile;\nimport org.gradle.api.tasks.TaskAction;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/// @author Glavo\npublic abstract class CreateLanguageList extends DefaultTask {\n    @InputDirectory\n    public abstract DirectoryProperty getResourceBundleDir();\n\n    @Input\n    public abstract Property<@NotNull String> getResourceBundleBaseName();\n\n    @Input\n    public abstract ListProperty<@NotNull String> getAdditionalLanguages();\n\n    @OutputFile\n    public abstract RegularFileProperty getOutputFile();\n\n    @TaskAction\n    public void run() throws IOException {\n        Path inputDir = getResourceBundleDir().get().getAsFile().toPath();\n        if (!Files.isDirectory(inputDir))\n            throw new GradleException(\"Input directory not exists: \" + inputDir);\n\n\n        SortedSet<Locale> locales = new TreeSet<>(LocalizationUtils::compareLocale);\n        locales.addAll(getAdditionalLanguages().getOrElse(List.of()).stream()\n                .map(Locale::forLanguageTag)\n                .toList());\n\n        String baseName = getResourceBundleBaseName().get();\n        String suffix = \".properties\";\n\n        try (var stream = Files.newDirectoryStream(inputDir, file -> {\n            String fileName = file.getFileName().toString();\n            return fileName.startsWith(baseName) && fileName.endsWith(suffix);\n        })) {\n            for (Path file : stream) {\n                String fileName = file.getFileName().toString();\n                if (fileName.length() == baseName.length() + suffix.length())\n                    locales.add(Locale.ENGLISH);\n                else if (fileName.charAt(baseName.length()) == '_') {\n                    String localeName = fileName.substring(baseName.length() + 1, fileName.length() - suffix.length());\n\n                    // TODO: Delete this if the I18N file naming is changed\n                    if (baseName.equals(\"I18N\")) {\n                        if (localeName.equals(\"zh\"))\n                            locales.add(Locale.forLanguageTag(\"zh-Hant\"));\n                        else if (localeName.equals(\"zh_CN\"))\n                            locales.add(Locale.forLanguageTag(\"zh-Hans\"));\n                        else\n                            locales.add(Locale.forLanguageTag(localeName.replace('_', '-')));\n                    } else {\n                        if (localeName.equals(\"zh\"))\n                            locales.add(Locale.forLanguageTag(\"zh-Hans\"));\n                        else\n                            locales.add(Locale.forLanguageTag(localeName.replace('_', '-')));\n                    }\n                }\n            }\n        }\n\n        Path outputFile = getOutputFile().get().getAsFile().toPath();\n        Files.createDirectories(outputFile.getParent());\n        Files.writeString(outputFile, locales.stream().map(locale -> '\"' + locale.toLanguageTag() + '\"')\n                .collect(Collectors.joining(\", \", \"[\", \"]\")));\n    }\n\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/CreateLocaleNamesResourceBundle.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.l10n;\n\nimport com.google.gson.Gson;\nimport com.google.gson.reflect.TypeToken;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.file.DirectoryProperty;\nimport org.gradle.api.file.RegularFileProperty;\nimport org.gradle.api.tasks.InputFile;\nimport org.gradle.api.tasks.OutputDirectory;\nimport org.gradle.api.tasks.TaskAction;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.*;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.*;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/// @author Glavo\npublic abstract class CreateLocaleNamesResourceBundle extends DefaultTask {\n\n    @InputFile\n    public abstract RegularFileProperty getLanguagesFile();\n\n    @OutputDirectory\n    public abstract DirectoryProperty getOutputDirectory();\n\n    private static String mapToFileName(String base, String ext, Locale locale) {\n        if (locale.equals(Locale.ENGLISH))\n            return base + \".\" + ext;\n        else if (locale.toLanguageTag().equals(\"zh-Hans\"))\n            return base + \"_zh.\" + ext;\n        else\n            return base + \"_\" + locale.toLanguageTag().replace('-', '_') + \".\" + ext;\n    }\n\n    private static final ResourceBundle.Control CONTROL = new ResourceBundle.Control() {\n    };\n\n    @TaskAction\n    public void run() throws IOException {\n        Path languagesFile = getLanguagesFile().get().getAsFile().toPath();\n        Path outputDir = getOutputDirectory().get().getAsFile().toPath();\n\n        if (Files.isDirectory(outputDir)) {\n            Files.walkFileTree(outputDir, new SimpleFileVisitor<>() {\n                @Override\n                public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {\n                    Files.deleteIfExists(file);\n                    return FileVisitResult.CONTINUE;\n                }\n\n                @Override\n                public @NotNull FileVisitResult postVisitDirectory(@NotNull Path dir, @Nullable IOException exc) throws IOException {\n                    if (!dir.equals(outputDir))\n                        Files.deleteIfExists(dir);\n                    return FileVisitResult.CONTINUE;\n                }\n            });\n        }\n        Files.deleteIfExists(outputDir);\n        Files.createDirectories(outputDir);\n\n        List<Locale> supportedLanguages;\n        try (var reader = Files.newBufferedReader(languagesFile)) {\n            supportedLanguages = new Gson().fromJson(reader, new TypeToken<List<String>>() {\n                    }).stream()\n                    .map(Locale::forLanguageTag)\n                    .sorted(LocalizationUtils::compareLocale)\n                    .toList();\n        }\n\n        if (!supportedLanguages.contains(Locale.ENGLISH))\n            throw new GradleException(\"Missing english in supported languages: \" + supportedLanguages);\n\n        // Ensure English is at the first position, this assumption will be used later\n        if (!supportedLanguages.get(0).equals(Locale.ENGLISH)) {\n            supportedLanguages = new ArrayList<>(supportedLanguages);\n            supportedLanguages.remove(Locale.ENGLISH);\n            supportedLanguages.add(0, Locale.ENGLISH);\n        }\n\n        EnumMap<LocaleField, SortedSet<String>> names = new EnumMap<>(LocaleField.class);\n        for (LocaleField field : LocaleField.values()) {\n            names.put(field, supportedLanguages.stream()\n                    .map(field::get)\n                    .filter(it -> !it.isBlank())\n                    .collect(Collectors.toCollection(() -> new TreeSet<>(field))));\n        }\n\n        Map<Locale, Properties> overrides = new HashMap<>();\n        for (Locale currentLanguage : supportedLanguages) {\n            InputStream overrideFile = CreateLocaleNamesResourceBundle.class.getResourceAsStream(\n                    mapToFileName(\"LocaleNamesOverride\", \"properties\", currentLanguage));\n            Properties overrideProperties = new Properties();\n            if (overrideFile != null) {\n                try (var reader = new InputStreamReader(overrideFile, StandardCharsets.UTF_8)) {\n                    overrideProperties.load(reader);\n                }\n            }\n            overrides.put(currentLanguage, overrideProperties);\n        }\n\n        Map<Locale, LocaleNames> allLocaleNames = new HashMap<>();\n\n        // For Upside Down English\n        UpsideDownTranslate.Translator upsideDownTranslator = new UpsideDownTranslate.Translator();\n        for (Locale currentLocale : supportedLanguages) {\n            Properties currentOverrides = overrides.get(currentLocale);\n            if (currentLocale.getLanguage().length() > 2 && currentOverrides.isEmpty()) {\n                // The JDK does not provide localized texts for these languages\n                continue;\n            }\n\n            LocaleNames currentDisplayNames = new LocaleNames();\n\n            for (LocaleField field : LocaleField.values()) {\n                SortedMap<String, String> nameToDisplayName = currentDisplayNames.getNameToDisplayName(field);\n\n                loop:\n                for (String name : names.get(field)) {\n                    String displayName = currentOverrides.getProperty(name);\n\n                    getDisplayName:\n                    if (displayName == null) {\n                        if (currentLocale.equals(UpsideDownTranslate.EN_QABS)) {\n                            String englishDisplayName = allLocaleNames.get(Locale.ENGLISH).getNameToDisplayName(field).get(name);\n                            if (englishDisplayName != null) {\n                                displayName = upsideDownTranslator.translate(englishDisplayName);\n                                break getDisplayName;\n                            }\n                        }\n\n                        // Although it cannot correctly handle the inheritance relationship between languages,\n                        // we will not apply this function to sublanguages.\n                        List<Locale> candidateLocales = CONTROL.getCandidateLocales(\"\", currentLocale);\n\n                        for (Locale candidateLocale : candidateLocales) {\n                            Properties candidateOverride = overrides.get(candidateLocale);\n                            if (candidateOverride != null && candidateOverride.containsKey(name)) {\n                                continue loop;\n                            }\n                        }\n\n                        displayName = field.getDisplayName(name, currentLocale);\n\n                        // JDK does not have a built-in translation\n                        if (displayName.isBlank() || displayName.equals(name)) {\n                            continue loop;\n                        }\n\n                        // If it is just a duplicate of the parent content, ignored it\n                        for (Locale candidateLocale : candidateLocales) {\n                            LocaleNames candidateLocaleNames = allLocaleNames.get(candidateLocale);\n                            if (candidateLocaleNames != null) {\n                                String candidateDisplayName = candidateLocaleNames.getNameToDisplayName(field).get(name);\n                                if (displayName.equals(candidateDisplayName)) {\n                                    continue loop;\n                                }\n                                break;\n                            }\n                        }\n\n                        // Ignore it if the JDK falls back to English when querying the display name\n                        if (!currentLocale.equals(Locale.ENGLISH)\n                                && displayName.equals(allLocaleNames.get(Locale.ENGLISH).getNameToDisplayName(field).get(name))) {\n                            continue loop;\n                        }\n                    }\n\n                    nameToDisplayName.put(name, displayName);\n                }\n            }\n\n            allLocaleNames.put(currentLocale, currentDisplayNames);\n        }\n\n        for (Map.Entry<Locale, LocaleNames> entry : allLocaleNames.entrySet()) {\n            if (!entry.getValue().isEmpty()) {\n                Path targetFile = outputDir.resolve(mapToFileName(\"LocaleNames\", \"properties\", entry.getKey()));\n                entry.getValue().writeTo(targetFile);\n            }\n        }\n    }\n\n    private static final class LocaleNames {\n        private final EnumMap<LocaleField, SortedMap<String, String>> displayNames = new EnumMap<>(LocaleField.class);\n\n        LocaleNames() {\n            for (LocaleField field : LocaleField.values()) {\n                displayNames.put(field, new TreeMap<>(field));\n            }\n        }\n\n        boolean isEmpty() {\n            return displayNames.values().stream().allMatch(Map::isEmpty);\n        }\n\n        SortedMap<String, String> getNameToDisplayName(LocaleField field) {\n            return displayNames.get(field);\n        }\n\n        void writeTo(Path file) throws IOException {\n            try (var writer = Files.newBufferedWriter(file, StandardOpenOption.CREATE_NEW)) {\n                boolean firstBlock = true;\n\n                for (var entry : displayNames.entrySet()) {\n                    LocaleField field = entry.getKey();\n                    SortedMap<String, String> values = entry.getValue();\n\n                    if (!values.isEmpty()) {\n                        if (firstBlock)\n                            firstBlock = false;\n                        else\n                            writer.newLine();\n\n                        writer.write(\"# \" + field.blockHeader + \"\\n\");\n\n                        for (var nameToDisplay : values.entrySet()) {\n                            writer.write(nameToDisplay.getKey() + \"=\" + nameToDisplay.getValue() + \"\\n\");\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private enum LocaleField implements Comparator<String> {\n        LANGUAGE(\"Languages\") {\n            @Override\n            public String get(Locale locale) {\n                return locale.getLanguage();\n            }\n\n            @Override\n            public String getDisplayName(String fieldValue, Locale inLocale) {\n                return new Locale.Builder()\n                        .setLanguage(fieldValue)\n                        .build()\n                        .getDisplayLanguage(inLocale);\n            }\n\n            @Override\n            public int compare(String l1, String l2) {\n                return LocalizationUtils.compareLanguage(l1, l2);\n            }\n        },\n        SCRIPT(\"Scripts\") {\n            @Override\n            public String get(Locale locale) {\n                return locale.getScript();\n            }\n\n            @Override\n            public String getDisplayName(String fieldValue, Locale inLocale) {\n                return new Locale.Builder()\n                        .setScript(fieldValue)\n                        .build()\n                        .getDisplayScript(inLocale);\n            }\n\n            @Override\n            public int compare(String s1, String s2) {\n                return LocalizationUtils.compareScript(s1, s2);\n            }\n        };\n\n        final String blockHeader;\n\n        LocaleField(String blockHeader) {\n            this.blockHeader = blockHeader;\n        }\n\n        public abstract String get(Locale locale);\n\n        public abstract String getDisplayName(String fieldValue, Locale inLocale);\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/LocalizationUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.l10n;\n\nimport org.gradle.api.GradleException;\n\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\n\nfinal class LocalizationUtils {\n    public static final Map<String, String> subLanguageToParent = loadCSV(\"sublanguages.csv\");\n\n    private static Map<String, String> loadCSV(String fileName) {\n        InputStream resource = LocalizationUtils.class.getResourceAsStream(fileName);\n        if (resource == null) {\n            throw new GradleException(\"Resource not found: \" + fileName);\n        }\n\n        HashMap<String, String> result = new HashMap<>();\n        try (resource) {\n            new String(resource.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> {\n                if (line.startsWith(\"#\") || line.isBlank())\n                    return;\n\n                String[] items = line.split(\",\");\n                if (items.length < 2) {\n                    throw new GradleException(\"Invalid line in sublanguages.csv: \" + line);\n                }\n\n                String parent = items[0];\n                for (int i = 1; i < items.length; i++) {\n                    result.put(items[i], parent);\n                }\n            });\n        } catch (RuntimeException | Error e) {\n            throw e;\n        } catch (Throwable e) {\n            throw new GradleException(\"Failed to load \" + fileName, e);\n        }\n\n        return Map.copyOf(result);\n    }\n\n    private static List<String> resolveLanguage(String language) {\n        List<String> langList = new ArrayList<>();\n\n        String lang = language;\n        while (true) {\n            langList.add(0, lang);\n\n            String parent = subLanguageToParent.get(lang);\n            if (parent != null) {\n                lang = parent;\n            } else {\n                return langList;\n            }\n        }\n    }\n\n    public static int compareLanguage(String l1, String l2) {\n        var list1 = resolveLanguage(l1);\n        var list2 = resolveLanguage(l2);\n\n        int n = Math.min(list1.size(), list2.size());\n        for (int i = 0; i < n; i++) {\n            int c = list1.get(i).compareTo(list2.get(i));\n            if (c != 0)\n                return c;\n        }\n\n        return Integer.compare(list1.size(), list2.size());\n    }\n\n    public static int compareScript(String s1, String s2) {\n        return s1.compareTo(s2);\n    }\n\n    public static int compareLocale(Locale l1, Locale l2) {\n        int c = compareLanguage(l1.getLanguage(), l2.getLanguage());\n        if (c != 0)\n            return c;\n\n        c = compareScript(l1.getScript(), l2.getScript());\n        if (c != 0)\n            return c;\n\n        c = l1.getCountry().compareTo(l2.getCountry());\n        if (c != 0)\n            return c;\n\n        c = l1.getVariant().compareTo(l2.getVariant());\n        if (c != 0)\n            return c;\n\n        return l1.toString().compareTo(l2.toLanguageTag());\n    }\n\n    private LocalizationUtils() {\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.l10n;\n\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.file.RegularFileProperty;\nimport org.gradle.api.tasks.InputFile;\nimport org.gradle.api.tasks.OutputFile;\nimport org.gradle.api.tasks.TaskAction;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.StandardOpenOption;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/// @author Glavo\n/// @see [language-subtag-registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)\npublic abstract class ParseLanguageSubtagRegistry extends DefaultTask {\n\n    @InputFile\n    public abstract RegularFileProperty getLanguageSubtagRegistryFile();\n\n    @OutputFile\n    public abstract RegularFileProperty getSublanguagesFile();\n\n    /// CSV file storing the mapping from subtag to their default scripts.\n    @OutputFile\n    public abstract RegularFileProperty getDefaultScriptFile();\n\n    @TaskAction\n    public void run() throws IOException {\n        List<Item> items;\n\n        try (var reader = Files.newBufferedReader(getLanguageSubtagRegistryFile().getAsFile().get().toPath())) {\n            var builder = new ItemsBuilder();\n            builder.parse(reader);\n            items = builder.items;\n        }\n\n        MultiMap scriptToSubtag = new MultiMap();\n        MultiMap languageToSub = new MultiMap();\n\n        // Classical Chinese should use Traditional Chinese characters by default\n        scriptToSubtag.add(\"Hant\", \"lzh\");\n\n        for (Item item : items) {\n            String type = item.firstValueOrThrow(\"Type\");\n            if (type.equals(\"grandfathered\") || type.equals(\"redundant\")\n                    || !item.allValues(\"Deprecated\").isEmpty())\n                continue;\n\n            String subtag = item.firstValueOrThrow(\"Subtag\");\n\n            mainSwitch:\n            switch (type) {\n                case \"language\", \"extlang\" -> {\n                    item.firstValue(\"Macrolanguage\")\n                            .ifPresent(macroLang -> languageToSub.add(macroLang, subtag));\n\n                    item.firstValue(\"Suppress-Script\")\n                            .ifPresent(script -> scriptToSubtag.add(script, subtag));\n                }\n                case \"variant\" -> {\n                    List<String> prefixes = item.allValues(\"Prefix\");\n                    String defaultScript = null;\n                    for (String prefix : prefixes) {\n                        String script = Locale.forLanguageTag(prefix).getScript();\n                        if (script.isEmpty()) {\n                            break mainSwitch;\n                        }\n\n                        if (defaultScript == null) {\n                            defaultScript = script;\n                        } else {\n                            if (!defaultScript.equals(script)) {\n                                break mainSwitch;\n                            }\n                        }\n                    }\n\n                    if (defaultScript != null) {\n                        scriptToSubtag.add(defaultScript, subtag);\n                    }\n                }\n                case \"region\", \"script\" -> {\n                    // ignored\n                }\n                default -> throw new GradleException(String.format(\"Unknown subtag type: %s\", type));\n            }\n        }\n\n        languageToSub.saveToCSV(getSublanguagesFile());\n        scriptToSubtag.saveToCSV(getDefaultScriptFile());\n    }\n\n    private static final class MultiMap {\n        private final TreeMap<String, Set<String>> allValues = new TreeMap<>(TAG_COMPARATOR);\n\n        void add(String key, String value) {\n            allValues.computeIfAbsent(key, k -> new TreeSet<>(TAG_COMPARATOR)).add(value);\n        }\n\n        void saveToCSV(RegularFileProperty csvFile) throws IOException {\n            try (var writer = Files.newBufferedWriter(csvFile.getAsFile().get().toPath(),\n                    StandardOpenOption.CREATE,\n                    StandardOpenOption.TRUNCATE_EXISTING)) {\n\n                for (Map.Entry<String, Set<String>> entry : allValues.entrySet()) {\n                    String key = entry.getKey();\n                    Set<String> values = entry.getValue();\n\n                    writer.write(key);\n\n                    for (String value : values) {\n                        writer.write(',');\n                        writer.write(value);\n                    }\n\n                    writer.newLine();\n                }\n            }\n        }\n    }\n\n    private static final class Item {\n        final Map<String, List<String>> values = new LinkedHashMap<>();\n\n        public @NotNull List<String> allValues(String name) {\n            return values.getOrDefault(name, List.of());\n        }\n\n        public @NotNull Optional<String> firstValue(String name) {\n            return Optional.ofNullable(values.get(name)).map(it -> it.get(0));\n        }\n\n        public @Nullable String firstValueOrNull(String name) {\n            return firstValue(name).orElse(null);\n        }\n\n        public @NotNull String firstValueOrThrow(String name) {\n            return firstValue(name).orElseThrow(() -> new GradleException(\"No value found for \" + name + \" in \" + this));\n        }\n\n        public void put(String name, String value) {\n            values.computeIfAbsent(name, ignored -> new ArrayList<>(1)).add(value);\n        }\n\n        @Override\n        public String toString() {\n            StringJoiner joiner = new StringJoiner(\"\\n\");\n\n            values.forEach((name, values) -> {\n                for (String value : values) {\n                    joiner.add(name + \": \" + value);\n                }\n            });\n\n            return joiner.toString();\n        }\n    }\n\n    private static final class ItemsBuilder {\n        private final List<Item> items = new ArrayList<>(1024);\n        private Item current = new Item();\n        private String currentName = null;\n        private String currentValue = null;\n\n        private void updateCurrent() {\n            if (currentName != null) {\n                current.put(currentName, currentValue);\n                currentName = null;\n                currentValue = null;\n            }\n        }\n\n        private void updateItems() throws IOException {\n            updateCurrent();\n\n            if (current.values.isEmpty())\n                return;\n\n            if (current.firstValue(\"Type\").isEmpty()) {\n                if (current.firstValue(\"File-Date\").isPresent()) {\n                    current.values.clear();\n                    return;\n                } else {\n                    throw new GradleException(\"Invalid item: \" + current);\n                }\n            }\n\n            items.add(current);\n            current = new Item();\n        }\n\n        void parse(BufferedReader reader) throws IOException {\n            Pattern linePattern = Pattern.compile(\"^(?<name>[A-Za-z\\\\-]+): (?<value>.*)$\");\n\n            String line;\n            while ((line = reader.readLine()) != null) {\n                if (line.isBlank()) {\n                    continue;\n                } else if (line.equals(\"%%\")) {\n                    updateItems();\n                } else if (line.startsWith(\"  \")) {\n                    if (currentValue != null) {\n                        currentValue = currentValue + \" \" + line;\n                    } else {\n                        throw new GradleException(\"Invalid line: \" + line);\n                    }\n                } else {\n                    updateCurrent();\n\n                    Matcher matcher = linePattern.matcher(line);\n                    if (matcher.matches()) {\n                        currentName = matcher.group(\"name\");\n                        currentValue = matcher.group(\"value\");\n                    } else {\n                        throw new GradleException(\"Invalid line: \" + line);\n                    }\n                }\n            }\n\n            updateItems();\n        }\n    }\n\n    private static final Comparator<String> TAG_COMPARATOR = (lang1, lang2) -> {\n        if (lang1.length() != lang2.length())\n            return Integer.compare(lang1.length(), lang2.length());\n        else\n            return lang1.compareTo(lang2);\n    };\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/UpsideDownTranslate.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.l10n;\n\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.file.RegularFileProperty;\nimport org.gradle.api.tasks.InputFile;\nimport org.gradle.api.tasks.OutputFile;\nimport org.gradle.api.tasks.TaskAction;\nimport org.jackhuang.hmcl.gradle.utils.PropertiesUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/// @author Glavo\npublic abstract class UpsideDownTranslate extends DefaultTask {\n\n    static final Locale EN_QABS = Locale.forLanguageTag(\"en-Qabs\");\n\n    private static final Map<String, String> PROPERTIES = Map.of(\n            \"datetime.format\", \"MMM d, yyyy, h:mm:ss a\"\n    );\n\n    @InputFile\n    public abstract RegularFileProperty getInputFile();\n\n    @OutputFile\n    public abstract RegularFileProperty getOutputFile();\n\n    @TaskAction\n    public void run() throws IOException {\n        Path inputFile = getInputFile().get().getAsFile().toPath();\n        Path outputFile = getOutputFile().get().getAsFile().toPath();\n\n        Properties english = PropertiesUtils.load(inputFile);\n\n        Properties output = new Properties();\n        Translator translator = new Translator();\n        english.forEach((k, v) -> {\n            if (PROPERTIES.containsKey(k.toString())) {\n                output.setProperty(k.toString(), PROPERTIES.get(k.toString()));\n            } else {\n                output.put(k, translator.translate(v.toString()));\n            }\n        });\n\n        Files.createDirectories(outputFile.getParent());\n        try (var writer = Files.newBufferedWriter(outputFile)) {\n            output.store(writer, \"This file is automatically generated, please do not modify it manually\");\n        }\n    }\n\n    static final class Translator {\n        private static final Map<Integer, Integer> MAPPER = loadMap();\n\n        private static Map<Integer, Integer> loadMap() {\n            var map = new LinkedHashMap<Integer, Integer>();\n\n            InputStream inputStream = UpsideDownTranslate.class.getResourceAsStream(\"upside_down.txt\");\n            if (inputStream != null) {\n                try (inputStream) {\n                    new String(inputStream.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> {\n                        if (line.isBlank() || line.startsWith(\"#\"))\n                            return;\n\n                        if (line.length() != 2) {\n                            throw new GradleException(\"Invalid line: \" + line);\n                        }\n\n                        map.put((int) line.charAt(0), (int) line.charAt(1));\n                    });\n                } catch (IOException e) {\n                    throw new GradleException(\"Failed to load upside_down.txt\", e);\n                }\n            } else {\n                throw new GradleException(\"upside_down.txt not found\");\n            }\n            return Collections.unmodifiableMap(map);\n        }\n\n        private static final Pattern FORMAT_PATTERN = Pattern.compile(\"^%(\\\\d\\\\$)?(\\\\d+)?(\\\\.\\\\d+)?([sdf])\");\n        private static final Pattern XML_TAG_PATTERN = Pattern.compile(\"^<(?<tag>[a-zA-Z]+)( href=\\\"[^\\\"]*\\\")?>\");\n\n        private final StringBuilder resultBuilder = new StringBuilder();\n\n        private void appendToLineBuilder(String input) {\n            for (int i = 0; i < input.length(); ) {\n                int ch = input.codePointAt(i);\n\n                if (ch == '%') {\n                    Matcher matcher = FORMAT_PATTERN.matcher(input).region(i, input.length());\n                    if (matcher.find()) {\n                        String formatString = matcher.group();\n                        resultBuilder.insert(0, formatString);\n                        i += formatString.length();\n                        continue;\n                    }\n                } else if (ch == '<') {\n                    Matcher matcher = XML_TAG_PATTERN.matcher(input).region(i, input.length());\n                    if (matcher.find()) {\n                        String beginTag = matcher.group();\n                        String endTag = \"</\" + matcher.group(1) + \">\";\n\n                        int endTagOffset = input.indexOf(endTag, i + beginTag.length());\n                        if (endTagOffset > 0) {\n                            resultBuilder.insert(0, endTag);\n                            appendToLineBuilder(input.substring(i + beginTag.length(), endTagOffset));\n                            resultBuilder.insert(0, beginTag);\n\n                            i = endTagOffset + endTag.length();\n                            continue;\n                        }\n                    }\n                }\n\n                int udCh = MAPPER.getOrDefault(ch, ch);\n                if (Character.isBmpCodePoint(udCh)) {\n                    resultBuilder.insert(0, (char) udCh);\n                } else {\n                    resultBuilder.insert(0, Character.toChars(udCh));\n                }\n\n                i += Character.charCount(ch);\n            }\n        }\n\n        String translate(String input) {\n            resultBuilder.setLength(0);\n            appendToLineBuilder(input);\n            return resultBuilder.toString();\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/mod/ParseModDataTask.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.mod;\n\nimport com.google.gson.*;\nimport com.google.gson.annotations.JsonAdapter;\nimport com.google.gson.reflect.TypeToken;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.file.RegularFileProperty;\nimport org.gradle.api.logging.Logger;\nimport org.gradle.api.logging.Logging;\nimport org.gradle.api.tasks.InputFile;\nimport org.gradle.api.tasks.OutputFile;\nimport org.gradle.api.tasks.TaskAction;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author Glavo\n */\npublic abstract class ParseModDataTask extends DefaultTask {\n\n    @InputFile\n    public abstract RegularFileProperty getInputFile();\n\n    @OutputFile\n    public abstract RegularFileProperty getOutputFile();\n\n    private static final Logger LOGGER = Logging.getLogger(ParseModDataTask.class);\n\n    private static final String S = \";\";\n    private static final String MOD_SEPARATOR = \",\";\n\n    private static final Pattern[] CURSEFORGE_PATTERNS = {\n            Pattern.compile(\"^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|data-packs|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons|scripts)/+(?<modid>[\\\\w-]+)(/(.*?))?$\"),\n            Pattern.compile(\"^/projects/(?<modid>[\\\\w-]+)(/(.*?))?$\"),\n            Pattern.compile(\"^/mc-mods/minecraft/(?<modid>[\\\\w-]+)(/(.*?))?$\"),\n            Pattern.compile(\"^/legacy/mc-mods/minecraft/(\\\\d+)-(?<modid>[\\\\w-]+)\"),\n    };\n\n    private static String parseName(String name) {\n        return name.replace(\"&amp;\", \"&\")\n                .replace(\"&lt;\", \"<\")\n                .replace(\"&gt;\", \">\");\n    }\n\n    private static String parseCurseforge(String url) {\n        URI res = URI.create(url.replace(\" \", \"%20\"));\n\n        if (!\"http\".equals(res.getScheme()) && !\"https\".equals(res.getScheme())) {\n            return \"\";\n        }\n\n        if (\"edge.forgecdn.net\".equals(res.getHost())) {\n            return \"\";\n        }\n\n        for (Pattern pattern : CURSEFORGE_PATTERNS) {\n            Matcher matcher = pattern.matcher(res.getPath());\n            if (matcher.matches()) {\n                return matcher.group(\"modid\");\n            }\n        }\n\n        return \"\";\n    }\n\n    private static final Pattern MCMOD_PATTERN =\n            Pattern.compile(\"^https://www\\\\.mcmod\\\\.cn/(class|modpack)/(?<modid>\\\\d+)\\\\.html$\");\n\n    private static String parseMcMod(String url) {\n        Matcher matcher = MCMOD_PATTERN.matcher(url);\n        if (matcher.matches()) {\n            return matcher.group(\"modid\");\n        }\n        return \"\";\n    }\n\n    private static String cleanChineseName(String chineseName) {\n        if (chineseName == null || chineseName.isBlank())\n            return \"\";\n\n        chineseName = chineseName.trim();\n\n        StringBuilder builder = new StringBuilder(chineseName.length());\n        int[] codePoints = chineseName.codePoints().toArray();\n        for (int i = 0; i < codePoints.length; i++) {\n            int ch = codePoints[i];\n            int prev = i > 0 ? codePoints[i - 1] : 0;\n\n            switch (ch) {\n                case '（' -> {\n                    if (Character.isWhitespace(prev) || prev == '！' || prev == '。')\n                        builder.append('(');\n                    else\n                        builder.append(\" (\");\n                }\n                case '）' -> builder.append(')');\n                default -> builder.appendCodePoint(ch);\n            }\n        }\n        return builder.toString().trim();\n    }\n\n    private static final Set<String> SKIP = Set.of(\n            \"Minecraft\",\n            \"The Building Game\"\n    );\n\n    @TaskAction\n    public void run() throws IOException {\n        Path inputFile = getInputFile().get().getAsFile().toPath().toAbsolutePath();\n        Path outputFile = getOutputFile().get().getAsFile().toPath().toAbsolutePath();\n\n        Files.createDirectories(outputFile.getParent());\n\n        List<ModData> modDatas;\n        try (BufferedReader reader = Files.newBufferedReader(inputFile)) {\n            modDatas = new Gson().fromJson(reader, TypeToken.getParameterized(List.class, ModData.class).getType());\n        }\n\n        try (BufferedWriter writer = Files.newBufferedWriter(outputFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) {\n            writer.write(\"#\\n\" +\n                    \"# Hello Minecraft! Launcher\\n\" +\n                    \"# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\\n\" +\n                    \"#\\n\" +\n                    \"# mcmod.cn\\n\" +\n                    \"# Copyright (C) 2025. All Rights Reserved.\\n\" +\n                    \"#\\n\");\n            for (ModData mod : modDatas) {\n                String chineseName = parseName(mod.name.main);\n                String subName = parseName(mod.name.sub);\n                String abbr = parseName(mod.name.abbr);\n\n                chineseName = chineseName == null ? \"\" : cleanChineseName(chineseName);\n                if (subName == null)\n                    subName = \"\";\n                if (abbr == null)\n                    abbr = \"\";\n\n                if (SKIP.contains(subName)) {\n                    continue;\n                }\n\n                if (chineseName.contains(S) || subName.contains(S)) {\n                    throw new GradleException(\"Error: \" + chineseName);\n                }\n\n                String curseforgeId = \"\";\n                String mcmodId = \"\";\n\n                Map<String, List<ModData.Link>> links = mod.links.list;\n                List<ModData.Link> curseforgeLinks = links.get(\"curseforge\");\n                List<ModData.Link> mcmodLinks = links.get(\"mcmod\");\n\n                if (curseforgeLinks != null && !curseforgeLinks.isEmpty()) {\n                    for (ModData.Link link : curseforgeLinks) {\n                        curseforgeId = parseCurseforge(link.url);\n                        if (!curseforgeId.isEmpty()) {\n                            break;\n                        }\n                    }\n                    if (curseforgeId.isEmpty()) {\n                        LOGGER.warn(\"Error curseforge: {}\", chineseName);\n                    }\n                }\n\n                if (mcmodLinks != null && !mcmodLinks.isEmpty()) {\n                    mcmodId = parseMcMod(mcmodLinks.get(0).url);\n                    if (mcmodId.isEmpty()) {\n                        throw new GradleException(\"Error mcmod: \" + chineseName);\n                    }\n                }\n\n                List<String> modId = new ArrayList<>();\n                if (mod.modid != null) {\n                    for (String id : mod.modid) {\n                        String cleanId = parseName(id.trim());\n\n                        if (cleanId.contains(MOD_SEPARATOR) || cleanId.contains(S)) {\n                            throw new GradleException(\"Error modid: \" + id);\n                        }\n\n                        modId.add(cleanId);\n                    }\n                }\n\n                String modIds = String.join(MOD_SEPARATOR, modId);\n\n                writer.write(curseforgeId + S + mcmodId + S + modIds + S + chineseName + S + subName + S + abbr + \"\\n\");\n            }\n        }\n    }\n\n    public static final class ModData {\n\n        public Name name;\n\n        @JsonAdapter(ModIdDeserializer.class)\n        public List<String> modid;\n\n        public Links links;\n\n        public static final class Name {\n            public String main;\n            public String sub;\n            public String abbr;\n        }\n\n        public static final class Link {\n            public String url;\n            public String content;\n        }\n\n        public static final class Links {\n            public Map<String, List<Link>> list;\n        }\n\n        public static final class ModIdDeserializer implements JsonDeserializer<List<String>> {\n            private static final Type STRING_LIST = TypeToken.getParameterized(List.class, String.class).getType();\n\n            @Override\n            public List<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n                if (json.isJsonNull()) {\n                    return null;\n                }\n\n                if (json.isJsonArray()) {\n                    return context.deserialize(json, STRING_LIST);\n                } else {\n                    JsonObject jsonObject = json.getAsJsonObject();\n                    JsonElement list = jsonObject.get(\"list\");\n                    if (list == null) {\n                        return null;\n                    } else {\n                        return context.deserialize(list, STRING_LIST);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/org/jackhuang/hmcl/gradle/utils/PropertiesUtils.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl.gradle.utils;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Properties;\n\n/// @author Glavo\npublic final class PropertiesUtils {\n    public static @NotNull Properties load(Path path) throws IOException {\n        Properties properties = new Properties();\n        try (var reader = Files.newBufferedReader(path)) {\n            properties.load(reader);\n        }\n        return properties;\n    }\n\n    private PropertiesUtils() {\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Languages\nlzh=Classical\n\n# Scripts\nQabs=Upside Down\n"
  },
  {
    "path": "buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride_lzh.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Languages\nen=英吉利語\nes=佛郎機語\nja=日本語\nru=羅剎語\nuk=渥蓮語\n\n# Scripts\nQabs=倒置"
  },
  {
    "path": "buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride_zh.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Languages\nlzh=文言\n\n# Scripts\nQabs=颠倒\n"
  },
  {
    "path": "buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride_zh_Hant.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\n# Languages\nlzh=文言\n"
  },
  {
    "path": "config/checkstyle/checkstyle.xml",
    "content": "<?xml version=\"1.0\" ?><!DOCTYPE module PUBLIC\n        \"-//Puppy Crawl//DTD Check Configuration 1.2//EN\"\n        \"http://www.puppycrawl.com/dtds/configuration_1_2.dtd\">\n\n<module name=\"Checker\">\n    <module name=\"NewlineAtEndOfFile\"/> <!-- force newline, important for git merge and POSIX compatibility: http://checkstyle.sourceforge.net/config_misc.html#NewlineAtEndOfFile -->\n    <module name=\"FileTabCharacter\"/> <!-- e.g. disallow tab character outside of strings -->\n    <module name=\"UniqueProperties\"><!-- must not have duplicate properties in .properties files: http://checkstyle.sourceforge.net/config_misc.html#UniqueProperties -->\n        <property name=\"fileExtensions\" value=\"properties\"/>\n    </module>\n\n    <module name=\"FileLength\"><!-- max line length for single file: http://checkstyle.sourceforge.net/config_sizes.html#FileLength -->\n        <property name=\"max\" value=\"2000\"/>\n    </module>\n\n    <module name=\"TreeWalker\">\n        <module name=\"SuppressionCommentFilter\"/> <!-- use //CHECKSTYLE:OFF (...) //CHECKSTYLE:ON to disable checkstyle: http://checkstyle.sourceforge.net/config_filters.html#SuppressionCommentFilter -->\n\n        <!-- Annotations -->\n        <module name=\"MissingDeprecated\"> <!-- if @deprecated and javadoc is there, must be explained in javadoc: http://checkstyle.sourceforge.net/config_annotation.html#MissingDeprecated -->\n        </module>\n        <module name=\"MissingOverride\"/> <!-- if has @inheritDoc in javadoc must have @Override http://checkstyle.sourceforge.net/config_annotation.html#MissingOverride -->\n        <module name=\"PackageAnnotation\"/> <!-- must only be in package-info: http://checkstyle.sourceforge.net/config_annotation.html#PackageAnnotation -->\n\n        <!-- Blocks -->\n        <module name=\"EmptyCatchBlock\"> <!-- empty catch blocks exception var name must be 'ignored' or must not be empty: http://checkstyle.sourceforge.net/config_blocks.html#EmptyCatchBlock -->\n            <property name=\"exceptionVariableName\" value=\"expected|ignore\"/>\n        </module>\n\n        <!-- Misc -->\n        <module name=\"ArrayTypeStyle\"/> <!-- e.g. int[] array is ok int array[] not: http://checkstyle.sourceforge.net/config_misc.html#ArrayTypeStyle -->\n        <module name=\"MutableException\"/> <!-- exception classes must be immutable: http://checkstyle.sourceforge.net/config_design.html#MutableException -->\n        <module name=\"UpperEll\"/> <!-- long values must be postfixed with 'L' not 'l':  http://checkstyle.sourceforge.net/config_misc.html#UpperEll -->\n        <module name=\"Indentation\">  <!-- Checks correct indentation of Java code:  http://checkstyle.sourceforge.net/config_misc.html#Indentation -->\n            <property name=\"basicOffset\" value=\"4\"/>\n            <property name=\"arrayInitIndent\" value=\"8\"/>\n            <property name=\"braceAdjustment\" value=\"0\"/>\n            <property name=\"caseIndent\" value=\"4\"/>\n            <property name=\"throwsIndent\" value=\"4\"/>\n            <property name=\"lineWrappingIndentation\" value=\"4\"/>\n            <property name=\"forceStrictCondition\" value=\"false\"/>\n        </module>\n\n        <!-- Modifier -->\n        <module name=\"RedundantModifier\"> <!-- Checks for redundant modifiers:  http://checkstyle.sourceforge.net/config_modifier.html#RedundantModifier -->\n            <property name=\"tokens\" value=\"METHOD_DEF, VARIABLE_DEF, INTERFACE_DEF, ANNOTATION_FIELD_DEF, ENUM_DEF, CLASS_DEF\"/>\n        </module>\n\n        <!-- Classes -->\n        <module name=\"FinalClass\"/> <!-- class with only private constructor must be final: http://checkstyle.sourceforge.net/config_design.html#FinalClass -->\n        <module name=\"SimplifyBooleanReturn\"/> <!-- directly return boolean does not check and return http://checkstyle.sourceforge.net/config_design.html#SimplifyBooleanReturn -->\n        <module name=\"StringLiteralEquality\"/> <!-- you can't write myString == \"this\" http://checkstyle.sourceforge.net/config_design.html#StringLiteralEquality -->\n        <module name=\"OneTopLevelClass\"/> <!-- only one root class per file http://checkstyle.sourceforge.net/config_design.html#OneTopLevelClass -->\n        <module name=\"ThrowsCount\"> <!-- max 5 throws definitions per method: http://checkstyle.sourceforge.net/config_design.html#ThrowsCount -->\n            <property name=\"max\" value=\"5\"/>\n        </module>\n        <!--<module name=\"InterfaceIsType\"/> interface must contain methods, should not be used for const only: http://checkstyle.sourceforge.net/config_design.html#InterfaceIsType -->\n        <module name=\"OuterTypeFilename\"/> <!-- class Foo must be in Foo.java: http://checkstyle.sourceforge.net/config_misc.html#OuterTypeFilename -->\n\n        <module name=\"HideUtilityClassConstructor\"/> <!-- utility class constructor must be private: http://checkstyle.sourceforge.net/config_design.html#HideUtilityClassConstructor -->\n<!--        <module name=\"VisibilityModifier\"> &lt;!&ndash; most members must be private http://checkstyle.sourceforge.net/config_design.html#VisibilityModifier &ndash;&gt;-->\n<!--            <property name=\"protectedAllowed\" value=\"true\"/>-->\n<!--            <property name=\"packageAllowed\" value=\"true\"/>-->\n<!--            <property name=\"allowPublicImmutableFields\" value=\"true\"/>-->\n<!--            <property name=\"allowPublicFinalFields\" value=\"true\"/>-->\n<!--            <property name=\"publicMemberPattern\" value=\"^TAG$|^CREATOR$\"/>-->\n<!--        </module>-->\n\n        <!-- Coding -->\n        <module name=\"CovariantEquals\"/> <!-- if you override equals with different type you must provide equals with same type: http://checkstyle.sourceforge.net/config_coding.html#CovariantEquals -->\n        <module name=\"DefaultComesLast\"/> <!-- in switch case default must be the last elem: http://checkstyle.sourceforge.net/config_coding.html#DefaultComesLast -->\n        <module name=\"EmptyStatement\"/> <!-- basically an empty semicolon: http://checkstyle.sourceforge.net/config_coding.html#EmptyStatement -->\n        <module name=\"EqualsHashCode\"/> <!-- if you implement equals, you must implement hashcode and vice versa: http://checkstyle.sourceforge.net/config_coding.html#EqualsHashCode -->\n        <module name=\"NoFinalizer\"/> <!-- Verifies there are no finalize() methods defined in a class: http://checkstyle.sourceforge.net/config_coding.html#NoFinalizer -->\n        <module name=\"FallThrough\"/> <!-- switch fallthrough with statement not allowed http://checkstyle.sourceforge.net/config_coding.html#FallThrough -->\n        <module name=\"IllegalInstantiation\"/> <!-- Must not use const of certain types (Activity, Fragment): http://checkstyle.sourceforge.net/config_coding.html#IllegalInstantiation -->\n\n        <!-- Size Limitations -->\n        <module name=\"ParameterNumber\"><!-- max params for method http://checkstyle.sourceforge.net/config_sizes.html#ParameterNumber -->\n            <property name=\"max\" value=\"10\"/>\n            <property name=\"ignoreOverriddenMethods\" value=\"true\"/>\n            <property name=\"tokens\" value=\"METHOD_DEF\"/>\n        </module>\n\n        <!-- Naming Conventions -->\n        <!--<module name=\"ConstantName\" /> for possible futer use-->\n\n        <!-- Whitespaces -->\n        <module name=\"EmptyLineSeparator\"> <!-- Checks for correct empty line placements, omit VAR token: http://checkstyle.sourceforge.net/config_whitespace.html#EmptyLineSeparator -->\n            <property name=\"allowMultipleEmptyLines\" value=\"false\"/>\n            <property name=\"tokens\" value=\"IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF, STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF\"/>\n        </module>\n        <module name=\"SingleSpaceSeparator\"/> <!-- Checks if a token is surrounded by whitespace: http://checkstyle.sourceforge.net/config_whitespace.html#SingleSpaceSeparator -->\n        <module name=\"GenericWhitespace\"/> <!-- Checks whitespaces with Java Generics <>: http://checkstyle.sourceforge.net/config_whitespace.html#GenericWhitespace -->\n        <module name=\"WhitespaceAround\"> <!-- Checks if a token is surrounded by whitespace: http://checkstyle.sourceforge.net/config_whitespace.html#WhitespaceAround -->\n            <property name=\"allowEmptyConstructors\" value=\"true\"/>\n            <property name=\"allowEmptyMethods\" value=\"true\"/>\n            <property name=\"allowEmptyTypes\" value=\"true\"/>\n            <property name=\"allowEmptyLambdas\" value=\"true\"/>\n            <property name=\"allowEmptyCatches\" value=\"true\"/>\n        </module>\n\n        <!-- Imports -->\n        <module name=\"RedundantImport\"/> <!-- e.g. double import statements: http://checkstyle.sourceforge.net/config_imports.html#RedundantImport -->\n        <module name=\"UnusedImports\"/> <!-- http://checkstyle.sourceforge.net/config_imports.html#UnusedImports -->\n        <module name=\"IllegalImport\"/> <!-- checks if import sun.* is used http://checkstyle.sourceforge.net/config_imports.html#IllegalImport -->\n\n        <!--  Custom -->\n        <module name=\"RegexpSinglelineJava\">\n            <property name=\"format\" value=\"\\.toLowerCase\\(\\)\"/>\n            <property name=\"ignoreComments\" value=\"true\"/>\n            <property name=\"message\" value=\"Should not use String#toLowerCase(), use String#toLowerCase(Locale.ROOT) instead.\"/>\n        </module>\n        <module name=\"RegexpSinglelineJava\">\n            <property name=\"format\" value=\"\\.toUpperCase\\(\\)\"/>\n            <property name=\"ignoreComments\" value=\"true\"/>\n            <property name=\"message\" value=\"Should not use String#toUpperCase(), use String#toUpperCase(Locale.ROOT) instead.\"/>\n        </module>\n    </module>\n</module>"
  },
  {
    "path": "config/jenkins/config-jenkins.sh",
    "content": "#!/usr/bin/env bash\n\nsed -i 's,//services.gradle.org/distributions/,//mirrors.cloud.tencent.com/gradle/,g' \"$PWD/gradle/wrapper/gradle-wrapper.properties\"\n\n"
  },
  {
    "path": "config/jenkins/dev/Jenkinsfile",
    "content": "pipeline {\n    agent any\n\n    environment {\n        HMCL_CI = '1'\n\n        VERSION_TYPE = 'dev'\n        MICROSOFT_AUTH_ID = credentials('microsoft_auth_id')\n        CURSEFORGE_API_KEY = credentials('curseforge_api_key')\n        HMCL_SIGNATURE_KEY = credentials('hmcl_signature_key')\n    }\n\n    stages {\n        stage('Build') {\n            steps {\n                sh 'bash ./config/jenkins/config-jenkins.sh'\n                sh './gradlew clean makeExecutables --stacktrace --no-daemon'\n                archiveArtifacts artifacts: 'HMCL/build/libs/*'\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "config/jenkins/stable/Jenkinsfile",
    "content": "pipeline {\n    agent any\n\n    environment {\n        HMCL_CI = '1'\n\n        VERSION_TYPE = 'stable'\n        MICROSOFT_AUTH_ID = credentials('microsoft_auth_id')\n        CURSEFORGE_API_KEY = credentials('curseforge_api_key')\n        HMCL_SIGNATURE_KEY = credentials('hmcl_signature_key')\n    }\n\n    stages {\n        stage('Build') {\n            steps {\n                sh 'bash ./config/jenkins/config-jenkins.sh'\n                sh './gradlew clean makeExecutables --stacktrace --no-daemon'\n                archiveArtifacts artifacts: 'HMCL/build/libs/*'\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "config/project.properties",
    "content": "#\n# Hello Minecraft! Launcher\n# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\nversionRoot=3.13\n"
  },
  {
    "path": "docs/Contributing.md",
    "content": "# Contributing Guide\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n**English** | 中文 ([简体](Contributing_zh.md), [繁體](Contributing_zh_Hant.md))\n<!-- #END LANGUAGE_SWITCHER -->\n\n## Build HMCL\n\n### Requirements\n\nTo build the HMCL launcher, you need to install JDK 17 (or higher). You can download it here: [Download Liberica JDK](https://bell-sw.com/pages/downloads/#jdk-25-lts).\n\nAfter installing the JDK, make sure the `JAVA_HOME` environment variable points to the required JDK directory.\nYou can check the JDK version that `JAVA_HOME` points to like this:\n\n<details>\n<summary>Windows</summary>\n\nPowerShell:\n\n```\nPS > & \"$env:JAVA_HOME/bin/java.exe\" -version\nopenjdk version \"25\" 2025-09-16 LTS\nOpenJDK Runtime Environment (build 25+37-LTS)\nOpenJDK 64-Bit Server VM (build 25+37-LTS, mixed mode, sharing)\n```\n\n</details>\n\n<details>\n<summary>Linux/FreeBSD</summary>\n\n```\n> $JAVA_HOME/bin/java -version\nopenjdk version \"25\" 2025-09-16 LTS\nOpenJDK Runtime Environment (build 25+37-LTS)\nOpenJDK 64-Bit Server VM (build 25+37-LTS, mixed mode, sharing)\n```\n\n</details>\n\n<details>\n<summary>macOS</summary>\n\n```\n> /usr/libexec/java_home --exec java -version\nopenjdk version \"25\" 2025-09-16 LTS\nOpenJDK Runtime Environment (build 25+37-LTS)\nOpenJDK 64-Bit Server VM (build 25+37-LTS, mixed mode, sharing)\n```\n\n</details>\n\n### Get HMCL Source Code\n\n- You can get the latest source code via [Git](https://git-scm.com/downloads):\n  ```shell\n  git clone https://github.com/HMCL-dev/HMCL.git\n  cd HMCL\n  ```\n- You can manually download a specific version of the source code from the [GitHub Release page](https://github.com/HMCL-dev/HMCL/releases).\n\n### Build HMCL\n\nTo build HMCL, switch to the root directory of the HMCL project and run the following command:\n\n```shell\n./gradlew clean makeExecutables\n```\n\nThe built HMCL program files are located in the `HMCL/build/libs` subdirectory under the project root.\n\n## Debug Options\n\n> [!WARNING]\n> This document describes HMCL's internal features, which we do not guarantee to be stable and may be modified or removed at any time.\n>\n> Please use these features with caution, as improper use may cause HMCL to behave abnormally or even crash.\n\nHMCL provides a series of debug options to control the behavior of the launcher.\n\nThese options can be specified via environment variables or JVM parameters. If both are present, JVM parameters will override the environment variable settings.\n\n| Environment Variable        | JVM Parameter                                | Function                                                  | Default Value                                                                                               | Additional Notes          |\n|-----------------------------|----------------------------------------------|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|---------------------------|\n| `HMCL_JAVA_HOME`            |                                              | Specifies the Java used to launch HMCL                    |                                                                                                             | Only effective for exe/sh |\n| `HMCL_JAVA_OPTS`            |                                              | Specifies the default JVM parameters when launching HMCL  |                                                                                                             | Only effective for exe/sh |\n| `HMCL_FORCE_GPU`            |                                              | Specifies whether to force GPU-accelerated rendering      | `false`                                                                                                     |                           |\n| `HMCL_ANIMATION_FRAME_RATE` |                                              | Specifies the animation frame rate of HMCL                | `60`                                                                                                        |                           |\n| `HMCL_LANGUAGE`             |                                              | Specifies the default language of HMCL                    | Uses the system default language                                                                            |                           |\n| `HMCL_UI_SCALE`             |                                              | Specifies the UI scaling for HMCL                         | Uses the system's current scaling                                                                           | Supports scale factor (1.5), percentage (150%), or DPI (144dpi).                          |\n|                             | `-Dhmcl.dir=<path>`                          | Specifies the current data folder of HMCL                 | `./.hmcl`                                                                                                   |                           |\n|                             | `-Dhmcl.home=<path>`                         | Specifies the user data folder of HMCL                    | Windows: `%APPDATA%\\.hmcl`<br>Linux/BSD: `$XDG_DATA_HOME/hmcl`<br>macOS: `~Library/Application Support/hmcl` |                           |\n|                             | `-Dhmcl.self_integrity_check.disable=true`   | Disables self-integrity checks during updates             |                                                                                                             |                           |\n|                             | `-Dhmcl.bmclapi.override=<url>`              | Specifies the API Root for BMCLAPI                        | `https://bmclapi2.bangbang93.com`                                                                           |                           |\n|                             | `-Dhmcl.discoapi.override=<url>`             | Specifies the API Root for foojay Disco API               | `https://api.foojay.io/disco/v3.0`                                                                          |                           | \n| `HMCL_FONT`                 | `-Dhmcl.font.override=<font family>`         | Specifies the default font for HMCL                       | Uses the system default font                                                                                |                           |\n|                             | `-Dhmcl.update_source.override=<url>`        | Specifies the update source for HMCL                      | `https://hmcl.huangyuhui.net/api/update_link`                                                               |                           |\n|                             | `-Dhmcl.authlibinjector.location=<path>`     | Specifies the location of the authlib-injector JAR file   | Uses the built-in authlib-injector                                                                          |                           |\n|                             | `-Dhmcl.openjfx.repo=<maven repository url>` | Adds a custom Maven repository for downloading OpenJFX    |                                                                                                             |                           |\n|                             | `-Dhmcl.native.encoding=<encoding>`          | Specifies the native encoding                             | Uses the system's native encoding                                                                           |                           |\n|                             | `-Dhmcl.microsoft.auth.id=<App ID>`          | Specifies the Microsoft OAuth App ID                      | Uses the built-in Microsoft OAuth App ID                                                                    |                           |\n|                             | `-Dhmcl.curseforge.apikey=<Api Key>`         | Specifies the CurseForge API key                          | Uses the built-in CurseForge API key                                                                        |                           |\n|                             | `-Dhmcl.native.backend=<auto/jna/none>`      | Specifies the native backend used by HMCL                 | `auto`                                                                                                      |                           |\n|                             | `-Dhmcl.hardware.fastfetch=<true/false>`     | Specifies whether to use fastfetch for hardware detection | `true`                                                                                                      |                           |\n"
  },
  {
    "path": "docs/Contributing_zh.md",
    "content": "# 贡献指南\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n[English](Contributing.md) | **中文** (**简体**, [繁體](Contributing_zh_Hant.md))\n<!-- #END LANGUAGE_SWITCHER -->\n\n## 构建 HMCL\n\n### 环境需求\n\n构建 HMCL 启动器需要安装 JDK 17 (或更高版本)。你可以从此处下载它: [Download Liberica JDK](https://bell-sw.com/pages/downloads/#jdk-25-lts)。\n\n在安装 JDK 后，请确保 `JAVA_HOME` 环境变量指向符合需求的 JDK 目录。\n你可以这样查看 `JAVA_HOME` 指向的 JDK 版本:\n\n<details>\n<summary>Windows</summary>\n\nPowerShell:\n```\nPS > & \"$env:JAVA_HOME/bin/java.exe\" -version\nopenjdk version \"25\" 2025-09-16 LTS\nOpenJDK Runtime Environment (build 25+37-LTS)\nOpenJDK 64-Bit Server VM (build 25+37-LTS, mixed mode, sharing)\n```\n\n</details>\n\n<details>\n<summary>Linux/FreeBSD</summary>\n\n```\n> $JAVA_HOME/bin/java -version\nopenjdk version \"25\" 2025-09-16 LTS\nOpenJDK Runtime Environment (build 25+37-LTS)\nOpenJDK 64-Bit Server VM (build 25+37-LTS, mixed mode, sharing)\n```\n\n</details>\n\n<details>\n<summary>macOS</summary>\n\n```\n> /usr/libexec/java_home --exec java -version\nopenjdk version \"25\" 2025-09-16 LTS\nOpenJDK Runtime Environment (build 25+37-LTS)\nOpenJDK 64-Bit Server VM (build 25+37-LTS, mixed mode, sharing)\n```\n\n</details>\n\n### 获取 HMCL 源码\n\n- 通过 [Git](https://git-scm.com/downloads) 可以获取最新源码:\n  ```shell\n  git clone https://github.com/HMCL-dev/HMCL.git\n  cd HMCL\n  ```\n- 从 [GitHub Release 页面](https://github.com/HMCL-dev/HMCL/releases)可以手动下载特定版本的源码。\n\n### 构建 HMCL\n\n想要构建 HMCL，请切换到 HMCL 项目的根目录下，并执行以下命令:\n\n```shell\n./gradlew clean makeExecutables\n```\n\n构建出的 HMCL 程序文件位于根目录下的 `HMCL/build/libs` 子目录中。\n\n## 调试选项\n\n> [!WARNING]\n> 本文介绍的是 HMCL 的内部功能，我们不保证这些功能的稳定性，并且随时可能修改或删除这些功能。\n>\n> 使用这些功能时请务必小心，错误地使用这些功能可能会导致 HMCL 行为异常甚至崩溃。\n\nHMCL 提供了一系列调试选项，用于控制启动器的行为。\n\n这些选项可以通过环境变量或 JVM 参数指定。如果两者同时存在，那么 JVM 参数会覆盖环境变量的设置。\n\n| 环境变量                        | JVM 参数                                       | 功能                             | 默认值                                                                                                         | 额外说明         |\n|-----------------------------|----------------------------------------------|--------------------------------|-------------------------------------------------------------------------------------------------------------|--------------|\n| `HMCL_JAVA_HOME`            |                                              | 指定用于启动 HMCL 的 Java             |                                                                                                             | 仅对 exe/sh 生效 |\n| `HMCL_JAVA_OPTS`            |                                              | 指定启动 HMCL 时的默认 JVM 参数          |                                                                                                             | 仅对 exe/sh 生效 |\n| `HMCL_FORCE_GPU`            |                                              | 指定是否强制使用 GPU 加速渲染              | `false`                                                                                                     |\n| `HMCL_ANIMATION_FRAME_RATE` |                                              | 指定 HMCL 的动画帧率                  | `60`                                                                                                        |              |\n| `HMCL_LANGUAGE`             |                                              | 指定 HMCL 的默认语言                  | 使用系统默认语言                                                                                                    |\n| `HMCL_UI_SCALE`             |                                              | 指定 HMCL 的 UI 缩放比例                 | 遵循系统当前的缩放比例                                                                                       | 支持倍数 (1.5)、百分比 (150%) 或 DPI (144dpi) |\n|                             | `-Dhmcl.dir=<path>`                          | 指定 HMCL 的当前数据文件夹               | `./.hmcl`                                                                                                   |              |\n|                             | `-Dhmcl.home=<path>`                         | 指定 HMCL 的用户数据文件夹               | Windows: `%APPDATA%\\.hmcl`<br>Linux/BSD: `$XDG_DATA_HOME/hmcl`<br>macOS: `~Library/Application Support/hmcl` |              |\n|                             | `-Dhmcl.self_integrity_check.disable=true`   | 检查更新时不检查本体完整性                  |                                                                                                             |              |\n|                             | `-Dhmcl.bmclapi.override=<url>`              | 指定 BMCLAPI 的 API Root          | `https://bmclapi2.bangbang93.com`                                                                           |              |\n|                             | `-Dhmcl.discoapi.override=<url>`             | 指定 foojay Disco API 的 API Root | `https://api.foojay.io/disco/v3.0`                                                                          |\n| `HMCL_FONT`                 | `-Dhmcl.font.override=<font family>`         | 指定 HMCL 默认字体                   | 使用系统默认字体                                                                                                    |              |\n|                             | `-Dhmcl.update_source.override=<url>`        | 指定 HMCL 更新源                    | `https://hmcl.huangyuhui.net/api/update_link`                                                               |              |\n|                             | `-Dhmcl.authlibinjector.location=<path>`     | 指定 authlib-injector JAR 文件的位置  | 使用 HMCL 内嵌的 authlib-injector                                                                                |              |\n|                             | `-Dhmcl.openjfx.repo=<maven repository url>` | 添加用于下载 OpenJFX 的自定义 Maven 仓库   |                                                                                                             |              |\n|                             | `-Dhmcl.native.encoding=<encoding>`          | 指定原生编码                         | 使用系统的本机编码                                                                                                   |              |\n|                             | `-Dhmcl.microsoft.auth.id=<App ID>`          | 指定 Microsoft OAuth App ID      | 使用 HMCL 内置的 Microsoft OAuth App ID                                                                          |              |\n|                             | `-Dhmcl.curseforge.apikey=<Api Key>`         | 指定 CurseForge API 密钥           | 使用 HMCL 内置的 CurseForge API 密钥                                                                               |              |\n|                             | `-Dhmcl.native.backend=<auto/jna/none>`      | 指定HMCL使用的本机后端                  | `auto`                                                                                                      |\n|                             | `-Dhmcl.hardware.fastfetch=<true/false>`     | 指定是否使用 fastfetch 检测硬件信息        | `true`                                                                                                      |\n\n"
  },
  {
    "path": "docs/Contributing_zh_Hant.md",
    "content": "# 貢獻指南\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n[English](Contributing.md) | **中文** ([简体](Contributing_zh.md), **繁體**)\n<!-- #END LANGUAGE_SWITCHER -->\n\n## 構建 HMCL\n\n### 環境需求\n\n構建 HMCL 啟動器需要安裝 JDK 17 (或更高版本)。你可以從此處下載它: [Download Liberica JDK](https://bell-sw.com/pages/downloads/#jdk-25-lts)。\n\n在安裝 JDK 後，請確保 `JAVA_HOME` 環境變數指向符合需求的 JDK 目錄。\n你可以這樣查看 `JAVA_HOME` 指向的 JDK 版本:\n\n<details>\n<summary>Windows</summary>\n\nPowerShell:\n```\nPS > & \"$env:JAVA_HOME/bin/java.exe\" -version\nopenjdk version \"25\" 2025-09-16 LTS\nOpenJDK Runtime Environment (build 25+37-LTS)\nOpenJDK 64-Bit Server VM (build 25+37-LTS, mixed mode, sharing)\n```\n\n</details>\n\n<details>\n<summary>Linux/FreeBSD</summary>\n\n```\n> $JAVA_HOME/bin/java -version\nopenjdk version \"25\" 2025-09-16 LTS\nOpenJDK Runtime Environment (build 25+37-LTS)\nOpenJDK 64-Bit Server VM (build 25+37-LTS, mixed mode, sharing)\n```\n\n</details>\n\n<details>\n<summary>macOS</summary>\n\n```\n> /usr/libexec/java_home --exec java -version\nopenjdk version \"25\" 2025-09-16 LTS\nOpenJDK Runtime Environment (build 25+37-LTS)\nOpenJDK 64-Bit Server VM (build 25+37-LTS, mixed mode, sharing)\n```\n\n</details>\n\n### 獲取 HMCL 原始碼\n\n- 透過 [Git](https://git-scm.com/downloads) 可以獲取最新原始碼:\n  ```shell\n  git clone https://github.com/HMCL-dev/HMCL.git\n  cd HMCL\n  ```\n- 從 [GitHub Release 頁面](https://github.com/HMCL-dev/HMCL/releases)可以手動下載特定版本的原始碼。\n\n### 構建 HMCL\n\n想要構建 HMCL，請切換到 HMCL 專案的根目錄下，並執行以下指令:\n\n```shell\n./gradlew clean makeExecutables\n```\n\n構建出的 HMCL 程式檔位於根目錄下的 `HMCL/build/libs` 子目錄中。\n\n## 除錯選項\n\n> [!WARNING]\n> 本文介紹的是 HMCL 的內部功能，我們不保證這些功能的穩定性，並且隨時可能修改或刪除這些功能。\n>\n> 使用這些功能時請務必小心，錯誤地使用這些功能可能會導致 HMCL 行為異常甚至崩潰。\n\nHMCL 提供了一系列除錯選項，用於控制啟動器的行為。\n\n這些選項可以透過環境變數或 JVM 參數設定。如果兩者同時存在，那麼 JVM 參數會覆蓋環境變數的設定。\n\n| 環境變數                        | JVM 參數                                       | 功能                             | 預設值                                                                                                         | 額外說明         |\n|-----------------------------|----------------------------------------------|--------------------------------|-------------------------------------------------------------------------------------------------------------|--------------|\n| `HMCL_JAVA_HOME`            |                                              | 設定用於開啟 HMCL 的 Java             |                                                                                                             | 僅對 exe/sh 生效 |\n| `HMCL_JAVA_OPTS`            |                                              | 設定開啟 HMCL 時的預設 JVM 參數          |                                                                                                             | 僅對 exe/sh 生效 |\n| `HMCL_FORCE_GPU`            |                                              | 設定是否強制使用 GPU 加速繪製              | `false`                                                                                                     |\n| `HMCL_ANIMATION_FRAME_RATE` |                                              | 設定 HMCL 的動畫幀率                  | `60`                                                                                                        |              |\n| `HMCL_LANGUAGE`             |                                              | 設定 HMCL 的預設語言                  | 使用系統預設語言                                                                                                    |\n| `HMCL_UI_SCALE`             |                                              | 設定 HMCL 的 UI 縮放比例               | 遵循系統目前的縮放比例                                                                                       | 支援倍數 (1.5)、百分比 (150%) 或 DPI (144dpi) |\n|                             | `-Dhmcl.dir=<path>`                          | 設定 HMCL 的目前資料存放位置               | `./.hmcl`                                                                                                   |              |\n|                             | `-Dhmcl.home=<path>`                         | 設定 HMCL 的使用者資料存放位置               | Windows: `%APPDATA%\\.hmcl`<br>Linux/BSD: `$XDG_DATA_HOME/hmcl`<br>macOS: `~Library/Application Support/hmcl` |              |\n|                             | `-Dhmcl.self_integrity_check.disable=true`   | 檢查更新時不檢查程式完整性                  |                                                                                                             |              |\n|                             | `-Dhmcl.bmclapi.override=<url>`              | 設定 BMCLAPI 的 API Root          | `https://bmclapi2.bangbang93.com`                                                                           |              |\n|                             | `-Dhmcl.discoapi.override=<url>`             | 設定 foojay Disco API 的 API Root | `https://api.foojay.io/disco/v3.0`                                                                          |\n| `HMCL_FONT`                 | `-Dhmcl.font.override=<font family>`         | 設定 HMCL 預設字體                   | 使用系統預設字體                                                                                                    |              |\n|                             | `-Dhmcl.update_source.override=<url>`        | 設定 HMCL 更新來源                    | `https://hmcl.huangyuhui.net/api/update_link`                                                               |              |\n|                             | `-Dhmcl.authlibinjector.location=<path>`     | 設定 authlib-injector JAR 檔的位置  | 使用 HMCL 內置的 authlib-injector                                                                                |              |\n|                             | `-Dhmcl.openjfx.repo=<maven repository url>` | 添加用於下載 OpenJFX 的自訂 Maven 倉庫   |                                                                                                             |              |\n|                             | `-Dhmcl.native.encoding=<encoding>`          | 設定原生編碼                         | 使用系統的本機編碼                                                                                                   |              |\n|                             | `-Dhmcl.microsoft.auth.id=<App ID>`          | 設定 Microsoft OAuth App ID      | 使用 HMCL 內建的 Microsoft OAuth App ID                                                                          |              |\n|                             | `-Dhmcl.curseforge.apikey=<Api Key>`         | 設定 CurseForge API 金鑰           | 使用 HMCL 內建的 CurseForge API 金鑰                                                                               |              |\n|                             | `-Dhmcl.native.backend=<auto/jna/none>`      | 設定 HMCL 使用的本機後端                  | `auto`                                                                                                      |\n|                             | `-Dhmcl.hardware.fastfetch=<true/false>`     | 設定是否使用 fastfetch 檢測硬體資訊        | `true`                                                                                                      |\n\n"
  },
  {
    "path": "docs/Localization.md",
    "content": "# Localization\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n**English** | 中文 ([简体](Localization_zh.md), [繁體](Localization_zh_Hant.md))\n<!-- #END LANGUAGE_SWITCHER -->\n\nHMCL provides localization support for multiple languages.\n\nThis document describes HMCL's support status for these languages and provides a guide for contributors who want to help with HMCL localization.\n\n## Supported languages\n\nCurrently, HMCL supports the following languages:\n\n| Language              | Language Tag | Preferred Localization Key | Preferred Localization File Suffix | [Game Language Files](https://minecraft.wiki/w/Language) | Support Status | Volunteers                                |\n|-----------------------|--------------|----------------------------|------------------------------------|----------------------------------------------------------|----------------|-------------------------------------------|\n| English               | `en`         | `default`                  | (empty)                            | `en_us`                                                  | **Primary**    | [Glavo](https://github.com/Glavo)         |\n| English (Upside Down) | `en-Qabs`    | `en-Qabs`                  | `en_Qabs`                          | `en_ud`                                                  | Automatic      |                                           |\n| Chinese (Simplified)  | `zh-Hans`    | `zh`                       | `_zh`                              | `zh_cn`                                                  | **Primary**    | [Glavo](https://github.com/Glavo)         |\n| Chinese (Traditional) | `zh-Hant`    | `zh-Hant`                  | `_zh_Hant`                         | `zh_tw` <br/> `zh_hk`                                    | **Primary**    | [Glavo](https://github.com/Glavo)         |\n| Chinese (Classical)   | `lzh`        | `lzh`                      | `_lzh`                             | `lzh`                                                    | Secondary      |                                           |\n| Japanese              | `ja`         | `ja`                       | `_ja`                              | `ja_jp`                                                  | Secondary      |                                           |\n| Spanish               | `es`         | `es`                       | `_es`                              | `es_es`                                                  | Secondary      | [3gf8jv4dv](https://github.com/3gf8jv4dv) |\n| Russian               | `ru`         | `ru`                       | `_ru`                              | `ru_ru`                                                  | Secondary      | [3gf8jv4dv](https://github.com/3gf8jv4dv) |\n| Ukrainian             | `uk`         | `uk`                       | `_uk`                              | `uk_ua`                                                  | Secondary      |                                           |\n\n<details>\n<summary>About Language Tags</summary>\n\nHMCL uses language tags that conform to the IETF BCP 47 standard.\n\nWhen choosing language tags, we follow these principles:\n\n1. For languages defined in the ISO 639 standard, if a tag has already been registered in the [IANA Language Subtag Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry),\n   we always use the registered tag.\n\n   For example, for English, we use `en` instead of `eng` as the language code.\n\n2. For non-standard languages defined by Minecraft, the code defined in the language file's `language.code` should be preferred over the game language file's name.\n\n   This is because Minecraft sometimes uses real-world country/region codes to represent joke languages \n   (for example, Pirate English uses the language file `en_pt`, but `PT` is actually the country code for Portugal).\n\n   For example, for Upside down English, we use `en-Qabs` as the language code instead of `en-UD`.\n\n</details>\n\n<details>\n<summary>About Localization Keys and File Suffixes</summary>\n\nLocalization file suffixes and keys are used to name [localization resources](#localization-resources).\n\nGenerally, the localization key is the language code for the resource, and the localization file suffix is obtained by replacing `-` with `_` in the language code and adding a leading underscore.\n\nAs a special case, for default resources, the localization key is `default` and the file suffix is empty.\n\nDue to the existence of the [resource fallback mechanism](#resource-fallback-mechanism),\nif there is no resource that exactly matches the current locale, \nHMCL will derive a search list from the current language tag and search for resources in order.\n\nWe recommend always providing a default resource (with the `default` key and empty file suffix) when providing localization resources,\nto ensure all users can load resources properly.\n\nWe also recommend using broader language tags for localization resources whenever possible,\nso users are less likely to fall back to the default resource.\n\nFor example, if you provide a Simplified Chinese localization resource, \nwe recommend using `zh` as the localization key instead of the more specific `zh-Hans`,\nso it will apply to all Chinese users and avoid falling back to the default resource for them.\n\nIf you want to provide both Simplified and Traditional Chinese resources, \nit is recommended to use the broader `zh` as the key for the resource with more users (as the default Chinese resource),\nand use the more specific `zh-Hans`/`zh-Hant` as the key for the resource with fewer users.\n\n</details>\n\nHMCL requires all pull requests that update documentation and localization resources to also update the resources\nfor all **primary** supported languages.\nIf the PR author is not familiar with the relevant languages, they can request translation help in the comments,\nand maintainers will help translate these texts before merging the PR.\n\nFor **secondary** supported languages, we cannot guarantee that these localization resources will always be updated in sync.\nWe need collaborators who are proficient in these languages to help with maintenance.\n\nWe will record volunteers willing to help maintain these language resources in the documentation.\nIf contributors want to have new localization texts translated into these languages in a timely manner,\nthey can @ these volunteers in the PR for help.\n\nIf you are willing to help maintain localization resources for any language, please open a PR\nand add your GitHub username to the volunteer list above.\nWe greatly appreciate your help.\n\n## Adding Support for a New Language\n\nHMCL welcomes anyone to participate in translation and contribution. \nHowever, maintaining translations for more languages requires more maintenance effort, so we have some requirements for newly added languages.\nPlease confirm the following requirements before contributing:\n\n- We prioritize [languages officially supported by Minecraft](https://minecraft.wiki/w/Language).\n\n  Unless there are special reasons, we do not provide support for languages not yet supported by Minecraft.\n- We hope to provide long-term maintenance support for all languages.\n\n  Since the maintainers of this project are proficient in only a limited number of languages, \n  to avoid support for new languages quickly becoming outdated due to lack of maintainers,\n  we hope to find people proficient in the language to help us maintain the newly added localization files in the long term.\n  If there may be a lack of long-term maintainers, we will be more cautious about adding support for that language.\n\nWe recommend that contributors submit a [feature request](https://github.com/HMCL-dev/HMCL/issues/new?template=feature.yml) before providing a new language translation,\ndiscuss with other contributors first, and determine the future maintenance plan before starting the translation work.\n\n### Getting Started with Translation\n\nIf you want to add support for a new language to HMCL, please start by translating [`I18N.properties`](../HMCL/src/main/resources/assets/lang/I18N.properties).\nThe vast majority of HMCL's texts are in this file, and translating it will translate the entire interface.\n\nThis is a Java Properties file, which is very simple in format.\nBefore translating, please read the introduction to this format: [Properties file](https://en.wikipedia.org/wiki/.properties).\n\nAs the first step of translation, please look up the two- or three-letter language tag for your language in [this table](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry).\nFor example, the language tag for English is `en`.\n\nAfter determining the language tag, please create a file named `I18N_<localization file suffix>.properties` (e.g., `I18N_en.properties`) next to the [`I18N.properties` file](../HMCL/src/main/resources/assets/lang).\nThen you can start translating in this file.\n\nThe `I18N.properties` file follows the [resource fallback mechanism](#resource-fallback-mechanism) to look up missing translations.\nThat is, you can translate entry by entry, and any untranslated entries will automatically fall back to English.\n\nAfter translating part of the file, you can [build HMCL yourself](./Contributing.md#build-hmcl), \nand your translations will be included in the compiled HMCL.\nIf your computer's default environment is not the target language,\nyou can set the environment variable `HMCL_LANGUAGE` to the language tag you just found from the table,\nand HMCL will automatically switch to that language.\n\nAt this point, you can push the file to GitHub and submit a PR to HMCL.\nThe maintainers of HMCL will complete the remaining steps for you.\n\n## Localization Resources\n\nAll documentation and localization resource files follow the naming rule `<resource name><localization file suffix>.<extension>`.\n\nFor example, for `README.md`, the localized versions in different languages are named as follows:\n\n- English: `README.md`\n- Chinese (Simplified): `README_zh.md`\n- Chinese (Traditional): `README_zh_Hant.md`\n- Chinese (Classical): `README_lzh.md`\n\nIn addition to localized files, HMCL also supports localizing certain text fields in JSON. Localized text in JSON uses the following format:\n\n```json5\n{\n    \"<Localization Key 1>\": \"<Localized Text 1>\",\n    \"<Localization Key 2>\": \"<Localized Text 2>\",\n    // ...\n    \"<Localization Key N>\": \"<Localized Text N>\"\n}\n```\n\nFor example, for the following text field:\n\n```json\n{\n    \"meow\": \"Meow\"\n}\n```\n\nIt can be rewritten as localized text:\n\n```json\n{\n    \"meow\": {\n        \"default\": \"Meow\",\n        \"zh\": \"喵呜\",\n        \"zh-Hant\": \"喵嗚\"\n    }\n}\n```\n\n## Resource Fallback Mechanism\n\nFor missing resources in a certain language, HMCL supports a resource fallback mechanism,\nwhich derives a search list based on different language tags and searches for resources in order according to this list.\n\nBefore searching, we first refine the language tag through the following steps.\n\n1. Normalize Language Codes\n\n   If the language code subtag in the current language tag is not registered in the IANA Language Subtag Registry, \n   HMCL will try to map it to a registered tag.\n\n   For example, HMCL will replace the language code `eng` with `en`.\n\n2. Map Macrolanguages to Individual Languages\n\n   If the current language code is an [ISO 639 macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage), \n   and the macrolanguage usually refers to a specific individual language, HMCL will replace it with that individual language.\n\n   For example, `zh` (Chinese) usually actually refers to `cmn` (Mandarin), so we replace the language code `zh` with `cmn`.\n\n3. Derive Script\n\n   If the current language tag does not specify a script, HMCL will try to derive the script according to the following rules in order:\n\n    1. If the current language tag specifies a variant, and the variant is registered in the IANA Language Subtag Registry, \n       and all its `Prefix` entries in the registry contain the same script, then set the current script to that script.\n\n       For example, if the current variant is `pinyin` (Chinese Pinyin), the script will be set to `Latn` (Latin).\n\n    2. If the current language code is assigned a `Suppress-Script` in the IANA Language Subtag Registry, set the current script to that script.\n\n       For example, if the current language code is `en` (English), the script will be set to `Latn` (Latin); \n       if the code is `ru` (Russian), the script will be set to `Cyrl` (Cyrillic).\n\n    3. If the current language code is `lzh` (Classical Chinese), set the script to `Hant` (Traditional Chinese Characters).\n\n    4. If the current language code is `zh` or a sublanguage of `zh`, check if the current region code is one of `TW`, `HK`, or `MO`. \n       If true, set the script to `Hant` (Traditional Chinese Characters);\n       otherwise, set it to `Hans` (Simplified Chinese Characters).\n\nAfter refining the language code, HMCL will derive a list of language tags based on this language tag.\n\nFor example, for the language tag `en-US`, HMCL will refine it to `en-Latn-US` and derive the following search list:\n\n1. `en-Latn-US`\n2. `en-Latn`\n3. `en-US`\n4. `en`\n5. `und`\n\nFor the language tag `zh-CN`, HMCL will refine it to `cmn-Hans-CN` and derive the following search list:\n\n1. `cmn-Hans-CN`\n2. `cmn-Hans`\n3. `cmn-CN`\n4. `cmn`\n5. `zh-Hans-CN`\n6. `zh-Hans`\n7. `zh-CN`\n8. `zh`\n9. `und`\n\nFor resources that can be merged (such as `.properties` files), \nHMCL will merge resources according to the priority of this list; for resources that are difficult to merge (such as font files), \nHMCL will load the highest-priority resource found in this list.\n\nIf the current language uses a three-letter ISO 639 code, but there is also a corresponding two-letter code, \nHMCL will map it to the two-letter code before searching for resources.\n\nFor example, if the current environment's language tag is `eng-US`, \nHMCL will map it to `en-US` and then search for localization resources according to the above rules.\n\n### Additional Rules for Chinese\n\nHMCL always adds `zh-CN` to the search list for all Chinese environments, \nand adds `zh-TW` to the search list for all Traditional Chinese environments.\n\nBelow are the localization resource search lists for several common Chinese environments.\n\n- `zh-CN`:\n    1. `cmn-Hans-CN`\n    2. `cmn-Hans`\n    3. `cmn-CN`\n    4. `cmn`\n    5. `zh-Hans-CN`\n    6. `zh-Hans`\n    7. `zh-CN`\n    8. `zh`\n    9. `und`\n- `zh-SG`:\n    1. `cmn-Hans-SG`\n    2. `cmn-Hans`\n    3. `cmn-SG`\n    4. `cmn`\n    5. `zh-Hans-SG`\n    6. `zh-Hans`\n    7. `zh-SG`\n    8. `zh-CN`\n    9. `zh`\n    10. `und`\n- `zh-TW`:\n    1. `zh-Hant-TW`\n    2. `zh-Hant`\n    3. `zh-TW`\n    4. `zh`\n    5. `zh-CN`\n    6. `und`\n- `zh-HK`:\n    1. `cmn-Hant-HK`\n    2. `cmn-Hant`\n    3. `cmn-HK`\n    4. `cmn`\n    5. `zh-Hant-HK`\n    6. `zh-Hant`\n    7. `zh-HK`\n    8. `zh-TW`\n    9. `zh`\n    10. `zh-CN`\n    11. `und`\n- `lzh`:\n    1. `lzh-Hant`\n    2. `lzh`\n    3. `zh-Hant`\n    4. `zh-TW`\n    5. `zh`\n    6. `zh-CN`\n    7. `und`\n\n## Automatic Synchronization of Documentation Content\n\n<!-- #BEGIN BLOCK -->\n<!-- #PROPERTY PROCESS_LINK=false -->\nTo simplify documentation maintenance, HMCL uses a macro mechanism to automatically maintain parts of the documentation content.\nRun the following command in the terminal:\n\n```bash\n./gradlew updateDocuments\n```\n\nThis will automatically update all documentation content.\n\nFor example, to create links for switching between different language versions of the same document, \nadd the following content under the document title:\n\n```markdown\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n<!-- #END LANGUAGE_SWITCHER -->\n```\n\nAfter running `./gradlew updateDocuments`, these two lines will be automatically replaced with language switcher links like the following:\n\n```markdown\n**English** (**Standard**, [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)\n```\n\nFor more about macros, see [MacroProcessor.java](../buildSrc/src/main/java/org/jackhuang/hmcl/gradle/docs/MacroProcessor.java).\n<!-- #END BLOCK -->\n"
  },
  {
    "path": "docs/Localization_zh.md",
    "content": "# 本地化\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n[English](Localization.md) | **中文** (**简体**, [繁體](Localization_zh_Hant.md))\n<!-- #END LANGUAGE_SWITCHER -->\n\nHMCL 为多种语言提供本地化支持。\n\n本文档介绍了 HMCL 对这些语言的支持状态，并给想要为 HMCL 的本地化工作做出贡献的贡献者提供了一份指南。\n\n## 支持的语言\n\n目前，HMCL 为这些语言提供支持:\n\n| 语言      | 语言标签      | 首选本地化键    | 首选本地化文件后缀  | [游戏语言文件](https://minecraft.wiki/w/Language) | 支持状态   | 志愿者                                       | \n|---------|-----------|-----------|------------|---------------------------------------------|--------|-------------------------------------------|\n| 英语      | `en`      | `default` | (空)        | `en_us`                                     | **主要** | [Glavo](https://github.com/Glavo)         |  \n| 英语 (颠倒) | `en-Qabs` | `en-Qabs` | `en_Qabs`  | `en_ud`                                     | 自动     |                                           |  \n| 中文 (简体) | `zh-Hans` | `zh`      | `_zh`      | `zh_cn`                                     | **主要** | [Glavo](https://github.com/Glavo)         |\n| 中文 (繁体) | `zh-Hant` | `zh-Hant` | `_zh_Hant` | `zh_tw` <br/> `zh_hk`                       | **主要** | [Glavo](https://github.com/Glavo)         |\n| 中文 (文言) | `lzh`     | `lzh`     | `_lzh`     | `lzh`                                       | 次要     |                                           |\n| 日语      | `ja`      | `ja`      | `_ja`      | `ja_jp`                                     | 次要     |                                           |\n| 西班牙语    | `es`      | `es`      | `_es`      | `es_es`                                     | 次要     | [3gf8jv4dv](https://github.com/3gf8jv4dv) |\n| 俄语      | `ru`      | `ru`      | `_ru`      | `ru_ru`                                     | 次要     | [3gf8jv4dv](https://github.com/3gf8jv4dv) |\n| 乌克兰语    | `uk`      | `uk`      | `_uk`      | `uk_ua`                                     | 次要     |                                           |\n\n<details>\n<summary>关于语言标签</summary>\n\nHMCL 使用符合 IETF BCP 47 规范的语言标签。\n\n在选择语言标签时，我们会遵循以下原则:\n\n1. 对于 ISO 639 标准中定义的语言，如果已经在 [IANA 语言子标签注册表](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)中注册，我们总是使用经过注册的标签。\n\n   例如，对于英语，我们使用 `en` 而不是 `eng` 作为语言代码。\n\n2. 对于 Minecraft 所定义的非标准语言，应当优先使用语言文件的 `language.code` 中定义的代码，而非游戏语言文件的名称。\n\n   这是因为 Minecraft 有时候会用现实中实际存在的国家/地区代码来表示虚构语言 (比如说海盗英语的语言文件为 `en_pt`，但 `PT` 其实是葡萄牙的国家代码)。\n\n   例如，对于颠倒的英语，我们使用 `en-Qabs` 作为语言代码，而不是 `en-UD`。\n\n</details>\n\n<details>\n<summary>关于本地化键和本地化文件后缀</summary>\n\n本地化文件后缀和本地化键用于为[本地化资源](#本地化资源)命名。\n\n通常来说，本地化键就是这份本地化资源对应的语言代码，而本地化文件后缀是将语言代码中的 `-` 替换为 `_`，并加上一个前缀下划线得到的。\n\n作为特例，对于默认的资源，本地化键为 `default`，本地化文件后缀为空。\n\n由于[资源回退机制](#资源回退机制)的存在。\n如果没有完全匹配当前语言环境的资源，HMCL 会根据当前环境的语言标签推导出一个搜索列表，根据该列表依次搜索资源。\n\n我们建议在提供本地化资源时，总是提供默认资源 (对应 `default` 本地化键和空的本地化文件后缀)，\n以确保所有用户都能正常加载资源。\n\n并且我们建议尽可能为本地化资源使用更宽泛的语言标签，使用户更不容易回退到默认资源上。\n\n例如，如果你提供了一份简体中文的本地化资源，那么我们推荐使用 `zh` 作为本地化键，而不是更具体的 `zh-Hans`，\n这样它会对于所有使用中文的用户生效，避免对于这些用户回退到默认资源上。\n\n如果你想同时提供简体中文和繁体中文的资源，那么推荐对用户占比更高的资源使用更宽泛的 `zh` 作为本地化键，使其作为默认的中文资源，\n而对用户占比更低的资源使用更具体的 `zh-Hans`/`zh-Hant` 作为本地化键。\n\n</details>\n\nHMCL 会要求所有 Pull Request 在更新文档和本地化资源时同步更新所有**主要**支持的语言对应的资源。\n如果 PR 作者对相关语言并不了解，那么可以直接在评论中提出翻译请求，\n维护者会在合并 PR 前帮助 PR 作者翻译这些文本。\n\n而对于**次要**支持的语言，我们不能保证这些本地化资源总是会同步更新。\n我们需要熟练掌握这些语言的协作者帮助我们进行维护。\n\n我们会在文档中记录愿意帮助我们维护这些语言本地化资源的志愿者。\n如果贡献者希望及时将新增的本地化文本翻译至这些语言，\n那么可以在 PR 中 @ 这些志愿者寻求帮助。\n\n如果你愿意帮助我们维护一些语言的本地化资源，那么请打开一个 PR，\n将自己的 GitHub 用户名加入上方的志愿者列表。\n我们非常感谢你的帮助。\n\n## 添加新的语言支持\n\nHMCL 欢迎任何人参与翻译和贡献。但是维护更多语言的翻译需要付出更多维护成本，所以我们对新增加的语言有一些要求。\n请在贡献前确认以下要求:\n\n- 我们优先考虑 [Minecraft 官方支持的语言](https://minecraft.wiki/w/Language)。\n  如果没有特殊理由，我们不会为 Minecraft 官方尚未支持的语言提供支持。\n- 我们希望对所有语言都提供长久的维护支持。\n  由于本项目的维护者们擅长的语言有限，为了避免对新语言的支持很快就因无人维护而过时，\n  我们希望能够找到擅长该语言者帮助我们长期维护新增的本地化文件。\n  如果可能缺少长期维护者，我们会更慎重地考虑是否要加入对该语言的支持。\n\n我们建议贡献者在提供新语言翻译之前先通过 [Issue](https://github.com/HMCL-dev/HMCL/issues/new?template=feature.yml) 提出一个功能请求，\n与其他贡献者先进行讨论，确定了未来的维护方式后再进行翻译工作。\n\n### 开始翻译\n\n如果你想为 HMCL 添加新的语言支持，请从翻译 [`I18N.properties`](../HMCL/src/main/resources/assets/lang/I18N.properties) 开始。\nHMCL 的绝大多数文本都位于这个文件中，翻译此文件就能翻译整个界面。\n\n这是一个 Java Properties 文件，格式非常简单。\n在翻译前请先阅读该格式的介绍: [Properties 文件](https://en.wikipedia.org/wiki/.properties)。\n\n作为翻译的第一步，请从[这张表格](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)中查询这个语言对应的两字母或三字母语言标签。\n例如，英语的语言标签为 `en`。\n\n在确定了语言标签后，请在 [`I18N.properties` 文件旁](../HMCL/src/main/resources/assets/lang)创建 `I18N_<本地化文件后缀>.properties` (例如 `I18N_en.properties`) 文件。\n随后，你就可以开始在这个文件中进行翻译工作了。\n\n`I18N.properties` 文件会遵循[资源回退机制](#资源回退机制)查询缺失的译文。\n也就是说，你可以逐条目进行翻译，而你尚未翻译的条目会自动回退到英语上。\n\n在翻译了一部分后，你可以[自行构建 HMCL](./Contributing_zh.md#构建-hmcl)，编译出的 HMCL 中就会包含你的译文。\n如果你的电脑默认环境不是该语言，你可以将环境变量 `HMCL_LANGUAGE` 指定为你刚刚从表格中找到的语言标签，\nHMCL 会自动切换至这个语言。\n\n到这里，你就可以把文件推送到 GitHub 上，并向 HMCL 提交 PR 了。\nHMCL 的维护者会替你完成其他步骤。\n\n## 本地化资源\n\n所有文档和本地化资源文件的命名规则为 `<资源名><本地化文件后缀>.<扩展名>`。\n\n例如，对于 `README.md` 来说，不同语言的本地化版本命名如下:\n\n- 英语: `README.md`\n- 中文 (简体): `README_zh.md`\n- 中文 (繁体): `README_zh_Hant.md`\n- 中文 (文言): `README_lzh.md`\n\n除了本地化文件，HMCL 还支持本地化 JSON 中的部分文本字段。JSON 中的本地化文本使用以下格式:\n\n```json5\n{\n    \"<本地化键 1>\": \"<本地化文本 1>\",\n    \"<本地化键 2>\": \"<本地化文本 2>\",\n    // ...\n    \"<本地化键 N>\": \"<本地化文本 N>\"\n}\n```\n\n例如，对于以下文本字段:\n\n```json\n{\n    \"meow\": \"Meow\"\n}\n```\n\n可以将其改写为本地化文本:\n\n```json\n{\n    \"meow\": {\n        \"default\": \"Meow\",\n        \"zh\": \"喵呜\",\n        \"zh-Hant\": \"喵嗚\"\n    }\n}\n```\n\n## 资源回退机制\n\n对于某个语言下的缺失的资源，HMCL 支持一套资源回退机制，会根据不同的语言标签推导出一个搜索列表，\n根据该列表依次搜索资源。\n\n在搜索前，我们会先通过以下步骤对语言标签进行细化推导。\n\n1. 归一化语言代码\n\n   如果当前语言标签中的语言代码子标签未在 IANA 语言子标签注册表中进行注册，HMCL 会先尝试将其映射为注册表中已注册的标签。\n\n   例如，HMCL 会将语言代码 `eng` 替换为 `en`。\n\n2. 映射宏语言至子语言\n\n   如果当前语言代码是一个 [ISO 639 宏语言](https://en.wikipedia.org/wiki/ISO_639_macrolanguage)，\n   且该宏语言通常指代某个个体语言，HMCL 会将其替换为该个体语言。\n\n   例如 `zh` (中文) 通常实际指代 `cmn` (官话)，所以我们会将语言代码 `zh` 替换为 `cmn`。\n\n3. 推导拼写脚本\n\n   如果当前语言标签中未指定拼写脚本，HMCL 会依次根据以下规则尝试推导拼写脚本:\n\n   1. 如果当前语言标签指定了语言变体，该语言变体已在 IANA 语言子标签注册表中，\n      且注册表中其所有 `Prefix` 都包含相同的拼写脚本，则将当前拼写脚本指定为该脚本。\n\n      例如，如果当前语言变体为 `pinyin` (汉语拼音)，则当前拼写脚本会被指定为 `Latn` (拉丁文)。\n\n   2. 如果当前语言代码在 IANA 语言子标签注册表中被指定了 `Suppress-Script`，则将当前拼写脚本指定为该脚本。\n\n      例如，如果当前语言代码为 `en` (英语)，则当前拼写脚本会被指定为 `Latn` (拉丁文);\n      如果当前语言代码为 `ru` (俄语)，则当前拼写脚本会被指定为 `Cyrl` (西里尔文)。\n\n   3. 如果当前语言代码是 `lzh` (文言)，则将当前拼写脚本指定为 `Hant` (繁体汉字)。\n\n   4. 如果当前语言代码是 `zh` 或 `zh` 的子语言，则检查当前国家/地区代码是否为 `TW`、`HK`、`MO` 之一。\n      如果结果为真，则将当前拼写脚本指定为 `Hant` (繁体汉字)；否则将当前拼写脚本指定为 `Hans` (简体汉字)。\n\n在对语言代码细化推导完成后，HMCL 会开始根据此语言标签推导出一个语言标签列表。\n\n例如，对于语言标签 `en-US`，HMCL 会将其细化为 `en-Latn-US`，并据此推导出以下搜索列表:\n\n1. `en-Latn-US`\n2. `en-Latn`\n3. `en-US`\n4. `en`\n5. `und`\n\n对于语言标签 `zh-CN`，HMCL 会将其细化为 `cmn-Hans-CN`，并据此推导出以下搜索列表:\n\n1. `cmn-Hans-CN`\n2. `cmn-Hans`\n3. `cmn-CN`\n4. `cmn`\n5. `zh-Hans-CN`\n6. `zh-Hans`\n7. `zh-CN`\n8. `zh`\n9. `und`\n\n对于能够混合的资源 (例如 `.properties` 文件)，HMCL 会根据此列表的优先级混合资源；\n对于难以混合的资源 (例如字体文件)，HMCL 会根据此列表加载找到的最高优先级的资源。\n\n如果当前语言使用 ISO 639 标准的三字符标签，但同时也存在对应的两字符标签，那么 HMCL 会将其映射至两字符后再搜索资源。\n\n例如，如果当前环境的语言标签为 `eng-US`，那么 HMCL 会将其映射至 `en-US` 后再根据上述规则搜索本地化资源。\n\n### 对于中文的额外规则\n\nHMCL 总是会将 `zh-CN` 加入所有中文环境的搜索列表中，将 `zh-TW` 加入所有繁体中文环境的搜索列表中。\n\n以下是几个常见中文环境对应的本地化资源搜索列表。\n\n- `zh-CN`:\n    1. `cmn-Hans-CN`\n    2. `cmn-Hans`\n    3. `cmn-CN`\n    4. `cmn`\n    5. `zh-Hans-CN`\n    6. `zh-Hans`\n    7. `zh-CN`\n    8. `zh`\n    9. `und`\n- `zh-SG`:\n    1.  `cmn-Hans-SG`\n    2.  `cmn-Hans`\n    3.  `cmn-SG`\n    4.  `cmn`\n    5.  `zh-Hans-SG`\n    6.  `zh-Hans`\n    7.  `zh-SG`\n    8.  `zh-CN`\n    9.  `zh`\n    10. `und`\n- `zh-TW`:\n    1. `zh-Hant-TW`\n    2. `zh-Hant`\n    3. `zh-TW`\n    4. `zh`\n    5. `zh-CN`\n    6. `und`\n- `zh-HK`:\n    1.  `cmn-Hant-HK`\n    2.  `cmn-Hant`\n    3.  `cmn-HK`\n    4.  `cmn`\n    5.  `zh-Hant-HK`\n    6.  `zh-Hant`\n    7.  `zh-HK`\n    8.  `zh-TW`\n    9.  `zh`\n    10. `zh-CN`\n    11. `und`\n- `lzh`:\n    1. `lzh-Hant`\n    2. `lzh`\n    3. `zh-Hant`\n    4. `zh-TW`\n    5. `zh`\n    6. `zh-CN`\n    7. `und`\n\n## 自动同步文档内容\n\n<!-- #BEGIN BLOCK -->\n<!-- #PROPERTY PROCESS_LINK=false -->\n为了简化文档的维护，HMCL 使用了一套宏机制自动维护文档的部分内容。在命令行中执行\n\n```bash\n./gradlew updateDocuments\n```\n\n即可自动更新所有文档内容。\n\n例如，为了创建在同一文档的不同语言译文之间跳转的链接，请在文档的标题下添加以下内容:\n\n```markdown\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n<!-- #END LANGUAGE_SWITCHER -->\n```\n\n随后执行 `./gradlew updateDocuments`，这两行内容会被自动替换为类似这样的跳转链接:\n\n```markdown\n**English** (**Standard**, [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)\n```\n\n关于宏的更多内容，请见 [MacroProcessor.java](../buildSrc/src/main/java/org/jackhuang/hmcl/gradle/docs/MacroProcessor.java)。\n<!-- #END BLOCK -->\n"
  },
  {
    "path": "docs/Localization_zh_Hant.md",
    "content": "# 在地化\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n[English](Localization.md) | **中文** ([简体](Localization_zh.md), **繁體**)\n<!-- #END LANGUAGE_SWITCHER -->\n\nHMCL 為多種語言提供在地化支援。\n\n本文件介紹了 HMCL 對這些語言的支援狀態，並給想要為 HMCL 的在地化工作做出貢獻的貢獻者提供了一份指南。\n\n## 支援的語言\n\n目前，HMCL 為這些語言提供支援:\n\n| 語言      | 語言標籤      | 首選在地化機碼    | 首選在地化檔案後綴  | [遊戲語言檔案](https://minecraft.wiki/w/Language) | 支援狀態   | 志願者                                       | \n|---------|-----------|-----------|------------|---------------------------------------------|--------|-------------------------------------------|\n| 英語      | `en`      | `default` | (空)        | `en_us`                                     | **主要** | [Glavo](https://github.com/Glavo)         |  \n| 英語 (顛倒) | `en-Qabs` | `en-Qabs` | `en_Qabs`  | `en_ud`                                     | 自動     |                                           |  \n| 中文 (簡體) | `zh-Hans` | `zh`      | `_zh`      | `zh_cn`                                     | **主要** | [Glavo](https://github.com/Glavo)         |\n| 中文 (繁體) | `zh-Hant` | `zh-Hant` | `_zh_Hant` | `zh_tw` <br/> `zh_hk`                       | **主要** | [Glavo](https://github.com/Glavo)         |\n| 中文 (文言) | `lzh`     | `lzh`     | `_lzh`     | `lzh`                                       | 次要     |                                           |\n| 日語      | `ja`      | `ja`      | `_ja`      | `ja_jp`                                     | 次要     |                                           |\n| 西班牙語    | `es`      | `es`      | `_es`      | `es_es`                                     | 次要     | [3gf8jv4dv](https://github.com/3gf8jv4dv) |\n| 俄語      | `ru`      | `ru`      | `_ru`      | `ru_ru`                                     | 次要     | [3gf8jv4dv](https://github.com/3gf8jv4dv) |\n| 烏克蘭語    | `uk`      | `uk`      | `_uk`      | `uk_ua`                                     | 次要     |                                           |\n\n<details>\n<summary>關於語言標籤</summary>\n\nHMCL 使用符合 IETF BCP 47 規範的語言標籤。\n\n在選取語言標籤時，我們會遵循以下原則:\n\n1. 對於 ISO 639 標準中定義的語言，如果已經在 [IANA 語言子標籤登錄檔](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)中註冊，我們總是使用經過註冊的標籤。\n\n   例如，對於英語，我們使用 `en` 而不是 `eng` 作為語言代碼。\n\n2. 對於 Minecraft 所定義的非標準語言，應當優先使用語言檔案的 `language.code` 中定義的程式碼，而非遊戲語言檔案的名稱。\n\n   這是因為 Minecraft 有時候會用現實中實際存在的國家/地區代碼來表示虛構語言 (比如說海盜英語的語言檔案為 `en_pt`，但 `PT` 其實是葡萄牙的國家代碼)。\n\n   例如，對於顛倒的英語，我們使用 `en-Qabs` 作為語言代碼，而不是 `en-UD`。\n\n</details>\n\n<details>\n<summary>關於在地化機碼和在地化檔案後綴</summary>\n\n在地化檔案後綴和在地化機碼用於為[在地化資源](#在地化資源)命名。\n\n通常來說，在地化機碼就是這份在地化資源對應的語言代碼，而在地化檔案後綴是將語言代碼中的 `-` 取代為 `_`，並加上一個前綴底線得到的。\n\n作為特例，對於預設的資源，在地化機碼為 `default`，在地化檔案後綴為空。\n\n由於[資源回退機制](#資源回退機制)的存在。\n如果沒有完全匹配目前語言環境的資源，HMCL 會根據目前環境的語言標籤推導出一個搜尋列表，根據該列表依次搜尋資源。\n\n我們建議在提供在地化資源時，總是提供預設資源 (對應 `default` 在地化機碼和空的在地化檔案後綴)，\n以確保所有使用者都能正常載入資源。\n\n並且我們建議盡可能為在地化資源使用更寬泛的語言標籤，使使用者更不容易回退到預設資源上。\n\n例如，如果你提供了一份簡體中文的在地化資源，那麼我們推薦使用 `zh` 作為在地化機碼，而不是更具體的 `zh-Hans`，\n這樣它會對於所有使用中文的使用者生效，避免對於這些使用者回退到預設資源上。\n\n如果你想同時提供簡體中文和繁體中文的資源，那麼推薦對使用者占比更高的資源使用更寬泛的 `zh` 作為在地化機碼，使其作為預設的中文資源，\n而對使用者占比更低的資源使用更具體的 `zh-Hans`/`zh-Hant` 作為在地化機碼。\n\n</details>\n\nHMCL 會要求所有 Pull Request 在更新文件和在地化資源時同步更新所有「**主要**」支援的語言對應的資源。\n如果 PR 作者對相關語言並不了解，那麼可以直接在評論中提出翻譯請求，\n維護者會在合併 PR 前幫助 PR 作者翻譯這些文字。\n\n而對於「**次要**」支援的語言，我們不能保證這些在地化資源總是會同步更新。\n我們需要熟練掌握這些語言的協作者幫助我們進行維護。\n\n我們會在文件中記錄願意幫助我們維護這些語言在地化資源的志願者。\n如果貢獻者希望及時將新增的在地化文字翻譯至這些語言，\n那麼可以在 PR 中 @ 這些志願者尋求幫助。\n\n如果你願意幫助我們維護一些語言的在地化資源，那麼請打開一個 PR，\n將自己的 GitHub 使用者名稱加入上方的志願者列表。\n我們非常感謝你的幫助。\n\n## 新增新的語言支援\n\nHMCL 歡迎任何人參與翻譯和貢獻。但是維護更多語言的翻譯需要付出更多維護成本，所以我們對新增加的語言有一些要求。\n請在貢獻前確認以下要求:\n\n- 我們優先考慮 [Minecraft 官方支援的語言](https://minecraft.wiki/w/Language)。\n  如果沒有特殊理由，我們不會為 Minecraft 官方尚未支援的語言提供支援。\n- 我們希望對所有語言都提供長久的維護支援。\n  由於本項目的維護者們擅長的語言有限，為了避免對新語言的支援很快就因無人維護而過時，\n  我們希望能夠找到擅長該語言者幫助我們長期維護新增的在地化檔案。\n  如果可能缺少長期維護者，我們會更慎重地考慮是否要加入對該語言的支援。\n\n我們建議貢獻者在提供新語言翻譯之前先透過 [Issue](https://github.com/HMCL-dev/HMCL/issues/new?template=feature.yml) 提出一個功能請求，\n與其他貢獻者先進行討論，確定了未來的維護方式後再進行翻譯工作。\n\n### 開始翻譯\n\n如果你想為 HMCL 新增新的語言支援，請從翻譯 [`I18N.properties`](../HMCL/src/main/resources/assets/lang/I18N.properties) 開始。\nHMCL 的絕大多數文字都位於這個檔案中，翻譯此檔案就能翻譯整個介面。\n\n這是一個 Java Properties 檔案，格式非常簡單。\n在翻譯前請先閱讀該格式的介紹: [Properties 檔案](https://en.wikipedia.org/wiki/.properties)。\n\n作為翻譯的第一步，請從[這張表格](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)中查詢這個語言對應的兩字母或三字母語言標籤。\n例如，英語的語言標籤為 `en`。\n\n在確定了語言標籤後，請在 [`I18N.properties` 檔案旁](../HMCL/src/main/resources/assets/lang)建立 `I18N_<在地化檔案後綴>.properties` (例如 `I18N_en.properties`) 檔案。\n隨後，你就可以開始在這個檔案中進行翻譯工作了。\n\n`I18N.properties` 檔案會遵循[資源回退機制](#資源回退機制)查詢缺失的譯文。\n也就是說，你可以逐條目進行翻譯，而你尚未翻譯的條目會自動回退到英語上。\n\n在翻譯了一部分後，你可以[自行構建 HMCL](./Contributing_zh_Hant.md#構建-hmcl)，編譯出的 HMCL 中就會包含你的譯文。\n如果你的電腦預設環境不是該語言，你可以將環境變數 `HMCL_LANGUAGE` 指定為你剛剛從表格中找到的語言標籤，\nHMCL 會自動切換至這個語言。\n\n到這裡，你就可以把檔案推送到 GitHub 上，並向 HMCL 提交 PR 了。\nHMCL 的維護者會替你完成其他步驟。\n\n## 在地化資源\n\n所有文件和在地化資源檔案的命名規則為 `<資源名><在地化檔案後綴>.<副檔名>`。\n\n例如，對於 `README.md` 來說，不同語言的在地化版本命名如下:\n\n- 英語: `README.md`\n- 中文 (簡體): `README_zh.md`\n- 中文 (繁體): `README_zh_Hant.md`\n- 中文 (文言): `README_lzh.md`\n\n除了在地化檔案，HMCL 還支援在地化 JSON 中的部分文字欄位。JSON 中的在地化文字使用以下格式:\n\n```json5\n{\n    \"<在地化機碼 1>\": \"<在地化文字 1>\",\n    \"<在地化機碼 2>\": \"<在地化文字 2>\",\n    // ...\n    \"<在地化機碼 N>\": \"<在地化文字 N>\"\n}\n```\n\n例如，對於以下文字欄位:\n\n```json\n{\n    \"meow\": \"Meow\"\n}\n```\n\n可以將其改寫為在地化文字:\n\n```json\n{\n    \"meow\": {\n        \"default\": \"Meow\",\n        \"zh\": \"喵呜\",\n        \"zh-Hant\": \"喵嗚\"\n    }\n}\n```\n\n## 資源回退機制\n\n對於某個語言下的缺失的資源，HMCL 支援一套資源回退機制，會根據不同的語言標籤推導出一個搜尋列表，\n根據該列表依次搜尋資源。\n\n在搜尋前，我們會先透過以下步驟對語言標籤進行細化推導。\n\n1. 歸一化語言代碼\n\n   如果目前語言標籤中的語言代碼子標籤未在 IANA 語言子標籤登錄檔中進行註冊，HMCL 會先嘗試將其映射為登錄檔中已註冊的標籤。\n\n   例如，HMCL 會將語言代碼 `eng` 取代為 `en`。\n\n2. 映射巨集語言至子語言\n\n   如果目前語言代碼是一個 [ISO 639 巨集語言](https://en.wikipedia.org/wiki/ISO_639_macrolanguage)，\n   且該巨集語言通常指代某個個體語言，HMCL 會將其取代為該個體語言。\n\n   例如 `zh` (中文) 通常實際指代 `cmn` (國語)，所以我們會將語言代碼 `zh` 取代為 `cmn`。\n\n3. 推導拼寫指令碼\n\n   如果目前語言標籤中未指定拼寫指令碼，HMCL 會依次根據以下規則嘗試推導拼寫指令碼:\n\n   1. 如果目前語言標籤指定了語言變體，該語言變體已在 IANA 語言子標籤登錄檔中，\n      且登錄檔中其所有 `Prefix` 都包含相同的拼寫指令碼，則將目前拼寫指令碼指定為該指令碼。\n\n      例如，如果目前語言變體為 `pinyin` (漢語拼音)，則目前拼寫指令碼會被指定為 `Latn` (拉丁文)。\n\n   2. 如果目前語言代碼在 IANA 語言子標籤登錄檔中被指定了 `Suppress-Script`，則將目前拼寫指令碼指定為該指令碼。\n\n      例如，如果目前語言代碼為 `en` (英語)，則目前拼寫指令碼會被指定為 `Latn` (拉丁文);\n      如果目前語言代碼為 `ru` (俄語)，則目前拼寫指令碼會被指定為 `Cyrl` (西里爾文)。\n\n   3. 如果目前語言代碼是 `lzh` (文言)，則將目前拼寫指令碼指定為 `Hant` (繁體漢字)。\n\n   4. 如果目前語言代碼是 `zh` 或 `zh` 的子語言，則檢查目前國家/地區代碼是否為 `TW`、`HK`、`MO` 之一。\n      如果結果為真，則將目前拼寫指令碼指定為 `Hant` (繁體漢字)；否則將目前拼寫指令碼指定為 `Hans` (簡體漢字)。\n\n在對語言代碼細化推導完成後，HMCL 會開始根據此語言標籤推導出一個語言標籤列表。\n\n例如，對於語言標籤 `en-US`，HMCL 會將其細化為 `en-Latn-US`，並據此推導出以下搜尋列表:\n\n1. `en-Latn-US`\n2. `en-Latn`\n3. `en-US`\n4. `en`\n5. `und`\n\n對於語言標籤 `zh-CN`，HMCL 會將其細化為 `cmn-Hans-CN`，並據此推導出以下搜尋列表:\n\n1. `cmn-Hans-CN`\n2. `cmn-Hans`\n3. `cmn-CN`\n4. `cmn`\n5. `zh-Hans-CN`\n6. `zh-Hans`\n7. `zh-CN`\n8. `zh`\n9. `und`\n\n對於能夠混合的資源 (例如 `.properties` 檔案)，HMCL 會根據此列表的優先度混合資源；\n對於難以混合的資源 (例如字體檔案)，HMCL 會根據此列表載入找到的最高優先度的資源。\n\n如果目前語言使用 ISO 639 標準的三字元標籤，但同時也存在對應的兩字元標籤，那麼 HMCL 會將其映射至兩字元後再搜尋資源。\n\n例如，如果目前環境的語言標籤為 `eng-US`，那麼 HMCL 會將其映射至 `en-US` 後再根據上述規則搜尋在地化資源。\n\n### 對於中文的額外規則\n\nHMCL 總是會將 `zh-CN` 加入所有中文環境的搜尋列表中，將 `zh-TW` 加入所有繁體中文環境的搜尋列表中。\n\n以下是幾個常見中文環境對應的在地化資源搜尋列表。\n\n- `zh-CN`:\n    1. `cmn-Hans-CN`\n    2. `cmn-Hans`\n    3. `cmn-CN`\n    4. `cmn`\n    5. `zh-Hans-CN`\n    6. `zh-Hans`\n    7. `zh-CN`\n    8. `zh`\n    9. `und`\n- `zh-SG`:\n    1.  `cmn-Hans-SG`\n    2.  `cmn-Hans`\n    3.  `cmn-SG`\n    4.  `cmn`\n    5.  `zh-Hans-SG`\n    6.  `zh-Hans`\n    7.  `zh-SG`\n    8.  `zh-CN`\n    9.  `zh`\n    10. `und`\n- `zh-TW`:\n    1. `zh-Hant-TW`\n    2. `zh-Hant`\n    3. `zh-TW`\n    4. `zh`\n    5. `zh-CN`\n    6. `und`\n- `zh-HK`:\n    1.  `cmn-Hant-HK`\n    2.  `cmn-Hant`\n    3.  `cmn-HK`\n    4.  `cmn`\n    5.  `zh-Hant-HK`\n    6.  `zh-Hant`\n    7.  `zh-HK`\n    8.  `zh-TW`\n    9.  `zh`\n    10. `zh-CN`\n    11. `und`\n- `lzh`:\n    1. `lzh-Hant`\n    2. `lzh`\n    3. `zh-Hant`\n    4. `zh-TW`\n    5. `zh`\n    6. `zh-CN`\n    7. `und`\n\n## 自動同步文件內容\n\n<!-- #BEGIN BLOCK -->\n<!-- #PROPERTY PROCESS_LINK=false -->\n為了簡化文件的維護，HMCL 使用了一套巨集機制自動維護文件的部分內容。在命令列中執行\n\n```bash\n./gradlew updateDocuments\n```\n\n即可自動更新所有文件內容。\n\n例如，為了建立在同一文件的不同語言譯文之間跳轉的連結，請在文件的標題下新增以下內容:\n\n```markdown\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n<!-- #END LANGUAGE_SWITCHER -->\n```\n\n隨後執行 `./gradlew updateDocuments`，這兩行內容會被自動取代為類似這樣的跳轉連結:\n\n```markdown\n**English** (**Standard**, [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)\n```\n\n關於巨集的更多內容，請見 [MacroProcessor.java](../buildSrc/src/main/java/org/jackhuang/hmcl/gradle/docs/MacroProcessor.java)。\n<!-- #END BLOCK -->\n"
  },
  {
    "path": "docs/PLATFORM.md",
    "content": "# Platform Support Status\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n**English** | 中文 ([简体](PLATFORM_zh.md), [繁體](PLATFORM_zh_Hant.md))\n<!-- #END LANGUAGE_SWITCHER -->\n\n## Launcher Compatibility\n\n<!-- #BEGIN BLOCK -->\n<!-- #PROPERTY NAME=LAUNCHER_COMPATIBILITY -->\n<table>\n  <thead>\n    <tr>\n      <th></th>\n      <th>Windows</th>\n      <th>Linux</th>\n      <th>macOS</th>\n      <th>FreeBSD</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>x86-64</td>\n      <td>\n        ✅️ Fully supported (Windows 7 ~ Windows 11)\n        <br>\n        ✅️ Fully supported (Windows Server 2008 R2 ~ 2025)\n        <br>\n        🕰️ <a href=\"https://github.com/HMCL-dev/HMCL/releases?q=3.6\">HMCL 3.6</a> (Windows Vista)\n        <br>\n        🕰️ <a href=\"https://github.com/HMCL-dev/HMCL/releases?q=3.6\">HMCL 3.6</a> (Windows Server 2003 ~ 2008) \n      </td>\n      <td>✅️ Fully supported</td>\n      <td>✅️ Fully supported</td>\n      <td>✅ Fully supported</td>\n    </tr>\n    <tr>\n      <td>x86</td>\n      <td>\n        🕰️ Limited support (Windows 7 ~ Windows 10)\n        <br>\n        🕰️ <a href=\"https://github.com/HMCL-dev/HMCL/releases?q=3.6\">HMCL 3.6</a> (Windows XP/Vista)\n      </td>\n      <td>🕰️ Limited support</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM64</td>\n      <td>✅️ Fully supported</td>\n      <td>✅️ Fully supported</td>\n      <td>✅️ Fully supported</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM32</td>\n      <td>/️</td>\n      <td>🕰️ Limited support</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>MIPS64el</td>\n      <td>/</td>\n      <td>🕰️ Limited support</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>RISC-V 64</td>\n      <td>/</td>\n      <td>✅️ Fully supported</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>LoongArch64</td>\n      <td>/</td>\n      <td>\n        ✅️ Fully supported (New World)\n        <br>\n        🕰️ Limited support (Old World)\n      </td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n  </tbody>\n</table>\n<!-- #END BLOCK -->\n\nLegend:\n\n* ✅️ Fully supported\n\n  Platforms that are fully supported. HMCL will provide support for these platforms as much as possible.\n\n* 🕰️ Limited support\n\n  These platforms are usually legacy platforms.\n\n  HMCL can work on these platforms, but some features may not be available.\n\n  We may drop some features for these platforms to reduce maintenance costs.\n\n* 🕰️ HMCL 3.6 (Limited support)\n\n  The main branch of HMCL no longer supports these platforms.\n\n  We continue to provide security patches and bug fixes for these platforms through the HMCL 3.6 LTS branch,\n  but no feature updates will be available.\n\n* / (Not supported)\n\n  HMCL does not support these platforms yet. We may support them in the future.\n\n## Game Compatibility\n\n<!-- #BEGIN BLOCK -->\n<!-- #PROPERTY NAME=GAME_COMPATIBILITY -->\n|                             | Windows                                           | Linux                      | macOS                                                                   | FreeBSD                     |\n|-----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:----------------------------|\n| x86-64                      | ✅️                                                | ✅️                         | ✅️                                                                      | 👌 (Minecraft 1.13~1.21.11) |\n| x86                         | ✅️ (~1.20.4)                                      | ✅️ (~1.20.4)               | /                                                                       | /                           |\n| ARM64                       | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.11) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔                           |\n| ARM32                       | /️                                                | 👌 (Minecraft 1.8~1.20.1)  | /                                                                       | /                           |\n| MIPS64el                    | /                                                 | 👌 (Minecraft 1.8~1.20.1)  | /                                                                       | /                           |\n| RISC-V 64                   | /                                                 | 👌 (Minecraft 1.13~1.21.5) | /                                                                       | /                           |\n| LoongArch64 (New World) | /                                                 | 👌 (Minecraft 1.6~1.21.11) | /                                                                       | /                           |\n| LoongArch64 (Old World) | /                                                 | 👌 (Minecraft 1.6~1.20.1)  | /                                                                       | /                           |\n| PowerPC-64 (Little-Endian)  | /                                                 | ❔                          | /                                                                       | /                           |\n| S390x                       | /                                                 | ❔                          | /                                                                       | /                           |\n<!-- #END BLOCK -->\n\nLegend:\n\n* ✅: Officially supported platform.\n\n  Fully supported by Mojang officials. Problems encountered in the game should be directly reported to the Mojang.\n\n* 👌: Supported platforms.\n\n  Support is provided by HMCL, tested to work, but may have more problems than a fully supported platform.  \n  Support for versions below Minecraft 1.6 is not guaranteed.  \n  If you encounter a problem that does not exist on fully supported platforms, you can report it to HMCL.\n\n* ❔: Low-level supported platforms.\n\n  HMCL can run on this platform and has some basic support. However, launching the game directly is not yet available.  \n  If you want to start the game, you will need to get the native libraries needed by Minecraft in another way and specify the native path in the instance settings.\n\n* `/`: Not applicable.\n\n  We have no plans to support these platforms at this time, mainly because we do not have the equipment to test them.  \n  If you can help us adapt, please file a support request via GitHub Issue.\n\n## Terracotta Compatibility\n\n<!-- #BEGIN BLOCK -->\n<!-- #PROPERTY NAME=TERRACOTTA_COMPATIBILITY -->\n<table>\n  <thead>\n    <tr>\n      <th></th>\n      <th>Windows</th>\n      <th>Linux</th>\n      <th>macOS</th>\n      <th>FreeBSD</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>x86-64</td>\n      <td>\n        ✅️ (Windows 10 ~ Windows 11)\n        <br>\n        ✅️ (Windows Server 2016 ~ 2025)\n      </td>\n      <td>✅️</td>\n      <td>✅️</td>\n      <td>✅️</td>\n    </tr>\n    <tr>\n      <td>x86</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM64</td>\n      <td>✅️</td>\n      <td>✅️</td>\n      <td>✅️</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM32</td>\n      <td>/️</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>MIPS64el</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>RISC-V 64</td>\n      <td>/</td>\n      <td>❔</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>LoongArch64</td>\n      <td>/</td>\n      <td>\n        ✅️ (New World)\n        <br>\n        ❌ (Old World)\n      </td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n  </tbody>\n</table>\n<!-- #END BLOCK -->\n"
  },
  {
    "path": "docs/PLATFORM_zh.md",
    "content": "# 平台支持状态\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n[English](PLATFORM.md) | **中文** (**简体**, [繁體](PLATFORM_zh_Hant.md))\n<!-- #END LANGUAGE_SWITCHER -->\n\n## 启动器兼容性\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=LAUNCHER_COMPATIBILITY -->\n<!-- #PROPERTY REPLACE=\"Fully supported\" \"完整支持\" -->\n<!-- #PROPERTY REPLACE=\"Limited support\" \"有限支持\" -->\n<!-- #PROPERTY REPLACE=\"Old World\" \"旧世界\" -->\n<!-- #PROPERTY REPLACE=\"New World\" \"新世界\" -->\n<table>\n  <thead>\n    <tr>\n      <th></th>\n      <th>Windows</th>\n      <th>Linux</th>\n      <th>macOS</th>\n      <th>FreeBSD</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>x86-64</td>\n      <td>\n        ✅️ 完整支持 (Windows 7 ~ Windows 11)\n        <br>\n        ✅️ 完整支持 (Windows Server 2008 R2 ~ 2025)\n        <br>\n        🕰️ <a href=\"https://github.com/HMCL-dev/HMCL/releases?q=3.6\">HMCL 3.6</a> (Windows Vista)\n        <br>\n        🕰️ <a href=\"https://github.com/HMCL-dev/HMCL/releases?q=3.6\">HMCL 3.6</a> (Windows Server 2003 ~ 2008) \n      </td>\n      <td>✅️ 完整支持</td>\n      <td>✅️ 完整支持</td>\n      <td>✅ 完整支持</td>\n    </tr>\n    <tr>\n      <td>x86</td>\n      <td>\n        🕰️ 有限支持 (Windows 7 ~ Windows 10)\n        <br>\n        🕰️ <a href=\"https://github.com/HMCL-dev/HMCL/releases?q=3.6\">HMCL 3.6</a> (Windows XP/Vista)\n      </td>\n      <td>🕰️ 有限支持</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM64</td>\n      <td>✅️ 完整支持</td>\n      <td>✅️ 完整支持</td>\n      <td>✅️ 完整支持</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM32</td>\n      <td>/️</td>\n      <td>🕰️ 有限支持</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>MIPS64el</td>\n      <td>/</td>\n      <td>🕰️ 有限支持</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>RISC-V 64</td>\n      <td>/</td>\n      <td>✅️ 完整支持</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>LoongArch64</td>\n      <td>/</td>\n      <td>\n        ✅️ 完整支持 (新世界)\n        <br>\n        🕰️ 有限支持 (旧世界)\n      </td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n  </tbody>\n</table>\n<!-- #END COPY -->\n\n图例：\n\n* ✅️ 完整支持\n\n  受到完整支持的平台。HMCL 会尽可能为此平台提供支持。\n\n* 🕰️ 有限支持\n\n  这些平台通常是老旧的遗留平台。\n\n  HMCL 可以在这些平台上工作，但部分功能可能无法使用。\n\n  我们可能会为了降低维护成本而放弃为此平台提供部分功能。\n\n* 🕰️ HMCL 3.6 (有限支持)\n\n  HMCL 主分支不再支持此平台。\n\n  我们通过 HMCL 3.6 LTS 分支继续为该平台提供安全补丁和错误修复，\n  但此平台上将无法接受到功能更新。\n\n* / (不受支持)\n\n  HMCL 尚不支持此平台。我们可能会在未来支持此平台。\n\n## 游戏兼容性\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=GAME_COMPATIBILITY -->\n<!-- #PROPERTY REPLACE=\"Old World\" \"旧世界\" -->\n<!-- #PROPERTY REPLACE=\"New World\" \"新世界\" -->\n<!-- #PROPERTY REPLACE=\"\\\\(use Rosetta 2\\\\)\" \"(使用 Rosetta 2)\" -->\n|                             | Windows                                           | Linux                      | macOS                                                                   | FreeBSD                     |\n|-----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:----------------------------|\n| x86-64                      | ✅️                                                | ✅️                         | ✅️                                                                      | 👌 (Minecraft 1.13~1.21.11) |\n| x86                         | ✅️ (~1.20.4)                                      | ✅️ (~1.20.4)               | /                                                                       | /                           |\n| ARM64                       | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.11) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔                           |\n| ARM32                       | /️                                                | 👌 (Minecraft 1.8~1.20.1)  | /                                                                       | /                           |\n| MIPS64el                    | /                                                 | 👌 (Minecraft 1.8~1.20.1)  | /                                                                       | /                           |\n| RISC-V 64                   | /                                                 | 👌 (Minecraft 1.13~1.21.5) | /                                                                       | /                           |\n| LoongArch64 (新世界) | /                                                 | 👌 (Minecraft 1.6~1.21.11) | /                                                                       | /                           |\n| LoongArch64 (旧世界) | /                                                 | 👌 (Minecraft 1.6~1.20.1)  | /                                                                       | /                           |\n| PowerPC-64 (Little-Endian)  | /                                                 | ❔                          | /                                                                       | /                           |\n| S390x                       | /                                                 | ❔                          | /                                                                       | /                           |\n<!-- #END COPY -->\n\n图例：\n\n* ✅: 官方支持的平台\n\n  受 Mojang 官方支持。在游戏中遇到的问题应该直接向 Mojang 反馈。\n\n* 👌: 支持的平台\n\n  由 HMCL 提供支持，经过测试可以正常运行，但可能比得到全面支持的平台有更多问题。  \n  不保证支持 Minecraft 1.6 以下的版本。  \n  如果你遇到在得到全面支持的平台上不存在的问题，可以向 HMCL 反馈。\n\n* ❔: 低级别支持的平台\n\n  HMCL 可以在这个平台上运行，并且有一些基本的支持。但是，还不能正常地启动游戏。  \n  如果你想正常启动游戏，则需要通过其他方式获得游戏所需的本地库 (LWJGL)，并在（全局）游戏设置中指定本地库路径。\n\n* `/`: 不支持的平台\n\n  我们目前还没有打算支持这些平台，主要是因为我们没有测试这些平台的设备。  \n  如果你能帮助我们进行测试，请通过提交 Issue 提出支持请求。\n\n## 陶瓦联机兼容性\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=TERRACOTTA_COMPATIBILITY -->\n<!-- #PROPERTY REPLACE=\"Old World\" \"旧世界\" -->\n<!-- #PROPERTY REPLACE=\"New World\" \"新世界\" -->\n<table>\n  <thead>\n    <tr>\n      <th></th>\n      <th>Windows</th>\n      <th>Linux</th>\n      <th>macOS</th>\n      <th>FreeBSD</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>x86-64</td>\n      <td>\n        ✅️ (Windows 10 ~ Windows 11)\n        <br>\n        ✅️ (Windows Server 2016 ~ 2025)\n      </td>\n      <td>✅️</td>\n      <td>✅️</td>\n      <td>✅️</td>\n    </tr>\n    <tr>\n      <td>x86</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM64</td>\n      <td>✅️</td>\n      <td>✅️</td>\n      <td>✅️</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM32</td>\n      <td>/️</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>MIPS64el</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>RISC-V 64</td>\n      <td>/</td>\n      <td>❔</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>LoongArch64</td>\n      <td>/</td>\n      <td>\n        ✅️ (新世界)\n        <br>\n        ❌ (旧世界)\n      </td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n  </tbody>\n</table>\n<!-- #END COPY -->\n"
  },
  {
    "path": "docs/PLATFORM_zh_Hant.md",
    "content": "# 平臺支援狀態\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n[English](PLATFORM.md) | **中文** ([简体](PLATFORM_zh.md), **繁體**)\n<!-- #END LANGUAGE_SWITCHER -->\n\n## 啟動器相容性\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=LAUNCHER_COMPATIBILITY -->\n<!-- #PROPERTY REPLACE=\"Fully supported\" \"完整支援\" -->\n<!-- #PROPERTY REPLACE=\"Limited support\" \"有限支援\" -->\n<!-- #PROPERTY REPLACE=\"New World\" \"新世界\" -->\n<!-- #PROPERTY REPLACE=\"Old World\" \"舊世界\" -->\n<table>\n  <thead>\n    <tr>\n      <th></th>\n      <th>Windows</th>\n      <th>Linux</th>\n      <th>macOS</th>\n      <th>FreeBSD</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>x86-64</td>\n      <td>\n        ✅️ 完整支援 (Windows 7 ~ Windows 11)\n        <br>\n        ✅️ 完整支援 (Windows Server 2008 R2 ~ 2025)\n        <br>\n        🕰️ <a href=\"https://github.com/HMCL-dev/HMCL/releases?q=3.6\">HMCL 3.6</a> (Windows Vista)\n        <br>\n        🕰️ <a href=\"https://github.com/HMCL-dev/HMCL/releases?q=3.6\">HMCL 3.6</a> (Windows Server 2003 ~ 2008) \n      </td>\n      <td>✅️ 完整支援</td>\n      <td>✅️ 完整支援</td>\n      <td>✅ 完整支援</td>\n    </tr>\n    <tr>\n      <td>x86</td>\n      <td>\n        🕰️ 有限支援 (Windows 7 ~ Windows 10)\n        <br>\n        🕰️ <a href=\"https://github.com/HMCL-dev/HMCL/releases?q=3.6\">HMCL 3.6</a> (Windows XP/Vista)\n      </td>\n      <td>🕰️ 有限支援</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM64</td>\n      <td>✅️ 完整支援</td>\n      <td>✅️ 完整支援</td>\n      <td>✅️ 完整支援</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM32</td>\n      <td>/️</td>\n      <td>🕰️ 有限支援</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>MIPS64el</td>\n      <td>/</td>\n      <td>🕰️ 有限支援</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>RISC-V 64</td>\n      <td>/</td>\n      <td>✅️ 完整支援</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>LoongArch64</td>\n      <td>/</td>\n      <td>\n        ✅️ 完整支援 (新世界)\n        <br>\n        🕰️ 有限支援 (舊世界)\n      </td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n  </tbody>\n</table>\n<!-- #END COPY -->\n\n圖例：\n\n* ✅️ 完整支援\n\n  受到完整支援的平臺。HMCL 會盡可能為此平臺提供支援。\n\n* 🕰️ 有限支援\n\n  這些平臺通常是老舊的遺留平臺。\n\n  HMCL 可以在這些平臺上運作，但部分功能可能無法使用。\n\n  我們可能會為了降低維護成本而放棄為此平臺提供部分功能。\n\n* 🕰️ HMCL 3.6（有限支援）\n\n  HMCL 主分支不再支援此平臺。\n\n  我們透過 HMCL 3.6 LTS 分支繼續為該平臺提供安全修補與錯誤修復，\n  但此平臺上將無法獲得功能更新。\n\n* /（不支援）\n\n  HMCL 尚未支援此平臺。我們未來可能會支援此平臺。\n\n## 遊戲相容性\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=GAME_COMPATIBILITY -->\n<!-- #PROPERTY REPLACE=\"New World\" \"新世界\" -->\n<!-- #PROPERTY REPLACE=\"Old World\" \"舊世界\" -->\n<!-- #PROPERTY REPLACE=\"\\\\(use Rosetta 2\\\\)\" \"(使用 Rosetta 2)\" -->\n|                             | Windows                                           | Linux                      | macOS                                                                   | FreeBSD                     |\n|-----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:----------------------------|\n| x86-64                      | ✅️                                                | ✅️                         | ✅️                                                                      | 👌 (Minecraft 1.13~1.21.11) |\n| x86                         | ✅️ (~1.20.4)                                      | ✅️ (~1.20.4)               | /                                                                       | /                           |\n| ARM64                       | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.11) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔                           |\n| ARM32                       | /️                                                | 👌 (Minecraft 1.8~1.20.1)  | /                                                                       | /                           |\n| MIPS64el                    | /                                                 | 👌 (Minecraft 1.8~1.20.1)  | /                                                                       | /                           |\n| RISC-V 64                   | /                                                 | 👌 (Minecraft 1.13~1.21.5) | /                                                                       | /                           |\n| LoongArch64 (新世界) | /                                                 | 👌 (Minecraft 1.6~1.21.11) | /                                                                       | /                           |\n| LoongArch64 (舊世界) | /                                                 | 👌 (Minecraft 1.6~1.20.1)  | /                                                                       | /                           |\n| PowerPC-64 (Little-Endian)  | /                                                 | ❔                          | /                                                                       | /                           |\n| S390x                       | /                                                 | ❔                          | /                                                                       | /                           |\n<!-- #END COPY -->\n\n圖例：\n\n* ✅: 官方支援的平臺\n\n  受 Mojang 官方支援。在遊戲中遇到的問題應該直接向 Mojang 回報。\n\n* 👌: 支援的平臺\n\n  由 HMCL 提供支援，經過測試可以正常執行，但可能比得到全面支援的平臺有更多問題。  \n  不保證支援 Minecraft 1.6 以下的版本。  \n  如果你遇到在得到全面支援的平臺上不存在的問題，可以向 HMCL 回報。\n\n* ❔: 低級別支援的平臺\n\n  HMCL 可以在這個平臺上執行，並且有一些基本的支援。但是，還不能正常地啟動遊戲。  \n  如果你想正常啟動遊戲，則需要透過其他方式獲得遊戲所需的本機庫 (LWJGL)，並在（全域）遊戲設定中指定本機庫路徑。\n\n* `/`: 不支援的平臺\n\n  我們目前還沒有打算支援這些平臺，主要是因為我們沒有測試這些平臺的裝置。  \n  如果你能幫助我們進行測試，請透過 Issue 提出支援請求。\n\n## 陶瓦聯機相容性\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=TERRACOTTA_COMPATIBILITY -->\n<!-- #PROPERTY REPLACE=\"New World\" \"新世界\" -->\n<!-- #PROPERTY REPLACE=\"Old World\" \"舊世界\" -->\n<table>\n  <thead>\n    <tr>\n      <th></th>\n      <th>Windows</th>\n      <th>Linux</th>\n      <th>macOS</th>\n      <th>FreeBSD</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>x86-64</td>\n      <td>\n        ✅️ (Windows 10 ~ Windows 11)\n        <br>\n        ✅️ (Windows Server 2016 ~ 2025)\n      </td>\n      <td>✅️</td>\n      <td>✅️</td>\n      <td>✅️</td>\n    </tr>\n    <tr>\n      <td>x86</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM64</td>\n      <td>✅️</td>\n      <td>✅️</td>\n      <td>✅️</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>ARM32</td>\n      <td>/️</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>MIPS64el</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>RISC-V 64</td>\n      <td>/</td>\n      <td>❔</td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n    <tr>\n      <td>LoongArch64</td>\n      <td>/</td>\n      <td>\n        ✅️ (新世界)\n        <br>\n        ❌ (舊世界)\n      </td>\n      <td>/</td>\n      <td>/</td>\n    </tr>\n  </tbody>\n</table>\n<!-- #END COPY -->\n"
  },
  {
    "path": "docs/README.md",
    "content": "<!-- #BEGIN BLOCK -->\n<!-- #PROPERTY NAME=TITLE -->\n<div align=\"center\">\n    <img src=\"/HMCL/src/main/resources/assets/img/icon@8x.png\" alt=\"HMCL Logo\" width=\"64\"/>\n</div>\n\n<h1 align=\"center\">Hello Minecraft! Launcher</h1>\n<!-- #END BLOCK -->\n\n<!-- #BEGIN BLOCK -->\n<!-- #PROPERTY NAME=BADGES -->\n<div align=\"center\">\n\n[![GitHub](https://img.shields.io/badge/GitHub-repo-blue?style=flat-square&logo=github)](https://github.com/HMCL-dev/HMCL)\n[![CNB](https://img.shields.io/badge/CNB-mirror-ff6200?style=flat-square&logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)\n[![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?style=flat-square&logo=gitee)](https://gitee.com/huanghongxun/HMCL)\n\n[![QQ Group](https://img.shields.io/badge/QQ-gray?style=flat-square&logo=qq&logoColor=ffffff)](https://docs.hmcl.net/groups.html)\n[![Discord](https://img.shields.io/badge/Discord-gray?style=flat-square&logo=discord)](https://discord.gg/jVvC7HfM6U)\n[![Bilibili](https://img.shields.io/badge/Bilibili-gray?style=flat-square&logo=bilibili)](https://space.bilibili.com/20314891)\n\n</div>\n<!-- #END BLOCK -->\n\n---\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n**English** (**Standard**, [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)\n<!-- #END LANGUAGE_SWITCHER -->\n\n## Introduction\n\nHMCL is an open-source, cross-platform Minecraft launcher that supports Mod Management, Game Customizing, ModLoader Installing (Forge, NeoForge, Cleanroom, Fabric, Legacy Fabric, Quilt, LiteLoader, and OptiFine), Modpack Creating, UI Customization, and more.\n\nHMCL has amazing cross-platform capabilities. Not only does it run on different operating systems like Windows, Linux, macOS, and FreeBSD, but it also supports various CPU architectures such as x86, ARM, RISC-V, MIPS, and LoongArch. You can easily enjoy Minecraft across different platforms through HMCL.\n\nFor systems and CPU architectures supported by HMCL, please refer to [this table](PLATFORM.md).\n\n## Download\n\nYou can download HMCL from the following sources:\n\n- [HMCL Official Website](https://hmcl.huangyuhui.net/download)\n- [GitHub Release](https://github.com/HMCL-dev/HMCL/releases)\n- [CNB Release](https://cnb.cool/HMCL-dev/HMCL/-/releases)\n\n## Contributing\n\nHMCL is a community-driven open-source project, and everyone is welcome to contribute code or provide suggestions.\n\nYou can contribute to HMCL development in the following ways:\n\n- Report bugs or request features by [creating an issue](https://github.com/HMCL-dev/HMCL/issues/new/choose) on GitHub.\n- Contribute code by forking the repository on GitHub and [submitting a pull request](https://github.com/HMCL-dev/HMCL/compare).\n\nBefore contributing, please read the [Contributing Guide](./Contributing.md), which includes the following:\n\n- [How to build and run HMCL from source](./Contributing.md#build-hmcl)\n- [Adjusting HMCL behavior using debug options](./Contributing.md#debug-options)\n\n## Contributors\n\nSince 2015, more than 120 contributors have participated in HMCL. Thank you for your hard work!\n\n[![Contributors](https://contrib.rocks/image?repo=HMCL-dev/HMCL)](https://github.com/HMCL-dev/HMCL/graphs/contributors)\n\n## License\n\nThe software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) license with the following additional terms:\n\n### Additional terms under GPLv3 Section 7\n\n1. When you distribute a modified version of the software, you must change the software name or the version number in a reasonable way in order to distinguish it from the original version. (Under [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374))\n\n   The software name and the version number can be edited [here](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35).\n\n2. You must not remove the copyright declaration displayed in the software. (Under [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370))\n"
  },
  {
    "path": "docs/README_en_Qabs.md",
    "content": "<div align=\"center\">\n    <img src=\"/HMCL/src/main/resources/assets/img/icon@8x.png\" alt=\"HMCL Logo\" width=\"64\" style=\"transform: rotate(180deg);\"/>\n</div>\n\n<h1 align=\"center\">ɹǝɥɔunɐ˥ ¡ʇɟɐɹɔǝuᴉW ollǝH</h1>\n\n<!-- #BEGIN BLOCK -->\n<!-- #PROPERTY PROCESS_LINK=false -->\n**ɥsᴉlƃuƎ** (**uʍoᗡ ǝpᴉsd∩** '[pɹɐpuɐʇS](README.md))\n<!-- #END BLOCK -->\n\n## uoᴉʇɔnpoɹʇuI\n\n˙[ǝlqɐʇ sᴉɥʇ](PLATFORM.md) oʇ ɹǝɟǝɹ ǝsɐǝld '˥ƆWH ʎq pǝʇɹoddns sǝɹnʇɔǝʇᴉɥɔɹɐ ∩ԀƆ puɐ sɯǝʇsʎs ɹoℲ\n\n˙˥ƆWH ɥƃnoɹɥʇ sɯɹoɟʇɐld ʇuǝɹǝɟɟᴉp ssoɹɔɐ ʇɟɐɹɔǝuᴉW ʎoɾuǝ ʎlᴉsɐǝ uɐɔ no⅄ ˙ɥɔɹ∀ƃuoo˥ puɐ 'SԀIW 'Λ-ƆSIɹ 'Wɹ∀ '98x sɐ ɥɔns sǝɹnʇɔǝʇᴉɥɔɹɐ ∩ԀƆ snoᴉɹɐʌ sʇɹoddns oslɐ ʇᴉ ʇnq 'pSqǝǝɹℲ puɐ 'SOɔɐɯ 'xnuᴉ˥ 'sʍopuᴉM ǝʞᴉl sɯǝʇsʎs ƃuᴉʇɐɹǝdo ʇuǝɹǝɟɟᴉp uo unɹ ʇᴉ sǝop ʎluo ʇoN ˙sǝᴉʇᴉlᴉqɐdɐɔ ɯɹoɟʇɐld-ssoɹɔ ƃuᴉzɐɯɐ sɐɥ ˥ƆWH\n\n˙ǝɹoɯ puɐ 'uoᴉʇɐzᴉɯoʇsnƆ I∩ 'ƃuᴉʇɐǝɹƆ ʞɔɐdpoW '(ǝuᴉℲᴉʇdO puɐ 'ɹǝpɐo˥ǝʇᴉ˥ 'ʇlᴉnQ 'ɔᴉɹqɐℲ ʎɔɐᵷǝꞀ 'ɔᴉɹqɐℲ 'ɯooɹuɐǝlƆ 'ǝƃɹoℲoǝN 'ǝƃɹoℲ) ƃuᴉllɐʇsuI ɹǝpɐo˥poW 'ƃuᴉzᴉɯoʇsnƆ ǝɯɐפ 'ʇuǝɯǝƃɐuɐW poW sʇɹoddns ʇɐɥʇ ɹǝɥɔunɐl ʇɟɐɹɔǝuᴉW ɯɹoɟʇɐld-ssoɹɔ 'ǝɔɹnos-uǝdo uɐ sᴉ ˥ƆWH\n\n## pɐoluʍoᗡ\n\n˙ǝʌoqɐ pǝʇsᴉl sǝʇᴉsqǝʍ lɐᴉɔᴉɟɟo ǝɥʇ ɯoɹɟ sǝsɐǝlǝɹ pɐoluʍop oʇ ʎluo pǝpuǝɯɯoɔǝɹ sᴉ ʇᴉ 'ʎɹɐssǝɔǝu ʇou ɥƃnoɥʇl∀\n\n˙[sǝsɐǝlǝɹ qnHʇᴉפ](https://github.com/HMCL-dev/HMCL/releases) uᴉ ˥ƆWH ɟo uoᴉsɹǝʌ ʇsǝʇɐl ǝɥʇ puᴉɟ oslɐ uɐɔ no⅄\n\n˙[ǝʇᴉsqǝʍ lɐᴉɔᴉɟɟo](https://hmcl.huangyuhui.net/download) ǝɥʇ ɯoɹɟ uoᴉsɹǝʌ ʇsǝʇɐl ǝɥʇ pɐoluʍop\n"
  },
  {
    "path": "docs/README_es.md",
    "content": "<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=TITLE -->\n<div align=\"center\">\n    <img src=\"/HMCL/src/main/resources/assets/img/icon@8x.png\" alt=\"HMCL Logo\" width=\"64\"/>\n</div>\n\n<h1 align=\"center\">Hello Minecraft! Launcher</h1>\n<!-- #END COPY -->\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=BADGES -->\n<div align=\"center\">\n\n[![GitHub](https://img.shields.io/badge/GitHub-repo-blue?style=flat-square&logo=github)](https://github.com/HMCL-dev/HMCL)\n[![CNB](https://img.shields.io/badge/CNB-mirror-ff6200?style=flat-square&logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)\n[![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?style=flat-square&logo=gitee)](https://gitee.com/huanghongxun/HMCL)\n\n[![QQ Group](https://img.shields.io/badge/QQ-gray?style=flat-square&logo=qq&logoColor=ffffff)](https://docs.hmcl.net/groups.html)\n[![Discord](https://img.shields.io/badge/Discord-gray?style=flat-square&logo=discord)](https://discord.gg/jVvC7HfM6U)\n[![Bilibili](https://img.shields.io/badge/Bilibili-gray?style=flat-square&logo=bilibili)](https://space.bilibili.com/20314891)\n\n</div>\n<!-- #END COPY -->\n\n---\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\nEnglish ([Standard](README.md), [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | **español** | [русский](README_ru.md) | [українська](README_uk.md)\n<!-- #END LANGUAGE_SWITCHER -->\n\n## Introducción\n\nHMCL es un lanzador de Minecraft de código abierto y multiplataforma que admite la gestión de mods, personalización del juego, instalación de ModLoaders (Forge, NeoForge, Cleanroom, Fabric, Legacy Fabric, Quilt, LiteLoader y OptiFine), creación de modpacks, personalización de la interfaz de usuario y más.\n\nHMCL tiene increíbles capacidades multiplataforma. No solo funciona en diferentes sistemas operativos como Windows, Linux, macOS y FreeBSD, sino que también es compatible con varias arquitecturas de CPU como x86, ARM, RISC-V, MIPS y LoongArch. Puedes disfrutar fácilmente de Minecraft en diferentes plataformas a través de HMCL.\n\nPara los sistemas y arquitecturas de CPU compatibles con HMCL, consulta [esta tabla](PLATFORM.md).\n\n## Descarga\n\nDescarga la última versión desde el [sitio web oficial](https://hmcl.huangyuhui.net/download).\n\nTambién puedes encontrar la última versión de HMCL en [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases).\n\nAunque no es necesario, se recomienda descargar las versiones solo de los sitios web oficiales mencionados anteriormente.\n\n## Licencia\n\nConsulta [README.md](README.md#license).\n"
  },
  {
    "path": "docs/README_ja.md",
    "content": "<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=TITLE -->\n<div align=\"center\">\n    <img src=\"/HMCL/src/main/resources/assets/img/icon@8x.png\" alt=\"HMCL Logo\" width=\"64\"/>\n</div>\n\n<h1 align=\"center\">Hello Minecraft! Launcher</h1>\n<!-- #END COPY -->\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=BADGES -->\n<div align=\"center\">\n\n[![GitHub](https://img.shields.io/badge/GitHub-repo-blue?style=flat-square&logo=github)](https://github.com/HMCL-dev/HMCL)\n[![CNB](https://img.shields.io/badge/CNB-mirror-ff6200?style=flat-square&logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)\n[![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?style=flat-square&logo=gitee)](https://gitee.com/huanghongxun/HMCL)\n\n[![QQ Group](https://img.shields.io/badge/QQ-gray?style=flat-square&logo=qq&logoColor=ffffff)](https://docs.hmcl.net/groups.html)\n[![Discord](https://img.shields.io/badge/Discord-gray?style=flat-square&logo=discord)](https://discord.gg/jVvC7HfM6U)\n[![Bilibili](https://img.shields.io/badge/Bilibili-gray?style=flat-square&logo=bilibili)](https://space.bilibili.com/20314891)\n\n</div>\n<!-- #END COPY -->\n\n---\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\nEnglish ([Standard](README.md), [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | **日本語** | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)\n<!-- #END LANGUAGE_SWITCHER -->\n\n## 紹介\n\nHMCLはオープンソースでクロスプラットフォーム対応のMinecraftランチャーです。Mod管理、ゲームカスタマイズ、Modローダーのインストール（Forge、NeoForge、Cleanroom、Fabric、Legacy Fabric、Quilt、LiteLoader、OptiFine）、Modパック作成、UIカスタマイズなど、さまざまな機能をサポートしています。\n\nHMCLは優れたクロスプラットフォーム性能を持っています。Windows、Linux、macOS、FreeBSDなどの異なるオペレーティングシステムだけでなく、x86、ARM、RISC-V、MIPS、LoongArchなどのさまざまなCPUアーキテクチャにも対応しています。HMCLを使えば、さまざまなプラットフォームでMinecraftを簡単に楽しむことができます。\n\nHMCLが対応しているシステムやCPUアーキテクチャについては、[この表](PLATFORM.md)をご参照ください。\n\n## ダウンロード\n\n最新版は[公式サイト](https://hmcl.huangyuhui.net/download)からダウンロードできます。\n\nまた、[GitHub Releases](https://github.com/HMCL-dev/HMCL/releases)でも最新版を入手できます。\n\n必要ではありませんが、上記の公式サイトからのみリリース版をダウンロードすることを推奨します。\n\n## ライセンス\n\nライセンスについては [README.md](README.md#license) をご参照ください。\n"
  },
  {
    "path": "docs/README_lzh.md",
    "content": "<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=TITLE -->\n<div align=\"center\">\n    <img src=\"/HMCL/src/main/resources/assets/img/icon@8x.png\" alt=\"HMCL Logo\" width=\"64\"/>\n</div>\n\n<h1 align=\"center\">Hello Minecraft! Launcher</h1>\n<!-- #END COPY -->\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=BADGES -->\n<div align=\"center\">\n\n[![GitHub](https://img.shields.io/badge/GitHub-repo-blue?style=flat-square&logo=github)](https://github.com/HMCL-dev/HMCL)\n[![CNB](https://img.shields.io/badge/CNB-mirror-ff6200?style=flat-square&logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)\n[![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?style=flat-square&logo=gitee)](https://gitee.com/huanghongxun/HMCL)\n\n[![QQ Group](https://img.shields.io/badge/QQ-gray?style=flat-square&logo=qq&logoColor=ffffff)](https://docs.hmcl.net/groups.html)\n[![Discord](https://img.shields.io/badge/Discord-gray?style=flat-square&logo=discord)](https://discord.gg/jVvC7HfM6U)\n[![Bilibili](https://img.shields.io/badge/Bilibili-gray?style=flat-square&logo=bilibili)](https://space.bilibili.com/20314891)\n\n</div>\n<!-- #END COPY -->\n\n---\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\nEnglish ([Standard](README.md), [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | **中文** ([简体](README_zh.md), [繁體](README_zh_Hant.md), **文言**) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)\n<!-- #END LANGUAGE_SWITCHER -->\n\n### 概敘\n\nHMCL 者，開源之礦藝啟器也。善理改囊，可自定遊戲，自動裝設 Forge、NeoForge、Cleanroom、Fabric、Legacy Fabric、Quilt、LiteLoader、OptiFine 諸改囊，亦能制改囊集，界面隨心易之。\n\nHMCL 跨域廣矣。Windows、Linux、macOS、FreeBSD 諸常見械綱，x86、ARM、RISC-V、MIPS、LoongArch 諸算器架構，皆可運行。\n憑此器，可遊《礦藝》於諸機之間，無礙也。  \n\n欲知 HMCL 所支平臺，詳觀[此表](PLATFORM_zh_Hant.md)。\n\n## 下載\n\n可從下述諸途得 HMCL：\n\n- [HMCL 官方網站](https://hmcl.huangyuhui.net/download)\n- [GitHub Release](https://github.com/HMCL-dev/HMCL/releases)\n- [CNB Release](https://cnb.cool/HMCL-dev/HMCL/-/releases)\n\n## 與貢\n\nHMCL 乃社群共驅之開源項目，迎諸君獻碼或建言。\n\n參與之道如下：\n\n- 於 GitHub 上[創建 Issue](https://github.com/HMCL-dev/HMCL/issues/new/choose)，以報謬誤或求功能。\n- 於 GitHub 上 Fork 倉庫，並[提交 Pull Request](https://github.com/HMCL-dev/HMCL/compare)，以貢代碼。\n\n貢獻之前，請閱[貢獻指南](./Contributing_zh_Hant.md)，中含：\n\n- [如何自源碼構建並執行 HMCL](./Contributing_zh_Hant.md#構建-hmcl)\n- [以勘誤選項調整 HMCL 之行為](./Contributing_zh_Hant.md#除錯選項)\n\n## 貢獻者\n\n自乙未年（2015）始，百二十余人參與 HMCL，謝其勤勞！\n\n[![Contributors](https://contrib.rocks/image?repo=HMCL-dev/HMCL)](https://github.com/HMCL-dev/HMCL/graphs/contributors)\n\n## 開源之約\n\n詳見 [README_zh_Hant.md](README_zh_Hant.md#開源協議)。\n"
  },
  {
    "path": "docs/README_ru.md",
    "content": "<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=TITLE -->\n<div align=\"center\">\n    <img src=\"/HMCL/src/main/resources/assets/img/icon@8x.png\" alt=\"HMCL Logo\" width=\"64\"/>\n</div>\n\n<h1 align=\"center\">Hello Minecraft! Launcher</h1>\n<!-- #END COPY -->\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=BADGES -->\n<div align=\"center\">\n\n[![GitHub](https://img.shields.io/badge/GitHub-repo-blue?style=flat-square&logo=github)](https://github.com/HMCL-dev/HMCL)\n[![CNB](https://img.shields.io/badge/CNB-mirror-ff6200?style=flat-square&logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)\n[![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?style=flat-square&logo=gitee)](https://gitee.com/huanghongxun/HMCL)\n\n[![QQ Group](https://img.shields.io/badge/QQ-gray?style=flat-square&logo=qq&logoColor=ffffff)](https://docs.hmcl.net/groups.html)\n[![Discord](https://img.shields.io/badge/Discord-gray?style=flat-square&logo=discord)](https://discord.gg/jVvC7HfM6U)\n[![Bilibili](https://img.shields.io/badge/Bilibili-gray?style=flat-square&logo=bilibili)](https://space.bilibili.com/20314891)\n\n</div>\n<!-- #END COPY -->\n\n---\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\nEnglish ([Standard](README.md), [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | **русский** | [українська](README_uk.md)\n<!-- #END LANGUAGE_SWITCHER -->\n\n## Введение\n\nHMCL — это открытый, кроссплатформенный лаунчер для Minecraft с поддержкой управления модами, настройки игры, установки загрузчиков модов (Forge, NeoForge, Cleanroom, Fabric, Legacy Fabric, Quilt, LiteLoader и OptiFine), создания модпаков, настройки интерфейса и многого другого.\n\nHMCL обладает отличной кроссплатформенностью. Он работает не только на различных операционных системах, таких как Windows, Linux, macOS и FreeBSD, но и поддерживает различные архитектуры процессоров: x86, ARM, RISC-V, MIPS и LoongArch. Благодаря HMCL вы можете легко наслаждаться Minecraft на разных платформах.\n\nСписок поддерживаемых систем и архитектур процессоров HMCL смотрите в [этой таблице](PLATFORM.md).\n\n## Загрузка\n\nСкачайте последнюю версию с [официального сайта](https://hmcl.huangyuhui.net/download).\n\nТакже вы можете найти последнюю версию HMCL в [релизах на GitHub](https://github.com/HMCL-dev/HMCL/releases).\n\nХотя это не обязательно, рекомендуется скачивать релизы только с указанных выше официальных сайтов.\n\n## Лицензия\n\nСм. [README.md](README.md#license).\n"
  },
  {
    "path": "docs/README_uk.md",
    "content": "<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=TITLE -->\n<div align=\"center\">\n    <img src=\"/HMCL/src/main/resources/assets/img/icon@8x.png\" alt=\"HMCL Logo\" width=\"64\"/>\n</div>\n\n<h1 align=\"center\">Hello Minecraft! Launcher</h1>\n<!-- #END COPY -->\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=BADGES -->\n<div align=\"center\">\n\n[![GitHub](https://img.shields.io/badge/GitHub-repo-blue?style=flat-square&logo=github)](https://github.com/HMCL-dev/HMCL)\n[![CNB](https://img.shields.io/badge/CNB-mirror-ff6200?style=flat-square&logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)\n[![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?style=flat-square&logo=gitee)](https://gitee.com/huanghongxun/HMCL)\n\n[![QQ Group](https://img.shields.io/badge/QQ-gray?style=flat-square&logo=qq&logoColor=ffffff)](https://docs.hmcl.net/groups.html)\n[![Discord](https://img.shields.io/badge/Discord-gray?style=flat-square&logo=discord)](https://discord.gg/jVvC7HfM6U)\n[![Bilibili](https://img.shields.io/badge/Bilibili-gray?style=flat-square&logo=bilibili)](https://space.bilibili.com/20314891)\n\n</div>\n<!-- #END COPY -->\n\n---\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\nEnglish ([Standard](README.md), [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | **українська**\n<!-- #END LANGUAGE_SWITCHER -->\n\n## Вступ\n\nHMCL — це відкритий, кросплатформний лаунчер для Minecraft, який підтримує керування модами, налаштування гри, встановлення модлоадерів (Forge, NeoForge, Cleanroom, Fabric, Legacy Fabric, Quilt, LiteLoader та OptiFine), створення модпаків, налаштування інтерфейсу та багато іншого.\n\nHMCL має чудові кросплатформні можливості. Він працює не лише на різних операційних системах, таких як Windows, Linux, macOS і FreeBSD, а й підтримує різні архітектури процесорів, такі як x86, ARM, RISC-V, MIPS і LoongArch. Ви можете легко насолоджуватися Minecraft на різних платформах за допомогою HMCL.\n\nЩодо підтримуваних систем і архітектур процесорів дивіться [цю таблицю](PLATFORM.md).\n\n## Завантаження\n\nЗавантажте останню версію з [офіційного сайту](https://hmcl.huangyuhui.net/download).\n\nТакож ви можете знайти останню версію HMCL у [релізах GitHub](https://github.com/HMCL-dev/HMCL/releases).\n\nХоча це не обовʼязково, рекомендується завантажувати релізи лише з офіційних сайтів, зазначених вище.\n\n## Ліцензія\n\nДивіться [README.md](README.md#license).\n\n## Внесок\n\nЯкщо ви хочете надіслати pull request, ознайомтеся з наступними вимогами:\n\n* IDE: IntelliJ IDEA\n* Компілятор: Java 17+\n"
  },
  {
    "path": "docs/README_zh.md",
    "content": "<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=TITLE -->\n<div align=\"center\">\n    <img src=\"/HMCL/src/main/resources/assets/img/icon@8x.png\" alt=\"HMCL Logo\" width=\"64\"/>\n</div>\n\n<h1 align=\"center\">Hello Minecraft! Launcher</h1>\n<!-- #END COPY -->\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=BADGES -->\n<div align=\"center\">\n\n[![GitHub](https://img.shields.io/badge/GitHub-repo-blue?style=flat-square&logo=github)](https://github.com/HMCL-dev/HMCL)\n[![CNB](https://img.shields.io/badge/CNB-mirror-ff6200?style=flat-square&logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)\n[![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?style=flat-square&logo=gitee)](https://gitee.com/huanghongxun/HMCL)\n\n[![QQ Group](https://img.shields.io/badge/QQ-gray?style=flat-square&logo=qq&logoColor=ffffff)](https://docs.hmcl.net/groups.html)\n[![Discord](https://img.shields.io/badge/Discord-gray?style=flat-square&logo=discord)](https://discord.gg/jVvC7HfM6U)\n[![Bilibili](https://img.shields.io/badge/Bilibili-gray?style=flat-square&logo=bilibili)](https://space.bilibili.com/20314891)\n\n</div>\n<!-- #END COPY -->\n\n---\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\nEnglish ([Standard](README.md), [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | **中文** (**简体**, [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)\n<!-- #END LANGUAGE_SWITCHER -->\n\n## 简介\n\nHMCL 是一款开源、跨平台的 Minecraft 启动器，支持模组管理、游戏自定义、游戏自动安装 (Forge、NeoForge、Cleanroom、Fabric、Legacy Fabric、Quilt、LiteLoader 和 OptiFine)、整合包创建、界面自定义等功能。\n\nHMCL 有着强大的跨平台能力。它不仅支持 Windows、Linux、macOS、FreeBSD 等常见的操作系统，同时也支持 x86、ARM、RISC-V、MIPS、LoongArch 等不同的 CPU 架构。你可以使用 HMCL 在不同平台上轻松地游玩 Minecraft。\n\n如果你想要了解 HMCL 对不同平台的支持程度，请参见 [此表格](PLATFORM_zh.md)。\n\n## 下载\n\n你可以从这些渠道下载 HMCL：\n\n- [HMCL 官方网站](https://hmcl.huangyuhui.net/download)\n- [GitHub Release](https://github.com/HMCL-dev/HMCL/releases)\n- [CNB Release](https://cnb.cool/HMCL-dev/HMCL/-/releases)\n\n## 参与贡献\n\nHMCL 是一个社区驱动的开源项目，欢迎任何人参与贡献代码或提出建议。\n\n你可以通过以下方式参与 HMCL 的开发：\n\n- 通过在 GitHub 上[创建 Issue](https://github.com/HMCL-dev/HMCL/issues/new/choose) 来报告 Bug 或提出功能请求。\n- 通过在 GitHub 上 Fork 仓库并[提交 Pull Request](https://github.com/HMCL-dev/HMCL/compare) 来贡献代码。\n\n在参与贡献前，请阅读[贡献指南](./Contributing_zh.md)，其中包含以下内容：\n\n- [如何从源码构建并运行 HMCL](./Contributing_zh.md#构建-hmcl)\n- [通过调试选项调整 HMCL 的行为](./Contributing_zh.md#调试选项)\n\n## 贡献者\n\n自 2015 年以来，HMCL 已经有超过 120 位贡献者参与其中，感谢他们的辛勤付出！\n\n[![Contributors](https://contrib.rocks/image?repo=HMCL-dev/HMCL)](https://github.com/HMCL-dev/HMCL/graphs/contributors)\n\n## 开源协议\n\n该程序在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 开源协议下发布，同时附有以下附加条款。\n\n### 附加条款 (依据 GPLv3 开源协议第七条)\n\n1. 当你分发该程序的修改版本时，你必须以一种合理的方式修改该程序的名称或版本号，以示其与原始版本不同。(\n   依据 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374))\n\n   该程序的名称及版本号可在 [此处](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35) 修改。\n\n2. 你不得移除该程序所显示的版权声明。(依据 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370))\n"
  },
  {
    "path": "docs/README_zh_Hant.md",
    "content": "<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=TITLE -->\n<div align=\"center\">\n    <img src=\"/HMCL/src/main/resources/assets/img/icon@8x.png\" alt=\"HMCL Logo\" width=\"64\"/>\n</div>\n\n<h1 align=\"center\">Hello Minecraft! Launcher</h1>\n<!-- #END COPY -->\n\n<!-- #BEGIN COPY -->\n<!-- #PROPERTY NAME=BADGES -->\n<div align=\"center\">\n\n[![GitHub](https://img.shields.io/badge/GitHub-repo-blue?style=flat-square&logo=github)](https://github.com/HMCL-dev/HMCL)\n[![CNB](https://img.shields.io/badge/CNB-mirror-ff6200?style=flat-square&logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)\n[![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?style=flat-square&logo=gitee)](https://gitee.com/huanghongxun/HMCL)\n\n[![QQ Group](https://img.shields.io/badge/QQ-gray?style=flat-square&logo=qq&logoColor=ffffff)](https://docs.hmcl.net/groups.html)\n[![Discord](https://img.shields.io/badge/Discord-gray?style=flat-square&logo=discord)](https://discord.gg/jVvC7HfM6U)\n[![Bilibili](https://img.shields.io/badge/Bilibili-gray?style=flat-square&logo=bilibili)](https://space.bilibili.com/20314891)\n\n</div>\n<!-- #END COPY -->\n\n---\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\nEnglish ([Standard](README.md), [uʍoᗡ ǝpᴉsd∩](README_en_Qabs.md)) | **中文** ([简体](README_zh.md), **繁體**, [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)\n<!-- #END LANGUAGE_SWITCHER -->\n\n## 簡介\n\nHMCL 是一款開源、跨平臺的 Minecraft 啟動器，支援模組管理、遊戲客製化、遊戲自動安裝 (Forge、NeoForge、Cleanroom、Fabric、Legacy Fabric、Quilt、LiteLoader 和 OptiFine)、模組包建立、介面客製化等功能。\n\nHMCL 有著強大的跨平臺能力。它不僅支援 Windows、Linux、macOS、FreeBSD 等常見的作業系統，同時也支援 x86、ARM、RISC-V、MIPS、LoongArch 等不同的 CPU 架構。你可以使用 HMCL 在不同平臺上輕鬆地遊玩 Minecraft。\n\n如果你想要了解 HMCL 對不同平臺的支援程度，請參見 [此表格](PLATFORM_zh_Hant.md)。\n\n## 下載\n\n你可以透過這些管道下載 HMCL：\n\n- [HMCL 官方網站](https://hmcl.huangyuhui.net/download)\n- [GitHub Release](https://github.com/HMCL-dev/HMCL/releases)\n- [CNB Release](https://cnb.cool/HMCL-dev/HMCL/-/releases)\n\n## 參與貢獻\n\nHMCL 是一個由社群驅動的開源專案，歡迎任何人參與貢獻程式碼或提出建議。\n\n你可以透過以下方式參與 HMCL 的開發：\n\n- 透過在 GitHub 上[建立 Issue](https://github.com/HMCL-dev/HMCL/issues/new/choose) 來回報 Bug 或提出功能請求。\n- 透過在 GitHub 上 Fork 倉庫並[提交 Pull Request](https://github.com/HMCL-dev/HMCL/compare) 來貢獻程式碼。\n\n在參與貢獻前，請閱讀[貢獻指南](./Contributing_zh_Hant.md)，其中包含以下內容：\n\n- [如何從原始碼構建並開啟 HMCL](./Contributing_zh_Hant.md#構建-hmcl)\n- [透過除錯選項調整 HMCL 的行為](./Contributing_zh_Hant.md#除錯選項)\n\n## 貢獻者\n\n自 2015 年以來，HMCL 已經有超過 120 位貢獻者參與其中，感謝他們的辛勤付出！\n\n[![Contributors](https://contrib.rocks/image?repo=HMCL-dev/HMCL)](https://github.com/HMCL-dev/HMCL/graphs/contributors)\n\n## 開源協議\n\n該程式在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 開源協議下發布，同時附有以下附加條款。\n\n### 附加條款 (依據 GPLv3 開源協議第七條)\n\n1. 當你分發該程式的修改版本時，你必須以一種合理的方式修改該程式的名稱或版本號，以示其與原始版本不同。(依據 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374))\n\n   該程式的名稱及版本號可在 [此處](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35) 修改。\n\n2. 你不得移除該程式所顯示的版權宣告。(依據 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370))\n"
  },
  {
    "path": "docs/ReleaseSchedule.md",
    "content": "# Hello Minecraft! Launcher Release Schedule\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n**English** | 中文 ([简体](ReleaseSchedule_zh.md), [繁體](ReleaseSchedule_zh_Hant.md))\n<!-- #END LANGUAGE_SWITCHER -->\n\nThis document describes the HMCL version release schedule starting from October 2025.\n\n## Versioning Rules\n\n### Version Branches\n\nHMCL has multiple **version branches**, each named in the format `<major>.<minor>` (e.g., `3.7`).\n\nThe major version is incremented only when there are significant architectural changes in HMCL,\nwhile the minor version is incremented regularly according to the release schedule.\n\n### Version Types\n\nHMCL has two types of versions: **Stable** and **Development**.\n\n#### Stable Version\n\nThe HMCL stable version is suitable for users who prioritize software stability.\nNew features are merged into the stable version only after thorough testing.\n\nThe stable version number follows the naming rule `<version branch>.<build number>` (e.g., `3.7.1`).\nThe build number is calculated independently for each version branch.\n\n#### Development Version\n\nThe HMCL development version is suitable for users who want to experience new features first.\nThe development version includes the latest features and bug fixes,\nbut may also have more issues due to insufficient testing.\n\nThe development version follows the naming rule `<version branch>.0.<build number>` (e.g., `3.7.0.300`).\nThe build number is shared globally across all version branches.\n\n## Release Channels\n\nHMCL has two main release channels: **Stable Channel** and **Development Channel**.\nThey are used to release the HMCL stable and development versions, respectively.\n\nTo test HMCL versions before official release, we will push updates to some users in advance.\nUsers can enable the \"Preview HMCL releases early\" option on the \"Settings > General\" page to receive preview updates from the corresponding channel.\n\n## Release Model\n\n```mermaid\ngantt\n    title HMCL Version Lifecycle (Example)\n    section 3.9\n        Development Phase: a1, 2025-11-15, 30d\n        Preview Phase: a2, after a1, 16d\n        Stable Release: milestone, after a2, 0d\n        Maintenance Phase: a3, after a2, 31d\n    section 3.10\n        Development Phase: b1, after a1, 31d\n        Preview Phase: b2, after b1, 16d\n        Stable Release: milestone, after b2, 0d\n        Maintenance Phase: b3, after b2, 31d\n    section 3.11\n        Development Phase: c1, after b1, 31d\n        Preview Phase: c2, after c1, 16d\n        Stable Release: milestone, after c2, 0d\n        Maintenance Phase: c3, after c2, 30d\n```\n\nIn general, we release a new version branch every month,\nand each version branch `x.y` has a lifecycle of two and a half months.\n\nThe lifecycle of each version branch is divided into the following stages:\n\n1. **Development Phase** (Mid-month M ~ Mid-month M+1)\n\n   During this phase, the version is developed in the Git branch `main`.\n\n   Typically, we release a development version `x.y.0.<build number>` based on this branch every week,\n   which includes all features and bug fixes merged during the week.\n\n2. **Preview Phase** (Mid-month M+1 ~ End of month M+1)\n\n   In mid-month M+1, we fork the `main` branch to create the `release/x.y` branch,\n   which corresponds to the fixed version branch `x.y`.\n\n   At the same time, the version branch corresponding to the `main` branch is incremented to `x.y♯`,\n   entering the development phase of the next version branch.\n\n   After the fork, no new features will be added to this version branch;\n   only bug fixes and data updates will be made.\n\n   Within a few days, we will release a stable preview version `x.y.1` and\n   push it to users in the stable channel who have enabled preview updates.\n\n   Before the end of month M+1, the stable version corresponding to version branch `x.y` will remain in preview status.\n   If issues are found during the preview, we will release and push new stable preview versions (such as `x.y.2`, `x.y.3`, etc.) after fixing them.\n\n3. **Stable Release** (End of month M+1)\n\n   At the end of month M+1, if there are no unexpected issues, we will promote the latest stable preview `x.y.z` to the official release and push it to all users in the stable channel.\n\n4. **Maintenance Phase** (Month M+2)\n\n   In month M+2, version branch `x.y` enters the maintenance phase.\n   We will release stable updates irregularly based on the number and severity of issues fixed,\n   and push them to all users in the stable channel.\n\n   At the end of month M+2, after the official release of the stable version corresponding to version branch `x.y♯`, the lifecycle of version branch `x.y` ends, the Git branch `release/x.y` is\n   archived, and no longer receives updates.\n\n## Long-Term Support Version Branches\n\nSome special version branches are selected as Long-Term Support (LTS) branches.\nTheir lifecycle does not end with the official release of the next version branch's stable version.\nWe will continue to backport necessary patches to these branches for a longer period.\n\nList of Long-Term Support Version Branches:\n\n| Version Branch | Official Release Date | End of Lifecycle | Current Support Status | Notes                                                                                                                                |\n|----------------|:---------------------:|:----------------:|:----------------------:|:-------------------------------------------------------------------------------------------------------------------------------------|\n| 3.6            |   November 23, 2024   |       TBD        |       Supported        | This is the last version branch that supports running on Java 8.<br>It is suitable for users on legacy platforms such as Windows XP. |\n"
  },
  {
    "path": "docs/ReleaseSchedule_zh.md",
    "content": "# Hello Minecraft! Launcher 发布计划\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n[English](ReleaseSchedule.md) | **中文** (**简体**, [繁體](ReleaseSchedule_zh_Hant.md))\n<!-- #END LANGUAGE_SWITCHER -->\n\n本文介绍了自 2025 年 10 月起的 HMCL 版本发布计划。\n\n## 版本规则\n\n### 版本分支\n\nHMCL 存在多个**版本分支**，每个版本分支都以 `<主版本号>.<次版本号>` 的形式命名 (例如 `3.7`)。\n\n其中，主版本号仅在 HMCL 发生重大架构变化时递增，次版本号会根据发布计划定时递增。\n\n### 版本类型\n\nHMCL 具有两种版本类型: **稳定版**和**开发版**。\n\n#### 稳定版\n\nHMCL 稳定版适合优先追求软件稳定性的用户使用。新功能在经过充分测试后才会被合并到稳定版中。\n\nHMCL 稳定版版本号遵循 `<版本分支>.<构建号>` 的命名规则 (例如 `3.7.1`)。其中构建号对每个版本分支独立计算。\n\n#### 开发版\n\nHMCL 开发版适合希望优先体验新功能的用户使用。开发版会包含最新功能和 BUG 修复，但因为未经过充分测试，也可能会存在更多问题。\n\nHMCL 开发版遵循 `<版本分支>.0.<构建号>` 的命名规则 (例如 `3.7.0.300`)。其中构建号对所有版本分支全局共享。\n\n## 发布通道\n\nHMCL 具有两个主要发布通道：**稳定版通道**和**开发版通道**。\n它们分别用于发布 HMCL 稳定版和开发版。\n\n为了在正式发布前测试 HMCL 版本，我们会向部分用户优先推送更新。\n用户可以在「设置 > 通用」页面中打开「提前预览 HMCL 版本」选项以接收到对应通道的预览更新。\n\n## 发布模型\n\n```mermaid\ngantt\n    title HMCL 版本生命周期 (示例)\n    section 3.9\n        开发阶段: a1, 2025-11-15, 30d\n        预览阶段: a2, after a1, 16d\n        稳定版发布: milestone, after a2, 0d\n        维护阶段: a3, after a2, 31d\n    section 3.10\n        开发阶段: b1, after a1, 31d\n        预览阶段: b2, after b1, 16d\n        稳定版发布: milestone, after b2, 0d\n        维护阶段: b3, after b2, 31d\n    section 3.11\n        开发阶段: c1, after b1, 31d\n        预览阶段: c2, after c1, 16d\n        稳定版发布: milestone, after c2, 0d\n        维护阶段: c3, after c2, 30d\n```\n\n通常情况下，我们每个月发布一个版本分支，每个版本分支 `x.y` 生命周期为两个半月。\n\n每个版本分支生命周期分为以下阶段:\n\n1. **开发阶段** (M 月中旬 ~ M+1 月中旬)\n\n   在此阶段，该版本在 Git 分支 `main` 中进行开发。\n\n   通常情况下，我们每周都会基于该分支发布一个开发版 `x.y.0.<构建号>`，其中包含当周合并的所有功能和错误修复。\n\n2. **预览阶段** (M+1 月中旬 ~ M+1 月底)\n\n   在 M+1 月中旬，我们会基于 Git 分支 `main` 分叉出分支 `release/x.y`，其对应的版本分支固定为 `x.y`。\n\n   与此同时，Git 分支 `main` 对应的版本分支递增至 `x.y♯`，进入下一个版本分支的开发版阶段。\n\n   在完成分叉后，该版本分支不会新增更多功能，只会进行错误修复和数据更新。\n\n   我们会在数日内发布稳定版预览 `x.y.1`，并将其推送给稳定版通道且接受预览更新的用户。\n\n   在 M+1 月底前，版本分支 `x.y` 对应的稳定版将一直保持预览状态。\n   若在预览过程中发现问题，我们会在修复后发布并推送新稳定版预览 (如 `x.y.2`、`x.y.3` 等)。\n\n3. **稳定版发布** (M+1 月底)\n\n   在 M+1 月底，如果没有意外情况，我们会将最新的稳定版预览 `x.y.z` 提升为正式版本，推送给稳定版通道的全部用户。\n\n4. **维护阶段** (M+2 月)\n\n   在 M+2 月，版本分支 `x.y` 进入维护阶段。我们会根据修复的问题数量和严重程度不定期地发布稳定版更新，并推送给稳定版通道的全部用户。\n\n   在 M+2 月底，版本分支 `x.y♯` 对应的稳定版正式发布后，版本分支 `x.y` 生命周期结束，Git 分支 `release/x.y` 被存档，不再接受更新。\n\n## 长期支持版本分支\n\n部分特殊版本分支会被我们选择为长期支持 (LTS) 版本分支，\n其生命周期不会因为下一个版本分支对应的稳定版正式发布而结束。\n我们会在更长的时间中继续为这些分支移植必要的补丁。\n\n长期支持版本分支列表：\n\n| 版本分支 |      正式发布日期      | 生命周期结束日期 | 当前受支持状态 | 备注                                                       |\n|------|:----------------:|:--------:|:-------:|:---------------------------------------------------------|\n| 3.6  | 2024 年 11 月 23 日 |    未定    |   支持中   | 这是最后一个支持使用 Java 8 运行的版本分支，<br>该版本分支适用于 Windows XP 等旧平台用户 |\n"
  },
  {
    "path": "docs/ReleaseSchedule_zh_Hant.md",
    "content": "# Hello Minecraft! Launcher 發布計劃\n\n<!-- #BEGIN LANGUAGE_SWITCHER -->\n[English](ReleaseSchedule.md) | **中文** ([简体](ReleaseSchedule_zh.md), **繁體**)\n<!-- #END LANGUAGE_SWITCHER -->\n\n本文介紹了自 2025 年 10 月起的 HMCL 版本發布計劃。\n\n## 版本規則\n\n### 版本分支\n\nHMCL 存在多個**版本分支**，每個版本分支都以 `<主版本號>.<次版本號>` 的形式命名 (例如 `3.7`)。\n\n其中，主版本號僅在 HMCL 發生重大架構變化時遞增，次版本號會根據發布計劃定時遞增。\n\n### 版本類型\n\nHMCL 具有兩種版本類型: **穩定版**和**開發版**。\n\n#### 穩定版\n\nHMCL 穩定版適合優先追求軟體穩定性的使用者使用。新功能在經過充分測試後才會被合併到穩定版中。\n\nHMCL 穩定版版本號遵循 `<版本分支>.<構建號>` 的命名規則 (例如 `3.7.1`)。其中構建號對每個版本分支獨立計算。\n\n#### 開發版\n\nHMCL 開發版適合希望優先體驗新功能的使用者使用。開發版會包含最新功能和 Bug 修復，但因為未經過充分測試，也可能會存在更多問題。\n\nHMCL 開發版遵循 `<版本分支>.0.<構建號>` 的命名規則 (例如 `3.7.0.300`)。其中構建號對所有版本分支全域共享。\n\n## 發布管道\n\nHMCL 具有兩個主要發布管道：**穩定版管道**和**開發版管道**。\n它們分別用於發布 HMCL 穩定版和開發版。\n\n為了在正式發布前測試 HMCL 版本，我們會向部分使用者優先推送更新。\n使用者可以在「設定 > 一般」頁面中開啟「提前測試 HMCL 預覽版本」選項以接收到對應管道的預覽更新。\n\n## 發布模型\n\n```mermaid\ngantt\n    title HMCL 版本生命週期 (範例)\n    section 3.9\n        開發階段: a1, 2025-11-15, 30d\n        預覽階段: a2, after a1, 16d\n        穩定版發布: milestone, after a2, 0d\n        維護階段: a3, after a2, 31d\n    section 3.10\n        開發階段: b1, after a1, 31d\n        預覽階段: b2, after b1, 16d\n        穩定版發布: milestone, after b2, 0d\n        維護階段: b3, after b2, 31d\n    section 3.11\n        開發階段: c1, after b1, 31d\n        預覽階段: c2, after c1, 16d\n        穩定版發布: milestone, after c2, 0d\n        維護階段: c3, after c2, 30d\n```\n\n通常情況下，我們每個月發布一個版本分支，每個版本分支 `x.y` 生命週期為兩個半月。\n\n每個版本分支生命週期分為以下階段:\n\n1. **開發階段** (M 月中旬 ~ M+1 月中旬)\n\n   在此階段，該版本在 Git 分支 `main` 中進行開發。\n\n   通常情況下，我們每週都會基於該分支發布一個開發版 `x.y.0.<構建號>`，其中包含當週合併的所有功能和錯誤修復。\n\n2. **預覽階段** (M+1 月中旬 ~ M+1 月底)\n\n   在 M+1 月中旬，我們會基於 Git 分支 `main` 分叉出分支 `release/x.y`，其對應的版本分支固定為 `x.y`。\n\n   與此同時，Git 分支 `main` 對應的版本分支遞增至 `x.y♯`，進入下一個版本分支的開發版階段。\n\n   在完成分叉後，該版本分支不會新增更多功能，只會進行錯誤修復和資料更新。\n\n   我們會在數日內發布穩定版預覽 `x.y.1`，並將其推送給穩定版且接受預覽更新的使用者。\n\n   在 M+1 月底前，版本分支 `x.y` 對應的穩定版將一直保持預覽狀態。\n   若在預覽過程中發現問題，我們會在修復後發布並推送新穩定版預覽 (如 `x.y.2`、`x.y.3` 等)。\n\n3. **穩定版發布** (M+1 月底)\n\n   在 M+1 月底，如果沒有意外情況，我們會將最新的穩定版預覽 `x.y.z` 標記為正式版本，推送給穩定版管道的全部使用者。\n\n4. **維護階段** (M+2 月)\n\n   在 M+2 月，版本分支 `x.y` 進入維護階段。我們會根據修復的問題數量和嚴重程度不定期地發布穩定版更新，並推送給穩定版管道的全部使用者。\n\n   在 M+2 月底，版本分支 `x.y♯` 對應的穩定版正式發布後，版本分支 `x.y` 生命週期結束，Git 分支 `release/x.y` 被存檔，不再接受更新。\n\n## 長期支援版本分支\n\n部分特殊版本分支會被我們選取為長期支援 (LTS) 版本分支，\n其生命週期不會因為下一個版本分支對應的穩定版正式發布而結束。\n我們會在更長的時間中繼續為這些分支移植必要的修補程式。\n\n長期支援版本分支列表：\n\n| 版本分支 |      正式發布日期      | 生命週期結束日期 | 目前受支援狀態 | 備註                                                       |\n|------|:----------------:|:--------:|:-------:|:---------------------------------------------------------|\n| 3.6  | 2024 年 11 月 23 日 |    未定    |   支援中   | 這是最後一個支援使用 Java 8 執行的版本分支，<br>該版本分支適用於 Windows XP 等舊平台使用者 |\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nhmclauncher = \"3.7.0.1\"\njetbrains-annotations = \"26.1.0\"\nkala-compress = \"1.27.1-1\"\nsimple-png-javafx = \"0.3.0\"\ngson = \"2.13.2\"\ntoml4j = \"0.7.2\"\nxz = \"1.11\"\nfx-gson = \"5.0.0\"\nconstant-pool-scanner = \"1.2\"\nopennbt = \"1.5\"\nnanohttpd = \"2.3.1\"\njsoup = \"1.21.2\"\nchardet = \"2.5.0\"\ntwelvemonkeys = \"3.13.0\"\njna = \"5.18.1\"\npci-ids = \"0.4.0\"\njava-info = \"1.0\"\nauthlib-injector = \"1.2.7\"\nmonet-fx = \"0.4.0\"\nterracotta = \"0.4.2\"\nnayuki-qrcodegen = \"1.8.0\"\n\n# testing\njunit = \"6.0.1\"\njimfs = \"1.3.0\"\n\n# plugins\nshadow = \"9.3.1\"\n\n[libraries]\nhmclauncher = { module = \"org.glavo.hmcl:HMCLauncher\", version.ref = \"hmclauncher\" }\njetbrains-annotations = { module = \"org.jetbrains:annotations\", version.ref = \"jetbrains-annotations\" }\nkala-compress-zip = { module = \"org.glavo.kala:kala-compress-archivers-zip\", version.ref = \"kala-compress\" }\nkala-compress-tar = { module = \"org.glavo.kala:kala-compress-archivers-tar\", version.ref = \"kala-compress\" }\nsimple-png-javafx = { module = \"org.glavo:simple-png-javafx\", version.ref = \"simple-png-javafx\" }\ngson = { module = \"com.google.code.gson:gson\", version.ref = \"gson\" }\ntoml = { module = \"com.moandjiezana.toml:toml4j\", version.ref = \"toml4j\" }\nxz = { module = \"org.tukaani:xz\", version.ref = \"xz\" }\nfx-gson = { module = \"org.hildan.fxgson:fx-gson\", version.ref = \"fx-gson\" }\nconstant-pool-scanner = { module = \"org.jenkins-ci:constant-pool-scanner\", version.ref = \"constant-pool-scanner\" }\nopennbt = { module = \"com.github.steveice10:opennbt\", version.ref = \"opennbt\" }\nnanohttpd = { module = \"org.nanohttpd:nanohttpd\", version.ref = \"nanohttpd\" }\njsoup = { module = \"org.jsoup:jsoup\", version.ref = \"jsoup\" }\nchardet = { module = \"org.glavo:chardet\", version.ref = \"chardet\" }\ntwelvemonkeys-imageio-webp = { module = \"com.twelvemonkeys.imageio:imageio-webp\", version.ref = \"twelvemonkeys\" }\njna = { module = \"net.java.dev.jna:jna\", version.ref = \"jna\" }\njna-platform = { module = \"net.java.dev.jna:jna-platform\", version.ref = \"jna\" }\npci-ids = { module = \"org.glavo:pci-ids\", version.ref = \"pci-ids\" }\njava-info = { module = \"org.glavo:java-info\", version.ref = \"java-info\" }\nauthlib-injector = { module = \"org.glavo.hmcl:authlib-injector\", version.ref = \"authlib-injector\" }\nmonet-fx = { module = \"org.glavo:MonetFX\", version.ref = \"monet-fx\" }\nnayuki-qrcodegen = { module = \"io.nayuki:qrcodegen\", version.ref = \"nayuki-qrcodegen\" }\n\n# testing\njunit-jupiter = { module = \"org.junit.jupiter:junit-jupiter\", version.ref = \"junit\" }\njimfs = { module = \"com.google.jimfs:jimfs\", version.ref = \"jimfs\" }\n\n[plugins]\nshadow = { id = \"com.gradleup.shadow\", version.ref = \"shadow\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.4.0-bin.zip\nnetworkTimeout=120000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts",
    "content": "version = \"1.0\"\n\ntasks.compileJava {\n    sourceCompatibility = \"1.8\"\n    targetCompatibility = \"1.8\"\n}\n\ntasks.jar {\n    manifest {\n        attributes(\n            \"Created-By\" to \"Copyright(c) 2013-2025 huangyuhui.\",\n            \"Implementation-Version\" to project.version\n        )\n    }\n}\n"
  },
  {
    "path": "minecraft/libraries/HMCLMultiMCBootstrap/src/main/java/org/jackhuang/hmcl/HMCLMultiMCBootstrap.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\nimport java.io.UnsupportedEncodingException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.net.URI;\nimport java.net.URLDecoder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Scanner;\n\npublic final class HMCLMultiMCBootstrap {\n    private HMCLMultiMCBootstrap() {\n    }\n\n    public static void main(String[] args) throws Throwable {\n        String profile = System.getProperty(\"hmcl.mmc.bootstrap\");\n        if (profile == null) {\n            launchLegacy(args);\n            return;\n        }\n\n        URI uri = URI.create(profile);\n        if (Objects.equals(uri.getPath(), \"/bootstrap_profile_v1/\")) {\n            launchV1(parseQuery(uri.getRawQuery()), args);\n        }\n    }\n\n    private static void launchV1(Map<String, String> arguments, String[] args) throws Throwable {\n        String mainClass = arguments.get(\"main_class\");\n        String installerInfo = arguments.get(\"installer\");\n\n        launch(installerInfo, mainClass, args);\n    }\n\n    private static void launchLegacy(String[] args) throws Throwable {\n        String mainClass = new String(Base64.getUrlDecoder().decode(System.getProperty(\"hmcl.mmc.bootstrap.main\")), StandardCharsets.UTF_8);\n        String installerInfo = new String(Base64.getUrlDecoder().decode(System.getProperty(\"hmcl.mmc.bootstrap.installer\")), StandardCharsets.UTF_8);\n\n        launch(installerInfo, mainClass, args);\n    }\n\n    private static void launch(String installerInfo, String mainClass, String[] args) throws Throwable {\n        System.out.println(\"This version is installed by HMCLCore's MultiMC combat layer.\");\n        System.out.println(\"Installer Properties:\");\n        System.out.println(installerInfo);\n        System.out.println(\"Main Class: \" + mainClass);\n        System.out.println(\"GAME MAY CRASH DUE TO BUGS. TEST YOUR GAME ON OFFICIAL MMC BEFORE REPORTING BUGS TO AUTHORS.\");\n\n        Method[] methods = Class.forName(mainClass).getMethods();\n        for (Method method : methods) {\n            // https://docs.oracle.com/javase/specs/jls/se21/html/jls-12.html#jls-12.1.4\n            if (\"main\".equals(method.getName()) &&\n                    Modifier.isStatic(method.getModifiers()) &&\n                    method.getReturnType() == void.class &&\n                    method.getParameterCount() == 1 &&\n                    method.getParameters()[0].getType() == String[].class\n            ) {\n                method.invoke(null, (Object) args);\n                return;\n            }\n        }\n\n        throw new IllegalArgumentException(\"Cannot find method 'main(String[])' in \" + mainClass);\n    }\n\n    private static Map<String, String> parseQuery(String queryParameterString) {\n        if (queryParameterString == null) return Collections.emptyMap();\n\n        Map<String, String> result = new HashMap<>();\n\n        try (Scanner scanner = new Scanner(queryParameterString)) {\n            scanner.useDelimiter(\"&\");\n            while (scanner.hasNext()) {\n                String[] nameValue = scanner.next().split(\"=\");\n                if (nameValue.length == 0 || nameValue.length > 2) {\n                    throw new IllegalArgumentException(\"bad query string\");\n                }\n\n                String name = decodeURL(nameValue[0]);\n                String value = nameValue.length == 2 ? decodeURL(nameValue[1]) : null;\n                result.put(name, value);\n            }\n        }\n        return result;\n    }\n\n    private static String decodeURL(String value) {\n        try {\n            return URLDecoder.decode(value, \"UTF-8\");\n        } catch (UnsupportedEncodingException e) {\n            throw new AssertionError(e);\n        }\n    }\n}\n"
  },
  {
    "path": "minecraft/libraries/HMCLTransformerDiscoveryService/build.gradle.kts",
    "content": "version = \"1.0\"\n\ndependencies {\n    compileOnly(project.files(\"lib/modlauncher-4.1.0.jar\"))\n}\n\ntasks.compileJava {\n    sourceCompatibility = \"1.8\"\n    targetCompatibility = \"1.8\"\n}\n\ntasks.jar {\n    manifest {\n        attributes(\n            \"Created-By\" to \"Copyright(c) 2013-2020 huangyuhui.\",\n            \"Implementation-Version\" to project.version\n        )\n    }\n}\n"
  },
  {
    "path": "minecraft/libraries/HMCLTransformerDiscoveryService/src/main/java/org/jackhuang/hmcl/HMCLTransformerDiscoveryService.java",
    "content": "/*\n * Hello Minecraft! Launcher\n * Copyright (C) 2020  huangyuhui <huanghongxun2008@126.com> and contributors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <https://www.gnu.org/licenses/>.\n */\npackage org.jackhuang.hmcl;\n\nimport cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;\n\nimport java.io.File;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\npublic class HMCLTransformerDiscoveryService implements ITransformerDiscoveryService {\n    private static final String CANDIDATES = System.getProperty(\"hmcl.transformer.candidates\");\n\n    @Override\n    public List<Path> candidates(Path gameDirectory) {\n        return Arrays.stream(CANDIDATES.split(File.pathSeparator))\n                .flatMap(path -> {\n                    try {\n                        return Stream.of(Paths.get(path));\n                    } catch (InvalidPathException e) {\n                        return Stream.of();\n                    }\n                }).collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "minecraft/libraries/HMCLTransformerDiscoveryService/src/main/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService",
    "content": "org.jackhuang.hmcl.HMCLTransformerDiscoveryService\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "rootProject.name = \"HMCL3\"\ninclude(\n    \"HMCL\",\n    \"HMCLCore\",\n    \"HMCLBoot\"\n)\n\nval minecraftLibraries = listOf(\"HMCLTransformerDiscoveryService\", \"HMCLMultiMCBootstrap\")\ninclude(minecraftLibraries)\n\nfor (library in minecraftLibraries) {\n    project(\":$library\").projectDir = file(\"minecraft/libraries/$library\")\n}\n"
  }
]